diff --git a/.github/workflows/build-linux-installer-deb.yml b/.github/workflows/build-linux-installer-deb.yml index 226f6e9d722b..5eadae0065ac 100644 --- a/.github/workflows/build-linux-installer-deb.yml +++ b/.github/workflows/build-linux-installer-deb.yml @@ -202,25 +202,21 @@ jobs: publish: name: Publish ${{ matrix.os.arch }} - runs-on: ${{ matrix.os.runs-on }} + runs-on: ubuntu-latest needs: - build - container: chianetwork/ubuntu-20.04-builder:latest timeout-minutes: ${{ matrix.os.timeout }} strategy: fail-fast: false matrix: python-version: ["3.10"] os: - - runs-on: ubuntu-latest - arch: amd64 + - arch: amd64 glue-name: "build-amd64-deb" timeout: 5 - - runs-on: [Linux, ARM64] - arch: arm64 + - arch: arm64 glue-name: "build-arm64-deb" - # TOOD: some self hosted runners are exhibiting slow artifact downloads sometimes - timeout: 20 + timeout: 5 env: CHIA_INSTALLER_VERSION: ${{ needs.build.outputs.chia-installer-version }} @@ -228,6 +224,10 @@ jobs: 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 @@ -289,7 +289,6 @@ jobs: 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 - if: env.FULL_RELEASE == 'true' || github.ref == 'refs/heads/main' 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 @@ -377,6 +376,10 @@ jobs: type: ubuntu # https://packages.ubuntu.com/jammy/python3 (22.04, 3.10) url: "docker://ubuntu:jammy" + - name: ubuntu:noble (24.04) + type: ubuntu + # https://packages.ubuntu.com/noble/python3 (24.04, 3.12) + url: "docker://ubuntu:noble" mode: - name: GUI file: chia-blockchain_*.deb diff --git a/.github/workflows/build-linux-installer-rpm.yml b/.github/workflows/build-linux-installer-rpm.yml index b57475ba0b34..c8ed3c75b5fc 100644 --- a/.github/workflows/build-linux-installer-rpm.yml +++ b/.github/workflows/build-linux-installer-rpm.yml @@ -207,8 +207,6 @@ jobs: runs-on: ubuntu-latest needs: - build - container: - image: chianetwork/rocky8-builder:latest timeout-minutes: 5 strategy: fail-fast: false @@ -221,6 +219,10 @@ jobs: 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 @@ -283,7 +285,6 @@ jobs: 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 - if: env.FULL_RELEASE == 'true' || github.ref == 'refs/heads/main' 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 diff --git a/.github/workflows/build-macos-installers.yml b/.github/workflows/build-macos-installers.yml index 7fee724b194d..d3aa6503c7fb 100644 --- a/.github/workflows/build-macos-installers.yml +++ b/.github/workflows/build-macos-installers.yml @@ -245,7 +245,7 @@ jobs: publish: name: Publish ${{ matrix.os.name }} DMG - runs-on: ${{ matrix.os.runs-on }} + runs-on: ubuntu-latest needs: - build timeout-minutes: 5 @@ -254,12 +254,10 @@ jobs: matrix: python-version: ["3.10"] os: - - runs-on: macos-11 - name: intel + - name: intel file-suffix: "" glue-name: "build-macos" - - runs-on: [MacOS, ARM64] - name: m1 + - name: m1 file-suffix: "-arm64" glue-name: "build-mac-m1" @@ -318,19 +316,10 @@ jobs: AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" - - name: Install AWS CLI - if: steps.check_secrets.outputs.HAS_AWS_SECRET - run: | - command -v aws || brew install awscli - - - name: Install GH CLI - run: | - command -v gh || brew install gh - - name: Create Checksums run: | ls - shasum -a 256 ${{ 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 + shasum -a 256 ${{ 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 @@ -345,29 +334,29 @@ jobs: 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 s3://download.chia.net/dev/Chia-${CHIA_DEV_BUILD}${{ matrix.os.file-suffix }}.dmg - 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 + 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 + 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 + 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/ - 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/ - 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/ + aws s3 cp ${{ github.workspace }}/build_scripts/final_installer/chia-${{ env.CHIA_INSTALLER_VERSION }}${{ matrix.os.file-suffix }}.dmg s3://download.chia.net/install/ + 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/ + 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/ - name: Upload release artifacts if: env.RELEASE == 'true' @@ -414,6 +403,10 @@ jobs: matrix: 13 runs-on: intel: macos-13 + - name: 14 + matrix: 14 + runs-on: + arm: macos-14 arch: - name: ARM64 matrix: arm diff --git a/.github/workflows/build-windows-installer.yml b/.github/workflows/build-windows-installer.yml index 507dae7458fd..c63e0b0b1c42 100644 --- a/.github/workflows/build-windows-installer.yml +++ b/.github/workflows/build-windows-installer.yml @@ -260,10 +260,7 @@ jobs: publish: name: Publish EXE - runs-on: [windows-2019] - defaults: - run: - shell: bash + runs-on: ubuntu-latest needs: - build timeout-minutes: 5 @@ -326,12 +323,6 @@ jobs: AWS_SECRET: "${{ secrets.CHIA_AWS_ACCOUNT_ID }}" GLUE_API_URL: "${{ secrets.GLUE_API_URL }}" - - name: Install AWS CLI - if: steps.check_secrets.outputs.HAS_AWS_SECRET - shell: pwsh - run: | - msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi - - name: Configure AWS credentials if: steps.check_secrets.outputs.HAS_AWS_SECRET uses: aws-actions/configure-aws-credentials@v4 @@ -350,10 +341,10 @@ jobs: 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 - shell: pwsh run: | - certutil.exe -hashfile ${{ github.workspace }}/chia-blockchain-gui/release-builds/windows-installer/ChiaSetup-${{ env.CHIA_INSTALLER_VERSION }}.exe SHA256 > ${{ 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/ + 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' diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 689d455f644e..7beff6db7876 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -30,7 +30,7 @@ jobs: - name: macOS matrix: macos runs-on: - intel: macos-latest + intel: macos-12 arm: [macos, arm64] - name: Windows matrix: windows diff --git a/.github/workflows/super-linter.yml b/.github/workflows/super-linter.yml index f40b8635d773..cd5d1ffd163d 100644 --- a/.github/workflows/super-linter.yml +++ b/.github/workflows/super-linter.yml @@ -52,12 +52,14 @@ jobs: ########################## - name: Checkout Code uses: actions/checkout@v4 + with: + fetch-depth: 0 ################################ # Run Linter against code base # ################################ - name: Lint Code Base - uses: github/super-linter@v5 + uses: github/super-linter@v6 # uses: docker://github/super-linter:v3.10.2 env: VALIDATE_ALL_CODEBASE: true @@ -73,7 +75,6 @@ jobs: VALIDATE_JSON: true VALIDATE_MD: true VALIDATE_POWERSHELL: true - VALIDATE_SHELL_SHFMT: true VALIDATE_TYPESCRIPT_ES: true VALIDATE_YAML: true DISABLE_ERRORS: false diff --git a/.github/workflows/test-install-scripts.yml b/.github/workflows/test-install-scripts.yml index 2f0ca60b7e20..8d87ae554c80 100644 --- a/.github/workflows/test-install-scripts.yml +++ b/.github/workflows/test-install-scripts.yml @@ -32,7 +32,9 @@ jobs: - major-dot-minor: "3.10" os: - runs-on: macos-latest - matrix: macos + matrix: macos-arm + - runs-on: macos-12 + matrix: macos-intel - runs-on: ubuntu-latest matrix: linux - runs-on: windows-latest @@ -126,6 +128,10 @@ jobs: type: ubuntu # https://packages.ubuntu.com/jammy/python3 (22.04, 3.10) url: "docker://ubuntu:jammy" + - name: ubuntu:noble (24.04) + type: ubuntu + # https://packages.ubuntu.com/noble/python3 (24.04, 3.12) + url: "docker://ubuntu:noble" arch: - name: ARM64 matrix: arm @@ -151,7 +157,7 @@ jobs: # The behavior we follow in install.sh is unique with Arch in that # we leave it to the user to install the appropriate version of python, # so we need to install python here in order for the test to succeed. - pacman --noconfirm -U --needed https://archive.archlinux.org/packages/p/python/python-3.9.9-1-x86_64.pkg.tar.zst + pacman --noconfirm -U --needed https://archive.archlinux.org/packages/p/python/python-3.11.8-1-x86_64.pkg.tar.zst - name: Prepare Debian if: ${{ matrix.distribution.type == 'debian' }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a1f3b07ebbc..90a8b140c077 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -92,7 +92,17 @@ jobs: python chia/_tests/build-job-matrix.py --per directory --verbose ${{ inputs.only && format('--only {0}', inputs.only) || '' }} ${{ inputs.duplicates > 1 && format('--duplicates {0}', inputs.duplicates) || '' }} ${{ inputs.build-job-matrix-arguments }} > matrix.json cat matrix.json echo configuration=$(cat matrix.json) >> "$GITHUB_OUTPUT" - echo matrix_mode=${{ ( github.event_name == 'schedule' || inputs.full-python-matrix ) && 'all' || ( github.repository_owner == 'Chia-Network' && github.repository != 'Chia-Network/chia-blockchain' ) && 'limited' || ( github.repository_owner == 'Chia-Network' && github.repository == 'Chia-Network/chia-blockchain' && github.ref == 'refs/heads/main' ) && 'main' || ( github.repository_owner == 'Chia-Network' && github.repository == 'Chia-Network/chia-blockchain' && startsWith(github.ref, 'refs/heads/release/') ) && 'all' || ( github.repository_owner == 'Chia-Network' && github.repository == 'Chia-Network/chia-blockchain' && startsWith(github.base_ref, 'release/') ) && 'all' || 'main' }} >> "$GITHUB_OUTPUT" + echo matrix_mode=${{ + ( github.repository_owner == 'Chia-Network' && github.repository != 'Chia-Network/chia-blockchain' ) + && 'limited' + || ( + ( github.event_name == 'schedule' || inputs.full-python-matrix ) + || ( github.repository_owner == 'Chia-Network' && github.repository == 'Chia-Network/chia-blockchain' && startsWith(github.ref, 'refs/heads/release/') ) + || ( github.repository_owner == 'Chia-Network' && github.repository == 'Chia-Network/chia-blockchain' && startsWith(github.base_ref, 'release/') ) + ) + && 'all' + || 'main' + }} >> "$GITHUB_OUTPUT" outputs: configuration: ${{ steps.configure.outputs.configuration }} diff --git a/.github/workflows/upload-pypi-source.yml b/.github/workflows/upload-pypi-source.yml index 87349441bc78..956fe8882679 100644 --- a/.github/workflows/upload-pypi-source.yml +++ b/.github/workflows/upload-pypi-source.yml @@ -44,7 +44,7 @@ jobs: matrix: macos emoji: 🍎 runs-on: - intel: macos-latest + intel: macos-12 arm: [macos, arm64] - name: Windows matrix: windows diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43a6446a5f07..58b2d57bf898 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,6 +33,11 @@ repos: hooks: - id: prettier types_or: [ini, json, toml, yaml] + - repo: https://github.com/scop/pre-commit-shfmt + rev: v3.8.0-1 + hooks: + - id: shfmt + args: ["--diff", "--write", "-i", "2"] - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 hooks: diff --git a/.repo-content-updater.yml b/.repo-content-updater.yml new file mode 100644 index 000000000000..dbb0e6b105e5 --- /dev/null +++ b/.repo-content-updater.yml @@ -0,0 +1,2 @@ +var_overrides: + DEPENDABOT_ACTIONS_REVIEWERS: '["cmmarslender", "altendky"]' diff --git a/CHANGELOG.md b/CHANGELOG.md index 563a7ec3c336..7f55ca680e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,94 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project does not yet adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) for setuptools_scm/PEP 440 reasons. +## 2.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` + +### 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. + ## 2.2.1 Chia blockchain 2024-03-4 ### Fixed diff --git a/activated.sh b/activated.sh index 283f61325d1d..f57ba21154ec 100755 --- a/activated.sh +++ b/activated.sh @@ -2,7 +2,10 @@ set -o errexit -SCRIPT_DIRECTORY=$(cd -- "$(dirname -- "$0")"; pwd) +SCRIPT_DIRECTORY=$( + cd -- "$(dirname -- "$0")" + pwd +) # shellcheck disable=SC1091 . "${SCRIPT_DIRECTORY}/venv/bin/activate" diff --git a/build_scripts/build_license_directory.sh b/build_scripts/build_license_directory.sh index 7650696ad56e..0272307a67eb 100644 --- a/build_scripts/build_license_directory.sh +++ b/build_scripts/build_license_directory.sh @@ -13,22 +13,21 @@ printf "%s\n" "$sum" license_list=$(license-checker --json | jq -r '.[].licenseFile' | grep -v null) # Split the license list by newline character into an array -IFS=$'\n' read -rd '' -a licenses_array <<< "$license_list" +IFS=$'\n' read -rd '' -a licenses_array <<<"$license_list" #print the contents of the array printf '%s\n' "${licenses_array[@]}" for i in "${licenses_array[@]}"; do - dirname="licenses/$(dirname "$i" | awk -F'/' '{print $NF}')" - mkdir -p "$dirname" - echo "$dirname" - cp "$i" "$dirname" + dirname="licenses/$(dirname "$i" | awk -F'/' '{print $NF}')" + mkdir -p "$dirname" + echo "$dirname" + cp "$i" "$dirname" done mv licenses/ ../build_scripts/dist/daemon cd ../build_scripts || exit 1 - # PULL IN THE LICENSES FROM PIP-LICENSE pip install pip-licenses || pip3 install pip-licenses @@ -40,8 +39,8 @@ license_path_array=() # read the output line by line into the array while IFS= read -r line; do - license_path_array+=("$line") -done <<< "$output" + license_path_array+=("$line") +done <<<"$output" # create a dir for each license and copy the license file over for i in "${license_path_array[@]}"; do diff --git a/build_scripts/build_linux_deb-1-gui.sh b/build_scripts/build_linux_deb-1-gui.sh index 86eb15ceaae6..0dff0604e39d 100644 --- a/build_scripts/build_linux_deb-1-gui.sh +++ b/build_scripts/build_linux_deb-1-gui.sh @@ -19,8 +19,8 @@ npm ci npm run build LAST_EXIT_CODE=$? if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "npm run build failed!" - exit $LAST_EXIT_CODE + echo >&2 "npm run build failed!" + exit $LAST_EXIT_CODE fi # Remove unused packages @@ -40,8 +40,8 @@ rm -rf packages/wallets cd ./packages/gui/node_modules || exit 1 echo "Remove unused node_modules in the gui package to make cache slim more" rm -rf electron/dist # ~186MB -rm -rf "@mui" # ~71MB -rm -rf typescript # ~63MB +rm -rf "@mui" # ~71MB +rm -rf typescript # ~63MB # Remove `packages/gui/node_modules/@chia-network` because it causes an error on later `electron-packager` command rm -rf "@chia-network" diff --git a/build_scripts/build_linux_deb-2-installer.sh b/build_scripts/build_linux_deb-2-installer.sh index d97567354415..3b29efb5c60e 100644 --- a/build_scripts/build_linux_deb-2-installer.sh +++ b/build_scripts/build_linux_deb-2-installer.sh @@ -4,11 +4,11 @@ set -o errexit if [ ! "$1" ]; then echo "This script requires either amd64 of arm64 as an argument" - exit 1 + exit 1 elif [ "$1" = "amd64" ]; then - PLATFORM="amd64" + PLATFORM="amd64" else - PLATFORM="arm64" + PLATFORM="arm64" fi export PLATFORM @@ -19,8 +19,8 @@ git submodule # set, this will attempt to Notarize the signed DMG if [ ! "$CHIA_INSTALLER_VERSION" ]; then - echo "WARNING: No environment variable CHIA_INSTALLER_VERSION set. Using 0.0.0." - CHIA_INSTALLER_VERSION="0.0.0" + echo "WARNING: No environment variable CHIA_INSTALLER_VERSION set. Using 0.0.0." + CHIA_INSTALLER_VERSION="0.0.0" fi echo "Chia Installer Version is: $CHIA_INSTALLER_VERSION" export CHIA_INSTALLER_VERSION @@ -39,8 +39,8 @@ SPEC_FILE=$(python -c 'import sys; from pathlib import Path; path = Path(sys.arg pyinstaller --log-level=INFO "$SPEC_FILE" LAST_EXIT_CODE=$? if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "pyinstaller failed!" - exit $LAST_EXIT_CODE + echo >&2 "pyinstaller failed!" + exit $LAST_EXIT_CODE fi # Creates a directory of licenses @@ -48,7 +48,6 @@ echo "Building pip and NPM license directory" pwd bash ./build_license_directory.sh - # Builds CLI only .deb # need j2 for templating the control file format_deb_version_string() { @@ -64,7 +63,8 @@ mkdir -p "dist/$CLI_DEB_BASE/opt/chia" mkdir -p "dist/$CLI_DEB_BASE/usr/bin" mkdir -p "dist/$CLI_DEB_BASE/DEBIAN" mkdir -p "dist/$CLI_DEB_BASE/etc/systemd/system" -CHIA_DEB_CONTROL_VERSION=$(format_deb_version_string "$CHIA_INSTALLER_VERSION"); export CHIA_DEB_CONTROL_VERSION +CHIA_DEB_CONTROL_VERSION=$(format_deb_version_string "$CHIA_INSTALLER_VERSION") +export CHIA_DEB_CONTROL_VERSION j2 -o "dist/$CLI_DEB_BASE/DEBIAN/control" assets/deb/control.j2 cp assets/systemd/*.service "dist/$CLI_DEB_BASE/etc/systemd/system/" cp -r dist/daemon/* "dist/$CLI_DEB_BASE/opt/chia/" @@ -80,7 +80,7 @@ cd ../chia-blockchain-gui/packages/gui || exit 1 # sets the version for chia-blockchain in package.json cp package.json package.json.orig -jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json > temp.json && mv temp.json package.json +jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json >temp.json && mv temp.json package.json echo "Building Linux(deb) Electron app" PRODUCT_NAME="chia" @@ -126,8 +126,8 @@ ls -l dist/linux*-unpacked/resources mv package.json.orig package.json if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "electron-builder failed!" - exit $LAST_EXIT_CODE + echo >&2 "electron-builder failed!" + exit $LAST_EXIT_CODE fi GUI_DEB_NAME=chia-blockchain_${CHIA_INSTALLER_VERSION}_${PLATFORM}.deb diff --git a/build_scripts/build_linux_rpm-1-gui.sh b/build_scripts/build_linux_rpm-1-gui.sh index 7e309c9d5c85..e993dc0cd45f 100644 --- a/build_scripts/build_linux_rpm-1-gui.sh +++ b/build_scripts/build_linux_rpm-1-gui.sh @@ -18,8 +18,8 @@ npm ci npm run build LAST_EXIT_CODE=$? if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "npm run build failed!" - exit $LAST_EXIT_CODE + echo >&2 "npm run build failed!" + exit $LAST_EXIT_CODE fi # Remove unused packages @@ -39,8 +39,8 @@ rm -rf packages/wallets cd ./packages/gui/node_modules || exit 1 echo "Remove unused node_modules in the gui package to make cache slim more" rm -rf electron/dist # ~186MB -rm -rf "@mui" # ~71MB -rm -rf typescript # ~63MB +rm -rf "@mui" # ~71MB +rm -rf typescript # ~63MB # Remove `packages/gui/node_modules/@chia-network` because it causes an error on later `electron-packager` command rm -rf "@chia-network" diff --git a/build_scripts/build_linux_rpm-2-installer.sh b/build_scripts/build_linux_rpm-2-installer.sh index 670ad88317c3..e869347b1267 100644 --- a/build_scripts/build_linux_rpm-2-installer.sh +++ b/build_scripts/build_linux_rpm-2-installer.sh @@ -7,19 +7,19 @@ git submodule if [ ! "$1" ]; then echo "This script requires either amd64 of arm64 as an argument" - exit 1 + exit 1 elif [ "$1" = "amd64" ]; then - export REDHAT_PLATFORM="x86_64" + export REDHAT_PLATFORM="x86_64" else - export REDHAT_PLATFORM="arm64" + export REDHAT_PLATFORM="arm64" fi # If the env variable NOTARIZE and the username and password variables are # set, this will attempt to Notarize the signed DMG if [ ! "$CHIA_INSTALLER_VERSION" ]; then - echo "WARNING: No environment variable CHIA_INSTALLER_VERSION set. Using 0.0.0." - CHIA_INSTALLER_VERSION="0.0.0" + echo "WARNING: No environment variable CHIA_INSTALLER_VERSION set. Using 0.0.0." + CHIA_INSTALLER_VERSION="0.0.0" fi echo "Chia Installer Version is: $CHIA_INSTALLER_VERSION" @@ -37,8 +37,8 @@ SPEC_FILE=$(python -c 'import sys; from pathlib import Path; path = Path(sys.arg pyinstaller --log-level=INFO "$SPEC_FILE" LAST_EXIT_CODE=$? if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "pyinstaller failed!" - exit $LAST_EXIT_CODE + echo >&2 "pyinstaller failed!" + exit $LAST_EXIT_CODE fi # Creates a directory of licenses @@ -90,10 +90,10 @@ cd ../chia-blockchain-gui/packages/gui || exit 1 # sets the version for chia-blockchain in package.json cp package.json package.json.orig -jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json > temp.json && mv temp.json package.json +jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json >temp.json && mv temp.json package.json export FPM_EDITOR="cat >../../../build_scripts/dist/gui.spec <" -jq '.rpm.fpm |= . + ["--edit"]' ../../../build_scripts/electron-builder.json > temp.json && mv temp.json ../../../build_scripts/electron-builder.json +jq '.rpm.fpm |= . + ["--edit"]' ../../../build_scripts/electron-builder.json >temp.json && mv temp.json ../../../build_scripts/electron-builder.json echo "Building Linux(rpm) Electron app" OPT_ARCH="--x64" @@ -118,8 +118,8 @@ ls -l dist/linux*-unpacked/resources mv package.json.orig package.json if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "electron-builder failed!" - exit $LAST_EXIT_CODE + echo >&2 "electron-builder failed!" + exit $LAST_EXIT_CODE fi GUI_RPM_NAME="chia-blockchain-${CHIA_INSTALLER_VERSION}-1.${REDHAT_PLATFORM}.rpm" diff --git a/build_scripts/build_macos-1-gui.sh b/build_scripts/build_macos-1-gui.sh index 90cde25a47ef..9512873a40ea 100644 --- a/build_scripts/build_macos-1-gui.sh +++ b/build_scripts/build_macos-1-gui.sh @@ -19,8 +19,8 @@ npm ci npm run build LAST_EXIT_CODE=$? if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "npm run build failed!" - exit $LAST_EXIT_CODE + echo >&2 "npm run build failed!" + exit $LAST_EXIT_CODE fi # Remove unused packages @@ -40,8 +40,8 @@ rm -rf packages/wallets cd ./packages/gui/node_modules || exit 1 echo "Remove unused node_modules in the gui package to make cache slim more" rm -rf electron/dist # ~186MB -rm -rf "@mui" # ~71MB -rm -rf typescript # ~63MB +rm -rf "@mui" # ~71MB +rm -rf typescript # ~63MB # Remove `packages/gui/node_modules/@chia-network` because it causes an error on later `electron-packager` command rm -rf "@chia-network" diff --git a/build_scripts/build_macos-2-installer.sh b/build_scripts/build_macos-2-installer.sh index a9f2126445f6..b91b12995d36 100644 --- a/build_scripts/build_macos-2-installer.sh +++ b/build_scripts/build_macos-2-installer.sh @@ -9,8 +9,8 @@ git submodule # set, this will attempt to Notarize the signed DMG. if [ ! "$CHIA_INSTALLER_VERSION" ]; then - echo "WARNING: No environment variable CHIA_INSTALLER_VERSION set. Using 0.0.0." - CHIA_INSTALLER_VERSION="0.0.0" + echo "WARNING: No environment variable CHIA_INSTALLER_VERSION set. Using 0.0.0." + CHIA_INSTALLER_VERSION="0.0.0" fi echo "Chia Installer Version is: $CHIA_INSTALLER_VERSION" @@ -28,11 +28,10 @@ SPEC_FILE=$(python -c 'import sys; from pathlib import Path; path = Path(sys.arg pyinstaller --log-level=INFO "$SPEC_FILE" LAST_EXIT_CODE=$? if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "pyinstaller failed!" - exit $LAST_EXIT_CODE + echo >&2 "pyinstaller failed!" + exit $LAST_EXIT_CODE fi - # Creates a directory of licenses echo "Building pip and NPM license directory" pwd @@ -45,7 +44,7 @@ cd ../chia-blockchain-gui/packages/gui || exit 1 # sets the version for chia-blockchain in package.json brew install jq cp package.json package.json.orig -jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json > temp.json && mv temp.json package.json +jq --arg VER "$CHIA_INSTALLER_VERSION" '.version=$VER' package.json >temp.json && mv temp.json package.json echo "Building macOS Electron app" OPT_ARCH="--x64" @@ -54,14 +53,14 @@ if [ "$(arch)" = "arm64" ]; then fi PRODUCT_NAME="Chia" if [ "$NOTARIZE" == true ]; then - echo "Setting credentials for signing" - export CSC_LINK=$APPLE_DEV_ID_APP - export CSC_KEY_PASSWORD=$APPLE_DEV_ID_APP_PASS - export PUBLISH_FOR_PULL_REQUEST=true - export CSC_FOR_PULL_REQUEST=true + echo "Setting credentials for signing" + export CSC_LINK=$APPLE_DEV_ID_APP + export CSC_KEY_PASSWORD=$APPLE_DEV_ID_APP_PASS + export PUBLISH_FOR_PULL_REQUEST=true + export CSC_FOR_PULL_REQUEST=true else - echo "Not on ci or no secrets so not signing" - export CSC_IDENTITY_AUTO_DISCOVERY=false + echo "Not on ci or no secrets so not signing" + export CSC_IDENTITY_AUTO_DISCOVERY=false fi echo npx electron-builder build --mac "${OPT_ARCH}" --config.productName="$PRODUCT_NAME" --config.mac.minimumSystemVersion="11" --config ../../../build_scripts/electron-builder.json npx electron-builder build --mac "${OPT_ARCH}" --config.productName="$PRODUCT_NAME" --config.mac.minimumSystemVersion="11" --config ../../../build_scripts/electron-builder.json @@ -72,8 +71,8 @@ ls -l dist/mac*/chia.app/Contents/Resources/app.asar mv package.json.orig package.json if [ "$LAST_EXIT_CODE" -ne 0 ]; then - echo >&2 "electron-builder failed!" - exit $LAST_EXIT_CODE + echo >&2 "electron-builder failed!" + exit $LAST_EXIT_CODE fi mv dist/* ../../../build_scripts/dist/ @@ -90,13 +89,13 @@ mv dist/"$DMG_NAME" final_installer/ ls -lh final_installer if [ "$NOTARIZE" == true ]; then - echo "Notarize $DMG_NAME on ci" - cd final_installer || exit 1 + echo "Notarize $DMG_NAME on ci" + cd final_installer || exit 1 xcrun notarytool submit --wait --apple-id "$APPLE_NOTARIZE_USERNAME" --password "$APPLE_NOTARIZE_PASSWORD" --team-id "$APPLE_TEAM_ID" "$DMG_NAME" xcrun stapler staple "$DMG_NAME" echo "Notarization step complete" else - echo "Not on ci or no secrets so skipping Notarize" + echo "Not on ci or no secrets so skipping Notarize" fi # Notes on how to manually notarize diff --git a/build_scripts/build_win_license_dir.sh b/build_scripts/build_win_license_dir.sh index 1c59d4ac42fe..091b7dbae1b0 100644 --- a/build_scripts/build_win_license_dir.sh +++ b/build_scripts/build_win_license_dir.sh @@ -13,7 +13,7 @@ printf "%s\n" "$sum" license_list=$(license-checker --json | jq -r '.[].licenseFile' | grep -v null) # Split the license list by newline character into an array -IFS=$'\n' read -rd '' -a licenses_array <<< "$license_list" +IFS=$'\n' read -rd '' -a licenses_array <<<"$license_list" #print the contents of the array printf '%s\n' "${licenses_array[@]}" @@ -39,8 +39,8 @@ license_path_array=() # read the output line by line into the array while IFS= read -r line; do - license_path_array+=("$line") -done <<< "$output" + license_path_array+=("$line") +done <<<"$output" # create a dir for each license and copy the license file over for i in "${license_path_array[@]}"; do diff --git a/build_scripts/check_dependency_artifacts.py b/build_scripts/check_dependency_artifacts.py index 7386a1c0cefe..f3cb71f7e52b 100644 --- a/build_scripts/check_dependency_artifacts.py +++ b/build_scripts/check_dependency_artifacts.py @@ -7,8 +7,12 @@ import sys import tempfile +# TODO: publish wheels for these excepted_packages = { "dnslib", # pure python + "chialisp_loader", + "chialisp_puzzles", + "chia_base", } diff --git a/build_scripts/clean-runner.sh b/build_scripts/clean-runner.sh index 6aba280b147e..432c8f828889 100644 --- a/build_scripts/clean-runner.sh +++ b/build_scripts/clean-runner.sh @@ -15,7 +15,7 @@ rm -rf chia-blockchain-gui/build || true rm -rf chia-blockchain-gui/daemon || true rm -rf chia-blockchain-gui/node_modules || true rm chia-blockchain-gui/temp.json || true -( cd "$PWD/chia-blockchain-gui" && git checkout HEAD -- package-lock.json ) || true +(cd "$PWD/chia-blockchain-gui" && git checkout HEAD -- package-lock.json) || true cd "$PWD" || true # Clean up old globally installed node_modules that might conflict with the current build diff --git a/build_scripts/npm_linux/package-lock.json b/build_scripts/npm_linux/package-lock.json index c2beb25067b8..8b1952380fd8 100644 --- a/build_scripts/npm_linux/package-lock.json +++ b/build_scripts/npm_linux/package-lock.json @@ -2639,9 +2639,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { "jake": "^10.8.5" }, @@ -9657,9 +9657,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "requires": { "jake": "^10.8.5" } diff --git a/build_scripts/npm_macos/package-lock.json b/build_scripts/npm_macos/package-lock.json index 297ff8d5cde4..a8998cc4ee07 100644 --- a/build_scripts/npm_macos/package-lock.json +++ b/build_scripts/npm_macos/package-lock.json @@ -2626,9 +2626,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { "jake": "^10.8.5" }, @@ -10218,9 +10218,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "ejs": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", - "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "requires": { "jake": "^10.8.5" } diff --git a/build_scripts/npm_windows/package-lock.json b/build_scripts/npm_windows/package-lock.json index 14e2f4e23b5a..4a2bbbbbaee7 100644 --- a/build_scripts/npm_windows/package-lock.json +++ b/build_scripts/npm_windows/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "electron-builder": "^24.6.3", + "electron-builder": "^24.13.3", "lerna": "^7.1.3" } }, @@ -2569,9 +2569,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "node_modules/ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dependencies": { "jake": "^10.8.5" }, @@ -9460,9 +9460,9 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, "ejs": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", - "integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "requires": { "jake": "^10.8.5" } diff --git a/chia-blockchain-gui b/chia-blockchain-gui index 5b608f0c3e5d..69287d77e005 160000 --- a/chia-blockchain-gui +++ b/chia-blockchain-gui @@ -1 +1 @@ -Subproject commit 5b608f0c3e5dba197fe456b4579036674c4af97c +Subproject commit 69287d77e0058bb882f1b8dd4120dff1f8e224ff diff --git a/chia/_tests/blockchain/test_blockchain.py b/chia/_tests/blockchain/test_blockchain.py index e6dfb77e6a08..1d501d50485d 100644 --- a/chia/_tests/blockchain/test_blockchain.py +++ b/chia/_tests/blockchain/test_blockchain.py @@ -8,7 +8,7 @@ from typing import List, Optional import pytest -from chia_rs import AugSchemeMPL, G2Element +from chia_rs import AugSchemeMPL, G2Element, MerkleSet from clvm.casts import int_to_bytes from chia._tests.blockchain.blockchain_test_utils import ( @@ -53,7 +53,6 @@ from chia.util.generator_tools import get_block_header from chia.util.hash import std_hash from chia.util.ints import uint8, uint32, uint64 -from chia.util.merkle_set import MerkleSet from chia.util.misc import available_logical_cores from chia.util.recursive_replace import recursive_replace from chia.util.vdf_prover import get_vdf_info_and_proof diff --git a/chia/_tests/conftest.py b/chia/_tests/conftest.py index dfa8f536edce..477a694e7d4f 100644 --- a/chia/_tests/conftest.py +++ b/chia/_tests/conftest.py @@ -264,7 +264,7 @@ def db_version(request) -> int: return request.param -SOFTFORK_HEIGHTS = [1000000, 5496000, 5496100, 5650000] +SOFTFORK_HEIGHTS = [1000000, 5496000, 5496100, 5716000] @pytest.fixture(scope="function", params=SOFTFORK_HEIGHTS) diff --git a/chia/_tests/core/cmds/test_keys.py b/chia/_tests/core/cmds/test_keys.py index a5911f3986a9..bd1a9fa54eb6 100644 --- a/chia/_tests/core/cmds/test_keys.py +++ b/chia/_tests/core/cmds/test_keys.py @@ -7,7 +7,7 @@ from typing import Dict, List, Optional import pytest -from click.testing import CliRunner +from click.testing import CliRunner, Result from chia.cmds.chia import cli from chia.cmds.keys import delete_all_cmd, generate_and_print_cmd, sign_cmd, verify_cmd @@ -21,13 +21,16 @@ "another venue evidence spread season bright private " "tomato remind jaguar original blur embody project can" ) +TEST_PUBLIC_KEY = "8a37cad4c5edf0a7544cbdb9f9383f7c5e82567d0236dc4f5b0547137afff9a5ce33aece3358d0202cafb9a12607ab53" +TEST_PK_FINGERPRINT = 2167729070 TEST_FINGERPRINT = 2877570395 @pytest.fixture(scope="function") -def keyring_with_one_key(empty_keyring): +def keyring_with_one_public_one_private_key(empty_keyring): keychain = empty_keyring - keychain.add_private_key(TEST_MNEMONIC_SEED) + keychain.add_key(TEST_MNEMONIC_SEED) + keychain.add_key(TEST_PUBLIC_KEY, private=False) return keychain @@ -191,6 +194,7 @@ def test_generate_with_existing_config(self, tmp_path, empty_keyring): (["add", "--label", "key_0"], "key_0", f"{TEST_MNEMONIC_SEED}\n"), (["add", "-l", ""], None, f"{TEST_MNEMONIC_SEED}\n"), (["add", "--label", ""], None, f"{TEST_MNEMONIC_SEED}\n"), + (["add", "-l", "key_0"], "key_0", f"{TEST_PUBLIC_KEY}\n"), ], ) def test_generate_and_add_label_parameter( @@ -218,8 +222,8 @@ def test_generate_and_add_label_parameter( # And make sure the label was set to the expected label assert_label(keychain, label, 0) - def test_set_label(self, keyring_with_one_key, tmp_path): - keychain = keyring_with_one_key + def test_set_label(self, keyring_with_one_public_one_private_key, tmp_path): + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path base_params = [ "--root-path", @@ -245,8 +249,8 @@ def set_and_validate(label: str): # Change the label set_and_validate("changed") - def test_delete_label(self, keyring_with_one_key, tmp_path): - keychain = keyring_with_one_key + def test_delete_label(self, keyring_with_one_public_one_private_key, tmp_path): + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path base_params = [ "--root-path", @@ -287,7 +291,7 @@ def test_show_labels(self, empty_keyring, tmp_path): # Add 10 keys to the keychain, give every other a label keys = [KeyData.generate(f"key_{i}" if i % 2 == 0 else None) for i in range(10)] for key in keys: - keychain.add_private_key(key.mnemonic_str(), key.label) + keychain.add_key(key.mnemonic_str(), key.label) # Make sure all 10 keys are printed correct result = runner.invoke(cli, [*base_params, *cmd_params], catch_exceptions=False) assert result.exit_code == 0 @@ -302,14 +306,15 @@ def test_show_labels(self, empty_keyring, tmp_path): else: assert label == key.label - def test_show(self, keyring_with_one_key, tmp_path): + def test_show(self, keyring_with_one_public_one_private_key, tmp_path): """ Test that the `chia keys show` command shows the correct key. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key assert len(keychain.get_all_private_keys()) == 1 + assert len(keychain.get_all_public_keys()) == 2 keys_root_path = keychain.keyring_wrapper.keys_root_path base_params = [ @@ -327,17 +332,25 @@ def test_show(self, keyring_with_one_key, tmp_path): # assert result.exit_code == 0 assert result.output.find(f"Fingerprint: {TEST_FINGERPRINT}") != -1 + assert result.output.find(f"Fingerprint: {TEST_PK_FINGERPRINT}") != -1 - def test_show_fingerprint(self, keyring_with_one_key, tmp_path): + # Try with non_observer_derivation + result = runner.invoke(cli, [*base_params, *cmd_params, "--non-observer-derivation"]) + assert result.output.find(f"Fingerprint: {TEST_FINGERPRINT}") != -1 + assert result.output.find(f"Fingerprint: {TEST_PK_FINGERPRINT}") != -1 + assert result.output.find("First wallet address (non-observer): N/A") != -1 + + def test_show_fingerprint(self, keyring_with_one_public_one_private_key, tmp_path): """ Test that the `chia keys show --fingerprint` command shows the correct key. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key # add a key - keychain.add_private_key(generate_mnemonic()) + keychain.add_key(generate_mnemonic()) assert len(keychain.get_all_private_keys()) == 2 + assert len(keychain.get_all_public_keys()) == 3 keys_root_path = keychain.keyring_wrapper.keys_root_path base_params = [ @@ -358,14 +371,15 @@ def test_show_fingerprint(self, keyring_with_one_key, tmp_path): assert len(fingerprints) == 1 assert str(TEST_FINGERPRINT) in fingerprints[0] - def test_show_json(self, keyring_with_one_key, tmp_path): + def test_show_json(self, keyring_with_one_public_one_private_key, tmp_path): """ Test that the `chia keys show --json` command shows the correct key. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key assert len(keychain.get_all_private_keys()) == 1 + assert len(keychain.get_all_public_keys()) == 2 keys_root_path = keychain.keyring_wrapper.keys_root_path base_params = [ @@ -385,13 +399,14 @@ def test_show_json(self, keyring_with_one_key, tmp_path): # assert result.exit_code == 0 assert json_result["keys"][0]["fingerprint"] == TEST_FINGERPRINT + assert json_result["keys"][1]["fingerprint"] == TEST_PK_FINGERPRINT - def test_show_mnemonic(self, keyring_with_one_key, tmp_path): + def test_show_mnemonic(self, keyring_with_one_public_one_private_key, tmp_path): """ Test that the `chia keys show --show-mnemonic-seed` command shows the key's mnemonic seed. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key assert len(keychain.get_all_private_keys()) == 1 @@ -413,13 +428,15 @@ def test_show_mnemonic(self, keyring_with_one_key, tmp_path): assert result.output.find(f"Fingerprint: {TEST_FINGERPRINT}") != -1 assert result.output.find("Mnemonic seed (24 secret words):") != -1 assert result.output.find(TEST_MNEMONIC_SEED) != -1 + assert result.output.find(f"Fingerprint: {TEST_PK_FINGERPRINT}") != -1 + assert result.output.find("Mnemonic seed (24 secret words):\nN/A") != -1 - def test_show_mnemonic_json(self, keyring_with_one_key, tmp_path): + def test_show_mnemonic_json(self, keyring_with_one_public_one_private_key, tmp_path): """ Test that the `chia keys show --show-mnemonic-seed --json` command shows the key's mnemonic seed. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key assert len(keychain.get_all_private_keys()) == 1 @@ -441,6 +458,8 @@ def test_show_mnemonic_json(self, keyring_with_one_key, tmp_path): # assert result.exit_code == 0 assert json_result["keys"][0]["fingerprint"] == TEST_FINGERPRINT assert json_result["keys"][0]["mnemonic"] == TEST_MNEMONIC_SEED + assert json_result["keys"][1]["fingerprint"] == TEST_PK_FINGERPRINT + assert json_result["keys"][1]["mnemonic"] is None def test_add_interactive(self, tmp_path, empty_keyring): """ @@ -584,7 +603,7 @@ def test_delete_all(self, empty_keyring): for i in range(5): mnemonic: str = generate_mnemonic() - keychain.add_private_key(mnemonic) + keychain.add_key(mnemonic) assert len(keychain.get_all_private_keys()) == 5 @@ -605,7 +624,7 @@ def test_generate_and_print(self): assert result.exit_code == 0 assert result.output.find("Mnemonic (24 secret words):") != -1 - def test_sign(self, keyring_with_one_key): + def test_sign(self, keyring_with_one_public_one_private_key): """ Test the `chia keys sign` command. """ @@ -636,7 +655,7 @@ def test_sign(self, keyring_with_one_key): != -1 ) - def test_sign_non_observer(self, keyring_with_one_key): + def test_sign_non_observer(self, keyring_with_one_public_one_private_key): """ Test the `chia keys sign` command with a non-observer key. """ @@ -726,12 +745,12 @@ def test_verify(self): assert result.exit_code == 0 assert result.output.find("True") == 0 - def test_derive_search(self, tmp_path, keyring_with_one_key): + def test_derive_search(self, tmp_path, keyring_with_one_public_one_private_key): """ Test the `chia keys derive search` command, searching a public and private key """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -763,11 +782,13 @@ def test_derive_search(self, tmp_path, keyring_with_one_key): "all", "a4601f992f24047097a30854ef656382911575694439108723698972941e402d737c13df76fdf43597f7b3c2fa9ed27a", "028e33fa3f8caa3102c028f3bff6b6680e528d9a0c543c479ef0b0339060ef36", + "--show-progress", ], catch_exceptions=False, ) assert result.exit_code == 0 + assert result.output.find(f"Searching keys derived from: {TEST_FINGERPRINT}") != -1 assert ( result.output.find( "Found public key: a4601f992f24047097a30854ef656382911575694439108723698" @@ -783,12 +804,191 @@ def test_derive_search(self, tmp_path, keyring_with_one_key): != -1 ) - def test_derive_search_wallet_address(self, tmp_path, keyring_with_one_key): + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_FINGERPRINT), + "search", + "--limit", + "10", + "--search-type", + "all", + "83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454e9ce1033f4cba8824259dc17dc3b643ab1003e6b961", + "--show-progress", + "--non-observer-derivation", + ], + ) + + assert result.exit_code == 0 + assert result.output.find(f"Searching keys derived from: {TEST_FINGERPRINT}") != -1 + assert ( + result.output.find( + "Found public key: 83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454" + "e9ce1033f4cba8824259dc17dc3b643ab1003e6b961 (HD path: m/12381n/8444n/2n/9n)" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_FINGERPRINT), + "search", + "--limit", + "10", + "--search-type", + "private_key", + "028e33fa3f8caa3102c028f3bff6b6680e528d9a0c543c479ef0b0339060ef36", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Found private key: 028e33fa3f8caa3102c028f3bff6b6680e528d9a0c543c479ef0b0339060ef36" + " (HD path: m/12381/8444/2/9)" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "search", + "--limit", + "10", + "--search-type", + "all", + "a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e3d4d1acfa5d63d6542336c7b24d2fceab991919e989", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Found public key: a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e" + "3d4d1acfa5d63d6542336c7b24d2fceab991919e989 (HD path: m/12381/8444/2/9)" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "search", + "--limit", + "10", + "--search-type", + "all", + "a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e3d4d1acfa5d63d6542336c7b24d2fceab991919e989", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Found public key: a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e" + "3d4d1acfa5d63d6542336c7b24d2fceab991919e989 (HD path: m/12381/8444/2/9)" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "search", + "--limit", + "10", + "--search-type", + "all", + "83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454e9ce1033f4cba8824259dc17dc3b643ab1003e6b961", + "a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e3d4d1acfa5d63d6542336c7b24d2fceab991919e989", + "--non-observer-derivation", + ], + ) + + assert result.exit_code == 1 + assert ( + result.output.find( + "Found public key: 83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454" + "e9ce1033f4cba8824259dc17dc3b643ab1003e6b961 (HD path: m/12381n/8444n/2n/9n)" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "search", + "--limit", + "10", + "--search-type", + "all", + "a4601f992f24047097a30854ef656382911575694439108723698972941e402d737c13df76fdf43597f7b3c2fa9ed27a", + "--show-progress", + ], + ) + + assert result.exit_code == 1 + assert result.output.find(f"Searching keys derived from: {TEST_PK_FINGERPRINT}") != -1 + assert ( + result.output.find( + "Could not find 'a4601f992f24047097a30854ef656382911575694439108723698972941e402d73" + "7c13df76fdf43597f7b3c2fa9ed27a'" + ) + != -1 + ) + + # 83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454e9ce1033f4cba8824259dc17dc3b643ab1003e6b961 + + def test_derive_search_wallet_address(self, tmp_path, keyring_with_one_public_one_private_key): """ Test the `chia keys derive search` command, searching for a wallet address """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -832,12 +1032,41 @@ def test_derive_search_wallet_address(self, tmp_path, keyring_with_one_key): != -1 ) - def test_derive_search_wallet_testnet_address(self, tmp_path, keyring_with_one_key): + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "search", + "--limit", + "40", + "--search-type", + "address", + "xch1p33y7kv48u7l68m490mr8levl6nkyxm3x8tfcnnec555egxzd3gs829wkl", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Found wallet address: " + "xch1p33y7kv48u7l68m490mr8levl6nkyxm3x8tfcnnec555egxzd3gs829wkl (HD path: m/12381/8444/2/9)" + ) + != -1 + ) + + def test_derive_search_wallet_testnet_address(self, tmp_path, keyring_with_one_public_one_private_key): """ Test the `chia keys derive search` command, searching for a testnet wallet address """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -883,12 +1112,43 @@ def test_derive_search_wallet_testnet_address(self, tmp_path, keyring_with_one_k != -1 ) - def test_derive_search_failure(self, tmp_path, keyring_with_one_key): + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "search", + "--limit", + "40", + "--search-type", + "address", + "txch1p33y7kv48u7l68m490mr8levl6nkyxm3x8tfcnnec555egxzd3gs2dzchv", + "--prefix", + "txch", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Found wallet address: " + "txch1p33y7kv48u7l68m490mr8levl6nkyxm3x8tfcnnec555egxzd3gs2dzchv (HD path: m/12381/8444/2/9)" + ) + != -1 + ) + + def test_derive_search_failure(self, tmp_path, keyring_with_one_public_one_private_key): """ Test the `chia keys derive search` command with a failing search. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -976,12 +1236,12 @@ def test_derive_search_hd_path(self, tmp_path, empty_keyring, mnemonic_seed_file != -1 ) - def test_derive_wallet_address(self, tmp_path, keyring_with_one_key): + def test_derive_wallet_address(self, tmp_path, keyring_with_one_public_one_private_key, mnemonic_seed_file): """ Test the `chia keys derive wallet-address` command, generating a couple of wallet addresses. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -1004,8 +1264,8 @@ def test_derive_wallet_address(self, tmp_path, keyring_with_one_key): os.fspath(keys_root_path), "keys", "derive", - "--fingerprint", - str(TEST_FINGERPRINT), + "--mnemonic-seed-filename", + str(mnemonic_seed_file), "wallet-address", "--index", "50", @@ -1033,12 +1293,63 @@ def test_derive_wallet_address(self, tmp_path, keyring_with_one_key): != -1 ) - def test_derive_wallet_testnet_address(self, tmp_path, keyring_with_one_key): + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "wallet-address", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Wallet address 9 (m/12381/8444/2/9): " "xch1p33y7kv48u7l68m490mr8levl6nkyxm3x8tfcnnec555egxzd3gs829wkl" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "wallet-address", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + "--non-observer-derivation", + ], + ) + assert result.exit_code == 0 + assert result.output.find("Need a private key for non observer derivation of wallet addresses") != -1 + + def test_derive_wallet_testnet_address(self, tmp_path, keyring_with_one_public_one_private_key): """ Test the `chia keys derive wallet-address` command, generating a couple of testnet wallet addresses. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -1092,12 +1403,43 @@ def test_derive_wallet_testnet_address(self, tmp_path, keyring_with_one_key): != -1 ) - def test_derive_child_keys(self, tmp_path, keyring_with_one_key): + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "wallet-address", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + "--prefix", + "txch", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Wallet address 9 (m/12381/8444/2/9): " + "txch1p33y7kv48u7l68m490mr8levl6nkyxm3x8tfcnnec555egxzd3gs2dzchv" + ) + != -1 + ) + + def test_derive_child_keys(self, tmp_path, keyring_with_one_public_one_private_key, mnemonic_seed_file): """ Test the `chia keys derive child-keys` command, generating a couple of derived keys. """ - keychain = keyring_with_one_key + keychain = keyring_with_one_public_one_private_key keys_root_path = keychain.keyring_wrapper.keys_root_path runner = CliRunner() @@ -1120,8 +1462,8 @@ def test_derive_child_keys(self, tmp_path, keyring_with_one_key): os.fspath(keys_root_path), "keys", "derive", - "--fingerprint", - str(TEST_FINGERPRINT), + "--mnemonic-seed-filename", + str(mnemonic_seed_file), "child-key", "--derive-from-hd-path", "m/12381n/8444n/2/3/4/", @@ -1164,3 +1506,204 @@ def test_derive_child_keys(self, tmp_path, keyring_with_one_key): ) != -1 ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_FINGERPRINT), + "child-key", + "--type", + "wallet", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + "--show-private-keys", + "--non-observer-derivation", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Wallet public key 9 (m/12381n/8444n/2n/9n): " + "83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454e9ce1033f4cba8824259dc17dc3b643ab1003e6b961" + ) + != -1 + ) + assert ( + result.output.find( + "Wallet private key 9 (m/12381n/8444n/2n/9n): " + "522f45786db6446d2f617a0c7df894385a21d05c7fbbfb34ee5aaaa417d8f41f" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_FINGERPRINT), + "child-key", + "--type", + "wallet", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + "--show-private-keys", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Wallet public key 9 (m/12381/8444/2/9): " + "a4601f992f24047097a30854ef656382911575694439108723698972941e402d737c13df76fdf43597f7b3c2fa9ed27a" + ) + != -1 + ) + assert ( + result.output.find( + "Wallet private key 9 (m/12381/8444/2/9): " + "028e33fa3f8caa3102c028f3bff6b6680e528d9a0c543c479ef0b0339060ef36" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "child-key", + "--derive-from-hd-path", + "m/12381/8444/2/3/4/", + "--index", + "30", + "--count", + "2", + "--show-hd-path", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Observer public key 30 (m/12381/8444/2/3/4/30): " + "b64b1c85bacc8fe3509a559d0178ce8373d8ea1dfcdb0c4b76fa03540da31d01276f7f000306d576c1c29468cf13b45a" + ) + != -1 + ) + assert ( + result.output.find( + "Observer public key 31 (m/12381/8444/2/3/4/31): " + "92ba067175f2e87dd47336302ef8331a05ee0a737a92deec1b23dcd49eeb783d7feafdd055e3cba24fd6c94b39eb7bf3" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "child-key", + "--type", + "wallet", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + ], + ) + + assert result.exit_code == 0 + assert ( + result.output.find( + "Wallet public key 9 (m/12381/8444/2/9): " + "a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e3d4d1acfa5d63d6542336c7b24d2fceab991919e989" + ) + != -1 + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "child-key", + "--type", + "wallet", + "--index", + "9", + "--count", + "1", + "--show-hd-path", + "--non-observer-derivation", + ], + ) + + assert isinstance(result.exception, ValueError) and result.exception.args == ( + "Cannot perform non-observer derivation on an observer-only key", + ) + + result: Result = runner.invoke( + cli, + [ + "--root-path", + os.fspath(tmp_path), + "--keys-root-path", + os.fspath(keys_root_path), + "keys", + "derive", + "--fingerprint", + str(TEST_PK_FINGERPRINT), + "child-key", + "--derive-from-hd-path", + "m/12381n/8444/2/3/4/", + "--index", + "30", + "--count", + "2", + "--show-hd-path", + ], + ) + + assert isinstance(result.exception, ValueError) and result.exception.args == ( + "Hardened path specified for observer key", + ) diff --git a/chia/_tests/core/daemon/test_daemon.py b/chia/_tests/core/daemon/test_daemon.py index 0456fa59e0ab..9c460852e927 100644 --- a/chia/_tests/core/daemon/test_daemon.py +++ b/chia/_tests/core/daemon/test_daemon.py @@ -10,6 +10,7 @@ import pytest from aiohttp import WSMessage from aiohttp.web_ws import WebSocketResponse +from chia_rs import G1Element from pytest_mock import MockerFixture from chia._tests.util.misc import Marks, datacases @@ -404,8 +405,11 @@ def mock_daemon_with_config_and_keys(get_keychain_for_function, root_path_popula keychain = Keychain() # populate the keychain with some test keys - keychain.add_private_key(test_key_data.mnemonic_str()) - keychain.add_private_key(test_key_data_2.mnemonic_str()) + keychain.add_key(test_key_data.mnemonic_str()) + keychain.add_key(test_key_data_2.mnemonic_str()) + + # Throw in an unused pubkey-only entry + keychain.add_key(bytes(G1Element()).hex(), private=False) # Mock daemon server with net_config set for mainnet return Daemon(services={}, connections={}, net_config=config) @@ -416,8 +420,8 @@ async def daemon_client_with_config_and_keys(get_keychain_for_function, get_daem keychain = Keychain() # populate the keychain with some test keys - keychain.add_private_key(test_key_data.mnemonic_str()) - keychain.add_private_key(test_key_data_2.mnemonic_str()) + keychain.add_key(test_key_data.mnemonic_str()) + keychain.add_key(test_key_data_2.mnemonic_str()) daemon = get_daemon client = await connect_to_daemon( @@ -604,6 +608,12 @@ async def test_get_network_info(daemon_client_with_config_and_keys: DaemonProxy) response={ "success": True, "wallet_addresses": { + G1Element().get_fingerprint(): [ + { + "address": "xch1dr2sj4jqdt6nj4l32d4f5dk7mrwak3qw5hsykty5lhhd00053y0szaz8zj", + "hd_path": "m/12381/8444/2/0", + } + ], test_key_data.fingerprint: [ { "address": "xch1zze67l3jgxuvyaxhjhu7326sezxxve7lgzvq0497ddggzhff7c9s2pdcwh", @@ -677,14 +687,30 @@ async def test_get_network_info(daemon_client_with_config_and_keys: DaemonProxy) }, ), WalletAddressCase( - id="missing private key", - request={"fingerprints": [test_key_data.fingerprint]}, + id="missing private key hardened", + request={"fingerprints": [test_key_data.fingerprint], "non_observer_derivation": True}, response={ "success": False, "error": f"missing private key for key with fingerprint {test_key_data.fingerprint}", }, pubkeys_only=True, ), + WalletAddressCase( + id="missing private key unhardened", + request={"fingerprints": [test_key_data.fingerprint]}, + response={ + "success": True, + "wallet_addresses": { + test_key_data.fingerprint: [ + { + "address": "xch1zze67l3jgxuvyaxhjhu7326sezxxve7lgzvq0497ddggzhff7c9s2pdcwh", + "hd_path": "m/12381/8444/2/0", + }, + ], + }, + }, + pubkeys_only=True, + ), ) @pytest.mark.anyio async def test_get_wallet_addresses( @@ -957,7 +983,7 @@ async def test_add_private_key(daemon_connection_and_temp_keychain): missing_mnemonic_response_data = { "success": False, "error": "malformed request", - "error_details": {"message": "missing mnemonic"}, + "error_details": {"message": "missing key information"}, } mnemonic_with_typo_response_data = { @@ -1047,7 +1073,7 @@ async def test_get_key(daemon_connection_and_temp_keychain): await ws.send_str(create_payload("get_key", {"fingerprint": test_key_data.fingerprint}, "test", "daemon")) assert_response(await ws.receive(), fingerprint_not_found_response_data(test_key_data.fingerprint)) - keychain.add_private_key(test_key_data.mnemonic_str()) + keychain.add_key(test_key_data.mnemonic_str()) # without `include_secrets` await ws.send_str(create_payload("get_key", {"fingerprint": test_key_data.fingerprint}, "test", "daemon")) @@ -1085,7 +1111,7 @@ async def test_get_keys(daemon_connection_and_temp_keychain): keys = [KeyData.generate() for _ in range(5)] keys_added = [] for key_data in keys: - keychain.add_private_key(key_data.mnemonic_str()) + keychain.add_key(key_data.mnemonic_str()) keys_added.append(key_data) get_keys_response_data_without_secrets = get_keys_response_data( @@ -1113,7 +1139,7 @@ async def test_get_public_key(daemon_connection_and_temp_keychain): await ws.send_str(create_payload("get_public_key", {"fingerprint": test_key_data.fingerprint}, "test", "daemon")) assert_response(await ws.receive(), fingerprint_not_found_response_data(test_key_data.fingerprint)) - keychain.add_private_key(test_key_data.mnemonic_str()) + keychain.add_key(test_key_data.mnemonic_str()) await ws.send_str(create_payload("get_public_key", {"fingerprint": test_key_data.fingerprint}, "test", "daemon")) response = await ws.receive() @@ -1139,7 +1165,7 @@ async def test_get_public_keys(daemon_connection_and_temp_keychain): keys = [KeyData.generate() for _ in range(5)] keys_added = [] for key_data in keys: - keychain.add_private_key(key_data.mnemonic_str()) + keychain.add_key(key_data.mnemonic_str()) keys_added.append(key_data) get_public_keys_response = get_public_keys_response_data(keys_added) @@ -1159,7 +1185,7 @@ async def test_get_public_keys(daemon_connection_and_temp_keychain): @pytest.mark.anyio async def test_key_renaming(daemon_connection_and_temp_keychain): ws, keychain = daemon_connection_and_temp_keychain - keychain.add_private_key(test_key_data.mnemonic_str()) + keychain.add_key(test_key_data.mnemonic_str()) # Rename the key three times for i in range(3): key_data = replace(test_key_data_no_secrets, label=f"renaming_{i}") @@ -1184,7 +1210,7 @@ async def test_key_renaming(daemon_connection_and_temp_keychain): async def test_key_label_deletion(daemon_connection_and_temp_keychain): ws, keychain = daemon_connection_and_temp_keychain - keychain.add_private_key(test_key_data.mnemonic_str(), "key_0") + keychain.add_key(test_key_data.mnemonic_str(), "key_0") assert keychain.get_key(test_key_data.fingerprint).label == "key_0" await ws.send_str(create_payload("delete_label", {"fingerprint": test_key_data.fingerprint}, "test", "daemon")) assert_response(await ws.receive(), success_response_data) @@ -1258,7 +1284,7 @@ async def test_key_label_methods( daemon_connection_and_temp_keychain, method: str, parameter: Dict[str, Any], response_data_dict: Dict[str, Any] ) -> None: ws, keychain = daemon_connection_and_temp_keychain - keychain.add_private_key(test_key_data.mnemonic_str(), "key_0") + keychain.add_key(test_key_data.mnemonic_str(), "key_0") await ws.send_str(create_payload(method, parameter, "test", "daemon")) assert_response(await ws.receive(), response_data_dict) diff --git a/chia/_tests/core/daemon/test_keychain_proxy.py b/chia/_tests/core/daemon/test_keychain_proxy.py index 1f3391197f9c..c65d0b69f397 100644 --- a/chia/_tests/core/daemon/test_keychain_proxy.py +++ b/chia/_tests/core/daemon/test_keychain_proxy.py @@ -2,13 +2,14 @@ import logging from dataclasses import replace -from typing import AsyncGenerator +from typing import Any, AsyncGenerator import pytest from chia.daemon.keychain_proxy import KeychainProxy, connect_to_keychain_and_validate from chia.simulator.block_tools import BlockTools from chia.simulator.setup_services import setup_daemon +from chia.util.errors import KeychainIsEmpty, KeychainKeyNotFound from chia.util.keychain import KeyData TEST_KEY_1 = KeyData.generate(label="🚽🍯") @@ -16,31 +17,56 @@ TEST_KEY_3 = KeyData.generate(label="☕️🍬") -@pytest.fixture(scope="function") -async def keychain_proxy(get_b_tools: BlockTools) -> AsyncGenerator[KeychainProxy, None]: +@pytest.fixture(scope="function", params=[True, False]) +async def keychain_proxy(get_b_tools: BlockTools, request: Any) -> AsyncGenerator[KeychainProxy, None]: async with setup_daemon(btools=get_b_tools) as daemon: log = logging.getLogger("keychain_proxy_fixture") keychain_proxy = await connect_to_keychain_and_validate(daemon.root_path, log) assert keychain_proxy is not None + if request.param: + keychain_proxy.keychain = daemon.keychain_server._default_keychain yield keychain_proxy await keychain_proxy.close() @pytest.fixture(scope="function") async def keychain_proxy_with_keys(keychain_proxy: KeychainProxy) -> KeychainProxy: - await keychain_proxy.add_private_key(TEST_KEY_1.mnemonic_str(), TEST_KEY_1.label) - await keychain_proxy.add_private_key(TEST_KEY_2.mnemonic_str(), TEST_KEY_2.label) + await keychain_proxy.add_key(TEST_KEY_1.mnemonic_str(), TEST_KEY_1.label) + await keychain_proxy.add_key(TEST_KEY_2.mnemonic_str(), TEST_KEY_2.label) return keychain_proxy @pytest.mark.anyio async def test_add_private_key(keychain_proxy: KeychainProxy) -> None: keychain = keychain_proxy - await keychain.add_private_key(TEST_KEY_3.mnemonic_str(), TEST_KEY_3.label) + await keychain.add_key(TEST_KEY_3.mnemonic_str(), TEST_KEY_3.label) key = await keychain.get_key(TEST_KEY_3.fingerprint, include_secrets=True) assert key == TEST_KEY_3 +@pytest.mark.anyio +async def test_add_public_key(keychain_proxy: KeychainProxy) -> None: + keychain = keychain_proxy + await keychain.add_key(bytes(TEST_KEY_3.public_key).hex(), TEST_KEY_3.label, private=False) + with pytest.raises(Exception, match="already exists"): + await keychain.add_key(bytes(TEST_KEY_3.public_key).hex(), "", private=False) + key = await keychain.get_key(TEST_KEY_3.fingerprint, include_secrets=False) + assert key is not None + assert key.public_key == TEST_KEY_3.public_key + assert key.secrets is None + + pk = await keychain.get_key_for_fingerprint(TEST_KEY_3.fingerprint, private=False) + assert pk is not None + assert pk == TEST_KEY_3.public_key + + pk = await keychain.get_key_for_fingerprint(None, private=False) + assert pk is not None + assert pk == TEST_KEY_3.public_key + + with pytest.raises(KeychainKeyNotFound): + pk = await keychain.get_key_for_fingerprint(1234567890, private=False) + + @pytest.mark.parametrize("include_secrets", [True, False]) @pytest.mark.anyio async def test_get_key(keychain_proxy_with_keys: KeychainProxy, include_secrets: bool) -> None: @@ -50,6 +76,18 @@ async def test_get_key(keychain_proxy_with_keys: KeychainProxy, include_secrets: assert key == expected_key +@pytest.mark.anyio +async def test_get_key_for_fingerprint(keychain_proxy: KeychainProxy) -> None: + keychain = keychain_proxy + with pytest.raises(KeychainIsEmpty): + await keychain.get_key_for_fingerprint(None, private=False) + await keychain_proxy.add_key(TEST_KEY_1.mnemonic_str(), TEST_KEY_1.label) + assert await keychain.get_key_for_fingerprint(TEST_KEY_1.fingerprint, private=False) == TEST_KEY_1.public_key + assert await keychain.get_key_for_fingerprint(None, private=False) == TEST_KEY_1.public_key + with pytest.raises(KeychainKeyNotFound): + await keychain.get_key_for_fingerprint(1234567890, private=False) + + @pytest.mark.parametrize("include_secrets", [True, False]) @pytest.mark.anyio async def test_get_keys(keychain_proxy_with_keys: KeychainProxy, include_secrets: bool) -> None: diff --git a/chia/_tests/core/data_layer/test_data_rpc.py b/chia/_tests/core/data_layer/test_data_rpc.py index 36b9e73ecaf5..ddf6aed08f3b 100644 --- a/chia/_tests/core/data_layer/test_data_rpc.py +++ b/chia/_tests/core/data_layer/test_data_rpc.py @@ -93,6 +93,7 @@ async def init_data_layer_service( wallet_service: Optional[WalletService] = None, manage_data_interval: int = 5, maximum_full_file_count: Optional[int] = None, + enable_batch_autoinsert: bool = True, ) -> AsyncIterator[DataLayerService]: config = bt.config config["data_layer"]["wallet_peer"]["port"] = int(wallet_rpc_port) @@ -101,6 +102,7 @@ async def init_data_layer_service( config["data_layer"]["port"] = 0 config["data_layer"]["rpc_port"] = 0 config["data_layer"]["manage_data_interval"] = 5 + config["data_layer"]["enable_batch_autoinsert"] = enable_batch_autoinsert if maximum_full_file_count is not None: config["data_layer"]["maximum_full_file_count"] = maximum_full_file_count if db_path is not None: @@ -806,8 +808,10 @@ async def offer_setup_fixture( self_hostname: str, two_wallet_nodes_services: SimulatorsAndWalletsServices, tmp_path: Path, + request: pytest.FixtureRequest, ) -> AsyncIterator[OfferSetup]: [full_node_service], wallet_services, bt = two_wallet_nodes_services + enable_batch_autoinsertion_settings = getattr(request, "param", (True, True)) full_node_api = full_node_service._api wallets: List[Wallet] = [] for wallet_service in wallet_services: @@ -822,12 +826,16 @@ async def offer_setup_fixture( async with contextlib.AsyncExitStack() as exit_stack: store_setups: List[StoreSetup] = [] - for wallet_service in wallet_services: + for enable_batch_autoinsert, wallet_service in zip(enable_batch_autoinsertion_settings, wallet_services): assert wallet_service.rpc_server is not None port = wallet_service.rpc_server.listen_port data_layer_service = await exit_stack.enter_async_context( init_data_layer_service( - wallet_rpc_port=port, wallet_service=wallet_service, bt=bt, db_path=tmp_path.joinpath(str(port)) + wallet_rpc_port=port, + wallet_service=wallet_service, + bt=bt, + db_path=tmp_path.joinpath(str(port)), + enable_batch_autoinsert=enable_batch_autoinsert, ) ) data_layer = data_layer_service._api.data_layer @@ -914,19 +922,20 @@ async def populate_offer_setup(offer_setup: OfferSetup, count: int) -> OfferSetu (offer_setup.taker, b"\x02"), ) for store_setup, value_prefix in setups: - await store_setup.api.batch_update( - { - "id": store_setup.id.hex(), - "changelist": [ - { - "action": "insert", - "key": value.to_bytes(length=1, byteorder="big").hex(), - "value": (value_prefix + value.to_bytes(length=1, byteorder="big")).hex(), - } - for value in range(count) - ], - } + await store_setup.data_layer.batch_insert( + store_id=store_setup.id, + changelist=[ + { + "action": "insert", + "key": value.to_bytes(length=1, byteorder="big"), + "value": (value_prefix + value.to_bytes(length=1, byteorder="big")), + } + for value in range(count) + ], + status=Status.PENDING, + enable_batch_autoinsert=False, ) + await store_setup.data_layer.publish_update(store_setup.id, uint64(0)) await process_for_data_layer_keys( expected_key=b"\x00", @@ -1005,8 +1014,8 @@ class MakeAndTakeReference: make_one_take_one_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "7a5870e08cda8fb9066d8e49aae73841c5926860d093433a7cc82764b87b1b56", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff808080a18f4f7f0ac07240de1477a4147ca1bf7afd88808fd88aabb0296ddcffd6cd3cee16696c5fad7fed5fa67699a4ec554717b58e15c12a8fcebddc70c52a4cee4c98b7210cfcb5eb2a1d8f75f2575c421474e767efef39e652c0d2ef14f5f433b8", # noqa + "trade_id": "b34b77304778961deca03bd5eb370ed35a7aa97c0e030b293d1285b74d1741f4", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080a18f4f7f0ac07240de1477a4147ca1bf7afd88808fd88aabb0296ddcffd6cd3cee16696c5fad7fed5fa67699a4ec554717b58e15c12a8fcebddc70c52a4cee4c98b7210cfcb5eb2a1d8f75f2575c421474e767efef39e652c0d2ef14f5f433b8", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1050,7 +1059,7 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x10".hex(), "value": b"\x01\x10".hex()}], taker_inclusions=[{"key": b"\x10".hex(), "value": b"\x02\x10".hex()}], - trade_id="a86b08e21b7677783812969fd8f8a1442d4d265cbb0bd2727bf6c16858789f5b", + trade_id="ecc205b2ffe49b87b2f385f595a395ab13cf0e0627e028a1222a0b4d255bdc18", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("8e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea"), @@ -1065,8 +1074,8 @@ class MakeAndTakeReference: make_one_take_one_same_values_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "19030b2dcea7a44d36df43cd3c08309bc55f354be087433d47727b64da8ec43e", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa09502844b542b20256008242b5676246b765d0e4c82714466a1140489e07bf0e880ffff81e8ff0bffffffffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f9180ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0352ef238e8561d0bfc8f754683b4d23ef9f40f7c4b574e333bb52ec81aa0b868ff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f91ffa0352ef238e8561d0bfc8f754683b4d23ef9f40f7c4b574e333bb52ec81aa0b868808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa06cae87c81f9edf8516076a9eb56a354ced636998a9f40b278c7bfddfb655df568080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ed15ec60900d04972f30df7bde60487bd63d17889f405398376809fc975e3177c0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f9180ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f91ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff808080877a8d3055ad9746e85b725221c78616af1a7b223a32be868d7b9135fbb4da0379307fa3073a958782d5db985425974a125117ae957b9074c0ee548bd4dfdd7ed19cbe9974a838383b51e19060ab6c2a7a83f676346125a288a6b6519e7aa148", # noqa + "trade_id": "d2bebdb0ee1fdd4a38f7c8c5a25bf6839794268f955e469818998ad5dad92d4d", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ed15ec60900d04972f30df7bde60487bd63d17889f405398376809fc975e3177c0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f9180ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f91ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa09502844b542b20256008242b5676246b765d0e4c82714466a1140489e07bf0e880ffff81e8ff0bffffffffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f9180ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0352ef238e8561d0bfc8f754683b4d23ef9f40f7c4b574e333bb52ec81aa0b868ff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f91ffa0352ef238e8561d0bfc8f754683b4d23ef9f40f7c4b574e333bb52ec81aa0b868808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa06cae87c81f9edf8516076a9eb56a354ced636998a9f40b278c7bfddfb655df568080ff018080808080ffff80ff80ff80ff80ff8080808080877a8d3055ad9746e85b725221c78616af1a7b223a32be868d7b9135fbb4da0379307fa3073a958782d5db985425974a125117ae957b9074c0ee548bd4dfdd7ed19cbe9974a838383b51e19060ab6c2a7a83f676346125a288a6b6519e7aa148", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1110,7 +1119,7 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x10".hex(), "value": b"\x05\x10".hex()}], taker_inclusions=[{"key": b"\x10".hex(), "value": b"\x05\x10".hex()}], - trade_id="09c4af6aa770f6797516dbae697a5efead5a1eaa29295fcc314b3f2f48fd9fe9", + trade_id="57e31189153fca54e70207c02bf27b8e271fcbfa1fac8076474a9a1cc04d3b63", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("1d1eb374688e3033cbce2514e4fded10ceffe068e663718b8a20716a65019f91"), @@ -1125,8 +1134,8 @@ class MakeAndTakeReference: make_two_take_one_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "651bc5d812aa3e72f63963504ced83da0a7c924ea5910b361dbbb58839ccfc92", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0890bd0a05cc152c27a3a72348d59e9c5fb46c18da8f32948f1f2143b35014aca80ffff81e8ff0bffffffffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70e80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa09130956ec241f3c4f8807f6889e65025947fbd7bb757d8df0ba2640e293bcc60ff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70effa09130956ec241f3c4f8807f6889e65025947fbd7bb757d8df0ba2640e293bcc60808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071e69c05748eab24bce907ef7840c14873f1e668bda10c7aab57e25bb7895a88db20000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70e80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70effa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff808080952dd3e68ea36950708547ddc57bea7a97d9e7a2d5c7921be7c72be43abc432406b6b1786e3a52b6a5dc53159409e55703d00eb60ca407a2712a5b637bc3599a0073d7c9a6009c16547f5fae0f8932002d5318fa4f2f4a9f19ab3f7362118230", # noqa + "trade_id": "04ea501a3344c1c4c7aedf50ec2751d69d2ff09bd6caeb1ea529071225f413bc", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071e69c05748eab24bce907ef7840c14873f1e668bda10c7aab57e25bb7895a88db20000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70e80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70effa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0890bd0a05cc152c27a3a72348d59e9c5fb46c18da8f32948f1f2143b35014aca80ffff81e8ff0bffffffffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70e80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa09130956ec241f3c4f8807f6889e65025947fbd7bb757d8df0ba2640e293bcc60ff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70effa09130956ec241f3c4f8807f6889e65025947fbd7bb757d8df0ba2640e293bcc60808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080952dd3e68ea36950708547ddc57bea7a97d9e7a2d5c7921be7c72be43abc432406b6b1786e3a52b6a5dc53159409e55703d00eb60ca407a2712a5b637bc3599a0073d7c9a6009c16547f5fae0f8932002d5318fa4f2f4a9f19ab3f7362118230", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1200,7 +1209,7 @@ class MakeAndTakeReference: {"key": b"\x11".hex(), "value": b"\x01\x11".hex()}, ], taker_inclusions=[{"key": b"\x10".hex(), "value": b"\x02\x10".hex()}], - trade_id="9c407637b889be3b61b3f5599b7391ee6edbf69a0c8c954656231c0bfb710b08", + trade_id="4f5412917a22233fa6186013392144bc469d23576d108b98faa9c7e76d036af4", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("043fed6d67961e36db2900b6aab24aa68be529c4e632aace486fbea1b26dc70e"), @@ -1215,8 +1224,8 @@ class MakeAndTakeReference: make_one_take_two_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "d92dc63d042226f6151b830edf79d58f075c3eb005cf9fab40d8f744bb9851c7", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab5ffa05eadd0f5982411ec074786cb6e2e37880d2ea1f007b47bc50a1b36cc2c61ba098080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808097ff2e118c10f392eb2e53be370c1a9226862b59ab0dca4e1751b2196cfdc301f71fddd152d1324e7f3d32800df7c5af14cd0e4ad5389bea2a0f10330916fc29d2debf7d8ab4c3ac496b5d301de36ebb7973888443e13244c68246e54377b775", # noqa + "trade_id": "fa88e673fd1235efd43c1ef4f2957c88f7dcd0b2cfd5bde54a65d52d148ce670", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab5ffa05eadd0f5982411ec074786cb6e2e37880d2ea1f007b47bc50a1b36cc2c61ba098080ff018080808080ffff80ff80ff80ff80ff808080808097ff2e118c10f392eb2e53be370c1a9226862b59ab0dca4e1751b2196cfdc301f71fddd152d1324e7f3d32800df7c5af14cd0e4ad5389bea2a0f10330916fc29d2debf7d8ab4c3ac496b5d301de36ebb7973888443e13244c68246e54377b775", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1263,7 +1272,7 @@ class MakeAndTakeReference: {"key": b"\x10".hex(), "value": b"\x02\x10".hex()}, {"key": b"\x11".hex(), "value": b"\x02\x11".hex()}, ], - trade_id="d53d08a6951849cd33de3a703bc133a2ae973a34ce4527e19e233fb5cb57bbe3", + trade_id="48efd113518a57895d45f5cde246d9e088e08d96a562e6013eb4c04527e4d5ba", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("8e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea"), @@ -1278,8 +1287,8 @@ class MakeAndTakeReference: make_one_existing_take_one_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "2dbea3ff730677d4ddb9ae30691e629d183f9103f08c3c7e849bff9ad94168a4", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa06766ecb6a87bcab8829fc9b3f08c8b5a83ecb7c5597c6a13ec346bfcafc1aab880ffff33ffa09b077471a29fd048bf897998e3f73ee5215345cd4943441e7c654dc11f2c579eff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27ffa09b077471a29fd048bf897998e3f73ee5215345cd4943441e7c654dc11f2c579e808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ef1932b0458af07a67925e9e0d5eca3ae137ba72bc689bd9b7b00bd0508ee6be80000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff8080809200d622b5462e5c56846dfbf374090fe9afef15ade6b26cac913dce6005df00550a26f624807adb98240ea731fa37d2054f37d4f2feb416b110c4cdf190ed5393e059c871b7e56897bf65aa4c1b95b31d2f16e41600d8f74372ee41cc11b39b", # noqa + "trade_id": "1efc47397715da444017864d15b92676bc416afcb7aba14047c6edfd6f4fa766", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ef1932b0458af07a67925e9e0d5eca3ae137ba72bc689bd9b7b00bd0508ee6be80000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa06766ecb6a87bcab8829fc9b3f08c8b5a83ecb7c5597c6a13ec346bfcafc1aab880ffff33ffa09b077471a29fd048bf897998e3f73ee5215345cd4943441e7c654dc11f2c579eff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27ffa09b077471a29fd048bf897998e3f73ee5215345cd4943441e7c654dc11f2c579e808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff80808080809200d622b5462e5c56846dfbf374090fe9afef15ade6b26cac913dce6005df00550a26f624807adb98240ea731fa37d2054f37d4f2feb416b110c4cdf190ed5393e059c871b7e56897bf65aa4c1b95b31d2f16e41600d8f74372ee41cc11b39b", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1328,7 +1337,7 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x09".hex(), "value": b"\x01\x09".hex()}], taker_inclusions=[{"key": b"\x10".hex(), "value": b"\x02\x10".hex()}], - trade_id="74ce97a6154467ca1a868e546a5d9e15e1e61c386aa27cb3686b198613972606", + trade_id="faea189031da8557299173e6731dafea53d85e116485ffaa8ff2070278145608", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), ], @@ -1342,8 +1351,8 @@ class MakeAndTakeReference: make_one_take_one_existing_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "d4387ed635c1ce7276dfee12f2a52bea3919291b15d126e6f4727af6944091a0", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa09dd8b0a6d67ee56221d0fe6bb131eb30d17c098c7548a78a962836011ea465bb8080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff808080a01626555f71b404f754061c4de0143b82248008fd1920466eee86ce447db889cf4287b8363a4e0bfdb628decddf19ea074c313ca4ce097f61ba67120e319481ccacbd1adcff2d1d95b859ff516691bca16e2614a56cbd3c1f1ec5580da269ab", # noqa + "trade_id": "b8576f50159e5e9eae5de00b8986084705f0796317d5da8cd0e968f2c1a7893e", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa09dd8b0a6d67ee56221d0fe6bb131eb30d17c098c7548a78a962836011ea465bb8080ff018080808080ffff80ff80ff80ff80ff8080808080a01626555f71b404f754061c4de0143b82248008fd1920466eee86ce447db889cf4287b8363a4e0bfdb628decddf19ea074c313ca4ce097f61ba67120e319481ccacbd1adcff2d1d95b859ff516691bca16e2614a56cbd3c1f1ec5580da269ab", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1387,7 +1396,7 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x10".hex(), "value": b"\x01\x10".hex()}], taker_inclusions=[{"key": b"\x09".hex(), "value": b"\x02\x09".hex()}], - trade_id="be67400ac9856e7aa1ec96071457bda73f7304115902ac387ad2b1e085115956", + trade_id="c390d8ff22ae5c6fe4559a01f713703aa023e6b8f2e89ec4340da5237d4d9c95", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("8e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea"), @@ -1401,8 +1410,8 @@ class MakeAndTakeReference: make_one_upsert_take_one_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "86a86e28563d06d2b28c49b7a16110fc9944c52004e9b3e5d81cce6138184caf", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0214dc115c3f3a3444619449b297fe03521f85c8cc12be80d8de35bb9cfb29e6d80ffff81e8ff0bffffffffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c680ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa05ef937e981ce68f2fa71e00b139acb3352b5ec32e7d6bc160874a456f106016cff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c6ffa05ef937e981ce68f2fa71e00b139acb3352b5ec32e7d6bc160874a456f106016c808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071e3a306420fb91e6a9c25cb93938b5c9e164ce761abc2ab967f8545cdcdc9e6e6d0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c680ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c6ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff808080a389748450bfea49b130c33fd987561f90eef2ffb8d5aac36590fa1d724f5d8bc0b86f23f0538b35922a098427a5afee0280ed240c03fcd9d736c7e752152f4387a023222ba5711c05417f369aeda2827cef78793acfd79dd7960c80932ccc89", # noqa + "trade_id": "99cac76f3180c06126087b1188f3a5cd6a5f5f2830ff639e7fca9e11afe2b477", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071e3a306420fb91e6a9c25cb93938b5c9e164ce761abc2ab967f8545cdcdc9e6e6d0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c680ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c6ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0214dc115c3f3a3444619449b297fe03521f85c8cc12be80d8de35bb9cfb29e6d80ffff81e8ff0bffffffffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c680ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa05ef937e981ce68f2fa71e00b139acb3352b5ec32e7d6bc160874a456f106016cff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa03761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c6ffa05ef937e981ce68f2fa71e00b139acb3352b5ec32e7d6bc160874a456f106016c808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff8080808080a389748450bfea49b130c33fd987561f90eef2ffb8d5aac36590fa1d724f5d8bc0b86f23f0538b35922a098427a5afee0280ed240c03fcd9d736c7e752152f4387a023222ba5711c05417f369aeda2827cef78793acfd79dd7960c80932ccc89", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1446,7 +1455,7 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x09".hex(), "value": b"\x01\x10".hex()}], taker_inclusions=[{"key": b"\x10".hex(), "value": b"\x02\x10".hex()}], - trade_id="72232956344e9f12eec28635e9299d367e9fd9c4a8759db0f8f110c872919ff0", + trade_id="25651c321494cee55394b73a69d638df34026d999887a88b154696880231df72", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("3761921b9b0520458995bb0ec353ea28d36efa2a7cfc3aba6772f005f7dd34c6"), @@ -1461,8 +1470,8 @@ class MakeAndTakeReference: make_one_take_one_upsert_reference = MakeAndTakeReference( entries_to_insert=10, make_offer_response={ - "trade_id": "51b7769fbf845f8c42b6c31dafb6247e2af958681972d2125f70d9157082c26b", - "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa05743f9c9e6f3ebd1506342bbf0a6bfb9dc68b58b3e7f6f32da759fb0fb74fe0e8080ff018080808080ffff80ff80ff80ff80ff8080808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff808080a7db721cb3da800e516ac87dace73404ad29cf68377a3af155e1ee1205dc34c07cefdbef7e3a9de7bf492a840c74883c039698b68f2d4d42062f5a2fd8c99da29226771305720f305cde56c417d0c5de53a9322fd3cedc4150d42540863edeaa", # noqa + "trade_id": "d9d985e9bc941df8f5718f31b597a061e99fb39d430def2b3d8bc289b0b1020e", + "offer": "00000003000000000000000000000000000000000000000000000000000000000000000052eba05592a7cbe77b4b1552cacec440b20d523d08a6be917c9213dc34f3033a0000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa042f08ebc0578f2cec7a9ad1c3038e74e0f30eba5c2f4cb1ee1c8fdb682c19dbb80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080ca2e21c90d263e63b73d449a3f8d57b9458846f7af27d9a61a515395fa14071ea55bba78c76265b4bac257251b1e89dd13637a7c18e8dcb03e092dfb7eb5a84a0000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2aff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0c842b1a384b8633ac25d0f12bd7b614f86a77642ab6426418750f2b0b86bab2a8080ffff3eff248080ff018080808080ff01808080ffffa032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243ffa08c4aebb18e8ce08405083c3d90a29f30239865142e2dcbca5393f40df9e3821dff0180ff01ffff80808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa243aa064e96a86637d8f5ebe153dc8645d29f43bee762d5ec10d06c8617fa60b8c50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa06661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e2780ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a132fae32c98cbb7d8f5814c49ee3f0ba6ec2172c5e5f6900655a65cd2157a06a1c6eb89c68c8d2cdcee9506c2217978ff018080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0bd7aa54c5f93ef1738439aa60b471ce2aa4c62fb18a7943aa10061f00dbdb83680ffff81e8ff0bffffffffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea80ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10deff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa08e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65eaffa0ca77e42ac3b3375edc54af271f21d075afd02d72969cababeec63e22f7ab10de808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa05743f9c9e6f3ebd1506342bbf0a6bfb9dc68b58b3e7f6f32da759fb0fb74fe0e8080ff018080808080ffff80ff80ff80ff80ff8080808080a7db721cb3da800e516ac87dace73404ad29cf68377a3af155e1ee1205dc34c07cefdbef7e3a9de7bf492a840c74883c039698b68f2d4d42062f5a2fd8c99da29226771305720f305cde56c417d0c5de53a9322fd3cedc4150d42540863edeaa", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1506,7 +1515,7 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x10".hex(), "value": b"\x01\x10".hex()}], taker_inclusions=[{"key": b"\x09".hex(), "value": b"\x02\x10".hex()}], - trade_id="399511c325cc7ac6df2e195271f9001f965d25327e46f89049aec1e286252746", + trade_id="515ecf094f1a4439faa9d64b8101b68df01536588ebbc9970c41c3f11ad0d602", maker_root_history=[ bytes32.from_hexstr("6661ea6604b491118b0f49c932c0f0de2ad815a57b54b6ec8fdbd1b408ae7e27"), bytes32.from_hexstr("8e54f5066aa7999fc1561a56df59d11ff01f7df93cadf49a61adebf65dec65ea"), @@ -1521,8 +1530,8 @@ class MakeAndTakeReference: make_one_take_one_unpopulated_reference = MakeAndTakeReference( entries_to_insert=0, make_offer_response={ - "trade_id": "94fd7c3078efd1f3fd378c72a2b2b8a2c44bb4ed00e22f42e5ce4c06db8f8ba1", - "offer": "000000030000000000000000000000000000000000000000000000000000000000000000785bba8a904677219cb83f053b1bcf3b5ed13d35895c5babb007c67727e743590000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0000000000000000000000000000000000000000000000000000000000000000080ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff80808080a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa478416871446884ef363bd105960c464b4208a293b348f0f1c2e12140df38469450000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0000000000000000000000000000000000000000000000000000000000000000080ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a3b0219722055ac0a66cd9de5cd3e86962d8c8ec6abb801b57e5c77ed98453b02ceae0e19548f6d4fc20b3a2ec82aa90ff018080ff018080808080ff01808080ffffa09563629e653a9fc3c65f55947883a47e062e6b67394091228ec01352ff78f333ff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0fccf087e5b81be2137cfaa35e65cc4e4a25183108907dad33c6d622e8e78349e80ffff81e8ff0bffffffffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c5880ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0b6565d3afb87a60cfdf66bc56cca80b14afc2be649971c8df647ce617b442e6eff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c58ffa0b6565d3afb87a60cfdf66bc56cca80b14afc2be649971c8df647ce617b442e6e808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff808080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa24386ab01fbd8342f8e1dac10d6e906cef3892857bd1865b6fd7ed4b01b39d568b50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c5880ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0846b58db3bd246785e202eeddfbb46acaf267f011307437cd4e0841f3da751f6ff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c58ffa0846b58db3bd246785e202eeddfbb46acaf267f011307437cd4e0841f3da751f68080ffff3eff248080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffff80808090ce896a92f47ab8adec0d7e977795762d85660495f2a6a0c025aec987faac7ec2bd78c17fdcfe126c35fb52ebe75c610df4013ecc5036e95168aba697678d89ab7718664439302d32f1eb85bf263461855c7f3da9050ba7c8da4abb01321836", # noqa + "trade_id": "120a94680f0cff61cdf3b123260a98253649a2b9762b7d2dead60caad96276e4", + "offer": "000000030000000000000000000000000000000000000000000000000000000000000000785bba8a904677219cb83f053b1bcf3b5ed13d35895c5babb007c67727e743590000000000000000ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0000000000000000000000000000000000000000000000000000000000000000080ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ff0affff04ff02ffff04ff03ff80808080ffff04ffff01ffff333effff02ffff03ff05ffff01ff04ffff04ff0cffff04ffff02ff1effff04ff02ffff04ff09ff80808080ff808080ffff02ff16ffff04ff02ffff04ff19ffff04ffff02ff0affff04ff02ffff04ff0dff80808080ff808080808080ff8080ff0180ffff02ffff03ff05ffff01ff02ffff03ffff15ff29ff8080ffff01ff04ffff04ff08ff0980ffff02ff16ffff04ff02ffff04ff0dffff04ff0bff808080808080ffff01ff088080ff0180ffff010b80ff0180ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff1effff04ff02ffff04ff09ff80808080ffff02ff1effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ff018080808080ff01808080ffffa00000000000000000000000000000000000000000000000000000000000000000ffffa00000000000000000000000000000000000000000000000000000000000000000ff01ff8080808032dbe6d545f24635c7871ea53c623c358d7cea8f5e27a983ba6e5c0bf35fa24386ab01fbd8342f8e1dac10d6e906cef3892857bd1865b6fd7ed4b01b39d568b50000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c5880ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff01ffff33ffa0846b58db3bd246785e202eeddfbb46acaf267f011307437cd4e0841f3da751f6ff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c58ffa0846b58db3bd246785e202eeddfbb46acaf267f011307437cd4e0841f3da751f68080ffff3eff248080ff018080808080ff01808080ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa01804338c97f989c78d88716206c0f27315f3eb7d59417ab2eacee20f0a7ff60bff0180ff01ffff808080a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa478416871446884ef363bd105960c464b4208a293b348f0f1c2e12140df38469450000000000000001ff02ffff01ff02ffff01ff02ffff03ffff18ff2fff3480ffff01ff04ffff04ff20ffff04ff2fff808080ffff04ffff02ff3effff04ff02ffff04ff05ffff04ffff02ff2affff04ff02ffff04ff27ffff04ffff02ffff03ff77ffff01ff02ff36ffff04ff02ffff04ff09ffff04ff57ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ffff011d80ff0180ffff04ffff02ffff03ff77ffff0181b7ffff015780ff0180ff808080808080ffff04ff77ff808080808080ffff02ff3affff04ff02ffff04ff05ffff04ffff02ff0bff5f80ffff01ff8080808080808080ffff01ff088080ff0180ffff04ffff01ffffffff4947ff0233ffff0401ff0102ffffff20ff02ffff03ff05ffff01ff02ff32ffff04ff02ffff04ff0dffff04ffff0bff3cffff0bff34ff2480ffff0bff3cffff0bff3cffff0bff34ff2c80ff0980ffff0bff3cff0bffff0bff34ff8080808080ff8080808080ffff010b80ff0180ffff02ffff03ffff22ffff09ffff0dff0580ff2280ffff09ffff0dff0b80ff2280ffff15ff17ffff0181ff8080ffff01ff0bff05ff0bff1780ffff01ff088080ff0180ff02ffff03ff0bffff01ff02ffff03ffff02ff26ffff04ff02ffff04ff13ff80808080ffff01ff02ffff03ffff20ff1780ffff01ff02ffff03ffff09ff81b3ffff01818f80ffff01ff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff808080808080ffff01ff04ffff04ff23ffff04ffff02ff36ffff04ff02ffff04ff09ffff04ff53ffff04ffff02ff2effff04ff02ffff04ff05ff80808080ff808080808080ff738080ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff34ff8080808080808080ff0180ffff01ff088080ff0180ffff01ff04ff13ffff02ff3affff04ff02ffff04ff05ffff04ff1bffff04ff17ff8080808080808080ff0180ffff01ff02ffff03ff17ff80ffff01ff088080ff018080ff0180ffffff02ffff03ffff09ff09ff3880ffff01ff02ffff03ffff18ff2dffff010180ffff01ff0101ff8080ff0180ff8080ff0180ff0bff3cffff0bff34ff2880ffff0bff3cffff0bff3cffff0bff34ff2c80ff0580ffff0bff3cffff02ff32ffff04ff02ffff04ff07ffff04ffff0bff34ff3480ff8080808080ffff0bff34ff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ffff21ff17ffff09ff0bff158080ffff01ff04ff30ffff04ff0bff808080ffff01ff088080ff0180ff018080ffff04ffff01ffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da9ffff04ffff01ff02ffff01ff02ffff01ff02ff3effff04ff02ffff04ff05ffff04ffff02ff2fff5f80ffff04ff80ffff04ffff04ffff04ff0bffff04ff17ff808080ffff01ff808080ffff01ff8080808080808080ffff04ffff01ffffff0233ff04ff0101ffff02ff02ffff03ff05ffff01ff02ff1affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ffff0bff12ffff0bff2cff1080ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff1affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ffff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff2effff04ff02ffff04ff09ff80808080ffff02ff2effff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff02ffff03ff0bffff01ff02ffff03ffff09ff23ff1880ffff01ff02ffff03ffff18ff81b3ff2c80ffff01ff02ffff03ffff20ff1780ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff33ffff04ff2fffff04ff5fff8080808080808080ffff01ff088080ff0180ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff0180ffff01ff02ffff03ffff09ff23ffff0181e880ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ffff02ffff03ffff22ffff09ffff02ff2effff04ff02ffff04ff53ff80808080ff82014f80ffff20ff5f8080ffff01ff02ff53ffff04ff818fffff04ff82014fffff04ff81b3ff8080808080ffff01ff088080ff0180ffff04ff2cff8080808080808080ffff01ff04ff13ffff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff17ffff04ff2fffff04ff5fff80808080808080808080ff018080ff0180ffff01ff04ffff04ff18ffff04ffff02ff16ffff04ff02ffff04ff05ffff04ff27ffff04ffff0bff2cff82014f80ffff04ffff02ff2effff04ff02ffff04ff818fff80808080ffff04ffff0bff2cff0580ff8080808080808080ff378080ff81af8080ff0180ff018080ffff04ffff01a0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c2ffff04ffff01ffa0000000000000000000000000000000000000000000000000000000000000000080ffff04ffff01a057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a9ffff04ffff01ff02ffff01ff02ffff01ff02ffff03ff0bffff01ff02ffff03ffff09ff05ffff1dff0bffff1effff0bff0bffff02ff06ffff04ff02ffff04ff17ff8080808080808080ffff01ff02ff17ff2f80ffff01ff088080ff0180ffff01ff04ffff04ff04ffff04ff05ffff04ffff02ff06ffff04ff02ffff04ff17ff80808080ff80808080ffff02ff17ff2f808080ff0180ffff04ffff01ff32ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff06ffff04ff02ffff04ff09ff80808080ffff02ff06ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ff018080ffff04ffff01b0a3b0219722055ac0a66cd9de5cd3e86962d8c8ec6abb801b57e5c77ed98453b02ceae0e19548f6d4fc20b3a2ec82aa90ff018080ff018080808080ff01808080ffffa09563629e653a9fc3c65f55947883a47e062e6b67394091228ec01352ff78f333ff0180ff01ffffff80ffff02ffff01ff02ffff01ff02ffff03ff5fffff01ff02ff3affff04ff02ffff04ff0bffff04ff17ffff04ff2fffff04ff5fffff04ff81bfffff04ff82017fffff04ff8202ffffff04ffff02ff05ff8205ff80ff8080808080808080808080ffff01ff04ffff04ff10ffff01ff81ff8080ffff02ff05ff8205ff808080ff0180ffff04ffff01ffffff49ff3f02ff04ff0101ffff02ffff02ffff03ff05ffff01ff02ff2affff04ff02ffff04ff0dffff04ffff0bff12ffff0bff2cff1480ffff0bff12ffff0bff12ffff0bff2cff3c80ff0980ffff0bff12ff0bffff0bff2cff8080808080ff8080808080ffff010b80ff0180ff02ffff03ff05ffff01ff02ffff03ffff02ff3effff04ff02ffff04ff82011fffff04ff27ffff04ff4fff808080808080ffff01ff02ff3affff04ff02ffff04ff0dffff04ff1bffff04ff37ffff04ff6fffff04ff81dfffff04ff8201bfffff04ff82037fffff04ffff04ffff04ff28ffff04ffff0bffff02ff26ffff04ff02ffff04ff11ffff04ffff02ff26ffff04ff02ffff04ff13ffff04ff82027fffff04ffff02ff36ffff04ff02ffff04ff82013fff80808080ffff04ffff02ff36ffff04ff02ffff04ff819fff80808080ffff04ffff02ff36ffff04ff02ffff04ff13ff80808080ff8080808080808080ffff04ffff02ff36ffff04ff02ffff04ff09ff80808080ff808080808080ffff012480ff808080ff8202ff80ff8080808080808080808080ffff01ff088080ff0180ffff018202ff80ff0180ffffff0bff12ffff0bff2cff3880ffff0bff12ffff0bff12ffff0bff2cff3c80ff0580ffff0bff12ffff02ff2affff04ff02ffff04ff07ffff04ffff0bff2cff2c80ff8080808080ffff0bff2cff8080808080ff02ffff03ffff07ff0580ffff01ff0bffff0102ffff02ff36ffff04ff02ffff04ff09ff80808080ffff02ff36ffff04ff02ffff04ff0dff8080808080ffff01ff0bffff0101ff058080ff0180ffff02ffff03ff1bffff01ff02ff2effff04ff02ffff04ffff02ffff03ffff18ffff0101ff1380ffff01ff0bffff0102ff2bff0580ffff01ff0bffff0102ff05ff2b8080ff0180ffff04ffff04ffff17ff13ffff0181ff80ff3b80ff8080808080ffff010580ff0180ff02ffff03ff17ffff01ff02ffff03ffff09ff05ffff02ff2effff04ff02ffff04ff13ffff04ff27ff808080808080ffff01ff02ff3effff04ff02ffff04ff05ffff04ff1bffff04ff37ff808080808080ffff01ff088080ff0180ffff01ff010180ff0180ff018080ffff04ffff01ff01ffff3fffa0fccf087e5b81be2137cfaa35e65cc4e4a25183108907dad33c6d622e8e78349e80ffff81e8ff0bffffffffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c5880ffa057bfd1cb0adda3d94315053fda723f2028320faa8338225d99f629e3d46d43a980ff808080ffff33ffa0b6565d3afb87a60cfdf66bc56cca80b14afc2be649971c8df647ce617b442e6eff01ffffa0a14daf55d41ced6419bcd011fbc1f74ab9567fe55340d88435aa6493d628fa47ffa0de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c58ffa0b6565d3afb87a60cfdf66bc56cca80b14afc2be649971c8df647ce617b442e6e808080ffff04ffff01ffffa07faa3253bfddd1e0decb0906b2dc6247bbc4cf608f58345d173adb63e8b47c9fffa07acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3a0eff07522495060c066f66f32acc2a77e3a3e737aca8baea4d1a64ea4cdc13da980ffff04ffff01ffa0a04d9f57764f54a43e4030befb4d80026e870519aaa66334aef8304f5d0393c280ffff04ffff01ffffa07f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab58080ff018080808080ffff80ff80ff80ff80ff808080808090ce896a92f47ab8adec0d7e977795762d85660495f2a6a0c025aec987faac7ec2bd78c17fdcfe126c35fb52ebe75c610df4013ecc5036e95168aba697678d89ab7718664439302d32f1eb85bf263461855c7f3da9050ba7c8da4abb01321836", # noqa "taker": [ { "store_id": "7acfcbd1ed73bfe2b698508f4ea5ed353c60ace154360272ce91f9ab0c8423c3", @@ -1545,25 +1554,29 @@ class MakeAndTakeReference: }, maker_inclusions=[{"key": b"\x10".hex(), "value": b"\x01\x10".hex()}], taker_inclusions=[{"key": b"\x10".hex(), "value": b"\x02\x10".hex()}], - trade_id="728e96f4404b6b7e7eb09df29b2ce77d9319fc796a0ea2d0d553c1904acf6cc2", + trade_id="2dc357c29da4362c6c63dbad128086062d53180395ae86b485dd429ce3791c37", maker_root_history=[bytes32.from_hexstr("de4ec93c032f5117d8af076dfc86faa5987a6c0b1d52ffc9cf0dfa43989d8c58")], taker_root_history=[bytes32.from_hexstr("7f3e180acdf046f955d3440bb3a16dfd6f5a46c809cee98e7514127327b1cab5")], ) @pytest.mark.parametrize( - argnames="reference", - argvalues=[ - pytest.param(make_one_take_one_reference, id="one for one"), - pytest.param(make_one_take_one_same_values_reference, id="one for one same values"), - pytest.param(make_two_take_one_reference, id="two for one"), - pytest.param(make_one_take_two_reference, id="one for two"), - pytest.param(make_one_existing_take_one_reference, id="one existing for one"), - pytest.param(make_one_take_one_existing_reference, id="one for one existing"), - pytest.param(make_one_upsert_take_one_reference, id="one upsert for one"), - pytest.param(make_one_take_one_upsert_reference, id="one for one upsert"), - pytest.param(make_one_take_one_unpopulated_reference, id="one for one unpopulated"), + "reference, offer_setup", + [ + pytest.param(make_one_take_one_reference, (True, True), id="one for one new/new batch_update"), + pytest.param(make_one_take_one_reference, (True, False), id="one for one new/old batch_update"), + pytest.param(make_one_take_one_reference, (False, True), id="one for one old/new batch_update"), + pytest.param(make_one_take_one_reference, (False, False), id="one for one old/old batch_update"), + pytest.param(make_one_take_one_same_values_reference, (True, True), id="one for one same values"), + pytest.param(make_two_take_one_reference, (True, True), id="two for one"), + pytest.param(make_one_take_two_reference, (True, True), id="one for two"), + pytest.param(make_one_existing_take_one_reference, (True, True), id="one existing for one"), + pytest.param(make_one_take_one_existing_reference, (True, True), id="one for one existing"), + pytest.param(make_one_upsert_take_one_reference, (True, True), id="one upsert for one"), + pytest.param(make_one_take_one_upsert_reference, (True, True), id="one for one upsert"), + pytest.param(make_one_take_one_unpopulated_reference, (True, True), id="one for one unpopulated"), ], + indirect=["offer_setup"], ) @pytest.mark.anyio async def test_make_and_take_offer(offer_setup: OfferSetup, reference: MakeAndTakeReference) -> None: @@ -2295,7 +2308,7 @@ async def test_wallet_log_in_changes_active_fingerprint( mnemonic = create_mnemonic() assert wallet_rpc_api.service.local_keychain is not None - private_key = wallet_rpc_api.service.local_keychain.add_private_key(mnemonic=mnemonic) + private_key = wallet_rpc_api.service.local_keychain.add_key(mnemonic_or_pk=mnemonic) secondary_fingerprint: int = private_key.get_g1().get_fingerprint() await wallet_rpc_api.log_in(request={"fingerprint": primary_fingerprint}) @@ -3106,7 +3119,7 @@ async def test_pagination_cmds( if max_page_size is None or max_page_size == 100: assert keys == { "keys": ["0x61616161", "0x6161"], - "root_hash": "0x3f4ae7b8e10ef48b3114843537d5def989ee0a3b6568af7e720a71730f260fa1", + "root_hash": "0x889a4a61b17be799ae9d36831246672ef857a24091f54481431a83309d4e890e", "success": True, "total_bytes": 6, "total_pages": 1, @@ -3126,7 +3139,7 @@ async def test_pagination_cmds( "value": "0x6161", }, ], - "root_hash": "0x3f4ae7b8e10ef48b3114843537d5def989ee0a3b6568af7e720a71730f260fa1", + "root_hash": "0x889a4a61b17be799ae9d36831246672ef857a24091f54481431a83309d4e890e", "success": True, "total_bytes": 9, "total_pages": 1, @@ -3143,7 +3156,7 @@ async def test_pagination_cmds( elif max_page_size == 5: assert keys == { "keys": ["0x61616161"], - "root_hash": "0x3f4ae7b8e10ef48b3114843537d5def989ee0a3b6568af7e720a71730f260fa1", + "root_hash": "0x889a4a61b17be799ae9d36831246672ef857a24091f54481431a83309d4e890e", "success": True, "total_bytes": 6, "total_pages": 2, @@ -3157,7 +3170,7 @@ async def test_pagination_cmds( "value": "0x61", } ], - "root_hash": "0x3f4ae7b8e10ef48b3114843537d5def989ee0a3b6568af7e720a71730f260fa1", + "root_hash": "0x889a4a61b17be799ae9d36831246672ef857a24091f54481431a83309d4e890e", "success": True, "total_bytes": 9, "total_pages": 2, diff --git a/chia/_tests/core/data_layer/test_data_store.py b/chia/_tests/core/data_layer/test_data_store.py index e27f4ff0d851..289efd6ff97f 100644 --- a/chia/_tests/core/data_layer/test_data_store.py +++ b/chia/_tests/core/data_layer/test_data_store.py @@ -372,12 +372,21 @@ async def test_get_ancestors_optimized(data_store: DataStore, store_id: bytes32) "use_optimized", [True, False], ) -async def test_batch_update(data_store: DataStore, store_id: bytes32, use_optimized: bool, tmp_path: Path) -> None: - num_batches = 10 - num_ops_per_batch = 100 if use_optimized else 10 - saved_roots: List[Root] = [] +@pytest.mark.parametrize( + "num_batches", + [1, 5, 10, 25], +) +async def test_batch_update( + data_store: DataStore, + store_id: bytes32, + use_optimized: bool, + tmp_path: Path, + num_batches: int, +) -> None: + total_operations = 1000 if use_optimized else 100 + num_ops_per_batch = total_operations // num_batches saved_batches: List[List[Dict[str, Any]]] = [] - + saved_kv: List[List[TerminalNode]] = [] db_uri = generate_in_memory_db_uri() async with DataStore.managed(database=db_uri, uri=True) as single_op_data_store: await single_op_data_store.create_tree(store_id, status=Status.COMMITTED) @@ -444,16 +453,21 @@ async def test_batch_update(data_store: DataStore, store_id: bytes32, use_optimi if (operation + 1) % num_ops_per_batch == 0: saved_batches.append(batch) batch = [] - root = await single_op_data_store.get_tree_root(store_id=store_id) - saved_roots.append(root) + current_kv = await single_op_data_store.get_keys_values(store_id=store_id) + assert {kv.key: kv.value for kv in current_kv} == keys_values + saved_kv.append(current_kv) for batch_number, batch in enumerate(saved_batches): assert len(batch) == num_ops_per_batch await data_store.insert_batch(store_id, batch, status=Status.COMMITTED) root = await data_store.get_tree_root(store_id) assert root.generation == batch_number + 1 - assert root.node_hash == saved_roots[batch_number].node_hash assert root.node_hash is not None + current_kv = await data_store.get_keys_values(store_id=store_id) + # Get the same keys/values, but possibly stored in other order. + assert {node.key: node.value for node in current_kv} == { + node.key: node.value for node in saved_kv[batch_number] + } queue: List[bytes32] = [root.node_hash] ancestors: Dict[bytes32, bytes32] = {} while len(queue) > 0: @@ -1516,6 +1530,18 @@ def id(self) -> str: return f"pre={self.pre},count={self.count}" +@dataclass +class BatchesInsertBenchmarkCase: + count: int + batch_count: int + limit: float + marks: Marks = () + + @property + def id(self) -> str: + return f"count={self.count},batch_count={self.batch_count}" + + @datacases( BatchInsertBenchmarkCase( pre=0, @@ -1537,6 +1563,11 @@ def id(self) -> str: count=1_000, limit=36, ), + BatchInsertBenchmarkCase( + pre=10_000, + count=25_000, + limit=52, + ), ) @pytest.mark.anyio async def test_benchmark_batch_insert_speed( @@ -1574,6 +1605,40 @@ async def test_benchmark_batch_insert_speed( ) +@datacases( + BatchesInsertBenchmarkCase( + count=50, + batch_count=200, + limit=195, + ), +) +@pytest.mark.anyio +async def test_benchmark_batch_insert_speed_multiple_batches( + data_store: DataStore, + store_id: bytes32, + benchmark_runner: BenchmarkRunner, + case: BatchesInsertBenchmarkCase, +) -> None: + r = random.Random() + r.seed("shadowlands", version=2) + + with benchmark_runner.assert_runtime(seconds=case.limit): + for batch in range(case.batch_count): + changelist = [ + { + "action": "insert", + "key": x.to_bytes(32, byteorder="big", signed=False), + "value": bytes(r.getrandbits(8) for _ in range(10000)), + } + for x in range(batch * case.count, (batch + 1) * case.count) + ] + await data_store.insert_batch( + store_id=store_id, + changelist=changelist, + status=Status.COMMITTED, + ) + + @pytest.mark.anyio async def test_delete_store_data(raw_data_store: DataStore) -> None: store_id = bytes32(b"\0" * 32) @@ -1925,6 +1990,38 @@ async def test_insert_key_already_present(data_store: DataStore, store_id: bytes await data_store.insert(key=key, value=value, store_id=store_id, reference_node_hash=None, side=None) +@pytest.mark.anyio +async def test_update_keys(data_store: DataStore, store_id: bytes32) -> None: + num_keys = 10 + missing_keys = 50 + num_values = 10 + new_keys = 10 + for value in range(num_values): + changelist: List[Dict[str, Any]] = [] + bytes_value = value.to_bytes(4, byteorder="big") + for key in range(num_keys + missing_keys): + bytes_key = key.to_bytes(4, byteorder="big") + changelist.append({"action": "delete", "key": bytes_key}) + for key in range(num_keys): + bytes_key = key.to_bytes(4, byteorder="big") + changelist.append({"action": "insert", "key": bytes_key, "value": bytes_value}) + + await data_store.insert_batch( + store_id=store_id, + changelist=changelist, + status=Status.COMMITTED, + ) + for key in range(num_keys): + bytes_key = key.to_bytes(4, byteorder="big") + node = await data_store.get_node_by_key(bytes_key, store_id) + assert node.value == bytes_value + for key in range(num_keys, num_keys + missing_keys): + bytes_key = key.to_bytes(4, byteorder="big") + with pytest.raises(KeyNotFoundError, match=f"Key not found: {bytes_key.hex()}"): + await data_store.get_node_by_key(bytes_key, store_id) + num_keys += new_keys + + @pytest.mark.anyio async def test_migration_unknown_version(data_store: DataStore) -> None: async with data_store.db_wrapper.writer() as writer: diff --git a/chia/_tests/core/full_node/stores/test_coin_store.py b/chia/_tests/core/full_node/stores/test_coin_store.py index 94ef7aca59ce..3e5849dc6f39 100644 --- a/chia/_tests/core/full_node/stores/test_coin_store.py +++ b/chia/_tests/core/full_node/stores/test_coin_store.py @@ -708,6 +708,30 @@ async def test_batch_no_puzzle_hashes(db_version: int) -> None: assert height is None +@pytest.mark.anyio +async def test_duplicate_by_hint(db_version: int) -> None: + async with DBConnection(db_version) as db_wrapper: + # Initialize coin and hint stores. + coin_store = await CoinStore.create(db_wrapper) + hint_store = await HintStore.create(db_wrapper) + + cr = CoinRecord( + Coin(std_hash(b"Parent Coin Id"), std_hash(b"Puzzle Hash"), uint64(100)), + uint32(10), + uint32(0), + False, + uint64(12321312), + ) + + await coin_store._add_coin_records([cr]) + await hint_store.add_hints([(cr.coin.name(), cr.coin.puzzle_hash)]) + + coin_states, height = await coin_store.batch_coin_states_by_puzzle_hashes([cr.coin.puzzle_hash]) + + assert coin_states == [cr.coin_state] + assert height is None + + @pytest.mark.anyio async def test_unsupported_version() -> None: with pytest.raises(RuntimeError, match="CoinStore does not support database schema v1"): diff --git a/chia/_tests/core/full_node/test_full_node.py b/chia/_tests/core/full_node/test_full_node.py index 766ce051078e..4438b6d1595e 100644 --- a/chia/_tests/core/full_node/test_full_node.py +++ b/chia/_tests/core/full_node/test_full_node.py @@ -185,7 +185,7 @@ async def test_block_compression( ph, DEFAULT_TX_CONFIG, ) - await wallet.wallet_state_manager.add_pending_transactions([tr]) + [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -224,7 +224,7 @@ async def check_transaction_confirmed(transaction) -> bool: ph, DEFAULT_TX_CONFIG, ) - await wallet.wallet_state_manager.add_pending_transactions([tr]) + [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -267,7 +267,7 @@ async def check_transaction_confirmed(transaction) -> bool: ph, DEFAULT_TX_CONFIG, ) - await wallet.wallet_state_manager.add_pending_transactions([tr]) + [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -279,7 +279,7 @@ async def check_transaction_confirmed(transaction) -> bool: ph, DEFAULT_TX_CONFIG, ) - await wallet.wallet_state_manager.add_pending_transactions([tr]) + [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -292,7 +292,7 @@ async def check_transaction_confirmed(transaction) -> bool: ph, DEFAULT_TX_CONFIG, ) - await wallet.wallet_state_manager.add_pending_transactions([tr]) + [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -305,7 +305,7 @@ async def check_transaction_confirmed(transaction) -> bool: ph, DEFAULT_TX_CONFIG, ) - await wallet.wallet_state_manager.add_pending_transactions([tr]) + [tr] = await wallet.wallet_state_manager.add_pending_transactions([tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, @@ -403,7 +403,7 @@ async def check_transaction_confirmed(transaction) -> bool: additions=new_spend_bundle.additions(), removals=new_spend_bundle.removals(), ) - await wallet.wallet_state_manager.add_pending_transactions([new_tr]) + [new_tr] = await wallet.wallet_state_manager.add_pending_transactions([new_tr]) await time_out_assert( 10, full_node_2.full_node.mempool_manager.get_spendbundle, diff --git a/chia/_tests/core/full_node/test_transactions.py b/chia/_tests/core/full_node/test_transactions.py index 3aabba0b2e26..65ccad4173b6 100644 --- a/chia/_tests/core/full_node/test_transactions.py +++ b/chia/_tests/core/full_node/test_transactions.py @@ -86,7 +86,7 @@ async def peak_height(fna: FullNodeAPI): [tx] = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( 10, ph1, DEFAULT_TX_CONFIG, 0 ) - await wallet_0.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) await time_out_assert( 10, @@ -160,7 +160,7 @@ async def test_mempool_tx_sync(self, three_nodes_two_wallets, self_hostname, see [tx] = await wallet_0.wallet_state_manager.main_wallet.generate_signed_transaction( 10, bytes32.random(seeded_random), DEFAULT_TX_CONFIG, 0 ) - await wallet_0.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet_0.wallet_state_manager.add_pending_transactions([tx]) await time_out_assert( 10, diff --git a/chia/_tests/core/mempool/test_mempool.py b/chia/_tests/core/mempool/test_mempool.py index b76ab24f2a5f..a67d7f0d5027 100644 --- a/chia/_tests/core/mempool/test_mempool.py +++ b/chia/_tests/core/mempool/test_mempool.py @@ -2693,11 +2693,6 @@ async def test_invalid_coin_spend_coin(self, one_node_one_block, wallet_a): assert res == (MempoolInclusionStatus.FAILED, Err.INVALID_SPEND_BUNDLE) -@pytest.fixture(name="softfork", scope="function", params=[False, True]) -def softfork_fixture(request): - return request.param - - coins = make_test_coins() diff --git a/chia/_tests/core/mempool/test_mempool_manager.py b/chia/_tests/core/mempool/test_mempool_manager.py index 87b061e02ff1..ef2fe1d1145c 100644 --- a/chia/_tests/core/mempool/test_mempool_manager.py +++ b/chia/_tests/core/mempool/test_mempool_manager.py @@ -1030,7 +1030,7 @@ async def get_unspent_lineage_info_for_puzzle_hash(_: bytes32) -> Optional[Unspe async def make_and_send_big_cost_sb(coin: Coin) -> None: conditions = [] g1 = G1Element() - for _ in range(144): + for _ in range(169): conditions.append([ConditionOpcode.AGG_SIG_UNSAFE, g1, IDENTITY_PUZZLE_HASH]) conditions.append([ConditionOpcode.CREATE_COIN, IDENTITY_PUZZLE_HASH, coin.amount - 10_000_000]) # Create a spend bundle with a big enough cost that gets it close to the limit @@ -1040,7 +1040,7 @@ async def make_and_send_big_cost_sb(coin: Coin) -> None: mempool_manager, coins = await setup_mempool_with_coins( coin_amounts=list(range(1_000_000_000, 1_000_000_030)), max_block_clvm_cost=550_000_000, - max_tx_clvm_cost=uint64(550_000_000 * 0.6), + max_tx_clvm_cost=uint64(550_000_000), mempool_block_buffer=20, ) # Create the spend bundles with a big enough cost that they get close to the limit @@ -1588,6 +1588,7 @@ async def make_setup_and_coins( [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]) 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) @@ -1605,6 +1606,7 @@ async def make_setup_and_coins( [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]) assert tx_a.spend_bundle is not None assert tx_b.spend_bundle is not None assert tx_c.spend_bundle is not None @@ -1621,6 +1623,7 @@ async def make_setup_and_coins( [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]) 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) @@ -1658,6 +1661,7 @@ async def make_setup_and_coins( coins={coins[5].coin}, extra_conditions=(e_announcement,), ) + [tx_d, tx_f], _ = await wallet.wallet_state_manager.sign_transactions([tx_d, tx_f]) 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 @@ -1686,6 +1690,7 @@ async def make_setup_and_coins( [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]) 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 36d3313b929e..dad89e6bd06b 100644 --- a/chia/_tests/core/mempool/test_mempool_performance.py +++ b/chia/_tests/core/mempool/test_mempool_performance.py @@ -47,6 +47,7 @@ async def test_mempool_update_performance( 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]) 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_merkle_set.py b/chia/_tests/core/test_merkle_set.py index 80d953a71152..239e889a3370 100644 --- a/chia/_tests/core/test_merkle_set.py +++ b/chia/_tests/core/test_merkle_set.py @@ -8,13 +8,12 @@ from typing import List, Optional, Tuple import pytest -from chia_rs import Coin, compute_merkle_set_root +from chia_rs import Coin, MerkleSet, compute_merkle_set_root, confirm_included_already_hashed from chia.simulator.block_tools import BlockTools from chia.types.blockchain_format.sized_bytes import bytes32 from chia.util.hash import std_hash from chia.util.ints import uint64 -from chia.util.merkle_set import MerkleSet, confirm_included_already_hashed from chia.util.misc import to_batches from chia.wallet.util.wallet_sync_utils import validate_additions, validate_removals @@ -53,21 +52,21 @@ def hashdown(buf: bytes) -> bytes32: @pytest.mark.anyio async def test_merkle_set_invalid_hash_size() -> None: # this is too large - with pytest.raises(AssertionError): + with pytest.raises(ValueError): MerkleSet([bytes([0x80] + [0] * 32)]) # type: ignore[list-item] with pytest.raises(ValueError, match="could not convert slice to array"): compute_merkle_set_root([bytes([0x80] + [0] * 32)]) # this is too small - with pytest.raises(AssertionError): + with pytest.raises(ValueError): MerkleSet([bytes([0x80] + [0] * 30)]) # type: ignore[list-item] with pytest.raises(ValueError, match="could not convert slice to array"): compute_merkle_set_root([bytes([0x80] + [0] * 30)]) # empty - with pytest.raises(AssertionError): + with pytest.raises(ValueError): MerkleSet([b""]) # type: ignore[list-item] with pytest.raises(ValueError, match="could not convert slice to array"): diff --git a/chia/_tests/core/test_rpc_util.py b/chia/_tests/core/test_rpc_util.py index a4c0b429ccc0..8153459314c7 100644 --- a/chia/_tests/core/test_rpc_util.py +++ b/chia/_tests/core/test_rpc_util.py @@ -8,6 +8,7 @@ from chia.rpc.util import marshal from chia.util.ints import uint32 from chia.util.streamable import Streamable, streamable +from chia.wallet.util.clvm_streamable import clvm_streamable @streamable @@ -51,3 +52,36 @@ async def test_rpc_endpoint(self: None, request: TestRequestType) -> TestRespons }, }, ) == {"qat": ["foofoo", "1", "ff", "qux"], "sub": {"qux": "qux"}} + + +@clvm_streamable +@dataclass(frozen=True) +class ClvmSubObject(Streamable): + qux: bytes + + +@streamable +@dataclass(frozen=True) +class TestClvmRequestType(Streamable): + sub: ClvmSubObject + + +@streamable +@dataclass(frozen=True) +class TestClvmResponseObject(Streamable): + sub: ClvmSubObject + + +@pytest.mark.anyio +async def test_clvm_streamable_marshalling() -> None: + @marshal + async def test_rpc_endpoint(self: None, request: TestClvmRequestType) -> TestClvmResponseObject: + return TestClvmResponseObject(request.sub) + + assert await test_rpc_endpoint( + None, + { + "sub": "ff81ff80", + "CHIP-0029": True, + }, + ) == {"sub": "ff81ff80"} diff --git a/chia/_tests/core/util/test_keychain.py b/chia/_tests/core/util/test_keychain.py index 4299cecaa550..cea338da1d11 100644 --- a/chia/_tests/core/util/test_keychain.py +++ b/chia/_tests/core/util/test_keychain.py @@ -66,13 +66,13 @@ def test_basic_add_delete(self, empty_temp_file_keyring: TempKeyring, seeded_ran with pytest.raises(ValueError, match="'ZZZZZZ' is not in the mnemonic dictionary; may be misspelled"): bytes_from_mnemonic(" ".join(bad_mnemonic)) - kc.add_private_key(mnemonic) + kc.add_key(mnemonic) assert kc._get_free_private_key_index() == 1 assert len(kc.get_all_private_keys()) == 1 - kc.add_private_key(mnemonic_2) + kc.add_key(mnemonic_2) with pytest.raises(KeychainFingerprintExists) as e: - kc.add_private_key(mnemonic_2) + kc.add_key(mnemonic_2) assert e.value.fingerprint == fingerprint_2 assert kc._get_free_private_key_index() == 2 assert len(kc.get_all_private_keys()) == 2 @@ -95,9 +95,9 @@ def test_basic_add_delete(self, empty_temp_file_keyring: TempKeyring, seeded_ran assert kc._get_free_private_key_index() == 0 assert len(kc.get_all_private_keys()) == 0 - kc.add_private_key(bytes_to_mnemonic(bytes32.random(seeded_random))) - kc.add_private_key(bytes_to_mnemonic(bytes32.random(seeded_random))) - kc.add_private_key(bytes_to_mnemonic(bytes32.random(seeded_random))) + kc.add_key(bytes_to_mnemonic(bytes32.random(seeded_random))) + kc.add_key(bytes_to_mnemonic(bytes32.random(seeded_random))) + kc.add_key(bytes_to_mnemonic(bytes32.random(seeded_random))) assert len(kc.get_all_public_keys()) == 3 @@ -105,7 +105,7 @@ def test_basic_add_delete(self, empty_temp_file_keyring: TempKeyring, seeded_ran assert kc.get_first_public_key() is not None kc.delete_all_keys() - kc.add_private_key(bytes_to_mnemonic(bytes32.random(seeded_random))) + kc.add_key(bytes_to_mnemonic(bytes32.random(seeded_random))) assert kc.get_first_public_key() is not None def test_add_private_key_label(self, empty_temp_file_keyring: TempKeyring): @@ -115,26 +115,26 @@ def test_add_private_key_label(self, empty_temp_file_keyring: TempKeyring): key_data_1 = KeyData.generate(label="key_1") key_data_2 = KeyData.generate(label=None) - keychain.add_private_key(mnemonic=key_data_0.mnemonic_str(), label=key_data_0.label) + keychain.add_key(mnemonic_or_pk=key_data_0.mnemonic_str(), label=key_data_0.label) assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True) # Try to add a new key with an existing label should raise with pytest.raises(KeychainLabelExists) as e: - keychain.add_private_key(mnemonic=key_data_1.mnemonic_str(), label=key_data_0.label) + keychain.add_key(mnemonic_or_pk=key_data_1.mnemonic_str(), label=key_data_0.label) assert e.value.fingerprint == key_data_0.fingerprint assert e.value.label == key_data_0.label # Adding the same key with a valid label should work fine - keychain.add_private_key(mnemonic=key_data_1.mnemonic_str(), label=key_data_1.label) + keychain.add_key(mnemonic_or_pk=key_data_1.mnemonic_str(), label=key_data_1.label) assert key_data_1 == keychain.get_key(key_data_1.fingerprint, include_secrets=True) # Trying to add an existing key should not have an impact on the existing label with pytest.raises(KeychainFingerprintExists): - keychain.add_private_key(mnemonic=key_data_0.mnemonic_str(), label="other label") + keychain.add_key(mnemonic_or_pk=key_data_0.mnemonic_str(), label="other label") assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True) # Adding a key with no label should not assign any label - keychain.add_private_key(mnemonic=key_data_2.mnemonic_str(), label=key_data_2.label) + keychain.add_key(mnemonic_or_pk=key_data_2.mnemonic_str(), label=key_data_2.label) assert key_data_2 == keychain.get_key(key_data_2.fingerprint, include_secrets=True) # All added keys should still be valid with their label @@ -148,7 +148,7 @@ def test_bip39_eip2333_test_vector(self, empty_temp_file_keyring: TempKeyring): mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" print("entropy to seed:", mnemonic_to_seed(mnemonic).hex()) - master_sk = kc.add_private_key(mnemonic) + master_sk = kc.add_key(mnemonic) tv_master_int = 8075452428075949470768183878078858156044736575259233735633523546099624838313 tv_child_int = 18507161868329770878190303689452715596635858303241878571348190917018711023613 assert master_sk == PrivateKey.from_bytes(tv_master_int.to_bytes(32, "big")) @@ -309,7 +309,7 @@ async def test_get_key(include_secrets: bool, get_temp_keyring: Keychain): with pytest.raises(KeychainFingerprintNotFound): keychain.get_key(expected_keys[-1].fingerprint, include_secrets) # Add it and validate all keys - keychain.add_private_key(mnemonic_str) + keychain.add_key(mnemonic_str) assert all(keychain.get_key(key_data.fingerprint, include_secrets) == key_data for key_data in expected_keys) # Remove 10 keys and validate the result `get_key` for each of them after each removal while len(expected_keys) > 0: @@ -335,7 +335,7 @@ async def test_get_keys(include_secrets: bool, get_temp_keyring: Keychain): if not include_secrets: key_data = replace(key_data, secrets=None) expected_keys.append(key_data) - keychain.add_private_key(mnemonic_str) + keychain.add_key(mnemonic_str) assert keychain.get_keys(include_secrets) == expected_keys # Remove all 10 keys and validate the result of `get_keys` after each removal while len(expected_keys) > 0: @@ -351,7 +351,7 @@ async def test_set_label(get_temp_keyring: Keychain) -> None: keychain: Keychain = get_temp_keyring # Generate a key and add it without label key_data_0 = KeyData.generate(label=None) - keychain.add_private_key(mnemonic=key_data_0.mnemonic_str(), label=None) + keychain.add_key(mnemonic_or_pk=key_data_0.mnemonic_str(), label=None) assert key_data_0 == keychain.get_key(key_data_0.fingerprint, include_secrets=True) # Set a label and validate it key_data_0 = replace(key_data_0, label="key_0") @@ -364,7 +364,7 @@ async def test_set_label(get_temp_keyring: Keychain) -> None: # Add a second key key_data_1 = KeyData.generate(label="key_1") assert key_data_1.label is not None - keychain.add_private_key(key_data_1.mnemonic_str()) + keychain.add_key(key_data_1.mnemonic_str()) # Try to set the already existing label for the second key with pytest.raises(KeychainLabelExists) as e: keychain.set_label(fingerprint=key_data_1.fingerprint, label=key_data_0.label) @@ -393,7 +393,7 @@ async def test_set_label(get_temp_keyring: Keychain) -> None: async def test_set_label_invalid_labels(label: str, message: str, get_temp_keyring: Keychain) -> None: keychain: Keychain = get_temp_keyring key_data = KeyData.generate() - keychain.add_private_key(key_data.mnemonic_str()) + keychain.add_key(key_data.mnemonic_str()) with pytest.raises(KeychainLabelInvalid, match=message) as e: keychain.set_label(key_data.fingerprint, label) assert e.value.label == label @@ -417,7 +417,7 @@ def assert_delete_raises(): assert_delete_raises() for key in [key_data_0, key_data_1]: - keychain.add_private_key(mnemonic=key.mnemonic_str(), label=key.label) + keychain.add_key(mnemonic_or_pk=key.mnemonic_str(), label=key.label) assert key == keychain.get_key(key.fingerprint, include_secrets=True) # Delete the label of the first key, validate it was removed and make sure the other key retains its label keychain.delete_label(key_data_0.fingerprint) @@ -446,7 +446,7 @@ async def test_delete_drops_labels(get_temp_keyring: Keychain, delete_all: bool) labels = [f"key_{i}" for i in range(5)] keys = [KeyData.generate(label=label) for label in labels] for key_data in keys: - keychain.add_private_key(mnemonic=key_data.mnemonic_str(), label=key_data.label) + keychain.add_key(mnemonic_or_pk=key_data.mnemonic_str(), label=key_data.label) assert key_data == keychain.get_key(key_data.fingerprint, include_secrets=True) assert key_data.label is not None assert keychain.keyring_wrapper.get_label(key_data.fingerprint) == key_data.label diff --git a/chia/_tests/farmer_harvester/test_farmer_harvester.py b/chia/_tests/farmer_harvester/test_farmer_harvester.py index f3aa9f965d1a..02a52b356093 100644 --- a/chia/_tests/farmer_harvester/test_farmer_harvester.py +++ b/chia/_tests/farmer_harvester/test_farmer_harvester.py @@ -60,7 +60,7 @@ async def test_start_with_empty_keychain( assert not farmer.started # Add a key to the keychain, this should lead to the start task passing # `setup_keys` and set `Farmer.initialized` - bt.local_keychain.add_private_key(generate_mnemonic()) + bt.local_keychain.add_key(generate_mnemonic()) await time_out_assert(5, farmer_is_started, True, farmer) assert not farmer.started @@ -127,7 +127,7 @@ async def handshake_done() -> bool: async with farmer_service.manage(): await time_out_assert(5, handshake_task_active, True) assert not await handshake_done() - bt.local_keychain.add_private_key(generate_mnemonic()) + bt.local_keychain.add_key(generate_mnemonic()) await time_out_assert(5, farmer_is_started, True, farmer) await time_out_assert(5, handshake_task_active, False) await time_out_assert(5, handshake_done, True) diff --git a/chia/_tests/pools/test_pool_puzzles_lifecycle.py b/chia/_tests/pools/test_pool_puzzles_lifecycle.py index 1c9f5d417017..eb1548a01fd6 100644 --- a/chia/_tests/pools/test_pool_puzzles_lifecycle.py +++ b/chia/_tests/pools/test_pool_puzzles_lifecycle.py @@ -15,6 +15,7 @@ SINGLETON_MOD_HASH, create_absorb_spend, create_p2_singleton_puzzle, + create_p2_singleton_puzzle_hash, create_pooling_inner_puzzle, create_travel_spend, create_waiting_room_inner_puzzle, @@ -189,6 +190,12 @@ def test_pool_lifecycle(self): DELAY_PH, ) p2_singleton_ph: bytes32 = p2_singleton_puz.get_tree_hash() + assert p2_singleton_ph == create_p2_singleton_puzzle_hash( + SINGLETON_MOD_HASH, + launcher_id, + DELAY_TIME, + DELAY_PH, + ) assert uncurry_pool_waitingroom_inner_puzzle(pool_wr_innerpuz) == ( starting_ph, relative_lock_height, diff --git a/chia/_tests/pools/test_pool_rpc.py b/chia/_tests/pools/test_pool_rpc.py index 58ca7e82595d..3be906e6893f 100644 --- a/chia/_tests/pools/test_pool_rpc.py +++ b/chia/_tests/pools/test_pool_rpc.py @@ -390,11 +390,11 @@ def mempool_empty() -> bool: status: PoolWalletInfo = (await client.pw_status(some_wallet.id()))[0] assert (await some_wallet.get_pool_wallet_index()) < 5 auth_sk = find_authentication_sk( - [some_wallet.wallet_state_manager.private_key], status.current.owner_pubkey + [some_wallet.wallet_state_manager.get_master_private_key()], status.current.owner_pubkey ) assert auth_sk is not None owner_sk = find_owner_sk( - [some_wallet.wallet_state_manager.private_key], status.current.owner_pubkey + [some_wallet.wallet_state_manager.get_master_private_key()], status.current.owner_pubkey ) assert owner_sk is not None assert owner_sk[0] != auth_sk diff --git a/chia/_tests/simulation/test_simulation.py b/chia/_tests/simulation/test_simulation.py index b88f404de350..f97c72b560a5 100644 --- a/chia/_tests/simulation/test_simulation.py +++ b/chia/_tests/simulation/test_simulation.py @@ -218,7 +218,7 @@ async def test_simulator_auto_farm_and_get_coins( DEFAULT_TX_CONFIG, uint64(0), ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) # 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 @@ -393,7 +393,7 @@ async def test_wait_transaction_records_entered_mempool( tx_config=DEFAULT_TX_CONFIG, coins={coin}, ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) await full_node_api.wait_transaction_records_entered_mempool(records=[tx]) assert tx.spend_bundle is not None @@ -445,7 +445,8 @@ async def test_process_transactions( ] for tx in transactions: assert tx.spend_bundle is not None, "the above created transaction is missing the expected spend bundle" - await wallet.wallet_state_manager.add_pending_transactions([tx]) + + transactions = await wallet.wallet_state_manager.add_pending_transactions(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 c12913c55350..aaad90485728 100644 --- a/chia/_tests/simulation/test_simulator.py +++ b/chia/_tests/simulation/test_simulator.py @@ -133,7 +133,7 @@ async def test_wait_transaction_records_entered_mempool( tx_config=DEFAULT_TX_CONFIG, coins={coin}, ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) await full_node_api.wait_transaction_records_entered_mempool(records=[tx]) assert tx.spend_bundle is not None @@ -168,7 +168,7 @@ async def test_process_transaction_records( tx_config=DEFAULT_TX_CONFIG, coins={coin}, ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) await full_node_api.process_transaction_records(records=[tx]) assert full_node_api.full_node.coin_store.get_coin_record(coin.name()) is not None diff --git a/chia/_tests/util/misc.py b/chia/_tests/util/misc.py index f3a753aae6be..4b58fc6d6eb6 100644 --- a/chia/_tests/util/misc.py +++ b/chia/_tests/util/misc.py @@ -383,7 +383,6 @@ class BenchmarkRunner: test_id: Optional[TestId] = None overhead: Optional[float] = None - @functools.wraps(_AssertRuntime) def assert_runtime(self, *args: Any, **kwargs: Any) -> _AssertRuntime: kwargs.setdefault("enable_assertion", self.enable_assertion) kwargs.setdefault("overhead", self.overhead) diff --git a/chia/_tests/util/test_misc.py b/chia/_tests/util/test_misc.py index 3c4125598871..5331f70763de 100644 --- a/chia/_tests/util/test_misc.py +++ b/chia/_tests/util/test_misc.py @@ -325,10 +325,7 @@ async def wait_for_valued_event_waiters( ) -> None: with anyio.fail_after(delay=adjusted_timeout(timeout)): for delay in backoff_times(): - # ignoring the type since i'm hacking into the private attribute - # hopefully this is ok for testing and if it becomes invalid we - # will end up with an exception and can adjust then - if len(event._event._waiters) >= count: # type: ignore[attr-defined] + if len(event._event._waiters) >= count: return await anyio.sleep(delay) diff --git a/chia/_tests/util/test_replace_str_to_bytes.py b/chia/_tests/util/test_replace_str_to_bytes.py new file mode 100644 index 000000000000..88d552337520 --- /dev/null +++ b/chia/_tests/util/test_replace_str_to_bytes.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +import pytest +from chia_rs import ConsensusConstants + +from chia.consensus.constants import replace_str_to_bytes +from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.util.ints import uint8, uint16, uint32, uint64, uint128 + +test_constants = ConsensusConstants( + SLOT_BLOCKS_TARGET=uint32(32), + MIN_BLOCKS_PER_CHALLENGE_BLOCK=uint8(16), + MAX_SUB_SLOT_BLOCKS=uint32(128), + NUM_SPS_SUB_SLOT=uint32(64), + SUB_SLOT_ITERS_STARTING=uint64(2**27), + DIFFICULTY_CONSTANT_FACTOR=uint128(2**67), + DIFFICULTY_STARTING=uint64(7), + DIFFICULTY_CHANGE_MAX_FACTOR=uint32(3), + SUB_EPOCH_BLOCKS=uint32(384), + EPOCH_BLOCKS=uint32(4608), + SIGNIFICANT_BITS=uint8(8), + DISCRIMINANT_SIZE_BITS=uint16(1024), + NUMBER_ZERO_BITS_PLOT_FILTER=uint8(9), + MIN_PLOT_SIZE=uint8(32), + MAX_PLOT_SIZE=uint8(50), + SUB_SLOT_TIME_TARGET=uint16(600), + NUM_SP_INTERVALS_EXTRA=uint8(3), + MAX_FUTURE_TIME2=uint32(2 * 60), + NUMBER_OF_TIMESTAMPS=uint8(11), + GENESIS_CHALLENGE=bytes32.fromhex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"), + AGG_SIG_ME_ADDITIONAL_DATA=bytes.fromhex("ccd5bb71183532bff220ba46c268991a3ff07eb358e8255a65c30a2dce0e5fbb"), + GENESIS_PRE_FARM_POOL_PUZZLE_HASH=bytes32.fromhex( + "d23da14695a188ae5708dd152263c4db883eb27edeb936178d4d988b8f3ce5fc" + ), + GENESIS_PRE_FARM_FARMER_PUZZLE_HASH=bytes32.fromhex( + "3d8765d3a597ec1d99663f6c9816d915b9f68613ac94009884c4addaefcce6af" + ), + MAX_VDF_WITNESS_SIZE=uint8(64), + MEMPOOL_BLOCK_BUFFER=uint8(10), + MAX_COIN_AMOUNT=uint64((1 << 64) - 1), + MAX_BLOCK_COST_CLVM=uint64(11000000000), + COST_PER_BYTE=uint64(12000), + WEIGHT_PROOF_THRESHOLD=uint8(2), + BLOCKS_CACHE_SIZE=uint32(4608 + (128 * 4)), + WEIGHT_PROOF_RECENT_BLOCKS=uint32(1000), + MAX_BLOCK_COUNT_PER_REQUESTS=uint32(32), + MAX_GENERATOR_SIZE=uint32(1000000), + MAX_GENERATOR_REF_LIST_SIZE=uint32(512), + POOL_SUB_SLOT_ITERS=uint64(37600000000), + SOFT_FORK2_HEIGHT=uint32(0), + SOFT_FORK4_HEIGHT=uint32(5650000), + HARD_FORK_HEIGHT=uint32(5496000), + HARD_FORK_FIX_HEIGHT=uint32(5496000), + PLOT_FILTER_128_HEIGHT=uint32(10542000), + PLOT_FILTER_64_HEIGHT=uint32(15592000), + PLOT_FILTER_32_HEIGHT=uint32(20643000), +) + + +def test_replace_str_to_bytes() -> None: + test2 = replace_str_to_bytes( + test_constants, + GENESIS_PRE_FARM_FARMER_PUZZLE_HASH="0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + ) + assert test2 == test_constants.replace( + GENESIS_PRE_FARM_FARMER_PUZZLE_HASH=bytes32.fromhex( + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ) + ) + assert test2.GENESIS_PRE_FARM_FARMER_PUZZLE_HASH == bytes32.fromhex( + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + ) + + +def test_replace_str_to_bytes_none() -> None: + test2 = replace_str_to_bytes(test_constants) + assert test2 == test_constants + + +def test_replace_str_to_bytes_uint8() -> None: + test2 = replace_str_to_bytes(test_constants, MIN_BLOCKS_PER_CHALLENGE_BLOCK=uint8(8)) + assert test2 == test_constants.replace(MIN_BLOCKS_PER_CHALLENGE_BLOCK=uint8(8)) + assert test2.MIN_BLOCKS_PER_CHALLENGE_BLOCK == 8 + + +def test_replace_str_to_bytes_invalid_field(caplog: pytest.LogCaptureFixture) -> None: + # invalid field + test2 = replace_str_to_bytes( + test_constants, FOOBAR="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + ) + assert test2 == test_constants + assert 'invalid key in network configuration (config.yaml) "FOOBAR". Ignoring' in caplog.text + + +def test_replace_str_to_bytes_deprecated_field(caplog: pytest.LogCaptureFixture) -> None: + # invalid, but deprecated, field. We don't warn on it + test2 = replace_str_to_bytes(test_constants, NETWORK_TYPE=1) + assert test2 == test_constants + assert caplog.text == "" + + +def test_replace_str_to_bytes_invalid_value() -> None: + # invalid value + with pytest.raises(ValueError, match="non-hexadecimal number found in"): + replace_str_to_bytes( + test_constants, + GENESIS_PRE_FARM_FARMER_PUZZLE_HASH="fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + ) diff --git a/chia/_tests/wallet/cat_wallet/test_cat_wallet.py b/chia/_tests/wallet/cat_wallet/test_cat_wallet.py index 1a25748ebc48..89101736e5f8 100644 --- a/chia/_tests/wallet/cat_wallet/test_cat_wallet.py +++ b/chia/_tests/wallet/cat_wallet/test_cat_wallet.py @@ -27,7 +27,7 @@ from chia.wallet.cat_wallet.cat_utils import CAT_MOD, construct_cat_puzzle from chia.wallet.cat_wallet.cat_wallet import CATWallet from chia.wallet.derivation_record import DerivationRecord -from chia.wallet.derive_keys import _derive_path_unhardened, master_sk_to_wallet_sk_unhardened_intermediate +from chia.wallet.derive_keys import master_pk_to_wallet_pk_unhardened from chia.wallet.lineage_proof import LineageProof from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_pk from chia.wallet.util.tx_config import DEFAULT_COIN_SELECTION_CONFIG, DEFAULT_TX_CONFIG @@ -336,7 +336,7 @@ async def test_cat_spend(wallet_environments: WalletTestFramework) -> None: 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) - await wallet2.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet2.wallet_state_manager.add_pending_transactions(tx_records) await wallet_environments.process_pending_states( [ @@ -453,7 +453,7 @@ async def test_cat_reuse_address(self_hostname: str, two_wallet_nodes: OldSimula tx_records = await cat_wallet.generate_signed_transaction( [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG.override(reuse_puzhash=True), fee=uint64(1) ) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + 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(): assert tx_record.to_puzzle_hash == cat_2_hash @@ -482,7 +482,7 @@ async def test_cat_reuse_address(self_hostname: str, two_wallet_nodes: OldSimula 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) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet2.wallet_state_manager.add_pending_transactions(tx_records) await time_out_assert(15, full_node_api.txs_in_mempool, True, tx_records) @@ -602,7 +602,7 @@ async def test_cat_doesnt_see_eve(self_hostname: str, two_wallet_nodes: OldSimul tx_records = await cat_wallet.generate_signed_transaction( [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, fee=uint64(1) ) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) await full_node_api.process_transaction_records(records=tx_records) await time_out_assert(30, wallet.get_confirmed_balance, funds - 101) @@ -618,7 +618,7 @@ async def test_cat_doesnt_see_eve(self_hostname: str, two_wallet_nodes: OldSimul [tx_record] = await wallet.wallet_state_manager.main_wallet.generate_signed_transaction( uint64(10), cc2_ph, DEFAULT_TX_CONFIG ) - await wallet.wallet_state_manager.add_pending_transactions([tx_record]) + [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) await full_node_api.process_transaction_records(records=[tx_record]) id = cat_wallet_2.id() @@ -701,7 +701,7 @@ async def test_cat_spend_multiple( tx_records = await cat_wallet_0.generate_signed_transaction( [uint64(60), uint64(20)], [cat_1_hash, cat_2_hash], DEFAULT_TX_CONFIG ) - await wallet_0.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet_0.wallet_state_manager.add_pending_transactions(tx_records) await full_node_api.process_transaction_records(records=tx_records) await time_out_assert(20, cat_wallet_0.get_confirmed_balance, 20) @@ -716,10 +716,10 @@ 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) - await wallet_1.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet_1.wallet_state_manager.add_pending_transactions(tx_records) tx_records_2 = await cat_wallet_2.generate_signed_transaction([uint64(20)], [cat_hash], DEFAULT_TX_CONFIG) - await wallet_2.wallet_state_manager.add_pending_transactions(tx_records_2) + tx_records_2 = await wallet_2.wallet_state_manager.add_pending_transactions(tx_records_2) await full_node_api.process_transaction_records(records=[*tx_records, *tx_records_2]) @@ -742,7 +742,7 @@ async def test_cat_spend_multiple( [uint64(30)], [cat_hash], DEFAULT_TX_CONFIG, memos=[[b"too"], [b"many"], [b"memos"]] ) - await wallet_1.wallet_state_manager.add_pending_transactions(tx_records_3) + 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) txs = await wallet_1.wallet_state_manager.tx_store.get_transactions_between(cat_wallet_1.id(), 0, 100000) for tx in txs: @@ -809,7 +809,7 @@ async def test_cat_max_amount_send( tx_records = await cat_wallet.generate_signed_transaction( amounts, puzzle_hashes, DEFAULT_TX_CONFIG, coins={spent_coint} ) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) await full_node_api.process_transaction_records(records=tx_records) await asyncio.sleep(2) @@ -899,7 +899,7 @@ async def test_cat_hint( [uint64(60)], [cat_2_hash], DEFAULT_TX_CONFIG, memos=[[cat_2_hash]] ) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) await full_node_api.process_transaction_records(records=tx_records) @@ -927,7 +927,7 @@ async def test_cat_hint( [uint64(10)], [cat_2_hash], DEFAULT_TX_CONFIG, memos=[[cat_2_hash]] ) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet.wallet_state_manager.add_pending_transactions(tx_records) await full_node_api.process_transaction_records(records=tx_records) @@ -945,7 +945,7 @@ async def test_cat_hint( 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) - await wallet.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await wallet2.wallet_state_manager.add_pending_transactions(tx_records) await full_node_api.process_transaction_records(records=tx_records) @@ -989,8 +989,9 @@ async def test_cat_change_detection( # Mint CAT to ourselves, immediately spend it to an unhinted puzzle hash that we have manually added to the DB # We should pick up this coin as balance even though it is unhinted because it is "change" - intermediate_sk_un = master_sk_to_wallet_sk_unhardened_intermediate(wallet_node_0.wallet_state_manager.private_key) - pubkey_unhardened = _derive_path_unhardened(intermediate_sk_un, [100000000]).get_g1() + pubkey_unhardened = master_pk_to_wallet_pk_unhardened( + wallet_node_0.wallet_state_manager.root_pubkey, uint32(100000000) + ) inner_puzhash = puzzle_hash_for_pk(pubkey_unhardened) puzzlehash_unhardened = construct_cat_puzzle( CAT_MOD, Program.to(None).get_tree_hash(), inner_puzhash @@ -1025,7 +1026,7 @@ async def test_cat_change_detection( construct_cat_puzzle(CAT_MOD, Program.to(None).get_tree_hash(), our_puzzle).get_tree_hash(), cat_amount_0, ) - eve_spend = await wallet_node_0.wallet_state_manager.sign_transaction( + eve_spend, _ = await wallet_node_0.wallet_state_manager.sign_bundle( [ make_spend( cat_coin, @@ -1224,7 +1225,7 @@ async def test_cat_melt_balance(wallet_environments: WalletTestFramework) -> Non ) ], ) - signed_spend = await env.wallet_state_manager.sign_transaction(new_spend.coin_spends) + signed_spend, _ = await env.wallet_state_manager.sign_bundle(new_spend.coin_spends) await env.rpc_client.push_tx(signed_spend) await time_out_assert(10, simulator.tx_id_in_mempool, True, signed_spend.name()) diff --git a/chia/_tests/wallet/cat_wallet/test_trades.py b/chia/_tests/wallet/cat_wallet/test_trades.py index 1e612ffe4505..f8ebbe3336af 100644 --- a/chia/_tests/wallet/cat_wallet/test_trades.py +++ b/chia/_tests/wallet/cat_wallet/test_trades.py @@ -460,13 +460,18 @@ async def test_cat_trades( assert trade_make is not None peer = wallet_node_taker.get_full_node_peer() + [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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, wallet_environments.tx_config, fee=uint64(1), ) - await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -669,13 +674,18 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): 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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, wallet_environments.tx_config, fee=uint64(1), ) - await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -794,12 +804,17 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): 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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, wallet_environments.tx_config, ) - await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -987,12 +1002,17 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): 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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, wallet_environments.tx_config, ) - await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -1233,12 +1253,17 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): 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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, wallet_environments.tx_config, ) - await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -1361,12 +1386,17 @@ async def assert_trade_tx_number(wallet_node, trade_id, number): 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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, wallet_environments.tx_config, ) - await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -1601,10 +1631,16 @@ async def test_trade_cancellation(wallets_prefarm): # Due to current mempool rules, trying to force a take out of the mempool with a cancel will not work. # Uncomment this when/if it does + # [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( - # Offer.from_bytes(trade_make.offer), + # maker_offer, + # ) + # tx_records = await wallet_taker.wallet_state_manager.add_pending_transactions( + # tx_records, + # additional_signing_responses=signing_response, # ) - # await wallet_taker.wallet_state_manager.add_pending_transactions(tx_records) # 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 @@ -1621,7 +1657,7 @@ async def test_trade_cancellation(wallets_prefarm): txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=fee, secure=True ) - await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + 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) @@ -1662,7 +1698,7 @@ async def test_trade_cancellation(wallets_prefarm): txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True ) - await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + 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) @@ -1712,7 +1748,7 @@ async def test_trade_cancellation_balance_check(wallets_prefarm): txs = await trade_manager_maker.cancel_pending_offers( [trade_make.trade_id], DEFAULT_TX_CONFIG, fee=uint64(0), secure=True ) - await trade_manager_maker.wallet_state_manager.add_pending_transactions(txs) + txs = await trade_manager_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) @@ -1763,16 +1799,22 @@ async def test_trade_conflict(three_wallets_prefarm): 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]) tr1, txs1 = await trade_manager_taker.respond_to_offer(offer, peer, DEFAULT_TX_CONFIG, fee=uint64(10)) - await trade_manager_taker.wallet_state_manager.add_pending_transactions(txs1) + 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) # 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)) 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)) - await trade_manager_trader.wallet_state_manager.add_pending_transactions(txs2) + txs2 = await trade_manager_trader.wallet_state_manager.add_pending_transactions( + txs2, additional_signing_responses=signing_response + ) 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) @@ -1825,7 +1867,7 @@ async def test_trade_bad_spend(wallets_prefarm): bundle = dataclasses.replace(offer._bundle, 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)) - await trade_manager_taker.wallet_state_manager.add_pending_transactions(txs1) + txs1 = await trade_manager_taker.wallet_state_manager.add_pending_transactions(txs1, sign=False) wallet_node_taker.wallet_tx_resend_timeout_secs = 0 # don't wait for resend def check_wallet_cache_empty() -> bool: @@ -1881,8 +1923,13 @@ async def test_trade_high_fee(wallets_prefarm): 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)) - await trade_manager_taker.wallet_state_manager.add_pending_transactions(txs1) + 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) await time_out_assert(15, get_trade_and_status, TradeStatus.CONFIRMED, trade_manager_taker, tr1) @@ -1937,7 +1984,13 @@ async def test_aggregated_trade_state(wallets_prefarm): assert success is True assert trade_make_2 is not None - agg_offer = Offer.aggregate([Offer.from_bytes(trade_make_1.offer), Offer.from_bytes(trade_make_2.offer)]) + [offer_1], signing_response_1 = await wallet_node_maker.wallet_state_manager.sign_offers( + [Offer.from_bytes(trade_make_1.offer)] + ) + [offer_2], signing_response_2 = await wallet_node_maker.wallet_state_manager.sign_offers( + [Offer.from_bytes(trade_make_2.offer)] + ) + 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( @@ -1948,7 +2001,10 @@ async def test_aggregated_trade_state(wallets_prefarm): assert trade_take is not None assert tx_records is not None - await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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.wait_for_wallets_synced(wallet_nodes=[wallet_node_maker, wallet_node_taker], timeout=60) diff --git a/chia/_tests/wallet/dao_wallet/test_dao_wallets.py b/chia/_tests/wallet/dao_wallet/test_dao_wallets.py index cb229a1097e5..92cbfae2a463 100644 --- a/chia/_tests/wallet/dao_wallet/test_dao_wallets.py +++ b/chia/_tests/wallet/dao_wallet/test_dao_wallets.py @@ -152,7 +152,7 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA fee_for_cat=fee_for_cat, ) - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -199,7 +199,7 @@ 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) - await dao_wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await dao_wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) @@ -219,7 +219,7 @@ async def test_dao_creation(self_hostname: str, two_wallet_nodes: OldSimulatorsA # 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) - await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_txs) + cat_txs = await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_txs) await full_node_api.wait_transaction_records_entered_mempool(records=cat_txs, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) @@ -315,7 +315,7 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators treasury_id = dao_wallet_0.dao_info.treasury_id # Get the full node sim to process the wallet creation spend - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -328,7 +328,7 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators 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) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) + [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_wallet_synced(wallet_node=wallet_node_0, timeout=30) @@ -339,7 +339,7 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators 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() ) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([cat_funding_tx]) + [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) @@ -355,6 +355,7 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators 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) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) @@ -380,7 +381,7 @@ async def test_dao_funding(self_hostname: str, three_wallet_nodes: OldSimulators 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) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([close_tx_0]) + [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) 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) @@ -490,7 +491,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_issuance), dao_rules, DEFAULT_TX_CONFIG ) - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -527,7 +528,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato cat_tx = await cat_wallet_0.generate_signed_transaction( [cat_amt, cat_amt], [ph_1, ph_2], DEFAULT_TX_CONFIG, fee=base_fee ) - await wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) + 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) @@ -535,13 +536,14 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs_0) + 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) 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) await full_node_api.process_all_wallet_transactions(wallet_1, timeout=60) @@ -549,6 +551,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato 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) await full_node_api.process_all_wallet_transactions(wallet_2, timeout=60) @@ -561,7 +564,7 @@ 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) - await wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) + [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) 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) @@ -585,7 +588,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [proposal_tx] = await dao_wallet_0.generate_new_proposal( xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee ) - await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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) @@ -607,7 +610,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [proposal_tx] = await dao_wallet_0.generate_new_proposal( mint_proposal_inner, DEFAULT_TX_CONFIG, vote_amount=dao_cat_0_bal, fee=base_fee ) - await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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) @@ -647,7 +650,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [proposal_tx] = await dao_wallet_0.generate_new_proposal( xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee ) - await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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) @@ -660,7 +663,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [proposal_tx] = await dao_wallet_0.generate_new_proposal( xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, fee=base_fee ) - await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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) @@ -674,7 +677,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [vote_tx_1] = await dao_wallet_1.generate_proposal_vote_spend( prop_0.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG ) - await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_1]) + [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) @@ -682,7 +685,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [vote_tx_2] = await dao_wallet_2.generate_proposal_vote_spend( prop_0.proposal_id, dao_cat_2_bal, False, DEFAULT_TX_CONFIG ) - await wallet_2.wallet_state_manager.add_pending_transactions([vote_tx_2]) + [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) @@ -716,7 +719,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato # Proposal 0: Close close_tx_0 = await dao_wallet_0.create_proposal_close_spend(prop_0.proposal_id, DEFAULT_TX_CONFIG) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_0]) + [close_tx_0] = await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_0]) close_sb_0 = close_tx_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()) @@ -736,7 +739,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [vote_tx_1] = await dao_wallet_1.generate_proposal_vote_spend( prop_1.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG ) - await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_1]) + [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) @@ -750,7 +753,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_1_state["closable"] close_tx_1 = await dao_wallet_0.create_proposal_close_spend(prop_1.proposal_id, DEFAULT_TX_CONFIG) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_1]) + [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) 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) @@ -761,7 +764,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [vote_tx_2] = await dao_wallet_1.generate_proposal_vote_spend( prop_2.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG ) - await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_2]) + [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) @@ -775,7 +778,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_2_state["closable"] close_tx_2 = await dao_wallet_0.create_proposal_close_spend(prop_2.proposal_id, DEFAULT_TX_CONFIG) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_2]) + [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) 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) @@ -788,7 +791,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [vote_tx_3] = await dao_wallet_1.generate_proposal_vote_spend( prop_3.proposal_id, dao_cat_1_bal, False, DEFAULT_TX_CONFIG ) - await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_3]) + [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) @@ -802,7 +805,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato assert prop_3_state["closable"] close_tx_3 = await dao_wallet_0.create_proposal_close_spend(prop_3.proposal_id, DEFAULT_TX_CONFIG) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_3]) + [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) 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) @@ -819,7 +822,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato [vote_tx_4] = await dao_wallet_1.generate_proposal_vote_spend( prop_4.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG ) - await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx_4]) + [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) @@ -839,7 +842,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato close_tx_4 = await dao_wallet_0.create_proposal_close_spend( prop_4.proposal_id, DEFAULT_TX_CONFIG, self_destruct=True ) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx_4]) + [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) @@ -854,7 +857,7 @@ async def test_dao_proposals(self_hostname: str, three_wallet_nodes: OldSimulato 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)) - await wallet_0.wallet_state_manager.add_pending_transactions([free_tx]) + [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) 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) @@ -921,7 +924,7 @@ async def test_dao_proposal_partial_vote( ) # Get the full node sim to process the wallet creation spend - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -954,7 +957,7 @@ async def test_dao_proposal_partial_vote( xch_funds, DEFAULT_TX_CONFIG, ) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) + [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 isinstance(funding_sb, SpendBundle) @@ -974,8 +977,8 @@ async def test_dao_proposal_partial_vote( 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 cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) 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) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) @@ -985,7 +988,7 @@ async def test_dao_proposal_partial_vote( # 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) - await dao_cat_wallet_0.wallet_state_manager.add_pending_transactions(txs) + 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()) @@ -1007,7 +1010,7 @@ async def test_dao_proposal_partial_vote( [proposal_tx] = await dao_wallet_0.generate_new_proposal( mint_proposal_inner, DEFAULT_TX_CONFIG, vote_amount=vote_amount, fee=uint64(1000) ) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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 isinstance(proposal_sb, SpendBundle) @@ -1028,7 +1031,7 @@ 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) - await wallet_1.wallet_state_manager.add_pending_transactions(txs) + 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()) @@ -1039,7 +1042,7 @@ async def test_dao_proposal_partial_vote( [vote_tx] = await dao_wallet_1.generate_proposal_vote_spend( prop.proposal_id, dao_cat_1_bal // 2, True, DEFAULT_TX_CONFIG ) - await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx]) + [vote_tx] = await wallet_1.wallet_state_manager.add_pending_transactions([vote_tx]) 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()) @@ -1058,7 +1061,7 @@ async def test_dao_proposal_partial_vote( try: close_tx = await dao_wallet_0.create_proposal_close_spend(prop.proposal_id, DEFAULT_TX_CONFIG, fee=uint64(100)) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) + [close_tx] = await dao_wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) close_sb = close_tx.spend_bundle except Exception as e: # pragma: no cover print(e) @@ -1079,8 +1082,8 @@ async def test_dao_proposal_partial_vote( 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 wallet_1.wallet_state_manager.add_pending_transactions(cat_tx) 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) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(puzzle_hash_0)) @@ -2459,7 +2462,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula ) # Get the full node sim to process the wallet creation spend - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -2482,7 +2485,7 @@ 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) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) + [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) @@ -2507,7 +2510,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula assert dao_cat_wallet_2 cat_tx = await cat_wallet_0.generate_signed_transaction([100000, 100000], [ph_1, ph_2], DEFAULT_TX_CONFIG) - await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) + 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) @@ -2522,7 +2525,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) @@ -2539,7 +2542,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula [proposal_tx] = await dao_wallet_0.generate_new_proposal( xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, uint64(1000) ) - await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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) @@ -2563,13 +2566,13 @@ 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) - await wallet_1.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_1.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) - await wallet_2.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_2.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) @@ -2583,7 +2586,7 @@ async def test_dao_concurrency(self_hostname: str, three_wallet_nodes: OldSimula [vote_tx_2] = await dao_wallet_2.generate_proposal_vote_spend( prop.proposal_id, dao_cat_1_bal, True, DEFAULT_TX_CONFIG ) - await wallet_2.wallet_state_manager.add_pending_transactions([vote_tx_2]) + [vote_tx_2] = await wallet_2.wallet_state_manager.add_pending_transactions([vote_tx_2]) 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()) @@ -2853,7 +2856,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd ) # Get the full node sim to process the wallet creation spend - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -2891,7 +2894,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd xch_funds, DEFAULT_TX_CONFIG, ) - await dao_wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) + [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) @@ -2921,7 +2924,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd [ph_1], DEFAULT_TX_CONFIG, ) - await cat_wallet_0.wallet_state_manager.add_pending_transactions(cat_tx) + 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) @@ -2933,7 +2936,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) @@ -2950,7 +2953,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd [proposal_tx] = await dao_wallet_0.generate_new_proposal( xch_proposal_inner, DEFAULT_TX_CONFIG, dao_cat_0_bal, uint64(1000) ) - await wallet_0.wallet_state_manager.add_pending_transactions([proposal_tx]) + [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) @@ -2984,7 +2987,7 @@ 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) - await wallet_1.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_1.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) @@ -3019,7 +3022,7 @@ async def test_dao_reorgs(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd 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)) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) + [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) 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) @@ -3107,7 +3110,7 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn wallet_node_0.wallet_state_manager, wallet_0, uint64(cat_issuance), dao_rules, DEFAULT_TX_CONFIG ) - await wallet_node_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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) @@ -3127,31 +3130,31 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn # 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, 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) @@ -3161,7 +3164,7 @@ 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) - await wallet_0.wallet_state_manager.add_pending_transactions([funding_tx]) + [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) 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) @@ -3248,7 +3251,7 @@ async def test_dao_votes(self_hostname: str, three_wallet_nodes: OldSimulatorsAn 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) - await wallet_0.wallet_state_manager.add_pending_transactions([close_tx]) + [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) 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) @@ -3326,7 +3329,7 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd fee_for_cat=fee_for_cat, ) - await wallet_0.wallet_state_manager.add_pending_transactions(tx_queue) + 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.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,7 +3352,7 @@ 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) - await wallet_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.wait_transaction_records_entered_mempool(records=txs, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) @@ -3357,7 +3360,7 @@ async def test_dao_resync(self_hostname: str, two_wallet_nodes: OldSimulatorsAnd # 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) - await wallet_0.wallet_state_manager.add_pending_transactions(cat_txs) + cat_txs = await wallet_0.wallet_state_manager.add_pending_transactions(cat_txs) await full_node_api.wait_transaction_records_entered_mempool(records=cat_txs, timeout=60) await full_node_api.process_all_wallet_transactions(wallet_0, timeout=60) diff --git a/chia/_tests/wallet/db_wallet/test_dl_offers.py b/chia/_tests/wallet/db_wallet/test_dl_offers.py index 1f6321b628c0..7d57243b810b 100644 --- a/chia/_tests/wallet/db_wallet/test_dl_offers.py +++ b/chia/_tests/wallet/db_wallet/test_dl_offers.py @@ -66,7 +66,7 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: maker_root, DEFAULT_TX_CONFIG, fee=fee ) assert await dl_wallet_maker.get_latest_singleton(launcher_id_maker) is not None - await wsm_maker.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_maker.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) maker_funds -= fee maker_funds -= 1 @@ -76,7 +76,7 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: taker_root, DEFAULT_TX_CONFIG, fee=fee ) assert await dl_wallet_taker.get_latest_singleton(launcher_id_taker) is not None - await wsm_taker.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_taker.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) taker_funds -= fee taker_funds -= 1 @@ -139,8 +139,11 @@ 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( - Offer.from_bytes(offer_maker.offer), + maker_offer, peer, DEFAULT_TX_CONFIG, solver=Solver( @@ -170,7 +173,9 @@ async def test_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: ), fee=fee, ) - await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( + tx_records, additional_signing_responses=signing_response + ) assert offer_taker is not None assert tx_records is not None @@ -229,7 +234,7 @@ 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) - await wallet_node_taker.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_taker.wallet_state_manager.add_pending_transactions(txs) await full_node_api.process_transaction_records(records=txs) @@ -251,11 +256,11 @@ async def test_dl_offer_cancellation(wallets_prefarm: Any, trusted: bool) -> Non dl_record, std_record, launcher_id = await dl_wallet.generate_new_reporter(root, DEFAULT_TX_CONFIG) assert await dl_wallet.get_latest_singleton(launcher_id) is not None - await wsm.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet, launcher_id, root) dl_record_2, std_record_2, launcher_id_2 = await dl_wallet.generate_new_reporter(root, DEFAULT_TX_CONFIG) - await wsm.add_pending_transactions([dl_record_2, std_record_2]) + [dl_record_2, std_record_2] = await wsm.add_pending_transactions([dl_record_2, std_record_2]) await full_node_api.process_transaction_records(records=[dl_record_2, std_record_2]) trade_manager = wsm.trade_manager @@ -289,7 +294,7 @@ async def test_dl_offer_cancellation(wallets_prefarm: Any, trusted: bool) -> Non cancellation_txs = await trade_manager.cancel_pending_offers( [offer.trade_id], DEFAULT_TX_CONFIG, fee=uint64(2_000_000_000_000), secure=True ) - await trade_manager.wallet_state_manager.add_pending_transactions(cancellation_txs) + cancellation_txs = await trade_manager.wallet_state_manager.add_pending_transactions(cancellation_txs) assert len(cancellation_txs) == 2 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) @@ -325,7 +330,7 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: maker_root, DEFAULT_TX_CONFIG, fee=fee ) assert await dl_wallet_maker.get_latest_singleton(launcher_id_maker_1) is not None - await wsm_maker.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_maker.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) maker_funds -= fee maker_funds -= 1 @@ -334,7 +339,7 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: maker_root, DEFAULT_TX_CONFIG, fee=fee ) assert await dl_wallet_maker.get_latest_singleton(launcher_id_maker_2) is not None - await wsm_maker.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_maker.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) maker_funds -= fee maker_funds -= 1 @@ -344,7 +349,7 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: taker_root, DEFAULT_TX_CONFIG, fee=fee ) assert await dl_wallet_taker.get_latest_singleton(launcher_id_taker_1) is not None - await wsm_taker.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_taker.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) taker_funds -= fee taker_funds -= 1 @@ -353,7 +358,7 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: taker_root, DEFAULT_TX_CONFIG, fee=fee ) assert await dl_wallet_taker.get_latest_singleton(launcher_id_taker_2) is not None - await wsm_taker.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_taker.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) taker_funds -= fee taker_funds -= 1 @@ -418,8 +423,11 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: assert success is True assert offer_maker is not 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( - Offer.from_bytes(offer_maker.offer), + maker_offer, peer, DEFAULT_TX_CONFIG, solver=Solver( @@ -462,7 +470,9 @@ async def test_multiple_dl_offers(wallets_prefarm: Any, trusted: bool) -> None: ), fee=fee, ) - await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) + tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions( + tx_records, additional_signing_responses=signing_response + ) assert offer_taker is not None assert tx_records is not None diff --git a/chia/_tests/wallet/db_wallet/test_dl_wallet.py b/chia/_tests/wallet/db_wallet/test_dl_wallet.py index fecdb26ec47b..e3079d746b0d 100644 --- a/chia/_tests/wallet/db_wallet/test_dl_wallet.py +++ b/chia/_tests/wallet/db_wallet/test_dl_wallet.py @@ -79,7 +79,9 @@ async def test_initial_creation( assert await dl_wallet.get_latest_singleton(launcher_id) is not None - await wallet_node_0.wallet_state_manager.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + [dl_record, std_record] + ) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -134,7 +136,9 @@ async def test_get_owned_singletons( assert await dl_wallet.get_latest_singleton(launcher_id) is not None - await wallet_node_0.wallet_state_manager.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + [dl_record, std_record] + ) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -191,7 +195,9 @@ async def test_tracking_non_owned( assert await dl_wallet_0.get_latest_singleton(launcher_id) is not None - await wallet_node_0.wallet_state_manager.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + [dl_record, std_record] + ) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet_0, launcher_id) @@ -205,7 +211,7 @@ async def test_tracking_non_owned( 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) - await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.process_transaction_records(records=txs) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet_0, launcher_id) @@ -261,7 +267,9 @@ async def test_lifecycle( assert await dl_wallet.get_latest_singleton(launcher_id) is not None - await wallet_node_0.wallet_state_manager.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + [dl_record, std_record] + ) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -296,7 +304,7 @@ async def test_lifecycle( assert new_record != previous_record assert not new_record.confirmed - await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.process_transaction_records(records=txs) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -317,7 +325,7 @@ async def test_lifecycle( assert new_record != previous_record assert not new_record.confirmed - await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) await full_node_api.process_transaction_records(records=txs) await time_out_assert(15, is_singleton_confirmed, True, dl_wallet, launcher_id) @@ -382,7 +390,9 @@ async def is_singleton_confirmed(wallet: DataLayerWallet, lid: bytes32) -> bool: initial_record = await dl_wallet_0.get_latest_singleton(launcher_id) assert initial_record is not None - await wallet_node_0.wallet_state_manager.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wallet_node_0.wallet_state_manager.add_pending_transactions( + [dl_record, std_record] + ) await asyncio.wait_for( full_node_api.process_transaction_records(records=[dl_record, std_record]), timeout=adjusted_timeout(timeout=15), @@ -413,14 +423,14 @@ async def is_singleton_confirmed(wallet: DataLayerWallet, lid: bytes32) -> bool: assert initial_record != record_0 assert record_0 != record_1 - await wallet_node_1.wallet_state_manager.add_pending_transactions(report_txs) + report_txs = await wallet_node_1.wallet_state_manager.add_pending_transactions(report_txs) await asyncio.wait_for( full_node_api.wait_transaction_records_entered_mempool(records=report_txs), timeout=adjusted_timeout(timeout=15), ) - await wallet_node_0.wallet_state_manager.add_pending_transactions(update_txs) + update_txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(update_txs) await asyncio.wait_for( full_node_api.process_transaction_records(records=report_txs), timeout=adjusted_timeout(timeout=15) @@ -465,7 +475,7 @@ async def is_singleton_generation(wallet: DataLayerWallet, launcher_id: bytes32, ) record_1 = await dl_wallet_0.get_latest_singleton(launcher_id) assert record_1 is not None - 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(update_txs_1) await full_node_api.wait_transaction_records_entered_mempool(update_txs_1) # Delete any trace of that update @@ -478,7 +488,7 @@ async def is_singleton_generation(wallet: DataLayerWallet, launcher_id: bytes32, assert record_0 is not None assert record_0 != record_1 - 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(update_txs_0) await asyncio.wait_for( full_node_api.process_transaction_records(records=update_txs_1), timeout=adjusted_timeout(timeout=15) @@ -543,13 +553,13 @@ async def test_mirrors(wallets_prefarm: Any, trusted: bool) -> None: dl_record, std_record, launcher_id_1 = await dl_wallet_1.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG) assert await dl_wallet_1.get_latest_singleton(launcher_id_1) is not None - await wsm_1.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_1.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_1, launcher_id_1, bytes32([0] * 32)) dl_record, std_record, launcher_id_2 = await dl_wallet_2.generate_new_reporter(bytes32([0] * 32), DEFAULT_TX_CONFIG) assert await dl_wallet_2.get_latest_singleton(launcher_id_2) is not None - await wsm_2.add_pending_transactions([dl_record, std_record]) + [dl_record, std_record] = await wsm_2.add_pending_transactions([dl_record, std_record]) await full_node_api.process_transaction_records(records=[dl_record, std_record]) await time_out_assert(15, is_singleton_confirmed_and_root, True, dl_wallet_2, launcher_id_2, bytes32([0] * 32)) @@ -564,7 +574,7 @@ async def test_mirrors(wallets_prefarm: Any, trusted: bool) -> None: launcher_id_2, uint64(3), [b"foo", b"bar"], DEFAULT_TX_CONFIG, fee=uint64(1_999_999_999_999) ) additions: List[Coin] = [] - await wsm_1.add_pending_transactions(txs) + txs = await wsm_1.add_pending_transactions(txs) for tx in txs: if tx.spend_bundle is not None: additions.extend(tx.spend_bundle.additions()) @@ -580,7 +590,7 @@ async def test_mirrors(wallets_prefarm: Any, trusted: bool) -> None: ) txs = await dl_wallet_1.delete_mirror(mirror.coin_id, peer_1, DEFAULT_TX_CONFIG, fee=uint64(2_000_000_000_000)) - await wsm_1.add_pending_transactions(txs) + txs = await wsm_1.add_pending_transactions(txs) await full_node_api.process_transaction_records(records=txs) await time_out_assert(15, dl_wallet_1.get_mirrors_for_launcher, [], launcher_id_2) diff --git a/chia/_tests/wallet/did_wallet/test_did.py b/chia/_tests/wallet/did_wallet/test_did.py index 183d784d05c4..fd135f843443 100644 --- a/chia/_tests/wallet/did_wallet/test_did.py +++ b/chia/_tests/wallet/did_wallet/test_did.py @@ -209,7 +209,7 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes 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 ) - await did_wallet_0.wallet_state_manager.add_pending_transactions([message_tx]) + [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() @@ -234,7 +234,7 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes test_message_spend_bundle, ) assert txs[0].spend_bundle is not None - await did_wallet_2.wallet_state_manager.add_pending_transactions(txs) + 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() @@ -250,7 +250,7 @@ async def test_creation_from_backup_file(self, self_hostname, three_wallet_nodes some_ph = 32 * b"\2" txs = await did_wallet_2.create_exit_spend(some_ph, DEFAULT_TX_CONFIG) - await did_wallet_2.wallet_state_manager.add_pending_transactions(txs) + 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() @@ -375,7 +375,7 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w message_tx, message_spend_bundle, attest1 = await did_wallet.create_attestment( coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG ) - await did_wallet.wallet_state_manager.add_pending_transactions([message_tx]) + [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()) @@ -384,7 +384,7 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w message_tx2, message_spend_bundle2, attest2 = await did_wallet_2.create_attestment( coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG ) - await did_wallet_2.wallet_state_manager.add_pending_transactions([message_tx2]) + [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() @@ -404,7 +404,7 @@ async def test_did_recovery_with_multiple_backup_dids(self, self_hostname, two_w 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) - await did_wallet_4.wallet_state_manager.add_pending_transactions(txs) + 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() ) @@ -535,7 +535,7 @@ async def test_did_find_lost_did(self, self_hostname, two_wallet_nodes, trusted) 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) - await did_wallet.wallet_state_manager.add_pending_transactions(txs) + 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()) @@ -617,7 +617,7 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, 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) - await did_wallet.wallet_state_manager.add_pending_transactions(txs) + 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()) @@ -647,7 +647,7 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, message_tx, message_spend_bundle, attest_data = await did_wallet.create_attestment( coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG ) - await did_wallet.wallet_state_manager.add_pending_transactions([message_tx]) + [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()) @@ -660,7 +660,7 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, 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) - await did_wallet_3.wallet_state_manager.add_pending_transactions(txs) + 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() ) @@ -690,7 +690,7 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, message_tx, message_spend_bundle, attest1 = await did_wallet_3.create_attestment( coin.name(), new_ph, pubkey, DEFAULT_TX_CONFIG ) - await did_wallet_3.wallet_state_manager.add_pending_transactions([message_tx]) + [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() @@ -705,7 +705,7 @@ async def test_did_attest_after_recovery(self, self_hostname, two_wallet_nodes, 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) - await did_wallet_2.wallet_state_manager.add_pending_transactions(txs) + 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() @@ -781,7 +781,7 @@ async def test_did_transfer(self, self_hostname, two_wallet_nodes, with_recovery # Transfer DID new_puzhash = await wallet2.get_new_puzzlehash() txs = await did_wallet_1.transfer_did(new_puzhash, fee, with_recovery, DEFAULT_TX_CONFIG) - await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) + 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() ) @@ -858,7 +858,7 @@ async def test_update_recovery_list(self, self_hostname, two_wallet_nodes, trust 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) - await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) + txs = await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) await full_node_api.farm_blocks_to_wallet(1, wallet) await time_out_assert(15, did_wallet_1.get_confirmed_balance, 101) await time_out_assert(15, did_wallet_1.get_unconfirmed_balance, 101) @@ -937,7 +937,7 @@ async def test_get_info(self, self_hostname, two_wallet_nodes, trusted): [tx] = await wallet.generate_signed_transaction( odd_amount, ph1, DEFAULT_TX_CONFIG.override(excluded_coin_ids=[coin_id]), fee ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) await full_node_api.process_transaction_records(records=[tx]) await full_node_api.wait_for_wallet_synced(wallet_node=wallet_node_2, timeout=15) @@ -1058,7 +1058,7 @@ async def test_update_metadata(self, self_hostname, two_wallet_nodes, trusted): 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) - await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) + 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() ) @@ -1330,7 +1330,7 @@ async def test_did_resync(self, self_hostname, two_wallet_nodes, trusted) -> Non # 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) - await did_wallet_1.wallet_state_manager.add_pending_transactions(txs) + 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() ) 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 57c97f17b2cb..fa2e305042bf 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_1_offers.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_1_offers.py @@ -178,10 +178,15 @@ async def test_nft_offer_sell_nft( assert not mempool_not_empty(full_node_api) peer = wallet_node_taker.get_full_node_peer() + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) await time_out_assert(20, mempool_not_empty, True, full_node_api) @@ -319,10 +324,15 @@ async def test_nft_offer_request_nft( taker_fee = 1 peer = wallet_node_taker.get_full_node_peer() + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None @@ -472,10 +482,15 @@ async def test_nft_offer_sell_did_to_did( taker_fee = 1 peer = wallet_node_taker.get_full_node_peer() + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None assert tx_records is not None @@ -656,10 +671,15 @@ async def test_nft_offer_sell_nft_for_cat( taker_fee = 1 peer = wallet_node_taker.get_full_node_peer() + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None assert tx_records is not None @@ -846,10 +866,15 @@ async def test_nft_offer_request_nft_for_cat( taker_fee = 1 peer = wallet_node_taker.get_full_node_peer() + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=uint64(taker_fee) + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) await time_out_assert(20, mempool_not_empty, True, full_node_api) assert trade_take is not None assert tx_records is not None @@ -1362,10 +1387,13 @@ async def test_complex_nft_offer( assert success 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)] + ) if royalty_basis_pts_maker == 10000: with pytest.raises(ValueError): trade_take, tx_records = await trade_manager_taker.respond_to_offer( - Offer.from_bytes(trade_make.offer), + maker_offer, wallet_node_taker.get_full_node_peer(), DEFAULT_TX_CONFIG, fee=FEE, @@ -1374,12 +1402,14 @@ async def test_complex_nft_offer( return else: trade_take, tx_records = await trade_manager_taker.respond_to_offer( - Offer.from_bytes(trade_make.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) + 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) @@ -1475,13 +1505,18 @@ async def get_cat_wallet_and_check_balance(asset_id: str, wsm: Any) -> uint128: assert success 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( - Offer.from_bytes(trade_make.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) + 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 time_out_assert(20, mempool_not_empty, True, full_node_api) 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 dbe2d8623764..24a8239f52e7 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_bulk_mint.py @@ -118,9 +118,10 @@ async def test_nft_mint_from_did( sb = tx_records[0].spend_bundle assert sb is not None - await api_0.push_tx({"spend_bundle": bytes(sb).hex()}) + 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, sb.name()) + 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 time_out_assert(30, nft_count, mint_total, nft_wallet_taker) @@ -621,9 +622,10 @@ async def test_nft_mint_from_did_multiple_xch( sb = tx_records[0].spend_bundle assert sb is not None - await api_0.push_tx({"spend_bundle": bytes(sb).hex()}) + 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, sb.name()) + 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 time_out_assert(30, nft_count, mint_total, nft_wallet_taker) @@ -722,9 +724,10 @@ async def test_nft_mint_from_xch( sb = tx_records[0].spend_bundle assert sb is not None - await api_0.push_tx({"spend_bundle": bytes(sb).hex()}) + 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, sb.name()) + 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 time_out_assert(30, nft_count, mint_total, nft_wallet_taker) @@ -1039,9 +1042,10 @@ async def test_nft_mint_from_xch_multiple_xch( sb = tx_records[0].spend_bundle assert sb is not None - await api_0.push_tx({"spend_bundle": bytes(sb).hex()}) + 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, sb.name()) + 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 time_out_assert(30, nft_count, mint_total, nft_wallet_taker) diff --git a/chia/_tests/wallet/nft_wallet/test_nft_offers.py b/chia/_tests/wallet/nft_wallet/test_nft_offers.py index 88a9c6ad346e..7e592637b949 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_offers.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_offers.py @@ -144,14 +144,19 @@ async def test_nft_offer_with_fee( taker_fee = uint64(1) + [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( + [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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, tx_config, fee=taker_fee, ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -216,10 +221,13 @@ async def test_nft_offer_with_fee( taker_fee = uint64(1) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - Offer.from_bytes(trade_make.offer), peer, tx_config, fee=taker_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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) assert trade_take is not None assert tx_records is not None @@ -478,11 +486,16 @@ async def test_nft_offer_with_metadata_update( taker_fee = uint64(1) + [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=taker_fee + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) assert trade_take is not None assert tx_records is not None @@ -652,14 +665,19 @@ async def test_nft_offer_nft_for_cat( taker_fee = uint64(1) + [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( + [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( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, tx_config, fee=taker_fee, ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) + 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 @@ -736,10 +754,13 @@ async def test_nft_offer_nft_for_cat( taker_fee = uint64(1) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - Offer.from_bytes(trade_make.offer), peer, tx_config, fee=taker_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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) assert trade_take is not None assert tx_records is not None @@ -843,7 +864,7 @@ 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_maker.wallet_state_manager.add_pending_transactions(txs) + txs = await nft_wallet_taker.wallet_state_manager.add_pending_transactions(txs) for tx in txs: if tx.spend_bundle is not None: await time_out_assert_not_none( @@ -886,11 +907,16 @@ async def test_nft_offer_nft_for_nft( taker_fee = uint64(1) + [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( + [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( - Offer.from_bytes(trade_make.offer), peer, DEFAULT_TX_CONFIG, fee=taker_fee + 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 ) - tx_records = await trade_manager_taker.wallet_state_manager.add_pending_transactions(tx_records) assert trade_take is not None assert tx_records is not None @@ -1065,11 +1091,15 @@ async def test_nft_offer_nft0_and_xch_for_cat( assert error is None assert trade_make is not None + [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( + [Offer.from_bytes(trade_make.offer)] + ) + taker_fee = uint64(1) peer = wallet_node_1.get_full_node_peer() trade_take, tx_records = await trade_manager_taker.respond_to_offer( - Offer.from_bytes(trade_make.offer), + maker_offer, peer, tx_config, fee=taker_fee, @@ -1078,13 +1108,9 @@ async def test_nft_offer_nft0_and_xch_for_cat( assert trade_take is not None assert tx_records is not None - tx_records = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(tx_records) - for tx in tx_records: - 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() - ) - + 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.wait_for_wallets_synced(wallet_nodes=[wallet_node_0, wallet_node_1], timeout=20) @@ -1156,22 +1182,20 @@ async def test_nft_offer_nft0_and_xch_for_cat( assert error is None assert trade_make is not None + [maker_offer], signing_response = await wallet_node_0.wallet_state_manager.sign_offers( + [Offer.from_bytes(trade_make.offer)] + ) + taker_fee = uint64(1) - trade_take, tx_records = await trade_manager_taker.respond_to_offer( - Offer.from_bytes(trade_make.offer), peer, tx_config, fee=taker_fee - ) + 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 - tx_records = await nft_wallet_maker.wallet_state_manager.add_pending_transactions(tx_records) - for tx in tx_records: - 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() - ) - + 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) # 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 6ae15c79be9b..beac1c1e02f5 100644 --- a/chia/_tests/wallet/nft_wallet/test_nft_wallet.py +++ b/chia/_tests/wallet/nft_wallet/test_nft_wallet.py @@ -348,9 +348,9 @@ async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestF coins={coins[1].coin}, ) assert len(txs) == 1 - assert txs[0].spend_bundle is not None txs = await wallet_node_0.wallet_state_manager.add_pending_transactions(txs) assert txs[0].spend_bundle is not None + assert len(compute_memos(txs[0].spend_bundle)) > 0 await wallet_environments.process_pending_states( @@ -404,9 +404,9 @@ async def test_nft_wallet_creation_and_transfer(wallet_environments: WalletTestF coins={coins[0].coin}, ) assert len(txs) == 1 - assert txs[0].spend_bundle is not None txs = await wallet_node_1.wallet_state_manager.add_pending_transactions(txs) assert txs[0].spend_bundle is not None + assert len(compute_memos(txs[0].spend_bundle)) > 0 await wallet_environments.process_pending_states( @@ -1170,7 +1170,7 @@ 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 did_wallet.wallet_state_manager.add_pending_transactions(txs) + txs = await wallet_node_1.wallet_state_manager.add_pending_transactions(txs) await make_new_block_with(resp, full_node_api, ph) coins_response = await wait_rpc_state_condition( diff --git a/chia/_tests/wallet/rpc/test_wallet_rpc.py b/chia/_tests/wallet/rpc/test_wallet_rpc.py index e49120b5eb10..434f9af9c9af 100644 --- a/chia/_tests/wallet/rpc/test_wallet_rpc.py +++ b/chia/_tests/wallet/rpc/test_wallet_rpc.py @@ -67,15 +67,23 @@ from chia.wallet.cat_wallet.cat_constants import DEFAULT_CATS from chia.wallet.cat_wallet.cat_utils import CAT_MOD, construct_cat_puzzle from chia.wallet.cat_wallet.cat_wallet import CATWallet -from chia.wallet.conditions import ConditionValidTimes, CreateCoinAnnouncement, CreatePuzzleAnnouncement, Remark +from chia.wallet.conditions import ( + ConditionValidTimes, + CreateCoinAnnouncement, + CreatePuzzleAnnouncement, + Remark, + conditions_to_json_dicts, +) from chia.wallet.derive_keys import master_sk_to_wallet_sk, master_sk_to_wallet_sk_unhardened from chia.wallet.did_wallet.did_wallet import DIDWallet from chia.wallet.nft_wallet.nft_wallet import NFTWallet +from chia.wallet.signer_protocol import UnsignedTransaction from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.transaction_record import TransactionRecord from chia.wallet.transaction_sorting import SortKey from chia.wallet.uncurried_puzzle import uncurry_puzzle from chia.wallet.util.address_type import AddressType +from chia.wallet.util.clvm_streamable import byte_deserialize_clvm_streamable from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.query_filter import AmountFilter, HashFilter, TransactionTypeFilter from chia.wallet.util.transaction_type import TransactionType @@ -301,19 +309,46 @@ async def test_send_transaction(wallet_rpc_environment: WalletRpcTestEnvironment await client.send_transaction(1, uint64(100000000000000001), addr, DEFAULT_TX_CONFIG) # Tests sending a basic transaction - tx = await client.send_transaction( + extra_conditions = (Remark(Program.to(("test", None))),) + non_existent_coin = Coin(bytes32([0] * 32), bytes32([0] * 32), uint64(0)) + tx_no_push = await client.send_transaction( 1, tx_amount, addr, memos=["this is a basic tx"], tx_config=DEFAULT_TX_CONFIG.override( excluded_coin_amounts=[uint64(250000000000)], - excluded_coin_ids=[bytes32([0] * 32)], + excluded_coin_ids=[non_existent_coin.name()], + reuse_puzhash=True, ), - extra_conditions=(Remark(Program.to(("test", None))),), + extra_conditions=extra_conditions, + push=False, + ) + response = await client.fetch( + "send_transaction", + { + "wallet_id": 1, + "amount": tx_amount, + "address": addr, + "fee": 0, + "memos": ["this is a basic tx"], + "puzzle_decorator": None, + "extra_conditions": conditions_to_json_dicts(extra_conditions), + "exclude_coin_amounts": [250000000000], + "exclude_coins": [non_existent_coin.to_json_dict()], + "reuse_puzhash": True, + "CHIP-0029": True, + "push": True, + }, ) + assert response["success"] + tx = TransactionRecord.from_json_dict_convenience(response["transactions"][0]) + [ + byte_deserialize_clvm_streamable(bytes.fromhex(utx), UnsignedTransaction) + for utx in response["unsigned_transactions"] + ] + assert tx == dataclasses.replace(tx_no_push, created_at_time=tx.created_at_time) transaction_id = tx.name - spend_bundle = tx.spend_bundle assert spend_bundle is not None @@ -424,7 +459,7 @@ async def test_get_farmed_amount_with_fee(wallet_rpc_environment: WalletRpcTestE tx_config=DEFAULT_TX_CONFIG, fee=uint64(fee_amount), ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) our_ph = await wallet.get_new_puzzlehash() await full_node_api.wait_transaction_records_entered_mempool(records=[tx]) @@ -1600,11 +1635,11 @@ async def _check_delete_key( # set farmer to first private key create_sk = master_sk_to_wallet_sk_unhardened if observer else master_sk_to_wallet_sk - sk = await wallet_node.get_key_for_fingerprint(farmer_fp) + sk = await wallet_node.get_key_for_fingerprint(farmer_fp, private=True) assert sk is not None farmer_ph = create_puzzlehash_for_pk(create_sk(sk, uint32(0)).get_g1()) - sk = await wallet_node.get_key_for_fingerprint(pool_fp) + sk = await wallet_node.get_key_for_fingerprint(pool_fp, private=True) assert sk is not None pool_ph = create_puzzlehash_for_pk(create_sk(sk, uint32(0)).get_g1()) 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 da3cffde0bce..171ee9bc09f2 100644 --- a/chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py +++ b/chia/_tests/wallet/simple_sync/test_simple_sync_protocol.py @@ -177,7 +177,7 @@ async def test_subscribe_for_ph(simulator_and_wallet: OldSimulatorsAndWallets, s spent_coin = tx_record.spend_bundle.removals()[0] assert spent_coin.puzzle_hash == puzzle_hash - await wallet.wallet_state_manager.add_pending_transactions([tx_record]) + [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) await full_node_api.process_transaction_records(records=[tx_record]) @@ -189,7 +189,7 @@ async def test_subscribe_for_ph(simulator_and_wallet: OldSimulatorsAndWallets, s [tx_record] = await wallet.generate_signed_transaction( uint64(10), SINGLETON_LAUNCHER_HASH, DEFAULT_TX_CONFIG, uint64(0) ) - await wallet.wallet_state_manager.add_pending_transactions([tx_record]) + [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) await full_node_api.process_transaction_records(records=[tx_record]) @@ -197,7 +197,7 @@ async def test_subscribe_for_ph(simulator_and_wallet: OldSimulatorsAndWallets, s # 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)) - await wallet.wallet_state_manager.add_pending_transactions([tx_record]) + [tx_record] = await wallet.wallet_state_manager.add_pending_transactions([tx_record]) await full_node_api.process_transaction_records(records=[tx_record]) @@ -258,7 +258,7 @@ async def test_subscribe_for_coin_id(simulator_and_wallet: OldSimulatorsAndWalle [tx_record] = await standard_wallet.generate_signed_transaction( uint64(10), puzzle_hash, DEFAULT_TX_CONFIG, uint64(0), coins=coins ) - await standard_wallet.wallet_state_manager.add_pending_transactions([tx_record]) + [tx_record] = await standard_wallet.wallet_state_manager.add_pending_transactions([tx_record]) await full_node_api.process_transaction_records(records=[tx_record]) @@ -296,7 +296,7 @@ async def test_subscribe_for_coin_id(simulator_and_wallet: OldSimulatorsAndWalle data_response = RespondToCoinUpdates.from_bytes(msg_response.data) assert len(data_response.coin_states) == 0 - await standard_wallet.wallet_state_manager.add_pending_transactions([tx_record]) + [tx_record] = await standard_wallet.wallet_state_manager.add_pending_transactions([tx_record]) await full_node_api.process_transaction_records(records=[tx_record]) diff --git a/chia/_tests/wallet/sync/test_wallet_sync.py b/chia/_tests/wallet/sync/test_wallet_sync.py index 728929164188..b0d44a246320 100644 --- a/chia/_tests/wallet/sync/test_wallet_sync.py +++ b/chia/_tests/wallet/sync/test_wallet_sync.py @@ -9,6 +9,7 @@ import pytest from aiosqlite import Error as AIOSqliteError +from chia_rs import confirm_not_included_already_hashed from colorlog import getLogger from chia._tests.connection_utils import disconnect_all, disconnect_all_and_reconnect @@ -526,8 +527,10 @@ async def test_request_additions_errors(simulator_and_wallet: OldSimulatorsAndWa await full_node_api.request_additions(RequestAdditions(last_block.height, std_hash(b""), [ph])) # No results + fake_coin = std_hash(b"") + assert ph != fake_coin res1 = await full_node_api.request_additions( - RequestAdditions(last_block.height, last_block.header_hash, [std_hash(b"")]) + RequestAdditions(last_block.height, last_block.header_hash, [fake_coin]) ) assert res1 is not None response = RespondAdditions.from_bytes(res1.data) @@ -536,6 +539,16 @@ async def test_request_additions_errors(simulator_and_wallet: OldSimulatorsAndWa assert response.proofs is not None assert len(response.proofs) == 1 assert len(response.coins) == 1 + full_block = await full_node_api.full_node.block_store.get_full_block(last_block.header_hash) + assert full_block is not None + assert full_block.foliage_transaction_block is not None + root = full_block.foliage_transaction_block.additions_root + assert confirm_not_included_already_hashed(root, response.proofs[0][0], response.proofs[0][1]) + # proofs is a tuple of (puzzlehash, proof, proof_2) + # proof is a proof of inclusion (or exclusion) of that puzzlehash + # proof_2 is a proof of all the coins with that puzzlehash + # all coin names are concatenated and hashed into one entry in the merkle set for proof_2 + # the response contains the list of coins so you can check the proof_2 assert response.proofs[0][0] == std_hash(b"") assert response.proofs[0][1] is not None @@ -564,9 +577,10 @@ async def test_request_additions_success(simulator_and_wallet: OldSimulatorsAndW 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]) 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]) await full_node_api.farm_new_transaction_block(FarmNewBlockProtocol(ph)) last_block = full_node_api.full_node.blockchain.get_peak() @@ -776,6 +790,7 @@ async def test_dusted_wallet( # 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]) 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]) @@ -833,6 +848,7 @@ async def test_dusted_wallet( 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -850,6 +866,7 @@ async def test_dusted_wallet( 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -895,6 +912,7 @@ async def test_dusted_wallet( # 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -932,6 +950,7 @@ async def test_dusted_wallet( # 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -989,6 +1008,7 @@ async def test_dusted_wallet( # 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1023,6 +1043,7 @@ async def test_dusted_wallet( # 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1065,6 +1086,7 @@ async def test_dusted_wallet( 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]) 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]) @@ -1078,6 +1100,7 @@ async def test_dusted_wallet( # 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1106,6 +1129,7 @@ async def test_dusted_wallet( # 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]) assert tx.spend_bundle is not None await full_node_api.send_transaction(SendTransaction(tx.spend_bundle)) @@ -1337,12 +1361,11 @@ async def assert_coin_state_retry() -> None: await assert_coin_state_retry() - await time_out_assert(30, wallet.get_confirmed_balance, 2_000_000_000_000) - [tx] = await wallet.generate_signed_transaction( uint64(1_000_000_000_000), bytes32([0] * 32), DEFAULT_TX_CONFIG, memos=[ph] ) - await wallet_node.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet_node.wallet_state_manager.add_pending_transactions([tx]) + await time_out_assert(30, wallet.get_confirmed_balance, 2_000_000_000_000) async def tx_in_mempool() -> bool: return full_node_api.full_node.mempool_manager.get_spendbundle(tx.name) is not None diff --git a/chia/_tests/wallet/test_clvm_streamable.py b/chia/_tests/wallet/test_clvm_streamable.py new file mode 100644 index 000000000000..20f9010a5875 --- /dev/null +++ b/chia/_tests/wallet/test_clvm_streamable.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import dataclasses +from typing import List, Optional, Tuple + +import pytest + +from chia.types.blockchain_format.program import Program +from chia.util.streamable import Streamable, streamable +from chia.wallet.util.clvm_streamable import ( + byte_deserialize_clvm_streamable, + byte_serialize_clvm_streamable, + clvm_streamable, + json_deserialize_with_clvm_streamable, + json_serialize_with_clvm_streamable, + program_deserialize_clvm_streamable, + program_serialize_clvm_streamable, +) + + +@clvm_streamable +@dataclasses.dataclass(frozen=True) +class BasicCLVMStreamable(Streamable): + a: str + + +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 + + +@streamable +@dataclasses.dataclass(frozen=True) +class OutsideStreamable(Streamable): + inside: BasicCLVMStreamable + a: str + + +@clvm_streamable +@dataclasses.dataclass(frozen=True) +class OutsideCLVM(Streamable): + inside: BasicCLVMStreamable + a: str + + +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 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 + + +@streamable +@dataclasses.dataclass(frozen=True) +class Compound(Streamable): + optional: Optional[BasicCLVMStreamable] + list: List[BasicCLVMStreamable] + + +@clvm_streamable +@dataclasses.dataclass(frozen=True) +class CompoundCLVM(Streamable): + optional: Optional[BasicCLVMStreamable] + list: List[BasicCLVMStreamable] + + +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 Compound.from_json_dict({"optional": {"a": "1"}, "list": [{"a": "1"}]}) == instance + + # regular streamable + falsey values + instance = Compound(optional=None, list=[]) + assert json_serialize_with_clvm_streamable(instance) == {"optional": None, "list": []} + assert json_deserialize_with_clvm_streamable({"optional": None, "list": []}, Compound) == instance + assert Compound.from_json_dict({"optional": None, "list": []}) == instance + + # 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 + + # 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 + + with pytest.raises(ValueError, match="@clvm_streamable"): + + @clvm_streamable + @dataclasses.dataclass(frozen=True) + class DoesntWork(Streamable): + tuples_are_not_supported: Tuple[str] diff --git a/chia/_tests/wallet/test_notifications.py b/chia/_tests/wallet/test_notifications.py index 5cf020b01295..5ad510524e36 100644 --- a/chia/_tests/wallet/test_notifications.py +++ b/chia/_tests/wallet/test_notifications.py @@ -135,7 +135,7 @@ async def track_coin_state(*args: Any) -> bool: 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) - await wsm_1.add_pending_transactions([tx]) + [tx] = await wsm_1.add_pending_transactions([tx]) await time_out_assert_not_none( 5, full_node_api.full_node.mempool_manager.get_spendbundle, diff --git a/chia/_tests/wallet/test_sign_coin_spends.py b/chia/_tests/wallet/test_sign_coin_spends.py index ceb365706b5a..5b60e3e79487 100644 --- a/chia/_tests/wallet/test_sign_coin_spends.py +++ b/chia/_tests/wallet/test_sign_coin_spends.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Optional +import re import pytest from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey @@ -21,24 +21,25 @@ calculate_synthetic_secret_key, puzzle_hash_for_synthetic_public_key, ) -from chia.wallet.sign_coin_spends import sign_coin_spends from chia.wallet.util.wallet_types import WalletType +from chia.wallet.wallet import Wallet from chia.wallet.wallet_puzzle_store import WalletPuzzleStore from chia.wallet.wallet_state_manager import WalletStateManager +from chia.wallet.wallet_user_store import WalletUserStore top_sk: PrivateKey = PrivateKey.from_bytes(bytes([1] * 32)) sk1_h: PrivateKey = master_sk_to_wallet_sk(top_sk, uint32(1)) -sk2_h: PrivateKey = calculate_synthetic_secret_key( - master_sk_to_wallet_sk(top_sk, uint32(2)), DEFAULT_HIDDEN_PUZZLE_HASH -) +sk2_h: PrivateKey = master_sk_to_wallet_sk(top_sk, uint32(2)) +sk2_h_synth: PrivateKey = calculate_synthetic_secret_key(sk2_h, DEFAULT_HIDDEN_PUZZLE_HASH) sk1_u: PrivateKey = master_sk_to_wallet_sk_unhardened(top_sk, uint32(1)) -sk2_u: PrivateKey = calculate_synthetic_secret_key( - master_sk_to_wallet_sk_unhardened(top_sk, uint32(2)), DEFAULT_HIDDEN_PUZZLE_HASH -) +sk2_u: PrivateKey = master_sk_to_wallet_sk(top_sk, uint32(2)) +sk2_u_synth: PrivateKey = calculate_synthetic_secret_key(sk2_u, DEFAULT_HIDDEN_PUZZLE_HASH) pk1_h: G1Element = sk1_h.get_g1() pk2_h: G1Element = sk2_h.get_g1() +pk2_h_synth: G1Element = sk2_h_synth.get_g1() pk1_u: G1Element = sk1_u.get_g1() pk2_u: G1Element = sk2_u.get_g1() +pk2_u_synth: G1Element = sk2_u_synth.get_g1() msg1: bytes = b"msg1" msg2: bytes = b"msg2" @@ -47,10 +48,10 @@ coin: Coin = Coin(bytes32([0] * 32), bytes32([0] * 32), uint64(0)) puzzle = SerializedProgram.from_bytes(b"\x01") solution_h = SerializedProgram.from_program( - Program.to([[ConditionOpcode.AGG_SIG_UNSAFE, pk1_h, msg1], [ConditionOpcode.AGG_SIG_ME, pk2_h, msg2]]) + Program.to([[ConditionOpcode.AGG_SIG_UNSAFE, pk1_h, msg1], [ConditionOpcode.AGG_SIG_ME, pk2_h_synth, msg2]]) ) solution_u = SerializedProgram.from_program( - Program.to([[ConditionOpcode.AGG_SIG_UNSAFE, pk1_u, msg1], [ConditionOpcode.AGG_SIG_ME, pk2_u, msg2]]) + Program.to([[ConditionOpcode.AGG_SIG_UNSAFE, pk1_u, msg1], [ConditionOpcode.AGG_SIG_ME, pk2_u_synth, msg2]]) ) spend_h: CoinSpend = make_spend( coin, @@ -64,88 +65,6 @@ ) -@pytest.mark.anyio -async def test_sign_coin_spends() -> None: - def derive_ph(pk: G1Element) -> bytes32: - return bytes32([0] * 32) - - def pk_to_sk(pk: G1Element) -> Optional[PrivateKey]: - if pk == pk1_h: - return sk1_h - return None - - def ph_to_sk(ph: bytes32) -> Optional[PrivateKey]: - if ph == derive_ph(G1Element()): - return sk2_h - return None - - with pytest.raises(ValueError, match="no secret key"): - await sign_coin_spends( - [spend_h], - pk_to_sk, - lambda _: None, - additional_data, - 1000000000, - [derive_ph], - ) - - with pytest.raises(ValueError, match="no secret key"): - await sign_coin_spends( - [spend_h], - lambda _: None, - ph_to_sk, - additional_data, - 1000000000, - [derive_ph], - ) - - with pytest.raises(ValueError, match="no secret key"): - await sign_coin_spends( - [spend_h], - pk_to_sk, - ph_to_sk, - additional_data, - 1000000000, - [], - ) - - signature: G2Element = ( - await sign_coin_spends( - [spend_h], - pk_to_sk, - ph_to_sk, - additional_data, - 1000000000, - [lambda _: bytes32([1] * 32), derive_ph], - ) - ).aggregated_signature - - assert signature == AugSchemeMPL.aggregate( - [ - AugSchemeMPL.sign(sk1_h, msg1), - AugSchemeMPL.sign(sk2_h, msg2 + coin.name() + additional_data), - ] - ) - - async def pk_to_sk_async(pk: G1Element) -> Optional[PrivateKey]: - return pk_to_sk(pk) - - async def ph_to_sk_async(ph: bytes32) -> Optional[PrivateKey]: - return ph_to_sk(ph) - - signature2: G2Element = ( - await sign_coin_spends( - [spend_h], - pk_to_sk_async, - ph_to_sk_async, - additional_data, - 1000000000, - [derive_ph], - ) - ).aggregated_signature - assert signature2 == signature - - @pytest.mark.anyio async def test_wsm_sign_transaction() -> None: async with manage_connection("file:temp.db?mode=memory&cache=shared", uri=True, name="writer") as writer_conn: @@ -156,9 +75,16 @@ async def test_wsm_sign_transaction() -> None: wsm.puzzle_store = await WalletPuzzleStore.create(db) wsm.constants = DEFAULT_CONSTANTS wsm.private_key = top_sk + wsm.root_pubkey = top_sk.get_g1() + wsm.user_store = await WalletUserStore.create(db) + wallet_info = await wsm.user_store.get_wallet_by_id(1) + assert wallet_info is not None + wsm.main_wallet = await Wallet.create(wsm, wallet_info) - with pytest.raises(ValueError, match="no secret key"): - await wsm.sign_transaction([spend_h]) + with pytest.raises( + ValueError, match=re.escape(f"Pubkey {pk1_h.get_fingerprint()} not found (or path/sum hinted to)") + ): + await wsm.sign_bundle([spend_h]) await wsm.puzzle_store.add_derivation_paths( [ @@ -177,8 +103,8 @@ async def test_wsm_sign_transaction() -> None: [ DerivationRecord( uint32(2), - puzzle_hash_for_synthetic_public_key(pk2_h), - G1Element(), + puzzle_hash_for_synthetic_public_key(pk2_h_synth), + pk2_h, WalletType.STANDARD_WALLET, uint32(1), True, @@ -186,16 +112,18 @@ async def test_wsm_sign_transaction() -> None: ] ) - signature: G2Element = (await wsm.sign_transaction([spend_h])).aggregated_signature + signature: G2Element = ((await wsm.sign_bundle([spend_h]))[0]).aggregated_signature assert signature == AugSchemeMPL.aggregate( [ AugSchemeMPL.sign(sk1_h, msg1), - AugSchemeMPL.sign(sk2_h, msg2 + coin.name() + additional_data), + AugSchemeMPL.sign(sk2_h_synth, msg2 + coin.name() + additional_data), ] ) - with pytest.raises(ValueError, match="no secret key"): - await wsm.sign_transaction([spend_u]) + with pytest.raises( + ValueError, match=re.escape(f"Pubkey {pk1_u.get_fingerprint()} not found (or path/sum hinted to)") + ): + await wsm.sign_bundle([spend_u]) await wsm.puzzle_store.add_derivation_paths( [ @@ -214,18 +142,18 @@ async def test_wsm_sign_transaction() -> None: [ DerivationRecord( uint32(2), - puzzle_hash_for_synthetic_public_key(pk2_u), - G1Element(), + puzzle_hash_for_synthetic_public_key(pk2_u_synth), + pk2_u, WalletType.STANDARD_WALLET, uint32(1), False, ) ] ) - signature2: G2Element = (await wsm.sign_transaction([spend_u])).aggregated_signature + signature2: G2Element = ((await wsm.sign_bundle([spend_u]))[0]).aggregated_signature assert signature2 == AugSchemeMPL.aggregate( [ AugSchemeMPL.sign(sk1_u, msg1), - AugSchemeMPL.sign(sk2_u, msg2 + coin.name() + additional_data), + AugSchemeMPL.sign(sk2_u_synth, msg2 + coin.name() + additional_data), ] ) diff --git a/chia/_tests/wallet/test_signer_protocol.py b/chia/_tests/wallet/test_signer_protocol.py new file mode 100644 index 000000000000..715989dd52a2 --- /dev/null +++ b/chia/_tests/wallet/test_signer_protocol.py @@ -0,0 +1,449 @@ +from __future__ import annotations + +import dataclasses +from typing import List, Optional + +import pytest +from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey + +from chia._tests.environments.wallet import WalletStateTransition, WalletTestFramework +from chia.rpc.wallet_request_types import ApplySignatures, GatherSigningInfo, SubmitTransactions +from chia.rpc.wallet_rpc_client import WalletRpcClient +from chia.types.blockchain_format.coin import Coin as ConsensusCoin +from chia.types.blockchain_format.program import Program +from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.types.coin_spend import CoinSpend, make_spend +from chia.types.spend_bundle import SpendBundle +from chia.util.hash import std_hash +from chia.util.ints import uint64 +from chia.wallet.conditions import AggSigMe +from chia.wallet.derivation_record import DerivationRecord +from chia.wallet.derive_keys import _derive_path_unhardened +from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( + DEFAULT_HIDDEN_PUZZLE_HASH, + calculate_synthetic_offset, +) +from chia.wallet.signer_protocol import ( + KeyHints, + PathHint, + SignedTransaction, + SigningInstructions, + SigningResponse, + SigningTarget, + Spend, + SumHint, + TransactionInfo, + UnsignedTransaction, +) +from chia.wallet.util.clvm_streamable import json_deserialize_with_clvm_streamable, json_serialize_with_clvm_streamable +from chia.wallet.util.tx_config import DEFAULT_COIN_SELECTION_CONFIG +from chia.wallet.wallet import Wallet +from chia.wallet.wallet_state_manager import WalletStateManager + + +def test_unsigned_transaction_type() -> None: + pubkey: G1Element = G1Element() + message: bytes = b"message" + + coin: ConsensusCoin = ConsensusCoin(bytes32([0] * 32), bytes32([0] * 32), uint64(0)) + puzzle: Program = Program.to(1) + solution: Program = Program.to([AggSigMe(pubkey, message).to_program()]) + + coin_spend: CoinSpend = make_spend(coin, puzzle, solution) + assert Spend.from_coin_spend(coin_spend).as_coin_spend() == coin_spend + + tx: UnsignedTransaction = UnsignedTransaction( + TransactionInfo([Spend.from_coin_spend(coin_spend)]), + SigningInstructions( + KeyHints([], []), + [SigningTarget(pubkey.get_fingerprint().to_bytes(4, "big"), message, bytes32([1] * 32))], + ), + ) + + assert tx == json_deserialize_with_clvm_streamable(json_serialize_with_clvm_streamable(tx), UnsignedTransaction) + as_json_dict = { + "coin": { + "parent_coin_id": "0x" + tx.transaction_info.spends[0].coin.parent_coin_id.hex(), + "puzzle_hash": "0x" + tx.transaction_info.spends[0].coin.puzzle_hash.hex(), + "amount": tx.transaction_info.spends[0].coin.amount, + }, + "puzzle": "0x" + bytes(tx.transaction_info.spends[0].puzzle).hex(), + "solution": "0x" + bytes(tx.transaction_info.spends[0].solution).hex(), + } + assert tx.transaction_info.spends[0].to_json_dict() == as_json_dict + + +@pytest.mark.parametrize( + "wallet_environments", + [ + { + "num_environments": 1, + "blocks_needed": [1], + "trusted": True, + "reuse_puzhash": True, + } + ], + indirect=True, +) +@pytest.mark.anyio +async def test_p2dohp_wallet_signer_protocol(wallet_environments: WalletTestFramework) -> None: + wallet: Wallet = wallet_environments.environments[0].xch_wallet + wallet_state_manager: WalletStateManager = wallet_environments.environments[0].wallet_state_manager + wallet_rpc: WalletRpcClient = wallet_environments.environments[0].rpc_client + + # Test first that we can properly examine and sign a regular transaction + [coin] = await wallet.select_coins(uint64(0), DEFAULT_COIN_SELECTION_CONFIG) + puzzle: Program = await wallet.puzzle_for_puzzle_hash(coin.puzzle_hash) + delegated_puzzle: Program = Program.to(None) + delegated_puzzle_hash: bytes32 = delegated_puzzle.get_tree_hash() + solution: Program = Program.to([None, None, None]) + + coin_spend: CoinSpend = make_spend( + coin, + puzzle, + solution, + ) + + derivation_record: Optional[DerivationRecord] = ( + await wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(coin.puzzle_hash) + ) + assert derivation_record is not None + pubkey: G1Element = derivation_record.pubkey + synthetic_pubkey: G1Element = G1Element.from_bytes(puzzle.uncurry()[1].at("f").atom) + message: bytes = delegated_puzzle_hash + coin.name() + wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA + + utx: UnsignedTransaction = UnsignedTransaction( + TransactionInfo([Spend.from_coin_spend(coin_spend)]), + ( + await wallet_rpc.gather_signing_info(GatherSigningInfo(spends=[Spend.from_coin_spend(coin_spend)])) + ).signing_instructions, + ) + assert utx.signing_instructions.key_hints.sum_hints == [ + SumHint( + [pubkey.get_fingerprint().to_bytes(4, "big")], + calculate_synthetic_offset(pubkey, DEFAULT_HIDDEN_PUZZLE_HASH).to_bytes(32, "big"), + wallet_state_manager.main_wallet.puzzle_for_pk(pubkey).uncurry()[1].at("f").as_atom(), + ) + ] + assert utx.signing_instructions.key_hints.path_hints == [ + PathHint( + wallet_state_manager.root_pubkey.get_fingerprint().to_bytes(4, "big"), + [uint64(12381), uint64(8444), uint64(2), uint64(derivation_record.index)], + ) + ] + assert len(utx.signing_instructions.targets) == 1 + assert utx.signing_instructions.targets[0].fingerprint == synthetic_pubkey.get_fingerprint().to_bytes(4, "big") + assert utx.signing_instructions.targets[0].message == message + + signing_responses: List[SigningResponse] = await wallet_state_manager.execute_signing_instructions( + utx.signing_instructions + ) + assert len(signing_responses) == 1 + assert signing_responses[0].hook == utx.signing_instructions.targets[0].hook + assert AugSchemeMPL.verify(synthetic_pubkey, message, G2Element.from_bytes(signing_responses[0].signature)) + + # Now test that we can partially sign a transaction + ACS: Program = Program.to(1) + ACS_PH: Program = Program.to(1).get_tree_hash() + not_our_private_key: PrivateKey = PrivateKey.from_bytes(bytes([1] * 32)) + not_our_pubkey: G1Element = not_our_private_key.get_g1() + not_our_message: bytes = b"not our message" + not_our_coin: ConsensusCoin = ConsensusCoin( + bytes32([0] * 32), + ACS_PH, + uint64(0), + ) + not_our_coin_spend: CoinSpend = make_spend(not_our_coin, ACS, Program.to([[49, not_our_pubkey, not_our_message]])) + + not_our_utx: UnsignedTransaction = UnsignedTransaction( + TransactionInfo([Spend.from_coin_spend(coin_spend), Spend.from_coin_spend(not_our_coin_spend)]), + ( + await wallet_rpc.gather_signing_info( + GatherSigningInfo(spends=[Spend.from_coin_spend(coin_spend), Spend.from_coin_spend(not_our_coin_spend)]) + ) + ).signing_instructions, + ) + assert not_our_utx.signing_instructions.key_hints == utx.signing_instructions.key_hints + assert len(not_our_utx.signing_instructions.targets) == 2 + assert not_our_utx.signing_instructions.targets[0].fingerprint == synthetic_pubkey.get_fingerprint().to_bytes( + 4, "big" + ) + assert not_our_utx.signing_instructions.targets[0].message == bytes(message) + assert not_our_utx.signing_instructions.targets[1].fingerprint == not_our_pubkey.get_fingerprint().to_bytes( + 4, "big" + ) + assert not_our_utx.signing_instructions.targets[1].message == bytes(not_our_message) + not_our_signing_instructions: SigningInstructions = not_our_utx.signing_instructions + with pytest.raises(ValueError, match=r"not found \(or path/sum hinted to\)"): + await wallet_state_manager.execute_signing_instructions(not_our_signing_instructions) + with pytest.raises(ValueError, match=r"No pubkey found \(or path hinted to\) for fingerprint"): + await wallet_state_manager.execute_signing_instructions( + dataclasses.replace( + not_our_signing_instructions, + key_hints=dataclasses.replace( + not_our_signing_instructions.key_hints, + sum_hints=[ + *not_our_signing_instructions.key_hints.sum_hints, + SumHint([bytes(not_our_pubkey)], std_hash(b"sum hint only"), bytes(G1Element())), + ], + ), + ) + ) + with pytest.raises(ValueError, match="No root pubkey for fingerprint"): + await wallet_state_manager.execute_signing_instructions( + dataclasses.replace( + not_our_signing_instructions, + key_hints=dataclasses.replace( + not_our_signing_instructions.key_hints, + path_hints=[ + *not_our_signing_instructions.key_hints.path_hints, + PathHint(bytes(not_our_pubkey), [uint64(0)]), + ], + ), + ) + ) + signing_responses_2 = await wallet_state_manager.execute_signing_instructions( + not_our_signing_instructions, partial_allowed=True + ) + assert len(signing_responses_2) == 2 + assert ( + bytes(AugSchemeMPL.aggregate([G2Element.from_bytes(sig.signature) for sig in signing_responses_2])) + == signing_responses[0].signature + ) + + signed_txs: List[SignedTransaction] = ( + await wallet_rpc.apply_signatures( + ApplySignatures(spends=[Spend.from_coin_spend(coin_spend)], signing_responses=signing_responses) + ) + ).signed_transactions + await wallet_rpc.submit_transactions(SubmitTransactions(signed_transactions=signed_txs)) + await wallet_environments.full_node.wait_bundle_ids_in_mempool( + [ + SpendBundle( + [spend.as_coin_spend() for tx in signed_txs for spend in tx.transaction_info.spends], + G2Element.from_bytes(signing_responses[0].signature), + ).name() + ] + ) + + await wallet_environments.process_pending_states( + [ + WalletStateTransition( + # We haven't submitted a TransactionRecord so the wallet won't know about this until confirmed + pre_block_balance_updates={}, + post_block_balance_updates={ + 1: { + "confirmed_wallet_balance": -1 * coin.amount, + "unconfirmed_wallet_balance": -1 * coin.amount, + "spendable_balance": -1 * coin.amount, + "max_send_amount": -1 * coin.amount, + "unspent_coin_count": -1, + }, + }, + ), + ] + ) + + +@pytest.mark.parametrize( + "wallet_environments", + [ + { + "num_environments": 1, + "blocks_needed": [1], + "trusted": True, + "reuse_puzhash": True, + } + ], + indirect=True, +) +@pytest.mark.anyio +async def test_p2blsdohp_execute_signing_instructions(wallet_environments: WalletTestFramework) -> None: + wallet: Wallet = wallet_environments.environments[0].xch_wallet + root_sk: PrivateKey = wallet.wallet_state_manager.get_master_private_key() + root_pk: G1Element = root_sk.get_g1() + root_fingerprint: bytes = root_pk.get_fingerprint().to_bytes(4, "big") + + # Test just a path hint + test_name: bytes32 = std_hash(b"path hint only") + child_sk: PrivateKey = _derive_path_unhardened(root_sk, [uint64(1), uint64(2), uint64(3), uint64(4)]) + signing_responses: List[SigningResponse] = await wallet.execute_signing_instructions( + SigningInstructions( + KeyHints( + [], + [PathHint(root_fingerprint, [uint64(1), uint64(2), uint64(3), uint64(4)])], + ), + [SigningTarget(child_sk.get_g1().get_fingerprint().to_bytes(4, "big"), test_name, test_name)], + ) + ) + assert signing_responses == [SigningResponse(bytes(AugSchemeMPL.sign(child_sk, test_name)), test_name)] + + # Test just a sum hint + test_name = std_hash(b"sum hint only") + other_sk: PrivateKey = PrivateKey.from_bytes(test_name) + sum_pk: G1Element = other_sk.get_g1() + root_pk + signing_instructions: SigningInstructions = SigningInstructions( + KeyHints( + [SumHint([root_fingerprint], test_name, bytes(sum_pk))], + [], + ), + [SigningTarget(sum_pk.get_fingerprint().to_bytes(4, "big"), test_name, test_name)], + ) + for partial_allowed in (True, False): + signing_responses = await wallet.execute_signing_instructions(signing_instructions, partial_allowed) + assert signing_responses == [ + SigningResponse( + bytes( + AugSchemeMPL.aggregate( + [ + AugSchemeMPL.sign(other_sk, test_name, sum_pk), + AugSchemeMPL.sign(root_sk, test_name, sum_pk), + ] + ) + ), + test_name, + ), + ] + # Toss in a random SigningTarget to see that the responses split up + signing_instructions = dataclasses.replace( + signing_instructions, + targets=[ + SigningTarget(sum_pk.get_fingerprint().to_bytes(4, "big"), test_name, test_name), + SigningTarget(b"random fingerprint", test_name, test_name), + ], + ) + signing_responses = await wallet.execute_signing_instructions(signing_instructions, partial_allowed=True) + assert signing_responses == [ + SigningResponse( + bytes( + AugSchemeMPL.sign(root_sk, test_name, sum_pk), + ), + test_name, + ), + SigningResponse( + bytes( + AugSchemeMPL.sign(other_sk, test_name, sum_pk), + ), + test_name, + ), + ] + + # Test both path and sum hint + test_name = std_hash(b"path and sum hint") + child_sk = _derive_path_unhardened(root_sk, [uint64(1), uint64(2), uint64(3), uint64(4)]) + other_sk = PrivateKey.from_bytes(test_name) + sum_pk = child_sk.get_g1() + other_sk.get_g1() + signing_instructions = SigningInstructions( + KeyHints( + [SumHint([child_sk.get_g1().get_fingerprint().to_bytes(4, "big")], test_name, bytes(sum_pk))], + [PathHint(root_fingerprint, [uint64(1), uint64(2), uint64(3), uint64(4)])], + ), + [SigningTarget(sum_pk.get_fingerprint().to_bytes(4, "big"), test_name, test_name)], + ) + for partial_allowed in (True, False): + signing_responses = await wallet.execute_signing_instructions(signing_instructions, partial_allowed) + assert signing_responses == [ + SigningResponse( + bytes( + AugSchemeMPL.aggregate( + [ + AugSchemeMPL.sign(other_sk, test_name, sum_pk), + AugSchemeMPL.sign(child_sk, test_name, sum_pk), + ] + ) + ), + test_name, + ), + ] + + # Test partial signing + test_name = std_hash(b"path and sum hint partial") + test_name_2 = std_hash(test_name) + root_sk_2 = PrivateKey.from_bytes(std_hash(b"a key we do not have")) + child_sk = _derive_path_unhardened(root_sk, [uint64(1), uint64(2), uint64(3), uint64(4)]) + child_sk_2 = _derive_path_unhardened(root_sk_2, [uint64(1), uint64(2), uint64(3), uint64(4)]) + other_sk = PrivateKey.from_bytes(test_name) + other_sk_2 = PrivateKey.from_bytes(test_name_2) + sum_pk = child_sk.get_g1() + other_sk.get_g1() + sum_pk_2 = child_sk_2.get_g1() + other_sk_2.get_g1() + signing_responses = await wallet.execute_signing_instructions( + SigningInstructions( + KeyHints( + [ + SumHint([child_sk.get_g1().get_fingerprint().to_bytes(4, "big")], test_name, bytes(sum_pk)), + SumHint([child_sk_2.get_g1().get_fingerprint().to_bytes(4, "big")], test_name_2, bytes(sum_pk_2)), + ], + [ + PathHint(root_fingerprint, [uint64(1), uint64(2), uint64(3), uint64(4)]), + PathHint( + root_sk_2.get_g1().get_fingerprint().to_bytes(4, "big"), + [uint64(1), uint64(2), uint64(3), uint64(4)], + ), + ], + ), + [ + SigningTarget(sum_pk.get_fingerprint().to_bytes(4, "big"), test_name, test_name), + SigningTarget(sum_pk_2.get_fingerprint().to_bytes(4, "big"), test_name_2, test_name_2), + ], + ), + partial_allowed=True, + ) + assert signing_responses == [ + SigningResponse( + bytes( + AugSchemeMPL.sign(child_sk, test_name, sum_pk), + ), + test_name, + ), + SigningResponse( + bytes(AugSchemeMPL.sign(other_sk, test_name, sum_pk)), + test_name, + ), + SigningResponse(bytes(AugSchemeMPL.sign(other_sk_2, test_name_2, sum_pk_2)), test_name_2), + ] + + # Test errors + unknown_path_hint = SigningInstructions( + KeyHints( + [], + [PathHint(b"unknown fingerprint", [uint64(1), uint64(2), uint64(3), uint64(4)])], + ), + [], + ) + unknown_sum_hint = SigningInstructions( + KeyHints( + [SumHint([b"unknown fingerprint"], b"", bytes(G1Element()))], + [], + ), + [], + ) + unknown_target = SigningInstructions( + KeyHints( + [], + [], + ), + [SigningTarget(b"unknown fingerprint", b"", std_hash(b"some hook"))], + ) + with pytest.raises(ValueError, match="No root pubkey for fingerprint"): + await wallet.execute_signing_instructions(unknown_path_hint) + with pytest.raises(ValueError, match="No pubkey found"): + await wallet.execute_signing_instructions(unknown_sum_hint) + with pytest.raises(ValueError, match="not found"): + await wallet.execute_signing_instructions(unknown_target) + + # Test no private key partial sign sum hint + wallet.wallet_state_manager.private_key = None + test_name = std_hash(b"sum hint partial no private key") + other_sk = PrivateKey.from_bytes(test_name) + sum_pk = other_sk.get_g1() + root_pk + signing_responses = await wallet.execute_signing_instructions( + SigningInstructions( + KeyHints( + [SumHint([root_fingerprint], test_name, bytes(sum_pk))], + [], + ), + [SigningTarget(sum_pk.get_fingerprint().to_bytes(4, "big"), test_name, test_name)], + ), + partial_allowed=True, + ) + assert signing_responses == [SigningResponse(bytes(AugSchemeMPL.sign(other_sk, test_name, sum_pk)), test_name)] diff --git a/chia/_tests/wallet/test_singleton_lifecycle_fast.py b/chia/_tests/wallet/test_singleton_lifecycle_fast.py index 0b0b8dbcd8c0..685778e5b1c7 100644 --- a/chia/_tests/wallet/test_singleton_lifecycle_fast.py +++ b/chia/_tests/wallet/test_singleton_lifecycle_fast.py @@ -295,7 +295,7 @@ def launcher_conditions_and_spend_bundle( singleton_full_puzzle_hash = singleton_full_puzzle.get_tree_hash() message_program = Program.to([singleton_full_puzzle_hash, launcher_amount, metadata]) expected_announcement = AssertCoinAnnouncement( - asserted_id=launcher_coin.name(), asserted_msg=message_program.get_tree_hash() + asserted_id=launcher_id, asserted_msg=message_program.get_tree_hash() ) expected_conditions = [] clsp = f"(0x{ConditionOpcode.ASSERT_COIN_ANNOUNCEMENT.hex()} 0x{expected_announcement.msg_calc})" diff --git a/chia/_tests/wallet/test_wallet.py b/chia/_tests/wallet/test_wallet.py index ed8b143e984e..06cee928f330 100644 --- a/chia/_tests/wallet/test_wallet.py +++ b/chia/_tests/wallet/test_wallet.py @@ -1332,7 +1332,7 @@ async def test_wallet_send_to_three_peers( uint64(0), ) assert tx.spend_bundle is not None - await wallet_0.wallet_state_manager.main_wallet.wallet_state_manager.add_pending_transactions([tx]) + [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]) # wallet0 <-> sever1 @@ -1530,13 +1530,13 @@ async def test_wallet_make_transaction_with_memo(self, wallet_environments: Wall [tx] = await wallet_0.generate_signed_transaction( uint64(tx_amount), ph_2, DEFAULT_TX_CONFIG, uint64(tx_fee), memos=[ph_2] ) - tx_id = tx.name.hex() 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 assert len(memos[tx_id]) == 1 @@ -1696,7 +1696,7 @@ async def test_wallet_prevent_fee_theft(self, wallet_environments: WalletTestFra if compute_additions(cs) == []: stolen_cs = cs # get a legit signature - stolen_sb = await wallet.wallet_state_manager.sign_transaction([stolen_cs]) + stolen_sb, _ = await wallet.wallet_state_manager.sign_bundle([stolen_cs]) name = stolen_sb.name() stolen_tx = TransactionRecord( confirmed_at_height=uint32(0), @@ -1892,7 +1892,6 @@ async def test_wallet_tx_reorg(self, wallet_environments: WalletTestFramework) - async def test_address_sliding_window(self, wallet_environments: WalletTestFramework) -> None: full_node_api = wallet_environments.full_node env = wallet_environments.environments[0] - wsm = env.wallet_state_manager wallet = env.xch_wallet peak = full_node_api.full_node.blockchain.get_peak_height() @@ -1900,7 +1899,7 @@ async def test_address_sliding_window(self, wallet_environments: WalletTestFrame puzzle_hashes = [] for i in range(211): - pubkey = master_sk_to_wallet_sk(wsm.private_key, uint32(i)).get_g1() + pubkey = master_sk_to_wallet_sk(wallet.wallet_state_manager.get_master_private_key(), uint32(i)).get_g1() puzzle: Program = wallet.puzzle_for_pk(pubkey) puzzle_hash: bytes32 = puzzle.get_tree_hash() puzzle_hashes.append(puzzle_hash) diff --git a/chia/_tests/wallet/test_wallet_node.py b/chia/_tests/wallet/test_wallet_node.py index f9287ab2acac..e753c5dd3ce6 100644 --- a/chia/_tests/wallet/test_wallet_node.py +++ b/chia/_tests/wallet/test_wallet_node.py @@ -5,9 +5,10 @@ import time import types from pathlib import Path -from typing import Any, List, Optional +from typing import Any, Dict, List, Optional import pytest +from chia_rs import G1Element, PrivateKey from chia._tests.util.misc import CoinGenerator from chia._tests.util.setup_nodes import OldSimulatorsAndWallets @@ -39,12 +40,13 @@ async def test_get_private_key(root_path_populated_with_config: Path, get_temp_k keychain = get_temp_keyring config = load_config(root_path, "config.yaml", "wallet") node = WalletNode(config, root_path, test_constants, keychain) - sk = keychain.add_private_key(generate_mnemonic()) + sk = keychain.add_key(generate_mnemonic()) fingerprint = sk.get_g1().get_fingerprint() - key = await node.get_private_key(fingerprint) + key = await node.get_key(fingerprint) assert key is not None + assert isinstance(key, PrivateKey) assert key.get_g1().get_fingerprint() == fingerprint @@ -54,17 +56,18 @@ async def test_get_private_key_default_key(root_path_populated_with_config: Path keychain = get_temp_keyring config = load_config(root_path, "config.yaml", "wallet") node = WalletNode(config, root_path, test_constants, keychain) - sk = keychain.add_private_key(generate_mnemonic()) + sk = keychain.add_key(generate_mnemonic()) fingerprint = sk.get_g1().get_fingerprint() # Add a couple more keys - keychain.add_private_key(generate_mnemonic()) - keychain.add_private_key(generate_mnemonic()) + keychain.add_key(generate_mnemonic()) + keychain.add_key(generate_mnemonic()) # When no fingerprint is provided, we should get the default (first) key - key = await node.get_private_key(None) + key = await node.get_key(None) assert key is not None + assert isinstance(key, PrivateKey) assert key.get_g1().get_fingerprint() == fingerprint @@ -79,7 +82,7 @@ async def test_get_private_key_missing_key( node = WalletNode(config, root_path, test_constants, keychain) # Keyring is empty, so requesting a key by fingerprint or None should return None - key = await node.get_private_key(fingerprint) + key = await node.get_key(fingerprint) assert key is None @@ -92,28 +95,124 @@ async def test_get_private_key_missing_key_use_default( keychain = get_temp_keyring config = load_config(root_path, "config.yaml", "wallet") node = WalletNode(config, root_path, test_constants, keychain) - sk = keychain.add_private_key(generate_mnemonic()) + sk = keychain.add_key(generate_mnemonic()) fingerprint = sk.get_g1().get_fingerprint() # Stupid sanity check that the fingerprint we're going to use isn't actually in the keychain assert fingerprint != 1234567890 # When fingerprint is provided and the key is missing, we should get the default (first) key - key = await node.get_private_key(1234567890) + key = await node.get_key(1234567890) assert key is not None + assert isinstance(key, PrivateKey) assert key.get_g1().get_fingerprint() == fingerprint +@pytest.mark.anyio +async def test_get_public_key(root_path_populated_with_config: Path, get_temp_keyring: Keychain) -> None: + root_path: Path = root_path_populated_with_config + keychain: Keychain = get_temp_keyring + config: Dict[str, Any] = load_config(root_path, "config.yaml", "wallet") + node: WalletNode = WalletNode(config, root_path, test_constants, keychain) + pk: G1Element = keychain.add_key( + "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + None, + private=False, + ) + fingerprint: int = pk.get_fingerprint() + + key = await node.get_key(fingerprint, private=False) + + assert key is not None + assert isinstance(key, G1Element) + assert key.get_fingerprint() == fingerprint + + +@pytest.mark.anyio +async def test_get_public_key_default_key(root_path_populated_with_config: Path, get_temp_keyring: Keychain) -> None: + root_path: Path = root_path_populated_with_config + keychain: Keychain = get_temp_keyring + config: Dict[str, Any] = load_config(root_path, "config.yaml", "wallet") + node: WalletNode = WalletNode(config, root_path, test_constants, keychain) + pk: G1Element = keychain.add_key( + "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + None, + private=False, + ) + fingerprint: int = pk.get_fingerprint() + + # Add a couple more keys + keychain.add_key( + "83062a1b26d27820600eac4e31c1a890a6ba026b28bb96bb66454e9ce1033f4cba8824259dc17dc3b643ab1003e6b961", + None, + private=False, + ) + keychain.add_key( + "a272d5aaa6046e64bd7fd69bae288b9f9e5622c13058ec7d1b85e3d4d1acfa5d63d6542336c7b24d2fceab991919e989", + None, + private=False, + ) + + # When no fingerprint is provided, we should get the default (first) key + key = await node.get_key(None, private=False) + + assert key is not None + assert isinstance(key, G1Element) + assert key.get_fingerprint() == fingerprint + + +@pytest.mark.anyio +@pytest.mark.parametrize("fingerprint", [None, 1234567890]) +async def test_get_public_key_missing_key( + root_path_populated_with_config: Path, get_temp_keyring: Keychain, fingerprint: Optional[int] +) -> None: + root_path: Path = root_path_populated_with_config + keychain: Keychain = get_temp_keyring # empty keyring + config: Dict[str, Any] = load_config(root_path, "config.yaml", "wallet") + node: WalletNode = WalletNode(config, root_path, test_constants, keychain) + + # Keyring is empty, so requesting a key by fingerprint or None should return None + key = await node.get_key(fingerprint, private=False) + + assert key is None + + +@pytest.mark.anyio +async def test_get_public_key_missing_key_use_default( + root_path_populated_with_config: Path, get_temp_keyring: Keychain +) -> None: + root_path: Path = root_path_populated_with_config + keychain: Keychain = get_temp_keyring + config: Dict[str, Any] = load_config(root_path, "config.yaml", "wallet") + node: WalletNode = WalletNode(config, root_path, test_constants, keychain) + pk: G1Element = keychain.add_key( + "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + None, + private=False, + ) + fingerprint: int = pk.get_fingerprint() + + # Stupid sanity check that the fingerprint we're going to use isn't actually in the keychain + assert fingerprint != 1234567890 + + # When fingerprint is provided and the key is missing, we should get the default (first) key + key = await node.get_key(1234567890, private=False) + + assert key is not None + assert isinstance(key, G1Element) + assert key.get_fingerprint() == fingerprint + + def test_log_in(root_path_populated_with_config: Path, get_temp_keyring: Keychain) -> None: root_path = root_path_populated_with_config keychain = get_temp_keyring config = load_config(root_path, "config.yaml", "wallet") node = WalletNode(config, root_path, test_constants) - sk = keychain.add_private_key(generate_mnemonic()) + sk = keychain.add_key(generate_mnemonic()) fingerprint = sk.get_g1().get_fingerprint() - node.log_in(sk) + node.log_in(fingerprint) assert node.logged_in is True assert node.logged_in_fingerprint == fingerprint @@ -136,11 +235,11 @@ def patched_update_last_used_fingerprint(self: Self) -> None: keychain = get_temp_keyring config = load_config(root_path, "config.yaml", "wallet") node = WalletNode(config, root_path, test_constants) - sk = keychain.add_private_key(generate_mnemonic()) + sk = keychain.add_key(generate_mnemonic()) fingerprint = sk.get_g1().get_fingerprint() # Expect log_in to succeed, even though we can't write the last used fingerprint - node.log_in(sk) + node.log_in(fingerprint) assert node.logged_in is True assert node.logged_in_fingerprint == fingerprint @@ -153,10 +252,10 @@ def test_log_out(root_path_populated_with_config: Path, get_temp_keyring: Keycha keychain = get_temp_keyring config = load_config(root_path, "config.yaml", "wallet") node = WalletNode(config, root_path, test_constants) - sk = keychain.add_private_key(generate_mnemonic()) + sk = keychain.add_key(generate_mnemonic()) fingerprint = sk.get_g1().get_fingerprint() - node.log_in(sk) + node.log_in(fingerprint) assert node.logged_in is True assert node.logged_in_fingerprint == fingerprint @@ -437,7 +536,7 @@ async def restart_with_fingerprint(fingerprint: Optional[int]) -> None: # Load another key without funds, make sure the balance is empty. other_key = KeyData.generate() assert wallet_node.local_keychain is not None - wallet_node.local_keychain.add_private_key(other_key.mnemonic_str()) + wallet_node.local_keychain.add_key(other_key.mnemonic_str()) await restart_with_fingerprint(other_key.fingerprint) assert await wallet_node.get_balance(wallet_id) == Balance() # Load the initial fingerprint again and make sure the balance is still what we generated earlier @@ -542,7 +641,7 @@ async def send_transaction( # Generate the transaction [tx] = await wallet.generate_signed_transaction(uint64(0), bytes32([0] * 32), DEFAULT_TX_CONFIG) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) # 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 7c1a18c77145..d84a9619ac09 100644 --- a/chia/_tests/wallet/test_wallet_retry.py +++ b/chia/_tests/wallet/test_wallet_retry.py @@ -27,15 +27,6 @@ async def farm_blocks(full_node_api: FullNodeSimulator, ph: bytes32, num_blocks: return num_blocks -def assert_sb_in_pool(node: FullNodeAPI, sb: SpendBundle) -> None: - assert sb == node.full_node.mempool_manager.get_spendbundle(sb.name()) - - -def assert_sb_not_in_pool(node: FullNodeAPI, sb: SpendBundle) -> None: - assert node.full_node.mempool_manager.get_spendbundle(sb.name()) is None - assert not node.full_node.mempool_manager.seen(sb.name()) - - def evict_from_pool(node: FullNodeAPI, sb: SpendBundle) -> None: mempool_item = node.full_node.mempool_manager.mempool.get_item_by_id(sb.name()) assert mempool_item is not None @@ -64,9 +55,9 @@ async def test_wallet_tx_retry( 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]) sb1: Optional[SpendBundle] = transaction.spend_bundle assert sb1 is not None - await wallet_1.wallet_state_manager.add_pending_transactions([transaction]) async def sb_in_mempool() -> bool: return full_node_1.full_node.mempool_manager.get_spendbundle(transaction.name) == transaction.spend_bundle @@ -76,7 +67,8 @@ async def sb_in_mempool() -> bool: # Evict SpendBundle from peer evict_from_pool(full_node_1, sb1) - assert_sb_not_in_pool(full_node_1, sb1) + assert full_node_1.full_node.mempool_manager.get_spendbundle(sb1.name()) is None + assert not full_node_1.full_node.mempool_manager.seen(sb1.name()) # Wait some time so wallet will retry await asyncio.sleep(2) diff --git a/chia/_tests/wallet/test_wallet_state_manager.py b/chia/_tests/wallet/test_wallet_state_manager.py index 23a7031eb739..06ee7bf518e5 100644 --- a/chia/_tests/wallet/test_wallet_state_manager.py +++ b/chia/_tests/wallet/test_wallet_state_manager.py @@ -61,7 +61,7 @@ async def test_get_private_key(simulator_and_wallet: OldSimulatorsAndWallets, ha wallet_state_manager: WalletStateManager = wallet_node.wallet_state_manager derivation_index = uint32(10000) conversion_method = master_sk_to_wallet_sk if hardened else master_sk_to_wallet_sk_unhardened - expected_private_key = conversion_method(wallet_state_manager.private_key, derivation_index) + expected_private_key = conversion_method(wallet_state_manager.get_master_private_key(), derivation_index) record = DerivationRecord( derivation_index, bytes32(b"0" * 32), diff --git a/chia/_tests/wallet/vc_wallet/test_vc_wallet.py b/chia/_tests/wallet/vc_wallet/test_vc_wallet.py index 6059e1c9100f..8481f7af20e9 100644 --- a/chia/_tests/wallet/vc_wallet/test_vc_wallet.py +++ b/chia/_tests/wallet/vc_wallet/test_vc_wallet.py @@ -343,7 +343,7 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None: uint64(2000000000), memos=["hey"], ) - await wallet_node_0.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet_node_0.wallet_state_manager.add_pending_transactions([tx]) await wallet_environments.process_pending_states( [ WalletStateTransition( @@ -514,7 +514,7 @@ async def test_vc_lifecycle(wallet_environments: WalletTestFramework) -> None: uint64(0), cat_discrepancy=(-50, Program.to(None), Program.to(None)), ) - await wallet_node_1.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet_node_1.wallet_state_manager.add_pending_transactions([tx]) await wallet_environments.process_pending_states( [ WalletStateTransition(), @@ -671,7 +671,7 @@ async def test_self_revoke(wallet_environments: WalletTestFramework) -> None: # Send the DID to oblivion txs = await did_wallet.transfer_did(bytes32([0] * 32), uint64(0), False, wallet_environments.tx_config) - await did_wallet.wallet_state_manager.add_pending_transactions(txs) + txs = await did_wallet.wallet_state_manager.add_pending_transactions(txs) await wallet_environments.process_pending_states( [ WalletStateTransition( diff --git a/chia/cmds/keys.py b/chia/cmds/keys.py index 976c7e2cfafe..62f25c5131c0 100644 --- a/chia/cmds/keys.py +++ b/chia/cmds/keys.py @@ -73,12 +73,12 @@ def show_cmd( show_keys(ctx.obj["root_path"], show_mnemonic_seed, non_observer_derivation, json, fingerprint) -@keys_cmd.command("add", help="Add a private key by mnemonic") +@keys_cmd.command("add", help="Add a private key by mnemonic or public key as hex") @click.option( "--filename", "-f", default=None, - help="The filename containing the secret key mnemonic to add", + help="The filename containing the secret key mnemonic or public key hex to add", type=str, required=False, ) @@ -93,15 +93,15 @@ def show_cmd( @click.pass_context def add_cmd(ctx: click.Context, filename: str, label: Optional[str]) -> None: from .init_funcs import check_keys - from .keys_funcs import query_and_add_private_key_seed + from .keys_funcs import query_and_add_key_info - mnemonic = None + mnemonic_or_pk = None if filename: from pathlib import Path - mnemonic = Path(filename).read_text().rstrip() + mnemonic_or_pk = Path(filename).read_text().rstrip() - query_and_add_private_key_seed(mnemonic, label) + query_and_add_key_info(mnemonic_or_pk, label) check_keys(ctx.obj["root_path"]) @@ -318,21 +318,19 @@ def search_cmd( ) -> None: import sys - from chia_rs import PrivateKey - from .keys_funcs import resolve_derivation_master_key, search_derive - private_key: Optional[PrivateKey] = None fingerprint: Optional[int] = ctx.obj.get("fingerprint", None) filename: Optional[str] = ctx.obj.get("filename", None) # Specifying the master key is optional for the search command. If not specified, we'll search all keys. - if fingerprint is not None or filename is not None: - private_key = resolve_derivation_master_key(filename if filename is not None else fingerprint) + sk = None + if fingerprint is None and filename is not None: + sk = resolve_derivation_master_key(filename) found: bool = search_derive( ctx.obj["root_path"], - private_key, + fingerprint, search_terms, limit, non_observer_derivation, @@ -340,6 +338,7 @@ def search_cmd( ("all",) if "all" in search_type else search_type, derive_from_hd_path, prefix, + sk, ) sys.exit(0 if found else 1) @@ -375,10 +374,13 @@ def wallet_address_cmd( fingerprint: Optional[int] = ctx.obj.get("fingerprint", None) filename: Optional[str] = ctx.obj.get("filename", None) - private_key = resolve_derivation_master_key(filename if filename is not None else fingerprint) + + sk = None + if fingerprint is None and filename is not None: + sk = resolve_derivation_master_key(filename) derive_wallet_address( - ctx.obj["root_path"], private_key, index, count, prefix, non_observer_derivation, show_hd_path + ctx.obj["root_path"], fingerprint, index, count, prefix, non_observer_derivation, show_hd_path, sk ) @@ -443,10 +445,13 @@ def child_key_cmd( fingerprint: Optional[int] = ctx.obj.get("fingerprint", None) filename: Optional[str] = ctx.obj.get("filename", None) - private_key = resolve_derivation_master_key(filename if filename is not None else fingerprint) + + sk = None + if fingerprint is None and filename is not None: + sk = resolve_derivation_master_key(filename) derive_child_key( - private_key, + fingerprint, key_type, derive_from_hd_path.lower() if derive_from_hd_path is not None else None, index, @@ -454,4 +459,5 @@ def child_key_cmd( non_observer_derivation, show_private_keys, show_hd_path, + sk, ) diff --git a/chia/cmds/keys_funcs.py b/chia/cmds/keys_funcs.py index 53071d9024e3..f9c587806a21 100644 --- a/chia/cmds/keys_funcs.py +++ b/chia/cmds/keys_funcs.py @@ -17,13 +17,20 @@ from chia.util.errors import KeychainException from chia.util.file_keyring import MAX_LABEL_LENGTH from chia.util.ints import uint32 -from chia.util.keychain import Keychain, KeyData, bytes_to_mnemonic, generate_mnemonic, mnemonic_to_seed +from chia.util.keychain import ( + Keychain, + KeyData, + bytes_to_mnemonic, + check_mnemonic_validity, + generate_mnemonic, + mnemonic_to_seed, +) from chia.util.keyring_wrapper import KeyringWrapper from chia.wallet.derive_keys import ( + master_pk_to_wallet_pk_unhardened, master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk, - master_sk_to_wallet_sk_unhardened, ) @@ -58,29 +65,34 @@ def generate_and_add(label: Optional[str]) -> None: """ unlock_keyring() print("Generating private key") - query_and_add_private_key_seed(mnemonic=generate_mnemonic(), label=label) + query_and_add_key_info(mnemonic_or_pk=generate_mnemonic(), label=label) -def query_and_add_private_key_seed(mnemonic: Optional[str], label: Optional[str] = None) -> None: +def query_and_add_key_info(mnemonic_or_pk: Optional[str], label: Optional[str] = None) -> None: unlock_keyring() - if mnemonic is None: - mnemonic = input("Enter the mnemonic you want to use: ") + if mnemonic_or_pk is None: + mnemonic_or_pk = input("Enter the mnemonic/observer key you want to use: ") if label is None: label = input("Enter the label you want to assign to this key (Press Enter to skip): ") if len(label) == 0: label = None - add_private_key_seed(mnemonic, label) + add_key_info(mnemonic_or_pk, label) -def add_private_key_seed(mnemonic: str, label: Optional[str]) -> None: +def add_key_info(mnemonic_or_pk: str, label: Optional[str]) -> None: """ - Add a private key seed to the keyring, with the given mnemonic and an optional label. + Add a private key seed or public key to the keyring, with the given mnemonic and an optional label. """ unlock_keyring() try: - sk = Keychain().add_private_key(mnemonic, label) - fingerprint = sk.get_g1().get_fingerprint() - print(f"Added private key with public key fingerprint {fingerprint}") + if check_mnemonic_validity(mnemonic_or_pk): + sk = Keychain().add_key(mnemonic_or_pk, label, private=True) + fingerprint = sk.get_g1().get_fingerprint() + print(f"Added private key with public key fingerprint {fingerprint}") + else: + pk = Keychain().add_key(mnemonic_or_pk, label, private=False) + fingerprint = pk.get_fingerprint() + print(f"Added public key with fingerprint {fingerprint}") except (ValueError, KeychainException) as e: print(e) @@ -148,35 +160,53 @@ def show_keys( return None if not json_output: - msg = "Showing all public keys derived from your master seed and private key:" + msg = "Showing all public keys derived from your master key:" if show_mnemonic: msg = "Showing all public and private keys" print(msg) def process_key_data(key_data: KeyData) -> Dict[str, Any]: key: Dict[str, Any] = {} - sk = key_data.private_key + sk = key_data.private_key if key_data.secrets is not None else None if key_data.label is not None: key["label"] = key_data.label key["fingerprint"] = key_data.fingerprint key["master_pk"] = bytes(key_data.public_key).hex() - key["farmer_pk"] = bytes(master_sk_to_farmer_sk(sk).get_g1()).hex() - key["pool_pk"] = bytes(master_sk_to_pool_sk(sk).get_g1()).hex() - first_wallet_sk: PrivateKey = ( - master_sk_to_wallet_sk(sk, uint32(0)) - if non_observer_derivation - else master_sk_to_wallet_sk_unhardened(sk, uint32(0)) - ) - wallet_address: str = encode_puzzle_hash(create_puzzlehash_for_pk(first_wallet_sk.get_g1()), prefix) - key["wallet_address"] = wallet_address + if sk is not None: + key["farmer_pk"] = bytes(master_sk_to_farmer_sk(sk).get_g1()).hex() + key["pool_pk"] = bytes(master_sk_to_pool_sk(sk).get_g1()).hex() + else: + key["farmer_pk"] = None + key["pool_pk"] = None + + if non_observer_derivation: + if sk is None: + first_wallet_pk: Optional[G1Element] = None + else: + first_wallet_pk = master_sk_to_wallet_sk(sk, uint32(0)).get_g1() + else: + first_wallet_pk = master_pk_to_wallet_pk_unhardened(key_data.public_key, uint32(0)) + + if first_wallet_pk is not None: + wallet_address: str = encode_puzzle_hash(create_puzzlehash_for_pk(first_wallet_pk), prefix) + key["wallet_address"] = wallet_address + else: + key["wallet_address"] = None + key["non_observer"] = non_observer_derivation - if show_mnemonic: + if show_mnemonic and sk is not None: key["master_sk"] = bytes(sk).hex() key["farmer_sk"] = bytes(master_sk_to_farmer_sk(sk)).hex() key["wallet_sk"] = bytes(master_sk_to_wallet_sk(sk, uint32(0))).hex() key["mnemonic"] = bytes_to_mnemonic(key_data.entropy) + else: + key["master_sk"] = None + key["farmer_sk"] = None + key["wallet_sk"] = None + key["mnemonic"] = None + return key keys = [process_key_data(key) for key in all_keys] @@ -184,7 +214,8 @@ def process_key_data(key_data: KeyData) -> Dict[str, Any]: if json_output: print(json.dumps({"keys": list(keys)})) else: - for key in keys: + for _key in keys: + key = {k: "N/A" if v is None else v for k, v in _key.items()} print("") if "label" in key: print("Label:", key["label"]) @@ -210,13 +241,15 @@ def delete(fingerprint: int) -> None: Keychain().delete_key_by_fingerprint(fingerprint) -def derive_sk_from_hd_path(master_sk: PrivateKey, hd_path_root: str) -> Tuple[PrivateKey, str]: +def derive_pk_and_sk_from_hd_path( + master_pk: G1Element, hd_path_root: str, master_sk: Optional[PrivateKey] = None +) -> Tuple[G1Element, Optional[PrivateKey], str]: """ Derive a private key from the provided HD path. Takes a master key and HD path as input, and returns the derived key and the HD path that was used to derive it. """ - from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened + from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened, _derive_pk_unhardened class DerivationType(Enum): NONOBSERVER = 0 @@ -239,28 +272,38 @@ class DerivationType(Enum): raise ValueError("Invalid HD path. Empty index") non_observer: bool = current_index_str[-1] == "n" + if non_observer and master_sk is None: + raise ValueError("Hardened path specified for observer key") current_index: int = int(current_index_str[:-1]) if non_observer else int(current_index_str) index_and_derivation_types.append( (current_index, DerivationType.NONOBSERVER if non_observer else DerivationType.OBSERVER) ) - current_sk: PrivateKey = master_sk - # Derive keys along the path - for current_index, derivation_type in index_and_derivation_types: - if derivation_type == DerivationType.NONOBSERVER: - current_sk = _derive_path(current_sk, [current_index]) - elif derivation_type == DerivationType.OBSERVER: - current_sk = _derive_path_unhardened(current_sk, [current_index]) - else: - raise ValueError(f"Unhandled derivation type: {derivation_type}") + if master_sk is not None: + current_sk: Optional[PrivateKey] = master_sk + assert current_sk is not None + for current_index, derivation_type in index_and_derivation_types: + if derivation_type == DerivationType.NONOBSERVER: + current_sk = _derive_path(current_sk, [current_index]) + elif derivation_type == DerivationType.OBSERVER: + current_sk = _derive_path_unhardened(current_sk, [current_index]) + else: + raise ValueError(f"Unhandled derivation type: {derivation_type}") # pragma: no cover + current_pk: G1Element = current_sk.get_g1() + else: + current_sk = None + current_pk = master_pk + for current_index, _ in index_and_derivation_types: + current_pk = _derive_pk_unhardened(current_pk, [current_index]) - return (current_sk, "m/" + "/".join(path) + "/") + return (current_pk, current_sk, "m/" + "/".join(path) + "/") def sign(message: str, private_key: PrivateKey, hd_path: str, as_bytes: bool, json_output: bool) -> None: - sk: PrivateKey = derive_sk_from_hd_path(private_key, hd_path)[0] + sk = derive_pk_and_sk_from_hd_path(private_key.get_g1(), hd_path, master_sk=private_key)[1] + assert sk is not None data = bytes.fromhex(message) if as_bytes else bytes(message, "utf-8") signing_mode: SigningMode = ( SigningMode.BLS_MESSAGE_AUGMENTATION_HEX_INPUT if as_bytes else SigningMode.BLS_MESSAGE_AUGMENTATION_UTF8_INPUT @@ -307,7 +350,8 @@ def _clear_line_part(n: int) -> None: def _search_derived( - current_sk: PrivateKey, + current_pk: G1Element, + current_sk: Optional[PrivateKey], search_terms: Tuple[str, ...], path: str, path_indices: Optional[List[int]], @@ -320,11 +364,11 @@ def _search_derived( prefix: str, ) -> List[str]: # Return a subset of search_terms that were found """ - Performs a shallow search of keys derived from the current sk for items matching + Performs a shallow search of keys derived from the current pk/sk for items matching the provided search terms. """ - from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened + from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened, _derive_pk_unhardened class DerivedSearchResultType(Enum): PUBLIC_KEY = "public key" @@ -336,6 +380,8 @@ class DerivedSearchResultType(Enum): current_path_indices: List[int] = path_indices if path_indices is not None else [] found_search_terms: List[str] = [] + assert not (non_observer_derivation and current_sk is None) + for index in range(limit): found_items: List[Tuple[str, str, DerivedSearchResultType]] = [] printed_match: bool = False @@ -349,15 +395,19 @@ class DerivedSearchResultType(Enum): # Derive the private key if non_observer_derivation: + assert current_sk is not None # semantics above guarantee this child_sk = _derive_path(current_sk, current_path_indices) + if search_public_key or search_address: + child_pk = child_sk.get_g1() else: - child_sk = _derive_path_unhardened(current_sk, current_path_indices) - - child_pk: Optional[G1Element] = None - - # Public key is needed for searching against wallet addresses or public keys - if search_public_key or search_address: - child_pk = child_sk.get_g1() + if search_public_key or search_address: + child_pk = _derive_pk_unhardened(current_pk, current_path_indices) + else: + child_pk = None + if search_private_key and current_sk is not None: + child_sk = _derive_path_unhardened(current_sk, current_path_indices) + else: + child_sk = None address: Optional[str] = None @@ -372,6 +422,7 @@ class DerivedSearchResultType(Enum): found_item_type: Optional[DerivedSearchResultType] = None if search_private_key and term in str(child_sk): + assert child_sk is not None # semantics above guarantee this found_item = private_key_string_repr(child_sk) found_item_type = DerivedSearchResultType.PRIVATE_KEY elif search_public_key and child_pk is not None and term in str(child_pk): @@ -419,7 +470,7 @@ class DerivedSearchResultType(Enum): def search_derive( root_path: Path, - private_key: Optional[PrivateKey], + fingerprint: Optional[int], search_terms: Tuple[str, ...], limit: int, non_observer_derivation: bool, @@ -427,6 +478,7 @@ def search_derive( search_types: Tuple[str, ...], derive_from_hd_path: Optional[str], prefix: Optional[str], + private_key: Optional[PrivateKey], ) -> bool: """ Searches for items derived from the provided private key, or if not specified, @@ -436,7 +488,6 @@ def search_derive( from time import perf_counter start_time = perf_counter() - private_keys: List[PrivateKey] remaining_search_terms: Dict[str, None] = dict.fromkeys(search_terms) # poor man's ordered set search_address = "address" in search_types search_public_key = "public_key" in search_types @@ -452,27 +503,41 @@ def search_derive( search_public_key = True search_private_key = True - if private_key is None: - private_keys = [sk for sk, _ in Keychain().get_all_private_keys()] - else: + if fingerprint is None and private_key is None: + public_keys: List[G1Element] = Keychain().get_all_public_keys() + private_keys: List[Optional[PrivateKey]] = [ + data.private_key if data.secrets is not None else None for data in Keychain().get_keys(include_secrets=True) + ] + elif fingerprint is None: + assert private_key is not None + public_keys = [private_key.get_g1()] private_keys = [private_key] + else: + master_key_data = Keychain().get_key(fingerprint, include_secrets=True) + public_keys = [master_key_data.public_key] + private_keys = [master_key_data.private_key if master_key_data.secrets is not None else None] - for sk in private_keys: + for pk, sk in zip(public_keys, private_keys): + if sk is None and non_observer_derivation: + continue current_path: str = "" found_terms: List[str] = [] if show_progress: - print(f"Searching keys derived from: {sk.get_g1().get_fingerprint()}") + print(f"Searching keys derived from: {pk.get_fingerprint()}") # Derive from the provided HD path if derive_from_hd_path is not None: - derivation_root_sk, hd_path_root = derive_sk_from_hd_path(sk, derive_from_hd_path) + derivation_root_pk, derivation_root_sk, hd_path_root = derive_pk_and_sk_from_hd_path( + pk, derive_from_hd_path, master_sk=sk + ) if show_progress: sys.stdout.write(hd_path_root) # Shallow search under hd_path_root found_terms = _search_derived( + derivation_root_pk, derivation_root_sk, tuple(remaining_search_terms.keys()), hd_path_root, @@ -519,6 +584,7 @@ def search_derive( sys.stdout.write(f"{account_str}/") # lgtm [py/clear-text-logging-sensitive-data] found_terms = _search_derived( + pk, sk, tuple(remaining_search_terms.keys()), current_path, @@ -569,16 +635,31 @@ def search_derive( def derive_wallet_address( root_path: Path, - private_key: PrivateKey, + fingerprint: Optional[int], index: int, count: int, prefix: Optional[str], non_observer_derivation: bool, show_hd_path: bool, + private_key: Optional[PrivateKey], ) -> None: """ Generate wallet addresses using keys derived from the provided private key. """ + if fingerprint is not None: + key_data: KeyData = Keychain().get_key(fingerprint, include_secrets=non_observer_derivation) + if non_observer_derivation and key_data.secrets is None: + print("Need a private key for non observer derivation of wallet addresses") + return + elif non_observer_derivation: + sk = key_data.private_key + else: + sk = None + pk = key_data.public_key + else: + assert private_key is not None + sk = private_key + pk = sk.get_g1() if prefix is None: config: Dict[str, Any] = load_config(root_path, "config.yaml") @@ -590,12 +671,13 @@ def derive_wallet_address( wallet_hd_path_root += f"{i}{'n' if non_observer_derivation else ''}/" for i in range(index, index + count): if non_observer_derivation: - sk = master_sk_to_wallet_sk(private_key, uint32(i)) + assert sk is not None + pubkey = master_sk_to_wallet_sk(sk, uint32(i)).get_g1() else: - sk = master_sk_to_wallet_sk_unhardened(private_key, uint32(i)) + pubkey = master_pk_to_wallet_pk_unhardened(pk, uint32(i)) # Generate a wallet address using the standard p2_delegated_puzzle_or_hidden_puzzle puzzle # TODO: consider generating addresses using other puzzles - address = encode_puzzle_hash(create_puzzlehash_for_pk(sk.get_g1()), prefix) + address = encode_puzzle_hash(create_puzzlehash_for_pk(pubkey), prefix) if show_hd_path: print( f"Wallet address {i} " @@ -613,7 +695,7 @@ def private_key_string_repr(private_key: PrivateKey) -> str: def derive_child_key( - master_sk: PrivateKey, + fingerprint: Optional[int], key_type: Optional[str], derive_from_hd_path: Optional[str], index: int, @@ -621,16 +703,24 @@ def derive_child_key( non_observer_derivation: bool, show_private_keys: bool, show_hd_path: bool, + private_key: Optional[PrivateKey], ) -> None: """ Derive child keys from the provided master key. """ + from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened, _derive_pk_unhardened - from chia.wallet.derive_keys import _derive_path, _derive_path_unhardened + if fingerprint is not None: + key_data: KeyData = Keychain().get_key(fingerprint, include_secrets=True) + current_pk: G1Element = key_data.public_key + current_sk: Optional[PrivateKey] = key_data.private_key if key_data.secrets is not None else None + else: + assert private_key is not None + current_pk = private_key.get_g1() + current_sk = private_key - derivation_root_sk: Optional[PrivateKey] = None - hd_path_root: Optional[str] = None - current_sk: Optional[PrivateKey] = None + if non_observer_derivation and current_sk is None: + raise ValueError("Cannot perform non-observer derivation on an observer-only key") # Key type was specified if key_type is not None: @@ -648,38 +738,51 @@ def derive_child_key( ) if non_observer_derivation: - current_sk = _derive_path(master_sk, path_indices) + assert current_sk is not None # semantics above guarantee this + current_sk = _derive_path(current_sk, path_indices) else: - current_sk = _derive_path_unhardened(master_sk, path_indices) + if current_sk is not None: + current_sk = _derive_path_unhardened(current_sk, path_indices) + else: + current_pk = _derive_pk_unhardened(current_pk, path_indices) derivation_root_sk = current_sk + derivation_root_pk = current_pk hd_path_root = "m/" for i in path_indices: hd_path_root += f"{i}{'n' if non_observer_derivation else ''}/" # Arbitrary HD path was specified elif derive_from_hd_path is not None: - derivation_root_sk, hd_path_root = derive_sk_from_hd_path(master_sk, derive_from_hd_path) + derivation_root_pk, derivation_root_sk, hd_path_root = derive_pk_and_sk_from_hd_path( + current_pk, derive_from_hd_path, master_sk=current_sk + ) # Derive child keys from derivation_root_sk - if derivation_root_sk is not None and hd_path_root is not None: - for i in range(index, index + count): - if non_observer_derivation: - sk = _derive_path(derivation_root_sk, [i]) - else: + for i in range(index, index + count): + if non_observer_derivation: + assert derivation_root_sk is not None # semantics above guarantee this + sk = _derive_path(derivation_root_sk, [i]) + pk = sk.get_g1() + else: + if derivation_root_sk is not None: sk = _derive_path_unhardened(derivation_root_sk, [i]) - hd_path: str = ( - " (" + hd_path_root + str(i) + ("n" if non_observer_derivation else "") + ")" if show_hd_path else "" - ) - key_type_str: Optional[str] - - if key_type is not None: - key_type_str = key_type.capitalize() + pk = sk.get_g1() else: - key_type_str = "Non-Observer" if non_observer_derivation else "Observer" + sk = None + pk = _derive_pk_unhardened(derivation_root_pk, [i]) + hd_path: str = ( + " (" + hd_path_root + str(i) + ("n" if non_observer_derivation else "") + ")" if show_hd_path else "" + ) + key_type_str: Optional[str] + + if key_type is not None: + key_type_str = key_type.capitalize() + else: + key_type_str = "Non-Observer" if non_observer_derivation else "Observer" - print(f"{key_type_str} public key {i}{hd_path}: {sk.get_g1()}") - if show_private_keys: - print(f"{key_type_str} private key {i}{hd_path}: {private_key_string_repr(sk)}") + print(f"{key_type_str} public key {i}{hd_path}: {pk}") + if show_private_keys and sk is not None: + print(f"{key_type_str} private key {i}{hd_path}: {private_key_string_repr(sk)}") def private_key_for_fingerprint(fingerprint: int) -> Optional[PrivateKey]: diff --git a/chia/cmds/plotnft.py b/chia/cmds/plotnft.py index 04744c4e9007..888c2d05ba74 100644 --- a/chia/cmds/plotnft.py +++ b/chia/cmds/plotnft.py @@ -94,7 +94,11 @@ def create_cmd( print(" pool_url argument (-u) is required for pool starting state") return valid_initial_states = {"pool": "FARMING_TO_POOL", "local": "SELF_POOLING"} - asyncio.run(create(wallet_rpc_port, fingerprint, pool_url, valid_initial_states[state], Decimal(fee), dont_prompt)) + asyncio.run( + create( + wallet_rpc_port, fingerprint, pool_url, valid_initial_states[state], Decimal(fee), prompt=not dont_prompt + ) + ) @plotnft_cmd.command("join", help="Join a plot NFT to a Pool") @@ -133,7 +137,7 @@ def join_cmd( pool_url=pool_url, fee=Decimal(fee), wallet_id=id, - prompt=dont_prompt, + prompt=not dont_prompt, ) ) @@ -170,7 +174,7 @@ def self_pool_cmd(wallet_rpc_port: Optional[int], fingerprint: int, id: int, fee fingerprint=fingerprint, fee=Decimal(fee), wallet_id=id, - prompt=dont_prompt, + prompt=not dont_prompt, ) ) diff --git a/chia/cmds/plotnft_funcs.py b/chia/cmds/plotnft_funcs.py index 7c7ef7788cb1..cf4208da108f 100644 --- a/chia/cmds/plotnft_funcs.py +++ b/chia/cmds/plotnft_funcs.py @@ -12,6 +12,7 @@ import aiohttp from chia.cmds.cmds_util import ( + cli_confirm, get_any_service_client, get_wallet_client, transaction_status_msg, @@ -61,7 +62,7 @@ async def create_pool_args(pool_url: str) -> Dict[str, Any]: async def create( - wallet_rpc_port: Optional[int], fingerprint: int, pool_url: Optional[str], state: str, fee: Decimal, prompt: bool + wallet_rpc_port: Optional[int], fingerprint: int, pool_url: Optional[str], state: str, fee: Decimal, *, prompt: bool ) -> None: async with get_wallet_client(wallet_rpc_port, fingerprint) as (wallet_client, fingerprint, _): fee_mojos = uint64(int(fee * units["chia"])) @@ -88,35 +89,28 @@ async def create( pool_msg = f" and join pool: {pool_url}" if pool_url else "" print(f"Will create a plot NFT{pool_msg}.") if prompt: - user_input: str = input("Confirm [n]/y: ") - else: - user_input = "yes" + cli_confirm("Confirm (y/n): ", "Aborting.") - if user_input.lower() == "y" or user_input.lower() == "yes": - try: - tx_record: TransactionRecord = await wallet_client.create_new_pool_wallet( - target_puzzle_hash, - pool_url, - relative_lock_height, - "localhost:5000", - "new", - state, - fee_mojos, - ) - start = time.time() - while time.time() - start < 10: - await asyncio.sleep(0.1) - tx = await wallet_client.get_transaction(tx_record.name) - if len(tx.sent_to) > 0: - print(transaction_submitted_msg(tx)) - print(transaction_status_msg(fingerprint, tx_record.name)) - return None - except Exception as e: - print( - f"Error creating plot NFT: {e}\n Please start both farmer and wallet with: chia start -r farmer" - ) - return - print("Aborting.") + try: + tx_record: TransactionRecord = await wallet_client.create_new_pool_wallet( + target_puzzle_hash, + pool_url, + relative_lock_height, + "localhost:5000", + "new", + state, + fee_mojos, + ) + start = time.time() + while time.time() - start < 10: + await asyncio.sleep(0.1) + tx = await wallet_client.get_transaction(tx_record.name) + if len(tx.sent_to) > 0: + print(transaction_submitted_msg(tx)) + print(transaction_status_msg(fingerprint, tx_record.name)) + return None + except Exception as e: + print(f"Error creating plot NFT: {e}\n Please start both farmer and wallet with: chia start -r farmer") async def pprint_pool_wallet_state( @@ -261,26 +255,20 @@ async def submit_tx_with_confirmation( ) -> None: print(message) if prompt: - user_input: str = input("Confirm [n]/y: ") - else: - user_input = "yes" - - if user_input.lower() == "y" or user_input.lower() == "yes": - try: - result = await func() - tx_record: TransactionRecord = result["transaction"] - start = time.time() - while time.time() - start < 10: - await asyncio.sleep(0.1) - tx = await wallet_client.get_transaction(tx_record.name) - if len(tx.sent_to) > 0: - print(transaction_submitted_msg(tx)) - print(transaction_status_msg(fingerprint, tx_record.name)) - return None - except Exception as e: - print(f"Error performing operation on Plot NFT -f {fingerprint} wallet id: {wallet_id}: {e}") - return - print("Aborting.") + cli_confirm("Confirm (y/n): ", "Aborting.") + try: + result = await func() + tx_record: TransactionRecord = result["transaction"] + start = time.time() + while time.time() - start < 10: + await asyncio.sleep(0.1) + tx = await wallet_client.get_transaction(tx_record.name) + if len(tx.sent_to) > 0: + print(transaction_submitted_msg(tx)) + print(transaction_status_msg(fingerprint, tx_record.name)) + return None + except Exception as e: + print(f"Error performing operation on Plot NFT -f {fingerprint} wallet id: {wallet_id}: {e}") async def join_pool( diff --git a/chia/cmds/sim_funcs.py b/chia/cmds/sim_funcs.py index 698d8ef42353..a29279e4de9d 100644 --- a/chia/cmds/sim_funcs.py +++ b/chia/cmds/sim_funcs.py @@ -158,7 +158,7 @@ def generate_and_return_fingerprint(mnemonic: Optional[str] = None) -> int: print("Generating private key") mnemonic = generate_mnemonic() try: - sk = Keychain().add_private_key(mnemonic, None) + sk = Keychain().add_key(mnemonic, None) fingerprint: int = sk.get_g1().get_fingerprint() except KeychainFingerprintExists as e: fingerprint = e.fingerprint diff --git a/chia/consensus/blockchain.py b/chia/consensus/blockchain.py index 1b6fcebdbab4..5891d81b2cb7 100644 --- a/chia/consensus/blockchain.py +++ b/chia/consensus/blockchain.py @@ -309,7 +309,7 @@ async def add_block( The result of adding the block to the blockchain (NEW_PEAK, ADDED_AS_ORPHAN, INVALID_BLOCK, DISCONNECTED_BLOCK, ALREDY_HAVE_BLOCK) An optional error if the result is not NEW_PEAK or ADDED_AS_ORPHAN - A StateChangeSumamry iff NEW_PEAK, with: + A StateChangeSummary iff NEW_PEAK, with: - A fork point if the result is NEW_PEAK - A list of coin changes as a result of rollback - A list of NPCResult for any new transaction block added to the chain diff --git a/chia/consensus/default_constants.py b/chia/consensus/default_constants.py index 4e6a4bf8d6d4..f41916b57fce 100644 --- a/chia/consensus/default_constants.py +++ b/chia/consensus/default_constants.py @@ -63,7 +63,7 @@ MAX_GENERATOR_REF_LIST_SIZE=uint32(512), # Number of references allowed in the block generator ref list POOL_SUB_SLOT_ITERS=uint64(37600000000), # iters limit * NUM_SPS SOFT_FORK2_HEIGHT=uint32(0), - SOFT_FORK4_HEIGHT=uint32(5650000), + SOFT_FORK4_HEIGHT=uint32(5716000), # June 2024 HARD_FORK_HEIGHT=uint32(5496000), HARD_FORK_FIX_HEIGHT=uint32(5496000), diff --git a/chia/daemon/keychain_proxy.py b/chia/daemon/keychain_proxy.py index 7b30031c931c..250a4bfe4156 100644 --- a/chia/daemon/keychain_proxy.py +++ b/chia/daemon/keychain_proxy.py @@ -5,10 +5,10 @@ import ssl import traceback from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload from aiohttp import ClientConnectorError, ClientSession -from chia_rs import AugSchemeMPL, PrivateKey +from chia_rs import AugSchemeMPL, G1Element, PrivateKey from chia.cmds.init_funcs import check_keys from chia.daemon.client import DaemonProxy @@ -20,6 +20,7 @@ KEYCHAIN_ERR_NO_KEYS, ) from chia.server.server import ssl_context_for_client +from chia.util.byte_types import hexstr_to_bytes from chia.util.config import load_config from chia.util.errors import ( KeychainIsEmpty, @@ -169,20 +170,42 @@ def handle_error(self, response: WsRpcMessage) -> None: raise Exception(f"{err}") raise Exception(f"{error}") - async def add_private_key(self, mnemonic: str, label: Optional[str] = None) -> PrivateKey: + @overload + async def add_key(self, mnemonic_or_pk: str) -> PrivateKey: ... + + @overload + async def add_key(self, mnemonic_or_pk: str, label: Optional[str]) -> PrivateKey: ... + + @overload + async def add_key(self, mnemonic_or_pk: str, label: Optional[str], private: Literal[True]) -> PrivateKey: ... + + @overload + async def add_key(self, mnemonic_or_pk: str, label: Optional[str], private: Literal[False]) -> G1Element: ... + + @overload + async def add_key( + self, mnemonic_or_pk: str, label: Optional[str], private: bool + ) -> Union[PrivateKey, G1Element]: ... + + async def add_key( + self, mnemonic_or_pk: str, label: Optional[str] = None, private: bool = True + ) -> Union[PrivateKey, G1Element]: """ - Forwards to Keychain.add_private_key() + Forwards to Keychain.add_key() """ - key: PrivateKey + key: Union[PrivateKey, G1Element] if self.use_local_keychain(): - key = self.keychain.add_private_key(mnemonic, label) + key = self.keychain.add_key(mnemonic_or_pk, label, private) else: response, success = await self.get_response_for_request( - "add_private_key", {"mnemonic": mnemonic, "label": label} + "add_key", {"mnemonic_or_pk": mnemonic_or_pk, "label": label, "private": private} ) if success: - seed = mnemonic_to_seed(mnemonic) - key = AugSchemeMPL.key_gen(seed) + if private: + seed = mnemonic_to_seed(mnemonic_or_pk) + key = AugSchemeMPL.key_gen(seed) + else: + key = G1Element.from_bytes(hexstr_to_bytes(mnemonic_or_pk)) else: error = response["data"].get("error", None) if error == KEYCHAIN_ERR_KEYERROR: @@ -304,37 +327,57 @@ async def get_first_private_key(self) -> Optional[PrivateKey]: return key - async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[PrivateKey]: + @overload + async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[PrivateKey]: ... + + @overload + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: Literal[True] + ) -> Optional[PrivateKey]: ... + + @overload + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: Literal[False] + ) -> Optional[G1Element]: ... + + @overload + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: bool + ) -> Optional[Union[PrivateKey, G1Element]]: ... + + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: bool = True + ) -> Optional[Union[PrivateKey, G1Element]]: """ Locates and returns a private key matching the provided fingerprint """ - key: Optional[PrivateKey] = None + key: Optional[Union[PrivateKey, G1Element]] = None if self.use_local_keychain(): - private_keys = self.keychain.get_all_private_keys() - if len(private_keys) == 0: + keys = self.keychain.get_keys(include_secrets=private) + if len(keys) == 0: raise KeychainIsEmpty() else: + selected_key = keys[0] if fingerprint is not None: - for sk, _ in private_keys: - if sk.get_g1().get_fingerprint() == fingerprint: - key = sk + for key_data in keys: + if key_data.public_key.get_fingerprint() == fingerprint: + selected_key = key_data break - if key is None: + else: raise KeychainKeyNotFound(fingerprint) - else: - key = private_keys[0][0] + key = selected_key.private_key if private else selected_key.public_key else: response, success = await self.get_response_for_request( - "get_key_for_fingerprint", {"fingerprint": fingerprint} + "get_key_for_fingerprint", {"fingerprint": fingerprint, "private": private} ) if success: pk = response["data"].get("pk", None) ent = response["data"].get("entropy", None) - if pk is None or ent is None: + if pk is None or (private and ent is None): err = f"Missing pk and/or ent in {response.get('command')} response" self.log.error(f"{err}") raise KeychainMalformedResponse(f"{err}") - else: + elif private: mnemonic = bytes_to_mnemonic(bytes.fromhex(ent)) seed = mnemonic_to_seed(mnemonic) private_key = AugSchemeMPL.key_gen(seed) @@ -343,6 +386,8 @@ async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[ else: err = "G1Elements don't match" self.log.error(f"{err}") + else: + key = G1Element.from_bytes(bytes.fromhex(pk)) else: self.handle_error(response) diff --git a/chia/daemon/keychain_server.py b/chia/daemon/keychain_server.py index 94747e4db3d7..b5b506c560f1 100644 --- a/chia/daemon/keychain_server.py +++ b/chia/daemon/keychain_server.py @@ -3,7 +3,7 @@ import logging from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Dict, List, Optional, Type +from typing import Any, Dict, List, Type from chia_rs import PrivateKey @@ -16,6 +16,7 @@ # Commands that are handled by the KeychainServer keychain_commands = [ "add_private_key", + "add_key", "check_keys", "delete_all_keys", "delete_key_by_fingerprint", @@ -173,7 +174,11 @@ def get_keychain_for_request(self, request: Dict[str, Any]) -> Keychain: async def handle_command(self, command: str, data: Dict[str, Any]) -> Dict[str, Any]: try: if command == "add_private_key": - return await self.add_private_key(data) + return await self.add_key( + {"mnemonic_or_pk": data.get("mnemonic", None), "label": data.get("label", None), "private": True} + ) + elif command == "add_key": + return await self.add_key(data) elif command == "check_keys": return await self.check_keys(data) elif command == "delete_all_keys": @@ -203,22 +208,23 @@ async def handle_command(self, command: str, data: Dict[str, Any]) -> Dict[str, log.exception(e) return {"success": False, "error": str(e), "command": command} - async def add_private_key(self, request: Dict[str, Any]) -> Dict[str, Any]: + async def add_key(self, request: Dict[str, Any]) -> Dict[str, Any]: if self.get_keychain_for_request(request).is_keyring_locked(): return {"success": False, "error": KEYCHAIN_ERR_LOCKED} - mnemonic = request.get("mnemonic", None) + mnemonic_or_pk = request.get("mnemonic_or_pk", None) label = request.get("label", None) + private = request.get("private", True) - if mnemonic is None: + if mnemonic_or_pk is None: return { "success": False, "error": KEYCHAIN_ERR_MALFORMED_REQUEST, - "error_details": {"message": "missing mnemonic"}, + "error_details": {"message": "missing key information"}, } try: - sk = self.get_keychain_for_request(request).add_private_key(mnemonic, label) + key = self.get_keychain_for_request(request).add_key(mnemonic_or_pk, label, private) except KeyError as e: return { "success": False, @@ -232,7 +238,12 @@ async def add_private_key(self, request: Dict[str, Any]) -> Dict[str, Any]: "error": str(e), } - return {"success": True, "fingerprint": sk.get_g1().get_fingerprint()} + if isinstance(key, PrivateKey): + fingerprint = key.get_g1().get_fingerprint() + else: + fingerprint = key.get_fingerprint() + + return {"success": True, "fingerprint": fingerprint} async def check_keys(self, request: Dict[str, Any]) -> Dict[str, Any]: if self.get_keychain_for_request(request).is_keyring_locked(): @@ -330,25 +341,25 @@ async def get_first_private_key(self, request: Dict[str, Any]) -> Dict[str, Any] return {"success": True, "private_key": key} async def get_key_for_fingerprint(self, request: Dict[str, Any]) -> Dict[str, Any]: - if self.get_keychain_for_request(request).is_keyring_locked(): + keychain = self.get_keychain_for_request(request) + if keychain.is_keyring_locked(): return {"success": False, "error": KEYCHAIN_ERR_LOCKED} - private_keys = self.get_keychain_for_request(request).get_all_private_keys() - if len(private_keys) == 0: + private = request.get("private", True) + keys = keychain.get_keys(include_secrets=private) + if len(keys) == 0: return {"success": False, "error": KEYCHAIN_ERR_NO_KEYS} - fingerprint = request.get("fingerprint", None) - private_key: Optional[PrivateKey] = None - entropy: Optional[bytes] = None - if fingerprint is not None: - for sk, entropy in private_keys: - if sk.get_g1().get_fingerprint() == fingerprint: - private_key = sk - break - else: - private_key, entropy = private_keys[0] - - if private_key is not None and entropy is not None: - return {"success": True, "pk": bytes(private_key.get_g1()).hex(), "entropy": entropy.hex()} - else: + try: + if request["fingerprint"] is None: + key_data = keys[0] + else: + key_data = keychain.get_key(request["fingerprint"], include_secrets=private) + except KeychainFingerprintNotFound: return {"success": False, "error": KEYCHAIN_ERR_KEY_NOT_FOUND} + + return { + "success": True, + "pk": bytes(key_data.public_key).hex(), + "entropy": key_data.entropy.hex() if private else None, + } diff --git a/chia/daemon/server.py b/chia/daemon/server.py index 32ff10c30b0b..db616ed00163 100644 --- a/chia/daemon/server.py +++ b/chia/daemon/server.py @@ -47,10 +47,10 @@ from chia.util.setproctitle import setproctitle from chia.util.ws_message import WsRpcMessage, create_payload, format_response from chia.wallet.derive_keys import ( + master_pk_to_wallet_pk_unhardened, master_sk_to_farmer_sk, master_sk_to_pool_sk, master_sk_to_wallet_sk, - master_sk_to_wallet_sk_unhardened, ) io_pool_exc = ThreadPoolExecutor() @@ -656,16 +656,17 @@ async def get_wallet_addresses(self, websocket: WebSocketResponse, request: Dict for key in keys: address_entries = [] - # we require access to the private key to generate wallet addresses - if key.secrets is None: + # we require access to the private key to generate wallet addresses for non observer + if key.secrets is None and non_observer_derivation: return {"success": False, "error": f"missing private key for key with fingerprint {key.fingerprint}"} for i in range(index, index + count): if non_observer_derivation: - sk = master_sk_to_wallet_sk(key.secrets.private_key, uint32(i)) + sk = master_sk_to_wallet_sk(key.private_key, uint32(i)) + pk = sk.get_g1() else: - sk = master_sk_to_wallet_sk_unhardened(key.secrets.private_key, uint32(i)) - wallet_address = encode_puzzle_hash(create_puzzlehash_for_pk(sk.get_g1()), prefix) + pk = master_pk_to_wallet_pk_unhardened(key.public_key, uint32(i)) + wallet_address = encode_puzzle_hash(create_puzzlehash_for_pk(pk), prefix) if non_observer_derivation: hd_path = f"m/12381n/8444n/2n/{i}n" else: @@ -686,6 +687,8 @@ async def get_keys_for_plotting(self, websocket: WebSocketResponse, request: Dic keys_for_plot: Dict[uint32, Any] = {} for key in keys: + if key.secrets is None: + continue sk = key.private_key farmer_public_key: G1Element = master_sk_to_farmer_sk(sk).get_g1() pool_public_key: G1Element = master_sk_to_pool_sk(sk).get_g1() diff --git a/chia/data_layer/data_layer.py b/chia/data_layer/data_layer.py index d449c23f6dc0..d5665b1f0d9c 100644 --- a/chia/data_layer/data_layer.py +++ b/chia/data_layer/data_layer.py @@ -338,6 +338,7 @@ async def batch_insert( store_id: bytes32, changelist: List[Dict[str, Any]], status: Status = Status.PENDING, + enable_batch_autoinsert: Optional[bool] = None, ) -> bytes32: await self._update_confirmation_status(store_id=store_id) @@ -352,7 +353,9 @@ async def batch_insert( raise ValueError(f"Singleton with launcher ID {store_id} is not owned by DL Wallet") t1 = time.monotonic() - batch_hash = await self.data_store.insert_batch(store_id, changelist, status) + if enable_batch_autoinsert is None: + enable_batch_autoinsert = self.config.get("enable_batch_autoinsert", True) + batch_hash = await self.data_store.insert_batch(store_id, changelist, status, enable_batch_autoinsert) t2 = time.monotonic() self.log.info(f"Data store batch update process time: {t2 - t1}.") # todo return empty node hash from get_tree_root @@ -950,6 +953,7 @@ async def process_offered_stores(self, offer_stores: Tuple[OfferStore, ...]) -> new_root_hash = await self.batch_insert( store_id=offer_store.store_id, changelist=changelist, + enable_batch_autoinsert=False, ) else: existing_root = await self.get_root(store_id=offer_store.store_id) diff --git a/chia/data_layer/data_layer_wallet.py b/chia/data_layer/data_layer_wallet.py index 8322bc046752..1670bd25162c 100644 --- a/chia/data_layer/data_layer_wallet.py +++ b/chia/data_layer/data_layer_wallet.py @@ -408,7 +408,6 @@ async def create_update_state_spend( new_puz_hash: Optional[bytes32] = None, new_amount: Optional[uint64] = None, fee: uint64 = uint64(0), - sign: bool = True, add_pending_singleton: bool = True, announce_new_state: bool = False, extra_conditions: Tuple[Condition, ...] = tuple(), @@ -576,10 +575,7 @@ async def create_update_state_spend( SerializedProgram.from_program(full_sol), ) - if sign: - spend_bundle = await self.sign(coin_spend) - else: - spend_bundle = SpendBundle([coin_spend], G2Element()) + spend_bundle = SpendBundle([coin_spend], G2Element()) if announce_new_state: spend_bundle = dataclasses.replace(spend_bundle, coin_spends=[coin_spend, second_coin_spend]) @@ -610,7 +606,7 @@ async def create_update_state_spend( ) 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) + 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: @@ -640,9 +636,6 @@ async def generate_signed_transaction( ) -> List[TransactionRecord]: launcher_id: Optional[bytes32] = kwargs.get("launcher_id", None) new_root_hash: Optional[bytes32] = kwargs.get("new_root_hash", None) - sign: bool = kwargs.get( - "sign", True - ) # This only prevent signing of THIS wallet's part of the tx (fee will still be signed) add_pending_singleton: bool = kwargs.get("add_pending_singleton", True) announce_new_state: bool = kwargs.get("announce_new_state", False) # Figure out the launcher ID @@ -669,7 +662,6 @@ async def generate_signed_transaction( puzzle_hashes[0], amounts[0], fee, - sign, add_pending_singleton, announce_new_state, extra_conditions, @@ -790,7 +782,7 @@ async def delete_mirror( ] ), ) - mirror_bundle: SpendBundle = await self.sign(mirror_spend) + mirror_bundle: SpendBundle = SpendBundle([mirror_spend], G2Element()) txs = [ TransactionRecord( confirmed_at_height=uint32(0), @@ -1112,9 +1104,6 @@ async def get_pending_change_balance(self) -> uint64: async def get_max_send_amount(self, unspent_records: Optional[Set[WalletCoinRecord]] = None) -> uint128: return uint128(0) - async def sign(self, coin_spend: CoinSpend) -> SpendBundle: - return await self.wallet_state_manager.sign_transaction([coin_spend]) - def get_name(self) -> str: return self.wallet_info.name @@ -1186,7 +1175,6 @@ async def make_update_offer( fee=fee_left_to_pay, launcher_id=launcher, new_root_hash=new_root, - sign=False, add_pending_singleton=False, announce_new_state=True, extra_conditions=extra_conditions, @@ -1209,18 +1197,16 @@ async def make_update_offer( 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)) - signed_bundle = await dl_wallet.sign(new_spend) new_bundle: SpendBundle = dataclasses.replace( txs[0].spend_bundle, - coin_spends=all_other_spends, + coin_spends=[*all_other_spends, new_spend], ) - agg_bundle: SpendBundle = SpendBundle.aggregate([signed_bundle, new_bundle]) - all_bundles.append(agg_bundle) + all_bundles.append(new_bundle) all_transactions.append( dataclasses.replace( txs[0], - spend_bundle=agg_bundle, - name=agg_bundle.name(), + spend_bundle=new_bundle, + name=new_bundle.name(), ) ) all_transactions.extend(txs[1:]) diff --git a/chia/data_layer/data_store.py b/chia/data_layer/data_store.py index dda88747408c..2355b7162381 100644 --- a/chia/data_layer/data_store.py +++ b/chia/data_layer/data_store.py @@ -1369,11 +1369,26 @@ async def clean_node_table(self, writer: Optional[aiosqlite.Connection] = None) else: await writer.execute(query, params) + async def get_leaf_at_minimum_height(self, root_hash: bytes32) -> TerminalNode: + root_node = await self.get_node(root_hash) + queue: List[Node] = [root_node] + while True: + assert len(queue) > 0 + node = queue.pop(0) + if isinstance(node, InternalNode): + left_node = await self.get_node(node.left_hash) + right_node = await self.get_node(node.right_hash) + queue.append(left_node) + queue.append(right_node) + elif isinstance(node, TerminalNode): + return node + async def insert_batch( self, store_id: bytes32, changelist: List[Dict[str, Any]], status: Status = Status.PENDING, + enable_batch_autoinsert: bool = True, ) -> Optional[bytes32]: async with self.transaction(): old_root = await self.get_tree_root(store_id) @@ -1393,6 +1408,16 @@ async def insert_batch( assert latest_local_root is not None + key_hash_frequency: Dict[bytes32, int] = {} + first_action: Dict[bytes32, str] = {} + for change in changelist: + key = change["key"] + hash = key_hash(key) + key_hash_frequency[hash] = key_hash_frequency.get(hash, 0) + 1 + if hash not in first_action: + first_action[hash] = change["action"] + + pending_autoinsert_hashes: List[bytes32] = [] for change in changelist: if change["action"] == "insert": key = change["key"] @@ -1400,6 +1425,19 @@ async def insert_batch( reference_node_hash = change.get("reference_node_hash", None) side = change.get("side", None) if reference_node_hash is None and side is None: + hash = key_hash(key) + # The key is not referenced in any other operation but this autoinsert, hence the order + # of performing these should not matter. We perform all these autoinserts as a batch + # at the end, to speed up the tree processing operations. + # Additionally, if the first action is a delete, we can still perform the autoinsert at the + # end, since the order will be preserved. + if enable_batch_autoinsert: + if key_hash_frequency[hash] == 1 or ( + key_hash_frequency[hash] == 2 and first_action[hash] == "delete" + ): + terminal_node_hash = await self._insert_terminal_node(key, value) + pending_autoinsert_hashes.append(terminal_node_hash) + continue insert_result = await self.autoinsert( key, value, store_id, True, Status.COMMITTED, root=latest_local_root ) @@ -1431,6 +1469,43 @@ async def insert_batch( else: raise Exception(f"Operation in batch is not insert or delete: {change}") + # Start with the leaf nodes and pair them to form new nodes at the next level up, repeating this process + # in a bottom-up fashion until a single root node remains. This constructs a balanced tree from the leaves. + while len(pending_autoinsert_hashes) > 1: + new_hashes: List[bytes32] = [] + for i in range(0, len(pending_autoinsert_hashes) - 1, 2): + internal_node_hash = await self._insert_internal_node( + pending_autoinsert_hashes[i], pending_autoinsert_hashes[i + 1] + ) + new_hashes.append(internal_node_hash) + if len(pending_autoinsert_hashes) % 2 != 0: + new_hashes.append(pending_autoinsert_hashes[-1]) + + pending_autoinsert_hashes = new_hashes + + if len(pending_autoinsert_hashes): + subtree_hash = pending_autoinsert_hashes[0] + if latest_local_root is None or latest_local_root.node_hash is None: + await self._insert_root(store_id=store_id, node_hash=subtree_hash, status=Status.COMMITTED) + else: + min_height_leaf = await self.get_leaf_at_minimum_height(latest_local_root.node_hash) + ancestors = await self.get_ancestors_common( + node_hash=min_height_leaf.hash, + store_id=store_id, + root_hash=latest_local_root.node_hash, + generation=latest_local_root.generation, + use_optimized=True, + ) + await self.update_ancestor_hashes_on_insert( + store_id=store_id, + left=min_height_leaf.hash, + right=subtree_hash, + traversal_node_hash=min_height_leaf.hash, + ancestors=ancestors, + status=Status.COMMITTED, + root=latest_local_root, + ) + root = await self.get_tree_root(store_id=store_id) if root.node_hash == old_root.node_hash: if len(changelist) != 0: diff --git a/chia/farmer/farmer.py b/chia/farmer/farmer.py index 21b4cad033d5..5d9a6c7c5ed9 100644 --- a/chia/farmer/farmer.py +++ b/chia/farmer/farmer.py @@ -4,6 +4,7 @@ import contextlib import json import logging +import sys import time import traceback from dataclasses import dataclass @@ -18,7 +19,7 @@ from chia.daemon.keychain_proxy import KeychainProxy, connect_to_keychain_and_validate, wrap_local_keychain from chia.plot_sync.delta import Delta from chia.plot_sync.receiver import Receiver -from chia.pools.pool_config import PoolWalletConfig, add_auth_key, load_pool_config, update_pool_url +from chia.pools.pool_config import PoolWalletConfig, load_pool_config, update_pool_url from chia.protocols import farmer_protocol, harvester_protocol from chia.protocols.pool_protocol import ( AuthenticationPayload, @@ -47,6 +48,7 @@ from chia.util.ints import uint8, uint16, uint32, uint64 from chia.util.keychain import Keychain from chia.util.logging import TimedDuplicateFilter +from chia.util.profiler import profile_task from chia.wallet.derive_keys import ( find_authentication_sk, find_owner_sk, @@ -191,6 +193,12 @@ async def start_task() -> None: return await asyncio.sleep(1) + if self.config.get("enable_profiler", False): + if sys.getprofile() is not None: + self.log.warning("not enabling profiler, getprofile() is already set") + else: + asyncio.create_task(profile_task(self._root_path, "farmer", self.log)) + asyncio.create_task(start_task()) try: yield @@ -523,8 +531,6 @@ async def update_pool_state(self) -> None: self.log.error(f"Could not find authentication sk for {p2_singleton_puzzle_hash}") continue - add_auth_key(self._root_path, pool_config, authentication_sk.get_g1()) - if p2_singleton_puzzle_hash not in self.pool_state: self.pool_state[p2_singleton_puzzle_hash] = { "p2_singleton_puzzle_hash": p2_singleton_puzzle_hash.hex(), diff --git a/chia/full_node/coin_store.py b/chia/full_node/coin_store.py index 07fb8199ced7..489a50a804a7 100644 --- a/chia/full_node/coin_store.py +++ b/chia/full_node/coin_store.py @@ -438,7 +438,9 @@ async def batch_coin_states_by_puzzle_hashes( if len(puzzle_hashes) == 0: return [], None - coin_states: List[CoinState] = [] + # Coin states are keyed by coin id to filter out and prevent duplicates. + coin_states_dict: Dict[bytes32, CoinState] = dict() + coin_states: List[CoinState] async with self.db_wrapper.reader() as conn: puzzle_hashes_db = tuple(puzzle_hashes) @@ -475,7 +477,8 @@ async def batch_coin_states_by_puzzle_hashes( ) for row in await cursor.fetchall(): - coin_states.append(self.row_to_coin_state(row)) + coin_state = self.row_to_coin_state(row) + coin_states_dict[coin_state.coin.name()] = coin_state if include_hinted: cursor = await conn.execute( @@ -496,8 +499,12 @@ async def batch_coin_states_by_puzzle_hashes( ) for row in await cursor.fetchall(): - coin_states.append(self.row_to_coin_state(row)) + coin_state = self.row_to_coin_state(row) + coin_states_dict[coin_state.coin.name()] = coin_state + coin_states = list(coin_states_dict.values()) + + if include_hinted: coin_states.sort(key=lambda cr: max(cr.created_height or uint32(0), cr.spent_height or uint32(0))) while len(coin_states) > max_items + 1: coin_states.pop() diff --git a/chia/full_node/full_node_api.py b/chia/full_node/full_node_api.py index 97bd31ce5467..20533b4964ea 100644 --- a/chia/full_node/full_node_api.py +++ b/chia/full_node/full_node_api.py @@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast import anyio -from chia_rs import AugSchemeMPL, G1Element, G2Element +from chia_rs import AugSchemeMPL, G1Element, G2Element, MerkleSet from chiabip158 import PyBIP158 from chia.consensus.block_creation import create_unfinished_block @@ -66,7 +66,6 @@ from chia.util.hash import std_hash from chia.util.ints import uint8, uint32, uint64, uint128 from chia.util.limited_semaphore import LimitedSemaphoreFullError -from chia.util.merkle_set import MerkleSet if TYPE_CHECKING: from chia.full_node.full_node import FullNode diff --git a/chia/full_node/mempool_manager.py b/chia/full_node/mempool_manager.py index 4c4c9ff23f60..f4385fadd40a 100644 --- a/chia/full_node/mempool_manager.py +++ b/chia/full_node/mempool_manager.py @@ -193,7 +193,7 @@ def __init__( # spends. self.nonzero_fee_minimum_fpc = 5 - BLOCK_SIZE_LIMIT_FACTOR = 0.6 + BLOCK_SIZE_LIMIT_FACTOR = 0.7 self.max_block_clvm_cost = uint64(self.constants.MAX_BLOCK_COST_CLVM * BLOCK_SIZE_LIMIT_FACTOR) self.max_tx_clvm_cost = ( max_tx_clvm_cost if max_tx_clvm_cost is not None else uint64(self.constants.MAX_BLOCK_COST_CLVM // 2) diff --git a/chia/plotting/create_plots.py b/chia/plotting/create_plots.py index ac33b5723153..c6321d792cb5 100644 --- a/chia/plotting/create_plots.py +++ b/chia/plotting/create_plots.py @@ -202,7 +202,7 @@ async def create_plots( # The plot id is based on the harvester, farmer, and pool keys if keys.pool_public_key is not None: plot_id: bytes32 = calculate_plot_id_pk(keys.pool_public_key, plot_public_key) - plot_memo: bytes32 = stream_plot_info_pk(keys.pool_public_key, keys.farmer_public_key, sk) + plot_memo: bytes = stream_plot_info_pk(keys.pool_public_key, keys.farmer_public_key, sk) else: assert keys.pool_contract_puzzle_hash is not None plot_id = calculate_plot_id_ph(keys.pool_contract_puzzle_hash, plot_public_key) @@ -210,11 +210,21 @@ async def create_plots( if args.plotid is not None: log.info(f"Debug plot ID: {args.plotid}") - plot_id = bytes32(bytes.fromhex(args.plotid)) + # Check if args.memo is of type bytes and convert it to a string if so + if isinstance(args.plotid, bytes): + plot_str = args.plotid.hex() # Convert bytes to hex string + else: + plot_str = args.plotid + plot_id = bytes32.fromhex(plot_str) if args.memo is not None: log.info(f"Debug memo: {args.memo}") - plot_memo = bytes32.fromhex(args.memo) + # Check if args.memo is of type bytes and convert it to a string if so + if isinstance(args.memo, bytes): + memo_str = args.memo.hex() # Convert bytes to hex string + else: + memo_str = args.memo + plot_memo = bytes.fromhex(memo_str) dt_string = datetime.now().strftime("%Y-%m-%d-%H-%M") diff --git a/chia/pools/pool_config.py b/chia/pools/pool_config.py index 84a9e2e83d0f..cc106546615d 100644 --- a/chia/pools/pool_config.py +++ b/chia/pools/pool_config.py @@ -62,26 +62,6 @@ def load_pool_config(root_path: Path) -> List[PoolWalletConfig]: return ret_list -# TODO: remove this a few versions after 1.3, since authentication_public_key is deprecated. This is here to support -# downgrading to versions older than 1.3. -def add_auth_key(root_path: Path, pool_wallet_config: PoolWalletConfig, auth_key: G1Element) -> None: - def update_auth_pub_key_for_entry(config_entry: Dict[str, Any]) -> bool: - auth_key_hex = bytes(auth_key).hex() - if config_entry.get("authentication_public_key", "") != auth_key_hex: - config_entry["authentication_public_key"] = auth_key_hex - - return True - - return False - - update_pool_config_entry( - root_path=root_path, - pool_wallet_config=pool_wallet_config, - update_closure=update_auth_pub_key_for_entry, - update_log_message=f"Updating pool config for auth key: {auth_key}", - ) - - def update_pool_url(root_path: Path, pool_wallet_config: PoolWalletConfig, pool_url: str) -> None: def update_pool_url_for_entry(config_entry: Dict[str, Any]) -> bool: if config_entry.get("pool_url", "") != pool_url: @@ -107,21 +87,20 @@ def update_pool_config_entry( ) -> None: with lock_and_load_config(root_path, "config.yaml") as config: pool_list = config["pool"].get("pool_list", []) + if pool_list is None: + return updated = False - if pool_list is not None: - 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 update_closure(pool_config_dict): - updated = True - except Exception as e: - log.error(f"Exception updating config: {pool_config_dict} {e}") - if updated: - log.info(update_log_message) - config["pool"]["pool_list"] = pool_list - save_config(root_path, "config.yaml", config) + 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 update_closure(pool_config_dict): + updated = True + except Exception as e: + log.error(f"Exception updating config: {pool_config_dict} {e}") + if updated: + log.info(update_log_message) + config["pool"]["pool_list"] = pool_list + save_config(root_path, "config.yaml", config) async def update_pool_config(root_path: Path, pool_config_list: List[PoolWalletConfig]) -> None: diff --git a/chia/pools/pool_puzzles.py b/chia/pools/pool_puzzles.py index 3d25219130dd..9d963c1753c2 100644 --- a/chia/pools/pool_puzzles.py +++ b/chia/pools/pool_puzzles.py @@ -4,6 +4,7 @@ from typing import List, Optional, Tuple from chia_rs import G1Element +from clvm.casts import int_to_bytes from chia.clvm.singleton import SINGLETON_LAUNCHER from chia.consensus.block_rewards import calculate_pool_reward @@ -17,6 +18,7 @@ from chia.util.ints import uint32, uint64 from chia.wallet.puzzles.load_clvm import load_clvm_maybe_recompile from chia.wallet.puzzles.singleton_top_layer import puzzle_for_singleton +from chia.wallet.util.curry_and_treehash import calculate_hash_of_quoted_mod_hash, curry_and_treehash, shatree_atom log = logging.getLogger(__name__) # "Full" is the outer singleton, with the inner puzzle filled in @@ -31,8 +33,10 @@ POOL_MEMBER_HASH = POOL_MEMBER_MOD.get_tree_hash() POOL_WAITING_ROOM_HASH = POOL_WAITING_ROOM_MOD.get_tree_hash() P2_SINGLETON_HASH = P2_SINGLETON_MOD.get_tree_hash() +P2_SINGLETON_HASH_QUOTED = calculate_hash_of_quoted_mod_hash(P2_SINGLETON_HASH) POOL_OUTER_MOD_HASH = POOL_OUTER_MOD.get_tree_hash() SINGLETON_LAUNCHER_HASH = SINGLETON_LAUNCHER.get_tree_hash() +SINGLETON_LAUNCHER_HASH_TREE_HASH = shatree_atom(SINGLETON_LAUNCHER_HASH) SINGLETON_MOD_HASH = POOL_OUTER_MOD_HASH SINGLETON_MOD_HASH_HASH = Program.to(SINGLETON_MOD_HASH).get_tree_hash() @@ -90,10 +94,25 @@ def create_p2_singleton_puzzle( ) +def create_p2_singleton_puzzle_hash( + singleton_mod_hash: bytes, + launcher_id: bytes32, + seconds_delay: uint64, + delayed_puzzle_hash: bytes32, +) -> bytes32: + # curry params are SINGLETON_MOD_HASH LAUNCHER_ID LAUNCHER_PUZZLE_HASH SECONDS_DELAY DELAYED_PUZZLE_HASH + return curry_and_treehash( + P2_SINGLETON_HASH_QUOTED, + shatree_atom(singleton_mod_hash), + shatree_atom(launcher_id), + SINGLETON_LAUNCHER_HASH_TREE_HASH, + shatree_atom(int_to_bytes(seconds_delay)), + shatree_atom(delayed_puzzle_hash), + ) + + def launcher_id_to_p2_puzzle_hash(launcher_id: bytes32, seconds_delay: uint64, delayed_puzzle_hash: bytes32) -> bytes32: - return create_p2_singleton_puzzle( - SINGLETON_MOD_HASH, launcher_id, seconds_delay, delayed_puzzle_hash - ).get_tree_hash() + return create_p2_singleton_puzzle_hash(SINGLETON_MOD_HASH, launcher_id, seconds_delay, delayed_puzzle_hash) def get_delayed_puz_info_from_launcher_spend(coinsol: CoinSpend) -> Tuple[uint64, bytes32]: diff --git a/chia/pools/pool_wallet.py b/chia/pools/pool_wallet.py index 9d803e77fbf4..d39317fe3853 100644 --- a/chia/pools/pool_wallet.py +++ b/chia/pools/pool_wallet.py @@ -46,8 +46,6 @@ from chia.util.ints import uint32, uint64, uint128 from chia.wallet.conditions import AssertCoinAnnouncement, Condition, ConditionValidTimes, parse_timelock_info from chia.wallet.derive_keys import find_owner_sk -from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_synthetic_public_key -from chia.wallet.sign_coin_spends import sign_coin_spends 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 @@ -219,20 +217,12 @@ async def get_current_state(self) -> PoolWalletInfo: curr_spend_i -= 1 assert pool_state is not None - current_inner = pool_state_to_inner_puzzle( - pool_state, - launcher_coin.name(), - self.wallet_state_manager.constants.GENESIS_CHALLENGE, - delayed_seconds, - delayed_puzhash, - ) return PoolWalletInfo( pool_state, self.target_state, launcher_coin, launcher_id, p2_singleton_puzzle_hash, - current_inner, tip_singleton_coin.name(), last_singleton_spend_height, ) @@ -471,7 +461,8 @@ async def create_new_pool_wallet_transaction( async def _get_owner_key_cache(self) -> Tuple[PrivateKey, uint32]: if self._owner_sk_and_index is None: self._owner_sk_and_index = find_owner_sk( - [self.wallet_state_manager.private_key], (await self.get_current_state()).current.owner_pubkey + [self.wallet_state_manager.get_master_private_key()], + (await self.get_current_state()).current.owner_pubkey, ) assert self._owner_sk_and_index is not None return self._owner_sk_and_index @@ -479,36 +470,6 @@ async def _get_owner_key_cache(self) -> Tuple[PrivateKey, uint32]: async def get_pool_wallet_index(self) -> uint32: return (await self._get_owner_key_cache())[1] - async def sign(self, coin_spend: CoinSpend) -> SpendBundle: - async def pk_to_sk(pk: G1Element) -> PrivateKey: - s = find_owner_sk([self.wallet_state_manager.private_key], pk) - if s is None: # pragma: no cover - # No pool wallet transactions _should_ hit this, but it can't hurt to have a backstop - private_key = await self.wallet_state_manager.get_private_key_for_pubkey(pk) - if private_key is None: - raise ValueError(f"No private key for pubkey: {pk}") - return private_key - else: - # Note that pool_wallet_index may be from another wallet than self.wallet_id - owner_sk, pool_wallet_index = s - if owner_sk is None: # pragma: no cover - # TODO: this code is dead, per hinting at least - # No pool wallet transactions _should_ hit this, but it can't hurt to have a backstop - private_key = await self.wallet_state_manager.get_private_key_for_pubkey(pk) - if private_key is None: - raise ValueError(f"No private key for pubkey: {pk}") - return private_key - return owner_sk - - return await sign_coin_spends( - [coin_spend], - pk_to_sk, - self.wallet_state_manager.get_synthetic_private_key_for_puzzle_hash, - self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA, - self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM, - [puzzle_hash_for_synthetic_public_key], - ) - async def generate_fee_transaction( self, fee: uint64, @@ -594,21 +555,14 @@ async def generate_travel_transactions( else: raise RuntimeError("Invalid state") - pk_bytes = pubkey_as_program.as_atom() - assert pk_bytes is not None - assert len(pk_bytes) == 48 - owner_pubkey = G1Element.from_bytes(pk_bytes) - assert owner_pubkey == pool_wallet_info.current.owner_pubkey - - signed_spend_bundle = await self.sign(outgoing_coin_spend) - assert signed_spend_bundle.removals()[0].puzzle_hash == singleton.puzzle_hash - assert signed_spend_bundle.removals()[0].name() == singleton.name() - assert signed_spend_bundle is not None + 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 - signed_spend_bundle = SpendBundle.aggregate([signed_spend_bundle, fee_tx.spend_bundle]) + unsigned_spend_bundle = SpendBundle.aggregate([unsigned_spend_bundle, fee_tx.spend_bundle]) fee_tx = dataclasses.replace(fee_tx, spend_bundle=None) tx_record = TransactionRecord( @@ -619,15 +573,15 @@ async def generate_travel_transactions( fee_amount=fee, confirmed=False, sent=uint32(0), - spend_bundle=signed_spend_bundle, - additions=signed_spend_bundle.additions(), - removals=signed_spend_bundle.removals(), + 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=signed_spend_bundle.name(), + name=unsigned_spend_bundle.name(), valid_times=ConditionValidTimes(), ) diff --git a/chia/pools/pool_wallet_info.py b/chia/pools/pool_wallet_info.py index 36faa668d11b..cf42aa8dacde 100644 --- a/chia/pools/pool_wallet_info.py +++ b/chia/pools/pool_wallet_info.py @@ -8,7 +8,6 @@ from chia.protocols.pool_protocol import POOL_PROTOCOL_VERSION from chia.types.blockchain_format.coin import Coin -from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 from chia.util.byte_types import hexstr_to_bytes from chia.util.ints import uint8, uint32 @@ -116,6 +115,5 @@ class PoolWalletInfo(Streamable): launcher_coin: Coin launcher_id: bytes32 p2_singleton_puzzle_hash: bytes32 - current_inner: Program # Inner puzzle in current singleton, not revealed yet tip_singleton_coin_id: bytes32 singleton_block_height: uint32 # Block height that current PoolState is from diff --git a/chia/protocols/shared_protocol.py b/chia/protocols/shared_protocol.py index 75719152f0c1..133f0b1c14fe 100644 --- a/chia/protocols/shared_protocol.py +++ b/chia/protocols/shared_protocol.py @@ -31,7 +31,7 @@ class Capability(IntEnum): # introduces RequestBlockHeaders, which is a faster API for fetching header blocks # !! the old API is *RequestHeaderBlock* !! BLOCK_HEADERS = 2 - # Specifies support for v1 and v2 versions of rate limits. Peers will ues the lowest shared capability: + # Specifies support for v1 and v2 versions of rate limits. Peers will use the lowest shared capability: # if peer A support v3 and peer B supports v2, they should send: # (BASE, RATE_LIMITS_V2, RATE_LIMITS_V3), and (BASE, RATE_LIMITS_V2) respectively. They will use the V2 limits. RATE_LIMITS_V2 = 3 diff --git a/chia/rpc/util.py b/chia/rpc/util.py index ddbee66ca436..2d6d2d2a4974 100644 --- a/chia/rpc/util.py +++ b/chia/rpc/util.py @@ -1,17 +1,27 @@ from __future__ import annotations +import dataclasses import logging import traceback from typing import TYPE_CHECKING, Any, Awaitable, Callable, Dict, List, Optional, Tuple, get_type_hints import aiohttp +from chia_rs import AugSchemeMPL from chia.types.blockchain_format.coin import Coin +from chia.types.coin_spend import CoinSpend from chia.types.spend_bundle import SpendBundle from chia.util.json_util import obj_to_response from chia.util.streamable import Streamable from chia.wallet.conditions import Condition, ConditionValidTimes, conditions_from_json_dicts, parse_timelock_info +from chia.wallet.trade_record import TradeRecord +from chia.wallet.trading.offer import Offer from chia.wallet.transaction_record import TransactionRecord +from chia.wallet.util.clvm_streamable import ( + byte_serialize_clvm_streamable, + json_deserialize_with_clvm_streamable, + json_serialize_with_clvm_streamable, +) from chia.wallet.util.transaction_type import TransactionType from chia.wallet.util.tx_config import TXConfig, TXConfigLoader @@ -33,11 +43,21 @@ def marshal(func: MarshallableRpcEndpoint) -> RpcEndpoint: async def rpc_endpoint(self, request: Dict[str, Any], *args: object, **kwargs: object) -> Dict[str, Any]: response_obj: Streamable = await func( self, - request_class.from_json_dict(request), + ( + request_class.from_json_dict(request) + if not request.get("CHIP-0029", False) + else json_deserialize_with_clvm_streamable(request, request_hint) + ), *args, **kwargs, ) - return response_obj.to_json_dict() + if not request.get("CHIP-0029", False): + return response_obj.to_json_dict() + else: + response_dict = json_serialize_with_clvm_streamable(response_obj) + if isinstance(response_dict, str): # pragma: no cover + raise ValueError("Internal Error. Marshalled endpoint was made with clvm_streamable.") + return response_dict return rpc_endpoint @@ -131,16 +151,33 @@ async def rpc_endpoint(self, request: Dict[str, Any], *args, **kwargs) -> Dict[s # unfortunately, this API isn't solely a tx endpoint return response - new_txs: List[TransactionRecord] = [ + 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) + + if not request.get("CHIP-0029", False): + response["unsigned_transactions"] = [tx.to_json_dict() for tx in unsigned_txs] + else: + response["unsigned_transactions"] = [byte_serialize_clvm_streamable(tx).hex() 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 + ) + response["signing_responses"] = [byte_serialize_clvm_streamable(r).hex() for r 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 + new_txs, merge_spends=merge_spends, sign=False ) - response["transactions"] = [tx.to_json_dict_convenience(self.service.config) for tx in new_txs] + response["transactions"] = [ + TransactionRecord.to_json_dict_convenience(tx, self.service.config) for tx in new_txs + ] # 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 @@ -182,8 +219,55 @@ async def rpc_endpoint(self, request: Dict[str, Any], *args, **kwargs) -> Dict[s response["tx"] = response["transactions"][0] else: response["tx"] = new_txs[0].to_json_dict() + if "txs" in response: + response["txs"] = [tx.to_json_dict() for tx in new_txs] if "tx_id" in response: response["tx_id"] = new_txs[0].name + if "trade_record" in response: + old_offer: Offer = Offer.from_bech32(response["offer"]) + signed_coin_spends: List[CoinSpend] = [ + coin_spend + for tx in new_txs + if tx.spend_bundle is not None + for coin_spend in tx.spend_bundle.coin_spends + ] + involved_coins: List[Coin] = [spend.coin for spend in signed_coin_spends] + signed_coin_spends.extend( + [spend for spend in old_offer._bundle.coin_spends if spend.coin not in involved_coins] + ) + new_offer_bundle: SpendBundle = SpendBundle( + signed_coin_spends, + AugSchemeMPL.aggregate( + [tx.spend_bundle.aggregated_signature for tx in new_txs if tx.spend_bundle is not None] + ), + ) + new_offer: Offer = Offer(old_offer.requested_payments, new_offer_bundle, old_offer.driver_dict) + response["offer"] = new_offer.to_bech32() + old_trade_record: TradeRecord = TradeRecord.from_json_dict_convenience( + response["trade_record"], bytes(old_offer).hex() + ) + new_trade: TradeRecord = dataclasses.replace( + old_trade_record, + offer=bytes(new_offer), + trade_id=new_offer.name(), + ) + response["trade_record"] = new_trade.to_json_dict_convenience() + if ( + await self.service.wallet_state_manager.trade_manager.trade_store.get_trade_record( + old_trade_record.trade_id + ) + is not None + ): + await self.service.wallet_state_manager.trade_manager.trade_store.delete_trade_record( + old_trade_record.trade_id + ) + await self.service.wallet_state_manager.trade_manager.save_trade(new_trade, new_offer) + for tx in await self.service.wallet_state_manager.tx_store.get_transactions_by_trade_id( + old_trade_record.trade_id + ): + await self.service.wallet_state_manager.tx_store.add_transaction_record( + dataclasses.replace(tx, trade_id=new_trade.trade_id) + ) return response diff --git a/chia/rpc/wallet_request_types.py b/chia/rpc/wallet_request_types.py index bb84d5e9a14a..62fc3afaa0fa 100644 --- a/chia/rpc/wallet_request_types.py +++ b/chia/rpc/wallet_request_types.py @@ -7,6 +7,7 @@ from chia.util.ints import uint32 from chia.util.streamable import Streamable, streamable from chia.wallet.notification_store import Notification +from chia.wallet.signer_protocol import SignedTransaction, SigningInstructions, SigningResponse, Spend @streamable @@ -21,3 +22,40 @@ class GetNotifications(Streamable): @dataclass(frozen=True) class GetNotificationsResponse(Streamable): notifications: List[Notification] + + +@streamable +@dataclass(frozen=True) +class GatherSigningInfo(Streamable): + spends: List[Spend] + + +@streamable +@dataclass(frozen=True) +class GatherSigningInfoResponse(Streamable): + signing_instructions: SigningInstructions + + +@streamable +@dataclass(frozen=True) +class ApplySignatures(Streamable): + spends: List[Spend] + signing_responses: List[SigningResponse] + + +@streamable +@dataclass(frozen=True) +class ApplySignaturesResponse(Streamable): + signed_transactions: List[SignedTransaction] + + +@streamable +@dataclass(frozen=True) +class SubmitTransactions(Streamable): + signed_transactions: List[SignedTransaction] + + +@streamable +@dataclass(frozen=True) +class SubmitTransactionsResponse(Streamable): + mempool_ids: List[bytes32] diff --git a/chia/rpc/wallet_rpc_api.py b/chia/rpc/wallet_rpc_api.py index 7de3267afb1f..b2b08daeeb89 100644 --- a/chia/rpc/wallet_rpc_api.py +++ b/chia/rpc/wallet_rpc_api.py @@ -19,7 +19,16 @@ from chia.protocols.wallet_protocol import CoinState from chia.rpc.rpc_server import Endpoint, EndpointResult, default_get_connections from chia.rpc.util import marshal, tx_endpoint -from chia.rpc.wallet_request_types import GetNotifications, GetNotificationsResponse +from chia.rpc.wallet_request_types import ( + ApplySignatures, + ApplySignaturesResponse, + GatherSigningInfo, + GatherSigningInfoResponse, + GetNotifications, + GetNotificationsResponse, + SubmitTransactions, + SubmitTransactionsResponse, +) from chia.server.outbound_message import NodeType from chia.server.ws_connection import WSChiaConnection from chia.types.blockchain_format.coin import Coin, coin_as_list @@ -88,6 +97,7 @@ from chia.wallet.puzzles import p2_delegated_conditions from chia.wallet.puzzles.clawback.metadata import AutoClaimSettings, ClawbackMetadata from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import puzzle_hash_for_synthetic_public_key +from chia.wallet.signer_protocol import SigningResponse from chia.wallet.singleton import ( SINGLETON_LAUNCHER_PUZZLE_HASH, create_singleton_puzzle, @@ -281,6 +291,10 @@ def get_routes(self) -> Dict[str, Endpoint]: "/vc_revoke": self.vc_revoke, # CR-CATs "/crcat_approve_pending": self.crcat_approve_pending, + # Signer Protocol + "/gather_signing_info": self.gather_signing_info, + "/apply_signatures": self.apply_signatures, + "/submit_transactions": self.submit_transactions, } def get_connections(self, request_node_type: Optional[NodeType]) -> List[Dict[str, Any]]: @@ -426,7 +440,7 @@ async def add_key(self, request: Dict[str, Any]) -> EndpointResult: # Adding a key from 24 word mnemonic mnemonic = request["mnemonic"] try: - sk = await self.service.keychain_proxy.add_private_key(" ".join(mnemonic)) + sk = await self.service.keychain_proxy.add_key(" ".join(mnemonic)) except KeyError as e: return { "success": False, @@ -611,7 +625,7 @@ async def push_transactions(self, request: Dict[str, Any]) -> EndpointResult: txs.append(tx) async with self.service.wallet_state_manager.lock: - await self.service.wallet_state_manager.add_pending_transactions(txs) + await self.service.wallet_state_manager.add_pending_transactions(txs, sign=request.get("sign", False)) return {} @@ -897,7 +911,7 @@ async def create_new_wallet( raise ValueError(f"Too many pool wallets ({max_pwi}), cannot create any more on this key.") owner_sk: PrivateKey = master_sk_to_singleton_owner_sk( - self.service.wallet_state_manager.private_key, uint32(max_pwi + 1) + self.service.wallet_state_manager.get_master_private_key(), uint32(max_pwi + 1) ) owner_pk: G1Element = owner_sk.get_g1() @@ -2007,7 +2021,9 @@ async def take_offer( 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)], } async def get_offer(self, request: Dict[str, Any]) -> EndpointResult: @@ -3696,6 +3712,10 @@ async def nft_mint_bulk( 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, @@ -4530,3 +4550,28 @@ class CRCATApprovePending(Streamable): return { "transactions": [tx.to_json_dict_convenience(self.service.config) for tx in txs], } + + @marshal + async def gather_signing_info( + self, + request: GatherSigningInfo, + ) -> GatherSigningInfoResponse: + return GatherSigningInfoResponse(await self.service.wallet_state_manager.gather_signing_info(request.spends)) + + @marshal + async def apply_signatures( + self, + request: ApplySignatures, + ) -> ApplySignaturesResponse: + return ApplySignaturesResponse( + [await self.service.wallet_state_manager.apply_signatures(request.spends, request.signing_responses)] + ) + + @marshal + async def submit_transactions( + self, + request: SubmitTransactions, + ) -> SubmitTransactionsResponse: + return SubmitTransactionsResponse( + await self.service.wallet_state_manager.submit_transactions(request.signed_transactions) + ) diff --git a/chia/rpc/wallet_rpc_client.py b/chia/rpc/wallet_rpc_client.py index f2793e76b853..e6bff3d79569 100644 --- a/chia/rpc/wallet_rpc_client.py +++ b/chia/rpc/wallet_rpc_client.py @@ -6,7 +6,16 @@ from chia.data_layer.data_layer_wallet import Mirror, SingletonRecord from chia.pools.pool_wallet_info import PoolWalletInfo from chia.rpc.rpc_client import RpcClient -from chia.rpc.wallet_request_types import GetNotifications, GetNotificationsResponse +from chia.rpc.wallet_request_types import ( + ApplySignatures, + ApplySignaturesResponse, + GatherSigningInfo, + GatherSigningInfoResponse, + GetNotifications, + GetNotificationsResponse, + SubmitTransactions, + SubmitTransactionsResponse, +) from chia.types.blockchain_format.coin import Coin from chia.types.blockchain_format.program import Program from chia.types.blockchain_format.sized_bytes import bytes32 @@ -19,6 +28,7 @@ from chia.wallet.trading.offer import Offer from chia.wallet.transaction_record import TransactionRecord from chia.wallet.transaction_sorting import SortKey +from chia.wallet.util.clvm_streamable import json_deserialize_with_clvm_streamable from chia.wallet.util.query_filter import TransactionTypeFilter from chia.wallet.util.tx_config import CoinSelectionConfig, TXConfig from chia.wallet.util.wallet_types import WalletType @@ -1609,3 +1619,39 @@ async def crcat_approve_pending( }, ) return [TransactionRecord.from_json_dict_convenience(tx) for tx in response["transactions"]] + + async def gather_signing_info( + self, + args: GatherSigningInfo, + ) -> GatherSigningInfoResponse: + return json_deserialize_with_clvm_streamable( + await self.fetch( + "gather_signing_info", + args.to_json_dict(), + ), + GatherSigningInfoResponse, + ) + + async def apply_signatures( + self, + args: ApplySignatures, + ) -> ApplySignaturesResponse: + return json_deserialize_with_clvm_streamable( + await self.fetch( + "apply_signatures", + args.to_json_dict(), + ), + ApplySignaturesResponse, + ) + + async def submit_transactions( + self, + args: SubmitTransactions, + ) -> SubmitTransactionsResponse: + return json_deserialize_with_clvm_streamable( + await self.fetch( + "submit_transactions", + args.to_json_dict(), + ), + SubmitTransactionsResponse, + ) diff --git a/chia/simulator/block_tools.py b/chia/simulator/block_tools.py index 646e4908d1c0..659762965ee6 100644 --- a/chia/simulator/block_tools.py +++ b/chia/simulator/block_tools.py @@ -326,10 +326,8 @@ async def setup_keys(self, fingerprint: Optional[int] = None, reward_ph: Optiona await keychain_proxy.delete_all_keys() self.farmer_master_sk_entropy = std_hash(b"block_tools farmer key") # both entropies are only used here self.pool_master_sk_entropy = std_hash(b"block_tools pool key") - self.farmer_master_sk = await keychain_proxy.add_private_key( - bytes_to_mnemonic(self.farmer_master_sk_entropy) - ) - self.pool_master_sk = await keychain_proxy.add_private_key( + self.farmer_master_sk = await keychain_proxy.add_key(bytes_to_mnemonic(self.farmer_master_sk_entropy)) + self.pool_master_sk = await keychain_proxy.add_key( bytes_to_mnemonic(self.pool_master_sk_entropy), ) else: diff --git a/chia/simulator/full_node_simulator.py b/chia/simulator/full_node_simulator.py index dd0c851967eb..29560e4d70d0 100644 --- a/chia/simulator/full_node_simulator.py +++ b/chia/simulator/full_node_simulator.py @@ -475,6 +475,32 @@ async def wait_transaction_records_entered_mempool( await asyncio.sleep(backoff) + async def wait_bundle_ids_in_mempool( + self, + bundle_ids: Collection[bytes32], + timeout: Union[None, float] = 5, + ) -> None: + """Wait until the ids of specific spend bundles have entered the mempool. + + Arguments: + records: The bundle ids to wait for. + """ + with anyio.fail_after(delay=adjusted_timeout(timeout)): + ids_to_check: Set[bytes32] = set(bundle_ids) + + for backoff in backoff_times(): + found = set() + for spend_bundle_name in ids_to_check: + tx = self.full_node.mempool_manager.get_spendbundle(spend_bundle_name) + if tx is not None: + found.add(spend_bundle_name) + ids_to_check = ids_to_check.difference(found) + + if len(ids_to_check) == 0: + return + + await asyncio.sleep(backoff) + async def wait_transaction_records_marked_as_in_mempool( self, record_ids: Collection[bytes32], @@ -664,7 +690,7 @@ async def create_coins_with_amounts( tx_config=DEFAULT_TX_CONFIG, primaries=outputs_group[1:], ) - await wallet.wallet_state_manager.add_pending_transactions([tx]) + [tx] = await wallet.wallet_state_manager.add_pending_transactions([tx]) transaction_records.append(tx) else: break diff --git a/chia/simulator/setup_services.py b/chia/simulator/setup_services.py index 0c218495b6c5..5407f64c4118 100644 --- a/chia/simulator/setup_services.py +++ b/chia/simulator/setup_services.py @@ -253,7 +253,7 @@ async def setup_wallet_node( entropy = bytes32.secret() if key_seed is None: key_seed = entropy - keychain.add_private_key(bytes_to_mnemonic(key_seed)) + keychain.add_key(bytes_to_mnemonic(key_seed)) first_pk = keychain.get_first_public_key() assert first_pk is not None db_path_key_suffix = str(first_pk.get_fingerprint()) diff --git a/chia/simulator/simulator_test_tools.py b/chia/simulator/simulator_test_tools.py index b837e20e938c..000c191d7229 100644 --- a/chia/simulator/simulator_test_tools.py +++ b/chia/simulator/simulator_test_tools.py @@ -42,7 +42,7 @@ def mnemonic_fingerprint(keychain: Keychain) -> Tuple[str, int]: ) # add key to keychain try: - sk = keychain.add_private_key(mnemonic) + sk = keychain.add_key(mnemonic) except KeychainFingerprintExists: pass fingerprint = sk.get_g1().get_fingerprint() diff --git a/chia/util/initial-config.yaml b/chia/util/initial-config.yaml index a99fc6443685..f90b530c6061 100644 --- a/chia/util/initial-config.yaml +++ b/chia/util/initial-config.yaml @@ -275,6 +275,11 @@ farmer: start_rpc_server: True rpc_port: 8559 + # when enabled, the farmer will print a pstats profile to the + # root_dir/profile-farmer directory every second. + # analyze with python -m chia.util.profiler + enable_profiler: False + # To send a share to a pool, a proof of space must have required_iters less than this number pool_share_threshold: 1000 logging: *logging @@ -448,8 +453,9 @@ full_node: # from at least 3 peers, or until we've waitied this many seconds max_sync_wait: 30 - # when enabled, the full node will print a pstats profile to the root_dir/profile every second - # analyze with chia/utils/profiler.py + # when enabled, the full node will print a pstats profile to the + # root_dir/profile-node directory every second. + # analyze with python -m chia.util.profiler enable_profiler: False # when enabled, each time a block is validated, the python profiler is @@ -541,6 +547,9 @@ introducer: wallet: rpc_port: 9256 + # when enabled, the wallet will print a pstats profile to the + # root_dir/profile-wallet directory every second. + # analyze with python -m chia.util.profiler enable_profiler: False enable_memory_profiler: False @@ -656,6 +665,8 @@ wallet: min_amount: 0 batch_size: 50 + auto_sign_txs: True + data_layer: # TODO: consider name # TODO: organize consistently with other sections @@ -687,6 +698,9 @@ data_layer: # separate log file (under logging/data_sql.log). log_sqlite_cmds: False + # Speeds up autoinserts. Disable to perform inserts one by one instead of in a batch. + enable_batch_autoinsert: True + logging: *logging ssl: diff --git a/chia/util/keychain.py b/chia/util/keychain.py index 7e76635c896f..c3d1f36d67c6 100644 --- a/chia/util/keychain.py +++ b/chia/util/keychain.py @@ -5,7 +5,7 @@ from dataclasses import dataclass from hashlib import pbkdf2_hmac from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Dict, List, Literal, Optional, Tuple, Union, overload import pkg_resources from bitstring import BitArray # pyright: reportMissingImports=false @@ -13,6 +13,7 @@ from typing_extensions import final from chia.types.blockchain_format.sized_bytes import bytes32 +from chia.util.byte_types import hexstr_to_bytes from chia.util.errors import ( KeychainException, KeychainFingerprintExists, @@ -87,6 +88,11 @@ def bytes_to_mnemonic(mnemonic_bytes: bytes) -> str: return " ".join(mnemonics) +def check_mnemonic_validity(mnemonic_str: str) -> bool: + mnemonic: List[str] = mnemonic_str.split(" ") + return len(mnemonic) in [12, 15, 18, 21, 24] + + def mnemonic_from_short_words(mnemonic_str: str) -> str: """ Since the first 4 letters of each word is unique (or the full word, if less than 4 characters), and its common @@ -307,13 +313,16 @@ def _get_key_data(self, index: int, include_secrets: bool = True) -> KeyData: public_key = G1Element.from_bytes(str_bytes[: G1Element.SIZE]) fingerprint = public_key.get_fingerprint() - entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32] + if len(str_bytes) == G1Element.SIZE + 32: + entropy = str_bytes[G1Element.SIZE : G1Element.SIZE + 32] + else: + entropy = None return KeyData( fingerprint=uint32(fingerprint), public_key=public_key, label=self.keyring_wrapper.get_label(fingerprint), - secrets=KeyDataSecrets.from_entropy(entropy) if include_secrets else None, + secrets=KeyDataSecrets.from_entropy(entropy) if include_secrets and entropy is not None else None, ) def _get_free_private_key_index(self) -> int: @@ -328,17 +337,45 @@ def _get_free_private_key_index(self) -> int: except KeychainUserNotFound: return index - def add_private_key(self, mnemonic: str, label: Optional[str] = None) -> PrivateKey: + @overload + def add_key(self, mnemonic_or_pk: str) -> PrivateKey: ... + + @overload + def add_key(self, mnemonic_or_pk: str, label: Optional[str]) -> PrivateKey: ... + + @overload + def add_key(self, mnemonic_or_pk: str, label: Optional[str], private: Literal[True]) -> PrivateKey: ... + + @overload + def add_key(self, mnemonic_or_pk: str, label: Optional[str], private: Literal[False]) -> G1Element: ... + + @overload + def add_key(self, mnemonic_or_pk: str, label: Optional[str], private: bool) -> Union[PrivateKey, G1Element]: ... + + def add_key( + self, mnemonic_or_pk: str, label: Optional[str] = None, private: bool = True + ) -> Union[PrivateKey, G1Element]: """ - Adds a private key to the keychain, with the given entropy and passphrase. The - keychain itself will store the public key, and the entropy bytes, + Adds a key to the keychain. The keychain itself will store the public key, and the entropy bytes (if given), but not the passphrase. """ - seed = mnemonic_to_seed(mnemonic) - entropy = bytes_from_mnemonic(mnemonic) - index = self._get_free_private_key_index() - key = AugSchemeMPL.key_gen(seed) - fingerprint = key.get_g1().get_fingerprint() + key: Union[PrivateKey, G1Element] + if private: + seed = mnemonic_to_seed(mnemonic_or_pk) + entropy = bytes_from_mnemonic(mnemonic_or_pk) + index = self._get_free_private_key_index() + key = AugSchemeMPL.key_gen(seed) + assert isinstance(key, PrivateKey) + pk = key.get_g1() + key_data = bytes(pk).hex() + entropy.hex() + fingerprint = pk.get_fingerprint() + else: + index = self._get_free_private_key_index() + pk_bytes = hexstr_to_bytes(mnemonic_or_pk) + key = G1Element.from_bytes(pk_bytes) + assert isinstance(key, G1Element) + key_data = pk_bytes.hex() + fingerprint = key.get_fingerprint() if fingerprint in [pk.get_fingerprint() for pk in self.get_all_public_keys()]: # Prevents duplicate add @@ -353,7 +390,7 @@ def add_private_key(self, mnemonic: str, label: Optional[str] = None) -> Private self.keyring_wrapper.set_passphrase( self.service, get_private_key_user(self.user, index), - bytes(key.get_g1()).hex() + entropy.hex(), + key_data, ) except Exception: if label is not None: @@ -410,7 +447,7 @@ def get_all_private_keys(self) -> List[Tuple[PrivateKey, bytes]]: try: key_data = self._get_key_data(index) all_keys.append((key_data.private_key, key_data.entropy)) - except KeychainUserNotFound: + except (KeychainUserNotFound, KeychainSecretsMissing): pass return all_keys @@ -444,7 +481,14 @@ def get_all_public_keys(self) -> List[G1Element]: """ Returns all public keys. """ - return [key_data[0].get_g1() for key_data in self.get_all_private_keys()] + all_keys: List[G1Element] = [] + for index in range(MAX_KEYS + 1): + try: + key_data = self._get_key_data(index) + all_keys.append(key_data.public_key) + except KeychainUserNotFound: + pass + return all_keys def get_first_public_key(self) -> Optional[G1Element]: """ diff --git a/chia/util/merkle_set.py b/chia/util/merkle_set.py deleted file mode 100644 index 77a47b82cd43..000000000000 --- a/chia/util/merkle_set.py +++ /dev/null @@ -1,371 +0,0 @@ -from __future__ import annotations - -from abc import ABCMeta, abstractmethod -from hashlib import sha256 -from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Tuple - -from chia.types.blockchain_format.sized_bytes import bytes32 - -if TYPE_CHECKING: - from hashlib import _Hash - -""" -A simple, confidence-inspiring Merkle Set standard - -Advantages of this standard: -Low CPU requirements -Small proofs of inclusion/exclusion -Reasonably simple implementation - -The main tricks in this standard are: - -Skips repeated hashing of exactly two things even when they share prefix bits - - -Proofs support proving including/exclusion for a large number of values in -a single string. They're a serialization of a subset of the tree. - -Proof format: - -multiproof: subtree -subtree: middle or terminal or truncated or empty -middle: MIDDLE 1 subtree subtree -terminal: TERMINAL 1 hash 32 -# If the sibling is empty truncated implies more than two children. -truncated: TRUNCATED 1 hash 32 -empty: EMPTY 1 -EMPTY: \x00 -TERMINAL: \x01 -MIDDLE: \x02 -TRUNCATED: \x03 -""" - -EMPTY = bytes([0]) -TERMINAL = bytes([1]) -MIDDLE = bytes([2]) -TRUNCATED = bytes([3]) - -BLANK = bytes32([0] * 32) - -prehashed: Dict[bytes, _Hash] = {} - - -def init_prehashed() -> None: - for x in [EMPTY, TERMINAL, MIDDLE]: - for y in [EMPTY, TERMINAL, MIDDLE]: - prehashed[x + y] = sha256(bytes([0] * 30) + x + y) - - -init_prehashed() - - -def hashdown(mystr: bytes) -> bytes: - assert len(mystr) == 66 - h = prehashed[bytes(mystr[0:1] + mystr[33:34])].copy() - h.update(mystr[1:33] + mystr[34:]) - return h.digest()[:32] - - -def compress_root(mystr: bytes) -> bytes32: - assert len(mystr) == 33 - if mystr[0:1] == MIDDLE: - return bytes32(mystr[1:]) - if mystr[0:1] == EMPTY: - assert mystr[1:] == BLANK - return BLANK - return bytes32(sha256(mystr).digest()[:32]) - - -def get_bit(mybytes: bytes, pos: int) -> int: - assert len(mybytes) == 32 - return (mybytes[pos // 8] >> (7 - (pos % 8))) & 1 - - -class Node(metaclass=ABCMeta): - hash: bytes - - @abstractmethod - def get_hash(self) -> bytes: - pass - - @abstractmethod - def is_empty(self) -> bool: - pass - - @abstractmethod - def is_terminal(self) -> bool: - pass - - @abstractmethod - def is_double(self) -> bool: - pass - - @abstractmethod - def add(self, toadd: bytes, depth: int) -> Node: - pass - - @abstractmethod - def is_included(self, tocheck: bytes, depth: int, p: List[bytes]) -> bool: - pass - - @abstractmethod - def other_included(self, tocheck: bytes, depth: int, p: List[bytes], collapse: bool) -> None: - pass - - @abstractmethod - def _audit(self, hashes: List[bytes], bits: List[int]) -> None: - pass - - -class MerkleSet: - root: Node - - def __init__(self, leafs: Iterable[bytes32]): - self.root = _empty - for leaf in leafs: - self.root = self.root.add(leaf, 0) - - def get_root(self) -> bytes32: - return compress_root(self.root.get_hash()) - - def add_already_hashed(self, toadd: bytes) -> None: - self.root = self.root.add(toadd, 0) - - def is_included_already_hashed(self, tocheck: bytes) -> Tuple[bool, bytes]: - proof: List[bytes] = [] - r = self.root.is_included(tocheck, 0, proof) - return r, b"".join(proof) - - def _audit(self, hashes: List[bytes]) -> None: - newhashes: List[bytes] = [] - self.root._audit(newhashes, []) - assert newhashes == sorted(newhashes) - - @staticmethod - def _with_root(root: Node) -> MerkleSet: - s = MerkleSet([]) - s.root = root - return s - - -class EmptyNode(Node): - def __init__(self) -> None: - self.hash = BLANK - - def get_hash(self) -> bytes: - return EMPTY + BLANK - - def is_empty(self) -> bool: - return True - - def is_terminal(self) -> bool: - return False - - def is_double(self) -> bool: - raise SetError() - - def add(self, toadd: bytes, depth: int) -> Node: - return TerminalNode(toadd) - - def is_included(self, tocheck: bytes, depth: int, p: List[bytes]) -> bool: - p.append(EMPTY) - return False - - def other_included(self, tocheck: bytes, depth: int, p: List[bytes], collapse: bool) -> None: - p.append(EMPTY) - - def _audit(self, hashes: List[bytes], bits: List[int]) -> None: - pass - - -_empty = EmptyNode() - - -def _make_middle(children: Any, depth: int) -> Node: - cbits = [get_bit(child.hash, depth) for child in children] - if cbits[0] != cbits[1]: - return MiddleNode(children) - nextvals: List[Node] = [_empty, _empty] - nextvals[cbits[0] ^ 1] = _empty - nextvals[cbits[0]] = _make_middle(children, depth + 1) - return MiddleNode(nextvals) - - -class TerminalNode(Node): - def __init__(self, hash: bytes, bits: Optional[List[int]] = None) -> None: - assert len(hash) == 32 - self.hash = hash - if bits is not None: - self._audit([], bits) - - def get_hash(self) -> bytes: - return TERMINAL + self.hash - - def is_empty(self) -> bool: - return False - - def is_terminal(self) -> bool: - return True - - def is_double(self) -> bool: - raise SetError() - - def add(self, toadd: bytes, depth: int) -> Node: - if toadd == self.hash: - return self - if toadd > self.hash: - return _make_middle([self, TerminalNode(toadd)], depth) - else: - return _make_middle([TerminalNode(toadd), self], depth) - - def is_included(self, tocheck: bytes, depth: int, p: List[bytes]) -> bool: - p.append(TERMINAL + self.hash) - return tocheck == self.hash - - def other_included(self, tocheck: bytes, depth: int, p: List[bytes], collapse: bool) -> None: - p.append(TERMINAL + self.hash) - - def _audit(self, hashes: List[bytes], bits: List[int]) -> None: - hashes.append(self.hash) - for pos, v in enumerate(bits): - assert get_bit(self.hash, pos) == v - - -class MiddleNode(Node): - def __init__(self, children: List[Node]): - self.children = children - if children[0].is_empty() and children[1].is_double(): - self.hash = children[1].hash - elif children[1].is_empty() and children[0].is_double(): - self.hash = children[0].hash - else: - if children[0].is_empty() and (children[1].is_empty() or children[1].is_terminal()): - raise SetError() - if children[1].is_empty() and children[0].is_terminal(): - raise SetError - if children[0].is_terminal() and children[1].is_terminal() and children[0].hash >= children[1].hash: - raise SetError - self.hash = hashdown(children[0].get_hash() + children[1].get_hash()) - - def get_hash(self) -> bytes: - return MIDDLE + self.hash - - def is_empty(self) -> bool: - return False - - def is_terminal(self) -> bool: - return False - - def is_double(self) -> bool: - if self.children[0].is_empty(): - return self.children[1].is_double() - if self.children[1].is_empty(): - return self.children[0].is_double() - return self.children[0].is_terminal() and self.children[1].is_terminal() - - def add(self, toadd: bytes, depth: int) -> Node: - bit = get_bit(toadd, depth) - child = self.children[bit] - newchild = child.add(toadd, depth + 1) - if newchild is child: - return self - newvals = [x for x in self.children] - newvals[bit] = newchild - return MiddleNode(newvals) - - def is_included(self, tocheck: bytes, depth: int, p: List[bytes]) -> bool: - p.append(MIDDLE) - if get_bit(tocheck, depth) == 0: - r = self.children[0].is_included(tocheck, depth + 1, p) - self.children[1].other_included(tocheck, depth + 1, p, not self.children[0].is_empty()) - return r - else: - self.children[0].other_included(tocheck, depth + 1, p, not self.children[1].is_empty()) - return self.children[1].is_included(tocheck, depth + 1, p) - - def other_included(self, tocheck: bytes, depth: int, p: List[bytes], collapse: bool) -> None: - if collapse or not self.is_double(): - p.append(TRUNCATED + self.hash) - else: - self.is_included(tocheck, depth, p) - - def _audit(self, hashes: List[bytes], bits: List[int]) -> None: - self.children[0]._audit(hashes, bits + [0]) - self.children[1]._audit(hashes, bits + [1]) - - -class TruncatedNode(Node): - def __init__(self, hash: bytes): - self.hash = hash - - def get_hash(self) -> bytes: - return MIDDLE + self.hash - - def is_empty(self) -> bool: - return False - - def is_terminal(self) -> bool: - return False - - def is_double(self) -> bool: - return False - - def add(self, toadd: bytes, depth: int) -> Node: - return self - - def is_included(self, tocheck: bytes, depth: int, p: List[bytes]) -> bool: - raise SetError() - - def other_included(self, tocheck: bytes, depth: int, p: List[bytes], collapse: bool) -> None: - p.append(TRUNCATED + self.hash) - - def _audit(self, hashes: List[bytes], bits: List[int]) -> None: - pass - - -class SetError(Exception): - pass - - -def confirm_included_already_hashed(root: bytes32, val: bytes, proof: bytes) -> bool: - return _confirm(root, val, proof, True) - - -def confirm_not_included_already_hashed(root: bytes32, val: bytes, proof: bytes) -> bool: - return _confirm(root, val, proof, False) - - -def _confirm(root: bytes32, val: bytes, proof: bytes, expected: bool) -> bool: - try: - p = deserialize_proof(proof) - if p.get_root() != root: - return False - r, junk = p.is_included_already_hashed(val) - return r == expected - except SetError: - return False - - -def deserialize_proof(proof: bytes) -> MerkleSet: - try: - r, pos = _deserialize(proof, 0, []) - if pos != len(proof): - raise SetError() - return MerkleSet._with_root(r) - except IndexError: - raise SetError() - - -def _deserialize(proof: bytes, pos: int, bits: List[int]) -> Tuple[Node, int]: - t = proof[pos : pos + 1] # flake8: noqa - if t == EMPTY: - return _empty, pos + 1 - if t == TERMINAL: - return TerminalNode(proof[pos + 1 : pos + 33], bits), pos + 33 # flake8: noqa - if t == TRUNCATED: - return TruncatedNode(proof[pos + 1 : pos + 33]), pos + 33 # flake8: noqa - if t != MIDDLE: - raise SetError() - v0, pos = _deserialize(proof, pos + 1, bits + [0]) - v1, pos = _deserialize(proof, pos, bits + [1]) - return MiddleNode([v0, v1]), pos diff --git a/chia/util/misc.py b/chia/util/misc.py index c147207ec53c..cf7f50d00137 100644 --- a/chia/util/misc.py +++ b/chia/util/misc.py @@ -168,7 +168,7 @@ def to_batches(to_split: Collection[T], batch_size: int) -> Iterator[Batch[T]]: raise ValueError("to_batches: batch_size must be greater than 0.") total_size = len(to_split) if total_size == 0: - return iter(()) + return if isinstance(to_split, list): for batch_start in range(0, total_size, batch_size): @@ -417,7 +417,12 @@ def available_logical_cores() -> int: assert count is not None return count - return len(psutil.Process().cpu_affinity()) + cores = len(psutil.Process().cpu_affinity()) + + if sys.platform == "win32": + cores = min(61, cores) # https://github.com/python/cpython/issues/89240 + + return cores def caller_file_and_line(distance: int = 1, relative_to: Iterable[Path] = ()) -> Tuple[str, int]: diff --git a/chia/util/streamable.py b/chia/util/streamable.py index 180f25ed6940..9e0e78f954cd 100644 --- a/chia/util/streamable.py +++ b/chia/util/streamable.py @@ -207,24 +207,28 @@ def streamable_from_dict(klass: Type[_T_Streamable], item: Any) -> _T_Streamable raise -def function_to_convert_one_item(f_type: Type[Any]) -> ConvertFunctionType: +def function_to_convert_one_item( + f_type: Type[Any], json_parser: Optional[Callable[[object], Streamable]] = None +) -> ConvertFunctionType: if is_type_SpecificOptional(f_type): - convert_inner_func = function_to_convert_one_item(get_args(f_type)[0]) + convert_inner_func = function_to_convert_one_item(get_args(f_type)[0], json_parser) return lambda item: convert_optional(convert_inner_func, item) elif is_type_Tuple(f_type): args = get_args(f_type) convert_inner_tuple_funcs = [] for arg in args: - convert_inner_tuple_funcs.append(function_to_convert_one_item(arg)) + convert_inner_tuple_funcs.append(function_to_convert_one_item(arg, json_parser)) # Ignoring for now as the proper solution isn't obvious return lambda items: convert_tuple(convert_inner_tuple_funcs, items) # type: ignore[arg-type] elif is_type_List(f_type): inner_type = get_args(f_type)[0] - convert_inner_func = function_to_convert_one_item(inner_type) + convert_inner_func = function_to_convert_one_item(inner_type, json_parser) # Ignoring for now as the proper solution isn't obvious return lambda items: convert_list(convert_inner_func, items) # type: ignore[arg-type] elif hasattr(f_type, "from_json_dict"): - return lambda item: f_type.from_json_dict(item) + if json_parser is None: + json_parser = f_type.from_json_dict + return lambda item: json_parser(item) elif issubclass(f_type, bytes): # Type is bytes, data is a hex string or bytes return lambda item: convert_byte_type(f_type, item) @@ -268,26 +272,28 @@ def function_to_post_init_process_one_item(f_type: Type[object]) -> ConvertFunct return lambda item: post_init_process_item(f_type, item) -def recurse_jsonify(d: Any) -> Any: +def recurse_jsonify(d: Any, next_recursion_step: Optional[Callable[[Any, Any], Any]] = None) -> Any: """ Makes bytes objects into strings with 0x, and makes large ints into strings. """ + if next_recursion_step is None: + next_recursion_step = recurse_jsonify if dataclasses.is_dataclass(d): new_dict = {} for field in dataclasses.fields(d): - new_dict[field.name] = recurse_jsonify(getattr(d, field.name)) + new_dict[field.name] = next_recursion_step(getattr(d, field.name), None) return new_dict elif isinstance(d, list) or isinstance(d, tuple): new_list = [] for item in d: - new_list.append(recurse_jsonify(item)) + new_list.append(next_recursion_step(item, None)) return new_list elif isinstance(d, dict): new_dict = {} for name, val in d.items(): - new_dict[name] = recurse_jsonify(val) + new_dict[name] = next_recursion_step(val, None) return new_dict elif issubclass(type(d), bytes): diff --git a/chia/wallet/cat_wallet/cat_wallet.py b/chia/wallet/cat_wallet/cat_wallet.py index ddf8478b8075..36c972a1217f 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 AugSchemeMPL, G1Element, G2Element +from chia_rs import G1Element, G2Element from typing_extensions import Unpack from chia.consensus.default_constants import DEFAULT_CONSTANTS @@ -18,7 +18,6 @@ from chia.types.condition_opcodes import ConditionOpcode from chia.types.spend_bundle import SpendBundle from chia.util.byte_types import hexstr_to_bytes -from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict from chia.util.errors import Err, ValidationError from chia.util.hash import std_hash from chia.util.ints import uint32, uint64, uint128 @@ -46,10 +45,6 @@ from chia.wallet.outer_puzzles import AssetType from chia.wallet.payment import Payment from chia.wallet.puzzle_drivers import PuzzleInfo -from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( - DEFAULT_HIDDEN_PUZZLE_HASH, - calculate_synthetic_secret_key, -) from chia.wallet.puzzles.tails import ALL_LIMITATIONS_PROGRAMS from chia.wallet.transaction_record import TransactionRecord from chia.wallet.uncurried_puzzle import uncurry_puzzle @@ -200,8 +195,8 @@ async def create_new_cat_wallet( valid_times=ConditionValidTimes(), ) chia_tx = dataclasses.replace(chia_tx, spend_bundle=spend_bundle, name=spend_bundle.name()) - await self.wallet_state_manager.add_pending_transactions([chia_tx, cat_record]) - return self, [chia_tx, cat_record] + tx_list = await self.wallet_state_manager.add_pending_transactions([chia_tx, cat_record]) + return self, tx_list @staticmethod async def get_or_create_wallet_for_cat( @@ -536,31 +531,6 @@ async def select_coins( assert sum(c.amount for c in coins) >= amount return coins - async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: - sigs: List[G2Element] = [] - for spend in spend_bundle.coin_spends: - args = match_cat_puzzle(uncurry_puzzle(spend.puzzle_reveal.to_program())) - if args is not None: - _, _, inner_puzzle = args - puzzle_hash = inner_puzzle.get_tree_hash() - private = await self.wallet_state_manager.get_private_key(puzzle_hash) - synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH) - conditions = conditions_dict_for_solution( - spend.puzzle_reveal, spend.solution, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM - ) - synthetic_pk = synthetic_secret_key.get_g1() - for pk, msg in pkm_pairs_for_conditions_dict( - conditions, spend.coin, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA - ): - try: - assert bytes(synthetic_pk) == pk - sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg)) - except AssertionError: - raise ValueError("This spend bundle cannot be signed by the CAT wallet") - - agg_sig = AugSchemeMPL.aggregate(sigs) - return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)]) - async def inner_puzzle_for_cat_puzhash(self, cat_hash: bytes32) -> Program: record: Optional[DerivationRecord] = ( await self.wallet_state_manager.puzzle_store.get_derivation_record_for_puzzle_hash(cat_hash) @@ -806,7 +776,7 @@ 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( + spend_bundle, chia_tx = await self.generate_unsigned_spendbundle( payments, tx_config, fee, @@ -814,8 +784,6 @@ async def generate_signed_transaction( coins=coins, extra_conditions=extra_conditions, ) - spend_bundle = await self.sign(unsigned_spend_bundle) - 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} diff --git a/chia/wallet/cat_wallet/dao_cat_wallet.py b/chia/wallet/cat_wallet/dao_cat_wallet.py index 18c80621b2b1..b7e3540e9201 100644 --- a/chia/wallet/cat_wallet/dao_cat_wallet.py +++ b/chia/wallet/cat_wallet/dao_cat_wallet.py @@ -357,9 +357,7 @@ async def create_vote_spend( spendable_cat_list.append(new_spendable_cat) cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list) - spend_bundle = await self.wallet_state_manager.sign_transaction(cat_spend_bundle.coin_spends) - assert isinstance(spend_bundle, SpendBundle) - return spend_bundle + return cat_spend_bundle async def enter_dao_cat_voting_mode( self, @@ -451,8 +449,7 @@ async def exit_vote_state( spendable_cat_list.append(new_spendable_cat) spent_coins.append(coin) - cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list) - spend_bundle: SpendBundle = await self.wallet_state_manager.sign_transaction(cat_spend_bundle.coin_spends) + 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( @@ -557,8 +554,7 @@ async def remove_active_proposal( ) spendable_cat_list.append(new_spendable_cat) - cat_spend_bundle = unsigned_spend_bundle_for_spendable_cats(CAT_MOD, spendable_cat_list) - spend_bundle = await self.wallet_state_manager.sign_transaction(cat_spend_bundle.coin_spends) + 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) diff --git a/chia/wallet/derive_keys.py b/chia/wallet/derive_keys.py index f85801df0054..0f20a223f1f0 100644 --- a/chia/wallet/derive_keys.py +++ b/chia/wallet/derive_keys.py @@ -30,6 +30,12 @@ def _derive_path_unhardened(sk: PrivateKey, path: List[int]) -> PrivateKey: return sk +def _derive_pk_unhardened(pk: G1Element, path: List[int]) -> G1Element: + for index in path: + pk = AugSchemeMPL.derive_child_pk_unhardened(pk, index) + return pk + + def master_sk_to_farmer_sk(master: PrivateKey) -> PrivateKey: return _derive_path(master, [12381, 8444, 0, 0]) @@ -51,11 +57,20 @@ def master_sk_to_wallet_sk_unhardened_intermediate(master: PrivateKey) -> Privat return _derive_path_unhardened(master, [12381, 8444, 2]) +def master_pk_to_wallet_pk_unhardened_intermediate(master: G1Element) -> G1Element: + return _derive_pk_unhardened(master, [12381, 8444, 2]) + + def master_sk_to_wallet_sk_unhardened(master: PrivateKey, index: uint32) -> PrivateKey: intermediate = master_sk_to_wallet_sk_unhardened_intermediate(master) return _derive_path_unhardened(intermediate, [index]) +def master_pk_to_wallet_pk_unhardened(master: G1Element, index: uint32) -> G1Element: + intermediate = master_pk_to_wallet_pk_unhardened_intermediate(master) + return _derive_pk_unhardened(intermediate, [index]) + + def master_sk_to_local_sk(master: PrivateKey) -> PrivateKey: return _derive_path(master, [12381, 8444, 3, 0]) diff --git a/chia/wallet/did_wallet/did_wallet.py b/chia/wallet/did_wallet/did_wallet.py index 186a3df5681b..f9f17dabb83a 100644 --- a/chia/wallet/did_wallet/did_wallet.py +++ b/chia/wallet/did_wallet/did_wallet.py @@ -17,7 +17,6 @@ from chia.types.coin_spend import CoinSpend, make_spend from chia.types.signing_mode import CHIP_0002_SIGN_MESSAGE_PREFIX, SigningMode from chia.types.spend_bundle import SpendBundle -from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict from chia.util.ints import uint16, uint32, uint64, uint128 from chia.wallet.conditions import ( AssertCoinAnnouncement, @@ -27,7 +26,6 @@ parse_timelock_info, ) from chia.wallet.derivation_record import DerivationRecord -from chia.wallet.derive_keys import master_sk_to_wallet_sk_unhardened from chia.wallet.did_wallet import did_wallet_puzzles from chia.wallet.did_wallet.did_info import DIDCoinData, DIDInfo from chia.wallet.did_wallet.did_wallet_puzzles import match_did_puzzle, uncurry_innerpuz @@ -626,8 +624,7 @@ async def create_update_spend( make_spend(coin, full_puzzle, fullsol), make_spend(new_coin, new_full_puzzle, new_full_sol), ] - unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - spend_bundle = await self.sign(unsigned_spend_bundle) + spend_bundle = SpendBundle(list_of_coinspends, G2Element()) if fee > 0: coin_name = coin.name() chia_tx = await self.standard_wallet.create_tandem_xch_tx( @@ -726,8 +723,7 @@ async def transfer_did( ] ) list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] - unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - spend_bundle = await self.sign(unsigned_spend_bundle) + spend_bundle = SpendBundle(list_of_coinspends, G2Element()) if fee > 0: coin_name = coin.name() chia_tx = await self.standard_wallet.create_tandem_xch_tx( @@ -816,7 +812,6 @@ async def create_message_spend( ) list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - signed_spend_bundle: SpendBundle = await self.sign(unsigned_spend_bundle) return TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -825,15 +820,15 @@ async def create_message_spend( fee_amount=uint64(0), confirmed=False, sent=uint32(0), - spend_bundle=signed_spend_bundle, - additions=signed_spend_bundle.additions(), + spend_bundle=unsigned_spend_bundle, + additions=unsigned_spend_bundle.additions(), removals=[coin], wallet_id=self.id(), sent_to=[], trade_id=None, type=uint32(TransactionType.OUTGOING_TX.value), - name=signed_spend_bundle.name(), - memos=list(compute_memos(signed_spend_bundle).items()), + name=unsigned_spend_bundle.name(), + memos=list(compute_memos(unsigned_spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) @@ -867,8 +862,7 @@ async def create_exit_spend(self, puzhash: bytes32, tx_config: TXConfig) -> List ] ) list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] - unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - spend_bundle = await self.sign(unsigned_spend_bundle) + spend_bundle = SpendBundle(list_of_coinspends, G2Element()) did_record = TransactionRecord( confirmed_at_height=uint32(0), @@ -952,8 +946,7 @@ async def create_attestment( list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] message_spend = did_wallet_puzzles.create_spend_for_message(coin.name(), recovering_coin_name, newpuz, pubkey) message_spend_bundle = SpendBundle([message_spend], AugSchemeMPL.aggregate([])) - unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - spend_bundle = await self.sign(unsigned_spend_bundle) + spend_bundle = SpendBundle(list_of_coinspends, G2Element()) did_record = TransactionRecord( confirmed_at_height=uint32(0), created_at_time=uint64(int(time.time())), @@ -1064,20 +1057,7 @@ async def recovery_spend( ) list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] - index = await self.wallet_state_manager.puzzle_store.index_for_pubkey(pubkey) - if index is None: - raise ValueError("Unknown pubkey.") - private = master_sk_to_wallet_sk_unhardened(self.wallet_state_manager.private_key, index) - message = bytes(puzhash) - sigs = [AugSchemeMPL.sign(private, message)] - for _ in spend_bundle.coin_spends: - sigs.append(AugSchemeMPL.sign(private, message)) - aggsig = AugSchemeMPL.aggregate(sigs) - # assert AugSchemeMPL.verify(pubkey, message, aggsig) - if spend_bundle is None: - spend_bundle = SpendBundle(list_of_coinspends, aggsig) - else: - spend_bundle = spend_bundle.aggregate([spend_bundle, SpendBundle(list_of_coinspends, aggsig)]) + spend_bundle = spend_bundle.aggregate([spend_bundle, SpendBundle(list_of_coinspends, G2Element())]) did_record = TransactionRecord( confirmed_at_height=uint32(0), @@ -1214,31 +1194,6 @@ async def sign_message(self, message: str, mode: SigningMode) -> Tuple[G1Element else: raise ValueError("Invalid inner DID puzzle.") - async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: - sigs: List[G2Element] = [] - for spend in spend_bundle.coin_spends: - puzzle_args = did_wallet_puzzles.match_did_puzzle(*spend.puzzle_reveal.to_program().uncurry()) - if puzzle_args is not None: - p2_puzzle, _, _, _, _ = puzzle_args - puzzle_hash = p2_puzzle.get_tree_hash() - private = await self.wallet_state_manager.get_private_key(puzzle_hash) - synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH) - conditions = conditions_dict_for_solution( - spend.puzzle_reveal, spend.solution, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM - ) - synthetic_pk = synthetic_secret_key.get_g1() - for pk, msg in pkm_pairs_for_conditions_dict( - conditions, spend.coin, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA - ): - try: - assert bytes(synthetic_pk) == pk - sigs.append(AugSchemeMPL.sign(synthetic_secret_key, msg)) - except AssertionError: - raise ValueError("This spend bundle cannot be signed by the DID wallet") - - agg_sig = AugSchemeMPL.aggregate(sigs) - return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)]) - async def generate_new_decentralised_id( self, amount: uint64, tx_config: TXConfig, fee: uint64 = uint64(0) ) -> List[TransactionRecord]: @@ -1359,7 +1314,7 @@ async def generate_eve_spend( ) list_of_coinspends = [make_spend(coin, full_puzzle, fullsol)] unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - return await self.sign(unsigned_spend_bundle) + return unsigned_spend_bundle async def get_spendable_balance(self, unspent_records=None) -> uint128: spendable_am = await self.wallet_state_manager.get_confirmed_spendable_balance_for_wallet( diff --git a/chia/wallet/nft_wallet/nft_wallet.py b/chia/wallet/nft_wallet/nft_wallet.py index 9ef9a3751a19..8bbd3423e75f 100644 --- a/chia/wallet/nft_wallet/nft_wallet.py +++ b/chia/wallet/nft_wallet/nft_wallet.py @@ -20,7 +20,6 @@ from chia.types.coin_spend import CoinSpend, compute_additions, make_spend from chia.types.signing_mode import CHIP_0002_SIGN_MESSAGE_PREFIX, SigningMode from chia.types.spend_bundle import SpendBundle -from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict from chia.util.hash import std_hash from chia.util.ints import uint16, uint32, uint64, uint128 from chia.wallet.conditions import ( @@ -442,42 +441,6 @@ async def generate_new_nft( txs.append(dataclasses.replace(tx_record, spend_bundle=SpendBundle.aggregate(bundles_to_agg))) return txs - async def sign(self, spend_bundle: SpendBundle, puzzle_hashes: Optional[List[bytes32]] = None) -> SpendBundle: - if puzzle_hashes is None: - puzzle_hashes = [] - sigs: List[G2Element] = [] - for spend in spend_bundle.coin_spends: - pks = {} - if not puzzle_hashes: - uncurried_nft = UncurriedNFT.uncurry(*spend.puzzle_reveal.to_program().uncurry()) - if uncurried_nft is not None: - self.log.debug("Found a NFT state layer to sign") - puzzle_hashes.append(uncurried_nft.p2_puzzle.get_tree_hash()) - for ph in puzzle_hashes: - private = await self.wallet_state_manager.get_private_key(ph) - pks[bytes(private.get_g1())] = private - synthetic_secret_key = calculate_synthetic_secret_key(private, DEFAULT_HIDDEN_PUZZLE_HASH) - synthetic_pk = synthetic_secret_key.get_g1() - pks[bytes(synthetic_pk)] = synthetic_secret_key - conditions = conditions_dict_for_solution( - spend.puzzle_reveal, spend.solution, self.wallet_state_manager.constants.MAX_BLOCK_COST_CLVM - ) - for pk, msg in pkm_pairs_for_conditions_dict( - conditions, spend.coin, self.wallet_state_manager.constants.AGG_SIG_ME_ADDITIONAL_DATA - ): - try: - sk = pks.get(pk) - if sk: - self.log.debug("Found key, signing for pk: %s", pk) - sigs.append(AugSchemeMPL.sign(sk, msg)) - else: - self.log.warning("Couldn't find key for: %s", pk) - except AssertionError: - raise ValueError("This spend bundle cannot be signed by the NFT wallet") - - agg_sig = AugSchemeMPL.aggregate(sigs) - return SpendBundle.aggregate([spend_bundle, SpendBundle([], agg_sig)]) - async def update_metadata( self, nft_coin_info: NFTCoinInfo, @@ -648,8 +611,7 @@ async def generate_signed_transaction( metadata_update=metadata_update, extra_conditions=extra_conditions, ) - spend_bundle = await self.sign(unsigned_spend_bundle) - spend_bundle = SpendBundle.aggregate([spend_bundle] + additional_bundles) + 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) @@ -1466,7 +1428,7 @@ async def mint_from_did( primaries=[], conditions=(AssertCoinAnnouncement(primary_announcement_hash),) ) xch_spends.append(make_spend(xch_coin, puzzle, solution)) - xch_spend = await self.wallet_state_manager.sign_transaction(xch_spends) + xch_spend = SpendBundle(xch_spends, G2Element()) # Create the DID spend using the announcements collected when making the intermediate launcher coins did_p2_solution = self.standard_wallet.make_solution( @@ -1510,10 +1472,9 @@ async def mint_from_did( # Collect up all the coin spends and sign them list_of_coinspends = [did_spend] + intermediate_coin_spends + launcher_spends unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - signed_spend_bundle = await did_wallet.sign(unsigned_spend_bundle) # Aggregate everything into a single spend bundle - total_spend = SpendBundle.aggregate([signed_spend_bundle, xch_spend, *eve_spends]) + 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] @@ -1704,15 +1665,14 @@ 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 = await self.wallet_state_manager.sign_transaction(xch_spends) + xch_spend = SpendBundle(xch_spends, G2Element()) - # Collect up all the coin spends and sign them + # Collect up all the coin spends list_of_coinspends = intermediate_coin_spends + launcher_spends unsigned_spend_bundle = SpendBundle(list_of_coinspends, G2Element()) - signed_spend_bundle = await self.sign(unsigned_spend_bundle) # Aggregate everything into a single spend bundle - total_spend = SpendBundle.aggregate([signed_spend_bundle, xch_spend, *eve_spends]) + 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] diff --git a/chia/wallet/puzzles/tails.py b/chia/wallet/puzzles/tails.py index bc573a9f328c..b897f8495b29 100644 --- a/chia/wallet/puzzles/tails.py +++ b/chia/wallet/puzzles/tails.py @@ -124,12 +124,11 @@ async def generate_issuance_bundle( ) ], ) - signed_eve_spend = await wallet.sign(eve_spend) 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, signed_eve_spend]) + return tx_record, SpendBundle.aggregate([tx_record.spend_bundle, eve_spend]) class GenesisByPuzhash(LimitationsProgram): @@ -287,12 +286,11 @@ async def generate_issuance_bundle( ) ], ) - signed_eve_spend = await wallet.sign(eve_spend) 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, signed_eve_spend]) + return tx_record, SpendBundle.aggregate([tx_record.spend_bundle, eve_spend]) # This should probably be much more elegant than just a dictionary with strings as identifiers diff --git a/chia/wallet/sign_coin_spends.py b/chia/wallet/sign_coin_spends.py deleted file mode 100644 index dee529d91220..000000000000 --- a/chia/wallet/sign_coin_spends.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import annotations - -import inspect -from typing import Any, Callable, List - -from chia_rs import AugSchemeMPL, G1Element, G2Element - -from chia.types.blockchain_format.sized_bytes import bytes32 -from chia.types.coin_spend import CoinSpend -from chia.types.spend_bundle import SpendBundle -from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict - - -async def sign_coin_spends( - coin_spends: List[CoinSpend], - secret_key_for_public_key_f: Any, # Potentially awaitable function from G1Element => Optional[PrivateKey] - secret_key_for_puzzle_hash: Any, # Potentially awaitable function from bytes32 => Optional[PrivateKey] - additional_data: bytes, - max_cost: int, - potential_derivation_functions: List[Callable[[G1Element], bytes32]], -) -> SpendBundle: - """ - Sign_coin_spends runs the puzzle code with the given argument and searches the - result for an AGG_SIG_ME condition, which it attempts to sign by requesting a - matching PrivateKey corresponding with the given G1Element (public key) specified - in the resulting condition output. - - It's important to note that as mentioned in the documentation about the standard - spend that the public key presented to the secret_key_for_public_key_f function - provided to sign_coin_spends must be prepared to do the key derivations required - by the coin types it's allowed to spend (at least the derivation of the standard - spend as done by calculate_synthetic_secret_key with DEFAULT_PUZZLE_HASH). - - If a coin performed a different key derivation, the pk presented to this function - would be similarly alien, and would need to be tried against the first stage - derived keys (those returned by master_sk_to_wallet_sk from the ['sk'] member of - wallet rpc's get_private_key method). - """ - signatures: List[G2Element] = [] - pk_list: List[G1Element] = [] - msg_list: List[bytes] = [] - for coin_spend in coin_spends: - # Get AGG_SIG conditions - conditions_dict = conditions_dict_for_solution(coin_spend.puzzle_reveal, coin_spend.solution, max_cost) - # Create signature - for pk_bytes, msg in pkm_pairs_for_conditions_dict(conditions_dict, coin_spend.coin, additional_data): - pk = G1Element.from_bytes(pk_bytes) - pk_list.append(pk) - msg_list.append(msg) - if inspect.iscoroutinefunction(secret_key_for_public_key_f): - secret_key = await secret_key_for_public_key_f(pk) - else: - secret_key = secret_key_for_public_key_f(pk) - if secret_key is None or secret_key.get_g1() != pk: - for derive in potential_derivation_functions: - if inspect.iscoroutinefunction(secret_key_for_puzzle_hash): - secret_key = await secret_key_for_puzzle_hash(derive(pk)) - else: - secret_key = secret_key_for_puzzle_hash(derive(pk)) - if secret_key is not None and secret_key.get_g1() == pk: - break - else: - raise ValueError(f"no secret key for {pk}") - signature = AugSchemeMPL.sign(secret_key, msg) - signatures.append(signature) - - # Aggregate signatures - aggsig = AugSchemeMPL.aggregate(signatures) - return SpendBundle(coin_spends, aggsig) diff --git a/chia/wallet/signer_protocol.py b/chia/wallet/signer_protocol.py new file mode 100644 index 000000000000..302dc0706f33 --- /dev/null +++ b/chia/wallet/signer_protocol.py @@ -0,0 +1,126 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import List + +from chia.types.blockchain_format.coin import Coin as _Coin +from chia.types.blockchain_format.program import Program +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 +from chia.util.ints import uint64 +from chia.util.streamable import Streamable +from chia.wallet.util.clvm_streamable import clvm_streamable + +# This file contains the base types for communication between a wallet and an offline transaction signer. +# These types should be compliant with CHIP-TBD + + +@clvm_streamable +@dataclass(frozen=True) +class Coin(Streamable): + parent_coin_id: bytes32 + puzzle_hash: bytes32 + amount: uint64 + + +@clvm_streamable +@dataclass(frozen=True) +class Spend(Streamable): + coin: Coin + puzzle: Program + solution: Program + + @classmethod + def from_coin_spend(cls, coin_spend: CoinSpend) -> Spend: + return cls( + Coin( + coin_spend.coin.parent_coin_info, + coin_spend.coin.puzzle_hash, + uint64(coin_spend.coin.amount), + ), + coin_spend.puzzle_reveal.to_program(), + coin_spend.solution.to_program(), + ) + + def as_coin_spend(self) -> CoinSpend: + return CoinSpend( + _Coin( + self.coin.parent_coin_id, + self.coin.puzzle_hash, + self.coin.amount, + ), + SerializedProgram.from_program(self.puzzle), + SerializedProgram.from_program(self.solution), + ) + + +@clvm_streamable +@dataclass(frozen=True) +class TransactionInfo(Streamable): + spends: List[Spend] + + +@clvm_streamable +@dataclass(frozen=True) +class SigningTarget(Streamable): + fingerprint: bytes + message: bytes + hook: bytes32 + + +@clvm_streamable +@dataclass(frozen=True) +class SumHint(Streamable): + fingerprints: List[bytes] + synthetic_offset: bytes + final_pubkey: bytes + + +@clvm_streamable +@dataclass(frozen=True) +class PathHint(Streamable): + root_fingerprint: bytes + path: List[uint64] + + +@clvm_streamable +@dataclass(frozen=True) +class KeyHints(Streamable): + sum_hints: List[SumHint] + path_hints: List[PathHint] + + +@clvm_streamable +@dataclass(frozen=True) +class SigningInstructions(Streamable): + key_hints: KeyHints + targets: List[SigningTarget] + + +@clvm_streamable +@dataclass(frozen=True) +class UnsignedTransaction(Streamable): + transaction_info: TransactionInfo + signing_instructions: SigningInstructions + + +@clvm_streamable +@dataclass(frozen=True) +class SigningResponse(Streamable): + signature: bytes + hook: bytes32 + + +@clvm_streamable +@dataclass(frozen=True) +class Signature(Streamable): + type: str + signature: bytes + + +@clvm_streamable +@dataclass(frozen=True) +class SignedTransaction(Streamable): + transaction_info: TransactionInfo + signatures: List[Signature] diff --git a/chia/wallet/trade_manager.py b/chia/wallet/trade_manager.py index 3eb18df9512f..17c4cd8d25dd 100644 --- a/chia/wallet/trade_manager.py +++ b/chia/wallet/trade_manager.py @@ -436,7 +436,7 @@ async def _create_offer_for_ids( if solver is None: solver = Solver({}) try: - coins_to_offer: Dict[Union[int, bytes32], List[Coin]] = {} + coins_to_offer: Dict[Union[int, bytes32], Set[Coin]] = {} requested_payments: Dict[Optional[bytes32], List[Payment]] = {} offer_dict_no_ints: Dict[Optional[bytes32], int] = {} for id, amount in offer_dict.items(): @@ -553,7 +553,7 @@ async def _create_offer_for_ids( Offer.ph(), tx_config, fee=fee_left_to_pay, - coins=set(selected_coins), + coins=selected_coins, extra_conditions=(*extra_conditions, *announcements_to_assert), ) all_transactions.append(tx) @@ -567,7 +567,7 @@ async def _create_offer_for_ids( [Offer.ph()], tx_config, fee=fee_left_to_pay, - coins=set(selected_coins), + coins=selected_coins, extra_conditions=(*extra_conditions, *announcements_to_assert), ) all_transactions.extend(txs) @@ -578,7 +578,7 @@ async def _create_offer_for_ids( [Offer.ph()], tx_config, fee=fee_left_to_pay, - coins=set(selected_coins), + coins=selected_coins, extra_conditions=(*extra_conditions, *announcements_to_assert), add_authorizations_to_cr_cats=False, ) diff --git a/chia/wallet/trading/trade_store.py b/chia/wallet/trading/trade_store.py index 4e68719bea3c..dd9145d33e70 100644 --- a/chia/wallet/trading/trade_store.py +++ b/chia/wallet/trading/trade_store.py @@ -481,6 +481,11 @@ async def rollback_to_block(self, block_index: int) -> None: cursor = await conn.execute("DELETE FROM trade_records WHERE confirmed_at_index>?", (block_index,)) await cursor.close() + async def delete_trade_record(self, trade_id: bytes32) -> None: + async with self.db_wrapper.writer_maybe_transaction() as conn: + await (await conn.execute("DELETE FROM trade_records WHERE trade_id=?", (trade_id.hex(),))).close() + await (await conn.execute("DELETE FROM trade_record_times WHERE trade_id=?", (trade_id,))).close() + async def _get_new_trade_records_from_old(self, old_records: List[TradeRecordOld]) -> List[TradeRecord]: trade_id_to_valid_times: Dict[bytes, ConditionValidTimes] = {} empty_valid_times = ConditionValidTimes() diff --git a/chia/wallet/util/clvm_streamable.py b/chia/wallet/util/clvm_streamable.py new file mode 100644 index 000000000000..ac98522ec6b3 --- /dev/null +++ b/chia/wallet/util/clvm_streamable.py @@ -0,0 +1,106 @@ +from __future__ import annotations + +import dataclasses +import functools +from typing import Any, Callable, Dict, Optional, Type, TypeVar, Union, get_args, get_type_hints + +from hsms.clvm_serde import from_program_for_type, to_program_for_type + +from chia.types.blockchain_format.program import Program +from chia.util.streamable import ( + Streamable, + function_to_convert_one_item, + is_type_List, + is_type_SpecificOptional, + is_type_Tuple, + recurse_jsonify, + streamable, +) + +_T_Streamable = TypeVar("_T_Streamable", bound=Streamable) + + +def clvm_streamable(cls: Type[Streamable]) -> Type[Streamable]: + wrapped_cls: Type[Streamable] = streamable(cls) + setattr(wrapped_cls, "_clvm_streamable", True) + + 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] + if is_type_Tuple(hints[field.name]): + raise ValueError("@clvm_streamable does not support tuples") + + return wrapped_cls + + +def program_serialize_clvm_streamable(clvm_streamable: Streamable) -> Program: + # Underlying hinting problem with clvm_serde + return to_program_for_type(type(clvm_streamable))(clvm_streamable) # type: ignore[no-any-return] + + +def byte_serialize_clvm_streamable(clvm_streamable: Streamable) -> bytes: + return bytes(program_serialize_clvm_streamable(clvm_streamable)) + + +def json_serialize_with_clvm_streamable( + streamable: Any, next_recursion_step: Optional[Callable[[Any, Any], Dict[str, Any]]] = None +) -> Union[str, Dict[str, Any]]: + if next_recursion_step is None: + next_recursion_step = recurse_jsonify + if hasattr(streamable, "_clvm_streamable"): + # If we are using clvm_serde, we stop JSON serialization at this point and instead return the clvm blob + return byte_serialize_clvm_streamable(streamable).hex() + else: + return next_recursion_step(streamable, json_serialize_with_clvm_streamable) + + +def program_deserialize_clvm_streamable(program: Program, clvm_streamable_type: Type[_T_Streamable]) -> _T_Streamable: + # Underlying hinting problem with clvm_serde + return from_program_for_type(clvm_streamable_type)(program) # type: ignore[no-any-return] + + +def byte_deserialize_clvm_streamable(blob: bytes, clvm_streamable_type: Type[_T_Streamable]) -> _T_Streamable: + return program_deserialize_clvm_streamable(Program.from_bytes(blob), clvm_streamable_type) + + +def is_compound_type(typ: Any) -> bool: + return is_type_SpecificOptional(typ) or is_type_Tuple(typ) or is_type_List(typ) + + +def json_deserialize_with_clvm_streamable( + json_dict: Union[str, Dict[str, Any]], streamable_type: Type[_T_Streamable] +) -> _T_Streamable: + if isinstance(json_dict, str): + return byte_deserialize_clvm_streamable(bytes.fromhex(json_dict), streamable_type) + else: + old_streamable_fields = streamable_type.streamable_fields() + new_streamable_fields = [] + for old_field in old_streamable_fields: + if is_compound_type(old_field.type): + inner_type = get_args(old_field.type)[0] + if hasattr(inner_type, "_clvm_streamable"): + new_streamable_fields.append( + dataclasses.replace( + old_field, + convert_function=function_to_convert_one_item( + old_field.type, + functools.partial(json_deserialize_with_clvm_streamable, streamable_type=inner_type), + ), + ) + ) + else: + new_streamable_fields.append(old_field) + elif hasattr(old_field.type, "_clvm_streamable"): + new_streamable_fields.append( + dataclasses.replace( + old_field, + convert_function=functools.partial( + json_deserialize_with_clvm_streamable, streamable_type=old_field.type + ), + ) + ) + else: + new_streamable_fields.append(old_field) + + setattr(streamable_type, "_streamable_fields", tuple(new_streamable_fields)) + return streamable_type.from_json_dict(json_dict) diff --git a/chia/wallet/util/wallet_sync_utils.py b/chia/wallet/util/wallet_sync_utils.py index b320322df2e2..05f401f1e3bd 100644 --- a/chia/wallet/util/wallet_sync_utils.py +++ b/chia/wallet/util/wallet_sync_utils.py @@ -5,7 +5,7 @@ import random from typing import Any, List, Optional, Set, Tuple, Union -from chia_rs import compute_merkle_set_root +from chia_rs import compute_merkle_set_root, confirm_included_already_hashed, confirm_not_included_already_hashed from chia.full_node.full_node_api import FullNodeAPI from chia.protocols.shared_protocol import Capability @@ -36,7 +36,6 @@ from chia.types.coin_spend import CoinSpend, make_spend from chia.types.header_block import HeaderBlock from chia.util.ints import uint32 -from chia.util.merkle_set import confirm_included_already_hashed, confirm_not_included_already_hashed from chia.wallet.util.peer_request_cache import PeerRequestCache log = logging.getLogger(__name__) diff --git a/chia/wallet/vc_wallet/cr_cat_wallet.py b/chia/wallet/vc_wallet/cr_cat_wallet.py index 35286ae87de5..188e90282b50 100644 --- a/chia/wallet/vc_wallet/cr_cat_wallet.py +++ b/chia/wallet/vc_wallet/cr_cat_wallet.py @@ -311,9 +311,6 @@ def puzzle_hash_for_pk(self, pubkey: G1Element) -> bytes32: # pragma: no cover async def get_new_cat_puzzle_hash(self) -> bytes32: # pragma: no cover raise NotImplementedError("get_new_cat_puzzle_hash is a legacy method and is not available on CR-CAT wallets") - async def sign(self, spend_bundle: SpendBundle) -> SpendBundle: # pragma: no cover - raise NotImplementedError("get_new_cat_puzzle_hash is a legacy method and is not available on CR-CAT wallets") - async def inner_puzzle_for_cat_puzhash(self, cat_hash: bytes32) -> Program: # pragma: no cover raise NotImplementedError( "inner_puzzle_for_cat_puzhash is a legacy method and is not available on CR-CAT wallets" @@ -652,7 +649,7 @@ async def generate_signed_transaction( ) ) - unsigned_spend_bundle, other_txs = await self._generate_unsigned_spendbundle( + spend_bundle, other_txs = await self._generate_unsigned_spendbundle( payments, tx_config, fee, @@ -662,10 +659,6 @@ async def generate_signed_transaction( add_authorizations_to_cr_cats=add_authorizations_to_cr_cats, ) - signed_spend_bundle: SpendBundle = await self.wallet_state_manager.sign_transaction( - unsigned_spend_bundle.coin_spends - ) - 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 = [ @@ -677,15 +670,15 @@ async def generate_signed_transaction( fee_amount=fee, confirmed=False, sent=uint32(0), - spend_bundle=signed_spend_bundle if i == 0 else None, - additions=list(set(signed_spend_bundle.additions()) - other_tx_additions) if i == 0 else [], - removals=list(set(signed_spend_bundle.removals()) - other_tx_removals) if i == 0 else [], + 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=signed_spend_bundle.name(), - memos=list(compute_memos(signed_spend_bundle).items()), + name=spend_bundle.name(), + memos=list(compute_memos(spend_bundle).items()), valid_times=parse_timelock_info(extra_conditions), ) for i, payment in enumerate(payments) diff --git a/chia/wallet/vc_wallet/vc_wallet.py b/chia/wallet/vc_wallet/vc_wallet.py index d9ba2505c2e4..932e27f0396e 100644 --- a/chia/wallet/vc_wallet/vc_wallet.py +++ b/chia/wallet/vc_wallet/vc_wallet.py @@ -201,7 +201,7 @@ async def launch_new_vc( solution = solution_for_conditions(dpuz.rest()) original_puzzle = await self.standard_wallet.puzzle_for_puzzle_hash(original_coin.puzzle_hash) coin_spends.append(make_spend(original_coin, original_puzzle, solution)) - spend_bundle = await self.wallet_state_manager.sign_transaction(coin_spends) + spend_bundle = SpendBundle(coin_spends, G2Element()) now = uint64(int(time.time())) add_list: List[Coin] = list(spend_bundle.additions()) rem_list: List[Coin] = list(spend_bundle.removals()) @@ -299,7 +299,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 = [await self.wallet_state_manager.sign_transaction([coin_spend])] + spend_bundles = [SpendBundle([coin_spend], G2Element())] tx_list: List[TransactionRecord] = [] if did_announcement is not None: # Need to spend DID diff --git a/chia/wallet/wallet.py b/chia/wallet/wallet.py index 462695e06088..9e7ae3c7eb12 100644 --- a/chia/wallet/wallet.py +++ b/chia/wallet/wallet.py @@ -4,7 +4,7 @@ import time from typing import TYPE_CHECKING, Any, ClassVar, Dict, List, Optional, Set, Tuple, cast -from chia_rs import AugSchemeMPL, G1Element, G2Element +from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey from typing_extensions import Unpack from chia.types.blockchain_format.coin import Coin @@ -20,16 +20,34 @@ from chia.wallet.coin_selection import select_coins from chia.wallet.conditions import AssertCoinAnnouncement, Condition, CreateCoinAnnouncement, parse_timelock_info from chia.wallet.derivation_record import DerivationRecord +from chia.wallet.derive_keys import ( + MAX_POOL_WALLETS, + _derive_path, + _derive_path_unhardened, + master_sk_to_singleton_owner_sk, +) from chia.wallet.payment import Payment from chia.wallet.puzzles.clawback.metadata import ClawbackMetadata from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( DEFAULT_HIDDEN_PUZZLE_HASH, + calculate_synthetic_offset, calculate_synthetic_secret_key, puzzle_for_pk, puzzle_hash_for_pk, + puzzle_hash_for_synthetic_public_key, solution_for_conditions, ) from chia.wallet.puzzles.puzzle_utils import make_create_coin_condition, make_reserve_fee_condition +from chia.wallet.signer_protocol import ( + PathHint, + Signature, + SignedTransaction, + SigningInstructions, + SigningResponse, + Spend, + SumHint, + TransactionInfo, +) from chia.wallet.transaction_record import TransactionRecord from chia.wallet.util.compute_memos import compute_memos from chia.wallet.util.puzzle_decorator import PuzzleDecoratorManager @@ -411,8 +429,7 @@ async def generate_signed_transaction( extra_conditions=extra_conditions, ) assert len(transaction) > 0 - self.log.info("About to sign a transaction: %s", transaction) - spend_bundle: SpendBundle = await self.wallet_state_manager.sign_transaction(transaction) + spend_bundle: SpendBundle = SpendBundle(transaction, G2Element()) now = uint64(int(time.time())) add_list: List[Coin] = list(spend_bundle.additions()) @@ -495,3 +512,184 @@ async def match_hinted_coin(self, coin: Coin, hint: bytes32) -> bool: if wallet_identifier is not None and wallet_identifier.id == self.id(): return True return False + + async def sum_hint_for_pubkey(self, pk: bytes) -> Optional[SumHint]: + pk_parsed: G1Element = G1Element.from_bytes(pk) + dr: Optional[DerivationRecord] = await self.wallet_state_manager.puzzle_store.record_for_puzzle_hash( + puzzle_hash_for_synthetic_public_key(pk_parsed) + ) + if dr is None: + return None + return SumHint( + [dr.pubkey.get_fingerprint().to_bytes(4, "big")], + calculate_synthetic_offset(dr.pubkey, DEFAULT_HIDDEN_PUZZLE_HASH).to_bytes(32, "big"), + pk, + ) + + async def path_hint_for_pubkey(self, pk: bytes) -> Optional[PathHint]: + pk_parsed: G1Element = G1Element.from_bytes(pk) + index: Optional[uint32] = await self.wallet_state_manager.puzzle_store.index_for_pubkey(pk_parsed) + if index is None: + index = await self.wallet_state_manager.puzzle_store.index_for_puzzle_hash( + puzzle_hash_for_synthetic_public_key(pk_parsed) + ) + root_pubkey: bytes = self.wallet_state_manager.root_pubkey.get_fingerprint().to_bytes(4, "big") + if index is None: + # Pool wallet may have a secret key here + if self.wallet_state_manager.private_key is not None: + for pool_wallet_index in range(MAX_POOL_WALLETS): + try_owner_sk = master_sk_to_singleton_owner_sk( + self.wallet_state_manager.private_key, uint32(pool_wallet_index) + ) + if try_owner_sk.get_g1() == pk_parsed: + return PathHint( + root_pubkey, + [uint64(12381), uint64(8444), uint64(5), uint64(pool_wallet_index)], + ) + return None + return PathHint( + root_pubkey, + [uint64(12381), uint64(8444), uint64(2), uint64(index)], + ) + + async def execute_signing_instructions( + self, signing_instructions: SigningInstructions, partial_allowed: bool = False + ) -> List[SigningResponse]: + root_pubkey: G1Element = self.wallet_state_manager.root_pubkey + pk_lookup: Dict[int, G1Element] = ( + {root_pubkey.get_fingerprint(): root_pubkey} if self.wallet_state_manager.private_key is not None else {} + ) + sk_lookup: Dict[int, PrivateKey] = ( + {root_pubkey.get_fingerprint(): self.wallet_state_manager.get_master_private_key()} + if self.wallet_state_manager.private_key is not None + else {} + ) + aggregate_responses_at_end: bool = True + responses: List[SigningResponse] = [] + + # TODO: expand path hints and sum hints recursively (a sum hint can give a new key to path hint) + # Next, expand our pubkey set with path hints + if self.wallet_state_manager.private_key is not None: + for path_hint in signing_instructions.key_hints.path_hints: + if int.from_bytes(path_hint.root_fingerprint, "big") != root_pubkey.get_fingerprint(): + if not partial_allowed: + raise ValueError(f"No root pubkey for fingerprint {root_pubkey.get_fingerprint()}") + else: + continue + else: + path = [int(step) for step in path_hint.path] + derive_child_sk = _derive_path(self.wallet_state_manager.get_master_private_key(), path) + derive_child_sk_unhardened = _derive_path_unhardened( + self.wallet_state_manager.get_master_private_key(), path + ) + derive_child_pk = derive_child_sk.get_g1() + derive_child_pk_unhardened = derive_child_sk_unhardened.get_g1() + pk_lookup[derive_child_pk.get_fingerprint()] = derive_child_pk + pk_lookup[derive_child_pk_unhardened.get_fingerprint()] = derive_child_pk_unhardened + sk_lookup[derive_child_pk.get_fingerprint()] = derive_child_sk + sk_lookup[derive_child_pk_unhardened.get_fingerprint()] = derive_child_sk_unhardened + + # Next, expand our pubkey set with sum hints + sum_hint_lookup: Dict[int, List[int]] = {} + for sum_hint in signing_instructions.key_hints.sum_hints: + fingerprints_we_have: List[int] = [] + for fingerprint in sum_hint.fingerprints: + fingerprint_as_int = int.from_bytes(fingerprint, "big") + if fingerprint_as_int not in pk_lookup: + if not partial_allowed: + raise ValueError( + "No pubkey found (or path hinted to) for " + f"fingerprint {int.from_bytes(fingerprint, 'big')}" + ) + else: + aggregate_responses_at_end = False + else: + fingerprints_we_have.append(fingerprint_as_int) + + # Add any synthetic offsets as keys we "have" + offset_sk = PrivateKey.from_bytes(sum_hint.synthetic_offset) + offset_pk = offset_sk.get_g1() + pk_lookup[offset_pk.get_fingerprint()] = offset_pk + sk_lookup[offset_pk.get_fingerprint()] = offset_sk + final_pubkey: G1Element = G1Element.from_bytes(sum_hint.final_pubkey) + final_fingerprint: int = final_pubkey.get_fingerprint() + pk_lookup[final_fingerprint] = final_pubkey + sum_hint_lookup[final_fingerprint] = [*fingerprints_we_have, offset_pk.get_fingerprint()] + + for target in signing_instructions.targets: + pk_fingerprint: int = int.from_bytes(target.fingerprint, "big") + if pk_fingerprint not in sk_lookup and pk_fingerprint not in sum_hint_lookup: + if not partial_allowed: + raise ValueError(f"Pubkey {pk_fingerprint} not found (or path/sum hinted to)") + else: + aggregate_responses_at_end = False + continue + elif pk_fingerprint in sk_lookup: + responses.append( + SigningResponse( + bytes(AugSchemeMPL.sign(sk_lookup[pk_fingerprint], target.message)), + target.hook, + ) + ) + else: # Implicit if pk_fingerprint in sum_hint_lookup + signatures: List[G2Element] = [] + for partial_fingerprint in sum_hint_lookup[pk_fingerprint]: + signatures.append( + AugSchemeMPL.sign(sk_lookup[partial_fingerprint], target.message, pk_lookup[pk_fingerprint]) + ) + if partial_allowed: + # In multisig scenarios, we return everything as a component signature + for sig in signatures: + responses.append( + SigningResponse( + bytes(sig), + target.hook, + ) + ) + else: + # In the scenario where we are the only signer, we can collapse many responses into one + responses.append( + SigningResponse( + bytes(AugSchemeMPL.aggregate(signatures)), + target.hook, + ) + ) + + # If we have the full set of signing responses for the instructions, aggregate them as much as possible + if aggregate_responses_at_end: + new_responses: List[SigningResponse] = [] + grouped_responses: Dict[bytes32, List[SigningResponse]] = {} + for response in responses: + grouped_responses.setdefault(response.hook, []) + grouped_responses[response.hook].append(response) + for hook, group in grouped_responses.items(): + new_responses.append( + SigningResponse( + bytes(AugSchemeMPL.aggregate([G2Element.from_bytes(res.signature) for res in group])), + hook, + ) + ) + responses = new_responses + + return responses + + async def apply_signatures( + self, spends: List[Spend], signing_responses: List[SigningResponse] + ) -> SignedTransaction: + signing_responses_set = set(signing_responses) + return SignedTransaction( + TransactionInfo(spends), + [ + Signature( + "bls_12381_aug_scheme", + bytes( + AugSchemeMPL.aggregate( + [ + G2Element.from_bytes(signing_response.signature) + for signing_response in signing_responses_set + ] + ) + ), + ) + ], + ) diff --git a/chia/wallet/wallet_node.py b/chia/wallet/wallet_node.py index a7a2f94fc1e5..40095451cd38 100644 --- a/chia/wallet/wallet_node.py +++ b/chia/wallet/wallet_node.py @@ -10,7 +10,21 @@ import time import traceback from pathlib import Path -from typing import TYPE_CHECKING, Any, AsyncIterator, ClassVar, Dict, List, Optional, Set, Tuple, Union, cast +from typing import ( + TYPE_CHECKING, + Any, + AsyncIterator, + ClassVar, + Dict, + List, + Literal, + Optional, + Set, + Tuple, + Union, + cast, + overload, +) import aiosqlite from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey @@ -214,11 +228,33 @@ def rollback_request_caches(self, reorg_height: int) -> None: for cache in self.peer_caches.values(): cache.clear_after_height(reorg_height) - async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[PrivateKey]: + @overload + async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[G1Element]: ... + + @overload + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: Literal[True] + ) -> Optional[PrivateKey]: ... + + @overload + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: Literal[False] + ) -> Optional[G1Element]: ... + + @overload + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: bool + ) -> Optional[Union[PrivateKey, G1Element]]: ... + + async def get_key_for_fingerprint( + self, fingerprint: Optional[int], private: bool = False + ) -> Optional[Union[PrivateKey, G1Element]]: try: keychain_proxy = await self.ensure_keychain_proxy() - # Returns first private key if fingerprint is None - key = await keychain_proxy.get_key_for_fingerprint(fingerprint) + # Returns first key if fingerprint is None + key: Optional[Union[PrivateKey, G1Element]] = await keychain_proxy.get_key_for_fingerprint( + fingerprint, private=private + ) except KeychainIsEmpty: self.log.warning("No keys present. Create keys with the UI, or with the 'chia keys' program.") return None @@ -235,18 +271,22 @@ async def get_key_for_fingerprint(self, fingerprint: Optional[int]) -> Optional[ return key - async def get_private_key(self, fingerprint: Optional[int]) -> Optional[PrivateKey]: + async def get_key(self, fingerprint: Optional[int], private: bool = True) -> Optional[Union[PrivateKey, G1Element]]: """ Attempt to get the private key for the given fingerprint. If the fingerprint is None, get_key_for_fingerprint() will return the first private key. Similarly, if a key isn't returned for the provided fingerprint, the first key will be returned. """ - key: Optional[PrivateKey] = await self.get_key_for_fingerprint(fingerprint) + key: Optional[Union[PrivateKey, G1Element]] = await self.get_key_for_fingerprint(fingerprint, private=private) if key is None and fingerprint is not None: - key = await self.get_key_for_fingerprint(None) + key = await self.get_key_for_fingerprint(None, private=private) if key is not None: - self.log.info(f"Using first key found (fingerprint: {key.get_g1().get_fingerprint()})") + if isinstance(key, PrivateKey): + fp = key.get_g1().get_fingerprint() + else: + fp = key.get_fingerprint() + self.log.info(f"Using first key found (fingerprint: {fp})") return key @@ -372,13 +412,19 @@ async def _start_with_fingerprint( multiprocessing_context = multiprocessing.get_context(method=multiprocessing_start_method) self._weight_proof_handler = WalletWeightProofHandler(self.constants, multiprocessing_context) self.synced_peers = set() - private_key = await self.get_private_key(fingerprint) + private_key = await self.get_key(fingerprint, private=True) if private_key is None: + public_key = await self.get_key(fingerprint, private=False) + else: + assert isinstance(private_key, PrivateKey) + public_key = private_key.get_g1() + if public_key is None: self.log_out() return False + assert isinstance(public_key, G1Element) # override with private key fetched in case it's different from what was passed if fingerprint is None: - fingerprint = private_key.get_g1().get_fingerprint() + fingerprint = public_key.get_fingerprint() if self.config.get("enable_profiler", False): if sys.getprofile() is not None: self.log.warning("not enabling profiler, getprofile() is already set") @@ -393,6 +439,7 @@ async def _start_with_fingerprint( if self.config.get("reset_sync_for_fingerprint") == fingerprint: await self.reset_sync_db(path, fingerprint) + assert private_key is None or isinstance(private_key, PrivateKey) self._wallet_state_manager = await WalletStateManager.create( private_key, self.config, @@ -401,6 +448,7 @@ async def _start_with_fingerprint( self.server, self.root_path, self, + public_key, ) if self.state_changed_callback is not None: @@ -414,7 +462,7 @@ async def _start_with_fingerprint( self._retry_failed_states_task = asyncio.create_task(self._retry_failed_states()) self.sync_event = asyncio.Event() - self.log_in(private_key) + self.log_in(fingerprint) self.wallet_state_manager.state_changed("sync_changed") # Populate the balance caches for all wallets @@ -614,8 +662,8 @@ async def _process_new_subscriptions(self) -> None: if peer is not None: await peer.close(9999) - def log_in(self, sk: PrivateKey) -> None: - self.logged_in_fingerprint = sk.get_g1().get_fingerprint() + def log_in(self, fingerprint: int) -> None: + self.logged_in_fingerprint = fingerprint self.logged_in = True self.log.info(f"Wallet is logged in using key with fingerprint: {self.logged_in_fingerprint}") try: diff --git a/chia/wallet/wallet_state_manager.py b/chia/wallet/wallet_state_manager.py index ff747b6b4a9d..a0b28741da70 100644 --- a/chia/wallet/wallet_state_manager.py +++ b/chia/wallet/wallet_state_manager.py @@ -25,7 +25,7 @@ ) import aiosqlite -from chia_rs import G1Element, G2Element, PrivateKey +from chia_rs import AugSchemeMPL, G1Element, G2Element, PrivateKey from chia.consensus.block_rewards import calculate_base_farmer_reward, calculate_pool_reward from chia.consensus.coinbase import farmer_parent_id, pool_parent_id @@ -51,6 +51,7 @@ from chia.types.mempool_inclusion_status import MempoolInclusionStatus from chia.types.spend_bundle import SpendBundle from chia.util.bech32m import encode_puzzle_hash +from chia.util.condition_tools import conditions_dict_for_solution, pkm_pairs_for_conditions_dict from chia.util.db_synchronous import db_synchronous_on from chia.util.db_wrapper import DBWrapper2 from chia.util.errors import Err @@ -85,11 +86,12 @@ from chia.wallet.derivation_record import DerivationRecord from chia.wallet.derive_keys import ( _derive_path, - _derive_path_unhardened, + _derive_pk_unhardened, + master_pk_to_wallet_pk_unhardened, + master_pk_to_wallet_pk_unhardened_intermediate, master_sk_to_wallet_sk, master_sk_to_wallet_sk_intermediate, master_sk_to_wallet_sk_unhardened, - master_sk_to_wallet_sk_unhardened_intermediate, ) from chia.wallet.did_wallet.did_info import DIDCoinData from chia.wallet.did_wallet.did_wallet import DIDWallet @@ -104,14 +106,21 @@ from chia.wallet.puzzle_drivers import PuzzleInfo from chia.wallet.puzzles.clawback.drivers import generate_clawback_spend_bundle, match_clawback_puzzle from chia.wallet.puzzles.clawback.metadata import ClawbackMetadata, ClawbackVersion -from chia.wallet.puzzles.p2_delegated_puzzle_or_hidden_puzzle import ( - DEFAULT_HIDDEN_PUZZLE_HASH, - calculate_synthetic_secret_key, - puzzle_hash_for_synthetic_public_key, +from chia.wallet.signer_protocol import ( + KeyHints, + PathHint, + SignedTransaction, + SigningInstructions, + SigningResponse, + SigningTarget, + Spend, + SumHint, + TransactionInfo, + UnsignedTransaction, ) -from chia.wallet.sign_coin_spends import sign_coin_spends from chia.wallet.singleton import create_singleton_puzzle, get_inner_puzzle_from_singleton, get_singleton_id_from_puzzle from chia.wallet.trade_manager import TradeManager +from chia.wallet.trading.offer import Offer from chia.wallet.trading.trade_status import TradeStatus from chia.wallet.transaction_record import TransactionRecord from chia.wallet.uncurried_puzzle import uncurry_puzzle @@ -183,7 +192,8 @@ class WalletStateManager: main_wallet: Wallet wallets: Dict[uint32, WalletProtocol[Any]] - private_key: PrivateKey + private_key: Optional[PrivateKey] + root_pubkey: G1Element trade_manager: TradeManager notification_manager: NotificationManager @@ -204,13 +214,14 @@ class WalletStateManager: @staticmethod async def create( - private_key: PrivateKey, + private_key: Optional[PrivateKey], config: Dict[str, Any], db_path: Path, constants: ConsensusConstants, server: ChiaServer, root_path: Path, wallet_node: WalletNode, + root_pubkey: Optional[G1Element] = None, ) -> WalletStateManager: self = WalletStateManager() @@ -221,7 +232,6 @@ async def create( self.log = logging.getLogger(__name__) self.lock = asyncio.Lock() self.log.debug(f"Starting in db path: {db_path}") - fingerprint = private_key.get_g1().get_fingerprint() sql_log_path: Optional[Path] = None if self.config.get("log_sqlite_cmds", False): sql_log_path = path_from_root(self.root_path, "log/wallet_sql.log") @@ -260,13 +270,26 @@ async def create( self.state_changed_callback = None self.pending_tx_callback = None self.db_path = db_path - puzzle_decorators = self.config.get("puzzle_decorators", {}).get(fingerprint, []) - self.decorator_manager = PuzzleDecoratorManager.create(puzzle_decorators) main_wallet_info = await self.user_store.get_wallet_by_id(1) assert main_wallet_info is not None self.private_key = private_key + if private_key is None: # pragma: no cover + if root_pubkey is None: + raise ValueError("WalletStateManager requires either a root private key or root public key") + else: + self.root_pubkey = root_pubkey + else: + calculated_root_public_key: G1Element = private_key.get_g1() + if root_pubkey is not None: + assert root_pubkey == calculated_root_public_key + self.root_pubkey = calculated_root_public_key + + fingerprint = self.root_pubkey.get_fingerprint() + puzzle_decorators = self.config.get("puzzle_decorators", {}).get(fingerprint, []) + self.decorator_manager = PuzzleDecoratorManager.create(puzzle_decorators) + self.main_wallet = await Wallet.create(self, main_wallet_info) self.wallets = {main_wallet_info.id: self.main_wallet} @@ -341,34 +364,21 @@ async def create( return self def get_public_key_unhardened(self, index: uint32) -> G1Element: - return master_sk_to_wallet_sk_unhardened(self.private_key, index).get_g1() + return master_pk_to_wallet_pk_unhardened(self.root_pubkey, index) async def get_private_key(self, puzzle_hash: bytes32) -> PrivateKey: record = await self.puzzle_store.record_for_puzzle_hash(puzzle_hash) if record is None: raise ValueError(f"No key for puzzle hash: {puzzle_hash.hex()}") if record.hardened: - return master_sk_to_wallet_sk(self.private_key, record.index) - return master_sk_to_wallet_sk_unhardened(self.private_key, record.index) + return master_sk_to_wallet_sk(self.get_master_private_key(), record.index) + return master_sk_to_wallet_sk_unhardened(self.get_master_private_key(), record.index) - async def get_synthetic_private_key_for_puzzle_hash(self, puzzle_hash: bytes32) -> Optional[PrivateKey]: - record = await self.puzzle_store.record_for_puzzle_hash(puzzle_hash) - if record is None: - return None - if record.hardened: - base_key = master_sk_to_wallet_sk(self.private_key, record.index) - else: - base_key = master_sk_to_wallet_sk_unhardened(self.private_key, record.index) - - return calculate_synthetic_secret_key(base_key, DEFAULT_HIDDEN_PUZZLE_HASH) + def get_master_private_key(self) -> PrivateKey: + if self.private_key is None: # pragma: no cover + raise ValueError("Wallet is currently in observer mode and access to private key is denied") - async def get_private_key_for_pubkey(self, pubkey: G1Element) -> Optional[PrivateKey]: - record = await self.puzzle_store.record_for_pubkey(pubkey) - if record is None: - return None - if record.hardened: - return master_sk_to_wallet_sk(self.private_key, record.index) - return master_sk_to_wallet_sk_unhardened(self.private_key, record.index) + return self.private_key def get_wallet(self, id: uint32, required_type: Type[TWalletType]) -> TWalletType: wallet = self.wallets[id] @@ -434,36 +444,32 @@ async def create_more_puzzle_hashes( f"Creating puzzle hashes from {start_index} to {last_index - 1} for wallet_id: {wallet_id}" ) self.log.info(f"Start: {creating_msg}") - intermediate_sk = master_sk_to_wallet_sk_intermediate(self.private_key) - intermediate_sk_un = master_sk_to_wallet_sk_unhardened_intermediate(self.private_key) + if self.private_key is not None: + intermediate_sk = master_sk_to_wallet_sk_intermediate(self.private_key) + intermediate_pk_un = master_pk_to_wallet_pk_unhardened_intermediate(self.root_pubkey) for index in range(start_index, last_index): if target_wallet.type() == WalletType.POOLING_WALLET: continue - # Hardened - pubkey: G1Element = _derive_path(intermediate_sk, [index]).get_g1() - puzzlehash: Optional[bytes32] = target_wallet.puzzle_hash_for_pk(pubkey) - if puzzlehash is None: - self.log.error(f"Unable to create puzzles with wallet {target_wallet}") - break - self.log.debug(f"Puzzle at index {index} wallet ID {wallet_id} puzzle hash {puzzlehash.hex()}") - new_paths = True - derivation_paths.append( - DerivationRecord( - uint32(index), - puzzlehash, - pubkey, - target_wallet.type(), - uint32(target_wallet.id()), - True, + if self.private_key is not None: + # Hardened + pubkey: G1Element = _derive_path(intermediate_sk, [index]).get_g1() + puzzlehash: bytes32 = target_wallet.puzzle_hash_for_pk(pubkey) + self.log.debug(f"Puzzle at index {index} wallet ID {wallet_id} puzzle hash {puzzlehash.hex()}") + new_paths = True + derivation_paths.append( + DerivationRecord( + uint32(index), + puzzlehash, + pubkey, + target_wallet.type(), + uint32(target_wallet.id()), + True, + ) ) - ) # Unhardened - pubkey_unhardened: G1Element = _derive_path_unhardened(intermediate_sk_un, [index]).get_g1() - puzzlehash_unhardened: Optional[bytes32] = target_wallet.puzzle_hash_for_pk(pubkey_unhardened) - if puzzlehash_unhardened is None: - self.log.error(f"Unable to create puzzles with wallet {target_wallet}") - break + pubkey_unhardened: G1Element = _derive_pk_unhardened(intermediate_pk_un, [index]) + puzzlehash_unhardened: bytes32 = target_wallet.puzzle_hash_for_pk(pubkey_unhardened) self.log.debug( f"Puzzle at index {index} wallet ID {wallet_id} puzzle hash {puzzlehash_unhardened.hex()}" ) @@ -981,7 +987,7 @@ async def spend_clawback_coins( self.log.error(f"Failed to create clawback spend bundle for {coin.name().hex()}: {e}") if len(coin_spends) == 0: return [] - spend_bundle: SpendBundle = await self.sign_transaction(coin_spends) + spend_bundle: SpendBundle = SpendBundle(coin_spends, G2Element()) tx_list: List[TransactionRecord] = [] if fee > 0: chia_tx = await self.main_wallet.create_tandem_xch_tx( @@ -2229,12 +2235,18 @@ async def coin_added( await self.create_more_puzzle_hashes() async def add_pending_transactions( - self, tx_records: List[TransactionRecord], merge_spends: bool = True + self, + tx_records: List[TransactionRecord], + merge_spends: bool = True, + sign: Optional[bool] = None, + additional_signing_responses: List[SigningResponse] = [], ) -> List[TransactionRecord]: """ Add a list of transactions to be submitted to the full node. Aggregates the `spend_bundle` property for each transaction onto the first transaction in the list. """ + if sign is None: + sign = self.config.get("auto_sign_txs", True) agg_spend: SpendBundle = SpendBundle.aggregate( [tx.spend_bundle for tx in tx_records if tx.spend_bundle is not None] ) @@ -2248,6 +2260,12 @@ async def add_pending_transactions( ) for i, tx in enumerate(tx_records) ] + if sign: + tx_records, _ = await self.sign_transactions( + tx_records, + additional_signing_responses, + additional_signing_responses != [], + ) all_coins_names = [] async with self.db_wrapper.writer_maybe_transaction(): for tx_record in tx_records: @@ -2553,12 +2571,151 @@ async def get_or_create_vc_wallet(self) -> VCWallet: return vc_wallet - async def sign_transaction(self, coin_spends: List[CoinSpend]) -> SpendBundle: - return await sign_coin_spends( - coin_spends, - self.get_private_key_for_pubkey, - self.get_synthetic_private_key_for_puzzle_hash, - self.constants.AGG_SIG_ME_ADDITIONAL_DATA, - self.constants.MAX_BLOCK_COST_CLVM, - [puzzle_hash_for_synthetic_public_key], + async def sum_hint_for_pubkey(self, pk: bytes) -> Optional[SumHint]: + return await self.main_wallet.sum_hint_for_pubkey(pk) + + async def path_hint_for_pubkey(self, pk: bytes) -> Optional[PathHint]: + return await self.main_wallet.path_hint_for_pubkey(pk) + + async def key_hints_for_pubkeys(self, pks: List[bytes]) -> KeyHints: + return KeyHints( + [sum_hint for pk in pks for sum_hint in (await self.sum_hint_for_pubkey(pk),) if sum_hint is not None], + [path_hint for pk in pks for path_hint in (await self.path_hint_for_pubkey(pk),) if path_hint is not None], + ) + + async def gather_signing_info(self, coin_spends: List[Spend]) -> SigningInstructions: + pks: List[bytes] = [] + signing_targets: List[SigningTarget] = [] + for coin_spend in coin_spends: + _coin_spend = coin_spend.as_coin_spend() + # Get AGG_SIG conditions + conditions_dict = conditions_dict_for_solution( + _coin_spend.puzzle_reveal.to_program(), + _coin_spend.solution.to_program(), + self.constants.MAX_BLOCK_COST_CLVM, + ) + # Create signature + for pk_bytes, msg in pkm_pairs_for_conditions_dict( + conditions_dict, _coin_spend.coin, self.constants.AGG_SIG_ME_ADDITIONAL_DATA + ): + pks.append(pk_bytes) + fingerprint: bytes = G1Element.from_bytes(pk_bytes).get_fingerprint().to_bytes(4, "big") + signing_targets.append(SigningTarget(fingerprint, msg, std_hash(pk_bytes + msg))) + + return SigningInstructions( + await self.key_hints_for_pubkeys(pks), + signing_targets, + ) + + async def gather_signing_info_for_bundles(self, bundles: List[SpendBundle]) -> List[UnsignedTransaction]: + utxs: List[UnsignedTransaction] = [] + for bundle in bundles: + signer_protocol_spends: List[Spend] = [Spend.from_coin_spend(spend) for spend in bundle.coin_spends] + utxs.append( + UnsignedTransaction( + TransactionInfo(signer_protocol_spends), await self.gather_signing_info(signer_protocol_spends) + ) + ) + + return utxs + + async def gather_signing_info_for_txs(self, txs: List[TransactionRecord]) -> List[UnsignedTransaction]: + return await self.gather_signing_info_for_bundles( + [tx.spend_bundle for tx in txs if tx.spend_bundle is not None] + ) + + async def gather_signing_info_for_trades(self, offers: List[Offer]) -> List[UnsignedTransaction]: + return await self.gather_signing_info_for_bundles([offer._bundle for offer in offers]) + + async def execute_signing_instructions( + self, signing_instructions: SigningInstructions, partial_allowed: bool = False + ) -> List[SigningResponse]: + return await self.main_wallet.execute_signing_instructions(signing_instructions, partial_allowed) + + async def apply_signatures( + self, spends: List[Spend], signing_responses: List[SigningResponse] + ) -> SignedTransaction: + return await self.main_wallet.apply_signatures(spends, signing_responses) + + def signed_tx_to_spendbundle(self, signed_tx: SignedTransaction) -> SpendBundle: + if len([_ for _ in signed_tx.signatures if _.type != "bls_12381_aug_scheme"]) > 0: + raise ValueError("Unable to handle signatures that are not bls_12381_aug_scheme") # pragma: no cover + return SpendBundle( + [spend.as_coin_spend() for spend in signed_tx.transaction_info.spends], + AugSchemeMPL.aggregate([G2Element.from_bytes(sig.signature) for sig in signed_tx.signatures]), + ) + + async def sign_transactions( + self, + tx_records: List[TransactionRecord], + additional_signing_responses: List[SigningResponse] = [], + partial_allowed: bool = False, + ) -> Tuple[List[TransactionRecord], List[SigningResponse]]: + unsigned_txs: List[UnsignedTransaction] = await self.gather_signing_info_for_txs(tx_records) + new_txs: List[TransactionRecord] = [] + all_signing_responses = additional_signing_responses.copy() + for unsigned_tx, tx in zip( + unsigned_txs, [tx_record for tx_record in tx_records if tx_record.spend_bundle is not None] + ): + signing_responses: List[SigningResponse] = await self.execute_signing_instructions( + unsigned_tx.signing_instructions, partial_allowed=partial_allowed + ) + all_signing_responses.extend(signing_responses) + new_bundle = self.signed_tx_to_spendbundle( + await self.apply_signatures( + unsigned_tx.transaction_info.spends, + [*additional_signing_responses, *signing_responses], + ) + ) + new_txs.append(dataclasses.replace(tx, spend_bundle=new_bundle, name=new_bundle.name())) + new_txs.extend([tx_record for tx_record in tx_records if tx_record.spend_bundle is None]) + return new_txs, all_signing_responses + + async def sign_offers( + self, + offers: List[Offer], + additional_signing_responses: List[SigningResponse] = [], + partial_allowed: bool = False, + ) -> Tuple[List[Offer], List[SigningResponse]]: + unsigned_txs: List[UnsignedTransaction] = await self.gather_signing_info_for_trades(offers) + new_offers: List[Offer] = [] + all_signing_responses = additional_signing_responses.copy() + for unsigned_tx, offer in zip(unsigned_txs, [offer for offer in offers]): + signing_responses: List[SigningResponse] = await self.execute_signing_instructions( + unsigned_tx.signing_instructions, partial_allowed=partial_allowed + ) + all_signing_responses.extend(signing_responses) + new_bundle = self.signed_tx_to_spendbundle( + await self.apply_signatures( + unsigned_tx.transaction_info.spends, + [*additional_signing_responses, *signing_responses], + ) + ) + new_offers.append(Offer(offer.requested_payments, new_bundle, offer.driver_dict)) + return new_offers, all_signing_responses + + async def sign_bundle( + self, + coin_spends: List[CoinSpend], + additional_signing_responses: List[SigningResponse] = [], + partial_allowed: bool = False, + ) -> Tuple[SpendBundle, List[SigningResponse]]: + [unsigned_tx] = await self.gather_signing_info_for_bundles([SpendBundle(coin_spends, G2Element())]) + signing_responses: List[SigningResponse] = await self.execute_signing_instructions( + unsigned_tx.signing_instructions, partial_allowed=partial_allowed ) + return ( + self.signed_tx_to_spendbundle( + await self.apply_signatures( + unsigned_tx.transaction_info.spends, + [*additional_signing_responses, *signing_responses], + ) + ), + signing_responses, + ) + + async def submit_transactions(self, signed_txs: List[SignedTransaction]) -> List[bytes32]: + bundles: List[SpendBundle] = [self.signed_tx_to_spendbundle(tx) for tx in signed_txs] + for bundle in bundles: + await self.wallet_node.push_tx(bundle) + return [bundle.name() for bundle in bundles] diff --git a/install-gui.sh b/install-gui.sh index c0d087ddfe27..5099d6d8c87c 100755 --- a/install-gui.sh +++ b/install-gui.sh @@ -4,7 +4,10 @@ set -o errexit export NODE_OPTIONS="--max-old-space-size=3000" -SCRIPT_DIR=$(cd -- "$(dirname -- "$0")"; pwd) +SCRIPT_DIR=$( + cd -- "$(dirname -- "$0")" + pwd +) if [ "${SCRIPT_DIR}" != "$(pwd)" ]; then echo "Please change working directory by the command below" @@ -26,14 +29,14 @@ fi # Allows overriding the branch or commit to build in chia-blockchain-gui SUBMODULE_BRANCH=$1 -nodejs_is_installed(){ +nodejs_is_installed() { if ! npm version >/dev/null 2>&1; then return 1 fi return 0 } -do_install_npm_locally(){ +do_install_npm_locally() { NODEJS_VERSION="$(node -v | cut -d'.' -f 1 | sed -e 's/^v//')" NPM_VERSION="$(npm -v | cut -d'.' -f 1)" @@ -89,7 +92,7 @@ do_install_npm_locally(){ # Work around for inconsistent `npm` exec path issue # https://github.com/Chia-Network/chia-blockchain/pull/10460#issuecomment-1054492495 -patch_inconsistent_npm_issue(){ +patch_inconsistent_npm_issue() { node_module_dir=$1 if [ ! -d "$node_module_dir" ]; then mkdir "$node_module_dir" @@ -110,11 +113,11 @@ if [ "$(uname)" = "Linux" ]; then # Debian/Ubuntu # Check if we are running a Raspberry PI 4 - if [ "$(uname -m)" = "aarch64" ] \ - && [ "$(uname -n)" = "raspberrypi" ]; then + if [ "$(uname -m)" = "aarch64" ] && + [ "$(uname -n)" = "raspberrypi" ]; then # Check if NodeJS & NPM is installed type npm >/dev/null 2>&1 || { - echo >&2 "Please install NODEJS&NPM manually" + echo >&2 "Please install NODEJS&NPM manually" } else if ! nodejs_is_installed; then @@ -124,7 +127,7 @@ if [ "$(uname)" = "Linux" ]; then fi do_install_npm_locally fi - elif type yum >/dev/null 2>&1 && [ ! -f "/etc/redhat-release" ] && [ ! -f "/etc/centos-release" ] && [ ! -f /etc/rocky-release ] && [ ! -f /etc/fedora-release ]; then + elif type yum >/dev/null 2>&1 && [ ! -f "/etc/redhat-release" ] && [ ! -f "/etc/centos-release" ] && [ ! -f /etc/rocky-release ] && [ ! -f /etc/fedora-release ]; then # AMZN 2 if ! nodejs_is_installed; then echo "Installing nodejs on Amazon Linux 2." @@ -191,8 +194,7 @@ if [ ! "$CI" ]; then git submodule update cd chia-blockchain-gui - if [ "$SUBMODULE_BRANCH" ]; - then + if [ "$SUBMODULE_BRANCH" ]; then git fetch --all git reset --hard "$SUBMODULE_BRANCH" echo "" diff --git a/install-timelord.sh b/install-timelord.sh index ea2e6c9e2f31..a9654ee7baca 100755 --- a/install-timelord.sh +++ b/install-timelord.sh @@ -15,20 +15,26 @@ usage() { INSTALL_PYTHON_DEV=1 -while getopts nh flag -do +while getopts nh flag; do case "${flag}" in - # development - n) INSTALL_PYTHON_DEV=;; - h) usage; exit 0;; - *) echo; usage; exit 1;; + # development + n) INSTALL_PYTHON_DEV= ;; + h) + usage + exit 0 + ;; + *) + echo + usage + exit 1 + ;; esac done if [ -z "$VIRTUAL_ENV" ]; then echo "This requires the chia python virtual environment." echo "Execute '. ./activate' before running." - exit 1 + exit 1 fi echo "Timelord requires CMake 3.14+ to compile vdf_client." @@ -47,92 +53,91 @@ THE_PATH=$(python -c 'import pkg_resources; print( pkg_resources.get_distributio CHIAVDF_VERSION=$(python -c 'import os; os.environ["CHIA_SKIP_SETUP"] = "1"; from setup import dependencies; t = [_ for _ in dependencies if _.startswith("chiavdf")][0]; print(t)') ubuntu_cmake_install() { - UBUNTU_PRE_2004=$(python -c 'import subprocess; id = subprocess.run(["lsb_release", "-is"], stdout=subprocess.PIPE); version = subprocess.run(["lsb_release", "-rs"], stdout=subprocess.PIPE); print(id.stdout.decode("ascii") == "Ubuntu\n" and float(version.stdout) < float(20.04))') - if [ "$UBUNTU_PRE_2004" = "True" ]; then - echo "Installing CMake with snap." - sudo apt-get install snapd -y - sudo apt-get remove --purge cmake -y - hash -r - sudo snap install cmake --classic - # shellcheck disable=SC1091 - . /etc/profile - else - echo "Ubuntu 20.04LTS and newer support CMake 3.16+" - sudo apt-get install cmake -y - fi + UBUNTU_PRE_2004=$(python -c 'import subprocess; id = subprocess.run(["lsb_release", "-is"], stdout=subprocess.PIPE); version = subprocess.run(["lsb_release", "-rs"], stdout=subprocess.PIPE); print(id.stdout.decode("ascii") == "Ubuntu\n" and float(version.stdout) < float(20.04))') + if [ "$UBUNTU_PRE_2004" = "True" ]; then + echo "Installing CMake with snap." + sudo apt-get install snapd -y + sudo apt-get remove --purge cmake -y + hash -r + sudo snap install cmake --classic + # shellcheck disable=SC1091 + . /etc/profile + else + echo "Ubuntu 20.04LTS and newer support CMake 3.16+" + sudo apt-get install cmake -y + fi } symlink_vdf_bench() { - if [ ! -e vdf_bench ] && [ -e venv/lib/"$1"/site-packages/vdf_bench ]; then - echo ln -s venv/lib/"$1"/site-packages/vdf_bench - ln -s venv/lib/"$1"/site-packages/vdf_bench . - elif [ ! -e venv/lib/"$1"/site-packages/vdf_bench ]; then - echo "ERROR: Could not find venv/lib/$1/site-packages/vdf_bench" - else - echo "./vdf_bench link exists." - fi + if [ ! -e vdf_bench ] && [ -e venv/lib/"$1"/site-packages/vdf_bench ]; then + echo ln -s venv/lib/"$1"/site-packages/vdf_bench + ln -s venv/lib/"$1"/site-packages/vdf_bench . + elif [ ! -e venv/lib/"$1"/site-packages/vdf_bench ]; then + echo "ERROR: Could not find venv/lib/$1/site-packages/vdf_bench" + else + echo "./vdf_bench link exists." + fi } if [ "$(uname)" = "Linux" ] && type apt-get; then - UBUNTU_DEBIAN=true - echo "Found Ubuntu/Debian." + UBUNTU_DEBIAN=true + echo "Found Ubuntu/Debian." elif [ "$(uname)" = "Linux" ] && type dnf || yum; then - RHEL_BASED=true - echo "Found RedHat." + RHEL_BASED=true + echo "Found RedHat." - if [ "$INSTALL_PYTHON_DEV" ]; then - yumcmd="sudo yum install $PYTHON_VERSION-devel gcc gcc-c++ gmp-devel libtool make autoconf automake openssl-devel libevent-devel boost-devel python3 cmake -y" - else - yumcmd="sudo yum install gcc gcc-c++ gmp-devel libtool make autoconf automake openssl-devel libevent-devel boost-devel python3 cmake -y" - fi + if [ "$INSTALL_PYTHON_DEV" ]; then + yumcmd="sudo yum install $PYTHON_VERSION-devel gcc gcc-c++ gmp-devel libtool make autoconf automake openssl-devel libevent-devel boost-devel python3 cmake -y" + else + yumcmd="sudo yum install gcc gcc-c++ gmp-devel libtool make autoconf automake openssl-devel libevent-devel boost-devel python3 cmake -y" + fi elif [ "$(uname)" = "Darwin" ]; then - MACOS=true - echo "Found MacOS." + MACOS=true + echo "Found MacOS." fi - if [ -e "$THE_PATH" ]; then - echo "$THE_PATH" - echo "vdf_client already exists, no action taken" + echo "$THE_PATH" + echo "vdf_client already exists, no action taken" else - if [ -e venv/bin/python ] && test "$UBUNTU_DEBIAN"; then - echo "Installing chiavdf dependencies on Ubuntu/Debian" - # If Ubuntu version is older than 20.04LTS then upgrade CMake - ubuntu_cmake_install - # Install remaining needed development tools - assumes venv and prior run of install.sh - echo "apt-get install libgmp-dev libboost-python-dev $PYTHON_DEV_DEPENDENCY libboost-system-dev build-essential -y" - sudo apt-get install libgmp-dev libboost-python-dev "$PYTHON_DEV_DEPENDENCY" libboost-system-dev build-essential -y - echo "Installing chiavdf from source on Ubuntu/Debian" - echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - symlink_vdf_bench "$PYTHON_VERSION" - elif [ -e venv/bin/python ] && test "$RHEL_BASED"; then - echo "Installing chiavdf dependencies on RedHat/CentOS/Fedora" - # Install remaining needed development tools - assumes venv and prior run of install.sh - echo "$yumcmd" - ${yumcmd} - echo "Installing chiavdf from source on RedHat/CentOS/Fedora" - echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - symlink_vdf_bench "$PYTHON_VERSION" - elif [ -e venv/bin/python ] && test "$MACOS"; then - echo "Installing chiavdf dependencies for MacOS." - brew install boost cmake gmp - echo "Installing chiavdf from source." - # User needs to provide required packages - echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - symlink_vdf_bench "$PYTHON_VERSION" - elif [ -e venv/bin/python ]; then - echo "Installing chiavdf from source." - # User needs to provide required packages - echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" - symlink_vdf_bench "$PYTHON_VERSION" - else - echo "No venv created yet, please run install.sh." - fi + if [ -e venv/bin/python ] && test "$UBUNTU_DEBIAN"; then + echo "Installing chiavdf dependencies on Ubuntu/Debian" + # If Ubuntu version is older than 20.04LTS then upgrade CMake + ubuntu_cmake_install + # Install remaining needed development tools - assumes venv and prior run of install.sh + echo "apt-get install libgmp-dev libboost-python-dev $PYTHON_DEV_DEPENDENCY libboost-system-dev build-essential -y" + sudo apt-get install libgmp-dev libboost-python-dev "$PYTHON_DEV_DEPENDENCY" libboost-system-dev build-essential -y + echo "Installing chiavdf from source on Ubuntu/Debian" + echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + symlink_vdf_bench "$PYTHON_VERSION" + elif [ -e venv/bin/python ] && test "$RHEL_BASED"; then + echo "Installing chiavdf dependencies on RedHat/CentOS/Fedora" + # Install remaining needed development tools - assumes venv and prior run of install.sh + echo "$yumcmd" + ${yumcmd} + echo "Installing chiavdf from source on RedHat/CentOS/Fedora" + echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + symlink_vdf_bench "$PYTHON_VERSION" + elif [ -e venv/bin/python ] && test "$MACOS"; then + echo "Installing chiavdf dependencies for MacOS." + brew install boost cmake gmp + echo "Installing chiavdf from source." + # User needs to provide required packages + echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + symlink_vdf_bench "$PYTHON_VERSION" + elif [ -e venv/bin/python ]; then + echo "Installing chiavdf from source." + # User needs to provide required packages + echo venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + venv/bin/python -m pip install --force --no-binary chiavdf "$CHIAVDF_VERSION" + symlink_vdf_bench "$PYTHON_VERSION" + else + echo "No venv created yet, please run install.sh." + fi fi echo "To estimate a timelord on this CPU try './vdf_bench square_asm 400000' for an ips estimate." diff --git a/install.sh b/install.sh index 69662d554824..06f68013a198 100755 --- a/install.sh +++ b/install.sh @@ -22,22 +22,28 @@ EXTRAS= PLOTTER_INSTALL= EDITABLE='-e' -while getopts adilpsh flag -do +while getopts adilpsh flag; do case "${flag}" in - # automated - a) :;; - # development - d) EXTRAS=${EXTRAS}dev,;; - # non-editable - i) EDITABLE='';; - # legacy keyring - l) EXTRAS=${EXTRAS}legacy-keyring,;; - p) PLOTTER_INSTALL=1;; - # simple install - s) :;; - h) usage; exit 0;; - *) echo; usage; exit 1;; + # automated + a) : ;; + # development + d) EXTRAS=${EXTRAS}dev, ;; + # non-editable + i) EDITABLE='' ;; + # legacy keyring + l) EXTRAS=${EXTRAS}legacy-keyring, ;; + p) PLOTTER_INSTALL=1 ;; + # simple install + s) : ;; + h) + usage + exit 0 + ;; + *) + echo + usage + exit 1 + ;; esac done @@ -54,7 +60,6 @@ fi # Get submodules git submodule update --init mozilla-ca - # You can specify preferred python version by exporting `INSTALL_PYTHON_VERSION` # e.g. `export INSTALL_PYTHON_VERSION=3.8` INSTALL_PYTHON_PATH= diff --git a/pytest.ini b/pytest.ini index 39916b64e4bb..89070e0e1bbf 100644 --- a/pytest.ini +++ b/pytest.ini @@ -26,13 +26,8 @@ filterwarnings = ignore:`coin_solutions` is now `coin_spends`:UserWarning ignore:Exception ignored in:pytest.PytestUnraisableExceptionWarning ignore:cannot collect test class:pytest.PytestCollectionWarning - ignore:The loop argument is deprecated since Python 3\.8, and scheduled for removal in Python 3\.10.:DeprecationWarning - ignore:The distutils package is deprecated:DeprecationWarning - ignore:There is no current event loop:DeprecationWarning - ignore:getargs. The 'u' format is deprecated. Use 'U' instead.:DeprecationWarning ignore:BackendFinder.find_spec\(\) not found; falling back to find_module\(\):ImportWarning ignore:BackendLoader.exec_module\(\) not found; falling back to load_module\(\):ImportWarning ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning - ignore:Properties from keyring.util are no longer supported. Use jaraco.classes.properties instead.:DeprecationWarning ignore:pkg_resources is deprecated as an API:DeprecationWarning ignore:record_property is incompatible with junit_family:pytest.PytestWarning diff --git a/setup.py b/setup.py index 2e99de3f577c..a315ef43890f 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ dependencies = [ "aiofiles==23.2.1", # Async IO for files "anyio==4.3.0", - "boto3==1.34.46", # AWS S3 for DL s3 plugin + "boto3==1.34.100", # AWS S3 for DL s3 plugin "chiavdf==1.1.4", # timelord and vdf verification "chiabip158==1.5.1", # bip158-style wallet filters "chiapos==2.0.4", # proof of space @@ -16,14 +16,14 @@ "clvm_tools==0.4.9", # Currying, Program.to, other conveniences "chia_rs==0.7.0", "clvm-tools-rs==0.1.40", # Rust implementation of clvm_tools' compiler - "aiohttp==3.9.2", # HTTP server for full node rpc + "aiohttp==3.9.4", # HTTP server for full node rpc "aiosqlite==0.20.0", # asyncio wrapper for sqlite, to store blocks "bitstring==4.1.4", # Binary data management library "colorama==0.4.6", # Colorizes terminal output "colorlog==6.8.2", # Adds color to logs "concurrent-log-handler==0.9.25", # Concurrently log and rotate logs "cryptography==42.0.5", # Python cryptography library for TLS - keyring conflict - "filelock==3.13.1", # For reading and writing config multiprocess and multithread safely (non-reentrant locks) + "filelock==3.14.0", # For reading and writing config multiprocess and multithread safely (non-reentrant locks) "keyring==25.1.0", # Store keys in MacOS Keychain, Windows Credential Locker "PyYAML==6.0.1", # Used for config file format "setproctitle==1.3.3", # Gives the chia processes readable names @@ -36,6 +36,7 @@ "zstd==1.5.5.1", "packaging==23.2", "psutil==5.9.4", + "hsms==0.3.1", ] upnp_dependencies = [ @@ -45,27 +46,27 @@ dev_dependencies = [ "build==1.2.1", "coverage==7.4.3", - "diff-cover==8.0.3", + "diff-cover==9.0.0", "pre-commit==3.5.0; python_version < '3.9'", - "pre-commit==3.6.2; python_version >= '3.9'", + "pre-commit==3.7.0; python_version >= '3.9'", "py3createtorrent==1.2.0", "pylint==3.1.0", "pytest==8.1.1", - "pytest-cov==4.1.0", + "pytest-cov==5.0.0", "pytest-mock==3.14.0", "pytest-xdist==3.5.0", - "pyupgrade==3.15.0", + "pyupgrade==3.15.2", "twine==5.0.0", "isort==5.13.2", "flake8==7.0.0", - "mypy==1.8.0", + "mypy==1.10.0", "black==24.2.0", "lxml==5.1.0", "aiohttp_cors==0.7.0", # For blackd - "pyinstaller==6.5.0", + "pyinstaller==6.6.0", "types-aiofiles==23.2.0.20240311", "types-cryptography==3.3.23.2", - "types-pyyaml==6.0.12.12", + "types-pyyaml==6.0.12.20240311", "types-setuptools==69.1.0.20240310", ] diff --git a/start-gui.sh b/start-gui.sh index 18e2a7075efc..7e036c0889b1 100755 --- a/start-gui.sh +++ b/start-gui.sh @@ -4,11 +4,14 @@ set -o errexit export NODE_OPTIONS="--max-old-space-size=3000" -SCRIPT_DIR=$(cd -- "$(dirname -- "$0")"; pwd) +SCRIPT_DIR=$( + cd -- "$(dirname -- "$0")" + pwd +) echo "### Checking GUI dependencies" -if [ -d "${SCRIPT_DIR}/.n" ]; then +if [ -d "${SCRIPT_DIR}/.n" ]; then export N_PREFIX="${SCRIPT_DIR}/.n" export PATH="${N_PREFIX}/bin:${PATH}" echo "Loading nodejs/npm from" diff --git a/tools/run_benchmark.sh b/tools/run_benchmark.sh index 06fe404eb400..4f3c9f83e15c 100755 --- a/tools/run_benchmark.sh +++ b/tools/run_benchmark.sh @@ -3,24 +3,24 @@ # pass in the name of the test run as the first argument run_benchmark() { - # shellcheck disable=SC2086 - python -m tools.test_full_sync run $3 --profile --test-constants "$1" & - test_pid=$! - python -m tools.cpu_utilization $test_pid - mkdir -p "$2" - mv test-full-sync.log cpu.png cpu-usage.log plot-cpu.gnuplot "$2" - python -m tools.test_full_sync analyze - mv slow-batch-*.profile slow-batch-*.png "$2" - # python -m chia.util.profiler profile-node >"$2/node-profile.txt" - # mv profile-node "$2" + # shellcheck disable=SC2086 + python -m tools.test_full_sync run $3 --profile --test-constants "$1" & + test_pid=$! + python -m tools.cpu_utilization $test_pid + mkdir -p "$2" + mv test-full-sync.log cpu.png cpu-usage.log plot-cpu.gnuplot "$2" + python -m tools.test_full_sync analyze + mv slow-batch-*.profile slow-batch-*.png "$2" + # python -m chia.util.profiler profile-node >"$2/node-profile.txt" + # mv profile-node "$2" } cd .. if [ "$1" = "" ]; then - TEST_NAME="node-benchmark" + TEST_NAME="node-benchmark" else - TEST_NAME=$1 + TEST_NAME=$1 fi # generate the test blockchain databases by running: