diff --git a/.github/actions/gomodtidy/Dockerfile b/.github/actions/gomodtidy/Dockerfile new file mode 100644 index 0000000000..9ea7e87afd --- /dev/null +++ b/.github/actions/gomodtidy/Dockerfile @@ -0,0 +1,5 @@ +FROM golang:alpine + +COPY entrypoint.sh /entrypoint.sh + +ENTRYPOINT ["/entrypoint.sh"] diff --git a/.github/actions/gomodtidy/action.yml b/.github/actions/gomodtidy/action.yml new file mode 100644 index 0000000000..27d3d8e9e3 --- /dev/null +++ b/.github/actions/gomodtidy/action.yml @@ -0,0 +1,5 @@ +name: 'Go mod tidy checker' +description: 'Checks that `go mod tidy` has been applied.' +runs: + using: 'docker' + image: 'Dockerfile' diff --git a/.github/actions/gomodtidy/entrypoint.sh b/.github/actions/gomodtidy/entrypoint.sh new file mode 100755 index 0000000000..32df725c8d --- /dev/null +++ b/.github/actions/gomodtidy/entrypoint.sh @@ -0,0 +1,34 @@ +#!/bin/sh -l +set -e +export PATH="$PATH:/usr/local/go/bin" + +cd "$GITHUB_WORKSPACE" + +cp go.mod go.mod.orig +cp go.sum go.sum.orig + +go mod tidy -compat=1.17 + +echo "Checking go.mod and go.sum:" +checks=0 +if [ "$(diff -s go.mod.orig go.mod | grep -c 'Files go.mod.orig and go.mod are identical')" = 1 ]; then + echo "- go.mod is up to date." + checks=$((checks + 1)) +else + echo "- go.mod is NOT up to date." +fi + +if [ "$(diff -s go.sum.orig go.sum | grep -c 'Files go.sum.orig and go.sum are identical')" = 1 ]; then + echo "- go.sum is up to date." + checks=$((checks + 1)) +else + echo "- go.sum is NOT up to date." +fi + +if [ $checks -eq 2 ]; then + exit 0 +fi + +# Notify of any issues. +echo "Run 'go mod tidy' to update." +exit 1 diff --git a/.github/workflows/arbitrator-ci.yml b/.github/workflows/arbitrator-ci.yml index c051772a77..623efdb176 100644 --- a/.github/workflows/arbitrator-ci.yml +++ b/.github/workflows/arbitrator-ci.yml @@ -37,7 +37,7 @@ jobs: detached: true - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -48,21 +48,21 @@ jobs: sudo ln -s /usr/bin/wasm-ld-14 /usr/local/bin/wasm-ld - name: Install go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.24.x - name: Install custom go-ethereum run: | cd /tmp - git clone --branch v1.14.11 --depth 1 https://github.com/ethereum/go-ethereum.git + git clone --branch v1.15.11 --depth 1 https://github.com/ethereum/go-ethereum.git cd go-ethereum go build -o /usr/local/bin/geth ./cmd/geth - name: Setup nodejs uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '24' cache: 'yarn' cache-dependency-path: '**/yarn.lock' @@ -127,6 +127,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: cache: false + version: v1.0.0 - name: Cache cbrotli uses: actions/cache@v4 @@ -138,8 +139,8 @@ jobs: target/lib/libbrotlicommon-static.a target/lib/libbrotlienc-static.a target/lib/libbrotlidec-static.a - key: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }}-arbitrator - restore-keys: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }} + key: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }}-arbitrator + restore-keys: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }} - name: Build cbrotli-local if: steps.cache-cbrotli.outputs.cache-hit != 'true' diff --git a/.github/workflows/bold.yml b/.github/workflows/bold.yml new file mode 100644 index 0000000000..560395dbba --- /dev/null +++ b/.github/workflows/bold.yml @@ -0,0 +1,71 @@ +name: Go + +on: + workflow_dispatch: + merge_group: + pull_request: + push: + branches: + - master + - develop + +jobs: + # formatting: + # name: Formatting + # runs-on: ubuntu-latest + # steps: + # - name: Checkout + # uses: actions/checkout@v5 + + # - name: Go mod tidy checker + # id: gomodtidy + # uses: ./.github/actions/gomodtidy + + build: + name: Build and Test Bold + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v5 + with: + submodules: true + + - name: Setup node/yarn + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'yarn' + cache-dependency-path: "**/yarn.lock" + + - name: Install go + uses: actions/setup-go@v5 + with: + go-version: 1.24.x + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + cache: false + version: v1.0.0 + + - name: AbiGen (nitro) + run: make contracts + + - name: Get dependencies + working-directory: ./bold + run: | + go get -v -t -d ./... + + - name: Build + working-directory: ./bold + run: go build -v ./... + + - name: Test + working-directory: ./bold + run: ANVIL=$(which anvil) go test -v -covermode=atomic -coverprofile=coverage.out -timeout=20m ./... + + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v5 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 95fd6fe135..0222cb23eb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true @@ -39,14 +39,14 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '24' cache: 'yarn' cache-dependency-path: '**/yarn.lock' - name: Install go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.24.x - name: Install wasm-ld run: | @@ -80,7 +80,8 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: cache: false - + version: v1.0.0 + - name: Install cbindgen run: cargo install --force cbindgen @@ -115,7 +116,7 @@ jobs: target/lib/libbrotlicommon-static.a target/lib/libbrotlienc-static.a target/lib/libbrotlidec-static.a - key: ${{ runner.os }}-brotli-${{ matrix.test-mode }}-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }} + key: ${{ runner.os }}-brotli-${{ matrix.test-mode }}-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }} - name: Build cbrotli-local if: steps.cache-cbrotli.outputs.cache-hit != 'true' @@ -153,13 +154,13 @@ jobs: if: matrix.test-mode == 'pathdb' run: | echo "Running tests with Path Scheme" >> full.log - ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 20m --cover --test_state_scheme path + ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 90m --cover --test_state_scheme path - name: run tests without race detection and hash state scheme if: matrix.test-mode == 'defaults' run: | echo "Running tests with Hash Scheme" >> full.log - ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 20m --test_state_scheme hash + ${{ github.workspace }}/.github/workflows/gotestsum.sh --tags cionly --timeout 60m --test_state_scheme hash - name: run redis tests if: matrix.test-mode == 'defaults' @@ -207,7 +208,7 @@ jobs: path: full.log - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v5 if: matrix.test-mode == 'defaults' with: fail_ci_if_error: false diff --git a/.github/workflows/close-trivial-prs.yml b/.github/workflows/close-trivial-prs.yml new file mode 100644 index 0000000000..ba01e0646e --- /dev/null +++ b/.github/workflows/close-trivial-prs.yml @@ -0,0 +1,46 @@ +name: Close trivial PRs + +on: + pull_request_target: + types: [labeled] + +jobs: + close-trivial-pr: + if: github.event.label.name == 'trivial' + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Fetch CONTRIBUTING.md snippet + id: snippet + env: + REPO: ${{ github.repository }} + run: | + SNIPPET=$(curl -sSfL "https://raw.githubusercontent.com/${REPO}/refs/heads/master/CONTRIBUTING.md" \ + | sed -n '//,//p' \ + | sed '//d') + + # Use GitHub Actions heredoc-style output to preserve multiline content + echo "snippet<> $GITHUB_OUTPUT + echo "$SNIPPET" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Comment and Close PR + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PR_NUMBER=${{ github.event.pull_request.number }} + REPO=${{ github.repository }} + DEF_BRANCH=${{ github.event.repository.default_branch }} + SNIPPET="${{ steps.snippet.outputs.snippet }}" + + gh pr close $PR_NUMBER --repo $REPO --comment "Thank you for your contribution. However, this PR has been automatically closed because it was labeled as **trivial**. As stated in our [CONTRIBUTING.md](../blob/${DEF_BRANCH}/CONTRIBUTING.md): + + --- + + ${SNIPPET} + + --- + + We appreciate meaningful contributions!" diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 53183861e0..2fe7c5df18 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,7 +46,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true @@ -54,16 +54,16 @@ jobs: run: sudo apt update && sudo apt install -y wabt - name: Setup nodejs - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '24' cache: 'yarn' cache-dependency-path: '**/yarn.lock' - name: Install go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version: 1.24.x - name: Install wasm-ld run: | @@ -94,12 +94,13 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: cache: false + version: v1.0.0 - name: Install cbindgen run: cargo install --force cbindgen - name: Cache Build Products - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/go-build @@ -108,13 +109,13 @@ jobs: - name: Cache wabt build id: cache-wabt - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/wabt-prefix key: ${{ runner.os }}-wabt-codeql-${{ matrix.language }}-${{ env.WABT_VERSION }} - name: Cache cbrotli - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-cbrotli with: path: | @@ -123,11 +124,11 @@ jobs: target/lib/libbrotlicommon-static.a target/lib/libbrotlienc-static.a target/lib/libbrotlidec-static.a - key: ${{ runner.os }}-brotli-${{ matrix.language }}-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }}-codeql - restore-keys: ${{ runner.os }}-brotli-${{ matrix.language }}-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }} + key: ${{ runner.os }}-brotli-${{ matrix.language }}-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }}-codeql + restore-keys: ${{ runner.os }}-brotli-${{ matrix.language }}-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }} - name: Cache Rust Build Products - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/ @@ -161,7 +162,7 @@ jobs: run: ./scripts/build-brotli.sh -w -d - name: Build Nitro for CodeQL - run: make build -j + run: make build # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 50c52506d2..067d3197c1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,35 +2,12 @@ name: Docker build CI run-name: Docker build CI triggered from @${{ github.actor }} of ${{ github.head_ref }} on: workflow_dispatch: + merge_group: pull_request: - types: [labeled] + types: [opened, synchronize, reopened] jobs: - # Only runs Docker build jobs if a PR has received a final "design approved" label as a final check - check-label: - name: Check for required label - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - outputs: - should-run: ${{ steps.check-label.outputs.has-label }} - steps: - - id: check-label - uses: actions/github-script@v6 - with: - script: | - const { data: labels } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - }); - const hasLabel = labels.some(label => label.name === 'design-approved'); - core.setOutput('has-label', hasLabel); - console.log(`PR has 'design-approved' label: ${hasLabel}`); - docker: name: Docker build - needs: check-label - # Run if it's not a PR or if it's a PR with the required label - if: github.event_name != 'pull_request' || needs.check-label.outputs.should-run == 'true' runs-on: arbitrator-ci services: # local registry @@ -40,7 +17,7 @@ jobs: - 5000:5000 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive - name: Set up Docker Buildx @@ -48,13 +25,13 @@ jobs: with: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }} restore-keys: ${{ runner.os }}-buildx- - name: Build nitro-node docker - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: target: nitro-node push: true @@ -63,7 +40,7 @@ jobs: cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - name: Build nitro-node-dev docker - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: target: nitro-node-dev push: true diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 0000000000..b1a67582cc --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,42 @@ +name: "fuzz" +on: + workflow_dispatch: + schedule: + - cron: "36 2 * * 1,4" + +permissions: + contents: write + pull-requests: write + +jobs: + list: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v5 + with: + go-version: "stable" + - id: list + uses: shogo82148/actions-go-fuzz/list@v1 + outputs: + fuzz-tests: ${{steps.list.outputs.fuzz-tests}} + + fuzz: + runs-on: ubuntu-latest + timeout-minutes: 360 + needs: list + strategy: + fail-fast: false + matrix: + include: ${{fromJson(needs.list.outputs.fuzz-tests)}} + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-go@v5 + with: + go-version: "stable" + - uses: shogo82148/actions-go-fuzz/run@v1 + with: + packages: ${{ matrix.package }} + fuzz-regexp: ${{ matrix.func }} + fuzz-time: "1m" diff --git a/.github/workflows/gotestsum.sh b/.github/workflows/gotestsum.sh index 22a687a020..787bad06ae 100755 --- a/.github/workflows/gotestsum.sh +++ b/.github/workflows/gotestsum.sh @@ -11,6 +11,7 @@ timeout="" tags="" run="" test_state_scheme="" +log=true race=false cover=false while [[ $# -gt 0 ]]; do @@ -47,6 +48,10 @@ while [[ $# -gt 0 ]]; do cover=true shift ;; + --nolog) + log=false + shift + ;; *) echo "Invalid argument: $1" exit 1 @@ -56,7 +61,7 @@ done packages=$(go list ./...) for package in $packages; do - cmd="stdbuf -oL gotestsum --format short-verbose --packages=\"$package\" --rerun-fails=2 --no-color=false --" + cmd="stdbuf -oL gotestsum --format short-verbose --packages=\"$package\" --rerun-fails=3 --rerun-fails-max-failures=30 --no-color=false --" if [ "$timeout" != "" ]; then cmd="$cmd -timeout $timeout" fi @@ -83,7 +88,11 @@ for package in $packages; do cmd="$cmd -args -- --test_loglevel=8" # Use error log level, which is the value 8 in the slog level enum for tests. fi - cmd="$cmd > >(stdbuf -oL tee -a full.log | grep -vE \"INFO|seal\")" + if [ "$log" == true ]; then + cmd="$cmd > >(stdbuf -oL tee -a full.log | grep -vE \"DEBUG|TRACE|INFO|seal\")" + else + cmd="$cmd | grep -vE \"DEBUG|TRACE|INFO|seal\"" + fi echo "" echo running tests for "$package" diff --git a/.github/workflows/merge-checks.yml b/.github/workflows/merge-checks.yml index c9f7957389..076bbe63cd 100644 --- a/.github/workflows/merge-checks.yml +++ b/.github/workflows/merge-checks.yml @@ -1,6 +1,7 @@ name: Merge Checks on: + merge_group: pull_request_target: branches: [ master ] types: [synchronize, opened, reopened, labeled, unlabeled] diff --git a/.github/workflows/nightly-ci.yml b/.github/workflows/nightly-ci.yml index 6e99f95424..8ed1d1c94c 100644 --- a/.github/workflows/nightly-ci.yml +++ b/.github/workflows/nightly-ci.yml @@ -16,7 +16,7 @@ jobs: # Only run on schedule AND main branch tests-scheduled: name: Scheduled tests - runs-on: ubuntu-8 + runs-on: arbitrator-ci services: redis: @@ -29,11 +29,11 @@ jobs: matrix: test-mode: [race, legacychallenge, long, challenge, l3challenge] - if: github.event_name == 'schedule' && github.ref == 'refs/heads/master' + if: github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && github.ref == 'refs/heads/master') steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true @@ -43,7 +43,7 @@ jobs: - name: Setup nodejs uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '24' cache: 'yarn' cache-dependency-path: '**/yarn.lock' @@ -84,12 +84,13 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: cache: false + version: v1.0.0 - name: Install cbindgen run: cargo install --force cbindgen - name: Cache Build Products - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/go-build @@ -97,7 +98,7 @@ jobs: restore-keys: ${{ runner.os }}-go- - name: Cache Rust Build Products - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/ @@ -110,7 +111,7 @@ jobs: restore-keys: ${{ runner.os }}-cargo-${{ steps.rust-version.outputs.version }}- - name: Cache cbrotli - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-cbrotli with: path: | @@ -119,7 +120,7 @@ jobs: target/lib/libbrotlicommon-static.a target/lib/libbrotlienc-static.a target/lib/libbrotlidec-static.a - key: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }} + key: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }} - name: Build cbrotli-local run: ./scripts/build-brotli.sh -l @@ -156,7 +157,7 @@ jobs: if: matrix.test-mode == 'race' run: | echo "Running tests with Hash Scheme" >> full.log - ${{ github.workspace }}/.github/workflows/gotestsum.sh --race --timeout 30m --test_state_scheme hash + ${{ github.workspace }}/.github/workflows/gotestsum.sh --race --timeout 90m --test_state_scheme hash - name: run challenge tests if: matrix.test-mode == 'challenge' @@ -181,7 +182,7 @@ jobs: path: full.log - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: false files: ./coverage.txt,./coverage-redis.txt @@ -192,7 +193,7 @@ jobs: # Only run this job if files in bold/legacy/ are modified tests-pr: name: PR modified files tests - runs-on: ubuntu-8 + runs-on: arbitrator-ci if: github.event_name == 'pull_request' permissions: @@ -200,9 +201,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: true + fetch-depth: 10 // Will cover most PRs + persist-credentials: true // In case changed-files requires deeper depth - name: Check changed files id: changed-files @@ -226,7 +229,7 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '24' cache: 'yarn' cache-dependency-path: '**/yarn.lock' @@ -273,6 +276,7 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 with: cache: false + version: v1.0.0 - name: Install cbindgen if: steps.changed-files.outputs.any_changed == 'true' @@ -280,7 +284,7 @@ jobs: - name: Cache Build Products if: steps.changed-files.outputs.any_changed == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cache/go-build @@ -289,7 +293,7 @@ jobs: - name: Cache Rust Build Products if: steps.changed-files.outputs.any_changed == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/ @@ -305,7 +309,7 @@ jobs: - name: Cache cbrotli if: steps.changed-files.outputs.any_changed == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-cbrotli with: path: | @@ -314,7 +318,7 @@ jobs: target/lib/libbrotlicommon-static.a target/lib/libbrotlienc-static.a target/lib/libbrotlidec-static.a - key: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yaml') }} + key: ${{ runner.os }}-brotli-${{ hashFiles('scripts/build-brotli.sh') }}-${{ hashFiles('.github/workflows/arbitrator-ci.yml') }} - name: Build cbrotli-local if: steps.changed-files.outputs.any_changed == 'true' && steps.cache-cbrotli.outputs.cache-hit != 'true' @@ -367,7 +371,7 @@ jobs: - name: Upload coverage to Codecov if: steps.changed-files.outputs.any_changed == 'true' - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: false files: ./coverage.txt,./coverage-redis.txt diff --git a/.github/workflows/release-ci.yml b/.github/workflows/release-ci.yml index 5282510e87..51f72b4615 100644 --- a/.github/workflows/release-ci.yml +++ b/.github/workflows/release-ci.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: submodules: recursive @@ -20,7 +20,7 @@ jobs: driver-opts: network=host - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ hashFiles('Dockerfile') }} diff --git a/.github/workflows/shellcheck-ci.yml b/.github/workflows/shellcheck-ci.yml index d1c7b58580..931b4c0263 100644 --- a/.github/workflows/shellcheck-ci.yml +++ b/.github/workflows/shellcheck-ci.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-8 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Run ShellCheck uses: ludeeus/action-shellcheck@master diff --git a/.github/workflows/submodule-pin-check.yml b/.github/workflows/submodule-pin-check.yml index ce8af6736c..b977ff502f 100644 --- a/.github/workflows/submodule-pin-check.yml +++ b/.github/workflows/submodule-pin-check.yml @@ -1,6 +1,7 @@ name: Merge Checks on: + merge_group: # Using pull_request_target for security. If we had used pull_request, then it # would be possible for malicious actors to create Pull Requests which would # trigger workflows that they could have modified locally to extract secrets. @@ -19,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 submodules: true @@ -34,6 +35,7 @@ jobs: [contracts-legacy]=origin/v2-main [nitro-testnode]=origin/master [bold]=origin/main + [safe-smart-account]=origin/release/v1.5.0 [arbitrator/langs/c]=origin/vm-storage-cache [arbitrator/tools/wasmer]=origin/stylus [contracts-local/lib/openzeppelin-contracts]=origin/release-v4.7 diff --git a/.gitignore b/.gitignore index f25dcec4ac..af6684bb02 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,17 @@ system_tests/test-data/* .configs/ system_tests/testdata/* arbos/testdata/* +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +# Output of the go coverage tool, specifically when used with LiteIDE +*.out +# Dependency directories (remove the comment below to include it) +# vendor/ +.idea +#other temporaries +tmp/* +.env diff --git a/.gitmodules b/.gitmodules index 109e3385c2..c2359f6fa2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "go-ethereum"] path = go-ethereum url = https://github.com/OffchainLabs/go-ethereum.git -[submodule "fastcache"] - path = fastcache - url = https://github.com/OffchainLabs/fastcache.git [submodule "arbitrator/wasm-libraries/soft-float/SoftFloat"] path = arbitrator/wasm-libraries/soft-float/SoftFloat url = https://github.com/OffchainLabs/SoftFloat.git @@ -12,8 +9,8 @@ url = https://github.com/google/brotli.git [submodule "contracts"] path = contracts - url = https://github.com/OffchainLabs/nitro-contracts.git - branch = develop + url = https://github.com/celestiaorg/nitro-contracts.git + branch = contracts-v1.2.1 [submodule "arbitrator/wasm-testsuite/testsuite"] path = arbitrator/wasm-testsuite/testsuite url = https://github.com/WebAssembly/testsuite.git @@ -22,7 +19,7 @@ url = https://github.com/OffchainLabs/wasmer.git [submodule "nitro-testnode"] path = nitro-testnode - url = https://github.com/OffchainLabs/nitro-testnode.git + url = https://github.com/celestiaorg/nitro-testnode.git [submodule "bold"] path = bold url = https://github.com/OffchainLabs/bold.git diff --git a/.nvmrc b/.nvmrc index 3c032078a4..a45fd52cc5 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -18 +24 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9394bfdba7..63d2df9bac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,8 +6,16 @@ Excited by our work want to get more involved in making Arbitrum more successful You can explore our [Open Issues](https://github.com/offchainlabs/nitro/issues) or [run a Nitro node](https://docs.arbitrum.io/run-arbitrum-node/run-nitro-dev-node) yourself and suggest improvements. + > [!IMPORTANT] -> Please, **do not send pull requests for trivial changes**, such as typos, these will be rejected. These types of pull requests incur a cost to reviewers and do not provide much value to the project. If you are unsure, please open an issue first to discuss the change. +> Please, **do not send pull requests for trivial changes**; these will be rejected. +> These types of pull requests incur a cost to reviewers and do not provide much value to the project. +> If you are unsure, please open an issue first to discuss the change. +> Here are some examples of trivial PRs that will most-likely be rejected: +> * Fixing typos +> * AI-generated code +> * Refactors that don't improve usability + ## Contribution Steps @@ -136,4 +144,4 @@ We love working with people that are autonomous, bring new experience to the tea Join our dynamic team of innovators and explore exciting career opportunities below to make a meaningful impact in a collaborative environment. Browse open positions and take the next step in your career today! -[Offchain Labs Careers](https://www.offchainlabs.com/careers) \ No newline at end of file +[Offchain Labs Careers](https://www.offchainlabs.com/careers) diff --git a/Dockerfile b/Dockerfile index a0c40b998a..340ba199bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ RUN apt-get update && \ FROM scratch AS brotli-library-export COPY --from=brotli-library-builder /workspace/install/ / -FROM node:18-bookworm-slim AS contracts-builder +FROM node:24.4.1-bookworm-slim AS contracts-builder RUN apt-get update && \ apt-get install -y git python3 make g++ curl RUN curl -L https://foundry.paradigm.xyz | bash && . ~/.bashrc && ~/.foundry/bin/foundryup -i 1.2.3 @@ -37,7 +37,7 @@ COPY contracts-legacy contracts-legacy/ COPY contracts-local contracts-local/ COPY contracts contracts/ COPY safe-smart-account safe-smart-account/ -RUN cd safe-smart-account && yarn install +RUN cd safe-smart-account && npm install COPY Makefile . RUN . ~/.bashrc && NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-solidity @@ -46,10 +46,10 @@ WORKDIR /workspace RUN apt-get update && apt-get install -y curl build-essential=12.9 FROM wasm-base AS wasm-libs-builder - # clang / lld used by soft-float wasm +# clang / lld used by soft-float wasm RUN apt-get update && \ apt-get install -y clang=1:14.0-55.7~deb12u1 lld=1:14.0-55.7~deb12u1 wabt - # pinned rust 1.84.1 +# pinned rust 1.84.1 RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain 1.84.1 --target x86_64-unknown-linux-gnu,wasm32-unknown-unknown,wasm32-wasip1 COPY ./Makefile ./ COPY arbitrator/Cargo.* arbitrator/ @@ -72,7 +72,7 @@ COPY --from=wasm-libs-builder /workspace/ / FROM wasm-base AS wasm-bin-builder RUN apt update && apt install -y wabt # pinned go version -RUN curl -L https://golang.org/dl/go1.23.1.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf - +RUN curl -L https://golang.org/dl/go1.24.5.linux-`dpkg --print-architecture`.tar.gz | tar -C /usr/local -xzf - COPY ./Makefile ./go.mod ./go.sum ./ COPY ./arbcompress ./arbcompress COPY ./arbos ./arbos @@ -85,6 +85,7 @@ COPY ./cmd/replay ./cmd/replay COPY ./daprovider ./daprovider COPY ./daprovider/das/dasutil ./daprovider/das/dasutil COPY ./daprovider/das/dastree ./daprovider/das/dastree +COPY ./daprovider/celestia ./daprovider/celestia COPY ./precompiles ./precompiles COPY ./statetransfer ./statetransfer COPY ./util ./util @@ -99,11 +100,11 @@ COPY ./contracts/src/precompiles/ ./contracts/src/precompiles/ COPY ./contracts/package.json ./contracts/yarn.lock ./contracts/ COPY ./safe-smart-account ./safe-smart-account COPY ./solgen/gen.go ./solgen/ -COPY ./fastcache ./fastcache COPY ./go-ethereum ./go-ethereum COPY scripts/remove_reference_types.sh scripts/ COPY --from=brotli-wasm-export / target/ -COPY --from=contracts-builder workspace/contracts/build/contracts/src/precompiles/ contracts/build/contracts/src/precompiles/ +COPY --from=contracts-builder workspace/contracts-local/out/precompiles/ contracts-local/out/precompiles/ +COPY --from=contracts-builder workspace/contracts/build/contracts/src/celestia/ contracts/build/contracts/src/celestia/ COPY --from=contracts-builder workspace/contracts/node_modules/@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json contracts/ COPY --from=contracts-builder workspace/contracts-legacy/build/contracts/src/precompiles/ contracts-legacy/build/contracts/src/precompiles/ COPY --from=contracts-builder workspace/.make/ .make/ @@ -238,9 +239,14 @@ RUN ./download-machine.sh consensus-v31 0x260f5fa5c3176a856893642e149cf128b5a8de RUN ./download-machine.sh consensus-v32 0x184884e1eb9fefdc158f6c8ac912bb183bf3cf83f0090317e0bc4ac5860baa39 #RUN ./download-machine.sh consensus-v40-rc.1 0x6dae396b0b7644a2d63b4b22e6452b767aa6a04b6778dadebdd74aa40f40a5c5 #RUN ./download-machine.sh consensus-v40-rc.2 0xa8206be13d53e456c7ab061d94bab5b229d674ac57ffe7281216479a8820fcc0 +RUN ./download-machine.sh consensus-v41 0xa18d6266cef250802c3cb2bfefe947ea1aa9a32dd30a8d1dfc4568a8714d3a7a +RUN ./download-machine.sh consensus-v50-alpha.1 0x28cfd8d81613ce4ebe750e77bfd95d6d95d4f53240488095a11c1ad3a494fa82 RUN ./download-machine.sh consensus-v40 0xdb698a2576298f25448bc092e52cf13b1e24141c997135d70f217d674bbeb69a +RUN ./download-machine.sh v3.2.1-rc.1 0xe81f986823a85105c5fd91bb53b4493d38c0c26652d23f76a7405ac889908287 celestiaorg +RUN ./download-machine.sh v3.3.2 0xaf1dbdfceb871c00bfbb1675983133df04f0ed04e89647812513c091e3a982b3 celestiaorg +RUN ./download-machine.sh consensus-v40-rc1 0x2249901020153123a4b81b2e0bc376bdf12bb463d291297791502c6577df17fd celestiaorg -FROM golang:1.23.1-bookworm AS node-builder +FROM golang:1.24.5-bookworm AS node-builder WORKDIR /workspace ARG version="" ARG datetime="" @@ -253,7 +259,6 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get install -y wabt COPY go.mod go.sum ./ COPY go-ethereum/go.mod go-ethereum/go.sum go-ethereum/ -COPY fastcache/go.mod fastcache/go.sum fastcache/ COPY bold/go.mod bold/go.sum bold/ RUN go mod download COPY . ./ @@ -324,7 +329,9 @@ COPY --from=node-builder /workspace/target/bin/daserver /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/daprovider /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/autonomous-auctioneer /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/bidder-client /usr/local/bin/ +COPY --from=node-builder /workspace/target/bin/el-proxy /usr/local/bin/ COPY --from=node-builder /workspace/target/bin/datool /usr/local/bin/ +COPY --from=node-builder /workspace/target/bin/genesis-generator /usr/local/bin/ COPY --from=nitro-legacy /home/user/target/machines /home/user/nitro-legacy/machines RUN rm -rf /workspace/target/legacy-machines/latest RUN export DEBIAN_FRONTEND=noninteractive && \ @@ -340,17 +347,21 @@ ENTRYPOINT [ "/usr/local/bin/nitro" , "--validation.wasm.allowed-wasm-module-roo USER user +# The nitro-node-validator is needed in case some modifications are needed in arbitrator or jit API. +# That was the case when enabling arbos30. +# We no longer support pre-arbos-30 wasmmoduleroots (newer wasmmoduleroots can execute old blocks) +# We keep the code (commented out), and the docker-target, for use in case such an update is needed again. FROM nitro-node AS nitro-node-validator -USER root -COPY --from=nitro-legacy /usr/local/bin/nitro-val /home/user/nitro-legacy/bin/nitro-val -COPY --from=nitro-legacy /usr/local/bin/jit /home/user/nitro-legacy/bin/jit -RUN export DEBIAN_FRONTEND=noninteractive && \ - apt-get update && \ - apt-get install -y xxd netcat-traditional && \ - rm -rf /var/lib/apt/lists/* /usr/share/doc/* /var/cache/ldconfig/aux-cache /usr/lib/python3.9/__pycache__/ /usr/lib/python3.9/*/__pycache__/ /var/log/* -COPY scripts/split-val-entry.sh /usr/local/bin -ENTRYPOINT [ "/usr/local/bin/split-val-entry.sh" ] -USER user +# USER root +# COPY --from=nitro-legacy /usr/local/bin/nitro-val /home/user/nitro-legacy/bin/nitro-val +# COPY --from=nitro-legacy /usr/local/bin/jit /home/user/nitro-legacy/bin/jit +# RUN export DEBIAN_FRONTEND=noninteractive && \ +# apt-get update && \ +# apt-get install -y xxd netcat-traditional && \ +# rm -rf /var/lib/apt/lists/* /usr/share/doc/* /var/cache/ldconfig/aux-cache /usr/lib/python3.9/__pycache__/ /usr/lib/python3.9/*/__pycache__/ /var/log/* +# COPY scripts/split-val-entry.sh /usr/local/bin +# ENTRYPOINT [ "/usr/local/bin/split-val-entry.sh" ] +# USER user FROM nitro-node-validator AS nitro-node-dev USER root diff --git a/Makefile b/Makefile index f897df7e90..479a92c54d 100644 --- a/Makefile +++ b/Makefile @@ -169,7 +169,7 @@ all: build build-replay-env test-gen-proofs @touch .make/all .PHONY: build -build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daprovider daserver autonomous-auctioneer bidder-client datool mockexternalsigner seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv) +build: $(patsubst %,$(output_root)/bin/%, nitro deploy relay daprovider daserver autonomous-auctioneer bidder-client datool blobtool el-proxy mockexternalsigner seq-coordinator-invalidate nitro-val seq-coordinator-manager dbconv genesis-generator) @printf $(done) .PHONY: build-node-deps @@ -230,22 +230,22 @@ test-go: .make/test-go .PHONY: test-go-challenge test-go-challenge: test-go-deps - gotestsum --format short-verbose --no-color=false -- -timeout 120m ./system_tests/... -run TestChallenge -tags challengetest + .github/workflows/gotestsum.sh --timeout 120m --run TestChallenge --tags challengetest --nolog @printf $(done) .PHONY: test-go-stylus test-go-stylus: test-go-deps - gotestsum --format short-verbose --no-color=false -- -timeout 120m ./system_tests/... -run TestProgramArbitrator -tags stylustest + .github/workflows/gotestsum.sh --timeout 120m --run TestProgramArbitrator --tags stylustest --nolog @printf $(done) .PHONY: test-go-redis test-go-redis: test-go-deps - gotestsum --format short-verbose --no-color=false -- -p 1 -run TestRedis ./system_tests/... ./arbnode/... -- --test_redis=redis://localhost:6379/0 + .github/workflows/gotestsum.sh --timeout 120m --run TestRedis --nolog -- --test_redis=redis://localhost:6379/0 @printf $(done) .PHONY: test-go-gas-dimensions test-go-gas-dimensions: test-go-deps - gotestsum --format short-verbose --no-color=false -- -timeout 120m ./system_tests/... -run "TestDim(Log|TxOp)" -tags gasdimensionstest + .github/workflows/gotestsum.sh --timeout 120m --run "TestDim(Log|TxOp)" --tags gasdimensionstest --nolog @printf $(done) .PHONY: test-gen-proofs @@ -328,9 +328,18 @@ $(output_root)/bin/autonomous-auctioneer: $(DEP_PREDICATE) build-node-deps $(output_root)/bin/bidder-client: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/bidder-client" +$(output_root)/bin/el-proxy: $(DEP_PREDICATE) build-node-deps + go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/el-proxy" + $(output_root)/bin/datool: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/datool" +$(output_root)/bin/blobtool: $(DEP_PREDICATE) build-node-deps + go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/blobtool" + +$(output_root)/bin/genesis-generator: $(DEP_PREDICATE) build-node-deps + go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/genesis-generator" + $(output_root)/bin/mockexternalsigner: $(DEP_PREDICATE) build-node-deps go build $(GOLANG_PARAMS) -o $@ "$(CURDIR)/cmd/mockexternalsigner" @@ -582,14 +591,14 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) @touch $@ .make/fmt: $(DEP_PREDICATE) build-node-deps .make/yarndeps $(ORDER_ONLY_PREDICATE) .make - golangci-lint run --disable-all -E gofmt --fix + golangci-lint fmt cargo fmt -p arbutil -p prover -p jit -p stylus --manifest-path arbitrator/Cargo.toml -- --check cargo fmt --all --manifest-path arbitrator/wasm-testsuite/Cargo.toml -- --check - yarn --cwd contracts prettier:solidity + forge fmt --root contracts-local @touch $@ .make/test-go: $(DEP_PREDICATE) $(go_source) build-node-deps test-go-deps $(ORDER_ONLY_PREDICATE) .make - gotestsum --format short-verbose --no-color=false + .github/workflows/gotestsum.sh --timeout 120m --nolog @touch $@ .make/test-rust: $(DEP_PREDICATE) wasm-ci-build $(ORDER_ONLY_PREDICATE) .make @@ -602,7 +611,7 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) @touch $@ .make/solidity: $(DEP_PREDICATE) safe-smart-account/contracts/*/*.sol safe-smart-account/contracts/*.sol contracts/src/*/*.sol contracts-legacy/src/*/*.sol contracts-local/src/*/*.sol contracts-local/gas-dimensions/src/*.sol .make/yarndeps $(ORDER_ONLY_PREDICATE) .make - yarn --cwd safe-smart-account build + npm --prefix safe-smart-account run build yarn --cwd contracts build yarn --cwd contracts build:forge:yul yarn --cwd contracts-legacy build @@ -611,7 +620,7 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) @touch $@ .make/yarndeps: $(DEP_PREDICATE) */package.json */yarn.lock $(ORDER_ONLY_PREDICATE) .make - yarn --cwd safe-smart-account install + npm --prefix safe-smart-account install yarn --cwd contracts install yarn --cwd contracts-legacy install make -C contracts-local install diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index 2b437968fa..d850235d2b 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -278,9 +278,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blst" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +checksum = "47c79a94619fade3c0b887670333513a67ac28a6a7e653eb260bf0d4103db38d" dependencies = [ "cc", "glob", @@ -349,15 +349,16 @@ checksum = "fca2be1d5c43812bae364ee3f30b3afcb7877cf59f4aeb94c66f313a41d2fac9" [[package]] name = "c-kzg" -version = "0.4.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" +checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" dependencies = [ "blst", "cc", "glob", "hex", "libc", + "once_cell", "serde", ] @@ -380,12 +381,13 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.1.7" +version = "1.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" +checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -1552,9 +1554,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" @@ -2129,6 +2131,12 @@ dependencies = [ "memmap2 0.6.2", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "simdutf8" version = "0.1.4" diff --git a/arbitrator/prover/Cargo.toml b/arbitrator/prover/Cargo.toml index da329b1cb5..a5b7b7e6de 100644 --- a/arbitrator/prover/Cargo.toml +++ b/arbitrator/prover/Cargo.toml @@ -37,7 +37,7 @@ wasmer-compiler-singlepass = { path = "../tools/wasmer/lib/compiler-singlepass", wasmparser.workspace = true num-derive = "0.4.1" num-traits = "0.2.17" -c-kzg = { version = "0.4.0", optional = true } # TODO: look into switching to rust-kzg (no crates.io release or hosted rustdoc yet) +c-kzg = { version = "2.1.1", optional = true } # TODO: look into switching to rust-kzg (no crates.io release or hosted rustdoc yet) sha2 = "0.9.9" lru = "0.12.3" once_cell = "1.19.0" diff --git a/arbitrator/prover/src/kzg-trusted-setup.json b/arbitrator/prover/src/kzg-trusted-setup.json deleted file mode 100644 index 6793490e2e..0000000000 --- a/arbitrator/prover/src/kzg-trusted-setup.json +++ /dev/null @@ -1,8265 +0,0 @@ -{ - "g1_monomial": [ - "0x97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb", - "0xad3eb50121139aa34db1d545093ac9374ab7bca2c0f3bf28e27c8dcd8fc7cb42d25926fc0c97b336e9f0fb35e5a04c81", - "0x8029c8ce0d2dce761a7f29c2df2290850c85bdfaec2955626d7acc8864aeb01fe16c9e156863dc63b6c22553910e27c1", - "0xb1386c995d3101d10639e49b9e5d39b9a280dcf0f135c2e6c6928bb3ab8309a9da7178f33925768c324f11c3762cfdd5", - "0x9596d929610e6d2ed3502b1bb0f1ea010f6b6605c95d4859f5e53e09fa68dc71dfd5874905447b5ec6cd156a76d6b6e8", - "0x851e3c3d4b5b7cdbba25d72abf9812cf3d7c5a9dbdec42b6635e2add706cbeea18f985afe5247459f6c908620322f434", - "0xb10f4cf8ec6e02491bbe6d9084d88c16306fdaf399fef3cd1453f58a4f7633f80dc60b100f9236c3103eaf727468374f", - "0xade11ec630127e04d17e70db0237d55f2ff2a2094881a483797e8cddb98b622245e1f608e5dcd1172b9870e733b4a32f", - "0xaf58c8a2f58f904ce20db81005331bf2d251e227e7d1bef575d691bdca842e6233eb2e26c2e116a61a78594772b38d25", - "0xb3c1313c31ec82da5a7a09e9cf6656ca598c243345fe8d4828e520ade91787ffb8b9867db789b34ad67cef47b26ff86d", - "0xa8ed8a235355948e0b04be080b7b3e145293accefb4704d1da9050796b2f6870516c1ebf77ae6a65359edcfd016c0f36", - "0x80e792d5ba24b8058f6d7291a2ec5cb68aab1e16e96d793128e86815631baf42c56b6205c19e25ce9727bd1fd6f9defb", - "0x816288c5d726b094e3fdf95cb8882f442c4d9d1101b92c7938a7dfd49bc50636d73ea1b05f75eb731c908c8fd8dee717", - "0xae009128d128ba2e1519bfa7a0c01ed494a7d461c3aba60f8a301701fed61fe4e31d6c79ce189542ae51df91e73ce1b3", - "0x96a866d60a9007d05825c332476a83e869e15b11d7257172a67690ea9bd3efea44bf9c8d42191454eb04fcf110b16396", - "0x8b250a2a06419adb9b611e89f7f8f2990aa301949b533ad3bf17c4a61ab5f5be0b1d5e2b571864d13f1bb75805c7795d", - "0x8450f49facf2e620fa45ee90e1801178842d927a2a25fc6ed7ba99a4eec7ae40eebfee41028eaa84f107f4a777694976", - "0x91049080cf659c0985a22d1366e59191bb89663f922e8168b9b7d85c8a73d74a6d9dceefd855d3d858b493670c750581", - "0xa1e167aeb2008087f3195926f1985c0a459d6ec57237255b1473a96de4e2c1cf766127c862c7dc853a6909e67cb06cf7", - "0xb667c0d4e26e20698b07567358625d5f003839c92de8088e12dbd74a6f6a3156b4ea8d252c9ad62af5f6c4fec1cf6cc7", - "0x8e4b5e304c0b1b161ae3e4b68b5e3ac66c42acd7c1ee2458044f6527c508a93995e50894d72d57c1350f91afe72775ff", - "0x8c642640aa7915421cdc21fd639f88a42052b1cfa358ff7702e60793a92b7b5926dae15a0c8f8f59cd3013f01c159ba3", - "0xa356f35e713cfc283056bf539de54a21731e61efb4c47319f20de4a4b723d76a33b65f4a67d298b9ec5c2a1579418657", - "0x93ce204146ce95f484dc79c27919a16c9e3fc14a9111c6c63d44491158d5838117d20851cc3227a5e8ba6ccf79e77f39", - "0xb585664cbb9a84b52f89114e1cf0cf1171bea78a136dc1404ac88a11210b2debc3b7a55e702da93ff629095c134a295e", - "0xb6dfd444ec7fdceb14c6328f26ca12c3f9fc4327d8d8c68948e92e7e61262b82d833a65a9e3af6353ffa832b6da25705", - "0xb4d4b8eb9ecfffe3f0d48fb4149c7b31aec1da7041ec03bd0750c52a2a7cbc3a7cfbf09d5bfdc56e3860826a62d0bb91", - "0xa4e248e3d61db52da9683fef188579c470d65e2df9064726847b1599fc774049ffdc6ef2ae578d5ed7874f1298ecdf69", - "0xa68a0fffc2e37d3183feb01b42234c0f4e510f9dc29d09c571e6da00fecad9da224cd0f31550070148667e226c4ca413", - "0x86adda2ffecb77236c18005051f31f9657a0d50fef2a1175dfda32e74d5d53df825c10f289eb0ad39df0c64fc9bc7729", - "0x998266d5c9c3764ed97d66fa9ed176af043999652bae19f0657c8328629d30af453230e3681c5a38e2f01e389ed8d825", - "0xa05261554d3c620af0c914cf27ab98f5d3593c33ab313c198e0c40d6c72022eb5943778cd4f73e9fe8383392a7004976", - "0xad243fb3631bf90fedb9d679fd71fc0cf06bda028591ded2bd4c634ea7b3c2bd22eca2ab318fcdaa6c2cda1e63e1c57b", - "0x89b9859a04f903c95e97fb2951f01cc6418a2505eee0b5bc7266b4d33e01b69b9fe7dc56fa9ebb5856095be0925a422d", - "0xa68d118343a5bbfbbab95ff9bfe53aeb7fdbaf16db983e6f4456366df2aa01fbdb6ee9901cb102fc7d2bd099be2f1f3e", - "0xb49301f25d5a9dd2ec60ddb0b4b477291958487efea9e54dc0e4ef388f03b8bbadd13259d191f7a0b7513876767d8282", - "0x8b93df7fb4513f67749905fd43db78f7026589b704ebb9ea3255d0ad6415437799f40f02e07efccda1e6fd5e8cd0a721", - "0xad88769ace96455da37c3c9019a9f523c694643be3f6b37b1e9dcc5053d1fe8e463abebdb1b3ef2f2fb801528a01c47c", - "0x80f0eb5dcbfaaf421bf59a8b9bd5245c4823c94510093e23e0b0534647fb5525a25ea3aeea0a927a1ee20c057f2c9234", - "0xb10ad82ea6a5aeabe345d00eb17910d6942b6862f7f3773c7d321194e67c9cced0b3310425662606634dcd7f8b976c04", - "0x82f6fd91f87822f6cc977808eeac77889f4a32fb0d618e784b2331263d0ffa820b3f70b069d32e0319c9e033ab75d3b4", - "0x9436d3dc6b5e25b1f695f8c6c1c553dab312ccace4dac3afddc141d3506467cd50cb04a49ea96ea7f5a8a7b0fc65ef37", - "0x8e0a9491651d52be8ebf4315fbbb410272f9a74b965d33b79ff1b9e1be3be59e43d9566773560e43280549c348e48f01", - "0x8809137e5d3a22400d6e645a9bd84e21c492371736c7e62c51cef50fee3aa7f2405724367a83fd051ff702d971167f67", - "0xb536a24f31a346de7f9863fc351fa602158404d2f94747eebe43abf1f21bf8f95a64146c02a4bec27b503f546789a388", - "0xb5cdf5a04fc12a0e0ef7545830061dff7fd8abea46e48fbe6235109e6c36ee6bffcb9529e2f3d0d701cf58bbfb6a4197", - "0xab15377525753467d042b7931f66f862cbbb77464212c9aa72d4e5c04375ef55f619b3a446091c1ba1a3b5d9f05e538f", - "0x905a75b943ad017ff78ea6ddd1d28a45c7273ee1c2e5e3353685813793ead3370c09cabd903fcab9d8b1c6961372d486", - "0x8147df4324faddc02fb0896367a7647b719b6499a361aecfdd3a34296fa6768ad31c34f9e873fd1e683386c44651883e", - "0xac91d08570dd91f89d2e01dca67cdc83b640e20f073ea9f0734759c92182bb66c5d645f15ebd91ed705b66486ed2088d", - "0xac6295ef2513bbea7ef4cdcf37d280300c34e63c4b9704663d55891a61bf5c91b04cc1d202a3a0a7c4520c30edc277c7", - "0xb604be776a012095c0d4ebc77797dd8dec62a54c0559fb2185d7bac6b50d4e5fd471ac2d7f4523206d5d8178eabd9a87", - "0x80ead68def272ce3f57951145e71ed6dc26da98e5825ef439af577c0c5de766d4e39207f205d5d21db903d89f37bbb02", - "0x9950b4a830388c897158c7fe3921e2fe24beedc7c84e2024e8b92b9775f8f99593b54a86b8870ec5087734295ba06032", - "0xb89ba714adabf94e658a7d14ac8fc197376a416841c2a80e1a6dde4f438d5f747d1fb90b39e8ea435c59d6ecda13dea1", - "0xb0c78e7cc60bd05be46d48fbb0421a678c7f14b8d93730deb66fbe1647613b2c62b5075126d917047820c57fc3509cb9", - "0xa860c4acc5444e9ae987e8c93cb9a5f17d954d63c060cc616f724e26bc73d2c54cd36e0492d1fde173847278e55942ba", - "0x8fb8269c9d5c15428e8d45da1251e4c4a4b600d47da0caea29fef246854d8fb6acae86a8e6440d0c429d8dd9c2dfee0c", - "0x96c5d8eb6fd5c525b348ee4335d200139e437e4be83690af0f35b7f336a7cda8c6d2958647988b84da9f2dd7bbb7710b", - "0xa7f62141c4346cc14e9823dc38ac7d587b0427022afc1498d12ee2c43f6ac3a82167057e670dd524b74137f8c3ceb56d", - "0x956aac50d06b46a3e94397f163f593f5010d366aa2d816c2205c7d0f47f90cf0f36c169e964f9bcf698d49182d47d91f", - "0xb812899bcdc0e70d79ca729cb01104bf60e1357b9085a10f64f3ba9865d57e9abd0a505a502d4de07afb46f4d266be2f", - "0xabce02c7e1372e25d40944dc9ece2904a8f59c8854c5f2875fe63ace8ce37d97881f4f9ab4f7bad070ec8e0daee58d3f", - "0x8fb13c515b2d6abb4e14ed753fad5cc36c3631dfe21a23d0f603aad719423dd5423157eefcbd9a9c6074e155b79eb38d", - "0xa9ef67304dc297ab5af778cf8afa849eeac27db4b6978963e97b95ef7a8d3264d0d07775f728c298a2b6daed2ecf5053", - "0xa9b975520adb066e2ff2a4cde53284c23bc84261a22dc43b1634d99eff8e7892e46bb6e6da7319c9e72788aa9ea7a1ea", - "0xa6eaea4ab4206294474d9b956d9d3188d558a5633de2bd05df0d3bac03dbcbe4ed85406349c1d2e660b77c6da1f5bf8c", - "0xaf4a19f77290dddee762e1e0d4bc9945aacea3f75756ae46cd3e58a8f74d1b5db73e4834687946b0f39191e32f2fed0c", - "0xaafa6523f58f1a4cabc924c86d842816d606afeea21fa4b2b8b9573425810fdcc41c98888318e868f9c05e2be12178a3", - "0x8ef38fba0a3fa4ebe985239c8b759c22aaef0c57e6f39050a651c869487803b0d1e389c3d958fb5a7f37740f050ac69e", - "0xb07dfc9f85913c608ca7596a2e361f05e4853fad00e796fd492d247de6414892ce160f627669b1ba933b6ad726415d4e", - "0x94da679ad1d78b2bff5283c938f17b2a7d6e9cbcdf59d340e6dfb652951c7a9e852ac0590f99cfee9631b9410f6f00ea", - "0x98a907c9c021a5b034d3720197c160a82c4b7146cb73d48efeed99b9d0c6b831812cf80ac7e19e85a676a8cd3ead72de", - "0xadb746595466a12929019d0048cea33236b05c1229d2eba73b259a18a786f2bc3f05fc0598d8ce253cecb80bdf679aaf", - "0xa2fbac016996d68f9027a157b0a3f6a336144a798d6113adfcda3a5d05b62c31f108f112aa915906aef22b7f83b9228b", - "0x81841dea1904406d1b6fa49b4b3f7f6cb40b7646cf44d36c9fa07e3dee29f8e47324b40d8356ddf653109673c3374e9b", - "0xa3edbb8aac5e60c775775cbdb19067341b2e2530de48738e84c2c07151241ee31f0d8333bf20c2bc9dcb7b2e638a6b5e", - "0xb8aa6890e22964828787ce86460d3a32f12a655bb5c28de500f2fcf6b61e3334640ec6ba96029a4912af0d18df4b4139", - "0x8ca43169f04243ad0fdb0152de17c60d9e31ee0ab520970fccd98590e05508821a183b4b367967e60d53c2c826ec5dbd", - "0xb179fffd9df8c00486c5a8b9327d599f5a11745ef564f06e126849b06fe2f99273c81f65bc941efb0debaadfecbfec1c", - "0xacf068f1c2b1926279cc82750ce21b0d6b0bfd0406f0d8bbfa959bd83935932957c7f6b8de318315bf0b75f6ee41a0f2", - "0xb97831da260919c856e9f71a41687f5979bc16f8a53b1037285b4a2f9ce93af5cfe70bf0ad484744827fb55c847b58eb", - "0xaff50b0bd907383b0c241727af364fe084d021221bfb1b09fb6c1a7752eeba45d662493d590f1f182764b90b25f17906", - "0xaeeef044c14e3ad41e1235c9e816e1eb49087fd3abe877b89b3bade74459186126e160bb569bcd77779e701b19b5f71a", - "0x8483deb2b7001ca7c438fcdca8ca6aba96c9cbc4becfd9b16a6062705eae270011bcaedcae69bb54630d8c78129e57c7", - "0xaeee8d24be4ac0d9784c029e239fb5e64316ce29b88f47394cfaaa8bb966a72061bff72f99d02dc51c9705854686e77f", - "0x90ae09525a16bb2422169e15d6831c87968a14ebc0d1d27e11a759839c73c655b9d33ee5b12f275d6f440688146fbd2f", - "0xa3a41fc7fefef101422465e506bea7f3ff23c26fe35f5732b86f5f2471fb93b37ebc339f84c6be1e8d22abc812c2e212", - "0x86f4b5293e8aea4af1f1fb05dcf99714cb3aff1cfc849b1bb73524061c921c9da9ad92579a852e1889da29d952f02fe5", - "0x8932ef39d4050a1e9dc0fd8afeaf159472d71c5c27f458c69d2730836606ea56e19c8c4febf2535f930d3260e9bc7637", - "0x86307b9f3696bb21c20e4558e30310389e7367803c353d437e9b696039a0ff054d9a4953b75237ab1d1dd6f71118c189", - "0x96e57730e683ef5b550c91de18b19ac73879f3e26234297db68d28747ed0953beb0f3913cfb720c602720bf9330685d8", - "0xb04a19ee70123782e47b238abde55baf60ac0c66292a998af0d14afc8bbeb1134e557b94cd17a020084631c09a0d3c02", - "0x829abc8718be8139569fcb2c398962f38f4201114d30e2b2fb23566f8a27a5c380f5605cec543415202a12ed859e33f6", - "0xa0744fa488c8fa92a722c5fc4ef5a47dfe824eccd87d26c8bab9c174cbb151d44b1b29082c48652f03d3177e5ec86001", - "0x81d4035ae9fd28bdcd78b135cb54955d3b685a527319df6ee7e904b8e6d796f5f5a5f5035ee1de750c4cb6050e452b9e", - "0xb205e8c2ec24d7104fa0106c09ad34b5a912c1adef553fb718838dd627355993c2ec01055c11d00b2c75b68e9516d44b", - "0xb12d09da7968fa7394e449624fc7174d1d76c069ccb03e140d4d87a2d3f6d1f7b9cfc930f0c80becc673406ebe63f08e", - "0xb23752c158695da85048fdf38b395681cc0e8998630af8a9ed41efbda08c9964c2dc8ae6e53377264be4467d702c0de4", - "0xb0d84582fd73628d96b8c1ec96197697c41a963542451a2ade0890af0d33c7161d0f18e1a1ce2c168ca2dc1e9119d55e", - "0x8b877e618b469aa187632e410b125d2999d5738fd66d482000706b51fd904a0c7e7daa8c9b729fa33817bbc4154cba2a", - "0xb1cfc8a7551b601723b937d497d01dec3ee7614c2bf13d430b1058d5ebc1406045009ff02c2ac15bf8cf16f860193d1e", - "0xb6d9da84f97b21e13175bbb0b5cc8e79e88b470c87a3e115726c1bd98e0288526c58f3faaa8aa170ace0cd6a60852525", - "0xad2e773c2d527671ca5fab7085dde4da31cd35f45d4315dd95d8893ff5fb900494dca08eccfc1a2fc7bf7c7fd2fcab97", - "0x8d5a79b34aeb761d4a0c73f09f02e9548e6d382c33ee6887a759ab05762b490b8a549ef2933c7e3a46415c154c0221c0", - "0xb6f2cbe81bd0a7298403be392f8456bed30aed7ef30216959357698f789affd2942ae5fbaf3f48ecebeb7c273b20cb57", - "0xb5b6c45d99cea7ce6a1dc134aff4a8f630f299b42bd59592a7592345f8cd35bcbee944e61b0723de732fcad6e4425b63", - "0x8077d64dfcb2418974e956ea6dbf8a4c05b25d2a025333ad7e2a379f1976dc036771403383a51bfa3476c9c619ef8bef", - "0xad2e0a9d479c77a5fb73b3613a177fdaad50dcb50fed50e756ba18164c153af30b07fb2565e80ff7469f1b0338b7b5de", - "0x81017d1d80a6b6df4e99d0d7f85a8180b5523e8fa2ea2672fddff604933f8a113cab27fce098dcb454d7d1f7ed266e04", - "0x852355479d68e76c7febf6dfe2ef8e80d575c0d3bd52c983803592021cfa898c571c0b884412c21e66f0dbfe03167b53", - "0x98e1bf8ad48421467c93b9f72b47dded7c41b4fcd36ea55ca43ab24b0d0b876f5a731f422579b7167c7138fad2121266", - "0x803369314abd5422019ed4b0ef652b4dbe97ef5a87b0ea373eec9628b64a12120b2c3d4eb53db405131ff786d14c7ac6", - "0xadf2613fc34f73e1160975c140e925ed84d254e03cc3bc7fc1d19957b499c9ba9d9e4c1639981b594a7095c0a52c6757", - "0xa2f6a68efdff6e4173c00692abcfdfcdaf6f8b62369afad3dafaae4f2f38c4860780b4624d185e20e4f4498b75b5fe94", - "0x8b1658aa0e119fb8401d486ed08d60240d26a8623ef9788e3b45ad09ae31259395b021bd16be395139cbb7149714e764", - "0xa7dd8bf21121285e00672ee8bb84e0cb39b2496fb53a26e35dfbca7f2b04e9a9ff9db15f53fe63fcbeafeb2deeaf2ca4", - "0xb6d8d709e44bc18f3b41d69608edce60c02bcba48d3b7e2fd420842657f0665a7343246dea149a25e8f3416284abae66", - "0xaaf744ca5e9bcb63e3e2939b7a1e96e4a93c88c76bec0cf4294dd7db95cdd3f6a7d92196e352d08680e2328bc4592899", - "0x84434b015a7c398d35f1ec71fce455d62ba4ed4f62da042ec31bb2b4db47073314354cd50bc322297a1cfe35138bf490", - "0x8d70b3a3cd9d5dfefdacfa418c0b775a112a47ce538d33a560a519660009c3f141fd6221c18539129e9c0acdaceeeb80", - "0xb8c6903412a800ec78a4c15f31c24385a267b0c0ece32fd31bbbb557fd70c3b2d60d8fc0f90fbd70f43baa1928ea30ba", - "0x8e391dd445ea06cabb433f057853f8159511b2f9bef41aed9ccd14e0a6fcd912bbaebd38fd5fb736cfde0fa34b7a4874", - "0xa40cd988f70613df32babbd1bbc2f1b29ff1ab0147b01161555a81d56c9621657999bcdb1df38485f687afc51d5d0f23", - "0xb6a008b4426b3d7b28ae04eee4698fc8ef6a35d89008ef5394da39ce582ce1a45dcfae9a33b90f6fa4237f3667803873", - "0x8987280debfb175c3b44a2f152ea82548e4f680966f1fcbee9bf7d714e31bf8080c33f52705ef3aeee70544b22516aba", - "0xa78a51a2c11eea7680a5a0ae417a2981f8c69c396e06da621eadd7510a3664ade49d065617bec67b3de779548a4f4509", - "0xa4d9163f0a1bc048385e94d5e0bcafeee1b18f28eb23505623b9e8ef16f3df76408254dfbe790e45f2884198060d388d", - "0x83dcae2568a0c518793c0f6e38b42f9ceb50673d100b556a17ec8bd9faeec84afe50b8d72422c6b2356959667bb8e2de", - "0x874731941be4474b4576226e5906b5dee89fc9b56a9870dcc7289c1a7d494d345ba6aba31f7546a16f9963283c05f744", - "0x82c1cfab1f501189ac20147fc4631075dbf1abf9125b7d42fcb4f31cf73f3d6461b1bd08fdf6e45cc54bc08a7d5d51d1", - "0xb978228286f5d4a10ce027b6bea3021affcaa805340ca4b5192c69e8c56db59f48e4a14a284ec015f53baf97389f62b2", - "0xaf125f4fdccd1c1b64fdffecb5ec7cf8c7392bbe476e1b89a5b5329c5ba4a526e58c11e72ab9de8a38d60af648d75adc", - "0x8411a41ec14295acab0d36389013535a80dfff6e024bffeb32fb3070762f61256419e8c51b2ad6de9dbe4f1e8e286912", - "0x8ea67a91112a41f9c65515cd496f4b0cdefa1400fc06568eef000c9eae6dc250fb7622eb3f2deca10b37287cd96fa463", - "0x8da99b6c55c31dee6a49aabb54da249d348a31d4416201a10c45a3b04b11e99d4ae9813632f0ee36c523b5cca62f6f49", - "0x8b44656341e039e2bd83a19c3bb9a88f6209482e274f8cd4f8557b728e5948dd80b5745f621b96f4562928689314e8c2", - "0xa02d424a615ba0dce8ed91f477e79852215a3a39d025059826fa278e7eebef19824b2a2844f5b3865a0f471b609a23f5", - "0xa1f115cebc3fff3bcf233da27cef19eae791660f155d088003460f75567a550bef0722885010ddc384acdeac635939dc", - "0xb61a55ce9d143c17876776e064b58a10baf0ba13553c785c1e47f57b5f94c0cda8bc89d43d73386e57816c15b61a8ec8", - "0xb4073f47041e20a8e548c7fb00e07ba3b9056c34eb4ab63bb0e7b48f8e338e8b56a17611a1b5f4c03b352450b86f1d69", - "0xa7b1a07b213205b682fc5b6acb7e76fdf97b280c26621d8f3b76b7c1deb3511957da33a4e358c8e8f3d98b2a8855d67e", - "0xb797e67c2670fbd9844e8a68c585f404b035dc14bd4ec75c3f95f932c777f9db5d5f5df7629164af488fc1213035cc5f", - "0x99618200797b945f595794d6468e5c618649554ad9ba896330f1cc844090eb956ae9fc23132912f9047085c5f0c3bf7b", - "0x81194aa1319abf534cb3927af9adfb178a99d0e3e8c99ab1105f1d3b4fed40ec2971caf1d6647acb0c8d681eca53097b", - "0x80673f18e4978dbc226a6cd4b128a1259d9a7f833879c6e2fbe24d69fef2c3c23a51a4f3e8d88fa4533434bbb0723661", - "0x8125bf6c7dbb2fb63aaa3f53283559f172c788223674adbeb6d5bd17cfe888e6b87a79aec774917f20ce911c1f85f8e7", - "0x884bcdb1878b14fc38adc9fb8b4dd0b3afde404fbeb664f26ddfebc81736018551f23e75ce4cfe4865f610bcd454fbd7", - "0xaec65c8d4be8316e98aa54888af01bc6703a0c5d04b69756ff39a0a947b66817ec59d76afe9f61a25749b5e890f03e02", - "0xaa457aaa1b014a4c5a8992847a187a23321bb43452c98745987d038e3b04046102ae859b7a8e980eea978a39d76a88ef", - "0xa9832ee63b08e19123f719bfe2fe742125f32463efa966c7709a98ebfc65277670e9ea1fa2d2d78b96bdc7523b0c4c3e", - "0xa87b6b1b7858f96d55064274f29fbde56067064962cf3c3e2ba3110b22ea633bc037a74d23543ce3307a46208855d74f", - "0x897cbe4ab68a753020fec732dfcc052c7ed9905342b5a6fe0aa25c631f9ad9b659e0ee75d46f0df6507b6720675ee28c", - "0x97c3b5f0d54c1fc45e79445c3ff30458959e406a069f5bbf7979d684195b4fa0406b87c1c008f4075bc9e602ed863152", - "0x921e65d582ea9322ddfad1c855331c3cac81f53c700b96db5305a643c084eb6793094e07944bfd41dc02c3b3cf671530", - "0x8f23ef1aca02a260a3b65d25b110f28d3bafca44727448c8f2d03c5e77eda620c1721b06681bd816ee6027664d76352a", - "0x946a89b132ec0795aea9ff9dde7b77e7feafffe6e4a2f093042a7e6c71cd6ab87ce0ca914a1b5fabad4e1f96a795f163", - "0xa01e2de9db33df6511172123ad6f7c64074237471df646b32dd9aff8c15278e2723108e4facaedca97e9f49503f8c792", - "0x99dcdcde45b2ea3f15279936feede5f7d3b63ca4972f335b0559c2fa6f9faabd8127aa892a36deb114357ca906553ed8", - "0xa3f8af37bfcf66b04d1896a4bd5d343f4733d4c3305369ac7e75a08f20f2004c10c642d2c7577f4e5c4d1f2cd851ac3b", - "0xb7294d15a3d674a56099f97a1adc9e82c15e90832eaf1722df110fc2abc8634c51515e5ad8522015498a3753b1fa8c49", - "0xb4f27f5062ba7a04ea0048b3025b5e3d5b5d319a9e80310c808a5fb4e8e77b38c10a0f3172cb805cadbcc8bc66d36ec7", - "0xaefe5decee0ae2dc372cc6cf4217daf97c4c908d145f100f0daf1ccdfdf641c78432c2e473e7e4b77dcdf2d4c2bb05f0", - "0xacc84af7648a535ffd218c0cc95c8f7b092418c548815f1bafc286b1fe14f6ccb51b2044db3bff864d0bb70e88604084", - "0x84d8e3dac0df6a22beb03742e1d4af684f139f07e2ea0f7fb27fc2d7d4f1e89b5e89f71af32ff115ed5e6092133535f0", - "0x8ada001e1a03a823c4c056f636e77adc0f9dc08689d28de0d99e0feecab5db13abf37b41ec268dbdb42c75419a046c68", - "0x87dac6c798d1744dff81d8bc3e0e04f3c9bf260e811685ddb9a9a8d6eda73927439b344f9a818d2103fad633de5a4a17", - "0xad9929a7d8a7d5d5954e48281a87e5c84f67e19110d73296b9989a09c76767a57a8115629239ffb4d99dfdf9c52ef6d9", - "0x81ac7cbeef8ec35a5c3b61cc887080c29e6cd3e08af37e45830d17400dbacfb374dd07bf370b979828c3875b2027d5c6", - "0x97f92c9182953b7e10f7a1bbb6b5b5c40b8275eb5a6eec1e29874c4712814749aa8c409651380216e1ff01d7b8511041", - "0xa09794d0bbe7db013045d3fd857c1544fe6231d21afa3495fa300371f6301a3a0f4b8ea175b281503dd06078ff371ae4", - "0x839bb58d320aa08116dd387a57a2b9bd9efc89c4cdfd82d0e47a00cabe644631d09be5436bd485df3b61b75ddf81a3ef", - "0xb1cdaa344f783757e8b9c1f84421da3c5be4c69f019a8fd4c1aa5bf1a63e8970c99e35c22cf3b48a0e6738bc6ba7ce8d", - "0x92af68e3216c78998208fb24b5ba0e645d0d3f5e28222b805668d7e9cdd6c033d3b22fd6df4c2d745d7f910d133cd226", - "0x87640a4ea4e605e2204e5232b29a6c1c31152d83547eef14122cb76a0da52b8653801af48455a3ed713b9dcfee7b1ef1", - "0x8147e5bf0c8f4731155ca0517ef3fae5a32b4d5d2d98ed0007b23893d8dbb7f8a1199c50c1750c2fa7c9cebe594b1bb0", - "0xa76b4473c63c3ab6103c729afd2482822e4150f3155af39983b0ff0766c71cb622455ce6304e23853661eaa322219d18", - "0xb3e2f05ca551bc3adec0067e4034aaffd72e0b64ac18ae25452c996927976c6727966e26d213b032521889be2170800d", - "0xa8414cd14cb3be658e9e0004ce511ef7063439b1cbc3166a11de030613fde4b59caad4e91d426927863c55382afbf476", - "0xb2f0f8ab99f4d0ea785ac84fdbc00b20217b1df59b30b51d9d209d489d53b69dd5d82cdacc16fd1dd15c3a4001595f50", - "0x8b2025d5fd658c9bbed619f3e3f6ac8efe7aeff8aa9401bd66a7ceb0062c44b353608ca073f95be99204f0a913bb77eb", - "0x94a46bc5a87291b42024b2137e623c70115b9c6b196604106bfbfa20f3f56ac7779763f56b580190d3cb2f1c648cada1", - "0xaca9355545118d0769cacf69c4b23d6d68d229cd8f68f1bc0c847c05569c5af6bbbd8c4dceb637b4a6b3b5c83841bf5e", - "0xb0731992cab87c7116406b283a84707a34838bfa3284b0f6082dfabeaf41c5ac2b0ddc1b420547a1b0955aee92de2dc0", - "0xb671f77588c0f69f6830a5b28e7d07ed161b81fa9791bb3a24aae6638e3aa5e186df74978a82549c370c18ebee04d4f0", - "0xb5621ed841780f3e6681d880a76cf519cdd20d35197b112eeaa686764d57b5dfa78ffe1a294b6bc76b6e3949cd2a2369", - "0xafeba2524659d00caecf089645611553187a6ed7102050f6dd20f5a19bed08ac7065912d88371ee06242897d58d652a4", - "0xb78bfb83d44ced14a20135804aba3f00128c3ce1f302e95567ce4097b0d973414153fb305b9f156882a5a0554bf25973", - "0x98510aede95d26b1adf214053eae051ffaf24894e2fa37961a91d0ff5392dd09388196648d95b73e90bd88f2587cc4bf", - "0xb35c682d49c295946b9f120fbc47b95abd9ee86d294abb003a92139fb825b509209562575015856a270eb3eea86397a7", - "0xb9641bf685571dd9c478dd2033a1f1b11cd3a662b26502c78595863b8e536a189674a9a85f7a253453ebfd1b99fbd841", - "0xb2ad37036a59b1c9b8457972665720a6868422ed8157b6810a9c0783006103be34ab732d7aeb8629653edd18fd0f1717", - "0xaf0920cff05179a3896ea6ea322c39adf91ada5bc40fe3f6fb1b1b4e121e907c904bbaa8ca00468b3749f3da144d71f3", - "0x8e269672818ef1e2f9e0c8aa65c84442fcd9151d74bb8e870cee8c0e3fe24526e1a5388b430cef47b67f79b4e4056bcc", - "0xaa29a16fe00ea3d143b1032b1dd26b8ce638f37f95c085c7e777e8e2784bd724bd5c38b1583c61a6ec7c451dd78fd3fb", - "0x87452b7435911cc5f513b0c81b15aa04972ecbe3d7bbd0a5d676c96a8a311301c0e07fac925c53a350b46fbd3d4d0fc1", - "0x869a81c351096f47748e41566ae7b77a454b1cdfaa41d34a5742f80df38fbf5cbb08924b6fdff58e3b18f05c62bbbbb1", - "0x8b7bc1b0486300981147a40a449ada9a41afc06d735cce8bf0fab3ee94ba2e2ea57b1397e3cd31bc295352beb8334ef7", - "0x93e93fc41adb2df279d95654921b4c2edf0d293dab58d0afefb221f777349ef88d0985b3447e3b935954a81f1580a92c", - "0x970fa7cdca8324faf3e62348bb50d78f580b4f43f2e1c11bd8382d48d0074a3c55c6407203a0c9cb1c5f2163ba421ef4", - "0x924983929e608d27e4a36d4ed919297869e3c64de51aca794d32d6e90aea546bf898d98ceca28a0b2187734821b78504", - "0x8d395332529c703d943d68415d443332b5c1342ca9d9a59bfa8bd4ab63e93358c4b0dde6ce1f2e8ea9dc8f52ad7ebd95", - "0x80200dda853e588256599e7f905add5d5ee7c74272780317694fbae39318ae9be05d5bcd7b20cf460069743f3d4ef240", - "0xa287d51d6359c9ef7c7ac1b20e479ce7d0146dba5606397bd04b7a622cec642508d5b45d51b31de71f9763595b6ac88e", - "0xa320396c075175d6599225cf2e1de8c7cab549f6316c07feb0f6eaa21f06b2dd29ab14fbdf2af4543b4890ec0fd08a4d", - "0xb1e9fe230418d20368691058adcbbe30011bab3000422f0371015ff8bd09c60fb5fa85d18550d35b1c900977ca48f58b", - "0x9718fc26a51783b971744933f20490e9b5cd9162f86b84788c4c5217f5409e37b5a39d628b18e5b35a757acf67596321", - "0xa0cf81fdb161f4f1b419c5e4caa36d4bdca2325f0cd25b119a30178016f171bd6fb88403e4e3aec026c4089f180d540e", - "0x8ab1e36bd04625ee794ef04c4dcb8e004d61aceb2b62438377f49ad95dcf025ba25eb799280004941e555bf7172af6fe", - "0x9257b9e3d14d37fc7efae49b0c68d36eaac546035f4a2654d566b3ce1b2c4564cbb03dc8ec66efceb768559a8a507a18", - "0x945d1123b839637ab5154a1972c3c83a0ff34a3b1a3465de6ef0416b1950f649869a3ef88d7f1036648ee385265ce2df", - "0x81449639d708860fc0229c94f754f7262e8a3c7f67960ff12dfd15df95f57a9ffcee2013e81978b7703dd42bd5d0816f", - "0xa865481deaae5a690fd53892791e5fa729db283b75a525a11cdfee1ce17e8e7f0b449d25f20b3c1b43da128dbdf98a8b", - "0x98766812a65fcd25b853546e3bba618a3edc9fd61510e4f8ab60c038a7fa50d197abeec8776109df0f2119be9445ad00", - "0xb1b8dd5379d903dc41d74e999b1ab693607a0d2905692f4fb96adf08f738e5d31f9d00df28ccb8b5856145ca552c3e3c", - "0x99d20be7b511bec78a8ed03c207aa4aa9097ba39d85e18f1b8d52f65431ab7e9a773c7b9ac3e8d8b25458bc91bd00703", - "0xb1b7c3563fe8cb33c7d3e0b89d00bdd13e86452ff507c2e69db7b3af06f247f139155396e9b0278753310dc63940a10b", - "0xb3dc9c08451b1de7c9969b1e47574bffff50490f4a16c51e12390195d9e9c72f794790caf7b0a835d64e01fec995d3ac", - "0xaaaa4761a00022ede0809d7063d3532b7bfae90ff16f45e17a340ad4ebaa2fbac40728ccc5fbe36a67ab0e707566c5dc", - "0x8319a1903314eab01f5442d2aee6ae9c3f6edfda0d9a88b416d0f874d7d1d05d08bb482102f8ca70a4fa34836d0840c1", - "0x932949a6e9edfec344932a74d4f81eec3667ece1e8b8ca840ce07ffd4b5d6d8f01657c764d64ac1b9190f876b136490e", - "0x904db1568128487e312fe629dd8bb920cecafd3bb9cad8b63e269ae0129f2f5c80cd82f0d81e7feca9835c3945a72d28", - "0xa17280693d30dcd43c85de8f6b02d5f30cb9097274ad680cede1ef105c903615b4c40f3c6aaca478642de324972514e0", - "0x8d5f76e093aee71d0cdeb017fdfcb13bd068039746de90690ce150a0bfdbe7ddc4d539df0f82c2d2890a40b191900594", - "0x96fa1f2196a3883cdd73c66d28403cbbb58f6a939a3697ee0d308d8a076393cbb4be86255af986869230ee410c01bcfa", - "0xa8b74438dc5cabd70a91bf25601af915c4418d074327a9b01e0190c27d3922c89bb9b41e0b366e82e313edda8f21983d", - "0xac9fdc1a9b2e3ff379eb2370979372e13c4177bf4574f1490fadf05a7073e6d61e703e2d8eed9ce984aba317d411e219", - "0xa45a6c9b958169f2f8df70143e6ac3e2f6f969a4eed6fd9f1c620711bc2454739bb69f0094079464790c5429c0d8aedd", - "0x8901cbdd1009864386577842c1e3d37835fddf834064d9613b4559ea9aef3084204e1f863c4306f874141f4374f449ff", - "0xb6c582161691e3635536686825be9c4d7399d668a7675738417e0363e064dfd28acdbd8dbc9e34c1dab8a1990f1f0eba", - "0x89e89ddaf3cacc78428f3168549c161283ca8337345750667c98212717b21e7d994eae4e45bbddacc832a18df1d79276", - "0x84be275627eed8e1a73c7af8a20cee1ef5cc568cfeea7ec323d7f91b44e9653e9aeed47c1896a8240b99dde545f0e1fa", - "0xa779a54ab4f40228f6e2539595fb8d509b70aab7c19e1928c1be69ec1dc19285c3898cf15e5f8b8bc725e13af177fe17", - "0x92e2a49d2b9b36349d442283b17d46f8f9bf5932c34223015ce62d2f285e7363b2c12232be4a838b5b6cf08e694c094c", - "0x8b4e28c6f3f36caa2cfb82ba88066c830f8017bd35608b077143dff236f3181230166f5a5c02fa0e5272297331726aed", - "0x85fd77d46162ffac4b8adb25baff0eb0512a53a3d01638b3a376ea34702279ce21c8e7d8884308c03e00c9bcc1a9fd29", - "0xaad5e46916ff1be29009b595d1d8fa160cc7aa01c7fbf3a68f445c87615790dcab1fcdbdceda533d182b6541f09f2f73", - "0x948df7654726250dae393325addd3c0a20431c81f00470962190335ea4b6d9f7463d6f308cda46b92084c1f24390b1da", - "0x8f577474dea132676504376c5542b730b6604fe3d965eaa194659fd11c52233bd0b11ab62e198c0f442327ff1c00e501", - "0xae2f1001546db3e0c19700adad997cd9f765fe7a51a502cbcd9a2a07a3a5db79c8f603e05cf96d80b688cb6c9b6cd3ae", - "0x953b68e5d9561088dd20406ea7fb6894cba33868a38ace38fc30b5813140cb15dd6dd2171befae5b4df2e4a9658889d8", - "0x86c52901655ff11419b084a04da8fc3596eae59d81d3461601c0baff59ba59e3d1dd0b7ce719e741a3e97c013e898579", - "0xb9a72dd5eff73f9912a28b55de073568efb3eb0241a10b77a2bfd4f30c2aa4fbfe0c89eb345c9f07fb725660873cb515", - "0x8e7353f5f2932e4ffd95811caf46c9bd1a53643c27eb41a4ebd211f230955cd71a8b27e17cfe8aa708d8514c0de67a66", - "0xa096b8e66312a92fb10839ebe60189a8d1bd34dff55f7dfae85e4d2f53a1a4a88211c19fc84494f066358ddce82be131", - "0x931c5cd82719d76596832b007969b5f75d65cffabb41b9dac7910300db677c1309abe77eeb9837a68c760bb72013b73a", - "0x8ba10f5118d778085122065b55dd1918fddb650cce7854d15a8f0da747da44d7b12d44fc29ad7dc38f174be803db74c6", - "0x8c971deec679372a328587d91fd24ab91043e936ca709c333453d7afd43ee256d08c71cb89f0ab0e89ae119831df6d86", - "0xa2ac28a58034fbd8fd518f409221bad0efec52670880f202e09c0530e2aabc2171ed95e99891790596ffad163d86c110", - "0xb3354e3dfa8068aba4f3741152b9204baa4e342c1cc77e6dd1419cbaf8da1d118be605846b8609e997d6a62a11f3423a", - "0xa12ab65a213c9d95c24865fddc2dffe0cf9fc527dd6bcdacc1bd7271e79929a4ab3427a231f4f49d0530474e6cbc88f9", - "0x90afd65b7e6973f8aafbe74da0f42441840d3c93bd69bc1bec8fa56824e7ca97ad1b427c8a85da7d588469bd4ccc50c3", - "0xa09175940c59489bac3d3da3a4091270d9118948cbbdd57f2bcc63fbf45b8010651c801d3e58dccf42733ce1d6b446a3", - "0xa843bbf286e3cecc1fe370ff1bcf5f1001bc2e95b34246625ff50d48ee62343e82fba2d25b8a4bd5f7b5ffe90920efa2", - "0xa3c4d1003219157fdbee2707ce07afa6c2a64ae8e450182c307ed7f070024071f30b12c4b0032960ff913c74e73a9976", - "0xb24af3f68d66f825d06fc3ff94fcccebe28b1a0d4ba29c48d3a3c953b9bf7ae6707f193fef25e2dcbd2b74e483c774f0", - "0xb0f657f7723184ef7d7e4381143f1ac8020d8c6c6f2dcbebb0eaf9870d61a81f2d452596503311e46d1b38f625d4756b", - "0xb90091004fc8f6205c51bec68547ac82dba0f5525631e7632cf6efe54eecd9020729fbee6105d1b8012402d3b79c54aa", - "0x8e3fa187713c60eb0a416d6900a894cdf81e6b6b69dae0bb64f6287f3c3f030cfa85c665f7aace1eab4937f380b8f728", - "0x879bf0784ccf6725c9cd1ea8c49fde31c91c605de1ea664a33c2ce24c277ee45d20b66309f98d989acb2ff3b77e13101", - "0xaf3f3a3ddc4e11abd627d5aef8adffa91c25df5f0c68b4d2b5d51e7d9af3395ba4f6f7ae2325a6672847e1ecc6cad628", - "0x973e667289e796d3a40f072e6fea575a9b371a9997cf8961677f8dd934619ddc47c1a3efe91bae9ef95acb11a8fe6d09", - "0xafa81c5606de82f46b93f4bb6db3fc0670f4e0d1091388b138a66b3827322d95a56168c951c30831d59eeadc227500bd", - "0xb83eff77db5b4c18574662942eb36f6261c59f655f8a9c3d3731412d0f257c8e80aacc995c4b2303058a1ba32522a434", - "0x912e5ac9234b9445be8260393ff08e4859a7a385e800b74d1534eeb971f58f74cfb518dfdb89f8705d89fbf721439129", - "0xab27c8ece4a51d23e22c2e22efa43487c941139b37ea1182e96efb54ca4809d8245eae0ebe8ba94f0ed4457896fe11b1", - "0xa6630585d104a745bc79dba266d9292bbdad346449c8ee8140a5e6e8a6194411df9cdbf3d3ef83468a536d4f052e9335", - "0x8b8c128244da48e7fec641a882d0005a2d05c7138d86a293e6a0a97c76bf632b44767d0ce44663c975e7f9f9679e25e3", - "0x87dbcaca67351a4e7d2297d7cdba4796d12f58857e7ee4abd0645563577ff33544a44cd84e50b3a3b420d6998de9b57c", - "0xb859ba43df259d7f8e7fac70bfd7aae546d57a5dc90e107b174a95bf7fd3cf00f740c4434848e69b2a7e6061f66c1ef1", - "0x99d6e20978fefc40c6d310187eb2ad3a39296f189ee122ed64d74f81033c3069d44f7a9d3988a1df635b609603a17272", - "0x99a5ddf3420cc0c92b21f71a805245608d4995ead447d8f73a670d26d33e26920d5f07bfe1f6230bd5f15978055b4253", - "0xb936ac0944d3c5e4b494f48f158000abb37b80b5c763f77fe856398c664b0f1ddbcc0a9a2a672db9278f08b4bafbe2ec", - "0xb4af85fbf4040e35a686dd016adec037c99b47cc2e4dfccaf7870ee9e8c97bff30f3035992def2a9d4af323c0b3af8ae", - "0xa5ee32b8bd5f8fa9000da4da0bf00565659a43285393d37080b555d0166bde64d87317b2eab2d48a0e7b287caa989be2", - "0x894d4ad58ecb1c9ebc4f5a97407082e56cb7358d7a881ba7da72321c5027498454f2c7fa2bd5f67a4b11d38c7f14344a", - "0x965be9eeaa0d450dacc1b1cc2fbf0d5d4b0dd188f2c89aaa9260e7307a2a1eb22db6092fccb662269e9a1abfc547cabb", - "0x805893c424aec206260c1c2d2509d2cb9e67ee528bd5179a8417a667aa216a3f318ed118b50d28da18e36c01f0805e3f", - "0x972d7040d4963b35260ef0cc37cd01746f1a2a87cedc0dc7b0ee7e838c9e4573784ea743f563b5267eb3905d4fa961ba", - "0x8c7156991d4c2e561888feaecf501f721b4174e7d14109e9deeac5a9d748301c07e11fb2b04b09799f0d34ff42cb77d1", - "0x894722ac35af3d507e81d737d21e16c5ba04686f8f004aa75934aae5e17acd3e065b96e229eb011c2f34096f4c62048b", - "0x81237937c247c88e8e31e2c72412189fe59c1daf65c5513489d86cf29ee922c0bb08e5f7890f09f4ada7e5262083d266", - "0x8cf62cda2fe0d9a6b42aa2a1c483f4ad26378c7cc2c2d1510a76df7560b07dba8528b33aaacb15f7f20b9d4c7c9f61f6", - "0xaaf0921fb3e1920eee5d0acb59dcc268b42f4b435d60d25d30357edd7dd758d035919691bd15311d85489dfa2e5ee696", - "0x92cec07be2247ef42002ebcaf65ec855611b8e893a5675796f2225f55412201b0bf9f4761924d0c8377b9f131e09e39f", - "0x8e514a62ac1e91773d99588415426c97ad63e917c10d762fe06ace5277a5c3bf3730e4b9e5d116f8493b9ab8687b70e3", - "0x83932df2d923a5052468a3ea87f7b55c6a80ede3594046ee4fe233046570921822bc16555b92ba6aeabaef9b1dc0805a", - "0xa2b5bfb249de3472113fd3f35bfabf3c21d5609da62a27ea6aab5f309c9068d94bc58ba03efb4ec11be06306d59e60e8", - "0x8106cf3ebe6f0507be8c6e8d137987315fe3689ecb75bb27980f36ba5efac504baccea0e7603549b6d126beccc278804", - "0xa73ee70b6fe8c082443972102c453fc0e386852476cf22224fc0bfe554735c12f96037fbf10922795f4502c4f052b5f4", - "0x932b27e175440169958504f3ed6400e7d6dcd5e716c19dcd0f15c56c04503ed133d5a993e111c016f141e32d68b29886", - "0x96f7ce4595318e0b4a6b368f788ff82226aac676aed4ace343867f751de414453a9aaaabef6e6224ce5aedc3d5cf77c4", - "0xa950c1e3bc9a14484997013d44d876374b939af437ae7c821c131fb886063ee9fe7214a25a0c7084f0b07b99412eff75", - "0xa9dba3886ed6855303106a1bdd26010f294218684e1c178afcfea3f37a2f04fd01724a31d82de3449046617e3507a115", - "0x87a2f776b32a6b550cf3ceeaf78db02819be74968d228b1d14e0d74a1cdf994bb500b7abef6619455e98d728701fac5c", - "0x8cd887b07e335edc0b27e6a660cebb64d210741395be431d79d570139687b056557159407459799a8197b6079644f666", - "0xb81a61fce00588909c13a90c1caa150f15788786af443ff60ce654b57147601f7e70b95659e01f470334a220b547611b", - "0x8aebc51141544c5f3d3b99422250424b9800031a8fdfbf22c430907a3a446fecaa2392105d66d64b1c8e847240da4a6a", - "0x90db7dc12baa02f3f86d3edadf9434e2b9318d4f6f0eca08276b765dbb38d8eb0d08be2fe70adf2bf16ceda5db08d3ca", - "0xaa1839894152d548cc6ad963de20fb6fcc843bc9af2a2bf967c63626b8ad19e900894d6106265f38f3afccca317c22f0", - "0x848e27b741496988a582515c0c8847b2bfc6a001259396cdeea1e1b1d2828ca3a626693a1bf4adf3a3d7f8b1fa3d75fe", - "0xa0aa11754d4ee136ac3ca609b17bcae77758763b2016544ca7921dddedd8aafcc7ad5f2b337c8bf53084eb8e43ea41fb", - "0xb8713b7aa1c112178195fdcc9b7024f46e6bc04c4e76c41abe620aa265287809200d98eaed6c9703fa97e81d6964f0ec", - "0x8605b5b33309e9ea6823542b85383c496794b8481c577497aaf99ba90496e794dce405be615bf92c7b6361460e6b82e3", - "0x826fa34faa7f83e063a7bf172addfc07badabada59cfc6604fdf481d29085251c0a67a1355b2cbd374e2975934b84cb6", - "0xb45d131082dc16fa53af010d43eefb79200dc23d2f3ee26af95ac6a5cebc49c84a9ed293e534ed16ff3ef9a4a25456ec", - "0x91bd6ce3c5396a7a0de489e49f0cdf6dce1cd2d0be7a410326423c3185bd1125ce1e610768be7f15f4e44b62f8834fc3", - "0x903ffbe3d33fbf106c01c727dc3a385201a67ded70d4df623934882f69a3a96c909b027a124f3d70cb072b0046a149e8", - "0xb405359db9d9ef4821a181b440ef2918c240595141d861d19a85867a5afa74d2972d22c988775eab441e734700bae4a3", - "0x8abb756d027233c83751910a832b0ef4d28d100077f1c5d656720c94906f91d85dd0ea94b1cc0ed95b692efee14c786e", - "0xa78ee77ab476a41a3454160ba7ca4085d8b1f7057c63e76db8b07cf20afdeddd2250cd00771a6329133bb4ad48ccc20a", - "0xa41810271d8c37197aa9b3dfcefe3498e42f5978d3f3d59defff4676d6402d8575b40683834f184f143b6cfbfc859b3a", - "0x90c24a0750242660bcc6d487358a3cc015730538a0a8beb00ad5ac2ef33cb8ca8a62121e50bec8f3d2f43900f8e3134a", - "0x8b96c39695d864ef5796941754978a1fd612b369f6b77fe5ae6587beac936ee28190af8f0a3822b63060af35e49a5c8b", - "0xacde2548883d0e63c0fc257bb9dadd919aba60a985b69ebcfa1bca78acca42fc1322ec30bcc8e7c188818f858d04ad33", - "0x895c86ae9ff8d95f2707d4838a3bc8ddb05b2611f0476f014b9c150d0e8332bc73285037a747426f09ac8179ba4e19fc", - "0x821761fe406e18bd86fa9ca9db99d382cd3b5c70c456f471fa3706d57763d147706304c75d54f51ce8f3115aa26e59d9", - "0xa803a80e3e8f47dc3c59ea23eafdec017458eac648b360cd42cbd075e0dde6f6f450b48c7646fb1e178c04f82ae51a12", - "0x91f40e1b6f588bd592829ce937996452c40be0fd6c43793c607866701ac6a8c7227e0891d45c6e7b1599382b0a3fbdbb", - "0x9408246d996a634a58689337f2526dfb3ba9ffef1d3ff91c32aa8cbbed900861ef25d6477308b67d76491edfcc70d65e", - "0xa492325a427f3df1c9c690c5b553daa8ac41f62f5ae55f425539222bacf959e2f67afabbba1732e120d3e7a6dcdf7049", - "0x8fd0c3e15477cae228613a171b6e9ec29ddc63ef74854d99b638adeffe39f89f34346a42851e8445e855a9f2bbef0f57", - "0xb735ed01fafa051004dbaad5e8c9e2faca8f6049ef9b590f256ea4d75b04594af12764ad4e6031735eae36f83179db93", - "0xa7d35f43fca06c86b3425dcb68a87186834ba9740664fd657915771beca4cdc0fa2fc9b4c2e9d9bdad8ec33543ddfa59", - "0xa1156e71e2db1b17df5da28747c88e091bd687bfee59d89096437ab4dc9a543fe5c5272d5023d72adbaab397a6fc94d1", - "0xab06a58bd81b33a411bade8d8c5232d38fadc2e38507159edea6e2e104b8ebd65ca02b05335118f691d44197b847a4dd", - "0x848b67a10f1e6ff8f5c228f226ef2ffeb67fb8f50925fc94cbb588d61896d9dc79726959e649898fd3354fe3ff7b7ee3", - "0xaa933397361f32b388edcf832f0db172a38e756b34d5f7a4a050fa7325058006c22cede26ee27917e8f1b0f301792bd7", - "0x89e49e7f02cfaae4a4b9c4180c9f6559d76e3a45774955859d4147970b1470dac37bdc9aedca1c32a20b045049161590", - "0xadc1825d5ab94fc719f25d8c9773f4d518134ed88eb13ac33cb910b2be3523ef9ef88d9e4aea2418b806e20108317bf6", - "0x96c4b444c8a023da644f3a343ebeeed19a8392d2ce175992461451c318a54273b76c3574d8f2dceda2947ddd34d1a674", - "0x8aa7e97e87c8c5b29bbd51a6d30396a6be1fb82b716ef83800f2c36d5b85467ade7e0f59d2db82c310fa92a9265f0b03", - "0x9146c32d99f02c3a6f764dcd9b4807f1585f528ac69dc4f84e4380f6fda4f9d5057c375671d51e7aca2b2b4140e83da0", - "0xa10760a533d9bc57536bcaf65f080302086aa50225437efd64e176841544711828c23a15c49c0dd1f357d3f10722ab72", - "0xacb0811777e17f7ae7aaba5f6fce81b759c067a4908730916195a2505c7450d0e6e2194c2ef0f241090597d58e70de47", - "0xb24f161e9bcdbad56665e2490b5e4c7768390d4668cd69a04ed74739062dbe832636dd33cda89e9b0afa8c77e93fc641", - "0x96b4d01106b831868a88ef016500ef2fa42d0ce87a37ca8ca4194a92a22c113edfe04eb2ca037329f3c1acc635148f55", - "0xaebbb95fb4f7adcc8e7a217aeb73f9e037cbb873d08c1cd9d68c6c6834511adf1af8b44567fee84327599bdcb734dedb", - "0xa9bd8b17300532fb94d028659bcafbe7bbdf32f8945baf5db4cfaa1bac09e57c94cad0ba046b4514044b8fe81ea8596d", - "0xa5557cbda599857c512533e7cadcf27bf8444daa0602aa7499cafc1cf1cf21f9d16429915db7485f0e9a1b5046cf01c5", - "0x8810307c40bc661c478a9747ebf2a30e5a5ead942d1ac0418db36ba5db0709c476f7d19685cabe6959e33ec1f3bff914", - "0x8829b741f41f2c32e10b252d9338deb486dba2f23996a44cf1dd888ad967a589d51329be34d764139f372a1043f6c2e5", - "0xa6b4728d18857c5fa082fa67bfb3b1d801e76b251b1e211a19c87cea5fe7ce757f943c85071f7a03a718388cd5690e95", - "0x86da7f397e2533cd487f962ae58e87bea2cd50af70ef2df9ea0f29f70b5843cde664d30ec207ab84fc817f3851277e02", - "0x8085776ef4ac6d42ab85b9d9135ecc6380720efd274f966544eeedf4684028197de76ecab919fa5414302597e1962bca", - "0xb05a065c733033d223ba13d16baa7a97bd8c8b8b1f0e59a9bdd36ee17e9922d48eb39bd180c168b122088a77f0bf321a", - "0xa89343fe44a93023dcc7ef71bd3bcb6786f68e1885ad260edc56a52445d34757f476395ba7ad35437f89bc573c7618dc", - "0xa114a9cd6105b524f3969c69faa2e09afe21753a93361a296f9e0e3b4e3e63726ddf2e6bfd3ddc046043e50bd44e539e", - "0x8a5611fec539cf681c05636bb580f29acc06f628bb012649ffa41ea6c1521194a5643d5dd843f09b6eb2c3bdb4d41acd", - "0xade247c4011ec73ec90b72f35afa59a999e64ba5a7e664a4b30874fea53ba6a14a76a41b58a5f891a20d019e5f091bdb", - "0x905b5d96df388160ade1ffe210d0c6d1979081bc3de3b8d93ac0d677cc2fc2dc1ef6dcd49d3947055514292a3fa2932e", - "0xa9520796ca9fccd11b7524d866507f731f0f88976f0de04286e68d7cf6dbd192d0d269f0cd60fd3d34011a9fe9e144c2", - "0x989a1edf4d7dae811eb57a865c8e64297837ffeeaae6ee6ac3af0f1044f023f1ca552bf00f1642491f0f0f20e820632e", - "0x879c8e63713f4935ed6e020559e140ea3073ced79d3096c152c430141272117b4fd9a9fc3eef012e81262df02ea14bd7", - "0x95074738ac1540c0312274333acd1ecad9c5509fee883c4d9295fa8d8200f6e637c363de395f9fa612f05c0dc58fae88", - "0xa770e4fc595269eb806b113ab3187ea75c8f96b57bf9fcfaf535f3eedc1d4d7e6285a20990575de0ff09f62d06ed0692", - "0x81283e5dfb6423439ff513eca1cc316941d196df8da2d1069d2d0b63f5289e630af2fd4119bc0144c002d33313372dab", - "0xabd1b108e743887b78f698f2aba9d5492f87a22868d1351d705d93a1084fd45be67170c68a6e18b07f400d9a01cda8c2", - "0x8509c3f67b92908cea8144f4e2a71631a66a61ac3547601c788907e52e380e5fe8ae4110aed95d13c67d3bcdd5b55a61", - "0x8fa5a790ec5cce6d4114128c295390120869aac5490a82feebd3c37a167120df2e7fdfaf2a4050a7dfebf48fb093212f", - "0x944753e1ea7d8bc727d46a7702077dc01dc0c6574e8263a16579b57ee155ca5901f71bb347a01a9a922b329d3ff75135", - "0xb46bc1fd4590b7a6275e20036d247c5909fc549c78e95b64ae7ed96e3b05bb044840f19f7650ebfe7008ba09fa83c3c9", - "0xb1e47e4d88e59a06c465348c6cc4181d40f45b91e5e883966d370c26622c328415c6144aa2f61ddb88ec752482c550ca", - "0x8bd4f8e293e3f1815c7e67167618fb3b0ea76424bc0985908957cfcede36109378e41b4d89555b8c2541b4c447e00461", - "0xa70589a867b2bfb63d0106083d58475d506637148549ed35c83f14e5c8de996e1b1f3447ecc80cf5cd134ef4db9d2fb6", - "0x8048b80ba6131d07370162724127b0f7cb17fa7f71855e55e5a75bd0a9e4fd71b0d0ea2d16ec98858e458528df8d06b5", - "0x97326cb94bae7530f4ec3235770c5a7ba042759e789d91c31fedbd979e3c0e6a2c69e2af3c1979c6fe0094274dbd53ce", - "0xa18e9c1d3eabd62af4e31a4b8e08494f4167fd4598c95d0123f39c46c53f9e93f76615900246e81a286c782ac37c569f", - "0x80309c59d4522b15aba617cd3c6238663e8b1c7ad84456346082c8f281140fc0edf9caa19de411c7e7fb809ca4fa3f4d", - "0x8e450c0990e2f65923f252311623038899eeff7b5c2da85b3a224e0ef7132588b291b782d53c477ecb70f34501466178", - "0x87843f96f41484e254e754c681a65681b9ae5c96c292140368743df9e60f7e2ada58ca2bb95fa39abe064b2ebf21eeba", - "0x858e8d5bf2a1cf26d8af5036b28b831d450a446026f58a1734b696c18f1f41482796b91cab0e5b443dd2f0b9cffa52b4", - "0x99627dd6bad8c05c5904cd23aa667d664da846496dbbb8452705c4ec01e1480e9c7295504a5a8529e4a0c842306b038d", - "0xb64b33256c18b2c886a837a0c0730fdfe73befb0e2796207c4dc592c5a33cd51f8c2ef47c584dd5773abf9ce9c1b0082", - "0x944f6da2a1546f0bfc4d98c3e73c79e935e33d208b6be26b0b5f8df6d0e3b74a5bda649853b99281bd3a3ec799a7dd04", - "0xa266d165435784d4e884640155e35b2a911b3f89e1e715986de419b166a36a341ba724877d80583fa3da566f6a828971", - "0xadff2698409d0756e78c534032ee926560c13d578cb178d5073172d049ebbce32a92692f7e2033ec781b9b0d894ddce0", - "0xa91933f110756c699c28bf9e24fd405bf432002a28c4349e0ca995528e56a5a2d101b8d78afa90a178ff1a9bf2ba515c", - "0x8e77839c0eb4da2d01e4053912cd823eddffbdc6b9c42199fba707ca6ab49fc324288b57be959fbfb11d59085d49324a", - "0xaa124517c76692036c737e987f27c2660514e12a953e63ff4bcb269dd18fc44dae95e282de8444bed09639ef6577af88", - "0xb285deae99688f1bd80f338772472fa2b35e68887c7eb52c4ef30fc733812444c5cd110050275ad999d5a9b57f782911", - "0x8877b0fa85b44ef31f50bdb70b879fa6df5eb1940e2b304fd0c8f08abb65f3118fa3d97ff93919038c1e452fb1160334", - "0x8a89f3b50dcbca655024542ca7d93df17deff5c7d01c7da2bdb69e76b3e0b4490d85c800fb3debb4b0b4d20c9527f7ad", - "0xb7e5dbe36e985354ac2f4ab7730fea01b850af00767a6c4d8ee72e884d0fe539bb81f2e34638fcf5d07b7c8d605f4c06", - "0xa85a1d78f6d4f9d5d83ec0f2a426708342d4e4a5d15625554e8452f6a843d9aa4db0c7e68caebdaf767c5b3a6a6b2124", - "0xa518078a9dac63c5bf511b21ed8e50d1ccede27ebfe9d240937be813f5ee56aef93dc3bf7c08606be1e6172f13f352ce", - "0x91144eedebda4d1ad801654ef4ecd46683489b177ba1de7259f7dd8242c8c1700e15938e06c5d29aa69f4660564209a0", - "0xa16c4657bc29d1d3271f507847b5a4f6401cee4ad35583ad6b7a68e6c2b9b462d77b5dd359fd88ea91ce93bb99130173", - "0x85b855778f4b506880a2833b8468871c700440a87112fa6a83fd3ddb7e294b3a232d045dc37dfc7100b36f910d93c2ae", - "0x8d86bb149d31bfbf1fabcae1b8183d19087fd601c3826a72a95d2f9cedb8bb0203d1136a754aa2dd61f84b7f515acfa9", - "0xacfe7264eee24e14e9f95251cbcfdd7e7f7112955a1972058444df3c2d2a1070627baefada3574ebd39600f7f2ea7595", - "0x906bd14ecca20ac4ae44bff77cc94eb5a4ecc61eba130de9838e066e8766ed3b58705f32c650e1e222b3100691b3806b", - "0x8f2cbc7b8593c4be941dd01b80dc406fe9dfdf813ef87df911763f644f6309d659ea9e3830ff9155e21b195fc3c01c57", - "0xa68eb15ed78fae0060c6d20852db78f31bebb59d4ddc3c5bdd9a38dbe4efa99141b311473033ff8f8ea23af219bc8125", - "0xa95cb76c9d23fc478c7e8a73161f2ff409c1e28a2624c7d5e026e3cee9e488f22225a0c5907264545a73e83260e3a4ec", - "0xb76f90e55fa37c9e2732fd6eba890dd9f1958c1a3e990bd0ce26055e22fe422d6f0bcc57a8a9890585717f0479180905", - "0xb80cc95f365fabd9602ec370ca67aa4fb1219a46e44adf039d63c432e786835bb6b80756b38f80d0864ecb80e4acb453", - "0xb753c86c82d98a5b04e89de8d005f513f5ea5ea5cf281a561d881ed9ad9d9a4be5febb6438e0dba3d377a7509d839df0", - "0xa664733f3b902fac4d1a65ea0d479bb2b54a4f0e2140ed258570da2e5907746e2ac173ace9120d8de4a5e29657ae6e05", - "0x9479722da1a53446e2559bb0e70c4e5bf3f86c0ce478eede6f686db23be97fcd496f00a9e174ceb89ab27f80621f9b80", - "0xb707fd21b75a8d244d8d578f3302d1b32bb2d09f2bd5247dff638d8b8b678c87d4feab83fe275c5553720a059d403836", - "0x93214c16831c6e1d6e5a1266f09f435bbed5030c3c4c96794b38d4a70871782002e558d960778e4465b1ff296ffedad8", - "0x8648f84e18eb63dad624e5fa0e7a28af2ee6d47c28f191be0918c412bf24b5460c04bf2b7a127c472914a0741843f78b", - "0xb67f61e75d6b773a6b58b847d87084b94f3cdac3daa7bef75c2238903a84250355a986b158ff96ba276ca13a6035fdd6", - "0xae9b094b7b5359ee4239d0858d3755a51aba19fce8ad82b0936cca48017523319c3309409ea6e9883a41bece2077e4d8", - "0x8d1d8e1fba8cebd7a0e1effea785a35e16b1a10842f43e2b161d75add11eccf8f942d2ae91c20eef6c1a0c813731ea9a", - "0xb82bd387458e3603782d5e2dec32ae03890a3fc156d7138d953f98eff4200de27c224f626e3648e80cd3dfc684c4790f", - "0xa6dd02a89ad1c84e25e91176c26355e21a01b126c1df4d22546159dab9d502dbc69bc0d793a017c1456516e4aa5fa53f", - "0xa9ab74a5c5459b8500beb0ad13e9cfe2656e966dc9b4f3f98bec7588023b4ddebf74e4fc722d30423f639f4ee1b2587f", - "0xb03e5f33ab7ecec12cbc547038d3fa4f7ea0437e571891c39660c38d148212d191be29e04eb2dc001b674219b7a15a9c", - "0x925df4fc6e898ca55090ad1a8f756cc5014167a042affda5b24896eeb6aac408545134920586a8e1a2b997de9758b78a", - "0x98c8580fb56ed329fad9665bdf5b1676934ddfb701a339cc52c2c051e006f8202e1b2b0f5de01127c2cacf3b84deb384", - "0xafc3765d374c60fac209abd976fe2c6f03ce5cc5c392f664bb8fac01be6d5a6e6251ac5fb54cfcd73e3b2db6af587cbb", - "0x8e7e98fb5a0b5b50d1a64a411f216c6738baaca97e06d1eba1c561e5c52809b9dab1da9f378b5f7d56a01af077e4f8cf", - "0xb724bf90309651afb2c5babaa62dc6eac2b8a565701520fe0508cee937f4f7b6f483fc164b15d4be4e29414ce5d3c7d4", - "0x9665160e7bf73c94f956ecb8ba8c46fe43ae55c354ce36da40ccc7594beae21d48d9c34d1af15228c42d062a84353a0c", - "0x8600ab3aa86b408ee6e477c55572573ed8cfb23689bbdadf9fccb00161b921ec66427d9988763a7009b823fa79f8a187", - "0xb0d8d19fd1022e7bc628d456b9bd1a2584dce504eb0bf0802bdb1abd7a069abbeeccdb97ce688f3f84a229342dbc1c33", - "0x8f447d5e5a65bb4b717d6939cbd06485b1d9870fe43d12f2da93ca3bb636133a96e49f46d2658b6c59f0436d4eede857", - "0xb94e327d408d8553a54e263f6daa5f150f9067364ded7406dcb5c32db3c2dffd81d466ee65378db78d1c90bc20b08ab3", - "0xb58c02781b74ef6f57f9d0714a96161d6bfa04aa758473fb4d67cc02094cd0c0f29d0527c37679a62b98771420cf638b", - "0x8cfa0a687ea51561713e928271c43324b938aa11bb90f7ffaa0e4a779b3e98899f2af59364ce67b73a46a88748c76efa", - "0x95d6d39c814c5362df69116558d81ce6f1c65fb400fc62de037f670d85f23f392c1451d43341c59bc342bc31842c8582", - "0xaf888b384c52d9e04e4db6c4e507c2037eb5857e9bcc33acf84fc3a02d93cbde8cce32141fce9f5fec715b5f24d56356", - "0xa7822bbc3c236fd58bd978f0fc15fe0b60933a0c953db6436a233441219418090ae0c07c490a6548e319029771cdaba7", - "0x8c53729f750922e5eb461774be8851a3f40fe42eed170881cc8024d590bf0a161d861f5c967144d15cdcdc3dc6b5cf88", - "0xa052a25a4aeab0d5bb79bc92a6ae14b5ad07d1baca73f4f6684ccecfc7ea69bc21eadeb9510452fdba116c0502dd698f", - "0x923946b83d37f60555dbac99f141f5a232728c6eb819a37e568c8c6e4d9e97a4229fb75d1de7e9d81f3356f69e6d36f1", - "0x8cab82cf7e415b64a63bd272fe514d8b1fa03ba29852ec8ef04e9c73d02a2b0d12092a8937756fdec02d27c8080fb125", - "0xb1123314852495e8d2789260e7b3c6f3e38cb068a47bdf54ed05f963258d8bcabaa36ccbea095ba008e07a2678ec85a7", - "0xa685b779514961e2652155af805996ceb15fb45c7af89c5896f161cac18e07b78c9776047c95b196362c9ad5430bcb22", - "0xb734dd88f6cc6329c1cb0316c08ade03369a11dc33191086c6a177cf24540c7ceee8199b7afa86c344d78d513f828e81", - "0xb0bf492fb136ecdb602c37636ed4deef44560ab752c0af5080a79c9f76a1f954eba60a0bf6ba8bd7b8cac21848c29741", - "0xa5c74682323e85ac20f912ab9c1d6e1b9246c4c829dca40c8a7d58ec07ea0ad3524be30623f351269552f49b65a1245c", - "0x837403b9cf830fb33ecc11a7c8433e07745973c36acdeb3fc9ea8f7d8d690d462e1250b7410f79f2f4180fe8f3962a4f", - "0xb03d64b944d49c83608f2c5b9c14070c025f7568c4c33d4eeb1da31d07f0bc5897e498b35b50d557ee129f0c3c68e254", - "0x827272aab8bf757e2483156e00fbebe1093a58070dd3af9855bbf946c7abfb9c8a850a6a8acda8c620902f391f968b8f", - "0x84c4eb863a865282d321302d06b362f8bd11c2bb0090f90ebffedd3eb3e7af704cff00d39a6d48cbea4262942e95200b", - "0xb044eb91653dc55dce75c8d636308a5a0dae1298de4382d318e934140a21ca90e8a210e06fdf93aadbbeab1c2ef3904a", - "0xa8c08955a4378522e09a351ecb21b54025a90f2936b974068e80862803e7da2b5380c4b83b4b4aad0409df8d6c8cc0cb", - "0xa763a5fb32bd6cb7d7c6199041f429782deacac22b6a8467077fab68824dd69343ebca63a11004c637b9cb3129dbf493", - "0x8c44c8afa9a623f05c2e2aba12e381abdb6753bb494da81f238452f24c758c0a0d517982f3999d2537b7279d381625ed", - "0x8613f47fda577cd3bda7c99b80cf4b2dd40699edfd3df78acb5e456dd41fd0773bc8da6c5e8cbf726a519b9fb7646ccc", - "0xb21a30d49d7e1c52068482b837a4475568d0923d38e813cea429c1000b5f79b8905b08f6db237e2eccf7ef3e29848162", - "0xb9bdf4915f3fbb8d84cdfd0deedf2c9dc5b14f52bf299ef5dca2f816988e66322df078da2c54b934b69728fd3bef40b5", - "0x993b45f389f55eba8e5ba1042d9a87242c383a066cbf19bc871b090abe04de9ff6c1438cb091875d21b8c10fac51db58", - "0xa85a95d14633d52d499727f3939979a498c154fd7ebb444b08f637b32c1caf5cca5e933a2f5d94f26851ae162707b77d", - "0xb9874c7c4be1c88a9646e0c2f467cd76bc21765b5ab85d551305f5ec0b4419e39d90703d4ac1bb01feb3b160517e97b7", - "0xad6771177fc78812904c90594712956357de1533a07fec3082ba707f19c5866596d624efc3e11773b3100547d8f6c202", - "0xa79f31921134f7197f79c43a4b5d5b86736a8d3ad5af1bdf4ad8789c2bfe1c905199c5e9f21e9f446247224f82b334f8", - "0xa7f1b6c45321222a350a86543162c6e4e3d2a7c2dce41aeb94c42c02418f0892dbd70c31700245d78c4d125163b2cd5e", - "0x92abafe3ec9dbe55c193fb69042500067eb8f776e9bf0f1cb5ab8eb12e3d34986d1204136856fb115c12784c3b8dea6e", - "0x89bc761238a4d989006ca5af5303c910c584fe7e6f22aa9f65f0718a1bc171e452c43695e9f5a591725e870770c0eceb", - "0xaa0e44c2b006a27d35e8087779411ba2f9f1966a0f5646ff6871bcf63a8b1a4a7638751b94c9b9798ccd491c940bc53f", - "0x8736fe82862b8106e7fdab7b5a964d87ec291a74b8eb1cb5a6c046a648c1b686064ef3d52297043b8940bfe870c712f8", - "0x956a3def1942f05144d8e9c3a82fd2d3610064b53b9eefde3d5594a8f705bf8f6849eb2c22181796beffeba43cc74ee4", - "0xaf27416d00cf97d5a1f4a1b6b51c010884cceca294f1151c3b684a3f83c3c8a3c30771df1166d833cbddf6c873c400c3", - "0xaac3b8dca2336fc4ffc63c362df461289e4bbd3418c621bde6c581d3ecedf66e2b3e523d4db39e3d8ba014577bf85efd", - "0x94c3a8167f62074e5b28c2bffe4b6ce645439a9a0c5da3ca1b3ee956590a465d6f84a8a4dbbe9070ffbd6bbc734e4d62", - "0x95e23ba6986d25ed4451215da05bd72c5491528271726d79a94c8cb16aef1c85b190d6c5b8a3a1191c7cafbab1dccf0c", - "0x953e3dadb5ad68f7de31ac09692948655d174fe16d88b96930ef35b331da7f1dbc4c17863cd07b4ec3135b5205891a27", - "0x915d018f18b5d63cb3301c2bb5c6e85e75a88ba80663c964d06575b6bacbbe59139d030b218ce0998271d5b28c00b26d", - "0x8c871ba3dd138a908b2f7effeea0e71df096b23e0dd47cab10b9762b250abfd1221da94a8ee884e05bdf02271fb85a04", - "0x96bad5c6ebc3080ecbe337409ae398bbeada651221c42a43ea3b7c08c21841ddbcfde544c9b8d4772de6f2ce92c0b963", - "0xb5dbcd0b1c44c62108841558ec0a48df4b327a741e208c38b1c052321eda6e6ad01af71d49dfcdd445ab6fa6f0c34e6d", - "0x97dba59219b69e8aef2659d1f10bbea98d74aefff1f6451de3f41be39acbac0122b8ff58b02e90554469e88911ec3547", - "0xb7e5682ec306478be4858296f5d03364a61f3260636a4242f984d351a02e8723378496beb30c4ca22def9c9ca193ea70", - "0x9656a7a3df4d11df3d8bc35930dff70a5e78a488ca57bba20bb06814fc390fc6c7cb3f39b22134992aad196cced577de", - "0x8b269695aa63eb56d0324ba984279dc4c88e565321f1d61d553622bd4f1910d5eff68393d3a830eb924472bd478c2aa3", - "0x9177bcd04b28c87bc0440268b4c8995c6790cad6039594971b2c177f0e197055231e776927d3fa30d98fb897a2ba401f", - "0xae0e943973482001c4f214b9da82e1c27e38aa254d0555e016095c537c835d3702bc2de5c67b234ab151e02b3b7a43a6", - "0x82fc719a7d38bf4787fe1888019ad89fbf29beb951d2fece8686d2beb9119d0c8c6d13bc598748c72c70d73d488140ca", - "0xb716dc66f87eb16b95df8066877353962d91bf98cf7346a7f27056c2a4956fb65e55cb512af278783887ab269e91cd76", - "0x81d58cd8bc6657362d724b966321cd29a1b5cdc4601a49fa06e07e1ad13b05e9f387ca4f053ed42396c508cd065c5219", - "0xb32ad0280df6651c27bb6ddbdc61d5eb8246722140a2e29c02b8b52127de57a970e1ded5c2a67f9491ae9667349f4c46", - "0xb68a2eb64cc43f423be8985b1a068e3814b0d6217837fb8fbfd9c786db9cca91885c86899c50a1242040b53bf304ced9", - "0x85887515d4e371eabb81194cbc070e0c422179e01dbda050b359bd5870449c7950e6b3947b7a4a0eb68199341cc89fc3", - "0xac5fff3c27dfbab78eb8aad37ac31cc747a82401ebf3644a4f4f5aa98d37b8bf3b3f4bd8a3428b32a127c25c9e19d239", - "0x86fceaa6fbf8913553a9e1e907fcb1f1986d5e401a7eafd353beefd1899d571454fea96ff5b2a21254d9fb693ec94951", - "0xb6778bb296d3f0de2531b67d36fdbfa21475be0ca48b9dfcc38f396c41b557823735ed0b583e525a2bae1fe06e04058c", - "0x898088babeb5b9866537d6489f7514524c118704abd66b54210dc40a1c1ddb0a1edf7fe0b6e0db53b836f1828ecf939e", - "0xb27854364b97274765f0fb8d1f80d3660d469785d1b68da05e2bd1e4b8cbbe04304804d4c8aabb44cf030eba6c496510", - "0x8c55bbf3603dc11cb78b6395ccbc01e08afcef13611f7c52956b7a65ccf9c70551bff3ae274367200be9fc2d5cb26506", - "0x947726f73cd6281cd448d94f21d3b91b96de7ad3ff039f9153befbb5f172db9f53cacb4f88c80a3db26e6a0f7a846eb0", - "0xa7b733a05e97528812d71cecb4f638a90d51acf6b8fcbc054787d6deb7e2595b7b8d1cbe1aa09d78375b5e684a2019bc", - "0x8d5ca6d161341461544c533314fe0a6655cde032c2d96f0e4ea7e41098b8b39fa075d38e2d8c74e2d0308f250d6cf353", - "0xb960e9f081393e2260b41f988935285586a26657a3d00b0692ea85420373b9f279b2f1bb2da2caae72dd2e314045f1bd", - "0x852a49c7388c10821b387c6d51617add97ba72485f52be95d347bac44c638c92e9c6a44ba0d32afc4d59178a497d944a", - "0x8412162a65147e1334ad5af512982b2b48eef565682b3f3e0bbe93fbc5e1103db9375a0c486bdb1b2c57e4cb3a8e7851", - "0x8f52c3eb5d4f1e1e82cfd2b291d4910195427603b796f6c311deb35ef14a01a57a9e6cad39619ad108f3e86f384f9e1c", - "0x88d221088f2bf0103c53e44d0d96cd7881ec2b0a965db9121a47481771a8b796edd5ac23c4f9c208a171dab301f7d3bb", - "0xb49c3235e8b3617ed08a1891b9e2bcb33dbdacceb94ca96330555b7e00904fe6a749ced9312b8634f88bcb4e76f91cb1", - "0xa85834215e32f284d6dfb0cbfd97f6cffc7b9d354e8f8126d54598bb42d7f858a2b914cf84fa664069632db2ff89a332", - "0xaa3d48eb483c6120c27d9b3e3d0178c1c942632ff54b69f5b3cfbc6ad4ff5b2b9ce6eb771fd1eea8edf4a74c97027265", - "0xa446cfded353cdd9487783b45846402b973cdeddf87e2bf10cf4661610fff35743cc25e8d3b5771dcedfb46b018a5d18", - "0x80998377b3b393ef3073f1a655ad9d1e34980750e9a5cfb95f53a221b053ddb4d6985747217e9c920735b0c851d7551f", - "0xa35ac469790fac6b8b07b486f36d0c02421a5f74ea2f0a20ffc5da8b622ac45dfccabfb737efa6e1689b4bd908234536", - "0x8fb1f6d8e9c463b16ac1d0f36e04544320d5a482dd6ffaec90ea0f02b4611aaca984828bf67f84dcc3506b69af0a00a1", - "0xb6e818d61aea62c5ed39c0a22ccbb327178feebdabda0c9927aa1549d2c5bb0637785c4aed2a6d9a7b4989fa8634c64a", - "0xb4e7208d16018bf67caafe996d436113eac619732e3f529a6efb7e6f094d8ebea55b7be0e122be075770f5957b6ea6f0", - "0xb691d38b552befac61f6d367287c38d01fec73b7f2efdb6713ca30314a37fb7c177eb111fe6bee657f2681014e07630a", - "0x9817587e418e6e7e8e97ae27067f17b55d25dfb14e98f63f530620c855d9a348c9fa571c8508e2741f902f8b9fdc0c5c", - "0xb6a6e5ca779ba140bf1d84cd5394ede8262f7479637ec0087a4b152243a1774ba916d8115ce759a3bebd1b409de5f2fc", - "0xb53d1c84ad766ff794bf497db3228efd2cc8ed5fc1958d89c1126efdff361610ecb45ea8e329b39035ab00a66c1259c7", - "0xadc31333c507c8e0f4aa2934fcdca57fd9c786722a50dbd5404e129541f7ac182cc7373bf14e1e4e06e6cf94b31b90eb", - "0xa82b7fde4642d982d95cec669efee140ad797a2442c7f6620580527d163accbf021b893446cbb8038ea82fe25b15d029", - "0x91f7acf8a8903979afa281646fdecb54aa4d2ed905748e156e92f0910de268fa29d67107d40863935d677d1de8039be2", - "0x86fea71c6d43a7d93216a92fc24dfce8521fd4534a9558b33762d002081247867a6eff54cad7116023277fb4049403ad", - "0x8ae5369a7f9f4c91f3be44b98089efd9c97c08f5bb4cd8b3150c115ecd86288fa0865a046a489c782973a111eb93966e", - "0xb6fb9e829aa2c81c2d9eac72bb2fd7f3a08e0cd763532c2ce3287444d33cf48b3621f205e9603ec58525934b61a795a9", - "0x83e35ca808d84e41fc92115e9f6e283e928c3a614e6dfc48fe78c33b6411262e7bfa731eadb1e1937bc03cff60032e1d", - "0x832fca5196c95098ad47b7d24ba2f9d042e1c73ad2273edd1c2ce36386796ccc26e8567847697f3fcc2a0536a2a2087a", - "0x8fdb7038bc8f462ab2b76bf7053362f9c030019f1b6105cf42219a4e620ecc961e3eacb16a8e581a562a97f1418b0128", - "0x8d3a5a404b51b1ad8ce3b23970e0d5cc57b573922341008e3a952a1dd24a135e19e55b79d86a70cfd82e1c0e9630f874", - "0xba00c025c1c21c57c03cdfc0bfd094b35422281ff0a64b68b240617aa58c6b18800af5f2047d3ff9068bbe987d6c7980", - "0xb468f0dd51964b3806b0aa04f3fe28a035e8f5567fc7d27555be33d02701a838b8dbfe1348b6422c4eac46d2c75c40c7", - "0x8a73a18c97da9958903c38584b08d0e7e26993a5d9b068a5e0e1ee0d8a873942745cf795f94f7a3d3ba88790a9fbb2f6", - "0x953a0a40c2c8102723736854d13b228698c14a02d85c8d2e61db1a768019ac305faf0d5db62ac976430ce087a5b20f1e", - "0x8998219da6b34f657cb8a621c890a52cb98c2bc0f26f26e2af666eebeadadc5e8bdf4f830a91d04aca8ce186190152c8", - "0x8941e08c3155ad432236ed05460420a05dd0aaab30477493ffb364b14c00ea5b9183d30d3442b6321d2d20c36e4f5c7e", - "0x93f293ff7fb56cf5b03aee6f3ad2ad78444398ed5b3be56d7bf5b56b5aa5a2b980d13895dd57a5726d1b067c20cc55e2", - "0x84a16f313e3f75e31824f58d19ab24c6611fb4c75140a7cadc3c166f68819547c1d0ff7f7d13f5d8ae30dff1d80e2aa4", - "0xb6e3e830b15039d3e28b08f5465bb089eade11ee3bd80afe39e010df7db1fcf0c56d698717677a41ddbc91eeaf6544d3", - "0x95e928e6dfff51351281568ae72da7d1edeb6e9fe01f30af0499e7505ba35a22b5bb919d41bb809a432dce83f3977663", - "0xaabeeb60ca46f9b0232ff82ea7766dcab8cc5aaf9d23539f30174f9486640bc9312868ca493b59b314519fc399973e47", - "0xb393a11e957d0bbb3ecf617b075b5906a3450b348e62916c04791b366f0a7397cccd6648440ac544bc30526e1f95aad8", - "0xabb5bfc3964a6d246da60bd809d0ea6daf4f8222efdc12ceb6730194e85f413ee7eb03bae300abf7ea900dbbc3d08971", - "0x96c1bd1d1d216a4bfbcf000c123f296c0d31e1684e9e3884c14df23bf528c8d599f82bb98fcea491716b617216a8e0be", - "0x92d1e570a56f1741fd9f3d9f488cc336421c6256c14a08d340a63720be49b0029e3780e3e193a2e22bf66cc652fa22a3", - "0x8769c08551e3a730e46f8e5d0db9cf38e565a001dfb50db3c30fa7fa0e98b19438edc23c6e03c8c144581b720d7b33a4", - "0xb850bd67fdf5d77d9288680b2f6b3bc0f210580447fb6c404eb01139a43fccb7ed20051999ae2323ea5a58de9676bfb4", - "0x80285da7a0aaf72c4528a137182d89a4db22a446e6c4a488cf3411937f4e83f7b00ec7549b0b4417682e283f91225dfe", - "0x80520368a80b97d80feb09dbc6908096c40ff7120f415702c1614d7112b0b57f6729581c71f4a3ce794ac959a46494ff", - "0x9817b4c27a490b1cd5a6337e7bc7e8005fa075dd980c6bf075ddfa46cd51cc307ad1d9f24e613b762a20fc6c877eab41", - "0xad66bda1a3034ec5e420b78107896ecf36126ce3ef9705163db259072dfa438c6107717a33572272062b9f60cb89557c", - "0x876114ef078c2915288e29c9abe6b0ad6a756b5ee2930ba1b8a17257f3f0557602d1225e8aa41ce8606af71ada2a971b", - "0xaa3d6cde4c3b9d3d5d0c77a33e67f182a3e1cf89b0921423b2024236171955b34afc52b1f25b1dad9da9b001371771d7", - "0x984d3e3a72412d290e3459339757af7520d1739c7af0cbcf659c71999328db44f407d92e8a69fea11625612c49eac927", - "0xae890d0faf5bd3280dcad20a5f90e23a206661be8842375fea2ab22aadc500849ffbc52fe743b376d46bb926cedae6a6", - "0xb1f231f3f4d710c3fe80099faeb56dac67c1baf53b8fe67a9920fe4f90e52cb9a4bf19211249a6456613b28efe337f18", - "0x8caa54b418ba609d16520af3dff2e96d5f2eeb162c065a1763beb926547b2cfb3ae41d738db2c5681a9bc8bc9e6b9a1a", - "0x932157ff56c5ac29cf6cf44f450c882b3acfbb9f43d12d118da3d6256bde4e6eb3183aea304ab6967f37baa718ffec99", - "0x9360bed8fc5b6aac36aa69473040689bfc30411d20ffb7275ef39b9ff5789f9055d149383ce9f0f7709a1f9d683adbfe", - "0x98b5b33209068335da72782179d0c7aeeabe94b5560a19d72088fe8323e56db7ce65debe37a97536b6b8a0ca3b840b61", - "0x89a385c11be40064160b030a1bb28c3921fc8078522618a238c7ea0f86f34717ed9af9b4e2e20f5128e5f7fc66ad841e", - "0xb615703cbc64b4192990cc7e4903b74aed6a0076ce113b59ef7719197ffa46fb29eb78ca56b49873487432d0625c0faa", - "0x90f0d77abae9d3ad73a218e5ccec505ad108ea098451461567ae8ef9661606ca8e78df53b5d628b20b7037bd24622330", - "0x92e0e7cc4dfadc5fa0ee6da0c8de0493030db6e54ba0317f52f232a6708b732068b6077bd13a17eb7eb40b88368085b5", - "0xa24dad20094985bfccc6df1343506ed3bf9dcbdf4b2085a87627a5d71f7568db067304e465f8f380c5c88e8a27291a01", - "0x8629a45a10619354c84bdc2f6c42f540eab5a46f53f2ae11970433d7a2aef007897590bf31dfba1c921614c6d6fe1687", - "0x84ac64040d4206f82b08c771f375da4b7d752e41d2aa0da20ce845f6bc1b880a855d3ee966bca19b8ec327b4b43e7f0e", - "0x9608e6050c25996c052509f43f24a85cdf184135f46eaac520a9a6e78e0d44a6cee50ebc054048c708aefde8cd6651c2", - "0xa32032b0e0d7cc35e480c328f315327f9385adb102a708c9ba637878deb74582ae26bb6d6e5f8c9e3a839b0e0154b82a", - "0xb7e3c78d63acc6564a49e9f00b0a820b56d4f37a2374af1f7f1d016268011df9e7af0670ed2b0eee961f15aa948328dd", - "0x8b88bfdd353acc91ad0d308a43e5fb40da22c228f2fe093c6d6904d70f69c6203f56636ed898b05df51d33f1095ef609", - "0xb1d7a430c51fc857af55047683fc18c453b013527196c5e1bf776819a3dffca802217e9249ae03f084e2ea03ad67fcc2", - "0x80558e28a819ddb5e72e97c54be0f57c173ccf78038d360d190b7f1350a19577b8e3f43fa2f7bf113a228cd3b965b2e4", - "0xb4b2ec44e746c00dfc5661ba2514930934fc805cdc29adc531c02d28ce3cc754414b0485d4ee593232cd1175f357ad66", - "0xb57cee5d32835f76572330f61ccd25a203f0e4a7e5053d32965db283aad92f287645533e8e615137208383ec51b1fd99", - "0x930256086b419a8a6581c52590d0dbd9f8a3564c79424198fca3866b786df2f6098a18c50dc4abd20853a7184b1ce15d", - "0x8e75fd01181cffcd618a983492390f486e8c889972a46c1f34a4e1b38f384e8e4efc7e3c18533aa2057da9f9623e2238", - "0xb375d927dd988429f9e2764e5943916131092c394fce13b311baa10f34b023dd3571da02553176091a0738cc23771b9a", - "0xb9e28e4c0d0477518034d000e32464852e6951c8db6f64ccdb1d2566f5094716213fbf2fc0e29ac88d0e79f725e3c926", - "0x963981e99392afbd2b8318d5a6b2b0cc69c7f2f2f13f4b38dddbfedb2b0eaf0584aecfcbda20a4c60789c15d77970a58", - "0xa7804e1977aa77c263c7c001afa6cf568032dea940e350d6a58ce4614f1a91c13ae1c78bfea740c229dce2444556976a", - "0x8787204177da3cde6d35cd3497fa8774d244f9faa9f4bd91b636a613a32ce2ea0326378cf9c4cf475e73ef751b355c4b", - "0x895aeef46a07152a04ec812f1aa1fd431389fa0ef6c6e96a5b833e70ea14073bc9984757a8ee456dbec9788e74e6f0ca", - "0x8d17f0e5826783440d1f0ec868003510a4d9952bfe4a638e44a36d94482ac18ba70ef7ff773bdf7a3b62d714dcf0fcba", - "0x810d5e36b31310b2e054a666d3b3f7ed16dfcb1765532d87ca2a3920316f0187303c27dd113db145d47e8961062a6c03", - "0xb4e2fb48ae04cf8580bb6a28095076c9b95e5f13122b917328f334d4ac8a8648ce442919e28319a40148987350ab5303", - "0xb85549a313544fa1eb3ceb78473b7d3d717fc85b808de7b79db7dbd0af838ebb020622a7503f1cbacab688dddb648f84", - "0x80665adee057088eae827a5fe904ec3ad77d8843cdce0322d535e0659b4abc74a4d7ddd8a94c27f2def5c34ac2c038ee", - "0xad72fc19c2ce99b5b717e35528fe7d3ac8add340b02ebeb4889d9a94c32f312a0b45ea84d21c54f84cc40ee4958b72e1", - "0x99d530c843dff89a47a5ee8c87303ab18f8a82b0d5b808fca050354b35da5c5a5594d55921c6362d6cc917d75bdc18dc", - "0x99c7286c293e1be21c5b2a669dfdfcd5aa587105d2886fc5a8eaf8984da4e907f7d7b8c2362d64a4f1621b077a2a08a0", - "0xb4a39e1a9ed5d80c9563c3ca3fadf76f5478c63a98f4346a61b930c9c733e002f3ff02bc16abfdb53d776184cc3f87ba", - "0x9378ea71b941979404c92d01fb70b33fa68d085bf15d60eb1c9fc2b5fcdee6379f5583389a3660a756a50019a2f19a69", - "0xb68e17344a2bc45b8e2e19466b86dc139afefbf9bad2e2e28276a725099ebac7f5763f3cb52002261e3abe45ef51eb1a", - "0x819e64dc412b2d194d693b9b3157c1070a226af35c629837df145ea12ad52fa8eabd65b025a63c1fb0726207a58cdde8", - "0xa5e8ff8748419466ff6df5d389125f3d46aedacf44eaf12cbfe2f68d218c7d5ab6de4a8279d13aecc25f3b1d98230894", - "0x91560d54a9715cfda9cf7133ae51c432d0bf7fcbaeb468004994e6838bfc5ddcfa30e4e780667d0c4c0376780b083017", - "0xae8adb3309cc89d79a55ff74f129bb311fe4f5351a8b87600a87e0c3ba60825f71fccf67eadcf7e4b243c619417540fd", - "0x8d92cc1a6baa7bfa96fbce9940e7187b3d142f1888bdcb09bb5c8abf63355e9fb942ac4b4819d9be0e0e822d3e8e2e08", - "0xa6e8b79fdd90c34735bb8fbef02165ccbe55ea726dc203b15e7a015bf311c9cac56efd84d221cc55eaa710ee749dbdfe", - "0xa409b151de37bddf39ce5f8aa3def60ee91d6f03ddd533fce9bf7bdbeac618cc982c4f1ffbf6e302b8353d8f28f8c479", - "0xb9693975ef82171b3b9fc318ca296e4fe6110b26cbdfd653418f7754563fa7b6e22d64f8025ee4243483fa321572bfe4", - "0xa039ebe0d9ee4a03ade08e2104ffd7169975b224061924cca2aae71464d250851e9f5f6f6cb288b5bf15df9e252712a6", - "0xb27834db422395bd330e53736a001341ce02c9b148c277dabac67dc422741bfa983c28d47c27e8214cd861f2bad8c6f6", - "0xa2bafaf4e2daf629fd27d7d5ac09fb5efc930ff2ae610f37519808683aa583fe1c6f37207daf73de1d8a164f79a0c981", - "0xb856cee1cfcf5e50db9af4ab0aed3db2f43c936eaea369b5bba65582f61f383c285efbda97b1c068c5d230cbe94f7722", - "0xa61ab205554c0550fa267e46a3d454cd1b0a631646b3df140623ff1bfffaa118e9abe6b62814968cc2a506e9c03ea9a0", - "0x8c78edcd106377b9cbdfa2abd5278724aed0d9e4ae5869b5d2b568fdabb7804c953bae96294fcc70ef3cd52ba2cbe4ed", - "0x8570869a9bbf6cc84966545a36586a60be4d694839f367b73dfc40b5f623fc4e246b39b9a3090694aa2e17e652d07fd1", - "0xa905b82c4da8d866a894da72315a95dc98faa3c7b3d809aef18f3b2be4801e736a1b79a406179e8cac8f74d27e71ac52", - "0xa8eb8679ff1a64908515f6720ff69434cb33d63aeb22d565fde506618908b1d37585e3bd4d044fd0838b55787af06b42", - "0xaf4d86b2fbd1684a657dffe4210321a71e6ae560c144d44668d1f324dc9630e98348c3d444622a689327c1a59cc169dd", - "0x80359c6eab16954559ab0e6a1fee9a0526c45d3cae1a371159a2e3aa9b893afdc3a785c9559a5fd9cd8cd774234bf819", - "0x8d4e5ff81eb5d17bbe8ae6416538ca51a9427ce142b311f5cbb14febbbbb9c1ffc6489fd625b9266264c366c12a9d997", - "0x92e181c66489c5fa063ba2a1a354b6fd3439b8b4365a8c90e42e169bfaa1fb5766bf3e0fe804399d18bc8fbcafb5c3b1", - "0xa9ddf229360a095393885083716cb69c819b2d7cfb100e459c2e6beb999ff04446d1e4a0534832ae3b178cbe29f4f1d3", - "0x8e085ef7d919302a1cc797857b75cff194bdbc1c5216434fa808c3dea0cf666f39d9b00f6d12b409693d7a9bd50a912c", - "0x916dc4dc89e5e6acf69e4485a09fc66968f9b292eac61a146df1b750aa3da2425a0743d492179f90a543a0d4cd72c980", - "0xb9cbf17e32c43d7863150d4811b974882da338cf0ed1313765b431b89457021dd1e421eeaa52840ef00551bb630962dc", - "0xa6fb875786daec1a91484481787093d8d691dd07e15c9c0c6ae0404bf9dc26083ed15d03c6d3fe03e29f28e20da21269", - "0xa870fcb54b9a029e8086de9b08da8782c64ad2cc2e7fdf955b913d294038bb8136193256b85267e75a4ca205808a76b4", - "0x99883f057e09b88bf0e316f9814c091837fd5c26eeb16fec108c9fed4b7a2bd1c783dac0e4242b5a906621ab606c1e50", - "0x85d89069ca3190577dab39bbec43c16bf6dbca439ad3eebd8f5e9f507d84c3c43e77fd6323224582566a3aa2c8018951", - "0x9363ba219e0003f6e8a9d8937b9e1449e4b2c5cd57194563b758bea39deab88778e8f8e4f7816970a617fb077e1e1d42", - "0x820622f25553c035326145c1d2d537dc9cfd064c2f5bdf6d4ec97814de5fe9a0fbd443345fa2ea0a9d40d81d3936aa56", - "0x87e31110aaf447e70c3316459250e4f7f8c24420c97828f9eb33b22107542c5535bdb48b0e58682dd842edea2886ff08", - "0x95bf80cac6f42029d843d1246588acb40a74802f9e94b2bf69b1833936767e701ef7b0e099e22ab9f20f8c0c4a794b6c", - "0xa46ecf612b2763d099b27fb814bd8fdbaee51d6b9ac277ad6f28350b843ce91d701371adfaaf4509400dc11628089b58", - "0x8604decf299fb17e073969708be5befeb1090ab688ad9f3f97a0847a40ea9a11bbcfc7a91e8dc27bc67a155123f3bd02", - "0x8eb765c8dc509061825f3688cb2d78b6fef90cf44db33783d256f09be284bc7282205279725b78882688a514247c4976", - "0xb5c30b2244fa109d66b3a5270b178960fdec47d31e63db0b374b80d2b626409eb76d2e8d1ebf47ef96c166743032fc5e", - "0xaab01e76290a7e936989530221646160bf8f64e61e79282e980c8c5dcaaa805ff096efd01d075a2c75917a3f4bf15041", - "0xb9d79671debd0b83d0c7c7c3e64c0fb1274300564b262771f839b49218501e7f38ef80cae1f7e5a3c34acdc74c89dab6", - "0x92c0eaceadf036b3b9dfd2712013aba3dd7c30b7760f501f52141618265baa31840fe77850a7014dc528f71f8cf39ce6", - "0xb3cdd098059980455dd5b1c04182df1bd12fa844a866f02a9f8a86aab95b59945baa9af99f687410bffc5b07153cb23c", - "0xb361b73a62f71256b7f6ea8e0f6615e14fc5a06ee98b928ab3c9dd3eef9d9d30070e9855c82b7facb639cacb3401e01f", - "0xb9c85fc0f25a3271cf28b1ca900078eaaa66cbab0a3e677606e898ac32781a2dfce4d9cbd07404599e2c3c02fa161c9d", - "0xac5b4fdac2a0b2e6430d9fc72bde4249d72183b197fc7347bb1546ae6f544426686bbe0caec3ee973b6836da5e831c44", - "0xb675aebf24b92e398e166f171a6df442b3f5919b6bee192f31675a5e8eeb77d34c6590a6f0c0857417e0f78cfb085db8", - "0xa9bef942044d8d62e6a40169f7dc7b49e40cd0d77f8678dd7c7bae6f46c46786f9b1e319a3fa408f22a54fd2a4d70804", - "0xa20d19cd917d5102ae9ca0cf532127d2b953aa3303310e8a8c4b3da025dded993a47e3a28e6b02acfadb6d65dc2d41a3", - "0xa47fdb04059b83b2afb86a47b2368bbd7247c337a36d3333b6e5ef2cc9476a92c4907e4c58a845c9ef9b497621e0b714", - "0x94a9e9ffc14b411e11a4ffa59878d59460263589003dc7b6915247c549f67feede279bf3645fdd92379022fb21e3caeb", - "0xb92e1177dd9ecdaf1370c71b14954219cf0851f309bc216d5907a4e2e84e0df3457018224150c142cc6bf86644bb4b73", - "0x8bc57fadd68a265b7df9b42227a9c0968db7b1bb50dc12f7d755505779f1ff2c408672b3091e903366acc9ce15d19fb6", - "0xb6b5efbe1ac4e1bd2e8447c45000d09397b772ca5496acc447b881022608a41c4f60388814607a01890190105bee7be3", - "0x95f7c85fd614df968f8ccf8d086579c9e1cec4644ecf06da26e3511cb39635a7326b3cec47bd51cf5646f1c660425e9c", - "0xb81765fb319bcdc74b4d608383ccb4af7dd84413b23af637be12e2827a75f7e4bcd14441cf979ed9038ae366fbb6f022", - "0xa120ea76cda8c6c50c97035078f6648afe6537809bdba26e7c9e61de8f3070d2347160f9d34010effbf2ec7e94f5749f", - "0x92c1b8631953b40d3cc77eee2c72a064b999c09a9b92c11d8fa7b4072966273901c9dba25f9f79f384d9f11a56f3fc7a", - "0xa4b00dc0ab67b2300abc9c516e34daf444d6497b066a90cfe3381ed2812304ed37b14f3b948990443dc6c1cf1bed460c", - "0xa9e9f7e13c9f031bc7b9e6f1417c7abcc38894fe7d3f54869ee277afd2efa3e6fb50757dd36c8c94d591e0abdea322cc", - "0x84f3e98f831792b5ad14bcfe62a4c9f296476c6087c4c1ec7767fc642fbca141ff6a3deeb8b4d4106a9cda5a9937eea0", - "0x8eb1a7931bbea9a714226fd74b0100ab88355287d9b0a349c095e9b5809b98f237ffd706bce7d67a770da355fb9cec7b", - "0x9738ef8739e1742c1f26b51a1621be0b89d37406a370c531e236f635c7064c661818817bb3858908986aa687b28b21be", - "0xa9cf3ce8501b003ccaf57552a4c4ec31081e44526d3aa3791d3dc4a7e438a357c0956f93c500356186d8fd4588ffac5e", - "0xa7af6a219cca59225839a9de5b19263cb23d75557d448bc7d677b62591a2e068c45e5f4457cceb3e9efa01d0601fc18a", - "0x972a24ece5eda7692cbb6fb727f92740451bc1281835e2a02931b2b05824a16b01dbe5edd03a0ed5b441ff25a5cc0188", - "0xb21d1ec7597ce95a42f759c9a8d79c8275d7e29047a22e08150f0f65014702f10b7edce8c03f6e7ab578ce8c3b0ec665", - "0xa13a1c7df341bd689e1f8116b7afc149c1ef39161e778aa7903e3df2569356ad31834fa58ceb191485585ce5ef6835c3", - "0xa57bdb08119dc3bc089b5b2b5383455c4de0c2fcdac2dcfa21c7ac5071a61635ff83eceb7412f53fab42d1a01991de32", - "0xb2968748fa4a6921ee752d97aa225d289f599a7db7a222450e69706533573ded450380c87f8cdd4a8b8c8db1b42b5c97", - "0x8718ec04e0d5f38e3034ecd2f13dfde840add500f43a5e13457a1c73db0d18138f938690c8c315b5bcbeb51e8b9a2781", - "0x82094789e26c4a04f2f30bdb97b9aecca9b756cbd28d22ab3c8bed8afc5b2963340ddfc5a5f505e679bf058cbc5dcbb8", - "0xa35b8a566dd6ab67eddc2467906bffc76c345d508e52e9e4bb407b4f2b2c5f39b31d5a4bf5022f87bf7181dc6be2fe41", - "0xa8c93b1e893d4777c0e3a1b4bef3be90c215781501407c4011457fc3240e13524b4d2bea64a6d0a3efe3f3b0dae9b8ab", - "0x877095ad18b1e5870818f7a606127ba1736a0b55b0dbcd281ec307c84b08afc0c9117e3a880fe48bfc225fbf37671a97", - "0x84405ee0421ed2db1add3593df8426a9c1fcc8063e875f5311a917febc193748678dd63171d0c21665fb68b6d786c378", - "0xa52cdc8209c3c310bed15a5db260c4f4d4857f19c10e4c4a4cfe9dfc324dfac851421bb801509cf8147f65068d21603c", - "0x8f8a028a70dda7285b664722387666274db92230b09b0672f1ead0d778cee79aae60688c3dfd3a8ed1efdeda5784c9d4", - "0xa0be42fecc86f245a45a8ed132d6efc4a0c4e404e1880d14601f5dce3f1c087d8480bad850d18b61629cf0d7b98e0ae0", - "0x83d157445fc45cb963b063f11085746e93ab40ece64648d3d05e33e686770c035022c14fdf3024b32b321abf498689ad", - "0x8a72bbf5a732e2d4f02e05f311027c509f228aef3561fc5edac3ef4f93313845d3a9f43c69f42e36f508efcc64a20be0", - "0xb9ca29b0ec8e41c6a02f54d8c16aebf377982488cbe2ed1753090f2db4f804f6269af03e015d647a82ef06ffaa8cba6c", - "0xb4df3858d61bbb5ded1cf0be22a79df65ae956e961fbb56c883e1881c4c21fe642e3f5a0c108a882e553ac59595e3241", - "0x86457d8890ac8858d7bab180ef66851247c2bf5e52bf69a4051d1d015252c389684fcc30bb4b664d42fbf670574ab3a3", - "0x86d5576ea6dfa06d9ebce4cd885450f270c88a283e1e0d29cab27851c14ed2f00355e167b52e1539f1218ad11d8f13dd", - "0x883ad1364dc2a92388bfafaa9bc943c55b2f813525831e817a6208c666829a40455dde494eba054b2495a95f7ce69e8a", - "0x8942371e6925231c2c603b5f5a882d8404d39f0c7c4232557c2610b21c2c07f145466da798ea78b7932da2b774aa3128", - "0xa799eb71496783cc7faf12c9d9804bf6180699a004b2f07fc5cc36840f63ce7eee7dde9275819a9aa3f8d92dc0d47557", - "0x8eb3fb5c769548ee38c7882f51b959c5d5a42b5935269ccf987d6ddbb25a206e80c6000bcc328af149e0727c0b7c02c0", - "0x8f3910d64e421a8f2d8db4c7b352ba5b3fc519d5663973fea5962efe4364fb74448770df944ef37ffe0382648fb56946", - "0xb41413e0c26ff124cf334dab0dc8e538293d8d519d11cc2d10895a96b2064ac60c7da39f08589b38726cffa4c3f0bfef", - "0xb46ef2eb10abae0f35fa4c9c7ee2665e8044b8d9f91988a241da40fd5bbc63166925582151941b400006e28bbc5ba22a", - "0xb8baa8b4c420bb572a3b6b85479b67d994c49a7ebfe1274687d946a0d0b36dfed7630cfb897350fa166f5e2eff8f9809", - "0x964b46d359c687e0dcfbdab0c2797fc2bd1042af79b7418795b43d32ffca4de89358cee97b9b30401392ff54c7834f9f", - "0x8410d0203d382ebf07f200fd02c89b80676957b31d561b76563e4412bebce42ca7cafe795039f46baf5e701171360a85", - "0xb1a8d5d473c1a912ed88ea5cfa37c2aea5c459967546d8f2f5177e04e0813b8d875b525a79c29cb3009c20e7e7292626", - "0xafaab9a1637429251d075e0ba883380043eaf668e001f16d36737028fded6faa6eeed6b5bb340f710961cee1f8801c41", - "0xaef17650003b5185d28d1e2306b2f304279da50925f2704a6a3a68312f29fe5c2f2939f14e08b0ba9dee06ea950ad001", - "0x97bcc442f370804aa4c48c2f8318d6f3452da8389af9335e187482d2e2b83b9382e5c297dce1a0f02935e227b74e09a3", - "0x8a67a27b199f0bcd02d52a3e32f9b76a486b830ec481a49a4e11807e98408b7052b48581b5dd9f0b3e93052ec45dfb68", - "0xb113bf15f430923c9805a5df2709082ab92dcdf686431bbad8c5888ca71cc749290fa4d4388a955c6d6ee3a3b9bc3c53", - "0x8629ca24440740ce86c212afed406026f4ea077e7aa369c4151b6fa57bca7f33f9d026900e5e6e681ae669fd2bd6c186", - "0x933a528371dcecc1ec6ded66b1c7b516bd691b3b8f127c13f948bfbcda3f2c774c7e4a8fbee72139c152064232103bdf", - "0x8568ddd01f81a4df34e5fa69c7f4bb8c3c04274147498156aec2e3bd98ea3e57c8a23503925de8fa3de4184563a2b79e", - "0x8160874ec030f30fda8f55bcf62613994ff7ed831e4901c7560eac647182b4a9b43bfaff74b916602b9d6ae3bfcaf929", - "0xae71c48d48cf9459800cdf9f8e96bc22e2d4e37259e5c92a2b24fbe2c6ca42675e312288603c81762f6ceb15400bc4c9", - "0xb05f39bb83fda73e0559db1fd4a71423938a87ad9f060d616d4f4a6c64bf99472a2cbfb95f88b9257c9630fc21a0b81f", - "0x80c8479a640ed7a39e67f2db5ad8dfd28979f5443e8e6c23da8087fc24134d4b9e7c94320ffa4154163270f621188c27", - "0x9969ba20ee29c64cb3285a3433a7e56a0fe4ddc6f3d93e147f49fe021bed4a9315266ebb2fb0eb3036bb02001ae015e6", - "0xa198c89fef2ab88e498703b9021becc940a80e32eb897563d65db57cc714eaa0e79092b09dd3a84cfab199250186edcc", - "0x8df14a3db8fe558a54d6120bad87405ba9415a92b08c498812c20416c291b09fed33d1e2fcf698eb14471f451e396089", - "0x81e245ef2649b8a5c8d4b27188dd7e985ef6639090bdc03462c081396cf7fc86ed7d01bfe7e649d2b399255e842bdc21", - "0x8659f622c7ab7b40061bcf7a10144b51ad3ab5348567195924f2944e8c4ce137a37f1ba328e4716c10806f3fb7271689", - "0xa575d610fc8fe09334ca619ecdadf02d468ca71dd158a5a913252ca55ea8d8f9ce4548937c239b9cb8ab752a4d5af24a", - "0x94744549cd9f29d99f4c8c663997bdfa90e975b31f1086214245de9c87b0c32209f515a0de64d72d5ef49c09b0a031fa", - "0x80a8677862b056df59e350c967a27436c671b65d58854e100115bac9824ba177e94c2a1bfcaa191a071b9cefdbee3989", - "0x91be9a5504ec99922440f92a43fe97ddce2f21b9d94cd3a94c085a89b70c903696cec203bbab6d0a70693ba4e558fb01", - "0x8c5a0087bcd370734d12d9b3ab7bc19e9a336d4b49fc42825b2bfedcd73bb85eb47bf8bb8552b9097cc0790e8134d08c", - "0x933aa9e6bd86df5d043e0577a48e17eea3352e23befdbb7d7dcac33b5703d5ace230443ac0a40e23bf95da4cc2313478", - "0x984b7ee4bd081ee06c484db6114c2ce0ba356988efb90f4c46ff85ed2865fb37f56a730166c29ef0ae3345a39cdeae7a", - "0xae830f908ea60276c6c949fb8813e2386cf8d1df26dcf8206aa8c849e4467243e074471380ed433465dc8925c138ea4c", - "0x874c1df98d45b510b4f22feff46a7e8ed22cfc3fad2ac4094b53b9e6477c8dfc604976ca3cee16c07906dece471aa6c6", - "0xa603eb60d4c0fb90fa000d2913689126849c0261e6a8649218270e22a994902965a4e7f8c9462447259495fe17296093", - "0xa7c73d759a8ad5e3a64c6d050740d444e8d6b6c9ade6fb31cb660fa93dc4a79091230baccb51c888da05c28cb26f6f3f", - "0xa4411b79b6a85c79ea173bd9c23d49d19e736475f3d7d53213c5349ebb94a266d510d12ba52b2ac7a62deaaaec7339b8", - "0x943b84f8bbcee53b06266b5c4cd24d649d972593837fe82b0bf5d5e1bbc1a2bf148e1426c366d7c39ab566b10224cadc", - "0x8300012096a8b4cefecc080054bf3ceb0918162ba263c6848860423407796b5eb517170c0bad8e4905ac69a383055a21", - "0x8244a1e3ad41908c6f037e2f8db052e81f281646141334829f36c707f307448b9ab79a7f382a1e8d86f877c90b59271c", - "0x8eca1b74687802ecc36a5d39e4516a9dee3de61a2047252d9ed737b49e0090c386e9d792ac004c96337681c7f29a16ad", - "0xb70fa47535f0524835039a20036c61e77f66146ad79d3d339214d8744742db41ceeb577c829d000011aeafbb12e09579", - "0x84b3abbce48689f3adbb99889c7fd1f3e15ab455d477e34f5151c5c1c358ed77a5b6a581879f7e0f1f34106e0792e547", - "0xab45ecb58c0ef0dbce3d16afc6ac281e0d90ec48741ea96a141152647e98fcc87f3a3ff07ba81f3179118453ce123156", - "0x90d231a145ba36a59087e259bbfc019fa369201fcfeaa4347d5fd0a22cd8a716e5a797f3cc357f2779edb08f3b666169", - "0xa4f6074d23c6c97e00130bc05f25213ca4fa76c69ca1ace9dece904a2bdd9d987661f5d55023b50028c444af47ff7a08", - "0x933af884939ad0241f3f1f8e8be65f91d77ac0fb234e1134d92713b7cfb927f1933f164aec39177daa13b39c1370fac8", - "0x80d1db6933ce72091332ae47dc691acb2a9038f1239327b26d08ea9d40aa8f2e44410bbda64f2842a398cbe8f74f770f", - "0xa7a08605be2241ccc00151b00b3196d9c0717c4150909a2e9cd05538781231762b6cc6994bebbd4cddae7164d048e7b2", - "0x96db0d839765a8fdbbac03430fa800519e11e06c9b402039e9ae8b6503840c7ecac44123df37e3d220ac03e77612f4e4", - "0x96d70f8e9acd5a3151a8a9100ad94f16c289a31d61df681c23b17f21749c9062622d0a90f6d12c52397b609c6e997f76", - "0x8cf8e22273f7459396ff674749ab7e24c94fe8ab36d45d8235e83be98d556f2b8668ba3a4ec1cb98fac3c0925335c295", - "0x97b7e796a822262abc1a1f5a54cb72a1ea12c6c5824ac34cd1310be02d858a3c3aa56a80f340439b60d100e59c25097d", - "0xa48208328b08769737aa1a30482563a4a052aea736539eceab148fa6653a80cb6a80542e8b453f1f92a33d0480c20961", - "0xb612184941413fd6c85ff6aa517b58303b9938958aa85a85911e53ed308778624d77eadb27ccf970573e25d3dfd83df7", - "0xb3717068011648c7d03bbd1e2fc9521a86d2c3ae69113d732c2468880a3b932ebec93596957026477b02842ed71a331b", - "0xa0ad363e1352dcf035b03830fef4e27d5fd6481d29d5e8c9d51e851e3862d63cdcbaf8e330d61c1b90886921dac2c6fd", - "0x8db409fdacfa4bfdaf01cc87c8e97b53ca3a6e3a526d794eaad1c2023f3df4b888f1bf19fee9a990fe6d5c7c3063f30c", - "0xb34d6975310ab15938b75ef15020a165fc849949065d32d912554b51ffa1d3f428a6d1a396cb9329367670391de33842", - "0x9117285e9e6762853fc074b8a92b3923864de2c88c13cea7bab574aaf8cdd324843455d2c3f83c00f91f27c7ecc5592a", - "0xb4b2e8f190ea0b60819894710c866bf8578dd1b231ae701d430797cc7ede6e216e8ca6a304f3af9484061563645bf2ab", - "0x8c493c6853ab135d96a464815dd06cad8b3e8b163849cdefc23d1f20211685753b3d3e147be43e61e92e35d35a0a0697", - "0x9864d7880f778c42d33cf102c425e380d999d55a975a29c2774cad920dfddb80087a446c4f32ed9a6ab5f22ec6f82af0", - "0x90f67fe26f11ca13e0c72b2c2798c0d0569ed6bc4ce5bbaf517c096e7296d5dd5685a25012f6c6d579af5b4f5d400b37", - "0xa228872348966f26e28a962af32e8fa7388d04bc07cfc0224a12be10757ac7ab16a3387c0b8318fcb0c67384b0e8c1a4", - "0xa9d9d64bba3c03b51acf70aeb746a2712ddafe3b3667ae3c25622df377c2b5504e7ab598263bec835ab972283c9a168b", - "0x932128971c9d333f32939a1b46c4f7cf7e9d8417bd08dc5bd4573ccbd6ec5b460ac8880fb7f142f7ef8a40eef76d0c6d", - "0x964115e7838f2f197d6f09c06fbb2301d6e27c0ecdf208350cf3b36c748436dac50f47f9f9ac651c09ab7ad7221c7e43", - "0xa5941f619e5f55a9cf6e7f1499b1f1bcddcc7cf5e274efedaaad73a75bc71b1fc5c29cd903f6c69dc9a366a6933ca9d1", - "0xa154bf5eaec096029e5fe7c8bf6c695ae51ace356bb1ad234747776c7e1b406dee2d58864c3f4af84ed69f310974125e", - "0xb504e6209d48b0338ab1e4bdab663bac343bb6e0433466b70e49dc4464c1ec05f4a98111fd4450393607510ae467c915", - "0x813411918ea79bdde295393284dc378b9bdc6cfcb34678b9733ea8c041ac9a32c1e7906e814887469f2c1e39287e80f8", - "0x8be0369f94e4d72c561e6edb891755368660208853988647c55a8eed60275f2dd6ee27db976de6ecf54ac5c66aaf0ae6", - "0xa7e2701e55b1e7ea9294994c8ad1c080db06a6fc8710cd0c9f804195dce2a97661c566089c80652f27b39018f774f85e", - "0x956b537703133b6ddf620d873eac67af058805a8cc4beb70f9c16c6787bf3cc9765e430d57a84a4c3c9fbdd11a007257", - "0x835ae5b3bb3ee5e52e048626e3ddaa49e28a65cb94b7ecdc2e272ff603b7058f1f90b4c75b4b9558f23851f1a5547a35", - "0x85d67c371d1bf6dc72cca7887fa7c886ce988b5d77dc176d767be3205e80f6af2204d6530f7060b1f65d360a0eaeff30", - "0xa84a6647a10fcef8353769ef5f55a701c53870054691a6e9d7e748cbe417b3b41dbb881bae67adc12cb6596c0d8be376", - "0x87ffe271fc0964cb225551c7a61008d8bcb8b3d3942970dbcc2b9f4f9045a767971880368ea254e2038a3a0b94ecf236", - "0x964bb721c51d43ee7dd67c1a2b7dd2cc672ce8fad78c22dcddb43e6aab48d9a4a7dc595d702aa54a6fb0ffabf01f2780", - "0xa89b3f84bb7dcbe3741749776f5b78a269f6b1bebb8e95d3cc80b834fd2177c6be058d16cacfd0d5e1e35e85cde8b811", - "0xb4314538e003a1587b5592ff07355ea03239f17e75c49d51f32babe8e048b90b046a73357bcb9ce382d3e8fbe2f8e68b", - "0x86daf4bf201ae5537b5d4f4d734ed2934b9cf74de30513e3280402078f1787871b6973aa60f75858bdf696f19935a0e2", - "0xb1adf5d4f83f089dc4f5dae9dbd215322fa98c964e2eaa409bf8ca3fa5c627880a014ed209492c3894b3df1c117236c4", - "0xb508d52382c5bac5749bc8c89f70c650bb2ed3ef9dc99619468c387c1b6c9ff530a906dfa393f78f34c4f2f31478508a", - "0xa8349a5865cb1f191bebb845dfbc25c747681d769dbffd40d8cedf9c9a62fa2cbc14b64bb6121120dab4e24bef8e6b37", - "0xaf0500d4af99c83db8890a25f0be1de267a382ec5e9835e2f3503e1bac9412acf9ff83a7b9385708ef8187a38a37bc77", - "0xb76d57a1c1f85b8a8e1722a47057b4c572800957a6b48882d1fc21309c2e45f648a8db0fcff760d1dbc7732cf37c009b", - "0xb93c996cec0d3714667b5a5a5f7c05a7dc00bbc9f95ac8e310626b9e41ae4cc5707fac3e5bd86e1e1f2f6d9627b0da94", - "0x93216fdb864217b4c761090a0921cf8d42649ab7c4da1e009ec5450432564cb5a06cb6e8678579202d3985bd9e941cef", - "0x8b8be41105186a339987ae3a5f075fbc91f34b9984d222dfed0f0f85d2f684b56a56ab5dc812a411570491743d6c8b18", - "0x959b72782a6b2469e77fe4d492674cc51db148119b0671bd5d1765715f49fa8a87e907646671161586e84979ef16d631", - "0x86b7fc72fb7e7904ea71d5e66ba0d5d898ace7850985c8cc4a1c4902c5bf94351d23ce62eed45e24321fb02adfa49fc8", - "0xa2f244e7c9aa272cb0d067d81d25e5a3045b80b5a520b49fd5996ece267a7f1bea42e53147bbf153d9af215ea605fc9e", - "0x81aa2efa5520eebc894ce909ba5ce3250f2d96baa5f4f186a0637a1eea0080dd3a96c2f9fadf92262c1c5566ddb79bab", - "0xb607dd110cfe510d087bcff9a18480ba2912662256d0ab7b1d8120b22db4ad036b2266f46152754664c4e08d0fc583f6", - "0x8f588d5f4837e41312744caac5eee9ddc3ad7085871041694f0b5813edf83dc13af7970f7c9b6d234a886e07fa676a04", - "0x924921b903207783b31016cbec4e6c99e70f5244e775755c90d03a8b769738be3ba61577aca70f706a9c2b80040c9485", - "0xae0a42a222f1a71cd0d3c69ffb2f04c13e1940cce8efabe032629f650be3ceed6abb79651dbb81cb39a33286eb517639", - "0xa07d7d76460f31f5f0e32e40a5ea908d9d2aebf111ac4fadee67ef6540b916733c35a777dcdc05f6417726ca1f2d57dd", - "0x88d7f8a31f8c99794291847d28745e5d0b5d3b9684ca4170b686ffbb5bb521a3ef6746c3c8db22e4250a0cdff7939d96", - "0x849573071fd98c020dc9a8622a9eff221cb9f889bde259e7127a8886b73bef7ad430b87750915658918dcfb6b7b4d8d3", - "0xb12d59f732fa47fad175d6263734da8db89230fd340a46ad1cdee51e577041a5c80bf24cd195593e637daf1a66ef5a98", - "0xabbcfb8a4a6d5e269ee1ac5e277df84416c73ca55ec88317f73608201af25af0cb65b943c54684a5651df3a26e3daca2", - "0xab157f589bdbaf067a6a7ba7513df0492933855d39f3a081196cf2352e0ddc0162d476c433320366e3df601e0556278d", - "0xa86c0619b92e5ae4f7daa876a2abc5ba189156afc2fa05eef464dfa342ba37fc670d0dc308ad3822fcb461ab001bac30", - "0xa3f292946476cfe8d5e544a5325439a00e0165a5f9bf3bb6a53f477baeac7697cc0377745536681aa116f326ce911390", - "0x8aecbbfd442a6a0f01c1c09db5d9d50213eb6f1ff6fab674cde3da06a4edff3ed317e804f78300c22ef70c336123e05d", - "0x834ed4b58211fcd647d7bf7c0a3ba9085184c5c856b085e8a0fcd5215c661ef43d36f3f0f6329a9f1370501b4e73b6e4", - "0xa114ea5ad2b402a0de6105e5730907f2f1e458d28ae35144cf49836e0ad21325fe3e755cfb67984ae0a32e65402aad1e", - "0xa005f12bed97d71cee288b59afe9affb4d256888727343944a99913980df2c963fe02f218e6ea992f88db693a4498066", - "0xa010f286ab06b966e3b91ff8f1bdbe2fe9ab41a27bc392d5787aa02a46e5080e58c62c7d907818caae9f6a8b8123e381", - "0x857bd6df2ddef04dbc7c4f923e0b1696d3016c8bfed07fdfa28a3a3bd62d89b0f9df49aae81cbb6883d5e7b4fadae280", - "0xb3927030da445bc4756ac7230a5d87412a4f7510581fb422212ce2e8cf49689aca7ba71678743af06d4de4914c5aa4a0", - "0xb86403182c98fcce558d995f86752af316b3b2d53ba32075f71c7da2596747b7284c34a1a87de604fcc71e7e117a8add", - "0x98dd19b5527733041689b2a4568edaf6aa0fe1a3dd800c290cda157b171e053648a5772c5d3d4c80e5a795bc49adf12e", - "0x88a3c227bb7c9bff383f9ad3f7762245939a718ab85ae6e5e13180b12bf724d42054d3852b421c1cd1b3670baddecb63", - "0xb3cfd9ad66b52bbe57b5fff0fad723434d23761409b92c4893124a574acc1e6b1e14b4ec507661551cbbe05e16db362e", - "0x923e1bb482cf421dd77801f9780f49c3672b88508a389b94015fd907888dc647ee9ea8ec8d97131d235d066daf1f42b7", - "0x8d5e16240f04f92aa948181d421006bdbc7b215648fb6554193224d00cf337ebbb958f7548cf01b4d828acffb9fbc452", - "0x8b2b8f18ad0559746f6cda3acca294a1467fb1a3bc6b6371bc3a61a3bfe59418934fa8706f78b56005d85d9cb7f90454", - "0xa9316e2a94d6e31426d2ae7312878ba6baaac40f43e2b8a2fa3ab5a774c6918551554b2dbb23dc82f70ba3e0f60b5b0d", - "0x9593116d92cf06b8cd6905a2ce569ee6e69a506c897911f43ae80fc66c4914da209fc9347962034eebbc6e3e0fe59517", - "0x887d89d2b2d3c82b30e8f0acf15f0335532bd598b1861755498610cb2dd41ff5376b2a0bb757cb477add0ce8cfe7a9fc", - "0xb514cfe17875ecb790ad055271cc240ea4bda39b6cfa6a212908849c0875cb10c3a07826550b24c4b94ea68c6bb9e614", - "0xa563d5187966d1257d2ed71d53c945308f709bcc98e3b13a2a07a1933dc17bcb34b30796bd68c156d91811fbd49da2cb", - "0xa7195ccc53b58e65d1088868aeeb9ee208103e8197ad4c317235bb2d0ad3dc56cb7d9a7186416e0b23c226078095d44c", - "0xa838e7a368e75b73b5c50fbfedde3481d82c977c3d5a95892ac1b1a3ea6234b3344ad9d9544b5a532ccdef166e861011", - "0x9468ed6942e6b117d76d12d3a36138f5e5fb46e3b87cf6bb830c9b67d73e8176a1511780f55570f52d8cdb51dcf38e8c", - "0x8d2fc1899bc3483a77298de0e033085b195caf0e91c8be209fd4f27b60029cbe1f9a801fbd0458b4a686609762108560", - "0x8f4e44f8ca752a56aa96f3602e9234ad905ad9582111daf96a8c4d6f203bf3948f7ce467c555360ad58376ee8effd2ba", - "0x8fb88640b656e8f1c7c966c729eb2ba5ccf780c49873f8b873c6971840db7d986bdf1332ba80f8a0bb4b4ee7401468fa", - "0xb72aa3235868186913fb5f1d324e748cd3ce1a17d3d6e6ea7639a5076430fe0b08841c95feb19bb94181fe59c483a9eb", - "0xb8b102690ebb94fc4148742e7e3fd00f807b745b02cbe92cd92992c9143b6db7bb23a70da64a8b2233e4a6e572fc2054", - "0x8c9ae291f6cd744e2c6afe0719a7fc3e18d79307f781921fb848a0bf222e233879c1eca8236b4b1be217f9440859b6ce", - "0xa658ede47e14b3aad789e07f5374402f60e9cacb56b1b57a7c6044ca2418b82c98874e5c8c461898ebd69e38fecd5770", - "0x89c0cb423580e333923eb66bda690f5aca6ec6cba2f92850e54afd882ba608465a7dbb5aa077cd0ca65d9d00909348ab", - "0xaed8e28d98d5508bd3818804cf20d296fe050b023db2ed32306f19a7a3f51c7aaafed9d0847a3d2cd5ba5b4dabbc5401", - "0x96a0fcd6235f87568d24fb57269a94402c23d4aa5602572ad361f3f915a5f01be4e6945d576d51be0d37c24b8b0f3d72", - "0x935d0c69edd5dfa8ed07c49661b3e725b50588f814eb38ea31bcc1d36b262fae40d038a90feff42329930f8310348a50", - "0x900518288aa8ea824c7042f76710f2ea358c8bb7657f518a6e13de9123be891fa847c61569035df64605a459dad2ecc8", - "0x947d743a570e84831b4fb5e786024bd752630429d0673bf12028eb4642beb452e133214aff1cfa578a8856c5ebcb1758", - "0xa787266f34d48c13a01b44e02f34a0369c36f7ec0aae3ec92d27a5f4a15b3f7be9b30b8d9dd1217d4eeedff5fd71b2e5", - "0xa24b797214707ccc9e7a7153e94521900c01a1acd7359d4c74b343bfa11ea2cdf96f149802f4669312cd58d5ab159c93", - "0x97f5ee9c743b6845f15c7f0951221468b40e1edaef06328653a0882793f91e8146c26ac76dd613038c5fdcf5448e2948", - "0x80abd843693aed1949b4ea93e0188e281334163a1de150c080e56ca1f655c53eb4e5d65a67bc3fc546ed4445a3c71d00", - "0x908e499eb3d44836808dacff2f6815f883aeced9460913cf8f2fbbb8fe8f5428c6fc9875f60b9996445a032fd514c70f", - "0xae1828ef674730066dc83da8d4dd5fa76fc6eb6fa2f9d91e3a6d03a9e61d7c3a74619f4483fe14cddf31941e5f65420a", - "0xa9f4dbe658cd213d77642e4d11385a8f432245b098fccd23587d7b168dbeebe1cca4f37ee8d1725adb0d60af85f8c12f", - "0x93e20ee8a314b7772b2439be9d15d0bf30cd612719b64aa2b4c3db48e6df46cea0a22db08ca65a36299a48d547e826a7", - "0xa8746a3e24b08dffa57ae78e53825a9ddbbe12af6e675269d48bff4720babdc24f907fde5f1880a6b31c5d5a51fbb00e", - "0xb5e94dfab3c2f5d3aea74a098546aa6a465aa1e3f5989377d0759d1899babf543ad688bb84811d3e891c8713c45886c5", - "0xa3929bada828bd0a72cda8417b0d057ecb2ddd8454086de235540a756e8032f2f47f52001eb1d7b1355339a128f0a53b", - "0xb684231711a1612866af1f0b7a9a185a3f8a9dac8bde75c101f3a1022947ceddc472beb95db9d9d42d9f6ccef315edbc", - "0xaf7809309edbb8eb61ef9e4b62f02a474c04c7c1ffa89543d8c6bf2e4c3d3e5ecbd39ec2fc1a4943a3949b8a09d315a6", - "0xb6f6e224247d9528ef0da4ad9700bee6e040bbf63e4d4c4b5989d0b29a0c17f7b003c60f74332fefa3c8ddbd83cd95c1", - "0xadbcec190a6ac2ddd7c59c6933e5b4e8507ce5fd4e230effc0bd0892fc00e6ac1369a2115f3398dfc074987b3b005c77", - "0x8a735b1bd7f2246d3fa1b729aecf2b1df8e8c3f86220a3a265c23444bdf540d9d6fe9b18ed8e6211fad2e1f25d23dd57", - "0x96b1bf31f46766738c0c687af3893d098d4b798237524cb2c867ed3671775651d5852da6803d0ea7356a6546aa9b33f2", - "0x8036e4c2b4576c9dcf98b810b5739051de4b5dde1e3e734a8e84ab52bc043e2e246a7f6046b07a9a95d8523ec5f7b851", - "0x8a4f4c32ee2203618af3bb603bf10245be0f57f1cfec71037d327fa11c1283b833819cb83b6b522252c39de3ce599fa5", - "0xad06ed0742c9838e3abaaffdb0ac0a64bad85b058b5be150e4d97d0346ed64fd6e761018d51d4498599669e25a6e3148", - "0x8d91cb427db262b6f912c693db3d0939b5df16bf7d2ab6a7e1bc47f5384371747db89c161b78ff9587259fdb3a49ad91", - "0xae0a3f84b5acb54729bcd7ef0fbfdcf9ed52da595636777897268d66db3de3f16a9cf237c9f8f6028412d37f73f2dfad", - "0x8f774109272dc387de0ca26f434e26bc5584754e71413e35fa4d517ee0f6e845b83d4f503f777fe31c9ec05796b3b4bc", - "0xa8670e0db2c537ad387cf8d75c6e42724fae0f16eca8b34018a59a6d539d3c0581e1066053a2ec8a5280ffabad2ca51f", - "0xac4929ed4ecad8124f2a2a482ec72e0ef86d6a4c64ac330dab25d61d1a71e1ee1009d196586ce46293355146086cabba", - "0x845d222cb018207976cc2975a9aa3543e46c861486136d57952494eb18029a1ebb0d08b6d7c67c0f37ee82a5c754f26f", - "0xb99fa4a29090eac44299f0e4b5a1582eb89b26ed2d4988b36338b9f073851d024b4201cd39a2b176d324f12903c38bee", - "0x9138823bc45640b8f77a6464c171af2fe1700bdc2b7b88f4d66b1370b3eafe12f5fbb7b528a7e1d55d9a70ca2f9fc8e6", - "0x8ac387dc4cf52bc48a240f2965ab2531ae3b518d4d1f99c0f520a3d6eb3d5123a35ef96bed8fa71ee2f46793fa5b33b3", - "0x864adec6339d4c2ba2525621fceabd4c455902f6f690f31a26e55413e0722e5711c509dc47ce0bcc27bbdc7651768d2d", - "0xa0a52edb72268a15201a968dabc26a22909620bda824bd548fb8c26cc848f704166ed730d958f0173bd3b0a672f367bd", - "0x949e445b0459983abd399571a1a7150aab3dd79f4b52a1cd5d733e436c71c1d4b74287c6b0ce6cc90c6711ba4c541586", - "0x858966355dac11369e3b6552f2b381665181693d5a32e596984da3314021710b25a37d8c548b08700eea13d86cb22f21", - "0x974bcbb8d38c5e6518745cc03ad436e585b61f31d705e7e2e5085da9655d768ac4d800904f892c3dab65d6223e3f1fd6", - "0x8092b6506b01308bf6187fde5ebd4fa7448c9a640961ba231be22ac5fa2c7635ef01e8b357722c7695d09b723101ea2a", - "0xa5b8ef360bf28533ee17d8cd131fff661d265f609db49599085c0c7d83b0af409a1b5c28e3a5e5d7f8459a368aa121e8", - "0xb031b6d5e3ceab0f0c93314b3b675f55cf18cbc86f70444af266fe39cb22fd7dad75d8c84e07f1c1bfa2cb8283e1361a", - "0x93ad489e4f74658320c1cceed0137c023d3001a2c930ed87e6a21dbf02f2eb6ad1c1d8bcb3739c85dcfbecb040928707", - "0xb15e4ec2cdab0d34aec8d6c50338812eb6ecd588cf123a3e9d22a7ca23b5a98662af18289f09e6cdd85a39a2863c945c", - "0xb304f71a9717cf40c22073f942618b44bf27cd5e2ed4a386ad45d75b0fcb5a8dafd35158211eaf639495c6f1a651cedb", - "0xb82d78d3eaaa7c5101b7a5aae02bd4f002cd5802d18c3abcda0dd53b036661c6d3c8b79e0abe591eab90b6fdc5fef5e3", - "0xabbd1884243a35578b80914a5084449c237ee4e4660c279d1073a4d4217d1b55c6b7e9c087dfd08d94ac1416273d8d07", - "0x92f4b61c62502745e3e198ec29bca2e18696c69dcb914d1f3a73f4998d012b90caf99df46e9bb59942e43cce377fe8fd", - "0x906e79df98185820c8208844e1ba6bd86cb96965814b01310bd62f22cbec9b5d379b2ef16772d6fc45a421b60cfd68fe", - "0xa0eae2784ef596e2eb270dd40c48d6c508e4394c7d6d08d4cc1b56fde42b604d10ba752b3a80f2c4a737e080ef51b44f", - "0x94c084985e276dc249b09029e49a4ef8a369cd1737b51c1772fbb458d61e3fe120d0f517976eba8ffa5711ba93e46976", - "0x83619a0157eff3f480ab91d1d6225fead74c96a6fd685333f1e8e4d746f6273e226bad14232f1d1168a274e889f202f1", - "0xa724fe6a83d05dbbf9bb3f626e96db2c10d6d5c650c0a909415fbda9b5711c8b26e377201fb9ce82e94fa2ab0bf99351", - "0xa8a10c1b91a3a1fa2d7fd1f78a141191987270b13004600601d0f1f357042891010717319489f681aa8a1da79f7f00d5", - "0xa398a2e95b944940b1f8a8e5d697c50e7aa03994a8a640dfad4ea65cfb199a4d97861a3ec62d1c7b2b8d6e26488ca909", - "0xa2eedfe5452513b2a938fffd560798ef81379c5a5032d5b0da7b3bb812addbaad51f564c15d9acbbfc59bb7eddd0b798", - "0xab31c572f6f145a53e13b962f11320a1f4d411739c86c88989f8f21ab629639905b3eedb0628067942b0dc1814b678ca", - "0xad032736dd0e25652d3566f6763b48b34ea1507922ed162890cd050b1125ec03b6d41d34fccba36ec90336f7cdf788ed", - "0x83028a558a5847293147c483b74173eca28578186137df220df747fccd7d769528d7277336ea03c5d9cdd0bc5ae3d666", - "0xab5d182cd1181de8e14d3ef615580217c165e470b7a094a276b78a3003089123db75c6e1650bf57d23e587c587cd7472", - "0xa4793e089fbdb1597654f43b4f7e02d843d4ab99ee54099c3d9f0bd5c0c5657c90bb076379a055b00c01b12843415251", - "0x98bdc52ee062035356fb2b5c3b41673198ddc60b2d1e546cb44e3bb36094ef3c9cf2e12bbc890feb7d9b15925439d1ea", - "0xa4f90cca6f48024a0341bd231797b03693b34e23d3e5b712eb24aba37a27827319b2c16188f97c0636a0c115381dc659", - "0x8888e6c2e4a574d04ba5f4264e77abc24ccc195f1a7e3194169b8a2ceded493740c52db4f9833b3dbf4d67a3c5b252cb", - "0x83dc4e302b8b0a76dc0292366520b7d246d73c6aebe1bdd16a02f645c082197bcff24a4369deda60336172cefbcf09af", - "0xa4eb2741699febfeb793914da3054337cc05c6fa00d740e5f97cb749ae16802c6256c9d4f0f7297dcdbb8b9f22fc0afa", - "0x8b65557d5be273d1cb992a25cfce40d460c3f288d5cb0a54bdef25cbd17cdea5c32ec966e493addf5a74fd8e95b23e63", - "0x97c6577e76c73837bcb398b947cb4d3323d511141e0ddd0b456f59fbb1e8f920a5c20d7827a24309145efddee786140f", - "0xabcc0849ffe2a6a72157de907907b0a52deece04cf8317bee6fe1d999444b96e461eac95b6afde3d4fe530344086a625", - "0x9385c0115cb826a49df1917556efa47b5b5e4022b6a0d2082053d498ec9681da904ecf375368bb4e385833116ea61414", - "0x8b868c1841f0cdc175c90a81e610b0652c181db06731f5c8e72f8fafa0191620742e61a00db8215a991d60567b6a81ca", - "0xa8df15406f31b8fcf81f8ff98c01f3df73bf9ec84544ddec396bdf7fafa6fe084b3237bf7ef08ad43b26517de8c3cd26", - "0xa9943d21e35464ce54d4cc8b135731265a5d82f9ccf66133effa460ffdb443cdb694a25320506923eede88d972241bf2", - "0xa1378ee107dd7a3abcf269fd828887c288363e9b9ca2711377f2e96d2ed5e7c5ec8d3f1da995a3dcbedf1752d9c088fc", - "0x8a230856f9227b834c75bdebc1a57c7298a8351874bf39805c3e0255d6fd0e846f7ad49709b65ec1fd1a309331a83935", - "0x877bcf42549d42610e1780e721f5800972b51ba3b45c95c12b34cb35eeaf7eac8fa752edd7b342411820cf9093fea003", - "0x84c7a0b63842e50905624f1d2662506b16d1f3ea201877dfc76c79181c338b498eceb7cad24c2142c08919120e62f915", - "0x8e18b1bd04b1d65f6ed349b5d33a26fe349219043ead0e350b50ae7a65d6ff5f985dd9d318d3b807d29faa1a7de4fe42", - "0x8ea7b5a7503e1f0b3c3cd01f8e50207044b0a9c50ed1697794048bbe8efd6659e65134d172fb22f95439e1644f662e23", - "0xb1954a2818cad1dad6d343a7b23afa9aa8ad4463edc4eb51e26e087c2010927535020d045d97d44086d76acdb5818cbf", - "0xa5271ea85d0d21fa1ff59b027cf88847c0f999bbf578599083ff789a9b5228bc161e1c81deb97e74db1a82a0afd61c50", - "0xaa2fa4c05af3387e2c799315781d1910f69977ec1cfea57a25f1a37c63c4daaa3f0ecd400884a1673e17dd5300853bcf", - "0xb1cd2a74ca0b8e6090da29787aef9b037b03b96607983a308b790133bd21297b21ca4e2edec890874096dbf54e9d04c3", - "0x801931607ec66a81272feaa984f0b949ad12d75ecf324ba96627bd4dc5ddead8ebf088f78e836b6587c2b6c0b3366b6c", - "0x95d79504710bdf0ad9b9c3da79068c30665818c2f0cdbba02cc0a5e46e29d596032ac984441b429bd62e34535c8d55b0", - "0x9857d41e25e67876510ff8dadf0162019590f902da1897da0ef6fc8556e3c98961edb1eb3a3a5c000f6c494413ded15e", - "0x8740c9ffe6bd179c19a400137c3bd3a593b85bd4c264e26b4dfb9e2e17ac73e5b52dfacc1dcb4033cfc0cd04785f4363", - "0x977f98f29d948b4097a4abdf9345f4c1fb0aa94ba0c6bf6faa13b76f3a3efc8f688e1fe96099b71b3e1c05041118c8d1", - "0xa364422b1239126e3e8d7b84953ce2181f9856319b0a29fcab81e17ac27d35798088859c1cfc9fc12b2dbbf54d4f70b3", - "0xa0f6ba637f0db7a48e07439bb92ddb20d590ce9e2ed5bab08d73aa22d82c32a9a370fe934cbe9c08aeb84b11adcf2e0e", - "0xa2c548641bd5b677c7748327cca598a98a03a031945276be6d5c4357b6d04f8f40dd1c942ee6ec8499d56a1290ac134d", - "0x9863e9cc5fbcdbd105a41d9778d7c402686bfd2d81d9ed107b4fda15e728871c38647529693306855bee33a00d257a7e", - "0xa54173bf47b976290c88fd41f99300135de222f1f76293757a438450880e6f13dbde3d5fe7afc687bdfbcfc4fbc1fc47", - "0xb8db413917c60907b73a997b5ab42939abd05552c56a13525e3253eb72b83f0d5cc52b695968a10005c2e2fe13290e61", - "0xa1f8388ef21697c94ba90b1a1c157f0dc138e502379e6fc5dc47890d284563e5db7716266e1b91927e5adf3cde4c0a72", - "0x9949013a59d890eb358eab12e623b2b5edb1acbee238dfad8b7253102abc6173922e188d5b89ec405aa377be8be5f16d", - "0xa00fdb7710db992041f6ddb3c00099e1ce311dea43c252c58f560c0d499983a89de67803a8e57baa01ee9d0ee6fa1e44", - "0xa8b1bcbed1951c9cdb974b61078412881b830b48cd6b384db0c00fa68bcc3f4312f8e56c892ea99d3511857ef79d3db9", - "0x8f3ee78404edc08af23b1a28c2012cee0bdf3599a6cb4ea689fc47df4a765ef519191819a72562b91a0fbcdb896a937e", - "0x8155bbb7fa8d386848b0a87caae4da3dec1f3dade95c750a64a8e3555166ccc8799f638bd80ed116c74e3a995541587a", - "0xabfe30adbc0a6f1fd95c630ed5dac891b85384fa9331e86b83217f29dff0bd7cad19d328485715a7e3df9a19069d4d2f", - "0x89d0783e496ee8dbb695764b87fb04cee14d4e96c4ba613a19736971c577d312079048142c12ce5b32b21e4d491d281b", - "0x856b8dbc9c5d8f56b6bb7d909f339ca6da9a8787bba91f09130a025ab6d29b64dbf728ba6ed26e160a23c1cdb9bc037b", - "0x8a30dd2ea24491141047a7dfe1a4af217661c693edf70b534d52ca547625c7397a0d721e568d5b8398595856e80e9730", - "0xae7e1412feb68c5721922ed9279fb05549b7ef6812a4fd33dbbbd7effab756ab74634f195d0c072143c9f1fd0e1ee483", - "0xb7ce970e06fa9832b82eef572f2902c263fda29fdce9676f575860aae20863046243558ede2c92343616be5184944844", - "0x85ed0531f0e5c1a5d0bfe819d1aa29d6d5ff7f64ad8a0555560f84b72dee78e66931a594c72e1c01b36a877d48e017ca", - "0xb8595be631dc5b7ea55b7eb8f2982c74544b1e5befc4984803b1c69727eac0079558182f109e755df3fd64bee00fcaa5", - "0x99e15a66e5b32468ef8813e106271df4f8ba43a57629162832835b8b89402eb32169f3d2c8de1eb40201ce10e346a025", - "0x844c6f5070a8c73fdfb3ed78d1eddca1be31192797ad53d47f98b10b74cc47a325d2bc07f6ee46f05e26cf46a6433efb", - "0x974059da7f13da3694ad33f95829eb1e95f3f3bfc35ef5ef0247547d3d8ee919926c3bd473ab8b877ff4faa07fcc8580", - "0xb6f025aecc5698f6243cc531782b760f946efebe0c79b9a09fe99de1da9986d94fa0057003d0f3631c39783e6d84c7d5", - "0xb0c5358bc9c6dfe181c5fdf853b16149536fbb70f82c3b00db8d854aefe4db26f87332c6117f017386af8b40288d08f9", - "0xa3106be5e52b63119040b167ff9874e2670bd059b924b9817c78199317deb5905ae7bff24a8ff170de54a02c34ff40a4", - "0xad846eb8953a41c37bcd80ad543955942a47953cbc8fb4d766eac5307892d34e17e5549dc14467724205255bc14e9b39", - "0xb16607e7f0f9d3636e659e907af4a086ad4731488f5703f0917c4ce71a696072a14a067db71a3d103530920e1ec50c16", - "0x8ed820e27116e60c412c608582e9bb262eaaf197197c9b7df6d62b21a28b26d49ea6c8bb77dfde821869d9b58025f939", - "0x97bc25201d98cde389dd5c0c223a6f844393b08f75d3b63326343073e467ac23aacef630ddc68545ea874299ba4a3b4f", - "0xb73c9695ad2eefd6cc989a251c433fab7d431f5e19f11d415a901762717d1004bb61e0cc4497af5a8abf2d567e59fef4", - "0xadaabe331eea932533a7cc0cf642e2a5e9d60bbc92dd2924d9b429571cbf0d62d32c207b346607a40643c6909b8727e2", - "0xa7b1bbfe2a5e9e8950c7cb4daab44a40c3ffab01dc012ed7fe445f4af47fa56d774a618fafe332ab99cac4dfb5cf4794", - "0xb4a3c454dcd5af850212e8b9ba5fe5c0d958d6b1cabbf6c6cfe3ccbc4d4c943309c18b047256867daf359006a23f3667", - "0xa5c0b32f6cef993834c1381ec57ad1b6f26ae7a8190dd26af0116e73dadc53bb0eeb1911419d609b79ce98b51fdc33bc", - "0xac2f52de3ecf4c437c06c91f35f7ac7d171121d0b16d294a317897918679f3b9db1cef3dd0f43adb6b89fe3030728415", - "0x94722ae6d328b1f8feaf6f0f78804e9b0219de85d6f14e8626c2845681841b2261d3e6a2c5b124086b7931bf89e26b46", - "0xa841a0602385d17afabca3a1bb6039167d75e5ec870fea60cfcaec4863039b4d745f1a008b40ec07bca4e42cb73f0d21", - "0x8c355f0a1886ffced584b4a002607e58ff3f130e9de827e36d38e57cb618c0cb0b2d2dea2966c461cb3a3887ede9aef1", - "0xa6a9817b0fc2fd1786f5ba1a7b3d8595310987fb8d62f50a752c6bb0b2a95b67d03a4adfd13e10aa6190a280b7ee9a67", - "0xa1d2e552581ecbafeaef08e389eaa0b600a139d446e7d0648ac5db8bbbf3c438d59497e3a2874fc692b4924b87ff2f83", - "0xa1b271c55389f25639fe043e831e2c33a8ba045e07683d1468c6edd81fedb91684e4869becfb164330451cfe699c31a8", - "0x8c263426e7f7e52f299d57d047a09b5eeb893644b86f4d149535a5046afd655a36d9e3fdb35f3201c2ccac2323a9582e", - "0xb41c242a7f7880c714241a97d56cce658ee6bcb795aec057a7b7c358d65f809eb901e0d51256826727dc0dc1d1887045", - "0x93001b9445813c82f692f94c0dc1e55298f609936b743cf7aae5ebfa86204f38833d3a73f7b67314be67c06a1de5682d", - "0x82087536dc5e78422ad631af6c64c8d44f981c195ddea07d5af9bb0e014cdc949c6fa6e42fce823e0087fdb329d50a34", - "0x8e071861ceba2737792741c031f57e0294c4892684506b7c4a0fc8b2f9a0a6b0a5635de3d1e8716c34df0194d789ae86", - "0xb471c997e1e11774bd053f15609d58838a74073a6c089a7a32c37dd3f933badf98c7e5833263f3e77bc0d156a62dd750", - "0x8d2d8686fb065b61714414bb6878fff3f9e1e303c8e02350fd79e2a7f0555ded05557628152c00166ce71c62c4d2feaa", - "0xae4c75274d21c02380730e91de2056c0262ffcecf0cbdb519f0bdb0b5a10ae2d4996b3dc4b3e16dbaea7f0c63d497fef", - "0x97140d819e8ca6330e589c6debdee77041c5a9cedb9b8cbd9c541a49207eeb7f6e6b1c7e736ec8ba6b3ab10f7fcd443a", - "0xaf6659f31f820291a160be452e64d1293aa68b5074b4c066dac169b8d01d0179139504df867dc56e2a6120354fc1f5be", - "0xa5e5d8088a368024617bfde6b731bf9eee35fc362bed3f5dfdd399e23a2495f97f17728fec99ca945b3282d1858aa338", - "0xa59cfc79d15dbdde51ab8e5129c97d3baba5a0a09272e6d2f3862370fdbaf90994e522e8bd99d6b14b3bb2e9e5545c6f", - "0xa30499b068083b28d6c7ddcc22f6b39b5ec84c8ee31c5630822c50ea736bb9dca41c265cffc6239f1c9ef2fd21476286", - "0x88ffe103eca84bbe7d1e39a1aa599a5c7c9d5533204d5c4e085402a51441bb8efb8971efe936efbbfa05e5cb0d4b8017", - "0xb202356fbf95a4d699154639e8cb03d02112c3e0128aab54d604645d8510a9ba98936028349b661672c3a4b36b9cb45d", - "0x8b89bb6574bf3524473cff1ff743abcf1406bd11fb0a72070ccd7d8fce9493b0069fb0c6655252a5164aee9e446ea772", - "0x93247b1038fa7e26667ee6446561d4882dc808d1015daafb705935ddc3598bb1433182c756465960480f7b2de391649e", - "0xb027f94d3358cbb8b6c8c227300293a0dee57bf2fee190a456ad82ecfb6c32f8090afa783e2ab16f8139805e1fb69534", - "0xa18bb1849b2f06c1d2214371031d41c76ffa803ee3aa60920d29dbf3db5fbfac2b7383d5d0080ba29ce25c7baa7c306b", - "0x827bf9fd647e238d5ac961c661e5bbf694b4c80b3af8079f94a2484cb8fba2c8cf60e472ebcd0b0024d98ae80ad2ff5a", - "0x838e891218c626a7f39b8fd546b013587408e8e366ecc636b54f97fa76f0a758bc1effa1d0f9b6b3bc1a7fcc505970a0", - "0x836523b5e8902d6e430c6a12cff01e417d2bd7b402e03904034e3b39755dee540d382778c1abe851d840d318ebedce7f", - "0x850a77dda9ac6c217e2ef00bf386a1adec18b7f462f52801c4f541215690502a77ef7519b690e22fdf54dc2109e0ca38", - "0xa8265c6ae7b29fc2bda6a2f99ced0c1945dd514b1c6ca19da84b5269514f48a4f7b2ccbab65c9107cfd5b30b26e5462f", - "0xab3d02ee1f1267e8d9d8f27cc388e218f3af728f1de811242b10e01de83471a1c8f623e282da5a284d77884d9b8cde0e", - "0x831edaf4397e22871ea5ddee1e7036bab9cc72f8d955c7d8a97f5e783f40532edbbb444d0520fefcffeab75677864644", - "0x80484487977e4877738744d67b9a35b6c96be579a9faa4a263e692295bb6e01f6e5a059181f3dd0278e2c3c24d10a451", - "0xaae65a18f28c8812617c11ecf30ad525421f31fb389b8b52d7892415e805a133f46d1feca89923f8f5b8234bd233486a", - "0xb3a36fd78979e94288b4cefed82f043a7e24a4a8025479cc7eb39591e34603048a41ee606ee03c0b5781ebe26a424399", - "0xb748b3fc0d1e12e876d626a1ba8ad6ad0c1f41ea89c3948e9f7d2666e90173eb9438027fadcd741d3ae0696bd13840f1", - "0xacdd252d7c216c470683a140a808e011c4d5f1b4e91aeb947f099c717b6a3bad6651142cde988330827eb7d19d5fb25c", - "0xb9a25556a6ca35db1ed59a1ec6f23343eab207a3146e4fc3324136e411c8dba77efd567938c63a39c2f1c676b07d8cdb", - "0xa8db6aef8f5680d2bdb415d7bcaae11de1458678dcb8c90c441d5986c44f83a9e5855662d0c1aace999172d8628d8fe1", - "0xaf58147108e9909c3a9710cc186eab598682dca4bfd22481e040b8c000593ecb22c4ede4253ac9504e964dfa95a9b150", - "0x8dd8bb70f1c9aec0fcc9478f24dfc9c3c36c0bf5ff7a67c017fa4dab2ec633fbd7bc9d8aa41ea63e2696971ed7e375f5", - "0xaa98d600b22aff993a4d7a3ccabd314e1825b200cb598f6b797d7e4d6a76d89e34a4d156c06bddfc62f2ef9b4c809d1d", - "0x8a8fc960d6c51294b8205d1dabe430bef59bda69824fa5c3c3105bef22ac77c36d2d0f38ffc95ce63731de5544ccbeff", - "0xb6d1020efe01dc8032bd1b35e622325d7b9af9dcd5c9c87c48d7d6ebc58644454294c59b7f4b209204b5b1f899f473bf", - "0x8a750dc9fe4891f2dfe5759fb985939810e4cdc0b4e243ff324b6143f87676d8cb4bcb9dfb01b550801cedcaaa5349e2", - "0x98c13142d3a9c5f8d452245c40c6dae4327dd958e0fda85255ea0f87e0bcbaa42a3a0bd50407ed2b23f9f6317a8a4bc5", - "0x99f2b83d9ec4fc46085a6d2a70fd0345df10f4a724c1ba4dee082a1fde9e642e3091992ebf5f90a731abcb6ec11f6d9b", - "0xb218546ab2db565b2489ea4205b79daa19ef2acbf772ccaaa5e40150e67ea466090d07198444b48e7109939aa2319148", - "0x84f9d1d868e4b55e535f1016558f1789df0daa0ead2d13153e02f715fe8049b1ce79f5bc1b0bbbb0b7e4dd3c04783f3f", - "0x80d870d212fbddfdda943e90d35a5a8aa0509a7a1e7f8909f2fcb09c51c3026be47cc7a22620a3063406872105b4f81a", - "0xb5b15138ff6551fac535d4bbce2ea6adc516b6b7734b4601c66ec029da2615e3119dc9ad6a937344acfd7b50e4a1a2ae", - "0x95d2f97652086e7ceb54e1d32692b1c867ffba23c4325740c7f10d369283d1b389e8afa0df967831ade55696931e7934", - "0x8a5b580403e1a99cd208f707e8ce0d3f658c8280417683f69008d09cc74d835a85f7380f391b36ead9ac66d9eedd1cbe", - "0xa8b0c90bff34c86720637b5a2081f0f144cfe2205c1176cacd87d348609bc67af68aed72414dc9aa6f44a82c92c2a890", - "0x865abbdd96c496892c165a8de0f9e73348bf24fce361d7a9048710178a3625881afb0006e9f5ee39124866b87904c904", - "0xace67bb994adef4b6f841cdf349195608030044562780a7e9b00b58a4ff117268a03ff01e5a3a9d9d7eff1dd01f5f4bf", - "0xb9371d59185b3d2d320d3fefeadb06ba2aa7d164352fb8dc37571509509fa214d736d244ac625a09a033a10d51611e2e", - "0xa8ef992771422dcf2d6d84386fde9fe5dba88bfded3dfcd14074ca04331b4fd53a7f316615cdfaf10ed932cbb424a153", - "0x868cbc75f8f789ea45eded2768a1dac0763347e0d8e8028d316a21005f17be179d26d5965903e51b037f2f57fe41765d", - "0xb607111bcdfd05fa144aa0281b13ee736079ebbbf384d938a60e5e3579639ed8ef8eb9ca184868cdb220a8e130d4a952", - "0xaca55702af5cae4cae65576769effd98858307a71b011841c563b97c2aa5aeb5c4f8645d254f631ed1582df3dbbf17da", - "0xb9b5cbace76246e80c20dfcc6f1e2c757a22ab53f7fd9ff8a1d309538b55174e55e557a13bf68f095ff6a4fa637ef21a", - "0x8571b0a96871f254e2397c9be495c76379faf347801cb946b94e63212d6a0da61c80e5d7bebbabcd6eaa7f1029172fe5", - "0x902540326281e6dc9c20d9c4deaaf6fbbbcc3d1869bd0cf7f081c0525bea33df5cfa24ead61430fda47fb964fcc7994b", - "0x841af09279d3536a666fa072278950fabf27c59fc15f79bd52acb078675f8087f657929c97b4bc761cbade0ecb955541", - "0xa1f958b147ddf80ab2c0746ba11685c4bae37eb25bfa0442e7e1078a00d5311d25499da30f6d168cb9302ea1f2e35091", - "0x863d939381db37d5a5866964be3392a70be460f0353af799d6b3ed6307176972686bd378f8ad457435a4094d27e8dfb7", - "0x835cd4d7f36eff553d17483eb6c041b14280beb82c7c69bca115929658455a1931212976c619bafb8179aed9940a8cc6", - "0x8d0770e3cb8225e39c454a1fc76954118491b59d97193c72c174ecc7613051e5aed48a534016a8cf0795c524f771a010", - "0x91aa4edb82f6f40db2b7bd4789cc08786f6996ebed3cb6f06248e4884bc949793f04a4c5ea6eefe77984b1cc2a45d699", - "0x8fb494ca2449f659ff4838833507a55500a016be9293e76598bbae0a7cb5687e4693757c2b6d76e62bd6c7f19ed080bb", - "0xb59b104449a880a282c1dd6a3d8debb1d8814ef35aab5673c1e500ee4cb0e840fb23e05fa5a0af92509c26b97f098f90", - "0xaca908e3bad65e854ae6be6c5db441a06bcd47f5abafdfa8f5a83c8cd3c6e08c33cab139c45887887a478338e19ceb9f", - "0x806f5d802040313a31964fc3eb0ee18ac91b348685bed93c13440984ee46f3d2da7194af18c63dea4196549129660a4e", - "0xae4b2dca75c28d8f23b3ab760b19d839f39ff5a3112e33cb44cff22492604a63c382b88ec67be4b0266924dd438c3183", - "0x99d1c29c6bd8bf384e79cd46e30b8f79f9cbc7d3bf980e9d6ffba048f0fc487cac45c364a8a44bb6027ad90721475482", - "0xa16e861c1af76d35528c25bf804bfc41c4e1e91b2927d07d8e96bffe3a781b4934e9d131ecf173be9399800b8269efac", - "0xa253303234fb74f5829060cdcef1d98652441ab6db7344b1e470d195a95722675988048d840201c3b98e794b1e8b037c", - "0x905ac8a0ea9ce0eb373fb0f83dd4cbe20afb45b9d21ae307846fd4757d4d891b26a6711924e081e2b8151e14a496da18", - "0xb485315791e775b9856cc5a820b10f1fa5028d5b92c2f0e003ba55134e1eddb3eb25f985f2611a2257acf3e7cfdfab5e", - "0xb6189c0458b9a043ebc500abc4d88083a3487b7ac47ed5e13ab2a41e0a1bee50d54a406063f92bc96959f19e822a89a7", - "0xa30e15f995fd099a223fc6dc30dad4b8d40bee00caa2bc3223ba6d53cd717c4968a3e90c4618c711ed37cc4cd4c56cf3", - "0xa1b1ed07fcc350bb12a09cd343768d208fc51a6b3486f0ece8f5a52f8a5810b4bc7ab75582ec0bc2770aed52f68eace5", - "0x88aa739fbae4bece147ba51a863e45d5f7203dbc3138975dc5aef1c32656feb35f014d626e0d5b3d8b1a2bda6f547509", - "0xab570f3c8eabfca325b3a2ea775ef6b0c6e6138c39d53c2310329e8fb162869fde22b0e55688de9eb63d65c37598fca3", - "0x89d274762c02158e27cb37052e296a78f2b643eb7f9ae409f8dac5c587d8b4d82be4ef7c79344a08ebec16ac4a895714", - "0x99c411d2ad531e64f06e604d44c71c7c384424498ecd0a567d31ec380727fb605af76643d0d5513dd0a8d018076dd087", - "0x80d0777fa9f79f4a0f0f937d6de277eec22b3507e2e398f44b16e11e40edf5feff55b3b07a69e95e7e3a1621add5ed58", - "0xb2430a460783f44feb6e4e342106571ef81ad36e3ddd908ec719febeb7acaf4b833de34998f83a1dab8f0137a3744c11", - "0xb8f38ccfc7279e1e30ad7cefc3ea146b0e2dff62430c50a5c72649a4f38f2bac2996124b03af2079d942b47b078cc4f8", - "0xa178a450a62f30ec2832ac13bbc48789549c64fc9d607b766f6d7998558a0e2fad007ae0148fc5747189b713f654e6ba", - "0x98c5ede296f3016f6597f7ccc5f82c88fd38ed6dc3d6da3e4a916bfd7c4c95928722a1d02534fe89387c201d70aa6fd2", - "0xa8cc5e98573705d396576e022b2ba2c3e7c7ece45cd8605cb534b511763682582299e91b4bb4100c967019d9f15bbfaf", - "0x848480ea7b7d9536e469da721236d932870b7bbee31ccf7ae31b4d98d91413f59b94a1e0d1786ee7342295aa3734969c", - "0xb88ea38f9ee432f49e09e4e013b19dff5a50b65453e17caf612155fff6622198f3cba43b2ea493a87e160935aaaf20a9", - "0x949376934a61e0ef8894339c8913b5f3b228fa0ae5c532ad99b8d783b9e4451e4588541f223d87273c0e96c0020d5372", - "0x96f90bb65ca6b476527d32c415814b9e09061648d34993f72f28fae7dc9c197e04ef979f804076d107bb218dfd9cb299", - "0xa4402da95d9942c8f26617e02a7cef0ebc4b757fac72f222a7958e554c82cc216444de93f659e4a1d643b3e55a95d526", - "0x81179cbc26a33f6d339b05ea3e1d6b9e1190bd44e94161ae36357b9cdf1e37d745d45c61735feed64371fe5384102366", - "0xad4dc22bdbd60e147fdac57d98166de37c727f090059cfc33e5ee6cf85e23c2643996b75cf1b37c63f3dc9d3c57ffa18", - "0x8a9b1b93dc56e078ce3bb61c2b0088fd6c3e303ba6b943231cc79d4a8e8572f4109bbde5f5aa7333aae3287909cb0fe2", - "0x8876ef583bc1513322457a4807d03381ba1f4d13e179260eaa3bddfede8df677b02b176c6c9f74c8e6eab0e5edee6de6", - "0xb6c67e228bf190fbaeb2b7ec34d4717ce710829c3e4964f56ebb7e64dc85058c30be08030fa87cc94f1734c5206aef5f", - "0xa00cb53b804ee9e85ce12c0103f12450d977bc54a41195819973c8a06dcb3f46f2bf83c3102db62c92c57ab4dd1e9218", - "0xa7675a64772eefddf8e94636fb7d1d28f277074327c02eea8fae88989de0c5f2dc1efed010f4992d57b5f59a0ab40d69", - "0x8d42bb915e0bf6a62bcdf2d9330eca9b64f9ec36c21ae14bf1d9b0805e5e0228b8a5872be61be8133ad06f11cb77c363", - "0xa5b134de0d76df71af3001f70e65c6d78bed571bc06bfddf40d0baad7ea2767608b1777b7ef4c836a8445949877eeb34", - "0xaeadbc771eaa5de3a353229d33ed8c66e85efbd498e5be467709cb7ff70d3f1a7640002568b0940e3abd7b2da81d2821", - "0x8c28da8e57a388007bd2620106f6226b011ee716a795c5d9f041c810edf9cf7345b2e2e7d06d8a6b6afa1ee01a5badc1", - "0x8ed070626a4d39ffd952ddb177bc68fd35b325312e7c11694c99b691f92a8ea7734aeb96cf9cc73e05b3c1b1dcad6978", - "0xada83e18e4842f3d8871881d5dbc81aed88a1328298bfdc9e28275094bd88d71b02e7b8501c380fa8d93096cbc62f4fb", - "0x8befc3bec82dcf000a94603b4a35c1950ba5d00d4bed12661e4237afa75062aa5dcef8eac0b9803136c76d2dd424a689", - "0x97c6f36c91ca5ca9230bfcbf109d813728b965a29b62e5f54c8e602d14a52ac38fa1270de8bfe1ab365426f3fc3654c7", - "0xb01d192af3d8dbce2fe2fece231449e70eb9ac194ec98e758da11ca53294a0fa8c29b1d23a5d9064b938b259ea3b4fb5", - "0x819a2c20646178f2f02865340db1c3c6ebc18f4e6559dd93aa604388796a34bd9fed28ad3ccc8afc57a5b60bb5c4e4ec", - "0xa9ffc877470afc169fecf9ec2dc33253b677371938b0c4ffa10f77bb80089afa2b4488437be90bb1bcf7586a6f4286e3", - "0xb533051c7ce7107176bcb34ad49fdb41fac32d145854d2fe0a561c200dcf242da484156177e2c8f411c3fdf1559ecf83", - "0x8fe2caff2e4241d353110a3618832f1443f7afe171fd14607009a4a0aa18509a4f1367b67913e1235ac19de15e732eb1", - "0x84705c6370619403b9f498059f9869fdf5f188d9d9231a0cb67b1da2e8c906ead51b934286497293698bba269c48aa59", - "0x899dddf312a37e3b10bdaaacc1789d71d710994b6ee2928ac982ad3fd8a4f6167672bc8bf3419412711c591afe801c28", - "0xb2f7916d946b903ded57b9d57025386143410a41a139b183b70aeca09cf43f5089ead1450fce4e6eb4fba2c8f5c5bbe5", - "0x8d5f742fe27a41623b5820914c5ca59f82246010fa974304204839880e5d0db8bc45ebab2ad19287f0de4ac6af25c09e", - "0xb93d4a1f6f73ac34da5ffbd2a4199cf1d51888bc930dc3e481b78806f454fcb700b4021af7525b108d49ebbbaa936309", - "0x8606f8d9121512e0217a70249937e5c7f35fbfe019f02248b035fa3a87d607bc23ae66d0443e26a4324f1f8e57fd6a25", - "0xb21312cdec9c2c30dd7e06e9d3151f3c1aceeb0c2f47cf9800cce41521b9d835cb501f98b410dc1d49a310fdda9bc250", - "0xa56420b64286bdddda1e212bba268e9d1ba6bdb7132484bf7f0b9e38099b94a540884079b07c501c519b0813c184f6b4", - "0x80b2cf0e010118cb2260f9c793cef136f8fa7b5e2711703735524e71d43bce2d296c093be41f2f59118cac71f1c5a2ff", - "0xadcb12d65163804d2f66b53f313f97152841c3625dbbda765e889b9937195c6fcd55d45cc48ebffabb56a5e5fe041611", - "0x8b8a42e50dc6b08ab2f69fc0f6d45e1ea3f11ba0c1008ee48448d79d1897356599e84f7f9d8a100329ed384d6787cfc4", - "0xaaa9c74afa2dec7eccfbd8bb0fc6f24ed04e74c9e2566c0755a00afdfdf3c4c7c59e2a037ec89c2f20af3fae1dd83b46", - "0xaa9f6e8fd59187171c6083ae433627d702eb78084f59010ff07aff8f821f7022ef5fbbe23d76814d811b720a8bfa6cc3", - "0xa56a3ded501659ad006d679af3287080b7ee8449e579406c2cae9706ef8bf19c1fc2eb2a6f9eaf2d3c7582cded73e477", - "0x81971e077c1da25845840222b4191e65f6d242b264af4e86800f80072d97d2a27a6adc87c3a1cb1b0dd63d233fbafa81", - "0xa6fa5453c4aaad2947969ee856616bf6448224f7c5bf578f440bcfc85a55beb40bef79df8096c4db59d1bd8ef33293ea", - "0x87c545adbfaaf71e0ab4bac9ae4e1419718f52b0060e8bb16b33db6d71b7248ae259d8dd4795b36a4bbb17f8fae9fd86", - "0xb4c7a9bc0910e905713291d549cec5309e2d6c9b5ea96954489b1dff2e490a6c8b1fa1e392232575f0a424ba94202f61", - "0x802350b761bcaba21b7afe82c8c6d36ee892b4524ab67e2161a91bbfa1d8e92e7e771efb1f22c14126218dd2cb583957", - "0xb4e7ddb9143d4d78ea8ea54f1c908879877d3c96ee8b5e1cb738949dcfceb3012a464506d8ae97aa99ea1de2abf34e3d", - "0xa49a214065c512ad5b7cc45154657a206ef3979aa753b352f8b334411f096d28fd42bca17e57d4baaafb014ac798fc10", - "0x8a80c70a06792678a97fe307520c0bf8ed3669f2617308752a2ab3c76fdf3726b014335a9b4c9cbcfc1df3b9e983c56f", - "0xa34721d9e2a0e4d08995a9d986dc9c266c766296d8d85e7b954651ad2ca07e55abb1b215898ee300da9b67114b036e0d", - "0x8cfce4564a526d7dca31e013e0531a9510b63845bbbd868d5783875ed45f92c1c369ce4a01d9d541f55f83c2c0a94f03", - "0xab3f5f03a5afc727778eb3edf70e4249061810eba06dc3b96b718e194c89429c5bfbec4b06f8bce8a2118a2fdce67b59", - "0xaa80c2529fc19d428342c894d4a30cb876169b1a2df81a723ab313a071cba28321de3511a4de7846207e916b395abcc9", - "0x82b7828249bf535ef24547d6618164b3f72691c17ca1268a5ee9052dba0db2fdd9987c8e083307a54399eab11b0f76b1", - "0x8fbcb56b687adad8655a6cf43364a18a434bf635e60512fad2c435cf046f914228fb314f7d8d24d7e5e774fb5ffb1735", - "0xa3010a61a2642f5ebbce7b4bc5d6ecb3df98722a49eb1655fe43c1d4b08f11dfad4bcec3e3f162d4cc7af6a504f4d47c", - "0xb3dcc0fdf531478e7c9ef53190aa5607fd053a7d2af6c24a15d74c279dbb47e3c803a1c6517d7e45d6534bb59e3527f5", - "0x8648f6316c898baaca534dff577c38e046b8dfa8f5a14ee7c7bc95d93ae42aa7794ba0f95688a13b554eeb58aeedf9ba", - "0x89fca6fc50407695e9315483b24f8b4e75936edf1475bcf609eed1c4370819abac0e6a7c3c44f669560367d805d9ba63", - "0xa367a17db374f34cd50f66fb31ba5b7de9dbe040f23db2dcc1d6811c0e863606f6c51850af203956f3399000f284d05f", - "0x91030f9ca0fff3e2dbd5947dcf2eba95eb3dbca92ee2df0ed83a1f73dbf274611af7daf1bb0c5c2ee46893ab87013771", - "0x84d56181f304ce94015ea575afeef1f84ea0c5dbb5d29fb41f25c7f26077b1a495aff74bd713b83bce48c62d7c36e42d", - "0x8fe2f84f178739c3e2a2f7dcac5351c52cbed5fa30255c29b9ae603ffd0c1a181da7fb5da40a4a39eec6ce971c328fcf", - "0xa6f9b77b2fdf0b9ee98cb6ff61073260b134eb7a428e14154b3aa34f57628e8980c03664c20f65becfe50d2bdd2751d4", - "0x8c6760865445b9327c34d2a1247583694fbeb876055a6a0a9e5cb460e35d0b2c419e7b14768f1cc388a6468c94fd0a0f", - "0xaf0350672488a96fe0089d633311ac308978a2b891b6dbb40a73882f1bda7381a1a24a03e115ead2937bf9dcd80572ad", - "0xa8e528ec2ee78389dd31d8280e07c3fdd84d49556a0969d9d5c134d9a55cd79e1d65463367b9512389f125ed956bc36a", - "0x942c66589b24f93e81fe3a3be3db0cd4d15a93fb75260b1f7419f58d66afaa57c8d2d8e6571536790e2b415eec348fd9", - "0x83fe4184b4b277d8bf65fb747b3c944170824b5832751057e43465526560f60da6e5bbee2f183cb20b896a20197168c7", - "0x88a71aada494e22c48db673d9e203eef7a4e551d25063b126017066c7c241ee82bedaa35741de4bd78a3dd8e21a8af44", - "0x8c642a3186ca264aac16ee5e27bd8da7e40e9c67ae159b5d32daa87b7de394bf2d7e80e7efb1a5506c53bfd6edd8c2c3", - "0x81855d6de9a59cef51bef12c72f07f1e0e8fe324fcc7ec3f850a532e96dcd434c247130610aaee413956f56b31cbb0dc", - "0xa01e61390dcd56a58ad2fcdb3275704ddfbedef3ba8b7c5fce4814a6cdd03d19d985dba6fd3383d4db089444ea9b9b4d", - "0x96494e89cbf3f9b69488a875434302000c2c49b5d07e5ff048a5b4a8147c98291ae222529b61bb66f1903b2e988e5425", - "0xb9689b3e8dddc6ec9d5c42ba9877f02c1779b2c912bba5183778dc2f022b49aed21c61c8ec7e3c02d74fe3f020a15986", - "0xa2a85e213b80b0511395da318cbb9935c87b82c305f717a264155a28a2ea204e9e726bae04ce6f012e331bd6730cbb9d", - "0x91b70f44c7d8c5980ce77e9033a34b05781cbe773854d3f49d2905cc711a3d87c20d5d496801ad6fd82438874ce732b8", - "0x884596417ff741bb4d11925d73852ffeea7161c7f232be3bdce9e6bbe7884c3a784f8f1807356ae49d336b7b53a2b495", - "0xae2aed8ab6951d8d768789f5bc5d638838d290d33ccc152edfb123e88ba04c6272b44294b0c460880451ad7b3868cc6a", - "0x89d8ebfb9beebc77189d27de31c55f823da87798a50bca21622cbf871e5d9f1d3182cf32ee9b90f157e6ce298e9efccf", - "0xafd00a4db4c2ed93cf047378c9402914b6b3255779f3bb47ded4ab206acb7eaebba0fd7762928e681b1aebcfee994adc", - "0xa2e49b6cd32e95d141ebc29f8c0b398bb5e1a04945f09e7e30a4062142111cd7aa712ac0e3e6394cfb73dd854f41ad77", - "0xae8e714ab6e01812a4de5828d84060f626358bb2b955f6fb99ae887b0d5ce4f67ebc079ab9e27d189bf1d3f24f7c2014", - "0xa3100c1eebf46d604e75ebf78569c25acf938d112b29ccbe1a91582f6bd8ef5548ae3961c808d3fb73936ac244e28dbc", - "0xa9a02dcff0e93d47ead9cdddc4759971c2d848580bf50e117eb100cafca6afeaa7b87208513d5f96b1e1440ffc1b0212", - "0x894ab01462137e1b0db7b84920a3b677fbb46c52b6f4c15320ef64f985e0fc05cec84cd48f389ce039779d5376966ea3", - "0xb1e40e8399ee793e5f501c9c43bde23538e3ce473c20a9f914f4a64f5b565748d13ab2406efe40a048965ee4476113e4", - "0xa5a7d97a19e636238968670a916d007bf2ce6ae8e352345d274101d0bbe3ac9b898f5b85814a7e4c433dd22ac2e000ff", - "0xb6394c43b82923231d93fd0aa8124b757163ba62df369898b9481f0118cb85375d0caac979a198ece432dbb4eb7cc357", - "0x82d522ae3ff4fe2c607b34b42af6f39c0cf96fcfe1f5b1812fca21c8d20cece78376da86dcbd6cdb140e23c93ae0bcb2", - "0xb6e0d986383bc4955508d35af92f2993e7e89db745f4525948c5274cfd500880cb5a9d58a5b13d96f6368bb266a4433e", - "0xb0b4325772ec156571d740c404e1add233fb693579f653b0fae0042b03157d3b904838f05c321d2d30f2dbd27c4d08ad", - "0xac41367250263a2099006ef80c30bac1d2f25731d4874be623b6e315c45b0dc9a65f530fce82fb3dc25bd0610008c760", - "0xb6c0b1ed7df53da04a6f3e796d3bfa186f9551c523bc67898bc0ecfc6b4a4a22f8c4d3bfc740ebf7b9fa5b0ea9431808", - "0x8e78fca17346601219d01e5cd6a4837161a7c8f86fe2a8d93574d8006da5f06ae7c48eea7d2b70992c2a69184619663c", - "0xa21f91f47e04fafbfafacf3185b6863766a2d0c324ccac2c3853a4748af5897dbbe31d91473b480f646121339c9bae2d", - "0xa464d68786ab1fc64bd8734fce0be6fbe8dc021d3e771ff492ada76eedff466577c25e282b7c8ab4c1fd95ef5ff3631e", - "0x829a24badc7714081e03509ccfb00818ce40430682c1c0e4a399cd10b690bda1f921aabcbf1edfb1d8a2e98e6c0cedd6", - "0x87ccf7e4bbcb818ef525435e7a7f039ecbb9c6670b0af163173da38cbdb07f18bc0b40b7e0c771a74e5a4bc8f12dfe2c", - "0x94087bd2af9dbeb449eb7f014cfbf3ee4348c0f47cde7dc0ad401a3c18481a8a33b89322227dee0822244965ae5a2abb", - "0x896b83ed78724dac8a3d5a75a99de8e056a083690152c303326aa833618b93ef9ec19ab8c6ef0efe9da2dbcccac54431", - "0x821e6a0d7ccf3c7bd6a6cc67cde6c5b92fb96542cb6b4e65a44bbc90bbc40c51ff9e04702cb69dd2452f39a2ff562898", - "0xb35b2096cda729090663a49cb09656c019fef1fc69a88496028d3a258ad2b3fd6d91ab832163eaa0077989f647e85e7e", - "0xb7857ef62c56d8bce62476cdb2ab965eddff24d932e20fc992bd820598686defe6cc0a7232d2be342696c2990d80721a", - "0xb343d974dfda3f6589043acd25d53aecf7c34b1e980ae135a55cda554ff55e531bc7c2dfe89b0d2c30e523c7b065dad1", - "0x8d139e16a73cd892b75f3f4e445a10d55d1118f8eeafc75b259d098338419e72e950df6ca49cb45677a3c4e16fb19cdc", - "0x817b8535bd759da392b2c5760c51b3952ecf663662a137c997f595c533cd561ed7e655673c11144242160e41d1f2dd71", - "0x817ee0f0819b0ccb794df17982d5b4332abff5fec5e23b69579db2767855642156d9b9acccf6ceab43332ccc8d2744dc", - "0x9835d2b652aec9b0eba0c8e3b6169567e257a6a3f274ec705dbc250ee63f0f8e4b342e47b9e0c280c778208483d47af8", - "0xb78c40177f54f0e6d03083a4f50d8e56b5aafdb90f1b047bb504777d6e27be5a58170330aee12fbaa5f1e9d4f944acfc", - "0xab8eebacf3806fac7ab951f6a9f3695545e2e3b839ca399a4ef360a73e77f089bb53d3d31dbd84ddfde55e5f013626e0", - "0x96c411fc6aecca39d07d2aff44d94b40814d8cfc4ee5a192fd23b54589b2801694d820a0dd217e44863ccff31dda891b", - "0x8249c424a0caf87d4f7ff255950bbc64064d4d1b093324bfe99583e8457c1f50e6996e3517bf281aa9b252c2a7c5a83a", - "0xacf6ed86121821a3dd63f3875b185c5ebe024bdb37878c8a8d558943d36db0616545a60db90789c0925295f45d021225", - "0xa37f155621a789f774dd13e57016b8e91b3a2512b5c75377ec8871b22a66db99655d101f57acaecd93115297caabfc21", - "0x92e60ee245bd4d349f1c656e034b1a7f0c6415a39ac4c54d383112734305488b3b90b0145024255735e0a32f38dba656", - "0xacec614e562ccfc93366309cfdc78c7d7ee0a23e3a7782a4fc4807b8803e6ebfb894a489d03e9a3c817ff2ec14813eba", - "0xb912f9dd26ed552cb14b007b893e6ed2494d12517e5761dbeb88521270144f8c3eb9571a0ad444b30a8a65e80bd95996", - "0x8375408dae79c547a29e9a9e5d4ec8241b36b82e45e4ca3b0c36d2227c02d17bb171528d3778eac3bbdc75d6c4e8a367", - "0x8c2d0e6e4406836da112edbbb63996408bb3cda4a2712fd245e4bb29a0100fdc89a2746d859b84a94565bc1cfa681813", - "0xa7431bf59e111c072d28c97626cd54fcdf018421d053a787d2aef454b91251ee8ff9d3702d06b088f92b9ad2bbebff15", - "0x8f3659b0fbeb90b7f30b7a49233325e806551a32911a654dca86e290b314483bbb33fe6482387bc48c35d85c1dd0441c", - "0x8dca5ba23f0bb76f7dacabf12886053552ba829a72827b472a2f01e19a893155cdce65f1fb670000f43e8c75ba015a31", - "0x8c1514c083c77624eeb5d995d60994a2866192e15c4474d0be4189fae0e9dbd62494ebb4c02fbc176b53be548abbc5a1", - "0x80498d2ed153381baf3b0f81da839ed0eea6af5796c422b8e59be805dba48c4395bb97824ac308170bb4f14f319c5ddf", - "0x84f5ebc3bf96362457993e9fa31493c31c4283075e2403f63d581b6b0db8a3df294b2085643f2007f4de38cb5d627776", - "0x958e6e38774da518193a98397978dbc73d1c3827b4996ec00b4183da2c305a187a0ada9aa306242814b229a395be83c9", - "0xab8b8fbf73845615e7fab3e09e96cc181159eab09f36b4c1239b3c03313c9aeb4bbb51e16316fe338b2319ed2571b810", - "0x977e4e33b33bd53394e591eba4f9a183e13704c61e467d74b28f4ad0b69aa51501a5221cb1e0e42bcb548ca518caa619", - "0xa9bb7ecb9846cc30d04aad56d253c3df7004cebb272f6adf7b40a84adef9f57291e0d08d64c961b9fc406cdb198aab9b", - "0x8d2b72dc36406a545a9da44e1fddfb953d4894710ca026d6421a4ac91e02d0373a599f2acfe41d8258bc9679cf6f43d3", - "0x904192fc8fe250f61ecb8a36abbbccae85f592bbf00c10039c30b5a1c733d752a04e4fd8a1000c6578616f8a16aa83a3", - "0x87f5fdfe20bbbf931b529ec9be77bbfcc398cad9d932d29f62c846e08a91d2f47ae56ad5345122d62a56f629f9a76c4d", - "0x84cc3a53b2e7b7e03015f796b6cb7c32d6ded95c5b49c233ac27fafa792994b43c93cda6e618b66fce381f3db69838ba", - "0xaab58da10d7bbe091788988d43d66a335644f3d0897bbc98df27dcc0c0fcee0ac72e24f1abdd77e25196a1d0d0728e98", - "0xa10ea8677c2b7da563d84aa91a314a54cab27bb417c257826ebdd3b045d2a0f12729fe630bbbf785d04874f99f26bee8", - "0xacc4970ef2a4435937a9b8a5a5a311226ca188d8f26af1adfcd6efb2376a59155b9a9ff1cff591bde4b684887d5da6e5", - "0x8dc7cf6fcca483c44eb55e7fb924bf3f76cf79b411ae4b01c6c968910877ac9c166b71350f4d935f19bdffb056477961", - "0xac2dd1182ded2054c2f4dbf27b71a0b517fb57193733a4e4e56aca8a069cff5078ffd3fd033683d076c1c639a4de63c7", - "0x932ec87c450cd0dc678daf8c63cd1bf46124fa472934e517fbbfb78199f288ff7f354b36e0cc6c8739d3f496cfe0913b", - "0xb0d631ced213e8492be60ea334dbe3b7799b86d85d5e8e70d02beef3ae87b1d76e1df3bdb5f7ba8a41904c96f6a64455", - "0x929d7239ead7575867e26b536b8badf2e11ca37840034d0e5c77039f8cce122eff5a1bf6e0bcadde6b3858e9f483d475", - "0xaaae5d372d02ee25b14de585af6fbc48f2c7cd2a6af4f08352951b45aa469599eff41e820df642ca1a0f881120e89dbe", - "0xb23c411741a6b059f04fa4f5fd9dd10e2a64915f2de6ea31e39c32f2f347a776a953320e5f7613fcb1167efe502f5c5c", - "0xa4581b0ae633fe29c6f09928e5efb16db019eeac57f79fef2fa1d3c9bee42ce0e852bc60b9d0133265373747e52a67a4", - "0x81b33afffd7b2575d4a9a1c5dd6eee675c084f82e06b9b3a52a3c9f76e087f12dca6e0ffddc42fb81ce1adb559d47a38", - "0x89cc890f06b424591556aabdfdbb36d7a23700425e90c9cfed7d3da226b4debe414ac5bdf175273828ce6c5355712514", - "0xa4399438be75cfae2bf825496704da5ed9001bed8538d8ac346c8cf0d4407808e9ee67573eb95fe1c6872ac21f639aaa", - "0xad537f7ce74a1ca9a46fc06f15c1c8a6c32363bd6ac78a3c579ed8f84252e38a914cac16709fe65360e822ef47896de4", - "0x8e53b69f5e3e86b86299452e20ea8068b49565d0d0ab5d50ce00158a18403ae44e1b078a3cfd3f919aa81eb049a30c6e", - "0xa59f2542c67a430fd3526215c60c02353ee18af2ff87cb6231a2564fe59b8efec421f18d8b8cc7f084675ecf57b3fd05", - "0xb8d9bac93ef56cb4026dd1c731d92260a608fd55b8321e39166678e1dab834d0efddb717685da87786caeb1aaf258089", - "0xaa2df56f4c6fe9e0f899116c37302675f796a1608338700f05a13e779eb7cf278e01947864a8c2c74cc9d9a763804446", - "0xb0108ff2e327dcb6982961232bf7a9a0356d4297902f4b38d380ff1b954bfbcae0093df0f133dd9e84d5966c7b1aada7", - "0xb06b813b01fe7f8cf05b79dc95006f0c01d73101583d456278d71cd78638df2b1115897072b20947943fa263ddab0cd6", - "0xaa41e6c4d50da8abf0ea3c3901412fe9c9dff885383e2c0c0c50ed2f770ada888a27ea08bbb5342b5ff402e7b1230f12", - "0xa48635dbb7debac10cb93d422c2910e5358ba0c584b73f9845028af4a763fd20da8f928b54b27782b27ca47e631ebf38", - "0x80a574c208e994799e4fa9ef895163f33153bc6487491d817c4049e376054c641c4717bda8efbeb09152fa421a7268a7", - "0xb592bfd78ae228afc219c186589b9b0b5c571e314976d1ed5c1642db9159d577679a73c049cfc3dcfefcd5a4f174eeea", - "0xaa1f08af3918c61eadf567a5b1a3cdcdfb1b925f23f1f9e3c47889762f4d979d64686ce1ce990055ef8c1030d98daa3b", - "0x857df4cfd56d41c6d0c7fcc1c657e83c888253bae58d33b86e0803a37461be5a57140a77fb4b61108d1d8565091ada1c", - "0x8fae66a72361df509d253012a94160d84d0b2260822c788927d32fd3c89500500908c8f850ef70df68ddaeb077fd0820", - "0xaa1dbefc9aef1e7b896ff7303837053c63cfb5c8a3d8204680d3228ac16c23636748fe59286468c99699ae668e769a0c", - "0xb64b1cb2ba28665ed10bad1dddc42f3f97383c39bad463c6615b527302e2aaf93eb6062946d2150bd41c329697d101be", - "0xb6d35e3b524186e9065cee73ea17c082feff1811b5ab5519dd7991cdff2f397e3a79655969755309bd08c7d5a66f5d78", - "0xa4dae7f584270743bbba8bb633bdb8bc4dcc43580e53d3e9e509ff6c327e384f14104b5bdfe5c662dc6568806950da37", - "0xaae84d3d9ad4e237b07c199813a42ed2af3bf641339c342d9abf7ebec29b5bd06249c4488ce5c9277d87f7b71b3ddd37", - "0xb82a463cf643821618a058bddf9f2acb34ac86a8de42a0fa18c9626e51c20351d27a9575398a31227e21e291b0da183e", - "0x8b6c921e8707aded3ea693f490322971b1a7f64786ef071bc9826c73a06bd8ae6bf21bc980425769627b529d30b253ce", - "0x80724937b27fc50f033c11c50835c632369f0905f413b1713a2b0a2274bec5d7a30438e94193d479ba6679dbe09a65ef", - "0xa1d9b259a2ca9cff8af6678b3af0a290c2f51e9cf26d5fe3c6a4fa3d28cbf33cb709b7f78b4f61cb9419427983c61925", - "0x96a3e69a5ed7a98ce59c4481f2ffb75be9542122ad0eb4952c84d4536760df217854d4ec561ce2f4a79d3793c22fa4f4", - "0x990c4d9a4a22d63a8976d34833cafc35936b165f04aed3504e9b435f0de1be4c83b097bbaa062483cf3dee3833b4f5b6", - "0xb9bf5e4b270aec4a0dc219457b5fed984b548892c4b700482525ba1a7df19284464f841dab94abfabcaa9a7b7a757484", - "0xacaecf49cb4786d17cf867d7a93bd4ffee0781766e11b5c1b29089ae0024c859d11b45828fbff5330b888543264d74a9", - "0xb0e1a0865b1e6f9e4a0e31d0c885526ac06678acc526fda5124742a2c303bd0e8871a0cb7951ec8ed9540fc247c8d844", - "0x82b3d327b3d1a631758451e12870816956cd0cef91fcf313a90dd533d5291193a0ff3cc447054564ce68c9b027a7ffd7", - "0xa2843602abb98f0f83e000f3415039788da1e9a096bfe8fed6b99bab96df948c814560424ffebe755cb72f40436fb590", - "0xab1c7b43cf838798d1e314bc26e04fc021e99a7bfbfc8ffde62fa8d7f92139de86a377289d5177021154229de01ede15", - "0x95e5cf5dd87ae3aed41b03c6c55f9dfad38dc126b17e7e587c156f7745c8da0bd1d60acb718fc1a03b61344f01e3de4d", - "0x86f021a3762bb47167f80d4ef1b1c873a91fe83409f9704f192efeebbc3ece0729cd2f92f63419907ea38ae47bc907d2", - "0xaaa1445dafbbcd645d4332d9806225e9346ee5ac6b22ad45e8922134fe12f3d433f567a6a4c19efdd9d5775a7de1e92f", - "0x8fd7e15688eef75df7b8bca3d61bc9fca4f56e047cdb6d0b864e7d1c4966eac27d6094b0c8482b49739f83ec51050198", - "0x80aab8b4d394eb011d4ec6a4c2815617308c9b847c6fa6a3d7e6af1c79420ef6ff2a13934a398581c40ee4cf1cac02ac", - "0x8970b97ac076a1d8a321ce00eada0edf974a46bf3cc26f6854e4218cdfc8d2b0c32199d9658f254b4fbae5a2c5535f41", - "0xa1aa2ec5b03df0a630e73dd048680ed6d3032c324941423f45cd1f16038789e5e75b876a13948732e9079a422f66a9fc", - "0xb5fe5f5e2f2ae2beeb8e95859a02fc45f01f9fb0ebb2bd8ec9ec976b3e806228821a9775096d341d662bc536c4d89452", - "0xa2bc1f170b62d0d5788b02391337b2ab157c38e725694e80aeead7383e05599be0e2f0fa27ef05db007061809356e147", - "0xa8a69701d4a8d0d972390e9f831fd8e9f424b2c2ef069e56bd763e9e835b3ce5f7cf5de5e5c297c06ace4aa74df1067c", - "0xb43d551af4ff3873557efe3f3fb98e5ede9008492f181f4796dd1a6bcda8b9445c155e8146966baa812afae1abe06b48", - "0xb4b1dae44fd596813f30602ab20e9b1fb20cb1bd650daacc97b7e054e5c0178b8131d439a9e5b142ca483cc012a362b3", - "0xb95b8a94c30a831eaaebea98c65cc5d0228c78afd6603d4aa426d8186aecc951f1a11c33951f51df04c7e6fa43ffb5ae", - "0xb100059624cf9db371bec80013a57a8f296d006c139a8766308f1ea821c7eccc26cad65bc640ab3f6cef9062653bf17d", - "0x8e5a2cb76716e0000d13bce5ef87acac307362a6096f090f5f64e5c5c71a10fddfdee8435e7166ba8c3ad8c3f540f3e4", - "0x93d2c43e21588c1e83c4255c52604b4ac3f40e656352d1827e95dd5222a45aebff9674e34fbbe7ed21eca77bd9b8dcbc", - "0x8aeaed611546bb9073b07512a9a1f38a7f436ab45e11775a0f9754baaf63e9bcc7bb59b47546a5ded5e4ba2f698e3b5f", - "0xaf9e6792e74a1163fe27612f999a2f3cfa9048914c5bef69e3b2a75162bb0ce6ece81af699ad7f0c5278a8df0ba000d2", - "0x850bf2d5d34791c371a36404036ad6fdcd8fb62d1bb17a57e88bda7a78ea322397ce24d1abf4d0c89b9cf0b4cc42feb3", - "0x87f7e2a1625e2b7861b11d593aaac933ed08a7c768aebd00a45d893ed295bbb6ed865037b152bb574d70be006ddc1791", - "0x8dcce8f4ad163b29a2348ea15431c2c6ea1189ece88d2790e9f46b9125bd790b22503ec391bc2dee8f35419863b2c50c", - "0xb4bf5266c37f12421dd684b29517982d5e4b65dfdfba5fc7bd7479fd854aabf250627498f1e1188a51c0a88d848ec951", - "0x8651623c690247f747af8fdffdc3e5f73d0662bc3279fa2423a3c654af9b6433b9e5e0155f1ce53857e67388e7e3401d", - "0xb155120f196d52760129dde2e2b1990039b99484cdc948fa98095cd23da87679850f522e5955eae34ac267d2144160d3", - "0xaec8115e8d7b6601fbceeccf92e35845a06706d46acd188452c9f7d49abef14c6b3a9a9369a8bab2fd4eb9288e2aaca5", - "0x998a8ca4dc0f145f67a8c456f1d6a7323c4836fe036dcbb0f27eb1c596d121eb97369638a9908cfaf218c7706f266245", - "0xb235fbafac62802742ee3d26b1f4e887f7d2da4d711ba7f9bb6ca024de7beec1de66bb830ce96d69538f7dcb93c51b26", - "0x9258d2ddc21ab4e3edcde7eb7f6a382a29f1b626003cc6fdd8858be90f4ad13240072d8a8d44ef8de51ca4f477fa6c45", - "0x99d038487821c948142c678acd8c792960993dd8cb5e02cb229153a1ee9f88249f4ad9007f08e5d82e2a71fb96bb5f32", - "0xa88ee9dbc73d3d8e0f447b76fdb3a27936bde479a58d5799176885583dc93830ac58bca9087075950ea75100cf51af23", - "0x88b9b15816e5a0387153c1f4b90f613beb3ea4596037da01a81fdd2bcbd0baf5598db99f77e7694e5a0d35e822758108", - "0x907ae4b637d06b15846ee27d08c9c9af42df261c5bdd10cf5bc71f8e5ca34b33ac2405307023c50bdb8dc7b98a2cd5fe", - "0x9393d6900e1d2d1a1e42412fefd99578d9ac1d855c90a3e7930a739085496448609d674ca9b34016ad91f22d1cac538e", - "0xa28ac56b216730b7dcdb5ab3fc22d424c21a677db99a9897a89ed253ea83acfd9d83125133f5be6d9cd92298df110af8", - "0xb027590ee8766f1e352f831fda732adbaf77152485223ad5489ef3b0ce2d2e9f98d547c111fe133847ebb738987fb928", - "0xa9cc08fbd5c3fee8f77cf6eb996a5cafa195df5134dab000e4d0312f970a5577942ee89794e618074f49841f1f933a42", - "0xa8b3535c3df0b1a409d3fc740527ee7dd5ac21756115cde6f87f98cc7623f50cfcf16790689cab113ee7c35a5bd4879f", - "0xb61420227b97e5603ae8a716c6759b619f02b8fdc48acbf854352aa6519dad74b97bacc1723ca564cbf3ca48539ed773", - "0x853762498de80eebf955a6c8ddd259af463e4e25f0b6ba7b6a27b19bdbf4c585de55760a16e2d9345cdba6b2a02610f3", - "0xa711c1b13fc6c30745203c5d06390e6c82bd7c50f61734aa8d99c626faba30119bc910be63ec916c91ba53f8483c05a8", - "0xb488c0a793f4481f46b5875d96eecd73e46209a91677769f0890c5e002ecd7d4b1c9f4ba68c47fbed40e3857b1d8717a", - "0xa651c5e812ae65b1c66d92c607e80be330737ea49c1dcfe019c0ecea0f41a320406935bb09206a4abff0d1c24599b9ad", - "0x85e34e7d96e4b97db98a43247b6c244383b11ca10bf4777364acf509a6faa618bc973e2136a4693fbc8ab597e308fd5a", - "0x99837214102b394fffa7f3883759554c6bb7a070f5c809303595a44195e02b9a169460dc6bbffb62bdc0e7ced5f0a5c1", - "0xa952f89c0afb4bdae8c62b89cc3cfb60d0576ba4fe01a5d99534792f38d8848d919b3fc7577435d8443a044d2ee0bcfa", - "0xa1ac1f81acb29798acdfc493854663519e2d1b0e9d23d286ce33882c34b4c1c0bb43dd9638166d8026315a44d9ec92a8", - "0xac9c58aa38219ae659d23007cc7b97fd25b7b610b2d81a8f9f94ddb089efc49c049a8ea4c56e6eaf7b6498f422a97b3c", - "0x87e61d501c242b484fb9a937ef21d485f6678d75257fc8fe831b528979068cadbe7e12b49c34058ec96d70a9d179ab14", - "0xaa45f6852f35cc8b65a4a8b5380641d2602a4fa4e3a035db9664df3ac2e170b1280c4a8b7b55161430063e54de4158a6", - "0xa46975614ddde6d134753c8d82c381966f87203d6e5a5fb99a93b0d43aa461466b37f07b8d0973a1abd6ee2b40f24348", - "0x8d35f97297773422351f4d99564c1359ef1a10cfb60aa0e6c8985a78f39b4268486312c8ebf9dd2ef50a771aa03158eb", - "0x8497c6242102d21e8b3ade9a9896c96308ab39171ab74cbd94e304c47598e2c2a7b0a0822492ac5c076ba91d4176481d", - "0x973f8fcb5f26915b3a3ef6fe58cc44bc7f4e115cd0ad9727d8d1b8113e126ae2e253a19922c5433be4ab2311a839c214", - "0xae3ee9f1d765a9baf54b4617a289c3b24930aa8d57658a6b0b113bbf9b000c4a78499296c6f428bbb64755dfd4f795d2", - "0xa5be7a8e522ef3dcf9d2951220faf22bb865d050f4af2880b8483222ff7aad7c0866219fcc573df9d829c6efbb517f98", - "0xa5f3c7fabd7853a57695c5ad6d5b99167d08b5414e35ed1068ae386e0cb1ee2afbbe4d2b9024379b6fc3b10c39024d36", - "0x978d5592d4798c9e6baceff095413589461267d6a5b56cd558ec85011342da16f4365d879b905168256f61d36d891b1f", - "0xb7b6eaffa095ecbd76d6e1e88ceebabaf674d9ef7e331e875c6d9b9faa1762c800ff1ea597c214c28080f67a50a96c1e", - "0x8a1ab53ae5ceaa42e06e58dd8faf6c215fc09ba111ca9eeb800612334d30d5971448be90fec62ed194328aadd8c8eecc", - "0xa9ca532cac8ace9a9e845382f8a7840bf40cb426f2fcad8a2f40aadbb400b3a74021627cc9351b0966b841b30284962e", - "0x8dddeda8854c8e7ddc52676dd1d0fed1da610ed5415ddd7d25b835bd8420a6f83d7b67ec682270c9648b2e2186343591", - "0x888906aac64fd41d5c518a832d4e044fdc430cfe142fd431caf4676cafc58853ce576f098910d729011be0a9d50d67b5", - "0x96a3f886a2824e750b1e2ea5c587132f52a0c5e3ff192260d8783c666206bd8ebd539933816d7cdd97e4bc374e0b1edf", - "0xa150a29ffb2632cc7ec560983d9804cd6da3596c0c25956d27eb04776508eae809659fc883834269437871735de5f9ed", - "0x81f7ad4d2959d9d4009d1dfbc6fee38f930f163eb5eac11e98dc38bd2f7f224e3f5c767583f8e52d58d34f3417a6cf90", - "0x97ccac905ea7d9c6349132dd0397b6a2de9e57fd2d70f55e50860e019de15c20171a50b28a5c00ef90d43b838253b3d1", - "0x95694f00c21e8a205d6cbda09956b5b6ec9242ec8c799a91f515b07dcc7de3b6f573e2c0ba149f5a83700cda2d1df0f5", - "0x82bbc3c4a3b3997584903db30fffd182a266c7d1df3e913f908d5a53122fa12cf5acd11d915d85d5bd110fcc43cee736", - "0x8d3f24b4949aa1b4162c28dfbb9f813dd1d8b330f71325448dc45ea34d59b69ca95059402aae011e1b5aba6e536bc6ec", - "0x92c734c19752d24782331e74c9af97a8399ddfdd32954e91cda7363dba876aca4f730b451c50a8913950420682da8121", - "0x8653d2c79f77b8c7dcdf7e8dee42433998aeedf1b583abfca686d47a854de1b75e9a4351580c96d1a2a9532659203361", - "0x886f0e414cb558c1a534a1916d3531320a9b6024639712ffe18164ce6313993a553e2b9aafe9c0716318f81a5d0bb1da", - "0xb31b5efaba5a5020c3bcea0f54860e0688c2c3f27b9b0e44b45d745158f484e474d5d3b1a0044dd6753c7fb4bf8ace34", - "0xb2d615bbdfdc042d6f67a6170127392d99f0e77ae17b0e1be6786ff2f281795f1bf11f83f2e0f8723b5cdd1db1856e09", - "0xa6e014cca531e6ac2922239b5bee39d69d9ba6d0fa96a4b812217dd342657d35606f0b9c5a317efd423cdb1047815e3d", - "0xa8921736b69c9fbb29f443715174bac753e908251804620c542fad6cfbfda7bdfe287f2902f30b043a8a4b4818cfdeef", - "0x8d73a9949a042ec2dcefa476e454cd9877eee543b1a6b3b96a78ffcff87421e8b26dd54d5b3192ac32073cb36497acc3", - "0xb936a71ee8df0e48867f3790adf55dc8efc6585024128de2495f8873bd00fd9fa0984472125e801ed9c3cdce6698f160", - "0x82f69c06209c28f64874e850601dda56af44ffc864f42efa8f9c6a0758207bf0a00f583840982dec0a517ab899a98e5b", - "0xb7a0a14411101473406f30e82f14b13e6efc9699e7193c0be04bb43d1b49e8c54812ce0f9b39131a20379c4c39d3bbe3", - "0x81159c969f38107af3b858d7582b22925a7ccced02fae3698482d7e9cdc6c568e959651991c6cf16c53a997442054b61", - "0x8bf1116a206e0ce9199fcab6ed2b44a9e46e8143bff3ed3f1431f8d55508fe2728b8902670cfd8d9b316f575f288ed9d", - "0xa279b2149824b64144eb92f5a36b22036d34a52bd5a66e5da4b61fbc95af6eda8e485c7914f448abd8674fc14d268d9d", - "0x8b98279b5f3588d1a2f8589d2756458690a502728800f8d94b28e00df842a101c96ab9c5aee87c5bbe65552c0c383b80", - "0xb4a27a351ec54420f94e0a0a79d7c7a7337940399646631baca93eeab5fd429d7fb39428be77dcbce64a13eaa3c8ca1d", - "0x90c08baa29ec8338ffce381eae3d23ce3f6ba54e5242dec21dc3caaed69cac13f2ab5e8d9d719bc95720fa182eee399c", - "0x85156d65bb4fef69ffd539ab918b3286105ca6f1c36a74351ab3310b339727483433e8f8784791f47b4ba35ca933c379", - "0x923005013c27209d07c06a6b92b0cbb248a69c5e15c600bbcc643e8dcd2402adebd94dd4cafb44ec422a127e9780aaec", - "0x863b23eb5463a6ef5a12039edc2f8e18e3c97b244841bc50af02459b1bcc558367edf2f6e4fe69f45f37887469dd536d", - "0x87a4a7708a112724ff9b69ebb25d623b5cae362ae0946daed2ec80e917800dbfcd69f999c253542533242e7b9a5cc959", - "0x8bf4347ceea7f94b53564f26b1a4749a16f13bf71a9e03a546f906f7c423089820ff217066159b0637d9d6824e9c101c", - "0xab07eef925d264145971628a39e4dd93ff849767f68ed06065802cf22756fc6bf384cf6d9ab174bfc1a87bcc37b037aa", - "0x8e3f10a42fad43887d522dc76b1480063267991c2457c39f1e790e0c16c03e38a4c8e79a0b7622892464957bf517ebd8", - "0xa8722fc7b1acf0be18f6ddf3ee97a5a9b02a98da5bc1126a8b7bf10d18ee415be9a85668eb604ef5a1f48659bc447eb5", - "0x878d6b2a9c0aca8e2bc2a5eb7dd8d842aa839bbd7754860c396a641d5794eab88a55f8448de7dbddf9e201cbc54fe481", - "0xada881c167d39d368c1e9b283cf50491c6bfc66072815608ba23ab468cfbd31ca1bd7f140e158e0d9e4d7ebfa670bc2d", - "0xa2b48578fa899d77a7ee1b9cb1e228b40c20b303b3d403fd6612649c81e7db5a7313ba9702adc89627b5fd7439f8b754", - "0x8e051280e10551558dcb5522120ac9216281c29071c0371aaa9bde52961fe26b21d78de3f98cb8cd63e65cff86d1b25c", - "0xa7c5022047930c958e499e8051056c5244ae03beb60d4ba9fe666ab77a913a067324dfb6debcb4da4694645145716c9d", - "0x95cff6ec03e38c5ab0f6f8dccde252d91856093d8429b7494efc7772996e7985d2d6965307c7fdfa484559c129cca9f9", - "0x993eb550d5e8661791f63e2fa259ab1f78a0e3edad467eb419b076a70923fede2e00ddc48a961d20001aaae89fad11e8", - "0xabb2826e4d4b381d64787a09934b9c4fe1d5f5742f90858228e484f3c546e16ee8a2a0b0a952d834a93154a8b18f3d16", - "0xa922ca9f2061996e65ef38a7c5c7755e59d8d5ce27d577abcdd8165b23b4877398d735f9cb470a771335fc7d99ecb7fc", - "0x90f22862216f6bc1bbf5437740a47605d1ff5147b1f06f7b13fec446e4c5a4a4a84792cb244a1905f3478a36f8d7065b", - "0x87f3d9a86afef5b79ea1ca690ee1ee4bb9754b66f7c50a42ad6b99af7c222c853ca161f440a0a2a60b3b5a54e3493240", - "0x80a9ca9a2d33b9cf61976b3860d79f5d00de89a06ef043d2a52931809018aeb4ce70423cbef375b29c2c750c2c8704c2", - "0xb4e798ef1d615896108dae37ac50c1e859216ab6dbac11653e44d06ce5209057b4b0dd6d31dcfcda87664a23c8ef1cbd", - "0xaaed6d1e7c5b1db06f80dae6c24857daadfb0268f20e48a98fba4b76de1ebf65fb84c3be95fd6a418b498f8285ec63bd", - "0xaeceaa316c6369492c939f94809bc80e0857abac86c0d85be8066bbf61afbaaec67e28c572437a8d35c49dd596b3134f", - "0xb791c3d53ed34a7d1c8aa89b7953e3684c3cd529230824dc529739a5fbe74b58b87f01e56e7a169f61c508237ef67160", - "0x9351f8c80634386c45c0050d2f813193f9d839173be941e2092d729be5403632a2f18dffdc323d69eb0dc31fa31c5866", - "0x97693184d5c0056ae244dfb6709cafa23a795dc22d497a307a7f9cf442d7452024023c54a8d6bda5d90a355ba2c84f3a", - "0x85362daa003d23511ca174a8caafe83d52b6436dc4e43c4c049e5388d9211b5cbef3885896914d86d39be0dd1f910511", - "0xa2511b5fa34b24eeb0e1bcbcf872a569d1ff5570fe7b0fb48f5542f7fe57bad808d34b50afa87580866a6cb0eba02f27", - "0xb382e3327eb1401f2d378dbb56ac7250adde0961bd718575a64d264ffd44772c20752d4035c3ba60eb435e160b375e20", - "0xafad8a5d40b536c0720556845a6b257ed42165c14fb4b4a874717d107752f49ed9380c5b048df3aca67287bb8fc411a8", - "0x8fad0c98434ca5373c2d767868f679b76b4a8d04bca8240ea3f388558262c2d61b73b16fc1160932652b5688c25fffcf", - "0x83898008b5cbb6f08f8ef3ec179427869682bb4e8d38f6e6a687a214d4a307436afc64ee67d70a5a8ba9730bf839aecc", - "0xb85232e79913785fd82b06890706972b4ad7a309489930ae23390d51aa5189731f8a2df24800409a8c36b3dd6fc91275", - "0xa24ff26ec792f3701da4c5638c1fca4fa4dae95b01827d6200d583c4caf17ea3171393ba2a8c23d1ee8b88402916f176", - "0xadc5c7a7ff6b41d6cc386b7fc69d7bb04179bdf267864f9aa577f0f6a88438191fa81ebaf13055c2f2d7290be6421ace", - "0xa05e835abd502d31454d40a019010ff90b6b0b1f993075a35c9907aeab7a342ac0ba6144dc9379aada6119157970e9b2", - "0x85ff07ba58463e7f153fc83f11302e9061e648a5cbd272bb0545030b20e11facd8b3ff90c9ac8c280a704fbda5c9d1b0", - "0xa6c735ada8f4587da8cdad7ea3ada01650b5a3ecab8d81daa7a5f5de51ef4a6592b524692584306f06be3f6701f2870c", - "0xb138deee4e53ae8d677fae104f713ef1b8babfecec16b6a85785a66a72784eb09d44c3b63567222ade714e98f7d1604e", - "0xae79c1a49dafcdd972acd95d8ad0a35c02adc7fd736d4c44c3cd13df5789d339b5ea16bddbbd43e486a061ab31baa5c0", - "0xab3cf2371a1d7dcd0ffe3869a0178230964b06694bf258b2073ea66a2afccd845b38485da83d02e1d607d4c5c36b78a8", - "0xab9609f28a325fd01cb39540e3a714506c44e52ef28ee640f361deb5760aadbb23e804663b0fa20a66e239c33f8d8bb8", - "0x8ed95ea8e76e1b42823d7915a6aae77d93746f846bf602841dfce0e47543a36efb9ee7e5b42c73c3209d911225cc471b", - "0xa80b6162036d43811482323f0ce59eb18740e33a63d7c7bbbf3be206985919e5342d53a69df537d43e8b7d7f51e8892f", - "0x93c03d0a5083408ba00c125a8a9385213d4c860072f0297857b1235045819b904e07f2425c13a661d0a01d2e53347f4b", - "0xa6581200f00f96c461621e1d26b14a23687dd97eb9f7df4ba641a84340ee7306dc1796248fba4804f185947ad13b4385", - "0x8be174018fa40f7e0cedc5ae68f38969eb7695f2205e9c573641e533d56f68c20abf38a23d2f0dcac371e60b21b18615", - "0x857ad4ee3218c647c58f09b8ab22bcc8976f00a768ab1f708618e868e6143474be846422ce2710a0ed39b5155b6f13a1", - "0xa490bec40f322d599f26bcefcdddd8f2ef6576aa737d5ce7e8d5d422741abe749e3e6a48489aed8c560633f72857e3c2", - "0xa9c0ee339621f1c4a2410f9b4d2f03f1b558dae2973807b8bccd920e8feb7f65dfde3e79986b72ad21fcc4567240381d", - "0x8592251568e750a430f7d2c6ddbb3ec82a4dd9fd83efe389e69aa177fd97ac2c96c59a6e86db20d8e6f125d65b46c4d3", - "0xa4e2f4aa6a682913b423b097c4069c4e46a1f3af9556b1bfd0580d0fc01e3991488458049e0735b2a629684a79271c8f", - "0x8c4f6a3e738cf74112b08b1680be08158013ef8a515a81215d8a36c9b756786d1b4cb4563923463f3329292f4b48bf6d", - "0x8bace547353c02ea00dd547eeda7259aa354d4772dd5e0c486c723cf88627b7112e196b879c3c92a9561b674d9fc486d", - "0x8d372f4901e25e8db64fa098148d4a4e709b0e9dcb756d0f90dad99dea393054193ae1a33d292a3dd772ff7ba05e4b71", - "0xa8c7ea6a6a031ed23d65639f01f5423190775558f479700597df7ae7e338a6ae5e9b32f470aff20787ac8b7eec84df6c", - "0xb6e9dcba240fdbbf66033410a79a2dd3e9e1ffdf2eae949b3a9ed720e939d92339991dc3e70a5ac7d5253f317daf0b7d", - "0x974dec4cd61af75721071752c664d9c2a5121f06ff1515c56139a177a3ca825f763b69d431d4607e393fa74dcc91cc58", - "0x958863e6ad583a9d370a6db3639066982e44766904e7afa849b132f6666b7d08ab931131b3bec7a506d6583e93d56767", - "0x8b93a33b5da9b3300c20a96d80b894e3789c77041183c2cb21751579c8c96857f60cfc2f075201b64e95a78985c5b321", - "0xb726cb9f7ef34ddbc2fad82b3b0af0b30cc913e26c5a614ae5c19cc9c55c8e6dae069db5315a8dcb6d987415bb550ca8", - "0xa730f515398a71bddd66cab2ff996659d4e47dfbb08ce7958a41021f76d269b91c7498b708cd14b183a8ef469c772803", - "0xa4eb3b18132eb0f5337f14e01d63ca0bec0db6a43870f800e5491db756c2f5fce519d8dba5528b4bcef550d06b33699c", - "0xb1ab6621eec1ee6784e632e214693f39a14f3715991996b883d66200963e065c86fa0667f7bc36b93b40b5d90ff708c2", - "0x80486a26c3532ad6e19f76d8c9344e2626c07363fd495264927cb5935fa9565ece670dc98767afb04af6a9a5c9231075", - "0x8ee20e0df3c84a1c6b0e21bcc325cf99235b747ffe47f17fdfba548a358ca75cbcc331dd50db2311b400ae882256a608", - "0xaef4268959e5541e7ec69c921a1e81a8374d7e44bf1bb2debf4101cf3cd6b7d6ca7f441758b388de96b3e0edb5b97be9", - "0x8793629bd29d689ec94b016de8886cac6e2ca6638911babb22db4a787661422da0639a4e4089ebeb689d173abfe75950", - "0xb487b3551c20a29e9a5abbda8c50ff594826283e443c09e3ae09b914e46060b3f9abf70434444ce1487e2a74e562616b", - "0x8f11531cfc5997dd04b997cb87ba1831aa7041d5434fe72de66304e3f165d882fac891391fbb1eb955c65319e65293b6", - "0xb195136875fd02a75676c33cb3e60504d5964f7a9e81f4c8c8fd38af62e2145c55f765b3158664566191188ac678f381", - "0xb374174b0b3eb04fa49eb4ece45173f0db5d829eac370a20a62309566e0f98b18f72f3633626893c053b7be6bfbd2366", - "0xb2a2f6b0cf652775679b2d677048f2ed8c31a3269e6cddcc7a10e3e6fee89e486b50d9d55fbe452b79c4157c0270fb77", - "0x892177c364dc59032594e7a6fd032286ffdf4fa0b9e3baeb37ec839faebfd2fd46c57b2c9bfe9977b59c93a9cc0ead1d", - "0x8ab7c0038a7dbb2ef200dbbe9acbc875829ecad4883792d5c6ce283de67ccd9aa935a9cc7b30b2bd9de7fca7bf2a9a05", - "0x83745cfc78ca709835aa6c6a233c2b86fb31e3f9f6a8becf63e501f2841c4366fb7d131b746c9d3291afda714ff05579", - "0xa723dcb67925ef007e8339dc578d2622d9bb77cfda87cca0088854a59414c02338752c56116a6c1281917842e8467c38", - "0x8a098142da0af2254c425fdbbd0d1b1a17b2bd781391ab37f181775524b8563c64ab8a1602aee2ac6c0a82ba11a8b1d1", - "0xb13bd7529a9b351c5d395c794c28bcb0a3167f1c992e8c062eef47be9be27895945231d249c73a0b6949daa295e14944", - "0xa20dcd2fc2222eaae467d9f5db861040f58bcb991a26e5663ac3aa5e1ff13d0010657c5af586cc4621757add2b905073", - "0xb818f660c3cc4e9f273c25ceeabe562c8afa8ff88529c26f2cf45ae6b2813cca5f350e3cbd56f6257c4df41722dabd25", - "0xb225d5987108b24411bc389276f12509a45e86d5ad6b6d929af5274df0be11109c0fed329669a0acafdf3b0beaa8f2ec", - "0x91fcb6d04576d3c6bae947bb7843b430e5fb0592ae49b0a65dfa5791f4eaa4bf2c7f436c8de7360f217001c2b4e5c67a", - "0x8821f7a1424ca3fdc5d4a5606ad10dfaba6094cf36669fa9f84cf7617e50425405d14980780e1e18a1ecea7913cda896", - "0x990dcb7f38f56521a70cb71bf4522649fcd46ac052c7feabb0748dfcac9f9c0f95d29e070d32af3cd0adbf869535e17b", - "0xb0fac1029fe2c1100f24e2f4bf10c7672199fce53513c7dde2e8d9b00702edf0143e0e1dc7ceae7dcc6994edc2422b6f", - "0xa514ebb1a33451b4915c05114db0b10168393613744df848b24e43e09f0bda23baefd9d731075198aace586615ac7911", - "0x8b77f7953c2e67049fdca3653b8d8cf3f799677f79b954da02bdad8cc4d6c855c1c7c16b4f6f9ba35f46426ec28b2d84", - "0x875520cfbda16ec5b1d1d00f578a910d0fc052f17870ba093e22e310bb07648d34817cc2b8811b6f52de535f7046a0d0", - "0xb8c77b4be0b430851c4ff69e91cb770db1935d848198601393810ef395efab52deb9d5c6525472bab720273d5e0e7a79", - "0xb6d4d437146671bdea62fb6545395ea3df39f1cdef21b8476b68e7a25aa7354f847740576d6c9f187bbae9941f0ae450", - "0x95c642f1bccdb62cd6a2212dcdd6ff8d49aee426ca08b7cf3a9d15249d24a9eed5533f92a70c84498c0797f8a57efa27", - "0xb617978047ed0f748c305aa7f30c2dacd0db00baa67fe0c5ce346ef0e6991dc7e05f18dcb2702467421f8390f27aa815", - "0x86411c7a00b3e8b43bf22fb061b1f54ad9bbf632cd74395a478218389c0f544668acf3dd7726532d080ca7da9a5f8608", - "0x97bf684a8849626c4710a6992f6c11f6b5406fd4dfe9e6aa502425aaafe9827e2c435aaf9a5d3d2ba3a4c0e8aec79ba4", - "0x8b178e2a125b461d3180906ffba0af3dce614c64058501fdd35243ababf892d6fcdea4834ce42c25d5569452b782a709", - "0x8ebed2c8a25c61da6a6a8cb0d8f5ea179e28869753eacc728f2c076f7aed8598cd3aa0981f120f9e7ea55b3a689ae882", - "0xa6f235b8e655ca3d634740b53d8c0a757ecc75d2b8838b7948997c1985473d01943d935f687b86cee56cd47c8e773443", - "0xa7959c465a9646908b9d8032a589e41a7dd999f2ffc54bb42f22e5f8a4d8c493a31bcc7ea2cac6c8dbcc59acace7181b", - "0x96d0532df2e12da20a57cadb6cf5f6c4ee1aa4775629358c25f1d51677a3e96d1fe3b232532324b4f02f941952d4cc68", - "0x90f493473d686b639a30d1ddc9c72eae6e983f1236e162e58e967a477c0654973ea2e1bdf4ba1a44d7247bc1befc2cab", - "0x8b2d87876d9c4085102a07ebb41c565ba69acab99ffc03efc18f20e48d3f3bbe4fc6ddab9c78fe479d9ada80504d85ba", - "0x829a0fb3200a28e09cacd6c5346000e7786116ddfd898f37dfd17bef454a8abc0fe939ed8735c00769f7f2f33cd4f906", - "0x86194ec9e88ddb7150e8b03e7a535b6e99863fc6762835601efd03615aa97aaeb413cb210e86035086ed852b39c9d019", - "0xb02efd116a7189cb317ceae392bc301ae55470f0489fa89934e182aeb8c67e280299b975786fe9a470bff46827defb9b", - "0x87d7c3903bd22b12d815506f150373f518d47dfc6e5fd74347d88b518124c9923d1e4c98defeb3a45d53d50b423e2175", - "0xa1a430406b28254a7d6348bc98e697e9bab43839aa05d53faee97546f84541ea0b559162619b2045182938f69bf61cae", - "0x99d243c226c61c6697fb3d2594f3533fa5dfd7cfc87107908cacde337d7a077fa5a9dc702d26081b065edb1227498e65", - "0x800ee5006ab6217161f42db0cfc552a81728bb4fbd7af6e4620ea099a65ef6664184af3f65a07fcec7e965529c5b49bf", - "0x91bfd307579cadc8f81009558605be3edbcb8dbba271475803484017f40130b2b216aef4f620d960193be681877d3a53", - "0x96a060459dec458d19a6f8af6e49dc6c7c58c55dd18915c5fce5e0f4b4a422fce3b9632f6059388fe760289abf70f173", - "0x9921a37f3e657222c7fda3588418a9071409711d9f1fccede7494429f02a45fbc52d79fbb64e9ccd518f60d06d0520d3", - "0x81052b0d15773cb75975ca9230ebb2579700e489c7e3f07cd9cde206fef38b8139bd4976d2b4a7840495fc645f96df03", - "0x88ac37ba66d1de5e23878c992e4d54023729e97e77351f50dc5918d738b5a73faf1dc6feec7e85784761836ba1c6f778", - "0xae1e6072c13060775f6086d1ae1f88b627ffcb810fc0e0e97deea1f3a15ef0aaa52a6dce2563e4beedadc131af2a8281", - "0x8b60a340f5e4f90badf83001b495ac9f13974c3d2054ddcb3e6b8ca99dec5cd63a263e05c282454191ab2e087d5a2911", - "0x832e2d56ba69dbf817b2b9dbd25c1538d5b8dbf5d9bc05e6be85054a423ebb66a71b157e166e0b9444ac171b34b7ccc9", - "0x8586036fc7dde1e7e3ecb61663130c4529866ae9f5f5095b9fccd24a4c70eea899aae5f10ea1ba66d1665b2d83be35b0", - "0xa77969453b5c083a207913272b5b69d4ccbd8718bdf54be8fbe11b4bd0a2168aae3ba8f9362afa69c0ffa28d7e5a2340", - "0xb7fe9568c214baad0ac5f83745611b481f744ec1c4fa78a549b180dcf79633e5ba75dc20055012a13d849eb7a9be57d3", - "0xb01cad1d2a6c51c0ce88243d1f52f95fb5ee315a905079688027511f0c4ecd0563a3a81846709d272fa5ccb9665e8043", - "0x8eae0a21adfc569aa57237654021c2bdb2c6f0f52ccc90a126682c21a1f9413c63d285f92b2b2f8649150a9284bf70b7", - "0x942acc947192b5f3cf60e92383e5d35f79e7a5904e8e9fd1c8a351676c83ad29b0afb6578d555457cf909f8f4d27adfd", - "0xa74e092f8628fba9abcabc27e2e9f3d5a9a941dfe50a2dfde2ad179aabc73afd196676925c2d98643ab8b3d02bdb66ad", - "0x896159daa2afd757cf3f9d34af248ad68bb3c62e4c9ac49919422727479cf669098f270b9e645607a7d11adad4c889b2", - "0xa428d8370813d78e7a2a24eebd36e9da2f8bb3605e5a39b5fcda939b531c35a8ebaaa642ba556250a37bddeec90326fb", - "0xa5fa04eb60a1d5ee9820e78f42f7be15e1c02757b539aead995768c6209684d6c183c71d282e0c12a4c15c03f9a89d4d", - "0x93c77d5d220e40affa7269a6915c076c9aef4db552c643ae5d560a79c955b491c6346ca4cf11cbb7fe1894e28d47b065", - "0x802e605d2de745eef6981d88e7a57ef4046a2062725e8080995374cea2b3273c27f35b7774d0dcba014710d8d6c501f2", - "0x82f7169e6ec9b3e2bd450f35ea2e66d06bcf900acf5b73139677b48e078ce2e16599103027b2326770c99c0a690f2015", - "0xb0c8581879439f9b997551233fe2de71aa03604f9cec37a7b18c5854342d9b67be468f3cac4bf6f64fe8a0066248c498", - "0xa3f626848a4db6e9fb01cac90d3362ec521e969ebd5228af694ea3671061476149f13d652942ac1e39f65591fed740f9", - "0x88a8e759b9cbe16a7c16e43f4afa2de6100d2eafa4dee75ccd653ec38c919013d0a6b35c1ee1eaee7c1985b58bcc9e92", - "0xa3d5fc7aaea072798490616552d947e95f49cf02a420314307aafb555287ec607d75589ba24b009cd68299dc6f7942fa", - "0xa809cceeb84f9bcf3c3ddafde3041e7bc3b1d14df8830ab849002176a0725e6f16f70774d8962cb0b8ac0dc43c4ac66f", - "0xb8f2e46c031cc8fa160a08c2ebdfa85345ed14771b06daa9636b0e7792b7fddbc501dfc85cc626a01104a43a7d3230c3", - "0xb5367e2a521c318b802ce16ceac80c4b8139f73ddb10ddf38433397cda70a86ea1f051cc55626a4e99d27f30f3975ff5", - "0x96d963660121c1441cd13141279cd371a6a0aa18b6a20761b18df60aa9c14e13489afd83695a0921d5232efe72045f07", - "0x80818d492fd85d666bd91aaf6257b86527fdd796773c793407df1d4a0f91d74649a6bab4d15155c36ed4c6e0a32c5636", - "0x931e22918905fd6c230d3d867ea42861f3074d320d14e1929031924c8ac209a5c552b679b24563bb12f9749b4ee983bd", - "0xa4de2c333e74ed9bfa3c0bf6a0beb90427abd9aa4221294cda74331646b58ef46ed57cccc8798ba2b9309894b17cfd69", - "0x883881554c1d88c0ed8d3b6dec3d200f6fea69a77ace3e4d6f86b41506a23724b4394ec8384075f9c75c3868ba8a8e8e", - "0xaa0539ecf6ec9bf06f24443027f8f24b6b3d8c5b2084248eecd4bcad3c9a69716e1a0d01057f09a65bff1006ac5e157a", - "0x856d74d44c943c9e809b42dc493dff20eca03cb0cf5ed45108c69b1f90d8592a53ae8100e99380a274fafad23e74cdfc", - "0x9188257446661c88da093b7c5ce998135913f63842d7c1586065377b169ee35b062d925367fb9b909ca971f1188667b1", - "0x8d3aa57cdafbe998938787479f5d590c1484c6dbe94e6c487e57a746ef5252be0eaa5976d6270de7db64b6b92e57a0f7", - "0xb8f4d6997240f9eda5aca0c43323a828d1563c491b3db2087f60ac4120a3fcd06075fb42bb19d0339ab5ee3fb7db25d2", - "0xad247ea94b8ae1e81eae4c9fd7b39e6601b53cff47b2547ff90a3cca87192eae28408082774a1fd14bf9ab459b7a4f1f", - "0x9598598070f8bdbcc49056c40971e673726cd8c1bc4baa0b5124dfb5fb750e7baa7a7df18eae2bd91955ddcb1ec67955", - "0xb874131ab1608667fa60ea29092d090859eed1812e90c609afff96d79e82c5ba546f617f4c96fc32c9bba97431c1e9af", - "0xb00750a9cdc75c2a54f0d3cc99b0fe02300754f25166f7ac85ff41ab5e9cfcca33a29be76a480f12a2d410c7cd5032e5", - "0x84b5bd1c90bb6c66755b28ba4af493ca1b0c3a4df9f436aac67d2e07289053f925cf6a149a84e74e1027dc8758150179", - "0x99caf64bd9d193ff306e8ab5da3f1bb2a190a60c3a82099b8d03d17fa810dc53d176c21379f479e828f60d25beb3ffd0", - "0xa8fd9de502f1c261d5733430e5a18d8b7892a98c9529a016fc2ee53892ae965dcd9c75850bcda4c7edb980b8d88e60ea", - "0x848c02cac636e047028a3fe8c1bf4066fb7591b96b0340f8fbd476ff01b35fa3e37d309333771a134f24800e5f3f9289", - "0xa1eab1a06dcca3439f0166441e7e7f2f5b56f5f8aa9f45e411c561f556e0fb71c514c06c26ac53b49a576caca5faac3d", - "0xaa603f970dcbe953e700e61c151182c8d32cbbb53ceef572ac93383db33a4b098b5c7b267e42d514ca66b740c0925efe", - "0xb55fd5301bd700ddb0b4f72fabe9a91ad49759506101fa802ed1677e9553595aa4d2c66f7574e78d21ce882ce0120ae7", - "0x829137bc4da7b4886d3d04d2c39cbf4b1dc40c813ac1adb425c7b9abf9142b516314cab79c68454df5d71994ce416144", - "0xb83a3a22735001f783dd48a01c4fb3598a51ff3987e842b8045c71c035b9e43645a55254ca5911a5676ef4a8af12d056", - "0x8ca8d463deb13f9eef5e533bc39efaeb0c15631282c5c0deee1673b0053a7cccd514af09801dd6c158caa159fe9351ac", - "0xa9ffb1427828f3c456b9c8cc50782de1ab0029b9233a0fd998bad0fd014d27e15c4a32d1e16ad41bff748378b5abdf49", - "0x9627e29f725ddd86456aff813976bbc4a836f4deabf5ad9f73d1a260ceb30948824df9c8841e6b3c529652202be181b3", - "0xb52c988647fe3d9276eed3c262e1044f57fbb116c64cf4f207235c205b3fda0f3d789bf90f5217401b468d85fdfda404", - "0x833bbd6e2924f5c4446cb76b881d1434a5badce9eb9b003f85d076e297ad7ef45b822069fe54d17427a348c3263fb838", - "0xa067a36352db6f82a116cb87d3db5f60b18576852409e2076cbbfc7843af78866313a4969385a40271051dd195d51116", - "0x902b99545971f9a103f99d7399acc347ac46fe156166e51deefc0e92aebf5893460c69aeeae11f5af9f49418e289ce6c", - "0x9206a0e9ce9b9880f29ef0417c96931985f5d83bb17cebdbba4ff2af81a3d37155b04649426f698aed372e4f669599e6", - "0xb54a5d7c976e45c0b1d44433595eae9d1ae9aeabfd58cd5ecb0c5804756a7b01c9a517754423b4714a3695533a3114c8", - "0x91b612131e84580ece228b81ace83da0269b53f94d3c02a1a0879ebbd81bdc252064b3d03a7e140b43a90f237d9a45a0", - "0xa6cead3b8607eaeafe37135bd6de8fbd16f806c131eb71c8d36bfbe295d45b070255e50dabf076e2c3f6b8699be71d6a", - "0x931da21e67b11ba6ce438546a24d063bcd51aebe39b4220a78d9c0aab88b2d37969b5ef3502d835507f9c8d6d006714c", - "0x8fda408caa9daf01122a2308b7b9d328f52e1e2f138a8bec30492488f4d710e5e52524a6455a3a2ae2818ec8a610b650", - "0xad8ad5c189644352d90c462731c46145410e5adf38682bb80f95495dd64d9d13782537d68690847bbb06c6be7175dbc7", - "0x87bb5cc466ade60feb0961421c3fabdc8a7e20f11df8437bfff63d3f8bd25305002a396c9d0fa4fb9a9986d4717f12c4", - "0x827cff72870ba00c29064a7d2b4973f322d6b6de7924c93d8bf8825e7a0e8478c7748f90f5c716bf83c55b2795d315d8", - "0xa225895a8e94229776ceb51b05356291f2dce748be17a60d5aeb33ef8507c368bafe5d1d6eea927f28b9d1422b661b9a", - "0x8e011323ce670ff51c964241a6b72e0e0ffbb3ff9bb2762492323fc3a4abf4718091be0945287c7329850e4f74462cde", - "0xa2c03c2e5f4e9d3ef361f68b188451994ad1b24de9f323370559c8abfcdc7bffd289d92e78a5f6b104b0a12c84dab2ef", - "0xa22b4771116ce22276fab1fec6826610707ce8a342f9f60b079c4e0259dac3cc41c96c560dfd0ada6edd2828f7c0e8d6", - "0x97c17441d0af9be83b42097aa8b7cec84a253b9a2b957214b8fa93c26d2add46144faffa7b8a55312059b10690f711f1", - "0x94bdf348849f31a2737cbae5e5848aee711067bac85c11c2e68b44c398cfafbf3493a3226cd1ddf7a916e7613fc7b6f6", - "0x838f59c6e8469a8ec6fd40b978a3607439aaebe1e50ff707eec72c0b8278af05b477bf12a384b56d03e3d4eb91e56f67", - "0xa1940f0db58185e2b3aedd2b0bc2b73b4a65c68e09b046f38e9dcd4e13c94f5406bea92635190bf315e48ec64eceef2f", - "0xb2f4e0ae44e1f1210a91d8f280f17091fa994034ba8c991583f8182a323e9b3001a712e3584fc2d64ecbf2d319d076b2", - "0x9342b89c721338d02c7854cd7466fb24d93d7313b6114ea591e6607439c8ddb911d1cf35f01898e9c557982bdff8f9b6", - "0x8583fcab15be1dd14d5a415f4b14d706c8c62f058500f1344b37730c8be6741779691f87ded3cbcf6516468b373cafb0", - "0x8fa9587c7989646571ad9032f34cedd353caee14f5be5cde1e9e0a1710f90c08faf6fa96a60e1f150f761c9c8ae7417d", - "0x8d9ff904cc08141f5a9879f5f77dc600e6edbe859082231a4d819953890199bcc5f940b730ea688332f07e5279d49e1c", - "0xb5f82b46e5ef9a2df8d144202d6e2e4f3bdae8e2048d2af5ea7deb3f722fbe6d370401954e74ff0d8cb1010ffb1f38d5", - "0xa3b5b57d435b06ed70530e060002a8fea71746ad07d969ca23f22b5e52624527595b6a6d54b4e953fb7b7596bac378f0", - "0xb90f89390df6d4b7879b915aa3c29b8d779d035033f8873bb7ac54a14ec98f0d08c0e3bf696e2ffa7b5730d736f571f8", - "0x8e81e371b92887e43d95c0dbdcc9575282b26ccebdc8cbf46587e4f2a83b61e9bc0c6d7d1f114b9d21e04fd6c180b12a", - "0x8d682947c51dffc6e0fe0a486293c9ed121f441805168236393087cf62f2a429cca60bf0e472564844347d32c6bea27e", - "0xa8341ec7dd189fa7168759240224192c58209b53fc961c18082deba217928c399bde08ceae42bffd37c1135b4d14a845", - "0xa94bb076dcc5ee5ec82fac57c5b384c690df12631882bd1b960e1eb8c04f787bc22b7bac315b9dc5a8a098f17f051a0b", - "0xab64e1c6f01b87706c88a3bd974454a438722768de7340b834ccf93ea9880c14ee7c2181432acf51f980d56de73832ee", - "0xb7b0058bb724d879e5ad7aed6230297c54cb599ef659e86bf2cc84c38225899fb388391df9b2e6fdf063171937fd8c72", - "0xae856f4fb74c27cc98b67429186e7df4feb01278cd57bfd3170af6e52e0a23b9e926bf9565a890cfb4ae8f2d590b2cd5", - "0x804b9c6702f0596d328f92fc1ed5a30a7ba17b9204524135001b569233fc4937035031d079f52fd04968f37c24013898", - "0x84274ed1af6bd6a968583995622b4d18c6a2bc703ce0d0edce45bb736529b4836343dcd11911a94a134dca7877e6cab8", - "0x88808098463f7505034c3b6328c8a08186b33f7a981c08376e429dd64b79b97753170531ed078dd265ded4ec0a1ed8d5", - "0x92823bfb23a4eb84d3759e7d717f0c8641ece0927cd2ba8c728c26bb35df2629a838002f353c8d3d75eb19520aab5f25", - "0x8db36bae4d960cdb9c51f419d7ddc81f372e56be605bc96a9d4072b829f05527c37c8f255cc6115300a2a0d2e6568d89", - "0xa8fcdbd7f3b4d7ff04149a209feb75e97149e7efceaa42d66a6b8e432590fe7bd01f1a77fa8b47108f670b612e33fee9", - "0xa9f4c53c62db7e5dbdea6918862d3c6d24b5bd8732a218edf0ba61e9d1861182323d8ecd7bef8f895b42970b492f6e40", - "0x8b95bc7f07818f4d7b409aff8da0b2c2ae136cde386f53a71565cae9fd14c73c13cc1cfd79c0f97cd77839fb738c5b9a", - "0xadbd1d11adc756b51a571ddbcbf4392415231ddad93da09acfafee03a9e4f9e1ce3826110619e5271feadfaffce3e793", - "0x95d327c8bb195cdf25fd79c98f9406a6b0316214b1630ebcce95bdaeffafa36fc1accc6882e0e5d13a8db5c0f3c0e61c", - "0x8cb2f1e2fb25558869afdacc7bb866544cfdd566cefcd048b48d458a886130bd086ecb7600a960a7f2563c61cb326510", - "0xb3aa8c4bf5b933d89cd74ca7f7176d6624d562d7d58b041328b49d7562a30b489cb606abb3c49e85baf04c28e9cd1f44", - "0x97f9053a85250c420599827297453c2cfde087065b823d9e43139e6a9cac3a2ec40a1b6e2f0726bdc870fff215462f0b", - "0x878d5dbe6b881389c2ca126ff66d87127c9aaa3f62f0d2c1ec0ea2b279ac95f8a06710dce166415db227655e2345a04d", - "0xb2c33a6b4203e3ca5247f0890e475518317ffc44cfbb1da9a1ba02114e8b752bea618050b876de5cf3b1906140a64471", - "0xa56170c8313d2b5541a795bea9934d4425b185b5c409f0484df6f44f0e4bcbf50b860ff46b7245cd99c1cfa8fc1965b7", - "0x96e2b658e2876a14147385fc423d2702a3cb76962b6b437222cf9cea39ebf4bdc03bbf434b747866d4bf72b4ceefa639", - "0x89c4a74fa2f067e7ae49c84ef782c331bcc9245db7e941804e2e99d12e987b4d25cb827778ad4c3566c4fc68018650b6", - "0xa01d30cea7d01c80ff26650020fab02e78fc3842e2398a81b44b21d58d4e9816166ff4ed2418831fa995a28ff35cb6f1", - "0xb960c80b55a8845bbf24bc3f23b0110ca701f9544ab6a5bb7929330213cb471321e55c390ceca3e24bff69bdb0d331c0", - "0x802c5b13f22be7be0e5db11eb3be0f0ea7f9182c932265060ba05fba20ea093dd2810d3b969ee3e387e60fe6ee834e8d", - "0x92478f88ef7435d15e39a97916c736abb28ea318394b88678fddbbaab3eaf31776110936abad116a8ff6ca632dd12043", - "0xa6d3da0370c303001d5ed99d1db8bce1f26b0e442f0f042e36db9674e92dcd6e80465e772f1e669f99221caee3392fe9", - "0x938f04f70a8f947d6df2f0c0e9af3cce0c06edbb3c131970dd60884fc0b0a0959c504a2a36c3ff76dfe919905671626a", - "0xa7117e55224230822e9983df2132347eb7208cb6798f291df926ab51e04b1a1f78d5568c9a8924ee6f57426134360f20", - "0xb91074c77ad93fe48dc2b10c0c5a62ca3ab7d98345b919c52d84a9dc419b59fc1b267e1c2d4b2e120016ef84bbdb0cbe", - "0xaa175c6b6edf02fe8778762c9575581c0ee6efc9dbf99c291a41444a23a056b893be6c45333d907d0bbe9fb0eef84d08", - "0xad36dcb4e2ab425aa339ae464b038d550cb11186741dcf257f1b8b80ed4f32ffabbece45e2dc1525d4c3eeed819ea04f", - "0x91cb35c1ffa9cd5aebef523edb8325078da3eb5cf9e95c675a76446fc7692aaee6f949de064ca2f3e0f082cc3fa93e20", - "0x82622f9410c143a86bc4d756b3c7b324dc295231ce865de020d61cc0868f2c150a473cea3a5b756b36771ce1032415a5", - "0xa5c29996ad3a53468ece9356a5b4ccb68971ea1c89cf39644f1da2d4a477c2ea99bf791ef902b87c225d8c53d67c4c92", - "0x92893eceed1af34fa92b23dcbab175b6a0188a27dbac9ad3317c4e39955a763cb383ab13fb1c519cde311d8a4d12e8b3", - "0x8a093cb191b94b0200e38d31955f9d240e2be1edcd6810a2396a061f17c3ddc9c4f4d56766ddff4e121be7110e03b869", - "0x93981473df0cb1f4b47c7d9b64e3123dcf1593845b401e619f5d7c70b5dbea375d1ca43fca65845fcf0a6b2e0af43791", - "0xa6beb6b0697070f9562910add88d9ba91992f8da127b27be81868b1596d1012f09ea7ed601b4a6474c921a1a1a6d866c", - "0x92026b1ee30f2ed61c9f30337c3356844217926aabdff383c19ca3c21e0bc49811ca5b308012bee4ef250cfae1615800", - "0xac0ebaea6d35f84dac4ce648af096305ba68a7a0aea0a11ab2fbe3162075444a158433c98141bc92ef3b3400d6deb46a", - "0x83046f482dee24ac3ca83373f0d1b82ac1c4beda0f229a9011a81ec659ff5fc1fb105e219975b5c744308c77a24f71e4", - "0xaa5a312c47ff7248dcb9c6ffbe5a0628ccd565c07365c4413734d415cd4fb35772622ed833862dddff520a67c509c6a5", - "0xa02fb88805c34018ac33582e19ed0a7e4616acc3dd0867e5f21914c2031c05c6dca30b8b35b57c2b137750f3878a6f8c", - "0xa60528f1f14bf0c496491d46a0fbbd6c343e4eb3f1631e92f96a3c5e5c684091aabe5801df7a67f7c6dfd1b0d35269d4", - "0xa1fd8e7fad8ca05a340c05a051bb0eb4197eed345f4104629a9e38e234b09d789cc5537024615feb4a6177d32d39e39e", - "0x8e70e36c1aa070815440e19443f1f04aae23b1b59fdbcba43b47b94a026c82c8f66c5dfe54f826f4d95ee1930cdb8008", - "0x8234c1969fa7e9079661e4ca309b71b1aaa10f4372be0b963205c23a81f5a3d52ec08ba9ff65b37f832b52d631580d61", - "0xa18cb4134127fb37c4abca328cd0047378a2e1423490af2bd3eba9ffcc99ca81a3c22404c0886f21f65c7b93c41d7981", - "0xb46fa45fe538816de776eec086e040005706cb3eca097e290abfb6864e745c879868aac8361894f3c3564373ef9ad55c", - "0xb96ca43b96c59e95439f75d1e726a35a9362f0dbd34963b156e103e080a8126a8dc3501f9fd541ff3bcf4677f5c4a86b", - "0xa8e8c87c7301613818d57387009e601a7ab5cbdc2890f63d985c30c74f9cea2d0584c116baf0d9cd5594386ee93fc661", - "0xb47e4f1b9153ef0981f813948150f283b47a7346fd9921d51fe8e4daedaef78ddeb4fd467c2ccb7cebd9816243da1c6e", - "0xa370c202a99c8441ffe96fad0f801086d4d7cc7b960f6e98cca29ceedf492afddfd0f351c9c4d29ac008bc255ec1a2a8", - "0x8f5e6ce1655d1c059b006174e3f5a55c88e1821c97f9702ad8e8455d46c2a83ae4482f2d43edda74a835686ec45a8a15", - "0xa30421e694930a3b65d397b2720d5f8e1eec2b6e2bb5a28d3f9b0a84db9aabd83850268bae64c2b10e313cccf120151b", - "0x8abe87163046f7a9b18e2a3c0b66e258facc1b31431420e0b70354b7a60ebd250a784634a76692e7d6f4330b62114945", - "0x894f033cf077d4eb312e3258d9dca414356271abce1d6094ecce6d018c5fadb1c15d8d69451574ad0701a2876db191c5", - "0xb0923d64f88ffc872654e1a294bb1af8681689c21cf08f39afe51448a68e60a9a0a74ccce9969276a932a52c07d095a3", - "0xb9ca23b5be8725fae7fa710eefd45522889c50c29c26384e00b78a962384f0aeff9d15cb5910e9565da12a577eb7e5ba", - "0xb242ccf292757197a9f470f2d80ccddc48c7f1235ba026bc68a93be2738bc968e8a200aff3e2f4807216442eb3fc50dc", - "0xadc2c3b375b308524b79a024ff87d122055440643fea6fc0a651bdb312c7cbe6a456afa9d342bc76446d77d8daf08bc2", - "0xab645955356c2ebf2f3df9da275e01daf0b44a52afc309277d6d9ad1b05484e5ae0d9d41ad485fe481e5e362826a86ae", - "0x8de96ac587a4449fcc8b7fd0a51b4b5185d9c2eb3434f94cbadd092de1e26b0f6b3f7b15a37e8424b1429121ddca0ecd", - "0x94c70ad4e9b871566f3da98170b665a09788d421818299857cde0853789fb943cbcf7d4b2c95246ea7b72edc56a8e36c", - "0xb2574be63497843340700b701d5cc8be6d23125bd62058802ee67cce1f3b5f5602b27c93fea5611f27dc695ac563f042", - "0x869ec89da7850cedd88bcb3a50a15cece233119b31b64a61bf6b2310892ce42d8b473b584b11e61db29ed24ce8033f83", - "0x8fbaa269da8e28e9adf4c1b08f109da786dbe9cba871c32eecbfb10619b7a5d65a26f9bb33e201a8ed20b3de94003fbb", - "0x8bf7a059c37242caf7f821a6314e4e4adf799e0dd86b37892a7172598892c07272acebd05b534755c57b51556b2d610f", - "0xb4e72645fca459898cdd9214892ed08b5c99f82049c0a30d72bac0b9717caa9c6cc16c3dc7aa6ea4d42dcd2a6c175df6", - "0xa39170da87a3495da55bbb9701c5461f3403447174ed6a4af75712f7ba4ac35f51a4234bc4b94da888a0959ee109c0c7", - "0xb45675b2774ea7696089dbf7a0afe6c22e85fd0e4ef3db508fbaf96c9d07f700c991789206da9309fd291be696357c5f", - "0xb52899e3e3f6341eefcbe1291db6664bf3b6e8021d32fb9c3e37b6258a35c1da927747b2ce990937d6f4c6c3e7d020d2", - "0x84e5bdb3dfe19700d79dd3fabb0159ccfa084f7288db836c855b827613ce8071067c8d7ac5cc2b4e88ed7f84b690f6e1", - "0x801477d200b6d12fc6e0a9bab1c8211193ab06e44551e037a9b4c36fc2d4f67760b9ff4eba9a3bc7b6e177e891f64ff6", - "0xb6b71a5116d3c22af26a7530f535e9b7851f25a84e562a8f17a125d55b9b3fc1bd8cfe65bdcbeeb328409521e802051c", - "0x8687e21c34d7804c12489d30680d131ce2133e2981bfa993afd8a8eeda958ebd5e6881d342d725338659882d9f21cf98", - "0xa024e97a7c4de32b6383c34431994abc533ecdbd6be9bff836ec1af022f5a86773bf345c6f33273797a61fb70a8fd5d6", - "0x83f784f095da20ce5b31f54d6cb14b32a8a12675f0029289c9cd036b7c87a8077be2d04a62618685720e6ee69c875e97", - "0xb4e9dfe7cb9d9efd3fe00d99ae5e48769d4af4bf43d4e05c0b54c9cfd8bc854de96b8d3ebf4dcc06b9dac66b7471a0de", - "0xa08b79f9d4673afcf7f38b57f484f88feb7c908f597663a2417f92c348150c2be6b5603f914eba0d9d5bdd4e5c5572c1", - "0xb0eaf919589988798cb01ba0610cd1b7fa3c08715675ece8ecd5f9ef6d5d7b2c4c8ae1ea7dfd202237171aa3e6f9de74", - "0xabff99a98baae4dd0954052503ce81827781694a5ea8c1149f96a3adde75dc2d630e138598cd2ae7fdc7a654aa17df8f", - "0x83e369b8680d8b9d995222b033b4f4f3e3b20e782113c941325c7fa9c742feef8747e4a212d9aa23285a259cc4faef8d", - "0xb16d5855dd2716613697eba36e2fae0872aaea6999e91cf6552f93f9a0b85ed4f6ff922a91b50816bd6cf8e7a4513fc9", - "0x848373db600e32e741aa1d37726bbb28956783f89ce2d781e95fb1ee1adf4359968a141678af268077eae4c25503204e", - "0x93a0dd0fdac18a31875564505b4e28f9e8bb2915faae666538597731ac56cd77f23f2456461e2f672983fb24ad91f6e0", - "0xab1ebbe49fa56524b564bc2e43784147073e6ea5d27a9540fbf2e04d0f87c645ed2fd28b3e4982cc4c0af1734ee47a6f", - "0xb3ee30b733839edab6f61f0738e3f4afaeccf700d8dc7415684f193b36d70d07acd5780cf539f12e0fbf8d4683be773a", - "0x88388f2cbdec47a6b3ae460b69eb0d2130ac14de950c22fd86de03e40d02292bb93cebe62432da39d509c1289f785fef", - "0x9370c41a54b68ff486b4cc6329c3a851716ebf1d088d77a6c56dec93a18b8a77b596cde74cc17d2adb2b2f411a2e4bbb", - "0xb9083b60dc16531f77b05a955b51a237a8f8c0173d72c352c5ca441b55abbc890b14937e457aaec4be5cbbf80cae0099", - "0xaafff8f6c6ebaad952c65054dfc7c829453ec735331bf8135e06406b7a9f740c9a200dc48bb2175516b41f77dc160121", - "0xb43d31fbbaf10526809e9e5bd8bb47a76e0fabd7852ee7744404559ab89f0f215ff518f3271a6aa972a459cab82ac558", - "0xb581ede48c6ef34e678f91dc4b89507413e00e70712e3e8c32a80eed770ec8d8b98caee9702d068aeaca6f704be57bd8", - "0x8cb0a137e68b001a5ccac61de27cac9fb78d4af7b2f5a00b8d95d33ac19cc50c69e760c5e0330a85c0ded1edce0fe6f9", - "0xb947fca07c7aa6c2bf13048275402b00b77b28f1d0ba4b589fbcede13f93b5b931c588560ab8ceba23bb8e748031b55d", - "0x81753cced5ff819901740a9a584334e355b497cb699f0be5a52cd555a4c9f149535c7bb355b54407f7f0ec27de6c2e19", - "0xb3d59273951ce97838c4853ec329782a255b5fc7c848e7992ded1be28a5ada7fa3254123afe32607b9991ec6e0659b08", - "0x86b253de246f82be1cb0cef01e87c3d022ca1829d2cc7e6a160a5afbd3ca6b94d75739b122e3bb16f8bde28a8f3223ba", - "0xb728b659fa2d8487e061a37f7d14a4c2d70cc37497a8715695d8d332cb274deee2ce23b9b5f6a7408516c02c3d526a49", - "0x81277b46d98848a45abfbe39842495659dcbb80dee985a4fc91d77d52b815487aa8bb455f411fcce4c3879c7a075a93f", - "0xb05b6f1fb4a6e654f0ee6b83e08b58b57059bb0b7c490405bc8d963c4a2d6be39c558917977e554e1e9e3169961cbf3e", - "0x88f75fa7d016fb6442551ec071cc1e2beeb3ccd213d16d744f573a82f5d70f41dd1b18af71d5f9e73d87f2f6b7dbe889", - "0x81a46434f1bbd65a661a0ff45a0295b8fd8a42a7969c5953721bc98698b64bddee3f806876d1e9983063fdd0c11f99df", - "0x8b4f6d33c510a4c9c7d623d9ae0c9aa631fcb987704726b2a4d8519372123bce3c439202f25b5b47045ec14ce39a21a8", - "0x8d5112b330fb63cf6ef3d2164b404c14ff9907d685015701399a260951912b19b8f270f869df317e9050a127763d7980", - "0xaadab394e84dfb82db15ecd2427f39b62352c3e1647c3bcd14fb24ae830ad0116f0fed87ddb63963b424a4741961386e", - "0x81ca4e5600d00a3bda24cbdea7a532a4cbbd893c10e7ff10667c15ffa8138b91667abe5466b31a3dcdd60155c48538c1", - "0xad943af1b8a5fcfcf309ed8f2f916339f254cd555c71a407a47365a139306286a05a8314e1c70e20a65fccd75d36fa12", - "0xb16597a0b437060a390467bbfab94c0bdd695ae898894f4689f939e30cc2119cc08ecb594546304adf876f4e275ebcd9", - "0xa44a4e0a6693be356065891c27eefa040a1a79475be53d54d5fdcea7e0668ff9b35f850974000ed119f6865aa6faa721", - "0xadef27d1b6e6921f4eaf69c79e2e01f5174f7033eaafdd33edcfa5119af23f3a834ffe1bdf19576581b797abd1865b34", - "0x90c1e9202f3ffe28f8e1f58e9650dc4ff4dbc158005b6f2296ec36147e524b4f2f87f8aafc39db5b006fe0c491c92f45", - "0xac817cd54288b6f7fe6338415344fc9e7b669414051631ab2f27851c052c044be06bf7235d668e194bef695923256368", - "0xab14944ef653a14456d4ebc12e3196df3f1b4707c4e50b317b5ccc8ca3a0720f0330609f0e7e71793f6ca01583f38c70", - "0xad5353f2f380837e5ffdf079350b3d42935a0517861d03af98db5ed3ea8501abd68885c8c65f5a66e944b1874826a450", - "0x8b5583863f84af8443ce8970b02e26cc5d959e47efbf8a66a54106ab165f1f76b36423aee74c7b5402fd1c4d7c1adfe6", - "0xb3b46037eed9fc30e4f8f0da8bdbdcc40a38e22e876ce9fde981883017854aba82c18eb00887d92ad847d30082fe7271", - "0x98a2b6fc90b7ad172e4368c1e54675b75c8bf2096d91c9f2b60b3397d3be3b705aed5389845dbd68f0f84438cd0f7687", - "0xb155e800852a5f90a2eac69cc4483428da1dc2c31588a13c924e60a7616ce9baeb7d4b829c772b260277cadd8ed84719", - "0xb8b92c520a1302b0cf7d993a52e1dacd7f27bda9868d59c55687d995ae676b7070af4c0792a9bc1c2635d44a4fee01bb", - "0x96dfe9bde526b8fc829eda825f55168b88e8f4e43d4d708cc3060df03437b46e12a8ac70d7788aa75760f6294d3e84d8", - "0xa3fa66c54e2fa084ced3bd838614c6c33042f492a5745d167a723c60d5e7d6020ffd1747981a23f8b68df21ad8f0fa77", - "0xb573ca10cc41fc04a642f6f62c355a4fda69b94b8e95dbb02fd1ccce4bce1191356e1fd66d372159944eb36a7071f005", - "0xacd0a1c9abddfd0ea223eda1722aaada362d34234455bd1c6be115d41e535b16f12ca428da7820a757fa4c98884a385d", - "0x96f242eee99c4db383b8754fa7987c0c159652e1866faec905a8d3f010e0a1ad05bd77b9ea8dfd653738959180f58430", - "0x9215a9b672a5d6e435e0e0a45156e0e20f75cbbdf1d14940fed3ddb63d433bef643796c7a4fff881829ebb2b2eba9460", - "0xb8ad9bfceaf08dc5a874387219ddd1170bc3a5e25ed72d321d59ae713be5ddf9fdfbd3aa7ab163be28dfa0dd14614e19", - "0xa19a1050590bc500b32c502f393e407abc3d8e683d6f6b978873aff3e3299b18b1f6b59e2b0fe237d819dbdfcfdc98ca", - "0xa6870fb11d4429686e52e1f44c8dcfc7ea24a020df9570c021578dbc1f9bdc8cf797cb3a72d7fc52805dba35d59f2cd0", - "0xa7be733b64d5c06c127bd1c87250e42bfe30ca91ed8ce51e0b6e377f454e8f6fef7f99bff650695df2fd10c375da349b", - "0xa1b97145dab30330eea2cdc8739b2446a3704b64505fcea3dd8a9b4a72edf222e98d967d6fd7f76794acfd97aa091065", - "0xb2127049907d2a3b654d1c940b740bfba3dbaf660f86ea79c2f909af7c9fe2a07a1caeb1be12370aeffaf8faa50f1582", - "0x8a207701214bb28e99b0784e9228b1c34afa701966267fe7110f6f29f5bb41eaae6cdb98844d0400787978fabd224de8", - "0x9925147a383b6f5f814520220ffdbf20b214225882c3ef49b1a1ca677709176ec82466fb9c4be2dfbe5640afb63b014a", - "0x8416ad93871623fb555b5390b80de99edaaf317350cc0c1ae9d54d59517074d40061f315cce8ba2026d9c1e6f6a1009f", - "0xa315f943deebbf0a2cdbcf3f8323e215a406e9cbfbcc3f6288714cb3a6befb1bf71b2a21ff7a2ec4731c65044c45b6b5", - "0x8213e0c2539c24efd186ffa8b6dd401ad2233bc19166a0623b26dd1e93614bbf792823f5599ac116231e2efde9885709", - "0x8e5cafd2f34a127a4a896f05e4d929eef06972a1826b3566446942198df26d62f7679b987db2b3765d9d8058b1cd85c2", - "0xb5302b399c9cdf912fd59007ad4737255552663b1e56dbe64a7b2ddd88d2093c73ea319b45db2dd49d1e03f5bef1a0ae", - "0xa0c2bcfbed4b008e1a56e5d2f2419aa59d7dd0ebd990f1c18588de702ad0fa79f445d69965fa9381e700eda13b309378", - "0x80a44eea1ffe24c26b16b8e2e70ee519258b9ad4b3e83cc4e5cca88ebc48d0160066f8b91d0581095b0de2428390c8b3", - "0x84a90cb9c7d2f799f1c4ed060387a4b793ab41c5c3eaffd3b60face9b9c3bae93cd2017283bf3de1e3dac63d0d84dd42", - "0x81d22febca276a05ba9bbc5591ee087b0491beb35b4d9f8fc0d041d642a574667ddc57660b20f5c568f7d61fdcb41bda", - "0xa3ac965ac27a28e102a439b74fbfc157e75fd57620e4c0750a466165f8aeecb2191dcf8e656f7525aa50d9c7c69b0b5c", - "0x913c17434ff0d9fc52e2ece4fec71b37d4474a18f3ea26925c1be2b250434d49759f58033ba0fce1c6862c6197930dc4", - "0xac430559c151a5e461f67b49c7786c97e1653fa8698e9759ddbdd99f5daf17fc5a012ae6330739440880728f24eba7c9", - "0xb10d8e9f8aed9361b042d1398ec74364f7c7c1cc5c7f917060572761138bdbe89bf409389ee3879f93bc8032dd67b308", - "0x937271005a4cc6a6ec134870c1b56471aa84ed4f4af1b3d5f334bc0c42762fae0c9a6a2828d3de6151a76dad7b72781c", - "0xa10e4dcf51889f69e6bd4c052f8d4036b9571ced98a3d7d779cbcb9fa5c3a82228566ea7cc1d012bf56dea0a40c5a64c", - "0xa0ed026528d9a8bb3201bc9dcd20598933e8c72fd315deea8da63d06e97392aa729d98a55a8a60fa4d5573513ba5c9fe", - "0xb723fcd04cddbd4c36feae827a03746ffef251c4f4c55a88beedaeeee194430a99f566f483668a0d88b13e7a4a37f1de", - "0x84a2cdceed44828c7c05a6a762edec0165e434e7029df617d6646aba48776e6c3b823f40689cee136536f8c93e08a629", - "0xb786264e3a237ac3a1d56c9f4e87438dfed620c867100fd38b01287f5b755c7820937403bfb86644e082094d3e410a00", - "0x92cc35b2065fca157c7bba54410f8bd85907a01c9f760aa0ddb7a82cb55811d24cb4dc6b725367a6a1c293b809a48ead", - "0xa12bbf22b117f00164a42515bc57cc9e6c43cc77fb737ee3d0c0cad94cb50cd3847d61cab469cf8ca76f7958bdcfc771", - "0x85985b00de533bde2a757eddf53be79ea39091d16af3fc92327bcd1cd59bf2bf4411a334da29ad775e8ffaf3cea7d7b8", - "0xaf9eb24185b0d330d0ea1d0b0fa78af0dcf42ced81cb0128f16cafdea687a9c5582bb6d7c5744117b271cd0b3303f0b5", - "0x8c8aaa1d85ed6327f85d579767c7a9158d209171b3efcb3e8a9d9e534c078e821b6aade255101d2c9ef6d67ba66f10be", - "0xa450518a03ffb40e1df89e0f88fd55b5b06f4872cdfb7ec55f40dc40d9424b3b289866336c195bdd54597d95569e0096", - "0x81e61cc69f93c435bd77f155e80626a9c764dd92b6c76af15c41346527948d8a6ca87d6351a0fe7987e2ee3aa66a9625", - "0xb615e0cebf4fdff4cb23a20c8389c370915ba26aa703b28efe4ab070b1603d1c5b6541684acf46b52a915f6aee447539", - "0xa7f51885c7a71885cc84ef734ecd107e8bf5f7a25131415f671d143cc1de92859e65001125323c7985799993af6c410d", - "0xabfbf7a46f32066989c32f774edcc68163f085ca81e94fe8c9fb32f8d451bbb2c20ac45cd8d97f9e618ab40186933b1a", - "0x8cf35a522b5cac1934004aa9dd236bc77198d43272888afa860cfc79b4b28dabf7a3c74098f84510897566fdd609aa45", - "0x86aa927df78f7a06a4985eb0a4f0b93529cef14f9fd2812d46abffbf25e618ead14d99c70e3c3bb2e17f3f7fabc9c264", - "0x860f1b4f4a398e9a8bb4739587cf96979cfbbe1687b7e91e5bd1198db726391b09b1a261bf12e96698818f60b5bd3537", - "0x8e7c4ee19ff115881051e8637dce1f5d6c65e865d0c757e8ce41b6d7bcd86c7070cce60649692bbf28c868c7e2e1e2f4", - "0xacf7ba01b0220419f09169ac8d16e5cc13dce08e88c90b8fdfaa33aab417f011a20b79a178d8a9f7211589d2e0affd7d", - "0xb404bde8e715aefbb9f20a353b911b79173ef3e2cf0aba98b5ae6190b90597d65043b0b4e014ad9ea6c77da2d213ea12", - "0x97e3615d1c77a402253bb55da2d1cdf82de316cefffe42b1022c94b4818d6dc4a313731db85321c537914bdf716a875c", - "0x940e950b96a4096a578c6874d747515936652b9b113a5f27f5a834a610867b05f9881e2679b0b289b8527baa0009b6dd", - "0x8de15a13ca236a3a285ce6e6826c502ae7365bbe468b6e8ac67b15b0bb49be0e996f1eec81ef69e4b7f54f8e4779a054", - "0xa12244777eacb08ecd42b5676b3a51153022ab97e9353ace0f47c6054c22de9ba60d2a60f59a36841c2a791cb1b7c288", - "0x94f7580203e39a2642ee2e7c969b9911f011d7f3a90c398e1302d26edb3df03df1d0c43baa1c6cf90dde95296d49e742", - "0x82ead33144aaecab965faf63af384565992f38fc1066e71e33d53f43ac93892e27fe78c4eaca1cccbc53364e26ff31e9", - "0xa0c129e9706d354249a7f8aa664ccd7ede89aa1445c5547410814b56d10dc086720953363ab1da8ff5f1ed5d8e575104", - "0x93b3057bf3f74edc95237781ae012cc4b1d3fd0455565ceaac7110290aa518ac32478ba4eb9851555fa87270fcc84f1f", - "0x949c2fd0b94f31f7cbf00c679bd3f6ec1a2f4056654708d39edf1a450b4e19a6e251d0bb24eb765087e698f61d3fca2c", - "0x99fd2e50e211ccb66b895eb2fc42f260f3ad5767f04c2fe238b81dae98aa6e3977443a51f4fe7b43f499caabe45699a5", - "0x84fe19626503218f327b5325bfd7c0c3d2614b47d34964aa0259d564e769c6c81502132cc1765b0b31fbe39852706927", - "0xb43287ec29d9010bec4284de58fed48dd1e129bac79f09d45153c9949131782f77b11b0c9f8ee06a39e5e9bbaa8e2c6d", - "0x908902f3ed45482df2f94415fc8e5a308057a40c8905d7cbbd58ec4848e19276577b7f7e69e5e684a8b981738e10f7ef", - "0x85cc7d9c1eae372b4f88758cd6e21604b4bc9f0794e1e74b6d9de96347f81944d01331385fae7a38e5f6096c1dc23465", - "0xaf60288c702082fc258b3dbd6952c6b75c1641a623905f491b1e72f49b9d39b33d150a336450abd3911a4c128166acdf", - "0xa7d8ac7e589558c4014369ab6f4c1f2196205b03e4278152ec0dbbd7ba54e803c3369a71d364a773aac8dbbd117e4a13", - "0x9833aed34e48c206e9328073597aee1123f5bec085339b4e6839a389a429bf3042798a31fac1464ce963204adface76b", - "0x84631a4f012bbb62133030224b57deb32dcf464cacc8ffde7775adbe68707263ab5527a1c75e597e03aa703ba658b889", - "0xa686a61f6467858a2a4c13e70ad81b1901290d3e51bbc0c6e366f9e652f575e91b11c75f640ccef8b0c6c1b05a43c9a0", - "0xb585f0ffd5144907703b41539bfad7f9f058f5985f63db911064ba6b07af8da2796b84b16db42b8d11135c3f846cd9e2", - "0xb525539516c7bb25f1d7e165f269dc8c9eedbba74df44887e178ab8fd798e2a31f39812ca922d6b64d91564f14012a64", - "0x91e480d7568fd2fae39c35b0a8d623e66a3160fee1dd4e9097255004938b11ac1cd3918dc6a1e5fbcb700c95a547e5e8", - "0x936ef55c69b842b6177de71fa48dc5442bf5132116b214302f8f242ca36a273a6bbfbfaf373777104dadbe8e7da5e970", - "0x8e950c0f6688abdff8a3b8bd77be6da6f2565c7b55711f5860ea62a3ab1d51aac31821c602bc11a45e33c69e7dde3ea4", - "0x90eed4595104a0527f8db1e028ff622ff70db4eae99cf47f6c2a0246ec7b103570a6a9a877e32e9647cc74969006743d", - "0xb756344f6c4ea05b792e416d9bd9ce9dd4bd904e7622761f28a85628506bfc9d88a25e5f04db62fad30a92fb1d8d8556", - "0xad79ba76534c1a02ac3e9b7308d390792984cd75b7e1d0e5e4ff123642d99d4ea1825643091aa8117336333c40d5bd94", - "0x832b08144887de0c0341d84f6945450af8d7a4eb32367d7703118186c1be525df9382ce61fed5f3b65a0bb3449185f7f", - "0xa322fb944e46d8e47994820890c94af423674716da810ea1da71e0a7733ad72c22114ca39a4b59c98ce4291a5684c154", - "0xb982851a65140dbea79bd3b5487e236feccee051deddcc17c2853032efca289ddb6eaf64be3dd85a73012fdbe9d2d4f3", - "0x8eed5e230e201830b44b9fadca4e156fe1a16bf840cf29da0f381ea0587b20c226de2465c67e6268973e776809af68e1", - "0x81c8f1c04490f36e41a53ee1b5185cb8adbb37c258fd6c3be8c56835bf574c37183a94d55b6554fca35d6e6dd9af0133", - "0x8c4928724107cc16d36f2976677eac0b852fc4c3c0bb2f9cd4d59cd24a113faf33b2faf405c3fcce25be51d41e42c2c4", - "0x8e4ba842636fdfc4d71f0983538ea5037d420acd26abd12efca48c252eea85544b2fa9fccdfec4e7c2a6359baffa112d", - "0xb4315b84700e26dec26f3488d308430fdff4809c10d4c24309627911cbb769ffaad0d1ecccd622dd02194eaf5ba59f91", - "0xab888308f757faef32648c1db01650dbc9aea248b09d06e6efcc996d395f48ec96f2d54a02de441d753fe8737862d991", - "0x805094cfd77e207d5c75f3cad99f41f763ec15443052cfd758c6a82ba422d831a1103a7f9b100da49c28198279c3d3dc", - "0xad857f33243e4a2cd2a773700def21fc7f94939d1a6d2c2125ecd58fc206ccafb07a2c02a1cfce19857d3654aca2c70c", - "0xa4d12d40149953daa70b89a329e918e9d93efb4e8004a9357fe76682dab9662c8507e16db83e849340f05cdb4933a373", - "0xa0dbac2ed4b5d03606524245e8a31080eb5bd3e9a0c51dad88c3b18e3e6bc5d64953a81c8e60425b80107ee6b62b1fb4", - "0x86da05355900f327164a78901f6e3db857531b33b1e855df1a67a9ba222c6b05fdb6b0ffbacaeb1ba5b45ff8979b6b68", - "0x932c9873aa3e226dd922b5a616c75153bd0390ce8f332a414b9c8cb6606c2501a37a2aa88097bc7d8e2c4261706eb38c", - "0xaccd9cdf07ccdd42033ce3b105e00bfd39e2304b1e3d66f8b1128645634452c20f759ec45adcef2fdf04408f62c4cc04", - "0xb75cfdfc1cb48918752eab17eb579820ee6e71e6667abdb64df834ffc8c1362fbbc23ca2c80dee248fe1fbb72d87dfc8", - "0x88b998c73b00638fde7d3dd650a08c5ab996dac6ac34251337fbff3fb5ae4a25dd20c1a16c987ad7ded19eca23cea891", - "0x8afef0956c942571a27f504553fb312cca9e50ce41b44e0466d0516c5abe4d8acf4594cdb03b1ccdbe3f2e6a9093b713", - "0x9042cd83c5ff261e9ebda26398caa16cac2cb840d19062fa8ae50e044c27104972948318f4c866dc4d578798272d3e49", - "0xad536719a64570a2cd1d72b6590ea1d02c8c49f259a7867be26c8191445165954bcfad50ea12688ace3fdfb0e98143bd", - "0x97c86328d63d297b6bc9718dc1ad5a05b908a750d1c455c700d84315589128ce4eea958aef2bcf0fcf4adbd8e3ce58d1", - "0x8e592cf0802e6a9541eeb654dc55055e11f3d757847285197132935ca35bbb1a9156829a39384dfa6f645ff89eb36738", - "0xac16c614998944f77590bf3913a010e13f2d3bbf6a172293baf5983506c1a2d89989fb72e598f5bba1ea10a691377c93", - "0xab8e6f5b46baa6632de3621497bcbdd584decb999fe7d8a3364843a1e0b76497600630b6a24dd30119d8bcbfca29f335", - "0xabe1d3af5279e60122d9cea8cc6581c819d7a0e20e3715da0f6da7e02d13a7653db643bd946e2fa9ba338eca81fbe140", - "0x8c33bd831ecfb18d1d0713e16beba768e9c42df62170c1f8a16764912be77f2ac5915623d1d25e8c462aa9c2f6669ca4", - "0x903692becae4a6409f7bdb127d9b11de57a5739fe24218dcbaa0092648d5332dfeef29a908ee9e43e5e0a51a4c3639bc", - "0x92591e90347ae286acd365eba32cd9ad8f20f4c9cad2dc579b195147ff290adf0d776bcb3d4b04a25d68a941fc0c781b", - "0xb64bbccf860299aec16e1f95c768a1f337c740bde612e6ba260e393edb8b04540127194761c42597abb9bcb771c576c3", - "0x9194f056ccfdfeb78a11c5347e2255d7a7ebd1251f9aebc0b58feb68d3e03a7dbbb74e3ef7309455853adfb4694bd01a", - "0xaa4f15f6d6a53ae65b7f6f91e8981d07a5919d2138679a561f7bb608dc4596e45ca06c9441d51fb678b2ad89ae7a17ae", - "0x90e3d18507beb30bde08c5001faf489a19ab545c177efb3f73fbf5605f9a0abcdc8bfbc44f832d6028e3e0a834bea98f", - "0x8f31dc0118c8c88a6e79e502d10e57652b7aba8409a5bf572ca63fed6b7cbad7f28bbc92ac2264f649792fc1d0715085", - "0xa307d1067ea4c56437b6f8913aa8fcbf4a24580fc1e3336e7f6518f0f3adb9c4733090e459a3f737414ec0048179c30a", - "0xb7cc41fdf89595cd81a821669be712cd75f3a6c7a18f95da7d7a73de4f51bb0b44771c1f7cd3cd949e6f711313308716", - "0xa9dc74e197fe60e8c0db06b18f8fe536381946edecdf31e9bd90e1ebfcad7f361544884e2fe83c23b5632912ec284faf", - "0x8b3e1e81326d611567e26ed29108f33ddb838c45bbd1355b3ae7e5d463612af64b63fff9fa8e6f2c14c8806021a5a080", - "0x92f6537bca12778866335acc1eb4c3dfc2c8e7e5cf03399743dcea46aa66cac92ac2963b0892784263ad0ebe26ffdbf6", - "0xb5cc0061f7a3e41513199c7dd91ac60d727366482a4c7328527f7bd4fc3509412f711bb722b4413b3736a219b843d15d", - "0xb3e9711d68d2c6f6e2cc27e385d5f603d9a1c9a96edeefa1ffdf390439954d19504d6aadc566b47e229ad4940ef020d2", - "0xa09d0d3f0e5dc73a4a0827b72710b514bbfce4a7fcd5141d498a5aad6c38071077f50d3f91af897d9ab677b7041dedda", - "0xb177fe260f3b86e9ac21f1bfbe2682ae5dd8c9aecebb84f37054bdab6e39094e611ce582210ceeddde66adf759dadb6d", - "0xb0ac6595eba9f5dc4b2fd21856267cfbcfb5b12aa34ec69ca32b80071c5b652e85c25a224d80443d503bf25fbbfe07e9", - "0x81f3c0e11b196bd4a2e8f07f8c037002566dc9037da81f3988add458a520c24dd1be3d43d851e28c0c6a85de4b57a542", - "0xa44308c95615f7fedb2d2127012924468c015df9f48359cc2e36ab4223870b0bfc1e9040baabefdf5266f93afaad896b", - "0x8493ec4c32d5a13b81039f1b436eb83f259945dc950e3c6c2ccf5087ec56dd2f60890ed4edf01728b6a54950e19b35c6", - "0xa1a439ec2a6a95bdac9aaa925ff337ba956c0d236ab5318354270e73ed6b73b4ae2d27b4c1686cf97b6526d04e65be81", - "0xb4659b7b53c55a4b2bbe210b53520b392f893500e18990d843b72d7379d45fb44dd1dd2184348d6fd853d6b9ecc6b7c6", - "0xafb2c68d75d00130b0e1b4f250001920213121791698ec04262db714cf7b1408d39f6cc10421f954845aad5b8250b77e", - "0xb22b843b40a97210f94043b552f348f66743055a3f274856a738e7d90a625b80e9bbb80cbbb450e1666eb56b8bd5c60f", - "0x800895ced82fe13d5fff65a93b0051c3df698bf1221b682accfdb63e3970f669ca37025750697f4e8ff2a3322ad57be4", - "0xb21f598c50d7b9f4a584d548f85e42055ef8e24991906d973749090261584c7f4f5e984b528926f7e75375dd84d51af8", - "0x849b1c68192d18274598dd6d0bf48fb5ee3b1ba25b331cff2d06f345bef3bed49760ca5690848cf33388f6a9a32cd646", - "0xaeb6fd9478b10ef456f6bbb1e6dd19b14475e65497772d12cfc097948383d3fbd191bf95f046b8bf1989954118e483d0", - "0xb1b5e0ea2835f7fc8b66e7731e392b43d16cbce04b52906b6751ab1b91978899db5fecbdabc23a19dabb253005468136", - "0x91b6b1284770cf6f7ef35bc0b872b76c7763ffcfa68f9c8cfabcb2f264a66d47598bb9293f6a40f4c3dd33c265f45176", - "0xb9ffed029846487c2cfb8a4bb61782bd8a878f3afdb73c377a0ebe63139fa070e3fcdc583eec3a53fdc5a421ff1fa877", - "0x998007249d041b0b40ff546131cfc86d0b3598dcedf9a8778a223f7ed68ba4833b97324cbb1de91292b8ff51beab44b3", - "0x8eb77ce9e0e406bf6f002870fb2fd1447646dd240df9bd485f8e0869298a1fc799d8a41b130c04370e9a9cc5c7540ca5", - "0x853db8157462c46f2af7e8f94f2ed1c9b9a7ba2896b4973296898ff3d523d6e29e0b63a5d26cecd5e490b33c87a4cecf", - "0xb1436b6f3278768f0979ee852944258f2599977d255bea6fc912ba17c5dff5bdc850cf3e1fc52be9d6d188e868670f4f", - "0xa76acbc5832019b3b35667ab027feff49f01199a80016620f5c463dfcbfb51bf276ed17b7b683158ba450660cc7973eb", - "0x94540cdb051faf3ae8b8c52662868c2dab66bd02505c4f5f8eb4d6b2e2e5fd9a610890c5dcf8fd887eee796d2b5753a8", - "0xaa35099666bceccf4eb3b65b13bba88e30a8be93693ab6761d8e5523343e8d6dd42d977e66499352fe4e9e9784a1dd0d", - "0x894471aad17be54319083c4b5e40adcfacf7c36c4aab0b671030b7ef321c53590a25eccd836efd20f32a93185fd315bb", - "0x8f52a9f705bb0dea958fcfbd52e2b6c08ad0f89a07a6b2942c1b4c37eead0d97a38a9e9aeb08d5d59b7fa2a9347f738b", - "0x9031c16b4f936c9cab55585dc5064739f696c3347ee2c0792320c9f749e760d120e396e8485ffc79d81c9f3337ad3d1c", - "0x82090a0d0d9b05459ec1c328ecd4707c333b784e3aaa0ef0072cee1eac83f9a653a75d83b9f63512a8c41200494826b4", - "0x92c3a9553001f9ea4d67236b8ad1a33275378202cc1babc03f313895458f4b2549bfbbbdd37bfb8fbff0decb6b9f820a", - "0x88651868f4da37338a22bc553388df5dd1dd0cb78c4d7d07c637d8f6faef4bed72476fdcd4304d5bedf3514011135f08", - "0x83fa0141bfebd88063f1d787719721b4c6b19ecf565b866de9d7d5d1a890e0e3d859b364bb65f8f8e688654456a40263", - "0x90a7fab753e5d56dfc0e53a6b4e6ab14508220f3a62b3f3f30570c4c9ad225e74122635826c92e8e3227ec45e551432a", - "0x8fa375b0345bf6e5e062d108f9feaec91029345ecac67ccf1264eac77b8654cbfdda1f10579f481889c0e210254eadde", - "0xb83f06116da9daebdb013b26724523f077debaf6bc618b48a7a68858a98d275f7899c4ec73a0a827219b9248dd81c8c9", - "0x8be1cada55e0c5ebb4fd460b2d209ae5326285a20c8bdd54ed9d1a87302f4063c8730bfda52d9d40e0d6fe43a0628465", - "0xa68ad6f813743ec13a811f2ef3982c82d9d9ac1f7733936aa1e122f8dc7f4a305cc221579ab8fc170c3f123a1576f9ab", - "0x8878f1128214fdbbb8a0edd85223741e021508ab6d36c50d38680f2951ee713ea056ed03f62b9461897963d50ceefe0b", - "0xacc0d43d1b0260528b7425b260a5dea445b232b37240759fc65fe26f7c9d8e51569c5722bc33e94de6492f4ba1783504", - "0xad80b1dd717b076910ee5ceabcb762e75e4d094dc83b93b65c16de1f75bc712cef223c05d5579c1561829406c07a97d9", - "0xa6fc9803f9c09d95fc326cc284f42ea5566255eb215dba8a9afb0be155ea11bcc55938b2d16f01cd2f2eda218c715efb", - "0x83ad733dbdfbaae8095a403dbf09130513f4ed4f08dcf8dd76ce83d1ea72999b7eea3a7b731da0d2bc80a83c6ee0e3e0", - "0x8748912fbd08cb34a85416b0937d9c4327e9eed20d6e30aeb024a7253f14f1e0d774f3326e54738d71aae080e28da0fe", - "0x8997e78d8acf23051428af67183ae9b2c4aa42b503745ffe33df35a35103c589987e1473ab14dcd28ee78ebcb10d8e95", - "0xa2f340502a7eb3c4a36412e6f028321372c4fa18a4743945607424e932af1271fa3e6598a162c872072529576eba6283", - "0x868ccf19b5044ab93b45c9ed3ae34fcb504fe1453d6c4a1d12c325032cf01eb90356de82080ed897e97dba13cae33a02", - "0xac8867005fe4354d67aa37b866a7e581d2f94f7bd0b9f4efb5c2d1370ec13147a60692051b02fd00ae60b512bce9b1ff", - "0x8fd01886b046819c83c12bb779e432b25ba13713f9227be702074ec3abb2bba6be37220a0a26a4bd4171b99b14e32bc4", - "0xa128981ed199f92b5959975c150a93a62fec50b61c80a3fa0634d90fc8058f76f5cbee77aae6889af12d296b30e613cd", - "0x81fe618552ff7a36c9235c6d4066cf2f930b5b38de4089e18166e4a06ca5723eadd1976d25e34b74b3ce942300b23e5b", - "0xab1223ea049e6e0fbf9b611de7fd7c15e5e9637cbd73aa0e36aea08a7503ba6804f2aa807186fdc9aa7f4f9195f72e24", - "0xb97285286981b2665f898abc13f3243b63005bef8db4cab3f658bf6167036b61af400f08db0fc3c640a9c623b760690d", - "0xae3ddff7c1f0fbb6a13dbbc667a61e863c2c7c51c2051e33cd61620142e7e30a7e0c4c1f8fbb512aa3a8640267c6ac26", - "0x99c2a89d5bef236060e51c4f952664094c20fbfca647e5d24a55c1fb8df2f3df58244fbbf3635db07b1c29ee3234fa6f", - "0xa5010764d4b9cd3b410638334d1f70c5f4843f45b4f4a9316aaea5fbb2c510a97449dd7a07b49f47334a69d37d9955d3", - "0x86706d011dcdc9e9d165d01fea1df68dd74bedaf15a39f92893c030cafe96f4498c4c1fec2d2136354341b3f440a1462", - "0x88fd57eb62bd7dc35722f3a0576c2138403a2f663a2603482e8974a895cf56ddbb02657dc6b89eb2cf5c1f9d1aff6426", - "0xb0dfd4c68e3acb6bb8a776adaa421fc5e268ed4d5964bb90a727091e5113b55b3f9c6d33cedb3ee47ff7acc5df8b1749", - "0x93b92bc942e1a636fc5c2dc1840de5faf158a113d640d5a475b48e2c56ccccaf9db0e37e90ce74c4b3f5c9ac3b2eb523", - "0xb29a16fa1ea95cbfc1873c435ad40dc8495ba6341801b72bd95d908147dcffb1b4bb426dd635f3af4c88984f56594dd8", - "0xb8f367105e1a2d554ac30200c66aeb579d3d30a8953d20fb6ebba2d876ec39c52ea5d654f1bb89b8ddf3d9d651f31cdf", - "0xb5fbc228c983d08adf8612eba5b3db3acff604439226f86aa133b02cce4ffde2f977c8dbb8b446b4375673f71634c89d", - "0xa399bea37d3056e0559f6644faa0af93063b4b545d504d7e228d3dbbc294af83d3c4cf37fe026b63899b4e7d50fd08f5", - "0x928ef411a36414b24aea26fdbed4bdb1bb6bdc2d967e2553ce54c7c4e077e76869cea590257645c9129dd55ce025295c", - "0x9684a4adeed416a9ce82ad79b55c4a3adcfbd43950bc442ed8a340381caedb70f4baaaf821e3a152f483f965d8f56162", - "0x92558a37f214d6f4cb6d72cd2f4ad24dff9d17611b9e4a41ee5c741a5d1ca9e4053b0584533ef4da206110b5dc3e2a35", - "0x973bf0724d1785cc5e85d2a8ee8c354ad4cf557217ced0b7940f6f064024c20b2bfc5b144c820b5083da4bf70690de4d", - "0xadaf1389dfa528210ca9c2657c5ff10d51f7e3b18e93a59c37211be0506c3576cb2c04ec80cd0f82605e53c5a3556620", - "0x85b58b223b09fda6f3ab674d75e780c49eb2167837243df049281e8f4fed653811138b398db9cdfe7405fdb8485602fe", - "0x849504d3db408d80745a07e850b0a804607b91a59922a5d3bc40da2748c029c029419cda38d2a4485cc0824c6b2504f0", - "0xa3f4afcb353bc2582a02be758ebf0cd18752410ca2e64231176bfa23828423e0a450a65f241a9ed8eab36cae8d9c567b", - "0xae362786cdf121206537af9590d330abbc6dc328b53cdd145dbed0e5df1364c816aae757c4c81f9d619e3698dd32bcdf", - "0x9024cfa5b0101eb02ab97866d5a3832944e5aa6888484cfba3d856576b920787b364fba5956bd7c68a305afedc958201", - "0x8a116df09fed923acefb2aecf38a4fbc4b973ee964d67f03791d70bee6356af43ffca117d4e9463ffaf0e0d5d5e5a69f", - "0x9163016175c73f1bbc912ddfe03bd4e1db19c64951c8909ee6befe71a1249d838e0db49f03670bb4c5c9b2ab0fb4fef3", - "0x8f6357318d8d16e7240a02b05ce5a4976b6079d49daa258789c6dbf4a47950ebe9de6411780fab06c7c1f35651433380", - "0x8e63cbae8be7341892dbedee3111adf0307c4ee9e375181aa53478f5ba9cdce164d6ae890e5f480119a3a51c6e989165", - "0xa9782f30674a4874d91bfba7eda63aeb5dbe66b040c768d6a925d8ee135f0655ea56276b105239cc0668fc91ddb68cd1", - "0x8d9d94b61ab84ec08665cbe0244ea41756785df019e453ef078c19380bd44c39d2958e8465c72eacf41eed5696037805", - "0xb1470e6f5d2e314474937cb5a3bc30c8bf5fc3f79014945f6ee895fe20028ffc272f9d3a7320aac93e36c96d8a5454e3", - "0xa444911bbafc71179766594f3606b6eaff041826607fd3192f62dec05cd0f01b78598609a530f6930e8440db66f76713", - "0xa9823d44e2638fca7bcc8796cc91c3eb17f46ad6db9f7f6510e093727614aa3a4f9b2c4011ef91dc1c2d224d08d8d05b", - "0xab86020972c359ab98294212558b4b14862040139876c67fc494184b5c9bcea1dbe32fe0c8dd9e60be9daa304acd599a", - "0xb7e5cb685bbdcfdb1e48259a5d68d047846c8a35c5b3f90172fb183d1df40d22eaf0edaca2761a07c29c577000ccfed0", - "0x8c88319dae4b28989817e79e6667fd891181e8d2ed91b9c6b614985bca14b12982462ec58b17be0463c24bbb79dd62a1", - "0x8c1c6867e7107fb2178157c991b9c8b0f90c8d57a51220bf3650438ccabccf62da4db8a9916491e730ff3d0c106496e3", - "0xa00a79bd58da6528b9af033087260f9f3d00519eafb4746b355204ee994e89481591b508eaa5402821083e250d38467b", - "0x8785abd7c37690f6aa870ee5c799eef72e398a7898b6767f698515be277b9c2fc1af12ea89b0620a848221343a3b5ec3", - "0x8aadae68543db65cef71d0e230a09508d72061398ef2fabec0f856aacff2125b79c70e620744aaf331faf3dfc8afb9bc", - "0x8ff0cd437fcad9630b8a2333176a55e178db4142ec841581590594d74d5b53baeac5fb903fdf7bcf83e245b95b58285e", - "0xaf274e8fad6b190be4e5dc92d2705ba6ac0d7e1ea29e958a5cdd4cb764de46a56d9eef62c999a16e7c50a50b2d9fe3a8", - "0x865e6ec7d1aa848786d6a7a4e87a24d442311f0810b01ef5a74928ab59fdfd651e48880b49680047e5b0df6b3c7c2ecc", - "0x800706baaeb35bf3bc33bdea9a8b5cb00d82df407b3b7e1b781a9359cf44fb410ed311591080181b768aae223d9246aa", - "0xa9496389d0780b309c6998374ae159f58a8d0fe9a1c24c36cebcb45b27d818e653b51a8ee1f01e30a9b2c46a548126ef", - "0xb5fccf4fc3186661939fbee2e89c2aa0e3a6ad4907bcc98c7750520540c4c183b1bbfcdf47f2f1c5e75c3a30cdf30c75", - "0xa90028e39081b736e628c2230cc1338f9210ed01309a40fdf08d39c10cced2cdf71271013bea6dba3a0444fe47963106", - "0xa0815cbb325a8fecf2e1bcc5046644be32d43a8001bd5d8cf0022e4572cd0d481b3e717002f7ab21e16da5f5d16886d6", - "0xb2024787fcda52abc4138150f15e81f4a5be442929b1651ddccbfd558029912be4d61c3c9b467605fff640edf7392494", - "0xab5aa60032304a584cc9245a33f528eae7157808dedd1ad83ebae00aadc25dbe1cd5917eb8b6b2c800df15e67bdd4c4d", - "0x866643847ef512c5119f2f6e4e3b8d3f4abb885f530bb16fcef0edb698a5b0768905e51536283925b6795a5e68b60ddc", - "0x806aa99c9a46ee11cc3ebf0db2344b7515db8c45b09a46a85f8b2082940a6f7263f3c9b12214116c88310e706f8e973a", - "0xa6eada8b9ff3cd010f3174f3d894eb8bb19efdbff4c6d88976514a5b9968b0f1827d8ac4fe510fb0ba92b64583734a1e", - "0x98480db817c3abbc8b7baedf9bf5674ec4afcfd0cd0fd670363510a426dad1bcf1b1cb3bf0f1860e54530deb99460291", - "0x81ab480187af4a3dfbc87be29eca39b342a7e8e1d1df3fc61985e0e43d8d116b8eac2f1021bde4ae4e5e3606c1b67a21", - "0x8a37df12dc997bf9b800f8fd581a614a1d5e32b843f067d63d1ca7fde2e229d24413d3a8308ec1e8389bf88154adb517", - "0xb045a55ca0bb505bd5e8fcc4cfdd5e9af1a7d5fe7a797c7ede3f0b09712b37f493d3fcf6ef0e759d7e0157db1f583c95", - "0xad502e53a50691238323642e1d8b519b3c2c2f0fd6a0dd29de231f453be730cf1adc672887d97df42af0a300f7631087", - "0x80597648f10c6d8fcd7421caf4e7f126179633078a1724817d2adc41b783723f302eabc947a7ba7767166dacf4ce8fa1", - "0xaefb56427966c81081999dffbe89f8a0c402041929cd4e83d6612866cfbb97744f4ab802578349fbecc641fa9955e81b", - "0xa340e493fb3fb604eab864d4b18a6e40ba657003f1f88787e88e48b995da3d0ab4926ce438bdc8d100a41912a47dace0", - "0xa6d777bfc0895eac541a092e14499ff8bf7156689d916a678b50a1460583b38e68158984bea113a0a8e970d8a6799a85", - "0x90ce469410f0e8cfff40472817eb445770833cdcf2895a69bc32bcf959854d41712599ceb2b0422008d7300b05e62e02", - "0x815c51be91d8516d5adc2fd61b6600957ed07cf5fdc809aa652b059bea8ed179638a19077a3f040334032f0e7900ac8b", - "0xb3ec6c0c3c007c49c6b7f7fc2ffd3d3a41cdff5ad3ac40831f53bfc0c799ffeed5f440a27acc5f64432e847cc17dd82e", - "0x823637abeab5fb19e4810b045254558d98828126e9a2d5895a34b9e4b4f49ab0a5b3ee2422f1f378995ea05df5516057", - "0xac05412bcf46c254f6548d8107a63928bba19ab6889de5d331eb68cf4d8ce206055b83af4cb7c6c23b50188391e93f84", - "0x88514163c587068178302bc56e9a8b3ad2fa62afd405db92f2478bb730101358c99c0fe40020eeed818c4e251007de9c", - "0xb1e657d0f7772795b3f5a84317b889e8ded7a08ea5beb2ab437bebf56bcb508ae7215742819ed1e4ae3969995fe3b35d", - "0xa727d4f03027fe858656ca5c51240a65924915bd8bd7ffa3cfc8314a03594738234df717e78bb55a7add61a0a4501836", - "0xb601682830fc4d48ece2bdc9f1a1d5b9a2879c40c46135f00c2c3ae1187c821412f0f0cfbc83d4e144ddd7b702ca8e78", - "0xb5cfea436aa1f29c4446979272a8637cb277f282825674ddb3acac2c280662fb119e6b2bdd52c4b8dbf2c39b1d2070d6", - "0x85c211645ff746669f60aa314093703b9045966604c6aa75aae28422621b256c0c2be835b87e87a00d3f144e8ab7b5f0", - "0x867628d25bab4cb85d448fd50fdd117be1decdd57292e194a8baa0655978fae551912851660a1d5b9de7a2afbb88ef5c", - "0xa4e79c55d1b13c959ff93ddcf1747722c6312a7941a3b49f79006b3165334bab369e5469f1bddebadb12bfaff53806d5", - "0xac61f0973e84546487c5da7991209526c380e3731925b93228d93a93bce1283a3e0807152354f5fe7f3ea44fc447f8fe", - "0xa1aa676735a73a671a4e10de2078fd2725660052aa344ca2eb4d56ee0fd04552fe9873ee14a85b09c55708443182183a", - "0x8e2f13269f0a264ef2b772d24425bef5b9aa7ea5bbfbefbcc5fd2a5efd4927641c3d2374d0548439a9f6302d7e4ba149", - "0xb0aacdaf27548d4f9de6e1ec3ad80e196761e3fb07c440909524a83880d78c93465aea13040e99de0e60340e5a5503cd", - "0xa41b25ae64f66de4726013538411d0ac10fdb974420352f2adb6ce2dcad7b762fd7982c8062a9bac85cdfcc4b577fd18", - "0xb32d87d5d551f93a16ec983fd4ef9c0efcdae4f5e242ce558e77bcde8e472a0df666875af0aeec1a7c10daebebab76ea", - "0xb8515795775856e25899e487bf4e5c2b49e04b7fbe40cb3b5c25378bcccde11971da280e8b7ba44d72b8436e2066e20f", - "0x91769a608c9a32f39ca9d14d5451e10071de2fd6b0baec9a541c8fad22da75ed4946e7f8b081f79cc2a67bd2452066a9", - "0x87b1e6dbca2b9dbc8ce67fd2f54ffe96dfcce9609210a674a4cb47dd71a8d95a5a24191d87ba4effa4a84d7db51f9ba0", - "0xa95accf3dbcbf3798bab280cabe46e3e3688c5db29944dbe8f9bd8559d70352b0cfac023852adc67c73ce203cbb00a81", - "0xa835f8ce7a8aa772c3d7cfe35971c33fc36aa3333b8fae5225787533a1e4839a36c84c0949410bb6aace6d4085588b1e", - "0x8ef7faa2cf93889e7a291713ab39b3a20875576a34a8072a133fed01046f8093ace6b858463e1e8a7f923d57e4e1bc38", - "0x969ecd85643a16d937f148e15fb56c9550aefd68a638425de5058333e8c0f94b1df338eaab1bd683190bfde68460622b", - "0x8982f4c76b782b9b47a9c5aeb135278e5c991b1558e47b79328c4fae4b30b2b20c01204ff1afb62b7797879d9dee48e2", - "0xb5098b7ba813178ced68f873c8c223e23a3283d9f1a061c95b68f37310bca4b2934a3a725fff1de1341c79bb3ba6007e", - "0x97b160787009f7b9649ed63db9387d48a669e17b2aba8656792eb4f5685bb8e6386f275476b4dfbb1b4cb0c2a69bc752", - "0x88b69369c71daad6b84fa51a0f64a6962d8c77e555b13c035ad6fa1038e7190af455b1bd61ae328b65d6a14cf3d5f0d5", - "0xaf88b87801361f0de26bd2533554ee6f4d8067e3122b54161c313c52cc9eafea00661c5c43e2d533485d1f26da4e5510", - "0x98ab18e3bbcb23ac1e34439849e56009bb765ab2f2558ebfd0a57cbe742169f114bceb930533fb911b22cb5a8fe172bc", - "0x9027507f1725d81e5ac0f0854c89ab627df3020fe928cb8745f887bf3310086c58fca1119fd5cd18a7d3561c042d58de", - "0xa676583f8a26e6f8991a0791916ce785b596ce372812f5eb7b4243ba9367ea95c797170fdac5b0c5e6b7f6519cc2b026", - "0xb91b0ab32638aef3365035a41c6068e36d2303bfee8640565e16c9a56c21703270fd45946ce663238a72c053eb3f2230", - "0xaaf4cd1ac0a30906dcd2b66b37848c6cc443da511e0b0367fd792887fdaf1500551590440e61d837dbee9d24c9801108", - "0xa06f20a02d3cd76029baad5a12592f181738378a83a95e90470fa7cc82a5ae9d2ed824a20eeb1e96e6edc0619f298688", - "0xa465d379c3481b294efc3f2f940b651c45579607cf72d143b99705eae42103a0279eb3595966453130e18935265e35d6", - "0x892a8af7816a806295278027a956663ea1297118ede0f2a7e670483b81fb14dccacc7a652e12f160e531d806ca5f2861", - "0xb480917c0e8b6e00de11b4416a20af6c48a343450a32ee43224559d30e1fecdece52cc699493e1754c0571b84f6c02c2", - "0xb3182da84c81e5a52e22cebed985b0efc3056350ec59e8646e7fd984cdb32e6ac14e76609d0ffaca204a7a3c20e9f95d", - "0xa04ea6392f3b5a176fa797ddec3214946962b84a8f729ffbd01ca65767ff6237da8147fc9dc7dd88662ad0faefdb538c", - "0x95c0d10a9ba2b0eb1fd7aa60c743b6cf333bb7f3d7adedce055d6cd35b755d326bf9102afabb1634f209d8dacfd47f1a", - "0xa1a583d28b07601541fa666767f4f45c954431f8f3cc3f96380364c5044ff9f64114160e5002fb2bbc20812b8cbd36cb", - "0xa1a0708af5034545e8fcc771f41e14dff421eed08b4606f6d051f2d7799efd00d3a59a1b9a811fa4eddf5682e63102ea", - "0xab27c7f54096483dd85c866cfb347166abe179dc5ffaca0c29cf3bfe5166864c7fa5f954c919b3ba00bdbab38e03407d", - "0xac8c82271c8ca71125b380ed6c61b326c1cfe5664ccd7f52820e11f2bea334b6f60b1cf1d31599ed94d8218aa6fbf546", - "0xa015ea84237d6aa2adb677ce1ff8a137ef48b460afaca20ae826a53d7e731320ebdd9ee836de7d812178bec010dd6799", - "0x925418cda78a56c5b15d0f2dc66f720bda2885f15ffafb02ce9c9eed7167e68c04ad6ae5aa09c8c1c2f387aa39ad6d1b", - "0x87c00bba80a965b3742deacafb269ca94ead4eb57fdb3ed28e776b1d0989e1b1dba289019cfb1a0f849e58668a4f1552", - "0x948d492db131ca194f4e6f9ae1ea6ebc46ebbed5d11f1f305d3d90d6b4995b1218b9606d114f48282a15661a8a8051ca", - "0x8179617d64306417d6865add8b7be8452f1759721f97d737ef8a3c90da6551034049af781b6686b2ea99f87d376bce64", - "0x918e3da425b7c41e195ed7b726fa26b15a64299fe12a3c22f51a2a257e847611ac6cfcc99294317523fc491e1cbe60c4", - "0xa339682a37844d15ca37f753599d0a71eedfbbf7b241f231dd93e5d349c6f7130e0d0b97e6abd2d894f8b701da37cb11", - "0x8fc284f37bee79067f473bc8b6de4258930a21c28ac54aaf00b36f5ac28230474250f3aa6a703b6057f7fb79a203c2c1", - "0xa2c474e3a52a48cd1928e755f610fefa52d557eb67974d02287dbb935c4b9aab7227a325424fed65f8f6d556d8a46812", - "0x99b88390fa856aa1b8e615a53f19c83e083f9b50705d8a15922e7c3e8216f808a4cc80744ca12506b1661d31d8d962e4", - "0xa1cbd03e4d4f58fc4d48fa165d824b77838c224765f35d976d3107d44a6cf41e13f661f0e86f87589292721f4de703fb", - "0xb3a5dde8a40e55d8d5532beaa5f734ee8e91eafad3696df92399ae10793a8a10319b6dc53495edcc9b5cfd50a389a086", - "0x996e25e1df5c2203647b9a1744bd1b1811857f742aee0801508457a3575666fcc8fc0c047c2b4341d4b507008cd674c2", - "0x93e0a66039e74e324ee6c38809b3608507c492ef752202fff0b2c0e1261ca28f1790b3af4fdb236f0ed7e963e05c1ec0", - "0xb6084e5818d2d860ac1606d3858329fbad4708f79d51a6f072dc370a21fdb1e1b207b74bc265a8547658bfb6a9569bb3", - "0xa5336126a99c0ecfc890584b2a167922a26cae652dfc96a96ab2faf0bf9842f166b39ceaf396cd3d300d0ebb2e6e0ebf", - "0xb8b6f13ce9201decaba76d4eca9b9fa2e7445f9bc7dc9f82c262f49b15a40d45d5335819b71ff2ee40465da47d015c47", - "0xb45df257b40c68b7916b768092e91c72b37d3ed2a44b09bf23102a4f33348849026cb3f9fbb484adfea149e2d2a180ff", - "0xa50d38ee017e28021229c4bb7d83dd9cdad27ab3aa38980b2423b96aa3f7dc618e3b23895b0e1379ca20299ff1919bbf", - "0x97542cf600d34e4fdc07d074e8054e950708284ed99c96c7f15496937242365c66e323b0e09c49c9c38113096640a1b6", - "0x822d198629697dcd663be9c95ff1b39419eae2463fa7e6d996b2c009d746bedc8333be241850153d16c5276749c10b20", - "0x9217bc14974766ebdfbf6b434dd84b32b04658c8d8d3c31b5ff04199795d1cfad583782fd0c7438df865b81b2f116f9c", - "0x93477879fa28a89471a2c65ef6e253f30911da44260833dd51030b7a2130a923770ebd60b9120f551ab373f7d9ed80aa", - "0x87d89ff7373f795a3a798f03e58a0f0f0e7deab8db2802863fab84a7be64ae4dcf82ece18c4ddbefccd356262c2e8176", - "0xa3ba26bd31d3cc53ceeced422eb9a63c0383cde9476b5f1902b7fe2b19e0bbf420a2172ac5c8c24f1f5c466eecc615d4", - "0xa0fe061c76c90d84bd4353e52e1ef4b0561919769dbabe1679b08ef6c98dcfb6258f122bb440993d976c0ab38854386b", - "0xb3070aa470185cb574b3af6c94b4069068b89bb9f7ea7db0a668df0b5e6aabdfe784581f13f0cf35cd4c67726f139a8c", - "0x9365e4cdf25e116cbc4a55de89d609bba0eaf0df2a078e624765509f8f5a862e5da41b81883df086a0e5005ce1576223", - "0xa9036081945e3072fa3b5f022df698a8f78e62ab1e9559c88f9c54e00bc091a547467d5e2c7cbf6bc7396acb96dd2c46", - "0x8309890959fcc2a4b3d7232f9062ee51ece20c7e631a00ec151d6b4d5dfccf14c805ce5f9aa569d74fb13ae25f9a6bbe", - "0xb1dc43f07303634157f78e213c2fae99435661cc56a24be536ccbd345ef666798b3ac53c438209b47eb62b91d6fea90a", - "0x84eb451e0a74ef14a2c2266ff01bd33d9a91163c71f89d0a9c0b8edfcfe918fc549565509cd96eed5720a438ff55f7f2", - "0x9863b85a10db32c4317b19cc9245492b9389b318cf128d9bbc7ec80a694fcbbd3c0d3189a8cad00cc9290e67e5b361ee", - "0x8a150ee474ebe48bdfcac1b29e46ac90dcded8abbe4807a165214e66f780f424be367df5ef1e94b09acf4a00cd2e614d", - "0xa6677a373130b83e30849af12475e192f817ba4f3226529a9cca8baaefb8811db376e4a044b42bf1481268c249b1a66e", - "0xb969cbf444c1297aa50d1dfa0894de4565161cb1fc59ba03af9655c5bf94775006fe8659d3445b546538a22a43be6b93", - "0x8383167e5275e0707e391645dc9dea9e8a19640ecfa23387f7f6fcaddff5cde0b4090dfad7af3c36f8d5c7705568e8d8", - "0xa353ddbc6b6837773e49bb1e33a3e00ca2fb5f7e1dba3a004b0de75f94a4e90860d082a455968851ef050ae5904452e0", - "0xadeccf320d7d2831b495479b4db4aa0e25c5f3574f65a978c112e9981b2663f59de4c2fa88974fdcabb2eedb7adab452", - "0xafa0eacc9fdbe27fb5e640ecad7ecc785df0daf00fc1325af716af61786719dd7f2d9e085a71d8dc059e54fd68a41f24", - "0xa5b803a5bbe0ca77c8b95e1e7bacfd22feae9f053270a191b4fd9bca850ef21a2d4bd9bcd50ecfb971bb458ff2354840", - "0xb023c9c95613d9692a301ef33176b655ba11769a364b787f02b42ceb72338642655ea7a3a55a3eec6e1e3b652c3a179e", - "0x8fa616aa7196fc2402f23a19e54620d4cf4cf48e1adfb7ea1f3711c69705481ddcc4c97236d47a92e974984d124589e5", - "0xa49e11e30cb81cb7617935e8a30110b8d241b67df2d603e5acc66af53702cf1e9c3ef4a9b777be49a9f0f576c65dcc30", - "0x8df70b0f19381752fe327c81cce15192389e695586050f26344f56e451df2be0b1cdf7ec0cba7ce5b911dcff2b9325ae", - "0x8fbbc21a59d5f5a14ff455ca78a9a393cab91deb61cf1c25117db2714d752e0054ed3e7e13dd36ad423815344140f443", - "0xa9a03285488668ab97836a713c6e608986c571d6a6c21e1adbd99ae4009b3dde43721a705d751f1bd4ebf1ea7511dfed", - "0xb2f32b8e19e296e8402251df67bae6066aeefd89047586d887ffa2eacdf38e83d4f9dc32e553799024c7a41818945755", - "0x942cf596b2278ad478be5c0ab6a2ad0ceafe110263cc93d15b9a3f420932104e462cf37586c374f10b1040cb83b862e0", - "0xaaa077a55f501c875ceae0a27ef2b180be9de660ef3d6b2132eb17256771ce609d9bc8aaf687f2b56ae46af34ad12b30", - "0x90ac74885be1448101cf3b957d4486e379673328a006ea42715c39916e9334ea77117ff4a60d858e2ccce9694547a14f", - "0x9256cdfc2339e89db56fd04bd9b0611be0eefc5ee30711bcece4aadf2efcc5a6dcc0cfd5f733e0e307e3a58055dff612", - "0xa4c7384e208a0863f4c056248f595473dcde70f019ddaede45b8caf0752575c241bac6e436439f380ac88eee23a858e9", - "0xa3aa67391781e0736dddc389f86b430b2fc293b7bd56bfd5a8ec01d1dd52ed940593c3ad4ce25905061936da062b0af6", - "0x80299275ec322fbb66cc7dce4482ddd846534e92121186b6906c9a5d5834346b7de75909b22b98d73120caec964e7012", - "0xaa3a6cd88e5f98a12738b6688f54478815e26778357bcc2bc9f2648db408d6076ef73cced92a0a6b8b486453c9379f18", - "0xb07c444681dc87b08a7d7c86708b82e82f8f2dbd4001986027b82cfbed17b9043e1104ade612e8e7993a00a4f8128c93", - "0xaf40e01b68d908ac2a55dca9b07bb46378c969839c6c822d298a01bc91540ea7a0c07720a098be9a3cfe9c27918e80e8", - "0xabd8947c3bbc3883c80d8c873f8e2dc9b878cbbb4fc4a753a68f5027de6d8c26aa8fbbafeb85519ac94e2db660f31f26", - "0xa234f9d1a8f0cb5d017ccca30b591c95ec416c1cb906bd3e71b13627f27960f61f41ed603ffbcf043fd79974ec3169a8", - "0x835aaf52a6af2bc7da4cf1586c1a27c72ad9de03c88922ad172dce7550d70f6f3efcc3820d38cd56ae3f7fc2f901f7a0", - "0xae75db982a45ad01f4aa7bc50d642ff188219652bb8d521d13a9877049425d57852f3c9e4d340ffec12a4d0c639e7062", - "0xb88884aa9187c33dc784a96832c86a44d24e9ffe6315544d47fc25428f11337b9ffd56eb0a03ad709d1bf86175059096", - "0x8492ca5afcc6c0187b06453f01ed45fd57eb56facbeea30c93686b9e1dab8eaabd89e0ccb24b5f35d3d19cd7a58b5338", - "0x9350623b6e1592b7ea31b1349724114512c3cce1e5459cd5bddd3d0a9b2accc64ab2bf67a71382d81190c3ab7466ba08", - "0x98e8bf9bed6ae33b7c7e0e49fc43de135bffdba12b5dcb9ff38cb2d2a5368bb570fe7ee8e7fbe68220084d1d3505d5be", - "0xab56144393f55f4c6f80c67e0ab68f445568d68b5aa0118c0c666664a43ba6307ee6508ba0bb5eb17664817bc9749af0", - "0x827d5717a41b8592cfd1b796a30d6b2c3ca2cdc92455f9f4294b051c4c97b7ad6373f692ddafda67884102e6c2a16113", - "0x8445ce2bb81598067edaa2a9e356eda42fb6dc5dd936ccf3d1ff847139e6020310d43d0fec1fe70296e8f9e41a40eb20", - "0x9405178d965ee51e8d76d29101933837a85710961bb61f743d563ef17263f3c2e161d57e133afac209cdb5c46b105e31", - "0xb209f9ed324c0daa68f79800c0a1338bbaf6d37b539871cb7570f2c235caca238a2c4407961fcb7471a103545495ef2c", - "0x92ae6437af6bbd97e729b82f5b0d8fb081ca822f340e20fae1875bdc65694cd9b8c037a5a1d49aa9cae3d33f5bad414e", - "0x9445bdb666eae03449a38e00851629e29a7415c8274e93343dc0020f439a5df0009cd3c4f5b9ce5c0f79aefa53ceac99", - "0x93fdab5f9f792eada28f75e9ac6042a2c7f3142ba416bfdb1f90aa8461dbe4af524eee6db4f421cb70c7bc204684d043", - "0xa7f4dc949af4c3163953320898104a2b17161f7be5a5615da684f881633174fb0b712d0b7584b76302e811f3fac3c12f", - "0xa8ac84da817b3066ba9789bf2a566ccf84ab0a374210b8a215a9dcf493656a3fa0ecf07c4178920245fee0e46de7c3ec", - "0x8e6a0ae1273acda3aa50d07d293d580414110a63bc3fb6330bb2ee6f824aff0d8f42b7375a1a5ba85c05bfbe9da88cb5", - "0xa5dea98852bd6f51a84fa06e331ea73a08d9d220cda437f694ad9ad02cf10657882242e20bdf21acbbaa545047da4ce5", - "0xb13f410bf4cfce0827a5dfd1d6b5d8eabc60203b26f4c88238b8000f5b3aaf03242cdeadc2973b33109751da367069e1", - "0xa334315a9d61b692ad919b616df0aa75a9f73e4ea6fc27d216f48964e7daebd84b796418580cf97d4f08d4a4b51037cd", - "0x8901ba9e963fcd2f7e08179b6d19c7a3b8193b78ca0e5cf0175916de873ca0d000cd7ac678c0473be371e0ac132f35a2", - "0xb11a445433745f6cb14c9a65314bbf78b852f7b00786501b05d66092b871111cd7bee25f702d9e550d7dd91601620abb", - "0x8c2f7b8e7b906c71f2f154cc9f053e8394509c37c07b9d4f21b4495e80484fc5fc8ab4bdc525bd6cfa9518680ba0d1a2", - "0xb9733cebe92b43b899d3d1bfbf4b71d12f40d1853b2c98e36e635fdd8a0603ab03119890a67127e6bc79afae35b0bef2", - "0xa560f6692e88510d9ba940371e1ada344caf0c36440f492a3067ba38e9b7011caac37ba096a8a4accb1c8656d3c019b3", - "0xac18624339c1487b2626eef00d66b302bdb1526b6340d6847befe2fdfb2b410be5555f82939f8707f756db0e021ed398", - "0xafd9a3b8866a7fe4f7bc13470c0169b9705fcd3073685f5a6dcff3bdbbc2be50ac6d9908f9a10c5104b0bffc2bc14dad", - "0x97f15c92fe1f10949ed9def5dd238bc1429706e5037a0e0afb71c2d0e5845e2fed95a171c393e372077a7c7059f8c0e0", - "0x9453a1d4d09c309b70968ea527007d34df9c4cfd3048e5391aac5f9b64ca0c05dde5b8c949c481cfc83ef2e57b687595", - "0xb80e4b7c379ad435c91b20b3706253b763cbc980db78f782f955d2516af44c07bbfa5888cbf3a8439dc3907320feb25a", - "0x8939f458d28fefe45320b95d75b006e98330254056d063e4a2f20f04bcb25936024efe8d436d491ed34b482f9b9ae49c", - "0xa9ead2e833f71f7e574c766440c4b3c9c3363698c7ade14499a56003a272832ee6d99440887fa43ccdf80265b9d56b97", - "0xb6547a36934f05ce7b779e68049d61351cf229ae72dc211cc96a2a471b2724782f9355fdb415ea6f0ea1eb84fe00e785", - "0x828bfb3099b7b650b29b0f21279f829391f64520a6ab916d1056f647088f1e50fac9253ef7464eceab5380035c5a59c4", - "0x8d714b9ea650be4342ff06c0256189e85c5c125adf6c7aeca3dba9b21d5e01a28b688fc2116ce285a0714a8f1425c0b8", - "0x8a82eda041b2e72a3d73d70d85a568e035fbd6dc32559b6c6cfdf6f4edcb59a6ba85b6294a721aa0a71b07714e0b99ae", - "0xaf5665ebc83d027173b14ffb0e05af0a192b719177889fadc9ac8c082fda721e9a75d9ce3f5602dbfd516600ee3b6405", - "0xa68fdddf03d77bebdb676e40d93e59bd854408793df2935d0a5600601f7691b879981a398d02658c2da39dbbf61ef96c", - "0x8c001ebc84fcf0470b837a08a7b6125126b73a2762db47bbdc38c0e7992b1c66bac7a64faa1bf1020d1c63b40adc3082", - "0x8553889b49f9491109792db0a69347880a9cf2911b4f16f59f7f424e5e6b553687d51282e8f95be6a543635247e2e2c2", - "0xa2c269d6370b541daf1f23cc6b5d2b03a5fa0c7538d53ae500ef875952fe215e74a5010329ff41461f4c58b32ad97b3d", - "0xa5dae097285392b4eba83a9fd24baa03d42d0a157a37fae4b6efc3f45be86024b1182e4a6b6eadcf5efe37704c0a1ae5", - "0x89871a77d2032387d19369933cd50a26bda643e40cfd0ce73febe717a51b39fae981406fd41e50f4a837c02a99524ef9", - "0x8a76d495e90093ec2ac22f53759dc1cf36fbb8370fb586acbd3895c56a90bbf3796bcc4fc422ca4058adf337ead1402e", - "0xad4eb7576c4954d20623c1336c63662c2a6fb46ec6ef99b7f8e946aa47488dcb136eab60b35600f98c78c16c10c99013", - "0x894c2b120cec539feb1d281baaadde1e44beafedeeec29b804473fe024e25c1db652f151c956e88d9081fb39d27e0b19", - "0x9196bd5c100878792444c573d02b380a69e1b4b30cb59a48114852085058a5fd952df4afee3ecceb5c4ede21e1ed4a1a", - "0xa996fffc910764ea87a1eedc3a3d600e6e0ff70e6a999cb435c9b713a89600fc130d1850174efe9fc18244bb7c6c5936", - "0x8591bb8826befa8bee9663230d9a864a5068589f059e37b450e8c85e15ce9a1992f0ce1ead1d9829b452997727edcf9d", - "0x9465e20bb22c41bf1fa728be8e069e25cda3f7c243381ca9973cbedad0c7b07d3dd3e85719d77cf80b1058ce60e16d68", - "0x926b5ce39b6e60b94878ffeae9ff20178656c375fb9cfe160b82318ca500eb3e2e3144608b6c3f8d6c856b8fe1e2fbcf", - "0xa1ef29cbc83c45eb28ad468d0ce5d0fdd6b9d8191ba5ffa1a781c2b232ed23db6b7b04de06ef31763a6bfe377fa2f408", - "0x9328e63a3c8acf457c9f1f28b32d90d0eeadb0f650b5d43486a61d7374757a7ada5fc1def2a1e600fa255d8b3f48036f", - "0xa9c64880fcb7654f4dd08f4c90baac95712dd6dd407e17ea60606e9a97dc8e54dd25cb72a9bf3fc61f8d0ad569fe369d", - "0xa908eb7b940c1963f73046d6b35d40e09013bfbfbeb2ccd64df441867e202b0f3b625fa32dd04987c3d7851360abdffc", - "0xb3947b5ed6d59e59e4472cdb1c3261de1b5278fb7cb9b5fca553f328b3b3e094596861ea526eca02395f7b7358155b7b", - "0x99da7f190d37bc58945f981cf484d40fcf0855cf8178e2ce8d057c7f0a9d9f77425fdbce9ef8366f44f671b20fd27d0b", - "0x913976d77d80e3657977df39571577fdf0be68ba846883705b454f8493578baa741cfaede53783e2c97cc08964395d83", - "0x8d754a61e5164a80b5090c13f3e936056812d4ae8dc5cc649e6c7f37464777249bc4ae760a9806939131f39d92cca5bf", - "0x82ffd098480828a90cb221a8c28584e15904bad477c13b2e2d6ef0b96a861ce4a309a328fe44342365349456ad7c654f", - "0x89ae3ce4b0357044579ca17be85d8361bb1ce3941f87e82077dd67e43ec0f95edd4bd3426225c90994a81a99e79490b7", - "0xa170892074016d57c9d8e5a529379d7e08d2c1158b9ac4487ac9b95266c4fd51cb18ae768a2f74840137eec05000dd5a", - "0xaafd8acd1071103c7af8828a7a08076324d41ea530df90f7d98fafb19735fc27ead91b50c2ca45851545b41d589d0f77", - "0x8623c849e61d8f1696dc9752116a26c8503fd36e2cbbc9650feffdd3a083d8cdbb3b2a4e9743a84b9b2ad91ac33083f2", - "0xac7166ddd253bb22cdbd8f15b0933c001d1e8bc295e7c38dc1d2be30220e88e2155ecd2274e79848087c05e137e64d01", - "0xa5276b216d3df3273bbfa46210b63b84cfe1e599e9e5d87c4e2e9d58666ecf1af66cb7ae65caebbe74b6806677215bd0", - "0x88792f4aa3597bb0aebadb70f52ee8e9db0f7a9d74f398908024ddda4431221a7783e060e0a93bf1f6338af3d9b18f68", - "0x8f5fafff3ecb3aad94787d1b358ab7d232ded49b15b3636b585aa54212f97dc1d6d567c180682cca895d9876cacb7833", - "0xab7cb1337290842b33e936162c781aa1093565e1a5b618d1c4d87dd866daea5cebbcc486aaa93d8b8542a27d2f8694c7", - "0x88480a6827699da98642152ebc89941d54b4791fbc66110b7632fb57a5b7d7e79943c19a4b579177c6cf901769563f2f", - "0xa725ee6d201b3a610ede3459660658ee391803f770acc639cfc402d1667721089fb24e7598f00e49e81e50d9fd8c2423", - "0x98924372da8aca0f67c8c5cad30fa5324519b014fae7849001dcd51b6286118f12b6c49061219c37714e11142b4d46de", - "0xa62c27360221b1a7c99697010dfe1fb31ceb17d3291cf2172624ebeff090cbaa3c3b01ec89fe106dace61d934711d42d", - "0x825173c3080be62cfdc50256c3f06fe190bc5f190d0eb827d0af5b99d80936e284a4155b46c0d462ee574fe31d60983d", - "0xa28980b97023f9595fadf404ed4aa36898d404fe611c32fd66b70252f01618896f5f3fda71aea5595591176aabf0c619", - "0xa50f5f9def2114f6424ff298f3b128068438f40860c2b44e9a6666f43c438f1780be73cf3de884846f1ba67f9bef0802", - "0xb1eee2d730da715543aeb87f104aff6122cb2bf11de15d2519ff082671330a746445777924521ec98568635f26988d0c", - "0x862f6994a1ff4adfd9fb021925cccf542fca4d4b0b80fb794f97e1eb2964ef355608a98eec6e07aadd4b45ee625b2a21", - "0x8ce69a18df2f9b9f6e94a456a7d94842c61dea9b00892da7cf5c08144de9be39b8c304aeca8b2e4222f87ba367e61006", - "0xb5f325b1cecd435f5346b6bc562d92f264f1a6d91be41d612df012684fdd69e86063db077bc11ea4e22c5f2a13ae7bee", - "0x85526870a911127835446cb83db8986b12d5637d59e0f139ad6501ac949a397a6c73bd2e7fba731b1bb357efe068242c", - "0x8552247d3f7778697f77389717def5a149fc20f677914048e1ed41553b039b5427badc930491c0bae663e67668038fd1", - "0xa545640ee5e51f3fe5de7050e914cfe216202056cd9d642c90e89a166566f909ee575353cb43a331fde17f1c9021414e", - "0x8b51229b53cff887d4cab573ba32ec52668d197c084414a9ee5589b285481cea0c3604a50ec133105f661321c3ca50f5", - "0x8cdc0b960522bed284d5c88b1532142863d97bbb7dc344a846dc120397570f7bd507ceb15ed97964d6a80eccfef0f28e", - "0xa40683961b0812d9d53906e795e6470addc1f30d09affebf5d4fbbd21ddfa88ce441ca5ea99c33fd121405be3f7a3757", - "0xa527875eb2b99b4185998b5d4cf97dd0d4a937724b6ad170411fc8e2ec80f6cee2050f0dd2e6fee9a2b77252d98b9e64", - "0x84f3a75f477c4bc4574f16ebc21aaa32924c41ced435703c4bf07c9119dd2b6e066e0c276ff902069887793378f779e0", - "0xa3544bc22d1d0cab2d22d44ced8f7484bfe391b36991b87010394bfd5012f75d580596ffd4f42b00886749457bb6334b", - "0xb81f6eb26934b920285acc20ceef0220dd23081ba1b26e22b365d3165ce2fbae733bbc896bd0932f63dcc84f56428c68", - "0x95e94d40a4f41090185a77bf760915a90b6a3e3ace5e53f0cb08386d438d3aa3479f0cd81081b47a9b718698817265cd", - "0xb69bd1625b3d6c17fd1f87ac6e86efa0d0d8abb69f8355a08739109831baeec03fd3cd4c765b5ff8b1e449d33d050504", - "0x8448f4e4c043519d98552c2573b76eebf2483b82d32abb3e2bfc64a538e79e4f59c6ca92adff1e78b2f9d0a91f19e619", - "0x8f11c42d6a221d1fda50887fb68b15acdb46979ab21d909ed529bcad6ae10a66228ff521a54a42aca0dad6547a528233", - "0xa3adb18d7e4a882b13a067784cf80ea96a1d90f5edc61227d1f6e4da560c627688bdf6555d33fe54cab1bca242986871", - "0xa24d333d807a48dc851932ed21cbdd7e255bad2699909234f1706ba55dea4bb6b6f8812ffc0be206755868ba8a4af3f9", - "0xa322de66c22a606e189f7734dbb7fda5d75766d5e69ec04b4e1671d4477f5bcb9ff139ccc18879980ebc3b64ab4a2c49", - "0x88f54b6b410a1edbf125db738d46ee1a507e69bc5a8f2f443eb787b9aa7dbd6e55014ec1e946aabeb3e27a788914fb04", - "0xb32ee6da1dcd8d0a7fd7c1821bb1f1fe919c8922b4c1eeed56e5b068a5a6e68457c42b192cbaef5dc6d49b17fa45bc0f", - "0x8a44402da0b3a15c97b0f15db63e460506cb8bef56c457166aea5e8881087d8202724c539ef0feb97131919a73aefca8", - "0xb967e3fead6171fa1d19fd976535d428b501baff59e118050f9901a54b12cc8e4606348454c8f0fc25bd6644e0a5532e", - "0xb7a0c9e9371c3efbbb2c6783ce2cc5f149135175f25b6d79b09c808bce74139020e77f0c616fa6dcb3d87a378532529d", - "0xa54207782ffc909cd1bb685a3aafabbc4407cda362d7b3c1b14608b6427e1696817aeb4f3f85304ac36e86d3d8caa65b", - "0x98c1da056813a7bfebc81d8db7206e3ef9b51f147d9948c088976755826cc5123c239ca5e3fe59bed18b5d0a982f3c3f", - "0xae1c86174dfafa9c9546b17b8201719aecd359f5bbeb1900475041f2d5b8a9600d54d0000c43dd061cfda390585726ff", - "0xa8ee5a8be0bd1372a35675c87bfd64221c6696dc16e2d5e0996e481fec5cdbcb222df466c24740331d60f0521285f7d3", - "0x8ddadbe3cf13af50d556ce8fc0dd77971ac83fad9985c3d089b1b02d1e3afc330628635a31707b32595626798ea22d45", - "0xa5c80254baf8a1628dc77c2445ebe21fbda0de09dd458f603e6a9851071b2b7438fe74214df293dfa242c715d4375c95", - "0xb9d83227ed2600a55cb74a7052003a317a85ca4bea50aa3e0570f4982b6fe678e464cc5156be1bd5e7bba722f95e92c5", - "0xb56085f9f3a72bea9aa3a8dc143a96dd78513fa327b4b9ba26d475c088116cab13843c2bff80996bf3b43d3e2bddb1d6", - "0x8fa9b39558c69a9757f1e7bc3f07295e4a433da3e6dd8c0282397d26f64c1ecd8eb3ba9824a7cacfb87496ebbb45d962", - "0x879c6d0cb675812ed9dee68c3479a499f088068501e2677caeae035e6f538da91a49e245f5fcce135066169649872bee", - "0x91aa9fd3fed0c2a23d1edda8a6542188aeb8abee8772818769bdee4b512d431e4625a343af5d59767c468779222cf234", - "0xa6be0bb2348c35c4143482c7ef6da9a93a5356f8545e8e9d791d6c08ed55f14d790d21ee61d3a56a2ae7f888a8fd46ca", - "0x808ee396a94e1b8755f2b13a6ffbedef9e0369e6c2e53627c9f60130c137299d0e4924d8ef367e0a7fad7f68a8c9193c", - "0xad1086028fcdac94d5f1e7629071e7e47e30ad0190ae59aaebfb7a7ef6202ab91323a503c527e3226a23d7937af41a52", - "0x9102bdaf79b907d1b25b2ec6b497e2d301c8eac305e848c6276b392f0ad734131a39cc02ed42989a53ca8da3d6839172", - "0x8c976c48a45b6bc7cd7a7acea3c2d7c5f43042863b0661d5cd8763e8b50730552187a8eecf6b3d17be89110208808e77", - "0xa2624c7e917e8297faa3af89b701953006bf02b7c95dfba00c9f3de77748bc0b13d6e15bb8d01377f4d98fb189538142", - "0xa405f1e66783cdcfe20081bce34623ec3660950222d50b7255f8b3cc5d4369aeb366e265e5224c0204911539f0fa165e", - "0x8d69bdcaa5d883b5636ac8f8842026fcc58c5e2b71b7349844a3f5d6fbecf44443ef4f768eac376f57fb763606e92c9f", - "0x82fce0643017d16ec1c3543db95fb57bfa4855cc325f186d109539fcacf8ea15539be7c4855594d4f6dc628f5ad8a7b0", - "0x8860e6ff58b3e8f9ae294ff2487f0d3ffae4cf54fd3e69931662dabc8efd5b237b26b3def3bcd4042869d5087d22afcf", - "0x88c80c442251e11c558771f0484f56dc0ed1b7340757893a49acbf96006aa73dfc3668208abea6f65375611278afb02a", - "0x8be3d18c6b4aa8e56fcd74a2aacb76f80b518a360814f71edb9ccf3d144bfd247c03f77500f728a62fca7a2e45e504c5", - "0x8b8ebf0df95c3f9b1c9b80469dc0d323784fd4a53f5c5357bb3f250a135f4619498af5700fe54ad08744576588b3dfff", - "0xa8d88abdaadd9c2a66bc8db3072032f63ed8f928d64fdb5f810a65074efc7e830d56e0e738175579f6660738b92d0c65", - "0xa0a10b5d1a525eb846b36357983c6b816b8c387d3890af62efb20f50b1cb6dd69549bbef14dab939f1213118a1ae8ec2", - "0x8aadf9b895aeb8fdc9987daa937e25d6964cbd5ec5d176f5cdf2f0c73f6f145f0f9759e7560ab740bf623a3279736c37", - "0x99aeda8a495031cc5bdf9b842a4d7647c55004576a0edc0bd9b985d60182608361ed5459a9d4b21aa8e2bd353d10a086", - "0x832c8b3bfcd6e68eee4b100d58014522de9d4cefa99498bc06c6dca83741e4572e20778e0d846884b33439f160932bca", - "0x841f56ebefc0823ab484fc445d62f914e13957e47904419e42771aa605e33ab16c44f781f6f9aa42e3a1baf377f54b42", - "0xa6e40271d419e295a182725d3a9b541ffd343f23e37549c51ecaa20d13cf0c8d282d6d15b24def5702bfee8ba10b12ac", - "0x8ac00925ac6187a4c5cde48ea2a4eaf99a607e58b2c617ee6f01df30d03fafada2f0469178dd960d9d64cbd33a0087d8", - "0xb6b80916b540f8a0fe4f23b1a06e2b830008ad138271d5ba3cd16d6619e521fe2a7623c16c41cba48950793386eea942", - "0x8412c0857b96a650e73af9d93087d4109dd092ddf82188e514f18fcac644f44d4d62550bfa63947f2d574a2e9d995bbb", - "0xb871395baa28b857e992a28ac7f6d95ec461934b120a688a387e78498eb26a15913b0228488c3e2360391c6b7260b504", - "0x926e2d25c58c679be77d0e27ec3b580645956ba6f13adcbc2ea548ee1b7925c61fcf74c582337a3b999e5427b3f752f2", - "0xa165fa43fecae9b913d5dcfc232568e3e7b8b320ce96b13800035d52844c38fd5dbf7c4d564241d860c023049de4bcbc", - "0xb4976d7572fd9cc0ee3f24888634433f725230a7a2159405946a79315bc19e2fc371448c1c9d52bf91539fd1fe39574b", - "0xa6b461eb72e07a9e859b9e16dfa5907f4ac92a5a7ca4368b518e4a508dc43f9b4be59db6849739f3ef4c44967b63b103", - "0xb976606d3089345d0bc501a43525d9dca59cf0b25b50dfc8a61c5bd30fac2467331f0638fab2dc68838aa6ee8d2b6bc9", - "0xb16ea61c855da96e180abf7647fa4d9dd6fd90adebadb4c5ed4d7cd24737e500212628fca69615d89cb40e9826e5a214", - "0x95a3e3162eb5ea27a613f8c188f2e0dcc5cbd5b68c239858b989b004d87113e6aa3209fa9fad0ee6ecef42814ba9db1a", - "0xb6a026ab56d3224220e5bce8275d023c8d39d1bdf7eec3b0923429b7d5ef18cf613a3591d364be8727bb1fa0ba11eabb", - "0x949f117e2e141e25972ee9ccdd0b7a21150de7bbf92bbd89624a0c5f5a88da7b2b172ba2e9e94e1768081f260c2a2f8d", - "0xb7c5e9e6630287d2a20a2dfb783ffe6a6ff104ff627c6e4e4342acc2f3eb6e60e9c22f465f8a8dc58c42f49840eca435", - "0x872be5a75c3b85de21447bb06ac9eb610f3a80759f516a2f99304930ddf921f34cbffc7727989cdd7181d5fc62483954", - "0xa50976ea5297d797d220932856afdd214d1248230c9dcd840469ecc28ea9f305b6d7b38339fedb0c00b5251d77af8c95", - "0x80b360f8b44914ff6f0ffbd8b5360e3cabe08639f6fe06d0c1526b1fe9fe9f18c497f1752580b30e950abd3e538ad416", - "0xa2f98f9bf7fac78c9da6bb41de267742a9d31cf5a04b2fb74f551084ec329b376f651a59e1ae919b2928286fb566e495", - "0x8b9d218a8a6c150631548e7f24bbd43f132431ae275c2b72676abbea752f554789c5ff4aac5c0eeee5529af7f2b509ef", - "0xaa21a243b07e9c7b169598bf0b102c3c280861780f83121b2ef543b780d47aaa4b1850430ee7927f33ece9847c4e0e1a", - "0x8a6f90f4ce58c8aa5d3656fe4e05acccf07a6ec188a5f3cde7bf59a8ae468e66f055ac6dfc50b6e8e98f2490d8deedc5", - "0x8e39f77ca4b5149ffe9945ceac35d068760ba338d469d57c14f626dd8c96dbe993dd7011beff727c32117298c95ee854", - "0x83bd641c76504222880183edd42267e0582642c4993fe2c7a20ce7168e4c3cbf7586e1d2d4b08c84d9b0bf2f6b8800b8", - "0xa9d332993cf0c1c55130e5cf3a478eb5e0bfb49c25c07538accc692ef03d82b458750a7b991cc0b41b813d361a5d31e3", - "0xa0fc60e6a6015df9bee04cea8f20f01d02b14b6f7aa03123ab8d65da071b2d0df5012c2a69e7290baae6ed6dd29ebe07", - "0xa2949dde2e48788ceaac7ec7243f287ffe7c3e788cdba97a4ab0772202aeef2d50382bed8bf7eff5478243f7eabe0bda", - "0xa7879373ea18572dba6cf29868ca955ffa55b8af627f29862f6487ee398b81fe3771d8721ca8e06716c5d91b9ac587cb", - "0xb3c7081e2c5306303524fbe9fe5645111a57dffd4ec25b7384da12e56376a0150ab52f9d9cc6ca7bdd950695e39b766d", - "0xa634a6a19d52dcb9f823352b36c345d2de54b75197bcd90528d27830bd6606d1a9971170de0849ed5010afa9f031d5be", - "0x88f2062f405fa181cfdb8475eaf52906587382c666ca09a9522537cfebbc7de8337be12a7fd0db6d6f2f7ab5aefab892", - "0xb1f0058c1f273191247b98783b2a6f5aa716cf799a8370627fc3456683f03a624d0523b63a154fe9243c0dfd5b37c460", - "0xae39a227cc05852437d87be6a446782c3d7fbe6282e25cf57b6b6e12b189bdc0d4a6e2c3a60b3979256b6b5baf8f1c5f", - "0x802a1af228ab0c053b940e695e7ef3338f5be7acf4e5ed01ac8498e55b492d3a9f07996b1700a84e22f0b589638909cd", - "0xa36490832f20e4b2f9e79ee358b66d413f034d6a387534b264cdeac2bca96e8b5bcbdd28d1e98c44498032a8e63d94d2", - "0x8728c9a87db2d006855cb304bba54c3c704bf8f1228ae53a8da66ca93b2dac7e980a2a74f402f22b9bc40cd726e9c438", - "0xa08f08ab0c0a1340e53b3592635e256d0025c4700559939aeb9010ed63f7047c8021b4210088f3605f5c14fb51d1c613", - "0x9670fd7e2d90f241e8e05f9f0b475aa260a5fb99aa1c9e61cd023cbad8ed1270ae912f168e1170e62a0f6d319cf45f49", - "0xa35e60f2dd04f098bf274d2999c3447730fe3e54a8aff703bc5a3c274d22f97db4104d61a37417d93d52276b27ef8f31", - "0x859df7a21bc35daec5695201bd69333dc4f0f9e4328f2b75a223e6615b22b29d63b44d338413ca97eb74f15563628cb7", - "0xb2b44ad3e93bc076548acdf2477803203108b89ecc1d0a19c3fb9814d6b342afc420c20f75e9c2188ad75fdb0d34bb2d", - "0x941173ee2c87765d10758746d103b667b1227301e1bcfecef2f38f9ab612496a9abd3050cef5537bf28cfecd2aacc449", - "0x92b0bea30ebed20ac30648efb37bac2b865daaa514316e6f5470e1de6cb84651ff77c127aa7beed4521bda5e8fc81122", - "0xaf17bf813bb238cf8bb437433f816786612209180a6c0a1d5141292dc2d2c37164ef13bfc50c718bfcc6ce26369298a2", - "0x8461fd951bdfda099318e05cc6f75698784b033f15a71bce26165f0ce421fd632d50df9eeced474838c0050b596e672c", - "0x83281aa18ae4b01e8201e1f64248cc6444c92ee846ae72adb178cef356531558597d84ff93a05abf76bfe313eb7dbe86", - "0xb62b150f73999c341daa4d2f7328d2f6ca1ef3b549e01df58182e42927537fc7971c360fe8264af724f4c0247850ef12", - "0xa7022a201f79c012f982b574c714d813064838a04f56964d1186691413757befeeaada063e7884297606e0eea1b1ed43", - "0xa42ac9e8be88e143853fd8e6a9ff21a0461801f0ac76b69cca669597f9af17ecb62cccdcdcbe7f19b62ab93d7f838406", - "0x80f1ca73b6ba3a2fbae6b79b39c0be8c39df81862d46c4990c87cbf45b87996db7859d833abc20af2fcb4faf059c436a", - "0xb355943e04132d5521d7bbe49aea26f6aa1c32f5d0853e77cc2400595325e923a82e0ff7601d1aee79f45fd8a254f6ae", - "0x87142c891d93e539b31d0b5ead9ea600b9c84db9be9369ff150a8312fe3d10513f4c5b4d483a82b42bc65c45dd9dd3bd", - "0x823c3d7f6dda98a9d8c42b3fee28d3154a95451402accadb6cf75fc45d2653c46a569be75a433094fa9e09c0d5cf1c90", - "0xb3c3497fe7356525c1336435976e79ec59c5624c2fb6185ee09ca0510d58b1e392965e25df8a74d90d464c4e8bb1422b", - "0x88c48d83e8ddc0d7eea051f3d0e21bc0d3a0bb2b6a39ece76750c1c90c382a538c9a35dc9478b8ceb8157dcccbbf187a", - "0x93da81a8939f5f58b668fefdc6f5f7eca6dc1133054de4910b651f8b4a3267af1e44d5a1c9e5964dc7ab741eb146894b", - "0x8b396e64985451ac337f16be61105106e262e381ea04660add0b032409b986e1ac64da3bc2feae788e24e9cb431d8668", - "0x9472068b6e331ea67e9b5fbf8057672da93c209d7ded51e2914dbb98dccd8c72b7079b51fd97a7190f8fc8712c431538", - "0xac47e1446cb92b0a7406f45c708567f520900dfa0070d5e91783139d1bfc946d6e242e2c7b3bf4020500b9f867139709", - "0x896053706869fb26bb6f7933b3d9c7dd6db5c6bd1269c7a0e222b73039e2327d44bda7d7ae82bf5988808b9831d78bcd", - "0xa55e397fa7a02321a9fe686654c86083ecedb5757586d7c0250ec813ca6d37151a12061d5feca4691a0fd59d2f0fdd81", - "0xae23f08ac2b370d845036518f1bddb7fea8dc59371c288a6af310486effeb61963f2eef031ca90f9bdbcf0e475b67068", - "0xb5462921597a79f66c0fec8d4c7cfd89f427692a7ce30d787e6fd6acd2377f238ec74689a0fdbe8ef3c9c9bd24b908dc", - "0xae67e8ea7c46e29e6aae6005131c29472768326819aa294aaf5a280d877de377b44959adb1348fa3e929dcbc3ae1f2c0", - "0x84962b4c66500a20c4424191bdfb619a46cda35bdb34c2d61edcb0b0494f7f61dd5bf8f743302842026b7b7d49edd4b5", - "0x846f76286dc3cc59cb15e5dabb72a54a27c78190631df832d3649b2952fa0408ecde7d4dfdae7046c728efa29879fb51", - "0x8f76c854eaee8b699547e07ad286f7dadfa6974c1328d12502bd7630ae619f6129272fdd15e2137ffef0143c42730977", - "0x8007b163d4ea4ec6d79e7a2aa19d06f388da0b3a56f3ee121441584e22a246c0e792431655632bf6e5e02cb86914eebf", - "0xac4d2cecc1f33e6fb73892980b61e62095ddff5fd6167f53ca93d507328b3c05440729a277dc3649302045b734398af1", - "0x92d2a88f2e9c9875abaff0d42624ccb6d65401de7127b5d42c25e6adccd7a664504c5861618f9031ced8aeb08b779f06", - "0xa832c1821c1b220eb003fc532af02c81196e98df058cdcc9c9748832558362915ea77526937f30a2f74f25073cb89afb", - "0xb6f947ab4cc2baec100ed8ec7739a2fd2f9504c982b39ab84a4516015ca56aea8eef5545cfc057dd44c69b42125fb718", - "0xb24afacf2e90da067e5c050d2a63878ee17aaf8fd446536f2462da4f162de87b7544e92c410d35bf2172465940c19349", - "0xb7a0aa92deac71eaab07be8fa43086e071e5580f5dbf9b624427bdd7764605d27303ae86e5165bed30229c0c11958c38", - "0xb0d1d5bfa1823392c5cf6ed927c1b9e84a09a24b284c2cd8fcb5fda8e392c7c59412d8f74eb7c48c6851dff23ae66f58", - "0xa24125ef03a92d2279fb384186ca0274373509cfec90b34a575490486098438932ee1be0334262d22d5f7d3db91efe67", - "0x83e08e5fba9e8e11c164373794f4067b9b472d54f57f4dbe3c241cf7b5b7374102de9d458018a8c51ab3aed1dddf146f", - "0x9453101b77bb915ed40990e1e1d2c08ea8ec5deb5b571b0c50d45d1c55c2e2512ec0ceca616ff0376a65678a961d344d", - "0x92a0516e9eb6ad233d6b165a8d64a062ce189b25f95d1b3264d6b58da9c8d17da2cd1f534800c43efcf2be73556cd2ff", - "0x958d0b5d7d8faf25d2816aa6a2c5770592ad448db778dd9b374085baa66c755b129822632eaabcb65ee35f0bf4b73634", - "0x90a749de8728b301ad2a6b044e8c5fd646ccd8d20220e125cba97667e0bb1d0a62f6e3143b28f3d93f69cdc6aa04122a", - "0x84bd34c8d8f74dec07595812058db24d62133c11afed5eb2a8320d3bfc28e442c7f0cfd51011b7b0bb3e5409cb7b6290", - "0xaecc250b556115d97b553ad7b2153f1d69e543e087890000eaa60f4368b736921d0342ce5563124f129096f5d5e2ca9d", - "0x977f17ac82ed1fbf422f9b95feb3047a182a27b00960296d804fd74d54bb39ad2c055e665c1240d2ad2e06a3d7501b00", - "0xaf5be9846bd4879ebe0af5e7ad253a632f05aedfe306d31fe6debe701ba5aa4e33b65efc05043bc73aadb199f94baed4", - "0x9199e12ec5f2aaaeed6db5561d2dcc1a8fe9c0854f1a069cba090d2dff5e5ba52b10c841ccbd49006a91d881f206150d", - "0x8f4a96a96ed8ceaf3beba026c89848c9ca4e6452ce23b7cf34d12f9cc532984a498e051de77745bdc17c7c44c31b7c30", - "0xaf3f2a3dbe8652c4bfca0d37fb723f0e66aab4f91b91a625114af1377ad923da8d36da83f75deb7a3219cd63135a3118", - "0xa6d46963195df8962f7aa791d104c709c38caa438ddd192f7647a884282e81f748c94cdf0bb25d38a7b0dc1b1d7bbcf7", - "0x86f3de4b22c42d3e4b24b16e6e8033e60120af341781ab70ae390cb7b5c5216f6e7945313c2e04261a51814a8cb5db92", - "0xb9f86792e3922896cfd847d8ff123ff8d69ecf34968fb3de3f54532f6cd1112b5d34eeabdca46ae64ad9f6e7e5b55edc", - "0x83edfbcbc4968381d1e91ab813b3c74ab940eaf6358c226f79182f8b21148ec130685fd91b0ea65916b0a50bccf524ea", - "0x93b61daca7a8880b7926398760f50016f2558b0bab74c21181280a1baf3414fc539911bb0b79c4288d29d3c4ad0f4417", - "0xad541aeb83a47526d38f2e47a5ce7e23a9adabe5efeae03541026881e6d5ef07da3ac1a6ed466ca924fa8e7a91fcff88", - "0xac4bba31723875025640ed6426003ed8529215a44c9ffd44f37e928feef9fc4dfa889088131c9be3da87e8f3fdf55975", - "0x88fa4d49096586bc9d29592909c38ea3def24629feacd378cc5335b70d13814d6dac415f8c699ee1bf4fe8b85eb89b38", - "0xb67d0b76cbd0d79b71f4673b96e77b6cda516b8faa1510cfe58ff38cc19000bb5d73ff8418b3dab8c1c7960cb9c81e36", - "0x98b4f8766810f0cfecf67bd59f8c58989eb66c07d3dfeee4f4bbce8fd1fce7cc4f69468372eaec7d690748543bd9691d", - "0x8445891af3c298b588dec443beacdf41536adb84c812c413a2b843fd398e484eb379075c64066b460839b5fe8f80177c", - "0xb603635c3ed6fdc013e2a091fc5164e09acf5f6a00347d87c6ebadb1f44e52ff1a5f0466b91f3f7ffc47d25753e44b75", - "0x87ec2fc928174599a9dafe7538fec7dcf72e6873b17d953ed50708afff0da37653758b52b7cafa0bf50dfcf1eafbb46c", - "0xb9dbd0e704d047a457d60efe6822dc679e79846e4cbcb11fa6c02079d65673ee19bbf0d14e8b7b200b9205f4738df7c7", - "0x9591ec7080f3f5ba11197a41f476f9ba17880f414d74f821a072ec5061eab040a2acba3d9856ff8555dfe5eaeb14ca19", - "0xb34c9d1805b5f1ce38a42b800dec4e7f3eb8c38e7d2b0a525378e048426fed150dbfe9cc61f5db82b406d1b9ff2d10bf", - "0xa36fdc649dc08f059dfa361e3969d96b4cc4a1ebf10b0cd01a7dd708430979e8d870961fef85878f8779b8e23caafb18", - "0x88dfc739a80c16c95d9d6f73c3357a92d82fa8c3c670c72bee0f1e4bac9ec338e1751eb786eda3e10f747dd7a686900f", - "0x84a535ad04f0961756c61c70001903a9adf13126983c11709430a18133c4b4040d17a33765b4a06968f5d536f4bfb5c5", - "0x8c86d695052a2d2571c5ace744f2239840ef21bb88e742f050c7fa737cd925418ecef0971333eb89daa6b3ddfede268c", - "0x8e9a700157069dc91e08ddcbdde3a9ad570272ad225844238f1015004239c542fceb0acce6d116c292a55f0d55b6175e", - "0x84d659e7f94e4c1d15526f47bc5877a4ef761c2a5f76ec8b09c3a9a30992d41b0e2e38ed0c0106a6b6c86d670c4235f3", - "0xa99253d45d7863db1d27c0ab561fb85da8c025ba578b4b165528d0f20c511a9ca9aff722f4ff7004843f618eb8fced95", - "0x89a3cacb15b84b20e95cd6135550146bbe6c47632cc6d6e14d825a0c79b1e02b66f05d57d1260cb947dc4ae5b0283882", - "0x8385b1555e794801226c44bd5e878cbe68aeac0a19315625a8e5ea0c3526b58cdd4f53f9a14a167a5e8a293b530d615a", - "0xb68c729e9df66c5cd22af4909fb3b0057b6a231c4a31cd6bf0fa0e53c5809419d15feb483de6e9408b052458e819b097", - "0x924f56eda269ec7ec2fc20c5731bf7f521546ddf573ccbe145592f1c9fee5134747eb648d9335119a8066ca50a1f7e50", - "0xb2100a26b9c3bec7ec5a53f0febbf56303f199be2f26b2d564cfee2adc65483b84192354f2865c2f4c035fa16252ae55", - "0x8f64dbed62e638563967ec1605a83216aed17eb99aa618c0543d74771ea8f60bbb850c88608d4f8584f922e30a8a0a72", - "0xb31b9e1ffe8d7260479c9413f8e680f3fe391ae8fcf44fcca3000d9b2473a40c1d32299f8f63865a57579a2d6c7e9f08", - "0xa5b1d136142eb23e322c6c07cb838a3f58ab6925472352ebd0bb47041a0d8729e1074ca223922f3a7a672ced7a1e562d", - "0x8d9470a5a15d833a447b5f108333d50f30aa7659e331c3f8080b1e928a99922edc650466a2f54f3d48afdb34bff42142", - "0x866368f5891564e5b2de37ad21ff0345c01129a14ea5667f9b64aad12d13ec034622872e414743af0bf20adb2041b497", - "0x88ef9c2ebf25fd0c04b7cfa35fbac2e4156d2f1043fa9f98998b2aa402c8f9a4f1039e782451a46840f3e0e4b3fa47d3", - "0x94ba04a4859273697e264a2d238dc5c9ff573ebc91e4796ea58eebe4080c1bf991255ab2ad8fb1e0301ce7b79cc6e69b", - "0x86b6bd0953309a086e526211bf1a99327269304aa74d8cdc994cee63c3a2d4b883e832b0635888dff2a13f1b02eb8df4", - "0x843ea6ea5f2c7a1fd50be56a5765dcce3ea61c99b77c1a729ee0cd8ec706385ac7062e603479d4c8d3527f030762d049", - "0x8d3675195a3b06f2d935d45becc59f9fa8fa440c8df80c029775e47fe9c90e20f7c8e4cc9a2542dd6bfe87536c428f0d", - "0x8978580b0c9b0aa3ab2d47e3cfd92fa891d3ddee57829ee4f9780e8e651900457d8e759d1a9b3e8f6ae366e4b57f2865", - "0x890112ec81d0f24b0dfbb4d228e418eff02ae63dc691caf59c1d103e1d194e6e2550e1bec41c0bfdb74fed454f621d0c", - "0x97da00bd4b19d1e88caff7f95b8b9a7d29bc0afe85d0c6a163b4b9ef336f0e90e2c49ce6777024bb08df908cc04ea1ca", - "0xb458268d275a5211106ccaa8333ce796ef2939b1c4517e502b6462e1f904b41184a89c3954e7c4f933d68b87427a7bfd", - "0xaac9c043ba8ba9283e8428044e6459f982413380ee7005a996dc3cc468f6a21001ecaa3b845ce2e73644c2e721940033", - "0x82145013c2155a1200246a1e8720adf8a1d1436b10d0854369d5b1b6208353e484dd16ce59280c6be84a223f2d45e5e2", - "0xb301bafa041f9b203a46beab5f16160d463aa92117c77a3dc6a9261a35645991b9bafcc186c8891ca95021bd35f7f971", - "0xa531b8d2ac3de09b92080a8d8857efa48fb6a048595279110e5104fee7db1dd7f3cfb8a9c45c0ed981cbad101082e335", - "0xa22ac1d627d08a32a8abd41504b5222047c87d558ffae4232cefdeb6a3dc2a8671a4d8ddfba2ff9068a9a3ffb0fe99b1", - "0xb8d9f0e383c35afb6d69be7ff04f31e25c74dd5751f0e51290c18814fbb49ee1486649e64355c80e93a3d9278bd21229", - "0x8165babccd13033a3614c878be749dfa1087ecbeee8e95abcfffe3aa06695711122cb94477a4d55cffd2febf0c1173de", - "0xa4c1bc84ecb9d995d1d21c2804adf25621676d60334bd359dac3a2ec5dc8de567aa2831c10147034025fb3e3afb33c4b", - "0xb77307cab8e7cb21e4038493058fb6db9e2ec91dda9d7f96f25acbc90309daf7b6d8a205682143ee35d675e9800c3b08", - "0xaaf7466083cd1f325ba860efe3faf4cebe6a5eecf52c3e8375d72043a5cfc8e6cb4b40f8e48f97266e84f0d488e8badf", - "0x9264a05a3abc2a5b4958f957f3a486a5eb3ddd10ff57aa6943c9430d0cfa01d63b72695b1ade50ac1b302d312175e702", - "0xb3f9e4c589ad28b1eceed99dc9980fac832524cfcbe4a486dfeedb4b97c080e24bdb3967e9ca63d2240e77f9addfaefd", - "0xb2c1e253a78e7179e5d67204422e0debfa09c231970b1bfb70f31a8d77c7f5059a095ca79d2e9830f12c4a8f88881516", - "0x81865a8a25913d1072cb5fd9505c73e0fde45e4c781ddd20fb0a7560d8b1cd5e1f63881c6efc05360e9204dfa6c3ce16", - "0xab71c2ea7fa7853469a2236dedb344a19a6130dc96d5fd6d87d42d3fffda172557d203b7688ce0f86acd913ce362e6cd", - "0x8aa2051bc3926c7bd63565f3782e6f77da824cb3b22bb056aa1c5bccfa274c0d9e49a91df62d0e88876e2bd7776e44b9", - "0xb94e7074167745323d1d353efe7cfb71f40a390e0232354d5dfd041ef523ac8f118fb6dcc42bf16c796e3f61258f36f8", - "0x8210fcf01267300cb1ccf650679cf6e1ee46df24ae4be5364c5ff715332746c113d680c9a8be3f17cacaeb3a7ba226ce", - "0x905ac223568eedc5acd8b54e892be05a21abbb4083c5dbec919129f9d9ffa2c4661d78d43bf5656d8d7aafa06f89d647", - "0xa6e93da7e0c998e6ce2592d1aa87d12bf44e71bec12b825139d56682cdce8f0ba6dbfe9441a9989e10578479351a3d9d", - "0xacde928a5e2df0d65de595288f2b81838155d5673013100a49b0cb0eb3d633237af1378148539e33ccd1b9a897f0fec3", - "0xa6e1a47e77f0114be6ae7acd2a51e6a9e38415cce7726373988153cdd5d4f86ef58f3309adc5681af4a159300ed4e5b5", - "0xad2b6a0d72f454054cb0c2ebc42cd59ff2da7990526bd4c9886003ba63b1302a8343628b8fe3295d3a15aa85150e0969", - "0xb0bc3aea89428d7918c2ee0cc57f159fba134dad224d0e72d21a359ca75b08fbb4373542f57a6408352033e1769f72c6", - "0xaad0497525163b572f135fad23fdd8763631f11deeaf61dea5c423f784fe1449c866040f303555920dc25e39cdb2e9b4", - "0x8ce5d8310d2e17342bf881d517c9afc484d12e1f4b4b08ad026b023d98cba410cd9a7cc8e2c3c63456652a19278b6960", - "0x8d9d57dbb24d68b6152337872bd5d422198da773174ade94b633f7c7f27670ff91969579583532ae7d8fe662c6d8a3b0", - "0x855a1c2d83becb3f02a8f9a83519d1cb112102b61d4cdd396844b5206e606b3fefdbcc5aa8751da2b256d987d74d9506", - "0x90eb7e6f938651f733cf81fcd2e7e8f611b627f8d94d4ac17ac00de6c2b841e4f80cada07f4063a13ae87b4a7736ca28", - "0x8161459a21d55e7f5f1cecfc1595c7f468406a82080bfa46d7fb1af4b5ec0cd2064c2c851949483db2aa376e9df418e6", - "0x8344ccd322b2072479f8db2ab3e46df89f536408cba0596f1e4ec6c1957ff0c73f3840990f9028ae0f21c1e9a729d7df", - "0x929be2190ddd54a5afe98c3b77591d1eae0ab2c9816dc6fe47508d9863d58f1ea029d503938c8d9e387c5e80047d6f1e", - "0x856e3d1f701688c650c258fecd78139ce68e19de5198cf1cd7bb11eba9d0f1c5af958884f58df10e3f9a08d8843f3406", - "0x8490ae5221e27a45a37ca97d99a19a8867bcc026a94f08bdccfbb4b6fa09b83c96b37ec7e0fd6ee05f4ae6141b6b64a8", - "0xb02dbd4d647a05ac248fda13708bba0d6a9cd00cae5634c1938b4c0abbb3a1e4f00f47aa416dcd00ffcdf166330bff9a", - "0x9076164bb99ca7b1a98d1e11cb2f965f5c22866658e8259445589b80e3cb3119c8710ede18f396ba902696785619079c", - "0xaacf016920936dae63778ad171386f996f65fe98e83cfcdd75e23774f189303e65cc8ad334a7a62f9230ed2c6b7f6fa4", - "0xa8031d46c7f2474789123469ef42e81c9c35eb245d38d8f4796bba406c02b57053f5ec554d45373ab437869a0b1af3f0", - "0xa4b76cd82dc1f305a0ee053e9a4212b67f5acc5e69962a8640d190a176b73fbc2b0644f896ff3927cd708d524668ed09", - "0xb00b029c74e6fdf7fb94df95ef1ccad025c452c19cddb5dccfb91efdcb8a9a1c17847cfa4486eae4f510e8a6c1f0791a", - "0x9455e5235f29a73e9f1a707a97ddb104c55b9d6a92cc9952600d49f0447d38ea073ee5cf0d13f7f55f12b4a5132f4b10", - "0xae118847542ed1084d269e8f3b503d0b6571a2c077def116ad685dcca2fca3dcb3f86e3f244284bdcd5ae7ac968d08a5", - "0x8dcb4965cd57e8b89cd71d6fc700d66caa805bfd29ab71357961527a7894e082d49145c2614b670dcb231ab9050d0663", - "0xadd6ed14f3183f4acc73feea19b22c9a330e431c674e5034924da31b69e8c02d79b570d12ef771a04215c4809e0f8a80", - "0x96ae7e110412ee87d0478fdbdbaab290eb0b6edd741bb864961845e87fd44bcbe630371060b8104d8bf17c41f2e3fca0", - "0xa20db17f384e9573ca0928af61affab6ff9dd244296b69b026d737f0c6cd28568846eca8dadf903ee0eecbb47368351d", - "0x937bfdf5feb0797863bc7c1be4dcc4f2423787952a3c77dfa3bfe7356f5dbcc4daebde976b84fc6bd97d5124fb8f85c9", - "0xa7050cc780445c124e46bba1acc0347ddcfa09a85b35a52cc5808bf412c859c0c680c0a82218f15a6daeefe73f0d0309", - "0xa9d9b93450e7630f1c018ea4e6a5ca4c19baa4b662eadfbe5c798fe798d8a3775ed1eb12bd96a458806b37ab82bdc10a", - "0xa52a4d5639e718380915daaefad7de60764d2d795443a3db7aeab5e16a1b8faa9441a4ccc6e809d8f78b0ac13eef3409", - "0x8e6f72b6664a8433b032849b03af68f9376b3c16c0bc86842c43fc7bf31e40bc9fc105952d5c5780c4afa19d7b802caa", - "0xa107ae72f037000c6ee14093de8e9f2c92aa5f89a0a20007f4126419e5cb982469c32187e51a820f94805c9fccd51365", - "0x9708218f9a984fe03abc4e699a4f3378a06530414a2e95e12ca657f031ef2e839c23fd83f96a4ba72f8203d54a1a1e82", - "0xb9129770f4c5fcac999e98c171d67e148abd145e0bf2a36848eb18783bb98dff2c5cef8b7407f2af188de1fae9571b1c", - "0x88cc9db8ff27eb583871eeeb517db83039b85404d735517c0c850bdfa99ae1b57fd24cf661ab60b4726878c17e047f37", - "0xa358c9aadc705a11722df49f90b17a2a6ba057b2e652246dc6131aaf23af66c1ca4ac0d5f11073a304f1a1b006bc0aa5", - "0xac79f25af6364a013ba9b82175ccee143309832df8f9c3f62c193660253679284624e38196733fb2af733488ab1a556e", - "0x82338e3ed162274d41a1783f44ae53329610134e6c62565353fbcc81131e88ce9f8a729d01e59e6d73695a378315111b", - "0xaa5ddcabf580fd43b6b0c3c8be45ffd26c9de8fa8d4546bb92d34f05469642b92a237d0806a1ad354f3046a4fcf14a92", - "0xb308d2c292052a8e17862c52710140ffafa0b3dbedd6a1b6334934b059fe03e49883529d6baf8b361c6e67b3fbf70100", - "0x96d870a15c833dddd8545b695139733d4a4c07d6206771a1524500c12607048731c49ec4ac26f5acc92dd9b974b2172c", - "0x8e99ee9ed51956d05faaf5038bffd48a2957917a76d9974a78df6c1ff3c5423c5d346778f55de07098b578ad623a390e", - "0xa19052d0b4b89b26172c292bbf6fd73e7486e7fd3a63c7a501bbd5cf7244e8e8ce3c1113624086b7cdf1a7693fdad8b5", - "0x958957caf99dc4bb6d3c0bc4821be10e3a816bd0ba18094603b56d9d2d1383ccc3ee8bc36d2d0aea90c8a119d4457eb4", - "0x8482589af6c3fc4aa0a07db201d8c0d750dd21ae5446ff7a2f44decf5bff50965fd6338745d179c67ea54095ecd3add4", - "0x8a088cc12cf618761eaa93da12c9158b050c86f10cd9f865b451c69e076c7e5b5a023e2f91c2e1eed2b40746ca06a643", - "0x85e81101590597d7671f606bd1d7d6220c80d3c62e9f20423e734482c94547714a6ac0307e86847cce91de46503c6a8a", - "0xb1bd39b481fc452d9abf0fcb73b48c501aaae1414c1c073499e079f719c4e034da1118da4ff5e0ce1c5a71d8af3f4279", - "0x942ae5f64ac7a5353e1deb2213f68aa39daa16bff63eb5c69fc8d9260e59178c0452227b982005f720a3c858542246c8", - "0x99fea18230e39df925f98e26ff03ab959cae7044d773de84647d105dfa75fd602b4f519c8e9d9f226ec0e0de0140e168", - "0x97b9841af4efd2bfd56b9e7cd2275bc1b4ff5606728f1f2b6e24630dbe44bc96f4f2132f7103bca6c37057fc792aeaab", - "0x94cdad044a6ab29e646ed30022c6f9a30d259f38043afcea0feceef0edc5f45297770a30718cbfec5ae7d6137f55fe08", - "0xa533a5efa74e67e429b736bb60f2ccab74d3919214351fe01f40a191e3ec321c61f54dd236f2d606c623ad556d9a8b63", - "0xb7bd0bb72cd537660e081f420545f50a6751bb4dd25fde25e8218cab2885dd81ffe3b888d608a396dfcb78d75ba03f3f", - "0xb1479e7aa34594ec8a45a97611d377206597149ece991a8cef1399738e99c3fa124a40396a356ab2ea135550a9f6a89f", - "0xb75570fc94b491aef11f70ef82aeb00b351c17d216770f9f3bd87f3b5ac90893d70f319b8e0d2450dc8e21b57e26df94", - "0xa5e3f3ab112530fe5c3b41167f7db5708e65479b765b941ce137d647adb4f03781f7821bb4de80c5dc282c6d2680a13d", - "0xb9b9c81b4cac7aca7e7c7baac2369d763dd9846c9821536d7467b1a7ec2e2a87b22637ab8bbeddb61879a64d111aa345", - "0xb1e3ee2c4dd03a60b2991d116c372de18f18fe279f712829b61c904103a2bd66202083925bc816d07884982e52a03212", - "0xa13f0593791dbbd360b4f34af42d5cc275816a8db4b82503fe7c2ff6acc22ae4bd9581a1c8c236f682d5c4c02cc274cc", - "0x86ba8238d3ed490abcc3f9ecc541305876315fb71bca8aaf87538012daab019992753bf1e10f8670e33bff0d36db0bf0", - "0xb65fbb89fafb0e2a66fe547a60246d00b98fe2cb65db4922d9cef6668de7b2f4bb6c25970f1e112df06b4d1d953d3f34", - "0xabb2d413e6f9e3c5f582e6020f879104473a829380b96a28123eb2bdd41a7a195f769b6ac70b35ba52a9fee9d6a289c3", - "0x88ec764573e501c9d69098a11ea1ad20cdc171362f76eb215129cfcca43460140741ea06cee65a1f21b708afb6f9d5b0", - "0xa7aaec27246a3337911b0201f4c5b746e45780598004dac15d9d15e5682b4c688158adffdef7179abb654f686e4c6adc", - "0xa1128589258f1fbfa33341604c3cb07f2a30c651086f90dce63ae48b4f01782e27c3829de5102f847cde140374567c58", - "0xaaf2b149c1ca9352c94cc201125452b1ed7ca7c361ed022d626899426cb2d4cc915d76c58fa58b3ad4a6284a9ae1bc45", - "0xaaf5c71b18b27cd8fe1a9028027f2293f0753d400481655c0d88b081f150d0292fb9bd3e6acabb343a6afb4afdb103b5", - "0x947c0257d1fb29ecc26c4dc5eab977ebb47d698b48f9357ce8ff2d2ed461c5725228cc354a285d2331a60d20de09ff67", - "0xb73e996fa30f581699052ed06054c474ebdf3ae662c4dc6f889e827b8b6263df67aeff7f2c7f2919df319a99bdfdceb1", - "0xb696355d3f742dd1bf5f6fbb8eee234e74653131278861bf5a76db85768f0988a73084e1ae03c2100644a1fa86a49688", - "0xb0abca296a8898ac5897f61c50402bd96b59a7932de61b6e3c073d880d39fc8e109998c9dba666b774415edddcff1997", - "0xb7abe07643a82a7cb409ee4177616e4f91ec1cf733699bf24dec90da0617fe3b52622edec6e12f54897c4b288278e4f3", - "0x8a3fae76993edbc81d7b47f049279f4dd5c408133436605d934dee0eadde187d03e6483409713db122a2a412cd631647", - "0x82eb8e48becfdf06b2d1b93bf072c35df210cf64ed6086267033ad219bf130c55ee60718f28a0e1cad7bc0a39d940260", - "0xa88f783e32944a82ea1ea4206e52c4bcf9962b4232e3c3b45bd72932ee1082527bf80864ce82497e5a8e40f2a60962d0", - "0x830cf6b1e99430ae93a3f26fbfb92c741c895b017924dcd9e418c3dc4a5b21105850a8dd2536fa052667e508b90738f2", - "0x990dce4c2c6f44bb6870328fba6aa2a26b0b8b2d57bfb24acf398b1edc0f3790665275f650884bd438d5403973469fa2", - "0xa2e5b6232d81c94bcb7fed782e2d00ff70fc86a3abddbe4332cb0544b4e109ae9639a180ae4c1f416752ed668d918420", - "0xb4cdf7c2b3753c8d96d92eb3d5fa984fef5d346a76dc5016552069e3f110356b82e9585b9c2f5313c76ffaecef3d6fd8", - "0x83b23b87f91d8d602bff3a4aa1ead39fcc04b26cf113a9da6d2bd08ba7ea827f10b69a699c16911605b0126a9132140f", - "0x8aae7a2d9daa8a2b14f9168fe82933b35587a3e9ebf0f9c37bf1f8aa015f18fb116b7fba85a25c0b5e9f4b91ba1d350b", - "0x80d1163675145cc1fab9203d5581e4cd2bed26ad49f077a7927dec88814e0bed7912e6bbe6507613b8e393d5ee3be9be", - "0x93ddeb77b6a4c62f69b11cf36646ed089dcaa491590450456a525faf5659d810323b3effa0b908000887c20ac6b12c80", - "0x9406360a2b105c44c45ba440055e40da5c41f64057e6b35a3786526869b853472e615e6beb957b62698a2e8a93608e13", - "0x93bfc435ab9183d11e9ad17dac977a5b7e518db720e79a99072ce7e1b8fcb13a738806f414df5a3caa3e0b8a6ce38625", - "0x8a12402c2509053500e8456d8b77470f1bbb9785dd7995ebbbe32fd7171406c7ce7bd89a96d0f41dbc6194e8f7442f42", - "0xaab901e35bf17e6422722c52a9da8b7062d065169bf446ef0cbf8d68167a8b92dab57320c1470fee1f4fc6100269c6e2", - "0x8cad277d9e2ba086378190d33f1116ba40071d2cb78d41012ec605c23f13009e187d094d785012b9c55038ec96324001", - "0x85511c72e2894e75075436a163418279f660c417e1d7792edce5f95f2a52024d1b5677e2e150bf4339ad064f70420c60", - "0x85549ca8dcbe49d16d4b3e2b8a30495f16c0de35711978ada1e2d88ad28e80872fca3fb02deb951b8bcb01b6555492e4", - "0x8d379ab35194fe5edf98045a088db240a643509ddc2794c9900aa6b50535476daa92fd2b0a3d3d638c2069e535cd783b", - "0xb45cfebe529556b110392cb64059f4eb4d88aaf10f1000fdd986f7f140fdd878ce529c3c69dfd2c9d06f7b1e426e38f3", - "0xac009efd11f0c4cdd07dd4283a8181420a2ba6a4155b32c2fed6b9f913d98e057d0f5f85e6af82efc19eb4e2a97a82df", - "0xb2c2cdffa82f614e9cb5769b7c33c7d555e264e604e9b6138e19bcfc49284721180b0781ecbf321d7e60259174da9c3c", - "0x95789960f848797abbe1c66ef05d01d920228ca1f698130c7b1e6ca73bfda82cee672d30a9787688620554e8886554ee", - "0x98444018fa01b7273d3370eeb01adc8db902d5a69b9afc0aa9eadfeb43c4356863f19078d3c0d74e80f06ecf5a5223f4", - "0x87d20b058050542f497c6645de59b8310f6eeec53acbc084e38b85414c3ea3016da3da690853498bde1c14de1db6f391", - "0xa5c12b3a40e54bee82a315c503c1ce431309a862458030dde02376745ec1d6b9c1dbeea481ae6883425e9dae608e444e", - "0xb9daa3bf33f0a2979785067dcece83250e7bf6deb75bb1dbbab4af9e95ddfb3d38c288cbef3f80519a8916a77a43b56c", - "0xb682ec3118f71bde6c08f06ea53378ea404f8a1c4c273dd08989f2df39d6634f6463be1d172ac0e06f0fa19ac4a62366", - "0xa4f94fd51ecf9d2065177593970854d3dce745eebb2a6d49c573cbf64a586ae949ddfa60466aaef0c0afb22bd92e0b57", - "0x86cd5609efd570c51adbc606c1c63759c5f4f025fcbefab6bc3045b6ad2423628c68f5931ff56fdda985168ce993cc24", - "0x981192e31e62e45572f933e86cdd5b1d28b1790b255c491c79bd9bb4964359b0e5f94f2ae0e00ef7fe7891b5c3904932", - "0x9898f52b57472ebc7053f7bf7ab6695ce8df6213fc7f2d6f6ea68b5baad86ec1371a29304cae1baadf15083296958d27", - "0xb676c4a8a791ae00a2405a0c88b9544878749a7235d3a5a9f53a3f822e0c5c1b147a7f3f0fc228049dc46e87aa6b6368", - "0x9976e10beff544e5c1645c81a807739eff90449df58ffdd8d1aa45dd50b4c62f9370538b9855a00dd596480f38ebe7a5", - "0xa0e91404894187ec23c16d39d647ada912a2c4febfd050a1ea433c4bfdc1568b4e97a78a89ba643aca3e2782033c3c58", - "0x91a6ea9a80476ed137eb81558ff1d55b8581663cccd41db4fc286876226b6515fd38661557419e1e46b6a3bc9cda3741", - "0xb9e8a1e23c60335a37a16f8085f80178a17d5e055d87ffe8cf63c532af923e5a5a2d76cf078164fb577996683796caa6", - "0xad8e151d87a37e8df438d0a6a7c02c3f511143efb93fde8aef334d218cb25932baf9e97c2f36c633620a024a5626af3d", - "0x978f942f210e8a482015e6fdc35a4c967c67b66e6e2a17a05cc7a0f2163aed227b775d4352b0c3cca6cbf4bd5bafaf75", - "0xb5e2e3d8b2e871c07f5899e108e133f87479959b80cb8a103fbecde00ccdbfbd997540eef33079c5cc14b1c00c009fd1", - "0x88a164b3fefd36857f429ab10002243b053f5d386466dbb9e5135ed3c72dd369a5a25e5e2aaa11f25488535e044e2f12", - "0xa66091c0db4e7cf05a089ec2b9ff74744354d0196968201f5e201699144b52bb13b4e68e12502727163e6db96e3565f2", - "0x8e65aff8e37240461b7374c20bfd1d58b73a525c28994a98f723daed9486130b3189f8efe5c5efcd7f5390cc366038da", - "0x8b37c21dd7304c3aa366959ba8c77ea8b22164a67e136808b6f8e48604297f7429a6c6ecf67b1d09b8b7ec083eacd7e0", - "0xb689b1277ad050f53da91a702516a06d7406ff33a4714ea859b3b2b69f8d0aa8f983c7e039b19c0759a3815d841fa409", - "0xb17f7a0a182ed4937f88489e4c4e6163dcf49fd2ea4d9efbba8126c743bea951cd769752acd02e921774dc8ebcfae33b", - "0x8b7fab4f90be825ac5d782a438e55c0a86be1c314a5dbc3cc6ed60760a8a94ef296391f1f6363652200cce4c188dae67", - "0xab8410c4eaa2bb43b0dd271aa2836061bc95cb600b0be331dada76ddb46711ff7a4ad8c466cc1078b9f9131f0dc9d879", - "0x9194bd7b3cc218624459d51c4d6dbc13da5d3de313448f8175650fa4cfab7cc4afcda5427b6676c3c13897dc638b401e", - "0x980f61a0f01349acd8fc9fdc88fc2c5813610c07eecb6ab14af0845a980792a60dadf13bb4437b0169ae3eff8f5984ce", - "0xb783bee24acea9c99d16434195c6940cf01fc2db135e21f16acae45a509eca3af6b9232a8aa3a86f9715c5f6a85cb1c3", - "0xa3079931c4b90966d1faa948db847741878b5828bc60325f5ebe554dcab4adcc19ee8bce645e48a8f4a9413bb3c6a093", - "0x801f61ac9318f6e033a99071a46ae06ed249394638c19720831fff850226363a4ae8486dd00967746298ee9f1d65462f", - "0xb34dbbed4f3bb91f28285c40f64ce60c691737cc2b2d2be5c7d0210611cd58341bb5bda51bb642d3ee2d80882e642a13", - "0x8750af19abfb915e63c81542b13d84526a0c809179bbcc1cd8a52b29f3aba3ae0f7cf6f4f01790bf64ef7db01d8ee887", - "0xa6ea10000eb2dd4efc242ac95bc3b3873cdd882fbeb7c9538c87e3143a263ca3a2e192b2159316a625cfb5fb0b6cdcb3", - "0xaa40ca54bc758a6c64cb932924917581062e088b3ad43976b28f2e11d8a7dea73f1fb50aeaa0e70182bb2dc07d805bb9", - "0xa4779dfd25b5ec9d75dfb54a4bb030364899a5e75c1492403acb19f2adc782c7ac4daeb66d2f5aeb74135afe9f318e3f", - "0xb4551e2805d63ca453f4f38b1921ac87ff687e1d70575ad38f3469d6f0608ef76b7b1b98ae1e6b1e7d928773aaab6e3b", - "0x99490ee722f96aad2743b08dd37bfeb75a8c59efaee4c9b694eaa05eb8a6bb23861a4480544c7617d04d23fd5e2543b4", - "0x8a7050d964d295fff98ae30d77ce730a055719313457e773fcce94c4d71a9b7cf63db67e54a8aab20fb1335b0130b5d5", - "0x903144e6bbee0a4fec17ff80fef0d2103981140c3d41776cfb184ced17f480a687dd093f6b538584327e6142812e3cd5", - "0xa5b30f7c6939bdc24a84ae784add927fec798b5a5ee3dd156c652df020728dd6d43898be364cf5ee181725fbcffc0964", - "0xb43d97ec2bc66af92d921a5c5c20a03ef2be2bc2c9b345f46d8287409fcbfd88ebc49d4509d64468222cd1d2021bf236", - "0x82dc23c7f5086c9ac6b4566359bfb830d203544b0d8332a210775670f899cd9ff48b94bfeba40040c25664ebdd5cfad8", - "0x9294cd017fea581dabb73dcc8c619904d7e022b664b0a8502c9d30f3807668af279948e7e41030ae296d492225297e95", - "0x8d6c9dc636c8e884f9a4299e5cff06d044ebc94ad783a4b71788347ea4a336d4d048b8a9ecabae789e8fcdc459723dfb", - "0x801a80bc49e882ec81b04e37407713f033f7bdac79252dfa3dc8c5bd0229fcbd4019890e402cf843b9378df08f72ab84", - "0xb4313ca32569d973900f6196363c0b280ddfa1b47c88d019e5f399b805b444a777950fc21ae198fc23ece52674b94abf", - "0x96f06056fd255fdabf78986e315e7c4fdf5495cf850536b7976baa97a994cc6a99c34609c33a0f2facba5e6f1026dce6", - "0x983ed80220a5545ffd70ef5e6ac10217d82ec9cd8f9a27ee77a5ff4074092308c0e6396fc4e9932a77ddd474e61f8b55", - "0x872a059aa630af73c4abbd076e8b333a973ffc5bdecf5dcc0600b00162184213cb19d4f601795030033beb808d5810ce", - "0xb040f318d9d3b8833da854014a44296dbd6762dd17cab13f91987256c54353b7f0800547cb645a7cc231997454209fdd", - "0xa8c4731a555308e8ce0b8325eb7a4cbf6113d07e9f41932df04480b72628d313b941c7055f1cc2ac45c7353b56e96ca9", - "0x8c24031440b77637e045a52e5ea3f488926ab0b426148975edf066c40a4581beecc1bfb18fc4cf5f9f96dc6681b4bd28", - "0xb39254b475abf342f301298feaa17a4b3051f30ea23a18acf59e003e2704ac96fe40691f1da387913bdf7aee6389f9a8", - "0xa1dbf938b604ccc6d60881cc71f38df568aa02752aa44d123514154017503f6c1c335ae43e359f1487bc8934073cd9c1", - "0x8d52aa1be9f429ece0580498d8fe9fef46d4a11f49436a82b8927f9503dacc41245907f126594c1cd30701286f8c092c", - "0xb826f396486942c0326d16f30a01b00a682c30a75553dc6ac34fd5b3e96b13c33b94738f522eebaffb59ff8c571c76e9", - "0xaa89f51cbf6e6c3e2aa2806187b69ab3361c84e89f393f3ed284fe84db46fc3944aa44f8928e3964f9c1a1ec27048f68", - "0xa254df0efa4203fb92b42a1cd81ca955922e14bf408262c8f7cb7dc703da0ca2c71556bd2d05b22ce9a90ad77309833d", - "0x93263c507e4d5f4e5df88e85b3d85c46ea729fb542a718b196333e2d9fb8a2e62dc1347cf146466a54ba12d200ef09d9", - "0x922e3c4a84246d89a07aa3e90f02e04b2cea9bebc0e68b742156f702aed31b28c6dfa7ac936ea2fc2e029adf68361f98", - "0x9a00628eeeda4ccbed3ef7834149aec4c77aac1a14bc2491ba5d1a4a2c5d29afb82ceaa5aac1c5ce1e42cdcaf53e30ba", - "0xab3a88df36d703920f6648a295a70ffa5316c96044f39ff132937bfda768937cb6a479e9ba4a4e66b377f3a9996a88c4", - "0x966b11526ab099d550ab33c6a9667e5cfdedf255da17a80a519d09acd78d2ea24ec18bd1ea7d8d63cf0a408f1c1fe0b3", - "0xb5c21b9817dc32f3df9d9988aa3560e1e840d586d01cd596bc0f850ab416b6013cbf7dbfd05ac981f26014c74bd2d2b2", - "0x9040abef5e2523e7f139c9f744a64b98fea3a57952059ffe4d5ed77fa87068203c090ef4e7f52c88fb82ea8a6fdca33e", - "0xa0dcdaeb7d3f5d30d49c004c5f478818c470187f4b0b4856812dcd1b3a86de58a99acb8ceb44c6b80c3060cf967c43a4", - "0xb5f4be9a69e4a6719ea91104820df8623b6d1073e8ee4168de10a7e49c8babea772bcbc6b0908185e98d607e49cd3609", - "0x8634020a5a78650015763c06121c606d2dd7b324aa17387910513dd6480fb797df541fc15b70d269b2794ad190595084", - "0x9504d1d0fb31ff1926c89040c04d51fd1f5cddf9d7ca3d036e7fd17e7a0f767ef33cee1d8bf7e17e2bc40949e7630417", - "0x812c72846ef6d692cf11d8f8c3de8fa78cc287303315114492667b19c702cd24d462020f1276895df26e937c38f361f8", - "0x8c97aa5e9ef2aa9a1435ef9ddfe62e850f0360864ed5fb82bf9fef4ef04d8fb4f827dc078bc911ee275e4501edd6617c", - "0xac5f7af5e23c8e429aaa6b6825129922b59d25b4608f07b65f21388a9ac3aa89096712f320afe6d56e44e1f0d51a4eb9", - "0xa8c84d9a8593a0cb5be1e450960f59878a4e6b70da54a7613dfc25911b7cc9e6d789d39401b0a0d6471ab9dcdc707976", - "0x8c9d5fd89611392c0f085ffa4fa642a181f0b9b23593deb5e10fdd1642722ca75ef34a037e88a8d03f2888fe7461f27c", - "0x8c74b05f91fb95c85e7bd41f6d9a1e41e667e68f3d19b325c1f25df1767019919edab89b92af237896cbc4e6d6dc1854", - "0xa3caecb91640821f0b2c4981b23f2069df8d2b98ce026c1538bc096b292f5f956a5d52c1c8d6a8165a1608083ba6494b", - "0x8ae8e0c36f8b79a69176ff29855df45d0fcd9e4d1dbaed8899f8fcdece676e418ec034a6c161e2a894f0c834aaecbfd1", - "0xb88d18c67dc3b1b6ed60ee437c441c1ed14ecddebccf43683605716f30058b1aa4ba05ff10cd8171ee97d8f58d70c094", - "0x94f43d84dcdfd9cd19115c7d8e9c1e856828eafbfdec93b876cf0007e317e30b2ad951dbabc186aa6ef90fdee4d91990", - "0xb44e4723f41fc1d5b0057f371e3381ae02566590b3f964b6eb07b2104f66ff78410c407235fa98d04f635694f3baca09", - "0xaddd8390173d29ca0811534d389253831fed75fed135398617836b6e70767269eacb1560b39a58f02042ca3b97fe59c4", - "0x80bdbdacc0c358c7ea52aeacdc5f9ceb6928bcf6e7dee7c17d8ae3bf7c2372aa7a0372363888968fc0921aaf4776d5d0", - "0xa486e2b6f04f403f9e609d69dfb3cfb992af56ecad1683271df3e3faa3b86638b81e73b39978fb829ee7133d72901f2d", - "0xa19472da57457e10c6a6307895393ddaec8f523760d66937fe26a025817319e234eaf69756ffdf1b84c81733424a96d7", - "0xad6a195397cbc2d75171f5e82090441eed60bd1ba42c39ef565b8b5a8281b04400678625b1dc46d617f694a7652a8e5d", - "0x8f98e721c06cec432e2221f2e1b06bb1469d916a8d88d6973acf68d1e003441d00390dafcead8ecdbf9eae4509baf5aa", - "0x91d62a0f9d13c59adfe1376ed6d057eae244d13c6b3d99be49a49e0075cf20f4085cf127774644ac93615be9ac9e5db6", - "0xaf45dec199245e2b326a0d79c4899ed44b1c0219db42602a4a6184ace0ff831a3276297af28f92e8b008ba412318e33e", - "0x8754bde54e8d2d169e6a7d6f0eae6097bc0461c395192bd00dd6f105677ea56ab384c02553ea5eeac0a65adcb0df77ee", - "0xb676afd2f5afc37a314c943d496e31b4885efcbcc2061036e370a74cfde5642bb035622d78d693bfc3136fc036c7edb4", - "0xaab6ffe6cc234397cf1822e02912bc282dfb314e92fb5a9e10d0c34ee9b5856d4b76e166bc2bb6fcdd66aabea35ec4ef", - "0xada6e62f90ee6b852ec4b72b22367acac2896f0df2c105beda27096583ddbedddc710d171330569f111c6e44a5b57ae7", - "0x802139dd15241a6de663d9b810121bdd9cf11f7f8c8ca6de63f4f8e731409e40d1fd3558b4f619ed42ee54929dff1c7e", - "0xad8e70531cec21b4e6f55be1751c2d025bd2d7d8158269b054cfe57fa29252d052ce4478ec7db6ec705789e2118d63b3", - "0xa8e4a4271769480e1b33a28c87a150ecc0b48bfe8a15ae04152197881de4ce4b03453aefe574842424edbbe4173e1a3a", - "0xb98c65726296610cef16c5b58da5491acd33bd5c5c5af4d934a9840649ef85730fbce8018dee09ded14e278009ed094a", - "0x8e213a7861223287b860f040e5caaa563daa0b681e4e09ec79ad00cc459238e70bbeaf7486bbe182fc12650700034ec5", - "0xa2879f9e1a556cf89b9b5b3bd8646a8cce6b60bcbc8095df44637f66a2da5858eee2dc9091475a8f64bb5aff849389cd", - "0x8a17cdb4077b9b0bcf28b93294ac5ae4c8bba8839fce0f1012b53187ac008f9858b02925fbfc421f1123afcdbd8b7753", - "0x86fd9c11528aa43946e4415ff64a3ca6409ee6f807368c68997b18605da65e415ccd85ad913820d450cb386593de666d", - "0x8ed55923b963c3d85a91aca11c40ff9c6c7f1e2b9bc199d1a270e5fb16aa62dec0136e97866145ae9d58a493e8b1cbbb", - "0xae32af5b5d418668ae123c639b149e5eed602404e8516da4a61db944b537a3620545e8e3d38cf10cdaea980ab2f80973", - "0x95cb8d9e9d6762d78dde0ad73869ffaca904a7d763a378b8cc11a7933d3e7d1c8aec4271a079b1b00f8887ee5b1ea21f", - "0xb5ea20b42a3ca247f00ab5328c05f0cf194973d5f7271c66c41c5055b1ffdca136be179709e0c1de209fbe07b9820bf3", - "0x98682f7cce471c92a8d6d15fee4ddf4d43dd97c3e3811d2913618ecacc6440b737717c07736ae4558c910e11ee98104e", - "0xa67da2c7cbba48e929ca4e4b9a6299fe01ef79eff8cc5cd3fdbdc0721a68130e4079f30ae151a573a7dcca8ecf2e684e", - "0xa9981c9f9dcbb3b0f6996f664fb2acd7573189f203be37b2b714662aa273551396abfb1f612ccde4e4c8127a050dbe4b", - "0x92d55eff8da600f886da9bf68e8eecf482faa4b268f3f286b3b3e5cc91b19604081498d4905b201bb4ec68e32b5591d9", - "0x963e3f1728de9d719c86d390f3eb9c3f99d1928347fab0abf10dbb37d76b59ddb64d4734c977863a6cd03ffece5ca895", - "0x93480e2de83c921056b6d8628ac37cd5ef7555ba43b0308fc13386cb0515d42c12ecd06057137aa71a7931beaf90b9ce", - "0x8feae57ff0e6a162cc81c99f45c6187d268fc0bee8c2bffc92142ef76c253d201f0e932943cf2fa312982b281ce1066b", - "0x8f8f4bd4200fb87afcd743274480220d77571928000d4197410dbb75439d368df6a06d941a6152206371d2ca9cac99e4", - "0x8ee7f11e79af4478e0a70eb424fe8078237ad99ba6d7e6bf1a8d5e44e40abd22d404bd39b718ad6fdf4c6601f2a47665", - "0xa98acfcec612b574943195b9ba95bebcc9c0b945c9f6b3e8760b2a4635909246a9d73b0b095c27b4ecb3339704e389b7", - "0xb520efd19f65e81dc285031ea3593f8c5dad793e4426beb9196ab46e45346f265fd71e50adb0da657977c60ed5724128", - "0xa3d9d0b7415280ce4dfa2429d47b2b8e37604a5157280a72cc81d541ffe44612dbb3ef7d03693fc42a569169d5842dc3", - "0x8c29e2d0b33801f6d9a9c065a76c5cad1fb0a001506b970307e21765ee97c732a4cbf1d7c1b72d95e0ad340b3b075224", - "0x839e21f292892a6eb596b9b1e9c4bd7c22a6fe71d3d04487c77840028d48392c5cbe73140a4e742338e0c8475cd0c1ad", - "0x8bea5c68e7743998619185bb662e958f1b4d3ca81019d84ac43c88911aab3abe4ee9bcc73cb95aa3ae87c0138801bde3", - "0xb8f262d21a94604049e008ce03dc857848168e1efca4522acb0ccc827ffb37f545e1947843a356563a76bc6489605b66", - "0xa7bd0842b0bb38d9943b82aa883f36f4eb8a6e8a7790d4f87faf306608f51d250a19b73984f1156cef5dd2581664614b", - "0xa993e649bd953627a88a2539dac3a12ec7f37a4c65b01425d9d34edf7ee10a71aa98f65c9e013107f824faf8aee041a9", - "0x8e07eced75c67cb4d2ec01857f6ac1408482e6b31cb2faa249e8cf99f180575587df530c7782a7539b5221121ef48aa0", - "0xb2f4578f26c05ecb9e2669ca744eb19d4f737321ac7d04fafd18beb7866e0fec9dd063953ae1f077b44b9c6f54db1279", - "0xb6b3788a6c7bcaf467d19daf6ab884d549aa866970c05a9181f544ff190d043192c84fe437a75a30b78b425461cca062", - "0xa270684903c61544b85a7041e81f65e787e1c1e23e57538fa8a69836bed0ca1673861dd29f743a1280f2f38eddd3aa83", - "0xa9c2397c4773dcad2821266dadfd2401d013d9f35de6744f2ec201f3507700adb1e6ec4f5a453be4764da8bf68543f26", - "0x83a3025ed6fd5df9d98be32a74e10a0d9728b560942d33ba028536fb148fc34ae87e92be2df3e420a8dfec08da495982", - "0x90dc70c183a90bab988b4a85b7b921c8070af0e5f220364fe11afa0722990b2c971e1e98eef62d3287fedfd9411f1df7", - "0x82d940937a6c636224d04f8e2536f93dcf20dc97a5f188875ad76c21b804aef9af10839419b61143c1f88a695959a6b4", - "0x8017f9473ce49d498d6f168137e77e62fe553e5a51e75b519cf2cbd1ab9afdafad80fd5e6fd0860e640b0d78ca8ed947", - "0x80573a0ec049fe1f7b3013b2839e145cd87e07c0e43826a29ef8c92516f9a30896c2ffcf3ed77ed22a6cf3101b1789d5", - "0x953349abd2559f9824db07cec857ad54f1a05018f3076425f8dbae37f8d92a46af2c04ab7c8ec0250449541187696e98", - "0xab7bd2c4f05ee9a9f252c4e16a20993a12c535c3809d124bae24642616521a9768d3f19eceaf8524583f47ae1f527684", - "0x9883b77ee834ee0112ca2f366d2a6fc213e0cf454e061438c2901a5ba35b7378f64da8adf6a476eb1562991ef5b4a5bc", - "0x89291811db308637356dbf7ed22cf07bfce33eb977734ee346e8c15a231b35d8b4443574f3fa97a40867b3e23b0bbfa4", - "0x93d753849d7d9588d39e38217500b123a6b628a873876612d9f98b5d611f52c89c573432d2176752b5d1cc2d94899b8b", - "0xa45add3c4844db3b7a237295fc85fddc788ac1ec395a0524d2fc90a539571a247146aea4aa10eec30a95e9617c85b98d", - "0x90f94578842db7a4de672da1e483858ece5e466c73c12f725a0fc71f42ff880c9447a33fa9096839bee817536f2591e2", - "0xb2c1b6fb031bb30460f157356562b44b4de096a0a112eab4fb3cc500aad38bc770da1fc2e73caf687a0da5e8537049c0", - "0xafb15e15fd930929c0e3c66482068a5afe0c7b7f82e216a76c5eb1113625bfa0b045a52259d472284cfbaf4796c71456", - "0xad222a9a3d907713418c151b8793d5e37634354322068f8206b9d0da1a3f53b0004193713d23ec35990639a1b6c2e075", - "0xb44a128dce97e8c4b178cdbca0a5c1b3f6e164490fac0fd68dbfe0aafa89920bb4ea420a8527e06c80dd19c2f135e3ef", - "0x8596e993ef18b8d94e9c42a90cb7060affc586b8e9b526820d25124285de5590134e2e86592e9dc4dd45ccf5d578fa60", - "0xb71bb0ad138141ed506b2253e84110d2db97cc2d24a3fd0d096b0022d9f38f87aa74e2f505074632d64e90bcc491aa30", - "0x84841eafd357309de47b92ca5ec163dec094a2e5271bc65898c31932e0160bee165e4decb23af339cfe09c83e1cc5441", - "0x8a2915ee39a6fd4a240b98533d7690ef1773ce578ed1fb05ed414ebe36f7ef289fa46f41768df57190438c356331e329", - "0x90bb337165386f1990cbd8ed2e8321ef21bc18125b015b4da0c37e5fcc446b26005379ee4fad8ce9348ceb4ab49e82e2", - "0xb707b50ea2ab05c6d183671587f25fe29eef23fe569d731459a1ac111a0b83a2cd65b88242876b34aeead3b05a15d745", - "0xae1f159f79b7996315c4f9acce7e21a6ed59d4ef76331196fc86911fda3035edd5c11d568b105175a36c948d0263b382", - "0x922bc525bace05e5dff6b5cabde5469ddd2c1c601f7131abc04ecefdd35095e6ac015b1aec3c3b25c5dee8d139baf60d", - "0xa7b060405b2740f82db64683187b1bb89e5f40c8438663c7cbc8ef2513929fe5f92625667a7f2f599a72a96b1fc8f08a", - "0xb9dfe94a08651db5efefbb813269bce80d814e3089b80c0654491e438d820bf521f8a4a4477909344ba88f7683eebb43", - "0x841817a9729465743576950b6e8eea32ebf39cca99ace86c4792f9f35926e2d6830c52854a3b2eaeb61694e6845008bd", - "0x934128034bde8fc7b93b952aa56e0ed28b36cfa04cfa1f0d5b38266dd40beedff5e0bab86e4717b0fb56c56be2eae26b", - "0xaee9d64caf28596308782cd8f3cf819506daf3378f86157ff775e618596411adf94efd0e9542787ca942066f02cbd332", - "0x85871184db314411a49575fee088c52ed5dba4e916ee001ec24d90898a0154d9790a06aa8a707ca7a8b986c0293b8d89", - "0x8d3d87edcc0187a099c97b581a598d357a41ac152303bb27c849eb78e72e15cb97cf9a0468fc36f245c3e152c76bb7dd", - "0x900475d165dec18b99eb7b5f9e9ad1d2d4f632e55fdcc4c5ecd7775fed462990e6aaafe9c669f40508f9b15f00bda31f", - "0xa25b5954edd57e7811a0d18532043d975c7b44b80f65cd630935d7b16ada05f30fe2b7be7ae8a2f54c25957faf3f1950", - "0xa089019afa3a7a15f7e7874e73b6773c0a824e6d3379b4c928e173321fb165ad979a6be004d394c28d19d410b2655d3e", - "0xb28f46797dee0c538bd3de815df641a0ef718ad3e52b2764aec380d6905b38b50ad6f60d0f68e096ca39960ba7734355", - "0xb0ac155d3d05851b04104e6b459f1a68e9e155437c92421a7c0e4dd511ef89cf71dfa3cc920769492ee283a65ebf029e", - "0x813c69a810745580d43d5b5480f0ba81000fbef0071e6b655c7346bef5ed774e9214a7816d40eb1774a5bd033767a046", - "0xb176345ca75c64f10ec33daa0dcf1f282b66a862fcd3d8d66c913f9a02db4c9d283dadc02eff13aaab94bc932a42234e", - "0x92560f67e5b995db4a489bb86ee78b4aee0800143b3535ad557a53e9e08716bd0202d9f5714722c2a5e8310046e3f5b3", - "0x8adb427bad9cc15fc6c457a96a6750dda8c46d859c5f69bf0e7ab8fc0964430b33967fd47cf0675b6ba1757f91255e6e", - "0xb120f723b80389a025b2daa891b140b3d7b8d520ae2a6a313f6e3d365a217af73292dcb249dca1f414ec05e865e3cdc7", - "0xa61a5d261a8dfe5996c42ea0a5ae703a2adcfda80e86837074d868eee16f87d38da19596c48b55dbd7a7cbec1a9b4996", - "0x99dc921eacc6bb867c5825ad4c83bc4af9dd78a18b3d0e1a60ad493e3805b8fb9b7922b577da1adb3d805edfc128d51d", - "0x85455fa165a07282aaab4a5bfb88027f47b9532e4af8195c048515f88b0db7e80f42e7a385fd4944faaa7f2a6544ad17", - "0x96dff2d1c8a879d443fe576d46bcceaf5f4551d2e8aad9c1a30883637c91090de99ad5eec228eb5febf93911502d3cbb", - "0xa87eb7f439377fb26c6bfe779701f4aea78dd7980b452a386afec62905e75217a1996c5234853432a62ef8bab21c31c3", - "0xb598278293823e9ccb638232a799211173b906444376337fdf044d0227d28fcc4c5867e6ecb3200e59ca0b139e71cac9", - "0xaa6fe147edc95027654d68140f428ec53cede3552c5f49c09d18bc6f6ae8c739a63042eb7291d14d717a4e1f0778abcb", - "0xae8ee18913d328b2fba71efe65526d3ee9c81beda53cf776baec4019ea30212010758cbb5dc85ed6620ce04b189f01f2", - "0xae9fb686777e88dffdd42805fe4114aa0da1b350d92a27ff3f8a817fb25af1fcfc9a06155affe0273bf13caad16a5351", - "0x95d372ba3a2ee38371538f34aae91b4844488e273f70c02f1992370f89fc2343eff95692d52ce9f21206abbee4959958", - "0xb15260376f0a34ca2827ff53acd7eaaef94c9acc2f244b36500423069cb1cdaa57ac8dd74adb5b53d0fd4265fcbb28ea", - "0xb0ffce6a8059537ef6affdbbc300547ef86e00109289239b0c6930456c562b4ed97f2e523963af17736dd71b46c44ac7", - "0xb5499a1277d34f9892f7579731ff53f423f2ffffa9ea43a6e929df8c525e301396249a2324818a6a03daa0e71fcd47b3", - "0x98dbfb8e97a377a25605a7665d4d53e66146204d8953afda661ae506858c5cd77ff7f21f5f10232e06dbc37378638948", - "0x84177e27e6da0e900c51f17077f5991e0e61bff00ca62c1623e627c5aea1b743f86eef6d55b13219a1947515150bade6", - "0xb50407bb5c61b057ab8935df94fd43ca04870015705b4f30ceac85c1035db0eb8293babc3d40e513b6fb6792ecbc27a9", - "0x988699a16917514e37f41ab5c24f4835ed8a2ca85d99972646fcc47c7e2a83c2816011144a8968a119657c4cda78d517", - "0x920c43fdcb738239ad542cb6504ab34498bce892311c781971d7db4dec70e288676de4d8697024b108cfa8757fa74035", - "0xaaa106329aac882e8d46b523f126a86d3cee2d888035ce65c0be4eaae3e92fd862f6ac2da458a835539cccafaba9e626", - "0x96e4c1562d14b7556f3d3e8a1b34ea4addc5a8170e1df541dc344728bcb74cd1630eb7ba4c70e9c68fd23c5c5d5a729b", - "0xa616ac5016d4e68e03074273cd3df9693ee0ce3458e8758b117a5c1bc6306dd2c7fad96b1bb37219c57ac62c78ad7a3e", - "0x8db7d9b20abfb1445babd484ae9e38ff9153ac8492230d7591e14e3fca7388a5ca6ef7d92ed445c8943cf5263e4a6ad7", - "0x88464134221aa7134878eb10928f31c8bd752ab68c27c9061c1de3f145c85731a4b76acdc7e939b399b6e497f9e6c136", - "0xa5f7c794f70b7c191c835dded21d442b6514bab5e4d19b56f630b6a2f1a84a1d69102d7a0dcca256aab5882d3f30f3ca", - "0xb96b6f98b6817b5fa6b1b1044e2411bdf08bf3ffaa9f38915d59e1d2b9bed8b3d645eee322ee611102ce308be19dbc15", - "0x92c26ade2e57257f498ac4ff0672d60b7ea26dad3eb39ed9a265162ccd205c36b882dba3689758c675f29e20836b62d9", - "0x8379a0299e75774930577071d258e89e471951642b98e5e664c148af584d80df4caa4bd370174dae258848c306f44be5", - "0xa0e53beda02bd82bf3d24bd1b65b656238128e734b6c7a65e3e45d3658d934f909c86ca4c3f2d19e0ac3c7aae58b342e", - "0x8ca5ceaeaf139188afd48f9bf034d8baf77bbf9669791c7e56ebf783394d7fcdf2a25fa4bdfcddfde649aa0dc67ccccd", - "0xa8060e6448844e9db4e9fb4da1c04bcf88fda4542def5d223f62c161490cf1408a85b7c484341929c0f9ce2a1d63e84b", - "0xaf6e1a5ecf50b754bb9eb2723096c9e9a8e82c29e9dcaa8856ab70074430534c5395534e1c0ed9ce98f4b84d4082fa67", - "0x81c8dbbef98f1b561e531683d5ae0f9b27b7f45dc6b2f6d61119ca0d559bf4ceb676d320afc5aba1811eeef7547a59d8", - "0x85b46cd64d605c7090a2faf1a2aadf22403b3692b3de1d83e38b2de0108d90ac56be35b0dca92c7a41c4b179a3567268", - "0x8dd3cc3062ddbe17fd962c2452c2968c73739608f007ad81fa1788931c0e0dda65032f344a12249d743852eb1a6d52a9", - "0x8630f1707aea9c90937b915f1f3d9d7ba6bda6d7fdef7a40877a40c1ee52471fd888f84c2b2c30b125451b2834f90d3b", - "0xb4a747e0bd4e1e0357861184dacec6714b2b7e4ee52fa227724369334cf54861d2f61724a4666dae249aa967d8e3972f", - "0xa72de682e6f9490b808d58f34a0d67f25db393c6941f9342a375de9ca560e4c5825c83797d7df6ed812b71a25e582fff", - "0x8d5ea7d5c01f1f41fffe282a334262cc4c31b5dcf31f42cc31d6c8e37c9bd2f1620a45519dab71e108fe21211c275b6c", - "0x8ccdc7e3642c2894acbf9367f3e99c85963cea46dc5473d175339a2391be57dd8815feacadec766e13645971213b9eb8", - "0x858e9b5fc8c13b651ff8eb92324bdda281db4cf39f7e7bd0472908b3e50b761fa06687f3d46f4047643029dc3e0ceeaa", - "0xae20d36c70cd754128c07cbc18dcb8d58b17d7e83416e84964b71ccff9701f63d93b2b44ec3fddc13bbe42ebdd66221e", - "0x860dbf7013da7709e24b491de198cb2fa2ffd49a392a7714ad2ab69a656ca23f6eafa90d6fdc2aa04a70f2c056af2703", - "0x8f809e5119429840cb464ed0a1428762ba5e177a16c92581679d7a63f59e510fdc651c6cc84d11e3f663834fcafeafdd", - "0x8d8a8dce82c3c8ea7d1cb771865c618d1e3da2348e5d216c4cbbd0ac541107e19b8f8c826220ca631d6f0a329215a8d6", - "0x86e3115c895ae965b819e9161511540445e887815502562930cedc040b162ecb1e8bdc1b6705f74d52bf3e927bc6b057", - "0xb9833b81a14115865ca48c9c6a3855f985228e04cbc285f59bf163dca5e966d69579ea4dba530b1e53f20bd4dccdc919", - "0xa71f5801838a6dbb162aa6f0be7beea56fadac1a4bcd8113a0a74ab14fc470a03775908c76822d64eb52a79b35530c05", - "0xa77ab73ae94b6d3378884f57eee400eff4a2969aa26e76281f577a61257347de704794761ea1465dd22a6cc6304fbc4a", - "0xacd1c5df3c487c04cf27f002e81f2348a0119349b3691012526a7b0d3bf911cdd3accbc9883112ed2ba852145e57fe68", - "0x8a28515a48832ac9eaf8a3fb3ad0829c46c944b4cb28acbcdbca1d0d4c3c623a36cda53a29291b8f2e0ea8ee056b1dee", - "0x846bafca11a7f45b674237359b2966b7bf5161916a18cf69f3ec42c855792d967d3bf3f3799b72d008766206bb7a1aa3", - "0xb24b341675b1db9a72c3405bbe4a95ccdfd18fa96f876ec946ccb5108f73e8816019998218a036b005ef9a458e75aeb3", - "0xb99c267b4a09193f3448bc8c323e91ef5b97e23aeff227033fe5f00e19bab5583f6e5fcb472ec84f12b13a54d5c0e286", - "0xa088aa478dbe45973b04ecafbcbd7ee85c9a77f594046545cdb83697a0c2b01b22b1af0b97dd75d387bb889e17f17aa7", - "0xa0c6b0cdff2d69964134a014e36c3709d9e63f6463c5cd7b01b6f0be673731b202d577539d89dd57a888326da1df95af", - "0xb4e6dc4ef11b2b41794ece70a8968e56705199d183366759568b6fa845d2cae127486e926b5b27ae9118bb21d1682c1d", - "0xa007804353f174098f02540a57e96227232444d5ae0a24232c244647148b6c049848cbd2b50d0a25af3ca9164bfff8ee", - "0x873fb034cc39c9cee553ece908fbf315f62efbc412b9afdde6a1889326b7f6f813e050b0601ba9921688e958cb75942e", - "0xb5676c90f0106c40d8683299e59d564f505ec990230cb076caef3ae33f2021e6aa5c9b27bb8fead05fc076df034c28f5", - "0xb5a67fc4c5539ad1ddf946a063110f824f7f08d2e4d30762c9d437748c96c9147a88efc22260573803ab545c18b108f2", - "0x817ff2b748a949973a91b69b0ec38efbd945aeb26a176d19f0fb76e261c7526c759e6f5516f9ed34de6eb1ac7838c9cb", - "0x99b76bda3526a5d841e059010fdb14eb2fa035a7d10463373a062a98c3c1a123e2da0848421dd7546d776438fd05e304", - "0xaa0d363270f90d56bbee7ea577b0c358532bda36d9247af6c57d000044a97ba41e35bb0db438f4c94551c6350e4e0674", - "0xacdae205d05f54b9544be96c9032350511895ccf413dbbc56d1f03053185df22a6d5b7ffcc3fbe96c3e2ce898ccfa73e", - "0xb091c220a1de18d384f50dd071dca4648ca4e708162c52a60e2cedc0188e77c54639f75bce9a468a64b2549119c07ded", - "0x878676133e5c700b1d4844564fa92a9930badb5293d882aa25ee6721a9f2cfab02088c31d62cf1342ae3edaea99a1ea0", - "0x9756d0793e6aba3b4dff48100bb49a5ec08ec733f966cb438379b91caf52fc2a5930830ec3f49aa15a02c82c1914dc7a", - "0x9722f760184d3b2d67cb2cea7fa41b1ff920a63446006bd98c6347c03d224d2d8328fa20ccd057690093d284b9a80360", - "0xb5a68489de4f253715a67f0879437bfe8f4dfc4e655ca344848980e6153b1d728acde028bb66fd626fa72eedd46ff683", - "0xa8cfc900b34835d9fd3add08044636f69614eff9ae929eac616c39bd760fd275ee89bf24b0f275dd77a66e54fd6b94e5", - "0x89967479bebf70b2893cad993bf7236a9efe4042d4408022fdbb47788fabedcec27d3bba99db778fcde41e43887e45af", - "0x889235938fcec60275c2cf0f19d73a44d03877d817b60bb26f4cbce09db0afae86d42d6847b21f07b650af9b9381fa82", - "0xb7fc321fa94557d8fbdd9fff55ab5c8788764614c1300d5ef1024290b2dbb9216bce15cb125da541f47b411a2e7e3c2d", - "0xb11b0c4dc9477176b3cda6b17858dbd8c35a933ed31364801093f310af082cb5a61700f36851e94835c5d4625bf89e32", - "0x9874e54d2939ee0600f4194f183877c30da26d7515e9e268fea8d24a675dd2945d1565d9016b62b1baab875ac892f4d2", - "0x90df3a77280d6f1fa25a986309bba9d5b89c3cf13656c933069bc78e6c314058716b62eacfa7ab4aff43518b8b815698", - "0x962b08299a287d77f28d3609f39fd31bc0069f7d478de17539e61fcc517045050644b0307c917208b300ce5d32affcca", - "0xb30eedca41afb6f083442aaa00f2e4d5dc0fda58e66aaf0f44e93d4af5c4bf8ea22afec888cacbf3fae26d88e8d344cc", - "0x847747a22fab3fe3c8cd67f3f1d54440f0b34ce7b513225dc8eb4fa789d7d9f3577631c0890a3d251e782a78418fecfa", - "0x8d1ef3cb5836e4039b34ee4e1b4820128eb1e8540e350309e4b8fea80f3ae803d1f25f4b9c115482b324adf7c8178bc7", - "0x8f8a2b0b0f24f09920b58c76f7d99ec2eb2e780b5a66f2f30a9ed267dcaea0ec63b472282076c7bf8548211376c72f6e", - "0x831ee6dc8889bbf4d345eaeb2f425959c112d2190764abbbe33bc44e1d9698af87ff5a54d01fac00cfee5878dee7c0f6", - "0xa7eb2479ac80d0ee23f2648fd46c5e819ad3a1f4752b613607ae712961b300e37f98704880ac0a75f700f87d67853c7a", - "0xaa4d1b9cec62db549833000d51e83b930db21af1d37c250fdc15d97bc98de7a5af60dbf7268c8ec9c194d5d5ccda3c1d", - "0x87396fd7e78c4bcf270369c23bc533b7fb363ca50d67262937dab40c7f15bd8448a8ba42e93cf35fb8b22af76740d5e1", - "0xa958b2a9ffccbca13c0c408f41afcfc14d3c7a4d30ea496ce786927399baaf3514ff70970ef4b2a72740105b8a304509", - "0xa5963a9dd3fe5507e3453b3b8ed4b593a4d2ced75293aee21bfed7280283348d9e08bf8244c1fce459aa2470211d41ea", - "0x8b06ddc3359827558b2bb57caf78b3e5a319504f8047735fcc8ec0becf099c0104a60d4d86773e7b841eb5b6b3c0cc03", - "0x9437e7278283f6d4d1a53d976c3c2c85c5fe9b5aec7e29d54a5423e425b4be15400ed314f72e22e7c44ee4bacf0e681c", - "0xb56067ee26a485ed532c16ec622bb09135a36c29b0451949aa36fee0b0954d4bf012e30d7e3fc56e9f153616b19349bc", - "0xa5c72f7f5d9f5b35e789830a064a59c10175093a0ce17654da7048827d0b9709b443a947346b0e5d96b5ea89b8d7c575", - "0xa8318d01182d4c9af2847a29a6b947feef5795fc12e487a30001cc1ec482b48450c77af4837edfa1aedf69f0642c7e5e", - "0x82ea421c091552d3dafa7da161420cb5601b819e861dd2ba1a788c3d1b5e8fa75cc3f2b0db125dde8742eb45b335efa2", - "0x8679fd1c7771ea3b12006d4a972f4f2892e61f108107d4586f58ee7f2533d95d89b9695d369cdace665f19c6bc3bc85e", - "0xb5ab3e8adee4c950fce4d33a0e2f85d3d886e60a6e2f4454b57bc68725f0cf246372d863167482cce1ea10a7c67c3af2", - "0xa85696927075ec188979180326c689016a0dc7a2f14ae02ea27c39ef91418cd44177d3fca5752cf6b298fd75fa012e26", - "0xa44f87b7232f102cd092f86c952a88afb635484a984da90a41a57a3d883c9469064bf105b9026024090486b6c6baa939", - "0x866ac91a437db945bbfdc11fcee583f3669fa0a78a7cecf50fbfa6ed1026d63ad6125deba8291452bf0c04f2a50e5981", - "0xb780d5a1e278fd4eef6139982e093ceafea16cb71d930768dea07c9689369ff589d0c7f47d5821d75fe93b28c5f41575", - "0xb025d0046e643506e66642c2c6a5397a8117bbfe086cee4175ff8b7120e4f1e6794e1e3f6ec11390993cca26d207ae43", - "0xa04a22b6e28c959ab265c7f48cde42bb6a00832c6beb2595b5df2879080a9424890960417d7d7ceb013d697d0ebf7267", - "0x81de9c656ac27f54d60d0252e33aff4e9e9e9c3363a50740baf15a2b9061f730a51ae1704e8c4a626153cf66d47f19b1", - "0xa15fab90599df889df11fa60c752948b68fba54005491180dafb66c5775547976d0eef33945e55d4818653e0818c6f92", - "0xb06f9be44ddb103a72fa4ebc242c8ee1975fe9bf9ef7124afeda9967ff3db644dbf31440151b824869406851a90984a2", - "0x99abdfe6806ae5efa2d11577da17bd874d847c5f810460148bc045bcf38c4fd564917eacb6ed61bb9164ed58055cd684", - "0xac53231077f83f0ae5f25e52b70bb6105d561c0ba178040c11c3df8450c508ed5df34f067fdaacf716f90b4926f36df5", - "0x99e3f509af44fc8d4ebc693d3682db45fd282971659f142c1b9c61592573a008fc00502c6af296c59c2e3e43ed31ec7a", - "0x98f2f5819670aff9a344e1c401f9faf5db83f5c0953d3244cfa760762560e1c3a3c7692bb7107ea6eaf5247ac6fd7cc8", - "0xb5b9f90391cec935db8d2b142571650fcbb6f6eb65b89c9329e84b10bfa1c656026674d70280ade4ba87eeaf9333714d", - "0xb0696b77ca8a0cdbe86cad12f358880926906fb50e14f55b1afc1e08478ae6376215cbb79bc9035de2808c7cd2b13b85", - "0xa51d746833062a65fd458a48a390631d5d59e98e2230b80d8f852cfc57d77f05eefcfd3c395ade1e86d4a39c2141365c", - "0x812d67654319f4ef3c9e4a2d4f027a4cb7768f1ea3f5fdde8d1b79187a4b874ff9a5c70f15b7efa079c2dc69d1b9b1fe", - "0x968978b653c6416bf810f6c2ffa3d1abbefbd06f66b6686e9a4fdce3f869e0ab1e43cce14dc83786596761c100ae17e1", - "0x98e1e6ab562ca7743783b802faeb0a24f1341abfb9655f106920aef08964a3c0e8083e1acda7ae28fed7cdd5478decb6", - "0xa91c0b982a0a7085a103600edf99e9d0bee4c4e7db6d9f8f376c215c7d42476218462a3765f2928e12c3dd49d688e4fd", - "0x8a43395b3124fab9e2438635bf88952e8e3084dad7ecb3a9927f9af0e0887bce4707084043671fc98ad03621e40a149e", - "0xb0b37626143d4a8c6f5693d5f1fe871525b4dd946c4239cde032b91f60a4d7a930d7ba28959737550d71c4a870a3a3be", - "0xb01c74acae1715c19df08d5f4a10e0c19d1356264eb17938d97127bf57e09ced05ba30d0fc1a9f32d6cff8b0d5f91c9a", - "0xb4c2328eb8a5a673406faed8f0aebb8540d2791646e37ce46e0e382506570ca276eb6f8e166dbbf9e0a84064873473b9", - "0x85cb9f769a185e3538e4a4beda9a008694e1bf8dfeea9dc07c5c40a9ceb1d31fcb13cacfaa52849ba1894b5027cb8c30", - "0x8742f91cddc9a115ddc73982f980f750d82d3760f2d46ee4490d5b17c6c3bb57c7d4c7b8d6311b7b41e59464c009b6a5", - "0x948ef86d17128a061e1bdd3ea7fcc7348e3ec87ec35dc20a58dd757d5d18037fe5e052bb359e27ab4c2320d9a52a6a0b", - "0xa70f6a214097c271e0d2d95e30fce72d38c30a2f186271fdff0e38e005aff5baed53739b8c4f9501aa7f529c5cb2da59", - "0x892a7574cf6704ad75b346c95ae6f2668904f1218c35b89b07a0c2dbf3c62173c348f6fd9473926eef56a37c0f635c04", - "0x837e85a41f39b4ded1420aa8fc3be46a7adb99305e0928c6d7643b7c44434b72984cea08eb68f5f803661df0db78c87d", - "0x94e495329f2aab3eeb68f347961d1006e69d990095877a4dcc376546233adf29a14bf6b16a0c39aa477e15368e87014c", - "0x851860a8fdf76a97048396553262637dade27f1f63f926997e74c7c72b14b10293eae7824e8dedffad1aead57c124f79", - "0x90481017a250972055ab1cf45ff17d2469517f10f18c9d4ef79a9bdc97a49093289bbacfefa8a1e491bbb75388b34ac0", - "0x983db15f7463df28091c691608ca9c51095530fa6b1b7b5b099c612e673d29e16787cc9ae1c64370ba6560582ce623c0", - "0xa477dab41014c778a1b78a7ce5936b7b842124509424e3bfc02cc58878c841c45f9e04ccc58b4f2ff8231488fff0b627", - "0x868ebba1c85d1f2a3bf34c0ab18721ea725378b24f6b6785637ee4019e65d4850e051c8408fe94a995cc918c7b193089", - "0x93cbf4238a37ccd4c8654f01a96af809a7d5b81b9e1eab04be2f861d9d2470996fb67367e5bf9dcd602dc11a3e4cf185", - "0x83113f4e696030cca9fdc2efc96ba179cf26887c677f76cde13820940ad6891cb106bb5b436d6b0f8867f2fd03933f7d", - "0x90c709f4e3359a6d215d03f45ad5cf8067aedd4aab03512dd62229696485a41dcd64e2acce327fda390e0352152fce13", - "0x9945cfced107a36f3cf028ba04c653360afc5013858b9a12fac48802efcbc198c9baf3a7f9b23dfdd5036e88bc7274c8", - "0x832ae60192b47fc735a8ddeaf68314b16256c90ab68099f58e43073e249c6939895c544a02fa34e40805bc6b5db33461", - "0x8b12c335818b643c1d22cbc2869606cf64e7ae54a7713617fc4dd3b2f052ebd6b920ca59ba2e9c7aa8cf71bb4f40f9e8", - "0xa2033eb7a373931c65d66989644aa0892ac3778b9a811b2f413d8bf534e282c339717979f9aa742162abb3468c195f87", - "0xaba2b4c37dea36bed6d39323e5f628ab607699c66767f9bf24ef5df1bfcad00c2664123c0d8d5bd782f1e14a06f4c769", - "0xb71963777535b4d407286d08f6f55da8f50418486392a0018ee10f9ae007a377b8b8336f33386b0eb01c45695c3ed2da", - "0x88dc87826941340913b564a4f9b74985a311371c8e7b47881235d81c081f1682bef313c2f86561a038757fb7d6a1a8dc", - "0x869e13e3fcf91396750150f9dc9307460494c1d365f57893fd06fb8acf87ac7dddc24e4320d9cad0414119013ea739b8", - "0x92194e292303d32b91ae9cecb8d6367c8799c2d928b2e2846dab1b901371a4e522fc4089aad8f4ee676f0614ff8b19d7", - "0xaa589a3e512cb4f8589bc61e826a06d9f9cb9fdfd57cf5c8a5a63841435b0548e30a424ca3d9ef52bf82cc83c6cb1134", - "0x81802e0194bc351b9a5e7a0a47911d3a0a331b280cf1936c6cf86b839d3a4ab64e800a3fe80ea6c72c3751356005a38b", - "0x88e5e9e3c802314ddd21cb86f2014948b7618502a70321c1caf72401654e361aac6990a674239afa1f46698545614c93", - "0xabac1e0f85d5c3ff6d54ed94930c81716d0ac92be49e3d393bed858833f4796c2b80bf7c943e7110de7b2d148463bfbf", - "0xb7eb416004febd574aef281745464f93ef835fd65b77d460b6ad5d5a85a24b536b4dec800cfe80ae98489e54447e8bb6", - "0xb3fd8ed1c30e7c15b0bc0baf0d9d1ecad266bafb281cd4e37c55edc76c202fb1e4ea315a91a2848f40f481793ae35058", - "0x86ef674ddf4b7d303c68bbfb53db00b925ccbf11d7d775ca09e458f4ecd868ca828103e8e7cd9d99672a193e81b83923", - "0x95ef414e9f7e93f0aaaeb63cd84eb37fc059eb8b6eced2f01b24835b043b1afb3458069c45218da790c44de7246860c9", - "0x93ec8f84c20b7752bfc84bb88c11d5f76456136377272b9ac95d46c34fce6dcfc54c0e4f45186dd8df6e2f924f7726ab", - "0x95df5f3f677c03a238a76582d7cb22ed998b9f89aecf701475467616335c18e435283764fb733fb7099810fec35932ae", - "0x8cda640695c6bc1497d19b9edc5ff4ea94c1c135d86f573d744358758f6066c1458901f9367190dcd24432ae41684cf0", - "0xb19aedf5569435ff62019d71baa5e0a970c6d95fe4758081604f16b8e6120e6b557209cdea0ccd2efec6ff9e902d6ce6", - "0xb3041f21f07d52e6bd723068df610aa894dfdde88094897593e50c5694c23025e412ef87a9d16cadd1adbb1c6e89ced4", - "0xa7f8d6ab0a7beb4f8d1cfef6960ebdaa364239eca949b535607dee5caeff8e5dfc2a9cfb880cc4466780c696cff2c3a6", - "0x99a565b4796e2b990bfcb234772d93c5ffdbe10453b5aa94662272009a606ba6ea30cc0c3c26aa22982c1e90738418a5", - "0x90c54b55ff19157c1e679d8d4f7f0687a70a27d88f123179a973c62565adfcc9347cfe31f54539038cf2f34556c86870", - "0x8612f34bcd018d742202d77d7ce26cf9bc4e0d78e50ddf75250b9944583b2c6648f992b635ea13fdaae119764e7c28d5", - "0xa04fb38e5529bf9c76ec2b5e3a1ef3c6f9effb6246c7f67301cfed707356ba1bf774f2867c77a5805933f0c8ad0ec644", - "0xb4800e7b503da0164885d253135c3b989690794d145182572181995e6fa1989f3d0324993e871bbd5f48fadd869d8a18", - "0x9981cd4f28ae7b7dadf454fb3aec29746dc2e0ca3bd371b2a57cd2135a7d93559e02132528ccd2d305b639d7ac51613d", - "0xa3ceec012dd1fbad3ef9f9f1d6fe7618e13d4d59e3f50540d2a57010d651092979c75442ec8b38a1ab678505e30b710d", - "0x8b97b8654d067fb4319a6e4ee439fb8de0f22fd9db5569ba0935a02235cb4edd40a4740836c303ec2394c59a0b96308b", - "0xb3d1bf4410fec669a269622c3ce63282c9ac864620d7b46c9dfcec52d8e79b90c4c90a69c32763136a7f2d148493524e", - "0x93174eba1e03f879e44921084aa0ee3562e48c2be49085de96ed7621c768ff52324d14c8cc81f17d7ed50c38ffb2c964", - "0xaa2194cd0fb7aec3dac9a1bd8ea08be785926ed6812538be6d3c54218ea4b563646af1f5c5f95cb914f37edfae55137d", - "0x93f2c0dd59364f6061d3da189e04d6c64389a3563b062e8f969a982cd68cc55b4f38b21546c8a67c8df466ff4f61f9c5", - "0xaa7dd497cc949c10209c7010ba4ce8a1efd3cd806a849971e3e01716ea06a62e9d5e122ad1d2b8e5a535fae0a01a7761", - "0xad402424b2a32bca775a66aa087580d7a81f0867f293f1c35580b9e87ccc5a2bab00c29a50fd0d7bd711085ae2248965", - "0x96237843d8e29ac77fc6ebf4acc12946ad11697de8e5f152fe5776f2475b790226a7d156ac48968dd68b89512dc55943", - "0xa45c25cdbb9fc327cc49a1666988af9ab4c5f79cea751437d576793a01c3eeea4c962c05c0947852fe0e4c63e1c84771", - "0x93dcf834a614a6f5484cc4ba059e733ab5dcc54253229df65ff5ad57b447353ebbc930736a4c96322e264e65736948dc", - "0xb9a94f82a82c0c5a26f2c1d5381afec3645e8ee04c947dc3b7ad59a73018db1e9965ab3642f2bbf60f32c430b074fb22", - "0x94eab29b3524ccbe0c4b928e5fa5dd8f684074b332fcf301c634d11083653ffee4f7e92ddbcb87ed038024954ad1747b", - "0xb8dca5f679931d6abef0674bad0639aefad64c2b80572d646aaab17adf5ca1ab2ebeecd5a526cadc230bec92ed933fc2", - "0x944d394958e539251b475c4304f103a09f62448b7d8a8eaef2f58e7de4f6e2e657d58d5b38e8513474115f323f6ec601", - "0x8a5ae1f13d433962d05df79d049b28e63fe72688fc3e6660aa28e0876a860c3dbc5fc889d79f5c4dec4b3a34cdf89277", - "0xafa5278724998eced338bb5932ecf1043d2be5dd93f4d231d05d2ea05b4455f2ffdc0eadcb335dcace96dd8b2b4926fb", - "0xb91153a2f4647ae82fc4ee7396d2ca23270ec7f8884ce9eead7e9376270678edd42dd3d4d6c003dfc2dde9fd88cc6e7c", - "0xadc932f1c679bf7889cb1ff4a2d2897d7973483fa283979a0ea3640c80ed106ea0934c1961dd42d74b22504be49851f2", - "0xa82e90761fae684d1415cee0649bb031bcb325ae0b28f128ab8e3650bccedd302a70de1a341ca8decfdda76f3349cad0", - "0x8ae353188b4b98835f4ef0333cccb9e29e1ac3ec11d554bc96f5880c101cb3c84b8eefe72f2287b0812735339fe66cfa", - "0xb8b41135bb1a1ffb64afbd83e2189e755f2c350e1273cf47c38ae9b8c4800d831436a69458b8ef9fa8b95a148d8ec9fd", - "0x96f75a04d8752fa93dc1eaf85ad333cff4eeec902a345576139e16de3a88eeb71b6726224349bb9844065cc454d959e9", - "0xab82b05e3923ad4c26f5727c60dc0d23063c03f5a4fd8077da66aa87042cad1bd99586d4ab35aa5e4ce6f4da6fecf3c1", - "0xa50c83db91c26ef7bf1720d8815b41bd056b49fd99710943679a162ccf46097a7a24585750ece886e38eb4fdb866fa37", - "0xa719f667914a84f62350dcc6f4f30b9ab428eac6837b70318c3ac491c1e69d48af5e1656c021818f377d911fe947c113", - "0xa148807aafddfa0a5624c7cb9e42468219e4bdb9994ec36bc19b6e6d7c4a54d3a0763d13ca80624af48bbd96d73afca5", - "0xaa012f205daf22a03e9fb13a63783dda7666f788a237232598d02a4d4becec7a699ab493f78d722ce68519262924c708", - "0x97fc15fab5952c5a2d698fd6f7ad48aff1c8aa589f7d3b14285fea5e858c471cf72f09a892e814104fa2b27eb9771e73", - "0x8da8840236812667c4c51c8fc8ab96d20dae8e2025290b9cde0147570a03384370b0fcbe20339c6aff09cca5d63e726f", - "0xb477d85359a8e423fed73409f61417a806cb89c9a401967622aba32bf85b569e82bca1b3394c79e180114a0d60b97316", - "0xb3d6ee2ed1e4c5cf8ba2c3a4f329832e41c7fdcbcda8a3fcbe8f60967fdb1717665610b7c1ac65582534d269d762aa09", - "0xa0b3b30b1b830b8331ee19f96b4a4321a6b93a3395b95d3a895682c65ec6ea64774b878b93514eaf353f2e4be28617b8", - "0xa2b88e9617f4d30ef4e686d1932ad43cd555fadcb5102e51bea19e6fca649284ccf4debb37b5cb2090ef386fa5bf5327", - "0x8a4446f7e8463ea977a68d6217a9046ad4356d6fc1c18d46c5d2ab681ea977b8faff136d65abea6bbf8936369cb33117", - "0x91e7464bc56e03f436228104939ddd50caace5a38f68817bb2991e193b57adf6835152bbf3dbcdebf0382ac9823f60c9", - "0x961a441e6cdf8106c4f45e5b47190d35644faec701c9cfc41ced40cfdd1fa83752fd56c1ac49131a47f1970a8f825904", - "0x94b7b165cc71c2ae82976b8f03c035fb70e90028992b853aa902c0467b384c7bcf01d56166bec5def4453e4d0c907e52", - "0xa5d32cffabbf547f900026b34ef46f08075b7a244565f615370d2f04edf50b094c95088a4a139ce07caf55bcd99afa07", - "0xb4e06e73660745f75ab2f34d9f6d2675b58f80f911ab6dd4c5a6ce1095f9a2b50d86f6ff9a05394190bdf96af0827920", - "0xad3fd8f83c0103b29d41319209dffca201d2b98094362da08da3fd6ff0ba96796b49d6bed525c9adb96c2954858e7f48", - "0xb0c27430695f0fd20ae31e1ec621da090094f2203e17411db9384695ffcf5c7c6badf461ba49ba70164aacebd6f278ee", - "0xb9bc6e972fc3b532fd2b1eeafc4bceb77604885f32132af6a9a842fa2440df452f49ec0cd9d86da1180e8deb0723b260", - "0x9729e22d6104b0174c136a854920f542b384d375040adcebe36acc253bdb55845eb43e34dc5a7cc27d22c417973c24d0", - "0xa8b420b36d48786c9231d454468a6e855dd7f71dcfd095efc9855ee70dbece0f06ad277f7829c5813fc30524c3e40308", - "0x8757dff5499668c93fc5d9cea0a8db61817b8ed407200d623030b5849a913d12f8371b667cfde8d8082026eda7407e8c", - "0xb859ad747ca5af661fbd03a1a282df6e84c224ecea645bc2d4ba5e35fa06cbf047387319fca0cbc76b712398c0798968", - "0x8e3173c27875f1460297af0fa736c945dc842ec3e476a973d3d5f790bf183ad3ffe96ac13868c5101d8e299890791864", - "0xa9d725e2b92c878be42b5eecc2c3081c63c7231ccc7e2dee17ca6a4caaeae22788fab1f1465fcbd7fc236613fc2bae4c", - "0x86f6c4f04a354cb2470ef91914816fd740f8d5795ce7ff981f55a2634695fde5951bbae7a4bbc4c63747040f8644170a", - "0x851773cb26f320f0c3f252d95ea7e058ffcc795dd0dc35e459aa1b6b448238909230d809e82022e64b7fca5d40b8324c", - "0x8962641e0306220d9892fe2d452caa286301a3c465185757be7bce2d9b2c9beb3040280099606cc86773e43941fd3439", - "0x8beb6e08c440b0de5fb85251d39d9e72db4e556a2dfe3dae59efd8b359d08492064cebd8d8993254b43bde8bd67d969a", - "0xa7e047894466ffe3dec4ab8d5462f2b1d8ac0df006b1d2dd26caf499ea857d93a811cf42233f9e948c9cb903beec004c", - "0x92eedd95557a91691a5e2835170390ce2401e223da43b78615a804c49566f9d31cbb7f10c8a8390c4bdcf691544fdba9", - "0xa5e5b5d8fa65824e958bbae98d146b4b332f97ed50e0bc2c58851dc2c174ab71bcbb1ae015cd2955c26b368487dd862f", - "0x853a494eafb308175629d581ed04bed71bbc3af9ca4c0dc483d03d27c993a2bbd88cea47c2085a6928d166fe6938fb77", - "0x83f06b88d29afbfbe8f61811690322ac4fdd6abb9a23612162e7a2dd6bcbb5f14cee298ebebc1a382484f7346dc51e60", - "0x8c9cf05735ea5a0e563490bdc7ed29a4426643711c651e35c8551ca6f855c8458ae8f0933a022d0bb9a952edfed411f6", - "0xb906b48d807748a26cc2a8848455a76ce502261afe31f61777b71917bdf7de2fece419db636439478c7582058f626c29", - "0x97efe1fa7c9b25d8bea79d74b6cdcf88f63f1e865f54b58512a2e60428630b0b40b8b6af1b5f71df47520507548c3cad", - "0x8ef5ca6e753818906bb3fc71405928d8e4108854ef0ef01c1009071b353bc2852e771fcb619d5fea45590e8f61003d7f", - "0x8e4d901661e2913740d70ba4d0745df5e8c9c0a260149d9362beadc7e669630ba909ff0e8a6cc85c54d6b7435d0d351e", - "0xb7c6ba3bebbd9592967954e3a480ee8df1d9f5965f04e7d78a5415b645128deae7ddaf6ed507c8877bfca91ce078e529", - "0x840bedb0ad4e25acf6cd25dee4f98fea495b2312dc5cb7a8388c5ab00b2acb9cd25da08e9fbead145a3107972b1ccd5d", - "0xa8d4578dbafdb27f3911af59962d89e75dea74db55346720357790da677312c203107d9c7911535aa563446fde7d4c47", - "0x86d3b77f231bfa09251b7fd2ce09c27ac520ec35d783e912476f9a4863f83d269eb175790d6e735da9260293d707f8ee", - "0xb34909f1cc033232652da0c34051a769dc76adb1aee00674a59dc1b860f6e610974c3b4bb69a69ccc73e01f042431242", - "0x90799854d0cf34e1d91ff8e101bc7c5007423d34d2f3bd9adea2ecac57e83f3a65a506bb93d4caea49b29f6d18149957", - "0x8ef94cde29b037e19a1ce7bf4418ad3c95cd9457412796ea385750c19a6690f13a3bb5bb6a9ee81e7a40face1e0a8bca", - "0x97053d21ae8d75972fb37f6fe516c38c32ab162fb56b9f510f954858f4e3ef6ac8c3a9557ed3f41b7b6aef05fe97f931", - "0x90a9f9f0f40991f3bddc58b92d40382147db22cce50d092d4a05aad251b46b94e71ec9f7107a180243288059fcc5ce29", - "0xa14265b1344ac2921b0f890d13bcfc432e4f648ce403e261fce4d3bb32ffee9e2794c02830346054f998e82784c77040", - "0x91928402ae121e56a3e64cd6f390127e6e92fbfb1967ec6efa4f52f3e8058f1f41a0f4fe96b5bcc11641c1139e790b2b", - "0x921c8c92b6d40da6c5a7b592acc74fc0f577d93767b9aa4a1cd302a72dbf503a1ea5b2c29fa0d0359bff3b8f252246d1", - "0x93ae0ebe0e8e133fd80cf67a499047e30ec4c4660ccec9d49098717ef57721a030f423e00c5e74af4ff4acf014a10497", - "0x82c865e21905aebfe0496af1c6ac7e342b5f446a9edb4f7da0f2fb0340abfd8e6fc545da874459d9aabe6bce0dd9bfcb", - "0xaee3961d8d2687c0f134b9c28b920bdc4021d925fbe14323c84224a9fe161248789249fb85436a5891d0bbff42c2a3e9", - "0x91aee420b98b6949482b8ff4be996b97245b4e8f583a6e085226539074f42aa89818395efd1a6699735a569bfe19d623", - "0xa48eec22c192e495b01722d0016a54acc45ff837e2a95c4294ce81d5a4e43e0053a6f0ead8a4fb3ddd35faf6607275b0", - "0xa26e15937c11faa30ffa64817f035e294cab0e839f73d29de8a244ad039be4e221eb47ea08d9a4658b0152fc3caf6110", - "0xb84450f948aa7c8682fccb9cae84d8e3558adf2d0ca5fb81eb200415291158720f8f3470542ab5b88c6873ad08e7fa9a", - "0xa8e8ec27d0608d020169a85d6ecdb40eb402f006a3b97afe32cc01987721b3a68a92ec693aeb4d357e189e05fadf699e", - "0xac87cd535ef5699312cc26f86adb71baa0be42e858bd5a2d94ac05737dac63430691e29b9a30d2559ad581a172519b2c", - "0xa4481e67b524f8cddf2046625efd3d75efee6aab87ddd2c1b22835647e918157e5e924ac760db2195c86d326f3db1615", - "0x891f29ded231486ee826840c8895cb325f7e84a5a6d2eac246cb3573612cde274720233b1978318a57ed337a046330a6", - "0x906b6e750e6178289012769807d2598925d7e51c260c14497d8af978b1695990e3352e6e809a752f376597a68083870c", - "0xb7a056898ee1e46f7f29702fb39232f678ec173eccd170303b3b0a30c8d8cf1a5321384e3513e3b03bb742c238deaa54", - "0x8f2f035fd96c3a336354c89ec9b8222803bf42e95fb2412c28d4e75eec99c1d4d402501ccae17357b757db8bdb0bfeab", - "0x81228625ffcedf977fba9cfa13f6edead3985e2651d5974789c394a69401cd7face9e20ae6694be4c0d4bab5e99c61a8", - "0x885a83eae25e61439ad809567a2ab148583402e01cfdd77b0e37ab4038935425c64b4e0886949bf06438c35e80aa13f4", - "0x8926387f48752f6933899c48e038cf14e7941ec6a58bcc0a436614b396296a17aa53e6873803dd3041dae470bd493fcb", - "0x95d0d3fa061f4d856eca78a569aa132db14cede7646f97e2aceb6da0c8ea53195d3b7a566fe5ec8c41b95ecdd89a1c6b", - "0xa3c817f4062ed6aa94064ea695d76c1825f3bf77b310fe1db28b8bedc9aaacbf1019dbd128adfd53042fb943d863a2b7", - "0xaf1208417aa584052da309169854149ede38a3ad63c76cad6e43afb6f1a7b854edf8310a0b00088c039259cedf0f859b", - "0x8b713fc3196bad35dbf364089049ada5477e540d78d76a5f0a9df98f7ba4a0e65dd0644509c149f9b07887298bf74b04", - "0x89c09c43c5b733c4a417cd9ebc0795cc3348b72778d31828a9171427779a82ef023c1a4fcfcdc919ae25056f9c826fde", - "0xa0759c850ed320c8c874435e90ace6edfb8e7b3f2a09d942b8ad8339c508044ee2ee26c70f1b626ec49a77971433b6a8", - "0xb85cbc58d4fd52286e714ac4eaaa0b2743a1de06fa03ddf8f6668ec6f1d204acccce93b10620272afb8c0b49bc4b0a43", - "0x814e0a87384e159892a8d23036985fa3f489c53bce192e107bd2d64f57b1bf5ea0acc1ef46c7a42bbc5cd0924d92b4a0", - "0xaa6821da96ad89d7881b878e141076522f104ea9a5bbdd1fce9f641898f7d6232c518a87a0f666871d7e3165c26081e4", - "0xa9041d714bfc067b5427252186fa3557bad598fc0067dc8521aa9bc1ae298f6e96113db5ac9f6bade9a85d5a950c9755", - "0xb8669340f3064692625e1bf682d34fbe69a61689e3aa6d6a3e822c781d406b0300dba9c3f7b8152a8c2513f1310d4291", - "0xa78c53316ce768a1dc5968030bf4fc885f4029b1ddb6a5d84a61c85af686c73727f62823891edfcb6ccf4545de366cff", - "0xad1d3aa29ea28292ddd438c865e2b5d93f32cdf009e6d5f5dc726de996583925727e6348bf1c28c22dec0bd86aaf867f", - "0xae1447a2062e9e28af5f38aecc60fe150cd10c2edeaf2110034aa144f6235ed7fbce432a58805d4fe1f6b12652d6e1cd", - "0xa32146634332d3303934550705353c6d4fae5fa5985105bba35041e74cd71e2aad67b45da171221f6ed80f36bf6dffa3", - "0xa232e8286184196ea77427b53d8b52c44d758ecc42d22556529db3136379b4989dec61cff610cc6cf6700a450a847a94", - "0x8a72c7255125a736da52dff5f77e44c3de29f88fc05f5ff9227c69df296930caaa11446595e6bea3bd946baac5ef957c", - "0x9688a981a9457678067f629f8efa6b522e7318b529f88d37ef56c5bf8f1c34fb9bb3a918ab73caab82bf5abb0c03518b", - "0x88286f3eabd71115fc3b17a6bf6981340a81cf7e5f96b0a1a016d4ec8c18fb486d46c70919123d0c189a6f5d6ff29a1e", - "0xb535e701b40d793c02ac0d625ca91620d3f4a512aa9741f71389e58381008b2f93d597586d06213c4e103d67d0ddf6c5", - "0x80d0c9dd941e8d8d3700cc51a434a5aaa3308cf8ebfd14128ccfd258f826b27cc3cf5c3ad7851340393abb1eeab3a157", - "0x87049225fa2380d93f18d3d90cb0697a56b373b66d7f24ab209966aed8b55a2790194d5885399db29dd5b1f189eda64f", - "0xa52df158ce8670e0290551e8878d63dd33b4759d6f50e448e63fc7fe6ea99dddb6f180be5fc0fc3918ce54c05f80b356", - "0x8b2a728b39c465fb0f60b0c486e5dc8d5845ccec03d3dd93b393cedeeb3fe1b44518359f1ed55fc770a8f74bfeb9923d", - "0x91fc05419dba718fa4a910dcf256ebea356bbea00522d8d5ec3e7ba4271a26035aac15e8d9f707969df1d655d92dac55", - "0x97c8779ae80c24c1f82d5a714762d6ee81069224e39515e41d8a71c9310dc5d1c55cc92bc5c6a4bd391ae4c321d1d4d2", - "0xb5e5aedba378c4484e3a7a4ed41b75b0844f674261c2501497de6f91f7274b5a4c1be0e055f2e0c0cab843d891169fbf", - "0x8a26212f27211b295beea500abc8e9d430a8500d3a350cc62f895d39e8b4668aa638c17633804ba353010000165637ae", - "0x864a95118e5d394e00e99efebd505df0125525c9ebe165764c453b80ad3edc730feebde3d93850745dfd88a27bb8f20b", - "0xa092e0b78290e826cc1ae56afffdd08f7c10954f549a3ea6666f3db1b6cdaeb7df53db28dd2a92446342930fe60a27ce", - "0xa1720224c0626a081b6c637b2a6d37da85d9a82241e5efef3bc15699b02a69f6304e43d8ff3144d60c16e00225d6b39e", - "0xa7b3d098cebea9cf32e19c5195608182b6afe9d4af6b9df532c047eb7a941a971279b2ae6a4b80f2f9d9313a6d788ce3", - "0xa3d2451e6788944802c5077a778d7b7299dbb9d1612676bb6baae78f39976e0fd879493cc4a4d737b8174b472a456850", - "0x930121b73da844571b1411d56760e80923a4ee09917b3e9cff4d3dcb0bc27026ff2c4e2c44e7aca7d3f8383f129c7f9b", - "0xb4b0119d163ee00a2b74bdf188a5cdcf054daaa48c483b94bbb4d09ff615afb4a91347db6363bc7535e2af9054ec2214", - "0xa5846decee706780201095a8cdd48fbf3d3a2eac8d089a818e5e22c29457494bbfb4399323b067f3d2be2197c33dbd98", - "0x96ba600df10ee7af5a9df29c0ca31dbed275d647faf9c66c7342de927ceb25b5bdd852dd7aae0228b27897f90fdd5d62", - "0xb6ac51ddc98edd9fb9f54ef84bf372a041d58dfdf0dfdbdc4b08ddc1a7ba93ddbb1413dda3c1545a3fd7386c6b85975c", - "0xb35f3efd91a0723e0d486188ea9675a3462106470455118392d7610470b623caca2fa33829721c05fbeb0fabcf570bfc", - "0x87f49e85df5f8055714a8ce7adf37f6a278e64e76ed74c60abe3edfc3611ef5b0426d4c6da45e5f3b74d30be1dc6f539", - "0x8ff8bb06902a71b1e9177a77367318b2e3e0a88f5d74d6907ca9943f4f9f1ceb5f297132c2a025259d17a67e880d1bad", - "0x85eb6de6c70fe5c53ab0ab27aa0fec439f136c979c557d317337cafa6e6c5cb3169679c9169567dec5f6c72b3c057d83", - "0xac18715ed1080771d760cb7066c6328faf65d9b30517903f8a5cad8d66d5c6381156b521107d7cd75ebb8c30e250706c", - "0xb95b9eae4703727e4ac9ddf2ae675906487bb78905a5f9cba74a4cbfd118d96b7afb6ef3ed5edf14fd963b830d71338c", - "0xa3b47b52fda16b62b11c8aa4daa56b0b669c4d5c56a3059b7d063284d8a91f6fff9ccccab23d6ceb9650483b2d353039", - "0x96a95b3f327df94c85e92f2e406f1649ac621533c256b062738f3c3ee137059a735a3e6072247acf57b1b0d8c219bd7f", - "0xb19b33cc04570be94eae8e943d5bb17bb0c96e9de4ca84f9f41b37320a1a03d397d53747dc13275fef1b356de557214f", - "0xa1faa3dcb931dd91507f3f12a17c43f6627fa2bc5c71fbdd27548e091eaaaba262477949cd51290e81196bffb954a492", - "0xb060a16079dca1d28a1fb33cbc26f368630ee042d980ce305230005d5b9ab533a7a695281ab76e9214458303932d8bbc", - "0xb303783196a858fe45d67e0520c30576da605fd69964449c20009fbd5099cf1de52a32d326d7c3b864de07440195ef40", - "0xaa550a4c20d1003d137ffd8fbdc1196d09ad53cfa0e202302093a80fa3bbc4c9aff83f34f2151785cc1ce5f30255693b", - "0xa7f8585f45566a351058e10c6f1ff4a7ba24811f1482a47202f581525615ca770da93f2f58878788b45b92cb446ef4ec", - "0x8206f63a9a5b59bd68e64a843e68fcdf706f4c13bbfcdfa9928298e5b9251006ae0bbd80c715aa3c9957d2c0148b5059", - "0xac9490abe1241319658f1c2c645cfa01296f5d4106020c7894b7ba4a65cdd52f6c5401bd3b3cf1c9863e088cd8c9a16f", - "0x85dd6d9c80a1b58c24c4d2cb7590d33d2454f381f58e820979948e5831972360cde67bbd56e1860077ef5192fcacb904", - "0x8b0285944c676fe2519cb68da0973275fa29c0718d838d363ce46651b068d29f867cf9fe579ff8da0bb8b37d202bb23c", - "0x95147275da658d43a758b203b9ca1f1c1478853e9bf77b5218593142e2bd9c0bf46d2206ab64cef99295de6e9a268edc", - "0xb8efa187fdd3e1f46c15cd596e9567690c10e253b5beaa5be8074b6ea4e6d3d06e0f2b05323453239e419ae1e7128521", - "0x8340464f52c92e31806fd3e8e65f56e27194d1f6daa4a0f0b3831e8102aba16f88bb5a621633ddb7dd0342e1d2d12343", - "0x8615d87dcab85a78dc052f05a01e751176b756b5dc9985014347454ce5752f459dd6464e1c5aff36cb6c51b783fa2692", - "0x80c6e35c0d3defbe4d3968792724a23f0b8830dd2fac58663583a49339ea20f1812cc4140e3ee867c7e716177319bbbe", - "0xa7aa63dbfc201dde8f29bb6e23d7aa5020dd35bd18a0cc93c8a10c35d695913fe25b9e8cf9b5fd1899e9657b22bc8863", - "0x97c2a4ba80c4caba2e729a603d2faa0120915e3fe64cbb065f7ff33de5f877f1ec9461cf455e88ec9e9ded9393939dba", - "0xa54bd1419f0e2d2d87757870f37c476c7e3a13502f1ada82fd7394fd29f8a00c4986473d753034d0954a2550badbac0b", - "0x8d3e2bf900d0d2b9b46e6e2f37620f0cc90526dbbcfaad4e4a37ed53f39fdd23bd3a6f21aa7e800eaec937d9710dd6e3", - "0xa88d2b1c7802b2dc216c2b6532406c091bfb12f29121b9a82c1154470e250188413ddd3e79f7e009ea987a4c45b332e5", - "0x8c552c2101dfdc3f99c2da436115452e4d364eefe029b12946f05673c5ce1cfb48d39a579625849236dc6c8e7277dd30", - "0x8415c252d52a26a6400c3189c928a98559bf24162ecf3eef1d10e439269c31d854b0b4f6ec7a2430e3f11b5d77de78d6", - "0x8b38905bad93a8d42339dbdb5e510003c51fcaf05e04f88fd7083753353bc1c4c00a5dd4a67431cd4456d0669c7040e2", - "0xb1d0ed8862250d0f0d9ef9dcf0cd16d84313d1a795dc0c08e0b150dadf9ce73d32d735e04632b289cafa69a6ee75dc89", - "0x9434e18a5fb631b10edb02057f2d1fe16000ee55ada3c26a079c9fc3943e29d6de99e52829fe7b333e962270c712e51e", - "0xb1b9f3914007e6fca8ad3e7e848a1108988cb2318da36df24767d804e95d1272943fda948451135cc1b5052a3953b081", - "0x8c02947a76d7b6c0a700a83dfb971dc105bfe996e18c521445f036310914b349ab28e57571e36ae08d13a46fb01c2f43", - "0x893472fbc225f973a0ac6a0a0130b9cfb7ab6869dff80df71a62b1f6beb4afd069bbf35b4f327165bc31dff39e4fcaa4", - "0xa7c176c0903175f3540d62f9afee994d5d9bf37081e094644b22f017e94c515afefde7bb07f638342abef7de657f8848", - "0x860186c2b1d3b1e657729bc804275fb5f5ee89eaa60848fcabd3871289665ea9f0efc8a95792d884972bcfa2de96223b", - "0x865b38aea6386d0ac8f501a7d934e23d01dc50105324e354d4c4fa3cb1d4c29c26f4566df7b1a728e10cfaa9d24552e6", - "0xb4eea5548de6969dada658df604b5d9c49002e2258352838003e0fdf7b299d81fb025807a7f37cf5b547cebd7f2c1f93", - "0x8982de11ba68d63a649a3b296d4d56c71e3c3eec016db250d733ab7c3b9a620c09c5a5d0b64fd30d3bc03037ca4b17c9", - "0x84d8b8a10d67eda4716673167c360fc9b95717cf36ef1d5bc6f2ef5b9d2624f0e76c2a704d016adf03e775ea8e28d83a", - "0x834d03ebd51aff4d777714783e750b84c16cb6627f8311bd8ff17c3b97fc4a5bba57d6c8f6d74f195d3030bcb5f07612", - "0xaaf49e0def0c4d5f2c1e9c17b51e931d2f754b19e80070954980b6c160178349f6d3c8d4808801d362e77f41a0008918", - "0x8ef4115edec841854e89f2bbd11498dac7396bca35dda554290d3db1c459ffc17be671f4a46d29fa78cbd6064cc2da20", - "0x9641dc8a64f4acd38e343a3062787c48c312f1382f7e310ccea3e95e066ab6dc980f6ed90a633236a435e68bf6b3c625", - "0x8a84cfc2cbeb18a11dd6c2a0aebb3f6fd58a33bb4b26101e826add03748595022e816afac79a4e7c20b3805252839dca", - "0x9770782d729017659844421e1639ffcda66a2044df9e19769b90292df87dcb146b20c6b9141bb2302029d84a5310665d", - "0x98c7ec9696454868ac52799d1c098c15ec4e08b34884dda186ebfe87d32840b81fd3282295df141c91137faf4cc02da8", - "0xa3f6eb921247617292162dfc8eec5b830ddc294a0fb92f5b4828a541091ffdaff34c392c1d7168259d6204405d90ec72", - "0xb185f77a468f07a54222d968a95635234e74fc942485604909308a9028ed2753b15902b9134749f381f7cd6b89cc8c3d", - "0x867608a682d53bd691dbc92eeb460d1c300b362ca49c11a280f6768ccec217f1145f9d59fe50d994f715ce89d38a74e1", - "0xafaad630ad8827cd71aade80edf3d7aeb65a344878db12fa848759e6233f6fceca563aa437e506ea9e0f1e47b126d45b", - "0xa12afbc84e3441594aecf85d089423dd3bb8bb33a1a384ddf7cc14caa72284caaa56aa179c15e3140fd56bb532491a67", - "0x98757b0b5e5837ddc156a4a01ce78f33bb1fce51e0c1254ee9b6d3942268d0feb50b93edbf6aa88f9ea7b3c0309830d8", - "0x89573f4a4ae752e9f964e42bec77d28a41840c28e4bcdf86a98a131d0b85367b885077823a6f916972de6ac110821bd2", - "0xa17f2745052de5de9c059307308fc49f56cb5230e7a41cb7e14a61c9efa742ee14c41023ce90c7f2261adc71e31045f8", - "0x914b07c53a41c0d480083f41a61c10429ea42dafea9a0db93862d2269ff69c41db8b110b4768687b88089b5e095523cf", - "0xb380cc3e0d26370976fe891d24ea4eeb1b6be8cfce01f47fd68838a27190e644fd57b049d3aa0a9589370de20e276944", - "0x906385fdfad60feec79eb1c303e750c659ceb22d9c16a95faaae093daadd53e7aa039a45d57e20951d6e1ca0dc899ef2", - "0xb5211ceee31b194dba60b616bfd91536e71b9213a3aaaf5aaf9b2f4cbdeb05191861d78b97eec58e3c81abe4f0488c04", - "0x97878e9e38c2f69d697800e7a2f132fc4babaacf471c79c26a757f771606e55fe696ece68a3163a0ffeb2f72274cf214", - "0x959431c1f54c46500c05aaa9a2bc4230531dad97ae768fa92bb85436c0ecc6374cf20fb0ef82d122db116820a943b401", - "0xb69e5a1c6798f30d33e42cb8d124f025d2c77c993c4c7107a539aacddf44d8d4d2239e802ece32e60ee4dbfdce201bdb", - "0xa8b09e5e9f802ad273b2efa02bcbc3d4a65ac68510510b9400a08d75b47b31c6f61ffdb3704abf535a3d6d9362fc6244", - "0xa41ace7f1efa930564544af9aa7d42a9f50f8ba834badcaf64b0801aaed0f1616b295284e74ca00c29a1e10c3de68996", - "0xa8f2aa0bbbc19420a7c7cec3e8d4229129b4eb08fff814d959300cd7a017ddb6548c9a6efebad567d5a6fde679a6ac6a", - "0x9683da74490a2161252d671d0bc16eb07110f7af171a1080dc4d9e4684854336a44c022efe3074eb29958ae8a1a14ace", - "0x8ef44d78d10795050c161b36afa9ab2f2f004ccf50fdeef42fe9cdc72ebb15a09389ca72a00001cd6d9b1d7b3bb766c3", - "0xadca54f3b14fb18298098970b0267301b7312afb75894deea1b2afa3e85b7a3b4efac9971ab54c5cbecba2da9f18507e", - "0xac5d4528f06fdccfc1370d5c3d03ed982fed0861a93a3f6453aa64e99360b124926d1892faaf72d89459e663721dfa99", - "0x98aa1c801bd615b8cba728fa993021e181e0ad717ba01c0290e7355694155407083eb53cb70819c4775da39d33224db7", - "0x8b3aea4c7c2bfe1020de3261ec085d79c7bf8a7903b825d2c70ebbb84af197bcc54e3653c5373a2045c3021526b63b66", - "0xa29f3de4cb3d99afff1daf7d431b38a33a9804fedc41626618928ed059df6f6fe9f298a046b594ffee951ed4d4e1400f", - "0x803fd346be540c5242667c18ee41b26bc812456ab13ff117196ed69b90ee608c8cb6554396b64066a546ec87a71ed6a9", - "0xa9c18d81ffd029c0339c72c499bb51685392253b996b6eabd8b76f05c6191ed8444a1397d63b9923743661a319517f7e", - "0xa048d5c390d08f07161faac71c5994baf152c883b205f3bb10d3501709d6516ae54d491b486303a11b751857a31f0052", - "0x9156fb4803e40e28d8d57d928481a8de4373687288da44fe88c5676a8ae013ed1fcc09d56a31140bf74e7f767253810e", - "0x98e289c725b18e0085afdfaf2acbc674dae7b0a2ecc2537a7d0b87e20eb785404ab05973a787f0495d2adb3e5565c09b", - "0x8a7237b249325bd67cdc1f9fb278710069033c304afbf270b7ea24dbc10c8eabe559a484d3edc733c77b4384932deb41", - "0x9056f2e5b02e5c2e04a69fa1323bbf1859d143761268d18e74632e43800a2a9c76fd681e924a19bc141de0e128d3e462", - "0xb9f2bf9e4e7263014296a82b9ecbb05d3f1efa4b2e675e3b38d3eace59da06a89c859256e1b77847886d6aa15f98f649", - "0x83b22949cca19030289bbf7cd2a0d8b84e1d468e78bc85271a6753241b89122627632723bc293cf904a5eb2b5dc6c3ae", - "0xa919aaf35dd0116168d2ee845122026416bec9633df113fbd913d8db5996221e234f98470d029a8ff182825b59fda20a", - "0x91726901f49d32b41afa15219073842278f60dcee223640903d871e318a1c2b541136b7b38a7b2ab7d31e4242fc29674", - "0x942b77666545bc9a858d36cfe857ab1a787c9528f4a0b87918a06bf510793264dcafd12ae6bd3ee300179dab7f40aed0", - "0x80adc1f2f9c47a96d416e44fcba41628abc0fae1f88f6a26aea4648419ab726f7fcc2187c7d5145e3d8f5a75c03937f4", - "0x8041e0f66ba9dcee01e336dd4d16ae5e4e1618512fc147cc8230003aa2940848162dc2187d4130bf550dc1f3559849d4", - "0x999e8adc51bab54386af1c5e8822986ad1b7ecaf1f8a4c2baa5bb2fe9d10710e49545c5a8bd89ed0e61a3d73a908e5ef", - "0x89272ffd39b6e9f99fafdd58bd9dc00f66f26a1d36b38a1ac6215e3546d966739eecda7fc236335479207cef95cce484", - "0xb8e0b7532af13f15dc04a0eb4ea8abd67e58f1b1c6ad2e70c0ffa04a5c18ec2018b5d7f4be2f9f86db5e0b3986f639d9", - "0xb96bd11b0f6ead4abd5fe1e4c6e995da7583b901afd01cc05e87d04663fb997997d6d39dd9fb067c62cb1b1cbb67516f", - "0x94ab08914088b973e8dbd5685decb95f3bf9e7e4700d50a05dbf5aaac9aea4be2c10c83096c02252e9238ceea1351d05", - "0xa188de419b062af21275d976494c131ba18d2b2ead8bdbfa38a777832448e64d4d9725c6a1d530ffb6513f18d5b68d9d", - "0x8f73c8c118fa25c76a4ec5611351953c491452743056a819c8c82ba4737a37d88da0b55f837e7239a5f46d2c05a1bbba", - "0x894a44769e0be1c26648b0d89c4c9f46dbdeb3a71b90c493093bee372bb9f2d3f319850fd886d51f4f58db0de5641742", - "0x87d239923b0db024a8d9b0281111d47b0761d81c50652268b074efa3ea70d793e30f874a91ce33a4acecd0cf38c01951", - "0xb1b48b75a97f9fc2dc9530dc69f6268829dd0ddd574516e7eb1b9f5c3a90058889a7bcf3d378738e6d4b02f5fbfa44db", - "0x83e3ee9526ffcb60c6e75b75550fc017912ec0daf96d0a0d5f58c1b229cce90c684ac7c3e17fb998def8e7e2e155d750", - "0xb9b7bba579e474b0abdc7775ff5f84c9f117c6ca17788cf5a5f01b2c35a14aa39036031c8d799fec2cfb371d9f7471fd", - "0x90d7faf4891fbc368a32f575dfb69f13e37161ab4f63a7139be103285a49490c2851a907f8d36e09e7d1a190dddbc6cd", - "0x968c8b9affe18fc34a4e21f0d8c5518341c566099e6b45b8721c9912bab3693c9cc343406fe90279692a1eef2a3f7311", - "0x8735baaf4704207550f77df73fb701d9a63329993a8cb355ccc0d80daf950145f37e9b4b22be2aba29898e974f9fd552", - "0x90f52b2dccf525b9191d836b205ffe966d9a94f6c5800f8f51f51f6c822619e5abdf1257ee523597858032d2e21014ec", - "0x831209f8f5257bb3eb452d3ee643d5f063299f8e4bfea91b47fc27453ac49fd0ba3cf9d493c24f2ca10d3c06d7c51cd6", - "0xa5a4db4571f69b0f60fb3e63af37c3c2f99b2add4fc0e5baf1a22de24f456e6146c8dc66a2ecaafeb71dce970083cd68", - "0xb63da69108fad437e48bd5c4fc6f7a06c4274afc904b77e3993db4575d3275fce6cffa1246de1346c10a617074b57c07", - "0xa449448d4156b6b701b1fa6e0fe334d7d5dd758432a0f91d785b4d45fb8a78e29d42631bc22aaa4ea26f8669e531fed7", - "0xaabe43de1350b6831ef03b0eef52c49ffb0ccd6189cce6f87f97c57a510ac0440806700ce2902e2e0b7a57b851405845", - "0x91015f144fe12d5d0b0808c61fa03efe0249058e1829bb18770242f5fb3811e4c8b57ff9cb43deccfc70552e4993892f", - "0x8e9c570811ce44133ce3e0a208053acb2493ef18aade57c319276ad532578a60d939ed0bde92f98b0e6a8d8aabd60111", - "0x8b21839b5dc1c9a38515c1076b45cedec245d1c185c0faac1d3d317f71f1bfebba57c2559bcdb413d9d7f0a2b07f3563", - "0x90413bbd162be1b711e9355d83769e6aac52fdfa74802d628ff009325aa174c68f5329ddd552ef93e8fdcb9b03b34af3", - "0x8b6b02e3f9dd1031ebd3df9a30432a3c86e64306062ef00a6d1243620d0cb66dc76f8d0d412eceff877ff8768c2696ce", - "0x9894b41d9fc715f8f6addace65451f41dc5ce7b983dd8cb33757b4d7259bef12f144e0077d0b662aa847d5a45f33c563", - "0xa353a9740f6188d73aa4175a6c5f97898a05ed7aae9d2a365f15b91dfa7c28b921fdef0a32d90b6fb82718b33d3ddb8d", - "0x984eab8faed87c403c9979f2d2340fb090cc26d00cb4092aeb187c3f4ee1df3f57cb8363f7764073188790b16dfc464b", - "0xa5c5ae0ba435fb7f3ddd5ad962358da326239ff236fc3b51bd22e88296236b109951cee1b98f444302badc58d1b5bfbe", - "0x880be1006b0156f2788813432f450f613d235f41aba52a6000d2ad310408ad73d86b79f6081aef1e8c51010d404ba670", - "0x937da751aae68f865c7a33fa38d718f20e2a1c65cb18c8e08f8441f0cdc77662789d2793794dd0a427cad30cd0b33f42", - "0x9496fde66c834ff86f205897db12bbf9a9bb78d9ba8b5fb539cd0a2c927cc6b4120c017b0a652750b45edbe5f650e5dd", - "0x97a6f409ffeb593e149307a14bc47befb632412d70565c5f13d6b7d032acd2e3ed0f7b6af701b387f11d69ee4a8094d7", - "0x97ed94934263dc0260f4f7513745ed3483cdddb9adb85dc33193c3a8b4d52affaf1ded23b59c34651afbffe80d40dc36", - "0xb2b26378d44f916bcf999db218b9892e06de8075f205c7dafd6d37a252185c2d1b58e2e809c717963d25627e31f068e4", - "0xb8f9fa1fb45fb19a45223f7be06c37d3a3501dd227c3e15999d1c34b605f888123026590697d0ae24d6c421df8112520", - "0x997aa71e3b2e8c780f6855e94453c682bee1356b5ce804619ef14834475511105b1e4d01470fe4e2215dc72182d9909c", - "0xac2cb2a7cf55aaf990cfada0218453853047e813d3f51f5a623d09f4714da79de6592671358a5edf938a67f905b6cb5b", - "0x8d8340d0c3081cd30d34f3ff6191e1ff6ad7994b4ebac19e5936f1157ca84e1813228b7605ee226366d6bab1e2bf62a2", - "0x9693b17669086003cb46c75fed26ea83914a54901a145e18c799a777db1df9c9ca6b2ea3ee91e7b0ab848dc89cf77f19", - "0xa6b6b2a6cd8c4922d78c8ba379373b375d66ac6ea04b830a23d5a496cf714a9439d81c865da92d52600aa4e2e43afcf1", - "0x89cb665020abc3f5e11a03c7ba5ec9d890fa9ed2630f1443a8e45a28c32786ed980b5343ffffaea60eeff5b313bc0d66", - "0xb37b989106594221bc6cf33a1a83c3e65ecdef279e90333a9e105b8139dc28384bb2277edd4b77c9e59d15e6afe074c5", - "0x98ce5aee5918d18b2326b30c1ba41669cce20bc7a1d1b585363305fbdea66055164a7ac398ca0f0e670291a3061022eb", - "0xb57f472d5f34beb4cf430d7c0f8ac5bd1c0621a284633ed36e6f7804bc2b7847f54b469c7ea163a436510d9e3b32f97e", - "0xae673a6579dbf0504c8fd0c8fc0252d2f7ae8da615a06f4d215c2f8a8f516201f24e5cc42967630c252905e5dbbd6377", - "0x97c1501835a31091a5a83f0546e01c85ee847a0ca52fb3cc0653f6a826e13d25ddc623a5dea139108f7270a1fd7043ea", - "0x9376ee667f3834f6c0da4324fdcca5c04712e0649877ee19da79a2d23be24640c38758fce562470ce2134ca34148ffe3", - "0x818af89c40379a10074cfaba6d5968ecf667f1a68a7edaa18e8977ccb34e0829f237c5634fbd079e7f22928b277f1096", - "0xb8e0af0be0a252b28df25d4a509f31878bcddf702af0e5553393c3dfd4a1f1247ad8dc2668bc8dedc9b41f6ad8e71b15", - "0x811667ffb60bc4316e44bd04573503f5b4dc44d1ec824393a699c950e5fa085b146537ddd6a08a3fede7700396a0df7d", - "0xad834cbf850b2f61ce799c4a0f8ab0c57039d4e1113933c50b0c00175171aadee84894d1376cf325bfd434c3deb44315", - "0xa8b7dfcdb40373ba4d55e751ccfb9070554434df9e359fc165284ee3dc35db6fb6055657ecf5a9e9b7b8e2e1abea4375", - "0xb56a5b9fd41c9d3f65532aa58bf71a38fcf07782e1ae0084dc537862fa02e6d66658b19d6f71c39cd5dbfac418da1837", - "0xa935af5ed224b9533b41a7e79f872f6851591da9e9d906050ccd1b2c772a1d6d010c5fc7160c4f8cd7d3aa14c3bcdc26", - "0xa81e580fc98692567b28323fc746f70c3139d989fb6aabf3529504d42d0620f05327e3385c2bd5faea010d60dd5c8bdf", - "0xa8b352054cdcde8ddb24989329a249b71498a5593a13edad1e913c795dcad3d24789abca9c7ed1d57efcc9e3156da479", - "0xb0de8a2bd7f93284b2bc700e442f52ada16a22ad8d86329591547411c23fff0333b2ab0c9edf82bf7903ebf69916eed1", - "0x843e9781b653d1a427f3534b2e86add49d308ca247546f9fcf565f9e08df921e4d969e1b8ed83f3f849e98c0f63e39be", - "0x84a4098c5dca9f73e827d44025473096101affd7193c40a0307e3215e850e753e9a08e6e74a442d57626ff26df77faac", - "0xb463eaaa2f3315b511c22a97fad353014d840a6a95fe0d457d0677e63e571407d7f5268f8775381a5e7adc3b4163eb88", - "0xad0417edaa16cfddc288eef4173aa7057ca4f81e815541ac588ef5f24b98d56fed6845deb6ae1a9740a28bb1cd8780a7", - "0x9271963b8fb2288a96e07eac13c0543ec41abdc6d978bd7c44ae08251ea49994412b542c77c8208cd71fd8e7852d4a70", - "0x8b68b6db9044d8bafc155d69e0daba95cd59d6afebb085791e999afed4f33a2479c633d31d534ff767b8cd433d591a23", - "0xa6a06a0e433e385437d9996ce823abda9848754aa9cdd25ec8701af35c9ec15df999825669bbc2e17cedb597a96e8eeb", - "0x94d414bff8b6b8597634b77a77d1060db8e1af0d0ddfb737a9bf1c66c8430e93a425510af2464bce4a7b29bc66cf325b", - "0xb6514049562af1c6fb7d0e8df6987b020f0b7a6e721f4862e36b1ba0e19af19414ede04b346be22d348b50875803d1bf", - "0xa42c7fb34f2fbee8aaccd1d86672d0acdf4e6bb083ff0456512d7e1e43be041cc0924322fcd986e6e1bce5d5ecce6f92", - "0x867cbdd169a52440ae0a75d33a28c7d00aa92b4b65aaac5e62aa53a8fc367c08ab8828cc8fa18b6e7d1f908d158e3382", - "0xa6fe0b768fff3e4a6153e59a7b7508eb2ee8165eaf5274d41ac2812bd4563c4ca2b132f0e27ea2f1c98759cc3589b61c", - "0xb3eb1dba43d10b9e17ffec8def053fc96f9883bacb49330a089a0ca5b9ab0182e8b5111ad4aa55c1ce1b6f4afa5c70a3", - "0xa1531351098bdfcda566ff4d811301c0305626c77f954a38420c490e7c684f517eb1a4e4bd2c3904a10bac889cba314a", - "0x92278d106ad2f27eacdb86bdb1faa0a07a93765bb79dcff191873c52253af83480114b2299ffe5324f9c31d0abbdbbd1", - "0x8900ba95a90c447fb6fa1f528af3d7a378aec25feb0620516b6b97e54b328fc31af42e46a8ad5e6e3029d83a6f2bbe5f", - "0x86053d481179c1ac910d5e7b9a5de82794b442f20e854583512ce1f9c3f09e71d1bf97d6700fe776debfe1527ab97a82", - "0xa32a60de492fc4340336416bccbd2591b5e414fca0aead82281212e24490acc01747537b3da783684e27aeb987245cc8", - "0x9820fe8e0338f21797143f368177e3669a1f3894b40ae9fa3b353125f7c8e85cc424dcf89878f2c7667f65db3b1e4165", - "0x934d64711b4348ac5e1395cc6a3215e5643b540f591380d254165486b0ec2a1d0d21c7d2c6310f9e0eed3d08ecf4b57c", - "0xb9fd32d589432eddcb66dc30ad78981360915854cc44b2afeb826b5d48a08e377dc91be66f5bf1e783d1a8bb320f7ccb", - "0x98c972cf01efff4fc2e485b47572e2d8dde22461d127ef401b71a111b0603203971e3cde40912643affd7341cd27e57a", - "0x8db6c1620760063edabd376f4399b6e1355462e04f5c81cdcb3989fdc00f9a466bc85ed899e886c89c149adad69edbad", - "0xad7b7fda0aa6e2aa66a27235ac5cc680aa04b85dce329fc4be84f75c9c961120a3d9e446aa44539aaac8ea203eecb4eb", - "0x8ccb01eaf41d816ce69ebd57754859e263530915e775c4e7d9dac37b2457a9099b9ae9b4c6cb09eb5ff246e3c9320c59", - "0xb895b83b5f7ca46e02697dbaa6157df6c7571864c83e504a8c77d965bc2ba97bf9353a71c56a020df64498bd40e30b21", - "0x8018c07a81c522fbc25f2cb14f2321c61b98bd8962ed8eb7d5823dbe5d1958a5ec2fb5622fd0868e991bcb6cae016ea1", - "0x95b16364e94d01b3664812264d7185032722a4afc23bdd33bc16ae87ee61816c741657c37138d9312cebfb5fcfbb3b2d", - "0x94a709209990a8b09bfb4b9581ab471aae3a29526eae861108b28edb84aab6d28f1d7a25dddd8150b70af34bee4ca2e4", - "0xae06c80839c5a13269b984ff4d8a5938c6f4d8d647b1b1daa8cf7f6145340b76a286cd615ec251a65501e6290162da50", - "0x875cbd0694eeb90d3567da9dc7f570d97b02bd9cf17bfa011efdd48f1d580608a3213bff4006603b8b4079fa66bded10", - "0xb27f88c455f025e1cd902097d6a224d76bdf9c9195adee30bef4a0b0411fff980787285896e1943a62271d0aca531446", - "0x8024880cde783cdb2b863e3dd856be92bacc5b2a1347e96e039fe34279ce528560d2df7d4d1624a4595dbafb40529697", - "0x8883d02c2a5c0e026d941c785128d4ac6f7a9de625ea735b7d6ff27a5ba10fa4d6370d450d99a855d919f40d64f86afc", - "0xa1beb985c45fdc30ac536f1c385b40b6113ef6fabc2f76d255490fe529468847a776efa674ba8fed72180f07d3f701f1", - "0xab83bd9b007561695210e3276fde72e507456ba277ad4c348a2aec7a6e9ebdc2277cb4bd0bca73bd79bd2240a1fc4456", - "0x8db27f516153812149854fd6bb1250e843a3ae1c9637df818b08bd016a769d0497ab6087fe3b2fd4080882713607bf46", - "0xb3891dde4e00d60386aeff161b4a0fbc30bb31ee7918ce5fc0b49aac3238a000ced192c9c4c08d90de3a0ba973d7cfd6", - "0x90a2049a15c02e59024a7a1cb0adea97501c60b1c7442fbbe560054c3d69264e69627ac57b7d9be01bef498bb2a60198", - "0x87df67a4bd72444b5faa4f3b067204c4927c869dd3b29ad192d859589a9b2c1d6d35ed68310081e140add254a9463092", - "0x8f80986a8dc8a0d6408ebbcb4f234e76413c11cb0d66067f9436bb232373100f20a4fded60f08dec3525315abfaa8523", - "0xb061e10beb12ba3683688a4ae3a91600d14878ef78a308d01b93e4918efc666450e3f7b0e56283468e218934231df98c", - "0x86b9e55f3783d62e381659d3e06699d788b88aab1ff99848db328a83c97d223f602201bf2127c5ecf419752fed0a224d", - "0x858d878e29925c87243e010020007f96fa33264e89c8693af12857b362aee3fac2244057e159651c476ebe1dfbd67bcb", - "0x8fd47cdef87d7a569ffce806d2c2dad100692d6c53e5f5dfc6e274f897dccadcee30fc6c6e61373961bbc1f3ecbfa698", - "0x892f2822daf3df3a759bef03168c1cb07408df62e024747a788e94d2da325f880bb9c6e136c7f6643f45b021c6ccb654", - "0x8714e37ac24f5a198f219e7c88a92172fc3db129e044e914663ac708d8101851e7c53fce79d32d0e6da74f2ccd1d30ff", - "0xae95e1dbba8b9e2c8dfbe1c202e9ccfd04fa396470035a699b902fbd86d5e6a31732a7c8cae00b9a4f6e51c8d560c7c3", - "0xb0cd058e77498e860fa20c5f8d9bd09bb249add1badf84ba8d1bd49e704b9b4bcd67a5c3d211840a2c8fefab3fea639b", - "0xb78e468d3a7da0dd481f333ae56534e2ef97587be2e259a458e25aa37952aed1cc5f835640f812d8052f5bada8f57b12", - "0x835de7965c6b26e7ad1b92eb6f0261d1f376fa12d61eb618d9b342b597c9c117a5a8f6a36269aeea88072b4641e6b5bf", - "0xb4d0eb99136b3643468c9c48a20fad62785a60fbdd3c054efac4bd1fa7979b4c9ca6c2c0b18069c0912bea2f19832790", - "0xa00c47315dc0700a850966836a95f3cebfde04dd094bde0742dee77b89a05b5ad655921f86fafd1e902938ff34d4c58d", - "0xab13fa0afaa92229a71ee91efae6d1b15f14b6eacefffb7401d41d0d6db24e24a8dbe8ee19b4680ecb69d2a0cb4e84e7", - "0xaa56c0fb18401210062dbc653df8e3732aa8921a1280e9737e99b26a0100a13a9cba8ad0317a69bba16193362ee0f030", - "0x8b410324a6406b345df0fa25f541ac20b7313fa55832752f70cf4c79f43b0bd3d5b4cdc447e6ba7bca08d0edffa8e29c", - "0x893362241ae412d9e5df46506407595c58ffbd7fb1fdaf0694c3432470599291238997abe118bf7737e56a4f5c9dc292", - "0x921618194a756be81cb49d6357cb392b32cc62d96c8ffb7e16d9659a0f226a0436bd378da7b835054dbe0de2c6372ef2", - "0x94a2904f10994928ff5367b777e1430047736fbece33442cf452018bfdeae62e84cd75cf80f8468285e347d504c94111", - "0xb4b81545b767f380bfe10e0fea9c3cc62ca8db40b43c83ffb245259378731298e3eb6c3bdc3a16932f88f5d8a86edc4d", - "0x936203c2453ff01c6fc635e4d54320d69e60047d805daae3b75633c2259108497b778f011e5a057249f11b2b888ea76c", - "0xb90bf6378d29339443c3f2008b1e2b5f0345f86e393027f14a295e583bf6e6c2b10f54b6dcc42079ff0d356c405b03bb", - "0x916913f550d327de2d8d6c7723dcef2e3869efaf95fd963d95c8980b97748c61ad8e2e629cead8577266d93fe39203bd", - "0xa033c6f3d5ecbabeb83eb363e54e5faa7ed2d7f4fb771b161762c4f003eac4e1afb236806b784baf2222cad54e2d3cd9", - "0xab289d4a5771147e6c29ff9ac2bf65d70081ea6c6af2d9b728c3c144574a31b5fd8632af57c18c389aa2cd994938bb0b", - "0x9488da2019ff13e290eeac132b491df58b5b7b23c2898ff1a67bffd7e9c9464c39bc8177a57950fd28589e3d9ff9c6c4", - "0xa5abe42b2e0891851440fb2aa6c1d8a86b571bce8b80c8e9e2692e5cb6d45a1b2f055c9fc4c74a7cd292871604129ea9", - "0x90bfef698e83c2ba4dc9304aa01edd274169a978b7154bca518daef394f55857d0d1922ebef3d91fc5ecb3b895d9e0ec", - "0x92328f1372b6406ec80786041b6d57018b8507e3881a08727aadfecfdfcfb0824394cbb1150117ac5da5d71b89e895ae", - "0x9719751c5f7a65ae2bed8aff7b4b8c34539ff011b259b7ff54f63f9d987b3fbdce5c99534ed561aadaf07bb6e939e208", - "0xa151816774aa9379fccec21cf212429a1c68cf91b055cbb9d931f461a8d5616c693331a11ac5c6fcfbd17d84ee0b44e4", - "0xa72977b1285618a45943ad00f33f37102e2885eccd2f76785254eeca495068fb1d8d49865343e9e8313c6c2c3b2024da", - "0xa6f5ad2e023a1585d90625c9f7094f0e8851c79f0eede8ec582ee8e063407cc5b8298e5fdc4c786e4fbbcecaf33e787e", - "0x82901e008febcea0c0a14ae21d985a397630e18ee6e346f4a449f23be228e8f338df567d30211a11180b94fbc5204bec", - "0xb9b57fdb8d14d1be87a25f89553b3966eb7869e0519ffdf4cc4d51f4cec90d68f7b81cdc0450e04207276e9c63ace721", - "0xa06eabcf43585a001448f3dc30411f3d5b74fd0a695c81eda9981842ba2bb0081d3f5a8360aa18b6d43ef13ea78b293d", - "0x926fe48a7e8f07559b7237beff9504476dd97b5b4d67acd01a3633358a6ba4c7abed5c87683a11209aa2ee759888e00e", - "0xa716cd3a84a963e2a5a46145b6ef4ebce705de52bf2945c374152a1e41c228a9c4eae0b6d1e222c1eea8b9c13c002177", - "0x8a9b5985df6fb32cdb06ba1591a977545444478f2fe985ed1b10de61c630f0a4693c2185d63f0dc0256b208072c43b17", - "0xa8eab26ae0ebcdf96a59fad1dc2d5e83b94abb2ea1774b607023f9d9e0fe065853b1e2242e794f989a80a47f550c0bd9", - "0x84adbf38164cd04f3d770a7f4b8eae7a5d25b4a803fb63c02b95b71b33e454319c44e07a760d22bf5f58e7e372d09a16", - "0x90f443a3ba1b9129a0bee400b5b29d42e50bb2aa56b0022bbfc3c6f8d69db40299871ec7c1b68421cc89e1af6b13a39a", - "0x81c5a94b379eb98c494a8d0067c748ba47e87a2ada0105202ed7651eb4e5111a0cd8569b06ae68d392c4fd74a37833d2", - "0x8f92324b14a1549ee0b186073a26691088e41556d33b54258fc6e0b000e9624156db4e97861a0ec22960e6c47ca8a1dd", - "0x8b021cd0fffe055068cc460aec3cc455952e2ac32be5fa060e0d1b6cf30ed15381618f801249e893b1b9f10dd82077b0", - "0xb3e9f0dcb3d6f0b138f589fa54dfb01f849890ab97016372d004aac55103f363a64bc0e606ddf75430f1534a30fc522d", - "0x8fdfe64af891db89b25daa859864d479cb7599486bd6f36e593f8f2f839f942261ffc3eed5001a93fde44cbcdc24c583", - "0xa9e4554373c5073e135874e2bacbee69c65308eb0785532fec6a37834e8d0b437b77a2f11cc63c87d7183b82cd9b6bc9", - "0xb4c47daca723ad7193ac5098cad4dcab654186ec5ea5c0fd014a3ac39726be954565a901694ba211820c011fa1c59e18", - "0x8835427e86cdceb4c11cbea331ed724e4e78af15e3bab5be54f6b926bf66b5d99bcc40dbc456d86342c9fa83a033c2d5", - "0x8ea84590a400cedba047c2661378921a42f5ca0421da58c1bcb37bc686a2aed98afab3fa5e6ba3a51029390ef3cdf4d4", - "0xb48551170fc479d69fffb00fae4fba301e92e37cae08f596db6f6489c3b7020edc074f9e8d7465b84e9dcef1b6b3aecc", - "0xa6f318b1eaab00836a330710e88bfe400395b3081485f6a212e3cba9463f6fe7864ba4f71e57a411ecdf2bcb4d189f96", - "0x848d5137a39999141a79f4bdf91150796ba36352d8525821bf3bd6e070b352792d79147341b8254dd60fa8c36e9e2618", - "0xa8526f8904b1eac4ae2a25534aa91e8031e9aac7b8f58d8f49897e920c36c0232f4a30aa6eed305deb0f7793c115b267", - "0xb8b6a727c44c37a8388383e959d195d1d0e51a657d4ba360633d219d43c5df645383e2406c25f1d418e72b862c3a6e9b", - "0x92e64adf65b42c978f36dd03ab22ba983bfbb61944efccdb45b337ceb486beda99818bf20d32a545503c4572bb0a4983", - "0x9653bb83df66260a0bd059cd4244ef7c661b089e403d26ba777d2090783ff31f963f5d3a9c125b1ad1a1d19134f3fc8d", - "0xa74e72355e71ae5eb36dc75191643500ca3e67f18833ee981010e7e7e60a68e1b01b05901eff05014b9ef29aa4829f45", - "0x8b2139a5da14524cf6acc593144db23db424b95b8c7041d8f6c7a14a6725dda1cd09c42bb3ae26a5a3650affaa742800", - "0xa60ddff4300ca44a7c7a00a1f98441ad1438e07c30275bc46551cee1b681926d2c825cc8f90399ee5f36bb9fbd07d3dd", - "0xa04e5e9958867a5acc15fdea0d88951cfebd37c657102f6ba1dcdaa5e46cf1c823ad0d98718e88e436f260b770599102", - "0x95e977abeb70d46fe8d7584204770f14c856a77680607304ce58077550152733758e7a8b98b11b378540542b1175fecd", - "0x8c9ec93ed35a25ce00d61609e92d567459a45e39922ccd1c64ab512e292787125bd4164c00af4cf89fd3cf9deddcd8bb", - "0x819819ad0338250d9c89aceda9e217df12ac54e940c77fb8420575caa3fa78930689d0377ba88f16d38179a807135dc6", - "0x8baafb379d4150ac382b14a64788d819146480d7a1dccd3deef6889686ded375900f5df069843ef14d754ad3d7540401", - "0xab827236996bb79b447714c6993af941c5ae66248df4d9a6f3650d44b853badb5c0cb67804210e07a7b9d66ca43092f6", - "0x927656c3eac8d2eb575e3daeb77f9605771170c325bee6aeade10c083d42bd8dcbf3bcc3d929ea437001c7cf9a95e2da", - "0xaf22b212d5ee44fd4197966b9690487c38a119cd6536cfb8c181f38a94610dd9e057f95774047a446504dd96dd11e326", - "0xa44bd94b9e01e3ba36340f2ac2201ecb477495d4f1fb6726a6b439302deabb5a35d237c6a6aeb7e3b0a65649f8656716", - "0xaf367aeeae3bba14fbdb05bcc1a521000dd9d37f5c34ae56fb306d3dfda201d0329a8b6e89d98e15825cb3c6bfdb1194", - "0xabcc4fbdea43e50ded9e2fb01464f4e87fb136e960141e8d39214f92794cfab5634f22cd40b18d8c0e501f2307aad23e", - "0x920786cbd674348b9853689915dfcab02cce2a4596d117962bce36aadddf4bdd143891e22f2c8015517039a64e8aede3", - "0x8cde63b9bd57cb3ef743f1f3e8250669eed739e5fbd68c500a3cc0c12f93862a69aebcdbc69dd8f476c2eb307f572a53", - "0xb967e65a5f1cd8d5d570f5e87e7e186fba51b9504f8e466392a76d8a971fb91fd9b7565bcc1647f50d7d15e48b93bc95", - "0x8d5a87b25fedf5edd57d870304bfd9081dc78c3e3e3b38b997260a92edac7feccdaf24feb51822d2edc223b70bb4ed5f", - "0xb6cd5d340a57f8ec73723c4f3ecd6601620dc8137a3e75a5d3c578bc79a9cae86b379950c644dee2ff99dad780d025c1", - "0xb6f0a8e754b7f52a85a2a2e6512cfd017f7fb0418d19bb318308951c4e242d3c65bbcb9748da9cbc91a738f9ca577332", - "0xa89dcf7d410bccec385400dd96b1cc6af89026a431d0f531aa992cbd7bc8bfd7c5f360bcb665bda1d72efa17bb982551", - "0x97788e7522427a46c4b6258d15623ef7a565712812fa80d001e1de8dc1791392702f3fa3cce5a8cd1c5755625a0ad10a", - "0xb5338fb5e137ff625b27c5148298f27ce8f493e2527c5d0facaa49f29cae34580d0d6c3c1074a2e46cd8db3f56004ea9", - "0x8962f006d7b1095dd0dd132ffe7e87e328510c95ad893cf3b2ab21c177c5cf2c27f47d8856f87e9762c547be009d25c0", - "0x87fee9ce9c26aa476e67e0791a809e0a06a8a98facf3faea730d438d3e516cdf75d645fa75c906e4e44ab9237a22c016", - "0xb75ab972e1a1214bab0b38cc3e973d44bb233acda5b4291f5e110b6fb78fdcab93dc63f01168debd898e165f615be1f7", - "0xb5a0fb52bca279d3853761a94b206acaf313df33ae6303d9b71edae90b66fc507adbc60fb11e758888736c81d5d80c0a", - "0x849b8f0005010e684701cd3a4e59e8c89e5fec59af6d2de5b6332cde03b865ea84f07f0b80ec3404380b0e148fbd2c24", - "0x96e2b0b6fe78408f9208f809f5c40398100b2dac202c8c5c33c2189560dea868270a598c419871a5a2b67783354f6014", - "0xb234b81f996142d0df2c719760bf996544820a03195a6dc0ff6a72543692f5a369bf63d1f0b477ef2fe7b3234e41f685", - "0xb85e39bcf40da1a12a535740176f4de749a93824079deb5fdaa004f3282fdefaf5275e3418c88c419bd42a3dd2ed2b3b", - "0xa27279304b89a18a4e2b443246f2368fb8b15f46a34533179b6bd2ef683f6e98e222b7a32880b39b8fac1afa90133803", - "0x8923c22cf15c9c1964213d725b337ece9ea854775a06f75f232c4859c7142a3942f418354e33066298aedfba3cb27e62", - "0xb109f714311fb9bc431ef57911e2cad6a3949455b9f23255cd7edea35be629e07f845fe53e2b12a32305ee2f4f264f27", - "0xb51e82ae5c7d48050e405897d0053e9ea4b2714d002e88f78c9a307cd50b9c6b3ee7cb86f86527be9d964b01895fab20", - "0x90db256931c7f98bcf3bffff4d496739185e7a20f329ee7bffd4e0850a37739948ec745285703967f4ca50ec370cf68b", - "0xa0485ac0445d88dafac56bfba2563b020cfc370f54c1606c89d12cfd8a4d1336d2ba50306e476155a6f5b0e0a1f2d092", - "0xa00754c3462e74bda928da855bbf90f9077db395e32f03cce9b2955546d900b72330d247b7d607b65e130f5b0d883de0", - "0x8547d56727c3ad8b5c8ce622ed9ad86fe8cd78e6e4848c9845914b5063b17330bd10b46d8d3f18f83ca09ecb28d1afb2", - "0x95b937b2a979bce0e159ac75c7d5d659be8599c92305e73e942aab414793364a3ec28c7c1c8491a5750ba84a29828d8d", - "0xb011e150f0294e45a0f4c69409999d0c2e602449dbd67ab95e8258466687cd733a0329083a31b03722f4e2580ddc95e9", - "0x924651a733ad5e5d9adadad3ea6a6babb8e455c8d5f2cb5bdc83fa422e7752592190ccedaa827b866861e73506a6968e", - "0xa4d5180122f8e31503ae027e54da50f72f5cfb910a6f7309bd882b5cd666f454672591f1f20e461e182a47d03b47052a", - "0xab19ae659c4f73ea3d21895269dbec583c7029955a36469124ebe295027010faab56c4a475973497f28e9a77c03b8fd0", - "0xae7ea1a803d0f439e91494f8f35fc1167dae23834c0c699ffe65d3da8b09f8df5a53195a99ca7b8558242279e69578fa", - "0xb9d63cf0e30f9800101b43b980bcd2f229758e74b21ad5354866b4e684791c08a184330dc316228a0d67fe0210f2bc4d", - "0x8c41629744391ddb96dcbbf9cd99b13d36e57d65962e0aeb92ebccf1c4cc769626feb3ec0363def08eceb102b3dd4ad6", - "0xb2848ff24faf9e667a8c19d050a93896e9e75b86595f7b762c7c74ccdfb9db126ae094961fee7f5d1192776c1ac1a524", - "0xaf013bc29206743ce934d5887b8d0fb3667c89bda465d2321835a3618513fba6a459dd7566268220ffce7e0c97e22b2c", - "0x8bb799e36db1132da8e8b028ea8487dd3266b4628c56dfae4ea275f3c47c78e3d7445ab8d0aaee4cbf42148b3a148175", - "0xae2b81fd47c038b5195a52ab8431f0d3cab4cf24c4237252d955aad2156adc16dda9d3270157e0bfe5a44022e5c051ef", - "0x8e0129213b1698d2ec6df132356805a8633ba79e672e586dfef664ffccca71834253ba14f296da962651fcba2c002622", - "0xa1ae30b500ae77cd9bbb803d737b4a5991cc780618ac22b5cc179efd8fe10afb8c135457f2e7b86ded485ea12eae70e5", - "0x8a39723077b7c0df6e3bf6548afa3910c214ee275951fbe5155a39473be98099626ea14d844630a6fa90292b9594665d", - "0xa628386c79b61aa7314b01d9814aeec20c2a66e3deda322a39957e7135c2e52b1da486d1b9cd61c87afb22c1d10f6462", - "0x97867f469b01249820aadd9a54e12d4fdadd4555f2d530450e1f8f6d2dae57360578e2c2c8ba41e3b5950df596537a98", - "0x97f192d0457c217affa5a24267dd16cb4c01de8fefde9df4884e1906d2f22e73382dcee6c7d910bf6430bb03f4a4f1e1", - "0x86d5b5739de8442dc74d0d8dc78e49210fe11bf8c6ff0f0faecbc47b64812d6b28c8afddf6d9c0212f1988451d6ccb1c", - "0x8ff3312ce9693cd4a9f4b8e75bd805f65b0790ee43fd9e075fe4cebc87185bdf161335049819f22530f54fed2779a5b9", - "0x8dc41d85548bee5d51941d55752a500bde3c5a8f3b362da4eec307a963968e26605048a111c9166d448b8dddf6f53892", - "0x996bdfd004b534151e309ac925fa5ee7801c9da4f6b4c43e156d1158b134535a2a3956e1255e0dd72ac2af6bddaebcaf", - "0xaead652704b788bf4983c8f725c644c327a6e9f6683215f5c826c09f82fd2e40631791f51d14e6aded91fdc018d45501", - "0x991ffab58a82b98ed8fc7b00c3faca153589fe09cebf6a137ad506387a1ca4dba475b0e4a1b9bdad829f1422facaec39", - "0x9652e6c4ae084221d6bad855ec0bc11b5f855c6efba67f644e0902ab790a98861cecc6ce047c68273c3aa7eeb2f4c7d9", - "0xb88b816507aaeea6dc92b861eabdc96988b74d7883f20a4b30ba249158acaff3c50d261742fc9ad2e9eba888a8d59065", - "0xacd028a51e16c07a10d2073b9d03070457ac5f1246365295a1359d015c460b92b4861125fabe6f114de8197045df408d", - "0x806d3cd9d02d41c49179fe7dac5b05dcfc9a205a283135d4f008d0771c58e6f963d7ad0f6798606edda718eb5c7ff3ed", - "0xb9b71f1657a6b206fc40159a941e127f252a7b324dea864ecd804f48c0ed86da9778a925fb65491204a92bc2a26fef32", - "0x80ed67bd0e74350c875abedc0e07fd42ce7cb926f0f3fb1949c6ac73f2300b5a14a5c6f6ff8aed99d5ea5029bb8e7ae6", - "0x9875f67a7a473714e4dd75ee0c763ddf88101532d9680724b3848fef69e218b04a96b90f88e0f4409aa40b9a21507ecc", - "0xb4a2bb1b421e5243e5e7576a0672dc19f9f70315a03f6411c19f76616ffbb70fc5dc0e57fd4ab85e24ea2261b7ce38ab", - "0x879723002ce43e6c75ba2246f51436efe3376242beff987d025c3c4476495af32d52a54fad5d9ec329a442b93bcff1ce", - "0xa4121efbefd9c3eb143619afa52a916f199c75024908047763b29466cdfc837c2fcc894aca63044c33c41c777e529b5b", - "0x895f637b497a9766714a3d9e3c275a1f0c9ddab105bf4c8b7e663f36cd79492022415bb4938c1a4849bda73106ace77c", - "0xb119acb8b161ce4384a924645a248a656a831af526cd337d97e08405415b9dd22060849c76b88a4785eb5e7214961759", - "0x802e712f4c0a17009c4be6c1e5ba2ca3b82adcb68793ec81f4489b7985babd8a3873d544de63d5e5de0cb4dc5048c030", - "0xab111051e4651b910c68ecfdc33f2d99e7bf4182df68cedbdbbcac219a543e04d93ecb2763fe32b40c095c7ca193c331", - "0x855c73ef6afc6bcaab4c1e6388519fd5cbb682f91995bebd558167715db454f38012291beccea8186a3fb7045c685b67", - "0xa29d02ec6d9baf84c19dfd0eb378307703bfafc0744b73335550f3cd1b647275e70215f02d1f4ab82a5df4d4e12dd938", - "0x91510a45b8a50cac982d2db8faf8318352418c3f1c59bc6bc95eab0089d5d3a3a215533c415380e50b7928b9d388ff89", - "0x8286e7a2751ca4e23ea7a15851ad96d2cadf5b47f39f43165dde40d38ddb33f63a07bc00600c22e41d68a66fd8a0fa51", - "0xa413d4e619b63799dd0f42ac57e99628d338b676d52aec2bb0d1bb39155ad9344b50cdfe1fe643ff041f1bc9e2cec833", - "0x85524e5bb43ae58784d7e0966a664717289e541c8fcaff651541718d79a718f040a70aa8daf735f6635dabfc85c00663", - "0x97f0d48a4028ff4266faf1c6997b6ad27404daa50ca4420c00b90f0b3e2d82ef8134d0a04108a74955e61e8dfeac082c", - "0x8df6145c6cc39034c2f7331d488b8a411931c8faa25d99c5432831292637fd983d4f6b1a6f55522b4a42a462d63c6845", - "0x98c2060f67a916991b391e67fcf23e5f305112807fe95bdddb8ce6c4084126557e4c5f003afb32e30bc6808b30d4b526", - "0x8964246b3c2b8f7312f0a99647c38ef41daf70d2b99b112412356e680185da6810ab8ee0855ad7409d334173bcc4438f", - "0xb56c2c416a7069c14bdb3f2e208c5a6ad5aac1cbe5b1faf99dc89c7141d0259d1c6250be9d9195500c4a41182ad2ec3d", - "0xb7864583a4cae3b1083dcdcff7f123d24a69920a57d6594d0b7219e31bf0e236682442b6499a1f6795cfeb4f5f236695", - "0xa064f94139bf1b70d476bde97099631b1284aa6b4d87f16bfc65c075e58b2f1b3c2d057605259f806e545674a1169881", - "0x80d1bc4acf14c0f487cd57c5d6157b7f38917e93cb660f1c25e474fcdcac3c3dfda50f6bcccfd6676bae25c4b6b5014e", - "0x8ad9a4976c4e3e282843518149fcf5d454240740f4b91466f6310b7216d23d70b9b47c42870293252f29f092f330967a", - "0x914197593d2d99d784c704cad7ecd3f0b9f55dce03fc928d13e1a1034566c4de754f1c2a5ade047b0956415fe40399ec", - "0x8d77f5e29c572ec3c0ca39cbae2072ba4102403265b3d8c347a00386da9c0b8688d6e3280c96037c300d57b3545f3773", - "0xabfdf79d935fd4f06a04938d6580a8cbf9735f0d498f49677f26e73d3b34b7075d525afcb4f14ef1632cb375bef7dd55", - "0xa97a8c446e3edc86efac7bda5e2e5d0158c909552a3bf86151df20ece63b8d18b608f477286fb1c7f05605ab7e6a7c2c", - "0x8618d946c7fd62486551c35486fa466bdfcdc63c941e4cff5a01fbbe566b7ea9dc763cbe73e2acae063060b619a212a9", - "0x8d03ee468070936004b06acf64b868963f721f37faa09887f8a82c155ad5c5732572a6855b531db58af03b1afe034a18", - "0x8d3247f75966ea63935ef6049f7c889c1651374adb446f49499fc9191dbcde7ea33cbc1f1e2d3d1756b6e69870404643", - "0xafc853c3a3facb4ba0267512b8242327cd88007cef3bf549184ee891b5ddc8c27267bae7700758ad5bc32753ebf55dae", - "0x80df863eaea289de5a2101f2288046fdbfaa64f2cf1d6419a0e0eb8c93e3880d3a3fdf4940f7524ea1514eef77fb514e", - "0x8434b5888c2b51d12d57da6fb7392fff29393c2e3bfee8e3f9d395e23ddc016f10ebe3e3182d9584fddbd93a6effcefc", - "0xb78cbb4c9e80e3808c8f006dc3148a59a9cace55bcbb20dd27597557f931e5df7eb3efd18d880fe63466636701a8925e", - "0xacb140e44098414ae513b6ef38480e4f6180c6d5f9d1ca40ae7fbadb8b046829f79c97fe2cc663cbccd5ccf3994180c6", - "0x936cb8dc959e1fc574f6bb31f28b756499532ebb79b2c97ff58b720d1cd50dc24b1c17d3beb853ba76cb8334106ce807", - "0xadda2116d9fab2c214ec10c0b75f7f1d75e0dd01e9c3e295a0a126af0ea2c66373d977f0aefdda2e569c0a25f4921d0e", - "0x89a5cefb80c92dcad7653b1545f11701d6312aef392986835d048f39d5bc062cabc8a9501c5439c2b922efc5f04954d0", - "0xb9acb52747ce7f759b9cdc781f54938968c7eeacb27c1a080474e59394a55ae1d5734caf22d80289d3392aab76441e89", - "0x8564f72ce60f15a4225f1a223d757ebd19300e341fd9c1fe5a8ece8776c69c601938fa2d5c21b0935bd2bb593293272b", - "0xa5567d7b277c4ebf80e09c7e200c20d6cb27acbaa118c66ef71cbccb33ee3ddce0e0f57b77277ae1db9c66ed6e2d8f30", - "0xb82e9c2d8df1cdd3b2417bf316d53e9f3cb58473c4cb5383f521ef53e0af961ef916e4f6557a6d8b4655ec01415231cd", - "0xaa816dfd2814c8a25bd2cbaf66303ee49784df471bac4b3188074ea30816f00f425234454d40d8ad8035aa925d74da36", - "0x9919f384df20faaa2d226b521cab207dd2b62420d25ebbda28c9b2ca76a2a52203b2ad7844c1a25f5c75f005c5a83149", - "0xb24a6aa35c2d0f87e36598b36224c64427cd69642b6f9c1bd478a62c70f8ee69f85028648f6603b4f04fb21355f2afb1", - "0x892e044bdb1276b455eac2204be105e1821f987c2570494b1f32aa09506caba7ed343cd09b1bc126fed5e0fda3d0eaad", - "0xaf0e01a3ad954dc048de18bc46bb1c4971db2467e839698e4dd05cd1adcb9261013fe9fd0cafb946c0b586f6aad86d4e", - "0xac152f0a9ace425378daf02510eb7923ff1ed2c0f8d1deb918e4efb63655de1ba58c96438e9aa23abdf2431dc771370d", - "0xad8c7419c097709347e2394195924e09617b47ac5c7a84aeb9deab8975f22155de0f70cf20d8a976551b14e3a2683a2b", - "0x808f14f67ae801536fb70a5898ab86e50ad35340cffd0648daed2f2c4564c9ad538034b2a179a6a8bfa27e9d93b4cbe0", - "0x80a74ab7ce4769db93cfa695a166db95f0a9c47885ff826ad5d93310f36d6b18b5351c67c858b9837b925e85a1995b63", - "0x95b88c3cdd64401c345828f4e4754b1a88b4875a14c08a668b90acd499b3b858842669ecd73a46c5d9f1de32ec1a0120", - "0x8ddbd770b7b18a5917eb43926fa05004e819f1d1ead05b915269e4a86b53e0633a90559007e59f6705a3769e2126ac56", - "0xab6db5fc220754f19948bef98844e6e38dd623565d1695e1198040c228ac4fd863c1f168cac1d036bbfb718d9d8dd036", - "0x97bef628e977c069e60c395a17740e0e1bc1828f5607ae7f30ce5a0c95f02b53af2ad062700a75212e462aa22c3c5465", - "0xb68d465e04fd17ca98501e61eccb0ce30401855e98046e0c1debba71c2153d6a7a704aa36a6f12454696e78e87181cdc", - "0xa79cfdd048f4181e005bd0fbac0a8424495474956b58ce858d2b700fb0f931c406282bd33bfa25c8991bc528d12a69c1", - "0x843f55fa0a6a0969daf2b48080738f30b269b2e7ec123a799e5b203c0b3b4b956dc95d095bc6550b0013918cdff8a225", - "0xb683cdf2823036827e5b454bfe04af9bec1850d25a7a7a44aee7696b6ff0468b7ed6885a41dde2b8f3ecc4aec880c3d2", - "0x8b500796e82acdc89778e0c0f230f744fb05f762000fee877bcf57e8fb703d212dbc2374887bdc2e7b7a273d83a85798", - "0xac35a8ee87bafecb1a87f15abc7ccf4109aab4ac91d357821e417f9b1474d196c38cc41cd13667f68d1ffab5e79a6e92", - "0xb6e517739390cfed5b395d33b14bce7cd7aaece57fe79a7eb3cbf150dc10765c3ea9fef7976a21a2243687e6eea38ef6", - "0xb53901eeee26692273365b789f2a60afc9b5f0df229c6d21b07016cf4c0e7985beec748aeca52262f68084393ab038e1", - "0xac4804f33d8ba2b4854ca3537bd8bf2dda72d4e94ff7ecaaf9bd3b7f098343d74d765471ef80072ae34f860b052cbfb1", - "0x8c6a30a93f1dde18039bbdd1ef294552bf79856e20bce863e4b8dd72d906be3ff22468ff3610e06b5a7d1745dde7ead9", - "0x88f0607fa3b7cefe20a02115572b16fc3222be86bb19e592c86c48afbe7e0dd523492b0c29a3bceb9a20f5538bc3134c", - "0xa660b801bbddad725975ddf9a8f606f76ecef831f954be224d6178c368e1c72d346f00c4a4c95c289b62d36f2af323cf", - "0xa75b9a6aea9542b698938dcd6cc2f6fe0c43e29f64b2f54aeb05d35fac73d41aa7fd750af4fa9333644aab8db90775b9", - "0x83e1b7129d963d1cd076c3baa5fe422148e939273db173e4d59d1858a7d841eacac7fe817d15ab8f8a493bf46c2045e6", - "0x9060a2e9c24de11f9c70e039b5ffe9e6d32f1ae39f3dda263610df2265d917679e689898e4a8bd84ad34613dca5e3761", - "0xb42fc8b863a2af15e04d1fe6693c09b46007c0b8298973fb4762b45b4590ad7fe0aa758918b2fe5ed1ed0359754fd955", - "0x83e6de7860fb256ecf7b47506a5e557d0fb0aefe57fb513c7dee2bd9604712d08ca26adca7ba9a54b712372a7c585a26", - "0x90586e9cbbf71475ecd3e7b5753b286804dcce61e165502a82b960099e79272de8b7494b8877b54ae838eb5d0f71af2f", - "0xb2e4b0d21208f73b7b75e08df80cde20c4578e117d37092a490af82354e2afd3a7dbab46fa2d12fcb731cdaece69c2ba", - "0xa010961239bb8809fc7fb4aa08fa30d33a130f9f417ee9ea60f587dcc5ef4e1b7abcdcbf8e848ecdcb7972ef6af46e78", - "0x8f511fd58d1e3403a5eefdc0a4ba6b8af848c7efddbf9575ee84449facde05ae9a24aa41a5725416467f6fbd11369c52", - "0xb24ebbd2d4482eb618cea1ac4fbfd9ed8c46c0988a27259300a7ce5ce1bb256aeca0357828cbbc4cf0dfafbf586040e1", - "0xb3ea29e9cca55250e9b7b9bd854edae40f0f0cc65fe478cd468795d1288cc20d7b34ced33bd1356f1f54a4291faa877d", - "0x8a8b20f222d9e65bbde33638033972e7d44c6a310b92a9d9c5273b324c4ad1a94f2a10cbce8300c34dbd9beb618c877d", - "0xb2436a9a647dc3f12c550e4ddc5b010e6f9cb3f3504742d377384b625fc38f5b71710a49fb73ffaf95b9856047c98201", - "0xa13f8b77c70621e421be94c7412454adc1937b9e09845c2853ef72cdbe500e5c1bf08e3c8b8d6b8eff4bce5b8dec9213", - "0xb25de8780c80d779e6c2e3c4e839a5a107d55b9cccc3ad7c575f9fe37ef44b35db4c1b58f6114a5f2f9ca11e1eb9c5fa", - "0x96ba6ad4358c7a645e5edb07d23836cbd35c47d9a66937d09486570e68da3c8f72a578bd2e14188d3acc17e563a652d7", - "0xa7f55989814051fda73f83b5f1a3d5385cd31dc34baf94b37c208b3eaca008ff696fd7f41e2ecffc2dd586de905bf613", - "0x882d0c7c81e58eb9560349f35c35e4498dcde7af7be8d7974b79d262304c26ab67ffa5ed287bb193d5f0ab46b4096015", - "0xa607158f0c1fd0377a8ee5e9715ac230abf97406c19b233d22f5911ebe716967cc10425546dc44e40c38bd6c2b4bca2e", - "0x87e8cde50e5d852d3f073a43d652f7186bac7354612517cfaecd4a1b942f06fef6f14546279c0dc0262e2997b835b2a4", - "0xa1c93acc6db9d5ee426fb4a0b846bb7a7b8d5915bec777a9fe6907246b0beafb8938941c8c79ed6082155f75dbc1e332", - "0xb1e4f61457b86f76cd93eafd7536f72baf239ce5a62bd5a8085a34e90576b1e118e25002d2de49b01d6e9a245ee7d3a2", - "0xa0435fe9a4bd1031ec5973a103ec9396b2ce9fd982f6d9ed780fa80ac06a6e47a0a6eb2daf52df1dc9292db622ee9fa3", - "0xb66d8e8a1717e4bfa42083b6ef4490e090a73168b2912f2111743e089027be0a4945a229ecf5d0b5eec11b23f0e11303", - "0x8eb764f26904eea4f4169be6e75beaa6a39e4eb524625a15a78befe3d8e3cc82692d9b135590c20ed460d6e4ba630ef7", - "0xb7e4aea6bb09829e53fe83e53f49a7a331a6d7bf76e0073d758577e6d6fbe63dab642b23657355cad48896ad8715119c", - "0x8f94207982373a99ffa282673f192aa98d0c4461fb77c31dc4549628bd9687a249f1b3c66b1840929341e42516c5c64a", - "0xa9c673cb247b13e17fa5e616f0399b7f5c7ad043e143e44ae68855a840870ab3d2aad737ebcf74c2cc9688d17ef3a794", - "0xb02635104dd28c02068985256975c0af783899eb996e37d021d9a35238deeea9e836760db21869be7b6c82aa687ded29", - "0xb33bc0966389710812b5f6698afa3e9c84839a1b85492ba11e6ded26695260abf66be6fb355d12d3a8524966f0f89e0f", - "0xa79c0dd09506951c33da3cbc23843fd02d641fc24c640a205e6e8150240372847312b9381fb03c5d301fe4dbee8d0da2", - "0xb74de6f3a2c502b5b658ebe8a9b7edd78afd036f5a2736aa06502863b6865d131b9e3542e72a86fa2e1d2db4927661ed", - "0x99e365def1452ff9fb4b9eccd36ff4154d128469ba5bd73e83ae457ab53977cf6fc04a5d05bdcde357ab539e34bd9fe0", - "0xb4f2bfb95abb47c67870aa6ca38ac8f3ae1b1a2bed064b1be7ff90865ea12e4930fcf66429c7ecd1183fae4a01539386", - "0xae4bde87f36b912e92398bf72e11d5389e93b2de1b277d7ed4b6fb5a9ab9f71a959ec3bcb734c11079440fe42b86fafd", - "0xb826459e568efdeeb66688482b67ef5020787275123fd3192f979b6175e3b0ed59e17cb734a0a052bf13f0afc7bd237c", - "0xa99dd735f4a7c85cb23dcc7f4835f9ab32026886909aaa95876b98029c37dc4d621726c872d3a9e50403443c958f4029", - "0x99083545034768010988bf8a9f34486c2cd9da27a1d10db3ab86eb69a1dd9c8ee723e7da4ef2aced63c1dbd53ccc52cb", - "0x8ac3209349f0142546c714ef7e9d1b094aab5469b8f080c0a37cb0362da5349e108760f272fbba770aa468e48d9a34c4", - "0xaf5f48ed74b21e3f2c1430192adb4b804dc873cd7e8f07130c556c30e7b78df0ef5a14b205368848fa9185e5a68dee0d", - "0xb8b741b65d68df89443523ba74203226f1e0d13bab073d183662d124e83e76cd318b2bfff09879c04d81b577ac895638", - "0x914abe4282d11176d4f2f08c6f15e6c2d0cde1ab4de00bbe888015c205f51929d97296a0a8d3ca5641f085a29ea89505", - "0x83ec306b2a9a6780efafe799df90b1aebdbff7d47921a136ea8a5648b9708a97231245a1082fea38e47ecafbbe000528", - "0x95d6b58d70b388dfcee4eda0c9805362ccfb60a87603add565b175b2c14ed92999dfdb0d3724ee3e5d30535f282641e9", - "0x97eeb4de607c8306e1d4e494f0d5db126d53fd04983ab5674ec5996b971899e734fa4011f2c889da21154ea1e76dbd2f", - "0x84ff21977fbd873ea06bec444d4ec9ff0e3902edc29dfa25f3bed269b3709e3116e99dc06cc3e77f53c53b736bf8fc29", - "0x8ecf483874a040a4a1c293af145094fedf203a5eb37c3e165857e108cce3e1210e0bfc0f26f4ae5e2194024929ba034d", - "0x97d9b92b2ef34609d69402167f81bce225ed3a95718a3b403f702b93e96a121a8f7f072d0ff47e8b25164e204d1576bf", - "0xab87c39cca1803b4e84b32e40ff30289e3cbbcfbe16a70f9e025643824752359be1f10c3e5398df402b6fec64d5a3537", - "0xaf84ca57e6944332884b5c84750afe0d5950015e127acec161853d55d48fd864c7da8d59cc5aba4ceceac650b813fcc0", - "0xb1d23d98edbe7089ce0a8432e0eb3b427c350fb4bb39eb2aca3c2bef68c432078cb9b4b2c4966255e00e734fa616638b", - "0x8e2b5252e0ea96d40835ebfb5693af49946509975682d68651396d6bb1463f09e75fd0afa04ccea49893b5b9c3e77e40", - "0x8db25e762f1d4a89a9a1cbc61c01698e775906bc88a921b2905735457a35df9ab84bae12e1b1b8dafadd50212f1acda1", - "0xb5f7cd163a801770a4034e2b837e00191b0ac63a2b91032ae9a99ec182d748798df48a14644935fabdbac9a43a26749a", - "0x998e7232e5906843d6272d4e04f3f00ca41a57e6dcc393c68b5b5899e6d3f23001913a24383ed00955d5ec823dbd3844", - "0xab2110a5174ae55ebb0a788f753597bd060ee8d6beafc5f7ce25046ea036dba939d67104bba91103d7838b50e36703d1", - "0xa211972a4f6a0303bec6c86f5c23c0d25ab4df0ba25876cbaad66ae010b5a00aa0c5daded85e4326261a17a563508a25", - "0xa49f53496a4041a01e07f2c2cf1e84e2ee726917bb103fd267451b9b7bb1331c0afde85a79a55409bfde27328b2a4745", - "0x934e915c67c7fc47adeabdde49f63f04644fe234672003be2aa0a2454dc8d9288f94293478936a450f2e3f249d395b5b", - "0xb6e69e9d6808ff7f60a01b7aea6781495d7a20f5b547852d3f0af727a7434209d3015a9dd04cbe3e272918e32e345508", - "0xb348d3462092b5c6fead7e515e09611438db8d69650876dd3b56226e303252bbeb9e9f3b888fb911445b0c87132a1d0e", - "0x8d6510334a905efe5a32001e167f1ba06f9bc4af7ffbf11b7f7bf3c0076b5cca373d8c47e98c1ba8755bb22632bfe0e7", - "0xa2d5200f20985dcd473d119ee97e1c0fafafa0f191185bfed9cac429cef8198d17665dac4f70342eea66e6e4a7370d58", - "0x8dd7eb6b1841b3f33425a158d33a172b79b2dc8a01378e4174e67a1a4c8f4b887f02c7c3a8f354ed9eac718155bcdf37", - "0xb16ca19388642f71afcd9f7007b490d82f83210ac1a989da9d4bf4c419de07af8c048cd301ec7e01b9d06abda7c169d5", - "0x93cb2d847d1a88de8c1c9d5b3c83efd0b7afb3682942bd2c8ab5ef35b33dc31a097a3e181daab8630d4e840b677216dc", - "0xa8b648c769e77a7b41c0c689fe2fba9bc585067e004bcb1732cb7b1618e97b317781c36c23a00680fc780b58c301a789", - "0x918c321100d57712866bdae84edf7e42df30a32853af257e0cb4da028842a43b49e775f3cecb85cd817269c728de7319", - "0xa7b0f6ce42e00c519e69b2c78fd9b75a2e7103e5892d3c1afd70c9b5b9e706180a4bf73dbb2d3eed52bfd521103ec5b3", - "0x90041994af3322b010891356afd8115340bd7fd7ba328716fbc4fe458236c8cad8c7564ae473d6091ec3a54bdab524c0", - "0xacb1ac83809573846231f9be2dc5f3e986cc36dd9574a620b1cced45bad0b11ea957ce8c6cbf964a0af916781c574f05", - "0xac54677dc002698fc4d454c7beb862ad085d0514f92576f3485a44c0cb47afb9db2c085058918a3508f9b3de0137d97c", - "0x8dea56e1bfa150e442f8484b2952b116781d08cfa3072d08657cc09b0217276efc4ab6f5fd726bfd826f6976ced8da29", - "0xa2b09e25baf01d4364b5205fa0c4dea84ef8fe03709113b034f88a0f0a502a81bf92c1d4641e2ac9f3a6f4203d3645ee", - "0xb95fe37aa351b4292691a9c2e547224c37ec2751a31ecce59810cb2ae0993da6fbe5efe0ab82f164462fa3764b6eb20f", - "0xa3498947e91a3a540e86940be664fc82f1e83ff41a0d95eb84b925e820602a41b7393c8b458bd4ebbe574a754586787a", - "0xaa2516d3620c832e5728fefdb1af0be30c871cbad4b166a7a4565af676e73bddc2f2f51acc603b3a022056daad2b330e", - "0xa9251b56467fb55f64c70729e2ec77a59d7eac79cc0b4b25ee405ac02aea46bf1cbc858bc773934a6d9bea57cb528185", - "0xae8c0a4ca7ba6bdca8764bac98df0581f00358db904e57867e6ffdf15542e55f7bad2dedac152ef88038b466ed901934", - "0xb0881e27e52cc6a57c4f3f278dffc7f63a9174b68bc867c16d8a151d9cc4d0aeb703d1074d1927faa9ffb43e10912c9a", - "0xb67138465d6654ded486d18e682f11a238d6a65d90f23d6b13eb6a1b7471efbac9ada6345dfb13e5432196d2a256829a", - "0x944c69a6f1126edd38f6eef60b8a5bd17147ab511e44e8e0a442e87244d8f35236ee0b8d3dac0631f8598f16486a5f74", - "0x995679dbe03dec775da26708cb9200dabcad983825f1ba601eb9395f9da350ca71e8af61dbff4c668fd0eebac7e4e356", - "0x89de362f02dc14de6995d43cdea3c854a0986c605ba5eb5dacf24e3a85983229bc99a2fcf50aba3df59f0fb20daffe29", - "0x84607f0e2d078df22d0866285614f5d78cf7697c94a7d1b5e02b770101ceecbfd53806b377b124a7320d9fed65000b97", - "0x93e3faab60050dac76ab44a29bcd521813e76ec8e4ae22712d77bb489bb49f98f9087acfd6a77016a09a42ddedab2d73", - "0xb7d64a7a35f21747b8e6a874be31ba770c0d13cbd41448411994e8cebb59591295a26bacbf74ee91e248a5b111aacca0", - "0x8dcad429a2b0d66b9eb8c1c3924d7a72979727db6a535526a3518bed2a9532d12aad1c5a778824ca4cb98e3e513f85f8", - "0x980882895faa347bd2fd1dda7b8ee7ed49e69843afe646f677b371eecc7a10e0f4e40bb55f28995a40080df471876816", - "0x89e8e7fb51df79971e2f7bf65783614abbb0d7f3f1b4a15d3f0d160deafa7ed1c446d9a5ae1a77160d4dd94ceed8af13", - "0x93fda8d350392e9c4d4ffe6534f7e7be53f32483d9319093e8436fbb8166a3c01085dc858373e65c7f4d014e0dc2bab7", - "0x897521a87b7ebf7152de5260c0875e3c7df1c53e734c672569219ee6f9bd196c5ecef159b6a1d3b7cd95e91b9b8803ff", - "0xb59affa408a0f7bd7930fa3b88750fd043ce672c10a3adeba95a12f23f0dda1793f761a86f7409ce1e6fd3b3b7195381", - "0xb4422ccc12f4fe99c530cda610053af9ffe635b633d52492fd81271d1f6f91b87171d572d5bd0e46ff63e221fb2fc4a5", - "0xa4542cdf3346ee0867c08d630c2aefc57442f1c05c0eba52d223bfdca5e9d0bb80775cff6ce2e28aa2730231fd7b1bb1", - "0xa7d297bb09118b914d286e5d1e87bdf13f7d174b988e38fb5427902e8e8c674072f36b19055a1070abcf357f8668f35b", - "0x9213b0ae24b7cb43ae95e25c09fead8bdbac55141694137d67eb5eab5e90a348a13d4d4d2cbc6436fc4f4f9f7334ced2", - "0x8aed71a0d116d832a372b42a0bb92a1980f3edf8189bdbaed7cde89fc0418b3ab21a04f5c6e1d3b8edf73f1f62bd6b15", - "0xa6c47d77d714c285c84c6b9458cbec5e3b191c0502dffd10ce049cf1ea27ddf868ef0cff13a2377289fa6c932b8e4f28", - "0x92f45622ec02483f2c1e07075a6695416d3768c8984856f284f40734346d56cb5b3322f20c2c9f0ef8e58ddc294a309a", - "0xaf6450d02b79ac9fc79f35655b58fd3619cd5d38c5317564b453f5f2d79d7a030bf767e399fe01b658a72fbd2cac2356", - "0xa3c01fed5240eb8a61ffa8ff4a120dbcebb53b8e19845949c77fb4f9b2c3dd52c7001df6219ad2f76c785a4ee0f64a2a", - "0xaf3136bfe8f774187bdf87555a1ac505322a956229a285d28bab1c88d4f4d12245af8dff35914a62e90e49f3dce6acb0", - "0xb20e21d28444fc96737958cd951858fda324b924b4d3d08932540fd4b87150f053db6985b96903906ce83dde0578cbb2", - "0xb7978101071268d1f485134b4dfd1e35f89b82c7d99ae91f58b6745f5e0273b7e06f3b23009033ecc3e41b2e9e85219b", - "0x9104b7d75245b784187175912cc0ad869e12f1983b98e052710fb33663224362bffd69ceed43e7d4ad7f998c0a699eb7", - "0xa7624cd71b92699ce3fde0e747976ee04ee820032ac45dd27d769edf3b3379a4b8db358e50c9d057c63b5a9b13d76bcd", - "0x9354a76f294005de8c59db10e638ae6e8c6d6b86a699d8da93143da8478d36116211c788d8285d8e01ea6647dfcaa1aa", - "0xb85935c04cae14af9848db5339ab6420122c041075ec1549314e3c9c5a610d9b794ea3617c50ca7af6b4aec8b06bc7dd", - "0xad6835a62311c84b30ce90e86c91c0f31c4a44bf0a1db65bf331b7cf530cca0488efaac009ab9ed14c1d487da9e88feb", - "0x80339f0245cc37a42bd14cd58d2a8d50c554364d3a8485d0520ea6d2c83db3597bf51a858b10c838bfc8b6bc35619638", - "0xb370420ac1a011f6d8f930511b788708ccf2fe23ca7b775b65faa5f5a15c112a4667ed6496ae452baf2204e9ce0dbf09", - "0x8ceab3dadca807a1c8de58ac5788313419c37bc89603692c7a4d96e2311b7fe9e813cc691a7e25a242828cdf98f8bbcd", - "0xac1526ebc6bd4ac92ee1b239f915e494d0279fbd065e4cab1f1b8a1663f67daa89560f6c99bbc3e63fa845520316d2e6", - "0x8240ab0bc36a29d43ec3059c7e6355ff39567e135f93b243145d3ada97fd1c970743819e0d58bd5171967daec144e7a1", - "0xa99743192a6f1967511b2d3038cc73edacb7e85f84b2926d8880d932d2fa12f5215592311a7548494b68a87ec70c93eb", - "0x8ffffc31c235997e59ab33c2f79f468399eb52b776fd7968f37a73e41949111957434f2c0a27645ab34c741eb627cd1f", - "0x8949d955309415d6d2cf6ee682ccd0427565142c1bfe43b17c38de05cd7185c48549a35b67665a0380f51aef10b62a8e", - "0x9614f727a9dac8ecd22b5b81b6e14d34f516db23a1a7d81771ddaa11f516ed04d4e78b78fda5dc9c276a55372f44c4d4", - "0xaa85d3ef157407bd8aa74032f66bc375fddaff90c612470b5ff5d93659f8c3523b2d1b6937b3cc4201c2aa339621180e", - "0x86f8fe8bf4c262dc6a04620a848e3844f5e39a2e1700c960f20ee66d4a559a90141ef4e5091d0f32acb1e915af1e0472", - "0xb3af2eb785b00588371beb3b49536b7919a3f2175d4817de5dcbf7fcc20c512852ef0f313327fd0589b10173f77b92e0", - "0x8388703c512eea59190351f3bd2cce83ff8bcb3c5aefc114cccf9e9b3f78200d8034c3ebe60448aaf6c912f0ff8f0cc4", - "0x95d0dbbbf08ec1ed3975fe7dd542be0a05156a2b3db5092825d918a849411ee536ed958201f74a5513e9743674d6658d", - "0x8d1a48802f1a2db247e633ddf61d3ef7a2c062c48dda59bf858916e04f56651a7d51e367d6535964ebf3ae6d2b21b421", - "0x971436871bfe868f25247145a55802945409b3150008535b372c949760d7949dd2fdb40d9b96ae7473bc8f6e9b83ecdb", - "0x8ca431728ac0f156763090828a7b6d860bf591e5b9dd3bb3b7f3ba0ca74191f9710ee55efd32db7d18eab5b479cee8a4", - "0x81e28f1a506e84c2b9aba1df720cb50e0b597b2c22f98acc34e710c934cc6f97dcaf33d589e845c2c1f6d8716d05ccac", - "0x8f43b11d3f00c41d16c9bc9bc0c44227c056bd77de4f1ca9a799418c5601e744f99066bef47da2d9088ae88eb259327c", - "0x8d330aa52744c08ef98cc5599eec8b9b4dd18aa01b803f1d1ca0e29b74f1aa2886ed0224390fc377af25852851fbee03", - "0xa06f5b203b67134c685039ec2bdbcc787353e2575ce73a415db24a517c0c31b59d1de89f12b97cbef0219fb6a1e90a20", - "0x9269a5f49bbb8fec1a387b5d105df88a027de615d5ca6afae20fe89b11746f8d23880db78dac238c955fc8bb3de18046", - "0xaf5074b3bc0656421c314547b45b5abd3045ca1b17f5e34ba39d8c1f7928a55d4ca5ea9c2ab59a55909b25255233e04e", - "0x8e7ee5d733c8e08f3fb7d85f0628de3de6835121672c65374905dc6d19e02fa2df14c13d5e9835dacd609a4df09abd26", - "0xa9b9aaf83d31e879dfb8e73a0708801b4dbdb5d7c8654b27d2c0f5797ebcacc8d00a82143e2060f0917c9d41f1a03de6", - "0x904872aa1c093cb00e1c8e369a3bdae6931c5b1ed705dd3bffba243dc4f42df3e7d7cf70303d513b34d2245743d765cf", - "0x8a4d6b3b1d6afe67383c66693f70b397e510be28e3d97dbc8ec543d699b6cbb0e72eb90a7f65e83cf9f7ef50fb18b128", - "0xa914de13916e6a0dc0e0fefecb3a443cca80d83276513b70c22c6e566a2d41acbd33a0e2836ee09abeffd3a4894e437e", - "0xb9c408f5f05934b0aefab301ba22f8254c5ebbf5405b6aa788f76e4b328c150b395f441e3566015a0deb3eca89afe9ff", - "0x8d32aa2c81b2a8b89f347c2e0b6567b2117ddbb778fda8a3f19004b7f5aa9dd814b9b3ad35f9223715d2447b2d12f159", - "0x8230e8b9c84cada1bf14ea6aa9ecdadd978d893cf5962fee6c7167ed21239210ea491987f2c8f2e8cfea8c140704ca28", - "0xa5d7b6285fea51c6f21d0976a7c3a97baa3d733a201bfaac0994db6c65611d91c5fc0ebc2a7724ee02b371e575573649", - "0xa54f00a9530f6930069f5e3a8b8b1d52ee1def0aad1763e3c609ec07f25410969b43d5943a94c235ed5eb207b33a402e", - "0xa8dc6e96399b81397734c61c3a8154e55a670fa25fa5854b3c66734cbb4ec0d8f6ba650ee3c71da3773ffc9e37abf8bd", - "0x8841fbfae1af4d400d49f74495f864804f043416c09c64705251d021b3ab7881f134a00b0241e61010617d04979d747d", - "0x95acea7ff4861cc969c1d8cc8775c5eae014ad6e2e0e2d0a911dd916c34ae69f53eef779cc24ff1eac18c2b478d3ba2b", - "0xa5dce74abcfb8c68031b47364bd9baf71a91db01e45514ab6216f5eb582ef8fe9b06aaa02f17be8b93392d9b19ab9c06", - "0x89e111169e4ae2f4016c07c574a3bdacd8d2f359561fbbdaa3474de9bc24ef8936784dfe6fe0e29a13cac85a3e622b61", - "0xa4c511af6bdf3892939aab651828259e4ef6ebecfdd503ecc14e61001575b313a89e209cb55a77ec19a64d29ada066ef", - "0x923c62156fbf3a44926ffb5dc71f7cef602dbe941a98c61f019a27a18a50c16b6135b6099fe04a2e1dc88a6cad989fb7", - "0xafb9191c541b61afa0ef14652e563cc5a557842ce2afea13e21507dde0ebbe6da5233af949c998c00865c79bb3d45ec8", - "0x8a1f0ad65cb2b225931f41dc53547d756111ecbf5bc57c5ee2cc1ffd61b126d0389d311ffe26cf06eaead95af09c5ca3", - "0x9040b20b5ac2e1a9d30abf7a4eea1ec2db8f3077cb2cfc8736b37222d8d3937f5d9f421167086dc5551e9f0bd2522d07", - "0xb6d888b8c6bd448dccaf99c3f690d47f802e134709ce102fb6f6fc68156943c0762be6f386338163e01eed2d1dd5f734", - "0xb94f0e27bbcda793e4a272603b3dcc739d3bf3207798df7319f8dc9d37cbd850e3724bdd30498c929debad971950223c", - "0x9769827767be9d7bacba1b687289e0794c6fe630d33c9b607da1f6a65e3f34cb8bd65327d9287c8c5f3c8b5f6d3d133e", - "0xaaac72c993aa2356c9a6a030950441de42b2d746bace29865382f0ef54835bc96958b2f00237d805ee6a69ca82117c1b", - "0xa2b1f027d80c1b0e79bfc7dd252e095b436fba23a97a1b2b16cdd39fd39a49e06a1ca9a1345c4dbb3d601ffa99f42bdc", - "0xb3fa0ad1478ca571e8aa230921f95d81aed7eca00275a51b33aadabd5cb9c530030691d1242a6ff24e2d4cfd72a47203", - "0xa43ed4368e78daad51b9bf1a685b1e1bfe05bed7340d4a00df718133f686690c99198b60031513328fc353c6825a5f2f", - "0x965e145711ecf998b01a18843cbb8db6b91ff46f668229281d4ca52236c4d40804ebc54276e9c168d2a2bfc299bcf397", - "0xae18e6efc6f54c1d9230210ac859c2f19180f31d2e37a94da2983a4264dbb58ad328ab3cbc6884ce4637c8c2390f7fc1", - "0x83a9200486d4d85f5671643b6daf3d0290b2e41520fb7ea7030e7e342d7789023da6a293a3984308b27eb55f879ad99d", - "0xb925fb6ca83479355a44abbcdf182bfac8a3c7cce6cfc7962be277ce34460eb837c561257569be3cb28023208dea80dd", - "0x9583dd991b62ae4bd5f379ccd3cec72cfae1c08137ddfbacc659a9641e7d5a82083de60005f74fc807bd2acd218d0789", - "0xae73bc32e9ff5926e1e06c07a3963080881b976c9875777f8e4cf96af91bf41bdbed4bd77e91253b8ec3c15b4a6d3977", - "0xb2a3ea90aa398717ba7d8c46743e4c487b63c5abb140555d8d20e5115df2f70d3c84a2cb9a5e0536b2d93d24f271b38d", - "0x91d119d3bf1d34cd839eb69c6de998b78482ab66bc93fa97e31fb9592f36cdfcd673f52366f8c8e8877e313b92d4a2ad", - "0xa1907e20120902cf68912cc3046f8806cabbd7673e80218814cb088e080dd93b5dccba395b13e0025f5755c183276c3a", - "0xb2e2011df72504065ec4c12cbc2137b95cfcd1355509671feb7b00dbf7f8d500476a49754cb7fb9219cb5cba7c8afe01", - "0xa48589fb7a74a3dfd782cb3503e6294a81dbb6adb412887569f9408e9079371edbd9822388e0b7ec8d3297ba270f53ef", - "0xa203909bfe196ac65ed3e6800d577b6ca5c8fe1d40f7f925a43852951e38883f2ffd250a9e16fab3ed3dc1249650247b", - "0x997ac293722a8b98f7e819f8e6c2d4c5bd1103b82d489d8b8aabeb905e95450b9b75bd61442cf68cc957212ec1c55617", - "0x9895a3de62395c33509b153b7820bd94fd2b011f0cac135fcf916482f1eda272ecc79f83a61837e99c3a3c4ab2c5c2a2", - "0x98c2ece4d49a64ec8e06407a0585081003bcef88af35210e22eab91169f8f0c044d611494b755e5bd915804b1d857747", - "0x8bc6dd083b36d076ddf0e0bb1bb87cfd059283ddabb3886f02eb7e27f1f0539b2819527b56b5c13436523c4603ac1d12", - "0x85ab8b7a696333c82dd5e179e12b2e127e67d911de609ff9a03cab95cbeedb1f364aa1f2b5e59353e4ba0d177f996151", - "0xa9478e214afa68c395aa2c7daf8ba1627feb71ad6d8bc7339734cdcdd5a42838e032736c28e6251c808d5a4875ef0d06", - "0x8c53f62cf06a35321c8af3871ee4459768d0745ebf48942b9f464206309f42fc7b2c50f196ae1e43b664f0e2e718a23a", - "0x8ba80662f6642d8866e832ec8082a4204ebc993fc304c4b794666856de0407620131a18dc053597bb40a3de0bf8aca22", - "0x8c8fac6b911785d1561a985580c03fb2ebc613ae33e486a92638aa7d4493374118d9a6d9d99121e29c68c3d67ee4e3f3", - "0x90f2c793eee07ad90157040b30558bb3b0164e8ddf856389d6742cf5bd1c712e4c6a8e5678da70a8e9e242ec7864117e", - "0x954abed8f6d58896b7f6438c9780236c1c83b02d60a29fa7361559e619e5bc9d67b3646ee39ffafe2b3019bb3357fb50", - "0xb79874f757a33085e1e751544de8fe3afbea92e0234f9c00254c2b36115a16ee46f085f22aa66e0c9177e5106f51b03b", - "0xaa148b287cf4f60c64f774282b421aae075f0eaa93a45aab4927750f47e2ef0b811d1846bbb15eeb2f293c80a7612e83", - "0xa588d8825e7b0168d45499dcff6faf0dfe1ba4f090fdc7c06d50344960c0121f10ad109b0b9d13b06ef22de5a04eef87", - "0x8f61ec93d14ebfa9c31731f9ef0fb8907505fedc79378e9a3f65c27bed4d74b41e129c97672ce5f567d897befbceec8c", - "0xa008218633f1da10efd01c155f7ed739faec902da6dc48e9f19ccbc8d32bb318d71806285cf2003de2c907bbdd4f8b22", - "0x88ad82c66f7085632d7e348d69da84200c53594553acf5432b50dd1e87f410c802dfea91be3cf804e3117ce13103f23e", - "0x8498dba17de0318af227a3f9ed86df37a5c33f9a538be9823f8dce4efc3579e8296cb3b7200cee7c5e0bfd9da23a4b69", - "0xb3c0342231dffe4c9bc7d9265597bc8cc4a82e2980ac6d1407108db5b00349dc91d5116fab51cf2802d58f05f653861d", - "0xb3f2730455f9bf5a058598bc60f47740117ba51f6a767e1134516a4e42338b513f377027acf8825da5c4d047a62984fd", - "0x816360914fbc9d8b865157bfab07aeb7b90bb5a7c5cd64847b1c3184a52266cd3f8f8f3ef99309ba2edc4622304bacc0", - "0x8fd21b2315b44a52d60b39ebc45970a47b9495f42b88217ae057bebcd3ea0e2476c0c3d13de7f72016ae12ae966a008d", - "0xb62014485bc217a0fe892ef1aef0e59604ad5a868face7a93f77a70ba3d7413443fbe7a44552a784d8eae1acb1d1c52b", - "0xa905822507e431b35f56724f6c8d2e93b0607ed7a4533073a99cce2b7c1c35367382447073a53036dfdb0d04978ccf2a", - "0x81672e39c2b31845142963351de3d9cd04c67c806fdfe77467867463dbbd8a9b0e2400ccc55016e57cbedb02d83a0544", - "0x90919c970ec668de8ec48a2a73bb75cb94f0f8380c79a7909fd8084df61ecd631476ddd474b27103c6817c8f3f260db9", - "0x8fbe37dfb04bf1d3029f8070fd988fc5e4b585e61eab6a8b66caf0ffef979d3ed6a662cd99468ce98ec802e985da5fad", - "0x950939aabb90b57a3d667f9820880eb0c4fee5c27fe211ce8ecd34663c21b5543c810b3676111d079ac98644c75ee0ae", - "0xb06201ec3c3cfdaf864a66af128effee8ec42d25f1e173c1edf9207979fa52c871757000c591d71a9b6cde40f5001a06", - "0xa79054e8febd0450c96ac7a5fd6bf419c4b17a5926f3bc23a8616f0cfbc2849d97470174cd1baa7c739b12615334b6b7", - "0x81c7391b2a1844ed26a84f054b5f03865b442b7a8d614cd44805b5705fe6a356ac182b66a3c8d415132e389efac5f6b2", - "0x825af1563d0fe53925ec9ac0df65d8211b333474e59359bf1bde8861eecd03f2ac74534d34b7e61031227c2fa7a74e1e", - "0xb60dd9bf036f1825295cd2014ef1f6d520cf729b4d6cee0b42cb871b60ae539b27c83aa3f96ee3d490ec27ce7e915115", - "0x89ca43d5b7f3622b42df7887572297a7f52d5204d85e2e1ac6e5d7aa7f8aaea5e3a07280477d910db025d17cd2e7373b", - "0xb93a2bc9b1b597f0e514fde76ce5bfb6e61eee39cbf1971ea6db38c3ecb055e7913ec8cd07fb0b0ffae3ca345883101c", - "0x8d45546bc30266b20c6c59fc4339eb633155aa58f115a8f976d13789eaae20a95b064fedead247c46665cc13ba856663", - "0xaa8eacfe00e8a4d9815de3f7619d9c420629ada6489933ca66a571bf6c044d08b391e0d9eec7d1cbebe8def1e7523f1e", - "0xb32fefc59a0d0319ccb1946b351ed70445d78d9fbb536fa710d3162b9659f10288f12d82b32ecc026d55f16cbad55441", - "0x99c7c45c34044c056b24e8f57123ba5e2c2c039e9f038a66899362840cffe021733e078866a8708504cdc35816cb335d", - "0x80def162c134540d5ec071b25ccc3eef4efe158be453af41a310b7916c49ec0ce06bb43dfee96b6d77339e11587de448", - "0xb5f2fa4f68f6a26bcb70d8eab62ad73509c08ee7aa622a14b3d16973ffff508ce6f1aff9ced77b8dcfef7319245cf2de", - "0xb4d0436019e779c789464716e1741c189e8945dab7f3072720bd9aa89882fa5b085a1755c48da21541f3cd70a41b0a71", - "0x931e798ef672e1472f4f84c727a101e70d77b3a9f0c0803a5220958d6bbeb8aeeb56c769ab472a3d6451249a13a3f56e", - "0x918c10a84de268aa8f1ba24b38fe55ff907be07b1e86b4a4adbf305c0d705c1cf5f65ce99e03e11676cedc89f1a4f331", - "0x8e55a8413b823715ccd92daee357cedd797e69a0e78b6fcdacb7318646b9903dfe05e5501f47b3c52e74055b9eb619a4", - "0x8b329bb63e6c985d7d072dff4680b3f8b1217ed20543277386bd30ec25240d9dc378837dcd5cf4fd9548658635f4c537", - "0x8c2be5386052b22986b33dbc63c5afacb6d0095495564ba4aa28fc8c880a3c78242fb083248d788ed928deb1e30a82c2", - "0x83a2b7bdfcbd25d6b059f27218e009ecb5ecc4da68ead885e00216411d8222062ca42f21c4d9cfa19c31522080af677b", - "0x9620334d2633e85646b2e2fc48dc6c3f09c64ef1706ed78a3bb6ce1f6b274a727364df71e97531dfdcb392f70f27f536", - "0xb6c84970ec04545121ec3b79376f4e45053c97e8bf2b11922cc2490a429c38735466097ecb81cc9d9692c74d2fb8abc8", - "0x8e55d707dcf265c5ae29a32c27ce66f200fddb724faa5bbf145ef42280ef645fa2f0cc3cfe2db8599b26c83b91e077df", - "0xb910b96b763966402bbebd68a32c15a225ec21e1357fa298478c5981a4310e556103fef0c73bd8903e11c4ed2c065647", - "0xa8fd933a0e9fe8c459809bd93b8ce153e2af55df94b61a1490736b19c89469954da8b72dbd072d798fc06fc3d7a3d60a", - "0x811b279c113828e114fd82c2070caa7eb089a46c8cabf865f9c77354a77ebebe0c4c6400dda0e66dd017cfc44d76851d", - "0x8ed03e91c331afb3ad6e42767e1b3e8d3a35fb831805ff1b5fd3e91878e04027ff5af1165a3ac295f1578faf2c83b581", - "0x95bf53683d64a0621bf1ca6ee17446783f6c535b7a54d6ea57723487a215759a54f886597a55dfdd560424e368ab2759", - "0xa9bea378768fb1d7ba365a16531c51fc1975f1c73caf2a0891da28509805fa84e2a8db7c6ccfbc620e9002317abf174c", - "0xb8308250891015deaf851c4e5a4cf4704d104f94064418488d7e3076d49f36240dcf6fdcf83f45fe8a1d97fb02e3db59", - "0xadcda6b63da21f4074f142f8e7f3a2274f624c733e3a4001054a1809711529c61356aa087f73aed877a58ccb41d38d12", - "0xb80e7869239ae26d1da2e6683f064d1dc93cf4a2b66e9439b3ad9b25324e969bf98014760d29e6b8de7ff152ef498d0f", - "0x8e9bf968911df3bb5e3a7655e9d8143e91ee87f14464d7ba9c86e1e31b03ab31b91eda121281b79cd974d9ed2657e33e", - "0x9007277e8335a43e6bc3c2f5f98c0ba7024a679b7156aeefe964f1a962e5ac82154ac39d1ffbad85a8f2440f3c1e354b", - "0x9422b9d670e997b7c919a429499f38e863c69c6a4d2bb28d85e36ae0895c620f68b71e39eba785e3d39a45be91507757", - "0x926094e01132938000d82dd9a571fef5ef104cd25b4015a25e3442af0329e585aaad5472f0e7a69899ba2d6f734b40aa", - "0x95552d8057f7e32c24d69e4d6c51c98403f198a20c5be8826254d19cab2f84d5758e2220cea7e38b7c8a7a23178fd564", - "0x8abcf8dcc8488bcc9ab23c51b9e7a0d91dfc7bebe88b7ed370ee68eceba643e939c5eae66a4aa5fe85120751780e351c", - "0xa91bf8198f029e6a4cf6f0cc39b629e9aeff1c77b8739e1d5c73d8c1d3fb5c8f6f23e27b435bf10b5b4ec1cf6a7249ed", - "0xb932d87ee3a4b81341511f90fe5aa36c571e8b914f25abcc33dd40ca67a3f6444fe9362c1434744e4af18d6e045c54a3", - "0xa8e960c2be9b1d805d387b3ebe2134d421a65f1fd4c1b4cccdce78f9926f139eea78e3afb449b3d6dd19b5d16ace48fe", - "0xa7e2f57cce509fe66707eaba9b4c042c1be93fd6034a9b51d1d30c45c4363eac79d54663d525c9873ab0eec0b1cc4ed3", - "0xaa162a31c2078f4b080199debf24494a8dfdfb9d8fc85b198a861b12a629c73128c55a883e4c2de3dfed6e0e1b83eeab", - "0xb5a4d075433eaf4115717a84b4dc37f843d44bba0bf820c92ecdedd5afb61be60f7708c8a151a678d9d5c0ae531bffb7", - "0xb56ab96f7a463c0079e05dc766f3a6a31cae5c5044947734ebe0a26e01367c6763cc8de6c2ee2f3b8218f05bef217474", - "0xb60792ac506b901065a8bc0180a86e028fe34b62ceae1ad640c759538ebf3a2ad9c8c927d662deed6f489ff3ff7813c4", - "0x8c8c2cdf075504d12d441a58542e1f8e4bdf92b3ee4775e836b2734c5ec1e3df919b931386417d04489a1dca806c87d2", - "0x8ed78e91e5c4a68894cefc2f7fa71f02e5e12d40f1bb74332139bc7be4d92c24e07d5ece0e82150ed474aa1337af4c18", - "0x87119c22ff8aa31150bde537d863cad661cc5159b12f084cc319224c533f0deb28526ed8568d00a1441e7d8bb4f05673", - "0x83a60ba5a9cccf22cebadf7318b706c9f29abd25db0e2fc1c802965351b53cbf316df72ee3e9b2d3ae7f3c4494cfdff1", - "0xb73b6a9fdd3e7463fbdaabc9a885b7c82201ad867d1bced1c2484300a01cbbb3f1e21afa95d4c7cbb6cb983416b63b90", - "0xb1d89ad16981ff9217708090d4017662d8838f21f3a3296cffe14590b533905fa06a20e40dd497bd291fa4dfd1bfc511", - "0x8abde560083e071a402e3c7bf31930f537f67d2a7bbc734a7480b1b760aa712ebd1cbcb65b00e11e384e980222fe14a9", - "0x89c731d8f31afea8bdc9c32527bdca257f2a840764d40f6e49403b8e75ae51017d505ea4fff91bf28b6f3a1bc65b8bbc", - "0x80e9ac8e077e86ad050ee73dfce268a69564ff1b8419e9c236d981fe7a5f0c2bc756e8603ec604b3b9e36da8fe10a49c", - "0xb4f1eea0f304898b1323c6382732e6f40e556bfc68af9ce73f6d54e92f5f23cc4f78eb3f43d578d81e7627fb40f092b3", - "0xa0e3a8d1348f8f153e08ac4839232d75d1d6e81b5de184ec4724f8213baf98d3fe739a96f6b39d79a053b628c3a09981", - "0xa6915ba0b52ffe4a381bbb8ff3791d9d3b848bf89b3bacbb2a7d2e5ae21f1353cdc304b3cb6e82416f7e604035c27d7e", - "0xb2c4c9cdfdd2fc9a340ba3ade9423344b9f429e8c7e20a8abbf26400376e312f3ae35d1c456be99dfb5c02fc8a36cbfa", - "0x9657d57ca0641825a0aa5687f3f87659d893f33aee819bafa5b1ca1db554811c1c844f971e278606e3a2f096defdc67c", - "0xa4ad24d0a557704ada24d8e27a15604bca28679e260b2c69ccc8e6cae5499866724b700605a90df7dfb35130756939b9", - "0xb18d9ea6682f73a1f99a9a4fc98c38fcda02c1a18e8c5fc080cf935a2ac877dc5223fca273dcde190b906178d0fd05bc", - "0x8ea5fefad0799c885f50ff10d94bd0af5b99b0a446cd1f367ae5ff529cc47e09f3018115f3c0ccac2fa05bb65b84945e", - "0x92450d52e6c7d13ebfcdf5674d6761bbae2fc5aabc865d35d031b588c383e0a64cf69a73dc93948632e2b98f74a5ed86", - "0xa356f171a98df4ec5a96d556eaccc6ad34b4238aafcf0e94ece27cdbb491749fc9692e78b84dfe80bdef2914079d34b5", - "0xb918703a4d3507d266414712ba8eb7ad17da07cc5f952b5c62ef130cc6ed1ae3bf01237fc8848c179725bdddd465b301", - "0xad2b0554570bfc9d97510cf59bc38e10ca54a93649c30ac9919bd0255e43bf525ab11b74f78a51ac0973cd0c5a5dcb54", - "0xa7ecaf4b631d179d32ac1632390d95196a0035e00da6c0e6e13b5c09ae44b15ae6c21538b5a31b73bc5f650ecd979b59", - "0xa37704eb4d728df2a367e59fcb6c26023136230e37f3b8a2f3ceeb1467f5cd30186fc0116f98b64a8146fd2c5903e8d9", - "0xb09373ce92314678299ae10ec1f93c702911beb4115c6b5ba6efbcab9c7afb599f59793912df70a98868bce6545a33dd", - "0xb52a878a1393094fd2b93f2d1eccabf2830ab10800ba4cc24dcc7849cd0978733263aef2fcb766a7cb575a7a99383db8", - "0x8dac097e006fda4fb9d6d7ae52adabd9448ebc8d5bd5b38ac0c4ed38ceb510763174f7adfb0b473c38e52147ccab4239", - "0x86b19c41efb949937d74a7875549ee5e997f9fdac7f7198085afda233cf74341a38d0ca3767c76cd35f875b89a35f78c", - "0x99f0d927e5ad25cd134f1c70b72631cc6b5cb4ddb86c0642b900464e33d971213a5239dddaf71f7a42f2d6d02a12dcc6", - "0x8355c38806c335d747d4e97f0083fb96585677da18b409a85175ec35dc3f74671817b34203eb18c2f729717ce083ede8", - "0xabb3603adb061a036eae0afa5f23d79c3b62442e0e3bcdeef896f88995585c1105cd3065410368456a4d36b5b0485a83", - "0x9051c5c0011784885187d04749f774b9b4f6bc594b0e4e18226de79dedc4d7aefa3529c3d2c728e180f96f3e204d578b", - "0x91888213e7d321d0bfac884edbd5cb756b280753bb5f8bc6acfc208f525757beca24bdf86fc68d3d8736ef176a960b49", - "0x91258bd7ce6e3b7516fe2f5391a368d826da299e0e99b1f82eaa44b62b110ab696adc92debab8ba098a52f38dfb3c5d8", - "0x96e3907340dffa9da3602d3b94bacff7e1bb8649edd3b9bbd06e1bc6781e78f91ababab12c0b9be7c66dfedc7001b66e", - "0x9513555688fcfb12ba63952ab36a67b36affdd71f7b843e8eb99ccbd45421698024608233efbdc905eaeb26b334b33af", - "0x9913ca9bcf11eeb408da02e4317c5ca0010fb2f4490b282ddb758001c08b438c3b35351a8cbe10b7fffc1293ccd22d4b", - "0x85dc2471860ebca88e5a2766161fdd77f926d2a34825d1134a30418f91a741759668e32fd1e37c415d07ab5824338e8a", - "0x8b128917e828a0b5eb6fa8ed72b52fae2dfaf74febee69a2e2f87e8df702f0c5bc0fb620c8d1d2a07f35a15ec9c0f5a8", - "0x964c39e7840c130b01bb481ae7bfc92682b0f124c9c383f9dbf3027f2249151925f4faf36905af476a54778d69da3f48", - "0x80671ece658cf850e522d46d25678f934ce6df043f25f8707235125765d40c2eaaf39eda6092f75039b22cb58bf2c29d", - "0xad4bb0e79fdaa340b1347a46b0f64e801c72a89770dda0a6e4bfd35f2df5146fce9934e4baecb1c2671077c771eb8089", - "0x80b3bd3adc6cf198fcd997f8867d2839a2eb28f57390352ec423b8a14cc1f2ab21c6e286505d6a21fb134dcd8d8f11cf", - "0xa26d46a6b8a75748895a1d599e7fd120d896340e79813167a400b2fe463452532a4cab419074663fe1d29fa716b76a33", - "0x82b1f3a8a1df29207d7ff020809113ab06080a7f0c631f76ad33f47cdfb6a567143144df97b4ed7f676d929195b04bba", - "0xad96633a3744648ff0a2e4491e8219c9c6ba6e655cb058c36320a8f72cd5f72c00bddf97083d07650ea9ddc005fc1ff4", - "0x91d0783788626c91662359dc3ff36a8bcc6831e3f4114f85c99910256b1d8f88a8612f53c7c417d55581dea486f38926", - "0x84edd9e87ff3d193ebb25f43474c33fe502a1e2100fd3f93fda6520f5e42214cc12e9f8045f99aa2423a0ee35e671854", - "0xb55e06a4b1fc3ff9a5520e0b7c8b5ac11b28385cce78d91ce93b82f1bd7f7afdd4195d0c13a76e80d0ed5a4f12325fa7", - "0xb0b15c7ddede2b81d9c835ecaa887650622e75d0d85f81b8bbec7ef24e9a31a9c9e3de1f382d8c76d878d1b01373f6c8", - "0xb1adb47c20f29784116b80f3670182d01b17612d5d91bd6502b0dcecdcf072541f582aafc5e7dd9a765cad52151684f4", - "0x8efd1018df9c9e9814a9c48f68c168551b999914a6719229f0c5bf0f20a288a2f5ba4a48ba966c5bffb0fbd346a4fcc6", - "0xb34ea2bd3269a4ddb2fbf2514401d2712fc46c22642f3557e3b9c7acbce9b454dcf789573ede9aa14f39605fdd03f8c4", - "0xa9e1428ce24eacfc460aec2e787c053327ba612f50d93510d58b2cb0f13291ca3d16358325ab3e86693fe686e4f526f7", - "0x91eac7361af4c66f725c153da665a3c55aca9ae73ead84ca2662cf736fe6a348a301be1954723206dda4a2120202954b", - "0xa6f02db89739c686407825fa7e84000ceedb9bd943e8a0908fef6f0d35dbc33c336072ba65e33e15ecfcd5714d01c2f0", - "0xa25666faa12e843a80365c0fef7d328a480c6e3cb7f224763c11d8cbabd0e7e91a5b647585ee905cc036afca14842bae", - "0xb4348576439cd2e48c01cb9cded7cc4a0ea364ab936dd679ddc7d58b48807e7fab070f2f1ea88595b11af4500849026a", - "0xa8c6c731e0d0464ef7e4fc1b049065eb4ce100c01e1a376365c636a0b23851022bf55805963bc15eb57434a837e81167", - "0xb0952937b154e3a4c206f96cd96c76ba37624956b0e4d43470bdd97b4af878326b589e3eaee82fc192437123096799a2", - "0x97d07ec31ecc9923192e48d37df2cf08750050fb452dcfbdb350fbc43e146bae3590c5b732b31ebfa1ce5d884ad5ad57", - "0xa69359aebbfe4cbc4d39d178150039fbf284cbc0edc68a6bd635ee3a1c76569a4a575c907fff691b2a4d82a384c2945f", - "0xb321c2c0f6b5902ee9056cce7404d858da9a573d27348c1a6bfea29b2746f2aee7abcb6192504e5a583b0caeaba117d7", - "0xa74e738aa6eb4eea58855ae6f422af22812fb388c83aacca5bd5fa4a88d4c01463174a229aea2830c348dd9ab9307854", - "0x94306a3b106bc1644346bc45c05cdc8287811d5c86cad691bde0c65d6a686eb9c0ce79ad91baa4547e5d058ae8bf7310", - "0xb64140fd77a07633e4ca8d60786452311dcdb8ce7095ba51dad8486f57c3bf4e69bced92603f71da992a48ad817ab275", - "0xaffe7f4310f1dc68e5e3cd640bedf864f51bfb46bb752063bfc18e95930021f784e509261ff9c560f53000c361b142d1", - "0xb0d2fee222c6f963ba3385547f921a48964da031d737892604f8f2677d4905dbf615046db57eae6c6dd756709ae6932a", - "0x81700c66aad7c2e51168e028b0fe086dea75d3b17d93a4dc1f47a6a0f025df0bae1c8c997901837ad859a84197e7bb00", - "0xaa4ac5fdd602f8b79cace18690e67bad557a93d00c0e295074185e8c6b4059a65495d9971685de2fc01d2171ac8b706a", - "0xa8becb3a64fdf35d65d2857898dcf8053b5057a73ab8c5bb5324af1a8015cff47efb85dc3eae7364cd5c850b7962bedf", - "0xb72ea09bd0b72f8cde3466f359ea69b194ede93dced534efba1b9ebc6f3bd53942fe2965e992e82edb6050cac4ed88dd", - "0x85bb8dd7eef023a251fb6f220af54687747f4c91983ff728163c4618ffac40ee6edc29a0aa6d455276bbe017f63757c2", - "0x85a485254a11b4c4a943d9ec509c0dd1cbfc0ff5273a00cf5c9f0babec973efb15348e5d9451b548293d778e3a2b62a5", - "0xb109f3ac809391e772b589c196b013db69a9b2b10ac3898feb70b986973731f30722b573cd0c9324158ec20416825385", - "0x8a4eb579a840d438bed008644f373ea9ba2f28470d50cf1d70af38ba0e17326c948527b1719dd1bd9ac656ebd5aedd10", - "0xa52e9d66ead5ee1e02ce6108e4ded790d8ec83164a0fa275ab1f89a32200726c8e988d66df131df9e62dd80203c13dce", - "0xb541cee9febf15d252475507e11d65c4b7819c26cf6d90352f5e8a8f5c63e254eddf22df0c35a7be5b244233e8e4ee5e", - "0x8153c297772adf4603c39349142f98cc15baeccaeae10c3230ee87d62255f6814d88d6ed208c368d2c02332426589748", - "0x970dc9782f1828474e9fab7dcdec19aa106725465a5844caed948eef5c9e48199c1b6bc1a637ed7864116927e84bc65a", - "0xa975a920624967f4ecc77ea5d9869c434caa64c330024194615a8d0640c5d4d4fb139ea11a0c73a5c6ae6dd3fbf0ab5d", - "0x811f0f9e0c12acfb4b9dca359eaef3bed18083bad96188befc036ad3143b121fff4777ca6dc70a835bbc4921bd25f5ff", - "0x82341c6ebdb97c8b72910da95c7eebccd1308b6a92999886aab552f0642882d5c7cc60931577d200efd6066530c998dd", - "0x860f7162c2f5fd1c0953c6ce75bd8c52eaa48032b914410681b8cc05e00b64130d1f96ec5a52df66a04c78a9f9f42981", - "0x8a578e674875571fe1a0459843495a5ee1d9fb6cd684b244feb9488f999a46f43363938cd0542879ea18ed14fba10a6e", - "0x8df217aba4da6781f0f5139aced472025523ed6e17e504511c04b677ca8197488e237d8bb5dff7b6b3898cd5a6393dd5", - "0xb2c9230ad35d7b471d3aee6f771517cf3145ad26200bd6fe9c7cf28120e2945fed402e212d2330a692f97bb9ac4dcf12", - "0xb78b89e29e8b782603b222cc8724eeb83b2d9d56bc02f59a3c899ab76429dc721358b07dcdaf422f59520b7e7ab4fb55", - "0x82682a5617843c4ac8d4efb4c3ce715c76c1da2c3bab1ede387db503f3489c1bfdfc07d9231d96f955df84fd225bc81b", - "0xb0f53725cc610e78b8e8a4e6823a2ffe44dd15a9a5bc8151ab7a3787ddd97e1d7f2f0e6efd2876e5f96417157143e3bf", - "0x92c5a93233085e2b244519078770c7192af62f3562113abc8902f9d72591eacf52bd15ce78653ab9170d5067606287f8", - "0xa43ef97dcd9b6ad288846bf31fccf78df72f94bc7ad768baf5bf0d5dfa27bd74ffcc6b6c6ed1d1f09e09be3afa5eaedf", - "0x817d43bd684a261fb30f709f7926cc4e1a31fd3a1a5e7e53ba4d664856827b340d7867e23d55617ab3514c8a26a7040d", - "0xa599e22d3286b32fafaaf79bd5b0c5b72f6bf266ec68948478f055391336d756b58f9afea0167b961fd94234989f0f02", - "0xb70db7d8e8356df2e2070f8d658e560081442f3f3b95e20f4bf30106835d76161101163659d5d12cc0f335fb042dc66e", - "0xb8f725b70c957aa3cd6b4bef0d9647393f7c9e0b7343e92439372f0e9aa3ceddd0cb9c30be331742b87c53f2eb030593", - "0xb2fb5e7762f26036e7e966f4454f886758804d1f4c2da17f3d13b0b67ca337f1fd89fd3cc798b07da6e05e8582c9537b", - "0xa377f944dccc300921e238ed67989872338137fe57f04cb5a913c787842e08b8a1adcfb4d2200abdc911fc1c766a7092", - "0xb82e98a606071c2a33f2ad44e7ace6d9471d5434500de8307b5d4e0083e3a5cbc67f0609ca8055f0ea0ee7501b9ed916", - "0x8e58f9a04d33a41ace4944615041662dc35057e645f63e127cf0d70f96ac307d33a62ce98f164d6eed8536c1a747dcbe", - "0xb5b11388071ffbf57ac47fc195736613b964ebb91cc8e2c17b32646f91d64ea506282b881897fca96c317364d3290de2", - "0xa40ee9b7551133856cfb3904837f9949a9558e59a418898affb78adf1500fd6ef6328fc4422161909aea2c79ad08c14b", - "0x81f9eb4ef28aacdb43e11dfc9aa92ba990be4d3c14b484fa677edad3a3fbfeaa859a7f9322b5e95818240d7326215abf", - "0x84939b2b6bc859437d1a7a8d6ec9a357c6b716c4b4cc22abc274af872655940cfc72c99f5d0283d90e05191fcdb1c232", - "0xb78a5b74a90a805410b6225fb9576d6d73752520f25cc3fd1edf8ea9f6559d3080f9acaa2246809b6a66879cd2ae446b", - "0x8d0a92baa88bf38dce5385ccf15d345b28e2e5d0a2d469e689353d80eaed8e8408933816d70ad752f226c59a0d5b5f0c", - "0xa7e15f8a8c1655b7b346c9488cff278c793505379b781b31b273b4bf09b3bdfca1c8ab2334746075d636b2e05859f215", - "0xb70daf14f2adce03c7b92d6aa181f0c507a80a37493d8dd12419d5ed5f943a98099fefb46ac827d6e4efb9b8233c99d6", - "0x8c2480814661744d116fba7355bc6b1914975e44cf0e976d50b6a20092bb1c636b7b44ed3fe8d63b5555ffc89fa759d6", - "0xa6059528a4fed36abb74ab992b22a4f9bf1d05c5de2bfe6837b9af1adfed98bc37ed7481b5a99675d432743021fcfdb3", - "0xb7e19f1b25bc159e5a769811e773c3a8ffe8be8ac77ed0b711540915e5c6e7bafdb407cf9b85c551f67fd621ce8142a5", - "0xa2f66d4f7d16ed3e7ef5fc90b42676c61a98ff18bd26ccce91de03b6a0130c1db17a6bc57be135e410a76d2255b15813", - "0xa139c916927dc3d3fb83598da9217ca64f0ae127215332e9a7ed82be923b89a801c44580d5617297175f9dafb1c4eaf3", - "0xaf08e1e1b04ec95366a12d99c80a9a9ac40ac984a575dd0230cdf4eb346a7686da55ef0a276f3356f814af31f9cbf1aa", - "0x98840aefe287369221c0721cd7c1b15b1d670c3cbbfda191cdb5434bcad757e59c30ec82b2d8c75947405888d44da435", - "0xb7c61c8d42daf2e278a12d8f6eed76090b71c82275f8b33504aba75d95103840e8acd083e97a5a5aa79897876a68940d", - "0xa0264048d2a2061d32eee4f661957ff351e78436bf49ef973c059612874ce9c91970869d011dc13a5b7c754476880a68", - "0x897199a4d8db8aa2db5d9be3d4f4312e41fa0739eb06c62e2e046c4b9be829a447e5d47227e2d96195d3b7b66eb59da6", - "0xb512a9082881f5dc90b02f8bc4f38b133348c2e933813852f6a8e7d8c270c9ce68a5524af7d1d3123e53b2d02a53d465", - "0x80b332469254a96f53c95ec79bb5a8bb1c387d40e58b73d72f84384c696ba0d3c81d6ac90be2979c364c44294e90432e", - "0xab680c2e547ea5cbf95bf813020beb461d50ee4341dea944eb48f6a8584d35682d20186e3b190b849a1ba25625a7f499", - "0x9070581993a0531d6be372d370c2e4ab2ee53f30e04a75ae61ea0fc2c320914506c4d2d4b4487c1f8fa88356fc45c895", - "0x8424303dad6b4051ab633ad27ee51783b2ead61c5a6dae1eb3ed72fc1f36e2a9b1f315504a4bd90f9664091f2f403d4c", - "0x82225611eee626556553b9316dab4043aff241a81826a33aebd9864a91e299b765ba1fb43eea2c2047e6b75b6d7fe3de", - "0x8a3fb221c616ad55c352dd5e0c09ee892022013d6965aef40d4f277a42e9fa01226fe973cb99aaf6ffe4f4f348fb54d1", - "0xb07c07679aa51713e8a7d7bc304dc15ed5664b66bd371877023f3b110b3927e09e259ef22895c4001421a69c6c013cc6", - "0x83556c76bdac0dd8db6da231b863c335be076e7299802eebc259e0818c369f933a4a4b18e2df8ca07e82f60767b462e0", - "0xa516f659b7915d2f7cd0f0f5ea2491b15f0c84dcb191e7671b28adf7cf14a56d42cfc0da94b3c269b45c535f6eeded49", - "0x80d7cc6f26066f753041b17ff1bd27f6d4b5603a43729d33d596e21a67356db84ca9710158089def425f6afaf3207f9e", - "0xb802a47f9009dbd48851209ea1e2739020e717f0ae80671d9f97a0e43de923273f66b7fcc136a064c8467372a5b02d28", - "0xac92fec1864a8a911633f377df87aab56713876316d48240fefeee49ab97f7406c22e70f4938b5912c5c4e766146b7a5", - "0x89224225b9835d04428b0a74edbff53dee2be285ddd1e5a3a8c37307c0500578155f0c4052e4bc8be04c56862fac099d", - "0xb1d3c8492fbf22ea60732745edd3b0163ba5a20d1a3315e3773f2540ee38cf308d42ec72cbb3e3dcea457d1d132c3904", - "0x8bd00e38ec30ee6c44a0e5b222f1f737c9ed2a4bb9225f1741d6334df966318c8a0fd2fbb109557fe8c9479694b8d8dc", - "0xa930ce5454efc0b247dc148aff869963fc5c240241d5590415cbd36634801a04d3873d93635911bb9c0c42ecb005cc63", - "0xb83d4f80e9e0fa47b42175df74935ba8aad2e559b80e84478ab1685bc3eb65d51b93e5738d5ca968cc055ca0c552a03c", - "0xb3ae21258f98051f13af3878b8103bc541fe6f20b1c3f8fb4689ddb8800b3c25cca9b55f0a4104bdf15dc4d5844abb8c", - "0x831ef8684c1cd446c58c59d0152aeade5cc305bca6aa296b92162615f052ba280fe289edd62fda6d9f0667c186445f52", - "0x97bf9659b14f133885916733b7d4ac7e215495953caba970fa259f7bf6b79e661090ec8d79e1c9ce8dfb17e8552f93af", - "0x84d5a89cc2332baaaf3d19627a65f4b107f8dd9228a1434b327732f59883bb54fb8ce60d6acd026ed4b0e94e545d1c33", - "0x8e66cb743f95ca5486400b0d89d02e20b98044be1e3a12983ff9fe086179e5a0ebf4dcd5098703191552e9aa660a6de5", - "0x87b4cfb35bacec805f8148786788db84eb8f4bcecdd0570ecb592c705450ce1a90b6d183d37ef58780ede3995be67497", - "0xa72a4fece5478011973afa543f6d8a8ea06a64b241cf7d8bd81fa3740ac2a4cf10e5120abcc1c1101f94da89507a40ca", - "0x89dc6001a96adcd2679916f43dd19ea00508c8d5dd6b0090eab7982fd2f3571b62f3029588a0649e73f49124525407ea", - "0x8ca75edf1259599e873530eff6151c822a4018e71a340534219ef8641cb6683215891df41d4e3c0ca2560e57a7aa913e", - "0x9282d32f868e5ee6f7fc229dda5b94b603476de30cec0a44a30edf396b52dc0ebd472b8f726d4b67d76179fecc1666a1", - "0xafa24704223707db89690bcf9761f07a093f6009ca9fc945e0a8801fc29f9f51292bf95243e466fe736088af36c55ca6", - "0xb51332508ddd9a2610edd2b0ad120272ca342e96c28baae37a2c4f07e689303a46c237712d07e446b1d67c75aa8ce32f", - "0x9219249f3799dfa4eb4770ee323f821e559e7406bb11b1f1889286221b22c8b40ccacbd9ac50ea3fa9ed754860bc24f0", - "0x993515270c128ede64fe6f06755259105d0ec74947b7eb05924a375fa5c6d14822f3d7d41dd04fa5df8aa2aa205a1dec", - "0xa83be4c2511bae430034ab15b194ac719d7b7041f9c0e321317f513a97db39e97b9ee1df92a1962f265b7a3e98cdd753", - "0x8ac7feaecd26f7b99fda3ed0b8a08bd6dd33ed5ba687c913ec0ffc64bbbefcda6f265072add4d944f2005634601ce68b", - "0xb4e3ac6b09299db9e1a469f3a0b2d8d724ee47a417a517bebc4c2ac3efc5cde086b57b9aa4efccdef2bcf8f456d973f6", - "0x9262a24a84fb7b2a84d700f98dcf3fefab8b47293778c20bfc356860cb84e0bf102bae9facd9986d92d1762e0a955836", - "0x97be2041c42bd25e5eb519279163b0857f8bef627492c27b1182f8bf0033769246be5886422cbd2409c08a2615352465", - "0xb0b87d059a00e3effa2e5e4925da913b245785f2932ac3ed364ad19a064d3561b8aa6afea22c951316074f0df179af36", - "0x891644b7b3321b06a2a40cd96c2b8b29d81cde5b48546483fdda439000982a9cbf1f6333fb6c089d39da6492cdfaefe9", - "0x8da9149b7f4783a24240b7b9c7e6df4abf8d699d3834e31ee591489bf4744141ab199c173db64397c1f9bd5f9c862ca1", - "0x8ad7f9fb2742654aa2964fd468e7645436cefd1308b064fd63fdf0d3adb4caf6cfe5426354f6cc284f208b03d6b2d918", - "0x8435e4668f7aeb027100d21e4e0b6ee22b401d21966a3736b95610de86c7e2f2c9ee5d0f901353675eee5ff458dad69e", - "0x9010895f045538bd11b47bb8996f27198c8d6cffd3220569e6b7407f68f35c47d1efdbcecbf9b5e241c3c2879a4f6936", - "0x92a9aa443b5ee7bf13b6f43f2d8d8db7f6f33fd4073a606ec5772421a55f464831419726130dd97829a7d4bfeb1ab078", - "0x843f3266560be6dcbe0258c3c7d7e332330e10630c069892954290288eda301e247f479505a8a1bf7e59c99ccafd104f", - "0x915bd1dad808f8a568725bd243f80b5476a2999d0ef60ea3ef6e754155bc4121b2b879d01570725b510c5a3f09cd83ef", - "0x97250d781815b1825be192714884630e9f564b9bd737d55b8ac79ab48d0fb3ca53bd21ead7b2fa82a05f24083f25645d", - "0x81e2d52333391ff2faab39611689a62d6ead77039e8703f4e012d53eea17a4d46f2e3342e44b6edbe73a542b461bda45", - "0x89c9f9fd5f638156b018831c1bb70c91215f4a2f5a73c84b1208bdf6ad652a55df7213336ce12bd910a0e1a726474f95", - "0x92bd02984d090ea7e2f3eb7d36d1e7b9d731b6b047e3cdd4af7cc4ee177415fea7a145205e484b366d84191f06af85c9", - "0x85a86fc61d5d916ccbb219db52953e1495230aaaca63237e9165276405f07ad9644e253ae394f1ccdd231944e7143313", - "0xa2ca5b3fbc9f3530f88c0ed7071ec3d89b272174c366eedb5d15d2b648c65d23c0faa4e92c776357e7c6883a0084d03c", - "0xad171f5badcc99c8ffc9d8b707d792046f86cd0aa478e0e2fbb32fe095f96cd134ca548d1f7713057694dc6b26465315", - "0x96bd15d57da9980870fbadc98c68db76824407dff2700c45b859bb70d98374d4a4ba99e3ed0b0c17f480fe08f16c6b8a", - "0x8300bac69ca088c3ff35749b437215e9e35a16393e9dc094f520516ba57a485def7029d30adfc72bca36eeb285c19301", - "0x8a09e20be64f346668fcc7b07fee9c0ea8094c935cbf4f3a4cdbb613d4b936c1edb9256b7c884efb72393d97c0da00e1", - "0xb1f85827ee6f041f93ab174d847a55710824fa131c9ade9561168c3962a25c617475ebc4105eba6e738961a754442bc8", - "0xa131558f92e215969f41b6a57d1e2f424149eea531723821dd4cf8c54325cbe66b002de2c8287de6b41ab4b5c35f060a", - "0x81ba492b8956f73557f361a856c6c884ebb300d828287d5699e22e0cfa75c8e77a61616551d0be5178263898c461d6f7", - "0xb2608f44d3c22fac8e13cb59e4ade8b9a98c4eb1ec0959ea400c97eb937ae3f66837e91917057148befade8389af2f6a", - "0xa6ff0323b5a18a4becb2cc6b376086b47cb2baffbfd1b0f2229ef2286fb4a34c5cd83a5faed5def7bbad519fcab8a856", - "0x857d879cb9eff22501d883071382832730704bfcc5cd5b07cdce7ab8dc41c565a1eb0e7e4befce8e0e03a4975d3f11ef", - "0xa2879a20c0360c516811c490289be7dfbf7dbd41d2f172c9239f99e3d091957e0446854f9d0f753d90384a80feb6fa56", - "0x83518624f33f19f87096a47d7b8e5f2d019b927e935a9021823fac6564c4f2328dcb172e25bb052748191e75ac682bd0", - "0x817ec79132faa4e2950665712b2c503d7fb542aa57b7b36e324f77cda79f8b77bde12314e2df65c5b5296a6bca9bb0b4", - "0xb2abf8fb7c3690816fa133d5b4aa509cd5a6e3257cfeb7513d1408b12371c4d58c44d123ac07360be0d0dd378e5bcf99", - "0xa9fe1e4fb1574c1affac5560939face1af6657f5d6abce08d32fc9d98ef03186dbb2dbb9fd1decd6d8f4e4687afecce9", - "0x89b2f41e51f33c3ca3e44b692e8a6681eb42a7f90b81c9e0a0bc538341df9e2039ee61f26d2ebe9e68df5ed1bccf8cdf", - "0x8b35aa7b1d9e2135b35a1d801f6c9f47c08a80e48603f3850b425f64e7fb9860d1adda04f92a1ba22d00dd0a26e781ca", - "0x960574978cadedbd4cd9f764bee92f94e08b7af65403de36b21bffc9424bcee845b3b028af2e9e545dd77cf1e69a6a7d", - "0x840aa0f34b5b6c39471f54d9e85f1eb946468c4fc01963a9027cd7864df01f73c2e864f1f07aeed4b1b1af72808dfa07", - "0x834464a84a11200e3c60f816044c254a7d9baed64aed45a17325cef7fd62338e0a26da78d199d30ac3411714dc813223", - "0xb4ac6fe2f5059546f4ad9a361426ead33237b6b9030b129bf0122085c85fe4ccb33cf90f5a7f23c5b708a5ac64b487f6", - "0xa12aa9035464795f2a67f3eaba478d5ebc838ed9e997c7dfa241e1ed60a94b367d3f969ccf0ef02028c35215698b309f", - "0xac8d926492ec2bb68c6d8aa9bce49085d3d266f3d5f1f924032b87c42b44e41da7c047eeb01e4618f9d0f123dcaa537d", - "0xa5142425825d813ed8ce1849d81aa40b11f1cc3daa89a9f798dd83065c74820b4da6122b3308f528b074531df66e1a5e", - "0x87ff55c9f5aae079e7bf24084dd9c6b3bc260727d942d79cbe8dc13341d98525b4ece3ed8169994b56a387642f09134a", - "0x88e680f148ef2ecdcfed33b61f9e0224790fddc9069bd6999e9bede1791e761637c0fd60b52990b6c93e6e5429e483ce", - "0x94bc20bf5aac6e9f1060d02eacd06c42aeac9a1c5635b15a83985dfb03938ddb4999a822e865635201489c7f75601b29", - "0x849221cab7599f25f0b114df092bd5e8c2430503ae959bef1543a101de0790a78245db6a145e26f40b5f9bcf533219a3", - "0x88b6f2c2e7a7954fad11009d839ce50780921f80292320868d481e38d26aecd80fa607e82219a99532d88cf33b39f562", - "0xb0d82947dc23c0b88b86c321b582c15decdb825ed909a731b42d46bc895009515a3dc646c98dbec7d71b0722df82392e", - "0xa2cfb9f7c1a76c8073363c1c3bebe5dc29fa76533caea41046c51ea9bbdc693a121b957cd96be5b6da18704d1865cff7", - "0x8f0ffab9a83355a22683a9d998d1c1089449eb308711eaad4265f05927ec6d0d1ca39217082a0b372e02234e78dbaaad", - "0xab024661e2b2937ad374c8cf2e3669f1dc55558a3a881e9ec4d461f27e0fa92e2bc88230f038bfb051cf2145ca747a07", - "0xb98d9b9ec9eefa56d38cca959ce1aee7b6d4b41a8dbbd34b3f50c0a5f97f84ed2502ded1ce8cdb5895872360d4ba6d61", - "0x851244158b3184a62d2c98d148e2b1102cf0d5500906bbc2deda95acc5e3bc4b4a3344febbb31ce05a56dfee86a74913", - "0x860d9e2cb886bd3620b5d7499d14b415532482569bd45fd76e3e8052d78a73ae4b2b41f139f9cfb136564108cd93c0f3", - "0x8305a052a0fb2bcd41f3aca075c5f7f233bd8f861451d03f3a6e6e31f7d08dd89fe1eb4dd7b238a78b12ddceaad9768c", - "0xadb703e4778c7e14fb83541ab00b5fc344108243ec6827c5d9b302ee68321aa569da1718424e6a57979ab7536d5eb43b", - "0xb1a754b87b9e21aeb86217ec5b4fadb7535344567f1bd15e88ec12a833fed68e26bfbe03b7709ce24ba6c925ea0a0e07", - "0x8c1e2f6bf820e1653f3b8213e9d959d8649196223c2aab57b7ebda094f4919f88d883bcc6a0cd0be335f26f5a2a9c962", - "0xa082deb9865fe8668e91db0e4fd7fb50fb3fdae3e7bf1217ce0aa6f286a624624cf936d762bb2b6c3fead6826694f846", - "0xa10540ca05fbcccdd0a2a66aabab3b36e9bb525794cbae68bc3dace6116f58942218e9d5e9af10d67b5f6fb6c774fdd4", - "0xb81d22c4ab0ccaf447cc5fc2ff3bd21746617e6773bf43257c0d80331be2e8437b88c9c45309ee46402b38d3d4911caf", - "0x84c7c6e924713cab3b149f641dabf63ad5abbc17c1d8ee7802a6630507aa1137f7e034ba1d12ec13f1e31efbab79bf13", - "0x8773b9d236e5fcfa8c32e471b555264692006bf9a869a3c327aed33da22dfbf5780ecea7158904d4d6ac4acfe9789388", - "0xa4c2c1bb7290eb7af2013f7dde78282148593f066b09faf42e61a3fcf81297caa5a00fdbf6b93609c8c5782a0f25341a", - "0xa7bfa6e3f273da3dcfac7cb9906bbe9fa4fc2872b184d79813ee273e6cc4d7f37f46164362707a1976f5b6a2c5d7ed1a", - "0x8b71502019e4263fcda354a0fd10aaa7da47f4abb7a0c715c7b017e9eea14f2b64009b29b467394668c7ca995adedf82", - "0xad7460fba7deccc3f9a7d204233de47ce30ffa55e1e164975cdf06480a6108720bc397b93ca8c959df77d44a1e1f05f4", - "0xa5b8df96ccb7b078a3918e74b1b10da21df982538d2c9313f5129b2797c8a6db9ff8707241ff72d3e9d5983397321736", - "0xaa6cfa6386660c01879656da6c4e72497690708bae6c5cd1d088f443cb5bbbe75561d6eec256a72b9728377eb83ef973", - "0xb9699ce7c5c878e44114ab7a598646c6c7616b8e08a9ef8ec291189ef9945c1a538d2abf1ce3b0da0f8eecb303b81b43", - "0xb8d0fd1d278f53c455de92ec4357885fc6648dc5f276930263da7dc885b4a9628a2113e28b66b1e64fd08189427c614f", - "0x84ad8d262f6ef5d93e82ff6f4af995148eedf6d8e079124daee9b99f506e2968922eac2c7d4aea741fceb7733f20b2d2", - "0xab5e30ab54641e3a44450118b8235554e0fcfffdfbe1430ceb3f7ef33325725741995fbbbb0c16f0875aef0f1e0c98ec", - "0x80e2cf8bf386ebda46045852751611f2af80eca2e910d9ec5f6e2c7376611534604ceafa639272b3d503b02bd66525a6", - "0xaaac69af8fbb87da1c1b7c1b9e59942887ae839a91f0c1d191c40fe8163d7f1dbe984e4fd33619c73e63abfa7058f1e3", - "0xa6194224ad838ab86e84dc80e9b8abb121ae6c3c7fddc476463d81f14168131e429a9757e18219b3896a667edda2c751", - "0xb68f36aa57aedc7d65752b74761e49127afa65466005a42556230dd608ecc8f5efdb2ce90bb445a8466e1fc780eea8c3", - "0x886c3fa235d6977822846b3d6eccb77f1e2cd8ba3dc04780666cf070cae208b7513dc4525d19a3fb6385cb55f5048e2a", - "0xa9801273ef850b99eb28f3dee84ba4c4017c95398730c447efe8c1146b0719f252709d3397ce60509e05da74ed0f373f", - "0xa58c2a5dd13e08ffa26a6c5e5eb18bd8f761ab64a711e928e6101512401ef2b1c41f67ba6d0823e16e89395d6b03ebb7", - "0x91318b564ec8b2d8c347ca827d4d3a060272aec585e1acd693b2bafa750565c72fec6a52c73bb3ae964fdaa479700532", - "0xa058db5d76f329c7e6873e80c7b6a088974522390ccaf171896066f0476742fd87a12fe9606c20d80920786a88d42cec", - "0x9838e07f9ed8b3fbca701be0ef32a3f90752bbe325aca4eaea5150d99eb2243332745c9e544fd1bb17e7e917202edab9", - "0x85a9ae7dd354f36e73baa5ecf8465d03f0c53b24caf510036b3e796e4764a2bc17f0373013af5b9f1b8973226eb58cd1", - "0x896a4ff4508d069a7da6ef7bed66e1080991daee8b227f3c959b4f47feaf75fd1b9e03d0917b247c2db11e105395d685", - "0xa36d9a6a037bf498dfc0e535f2034e6cd433c7b52e520469811eb2e9f04499a6ce40257d2905300df7d81f38d1bba075", - "0x97aac3c5492aca879b4c06db1834b30b8850a244d29296046a84c637d9580c8521ab4752ef814c96f255a139660d7639", - "0x8552bf592a84ab4b356d01643c90347377ebf1f2b38a8c2e55a3f34537b8c7dcbd62e6776d6c2114f2bc2d4344d1567c", - "0x84474ad163db8e590943ccd1dc50b4f444beb8275919b33f53d42cba89831e9d42ce2de52b26f4412e2a0676ce913277", - "0x900799dfaf5eafeb297c7b4f892438bf2a65ce04034d66f8e5cc3836e4eaffe782fba4f4455a0fcab49102a240d1780e", - "0x817176415e35ad4a204b9fd5771bae6cc270f6ff050996cec89efbe461b2940ae5dd3c6c7d7e31b1da5285b207efed27", - "0x965e5791c927d47569bc54ec9b4c5305788aecd87a26e402aabeaeccc03480df46f0586ca2e2a9918885cd03332af166", - "0xb96d9ada4b5a04a94807d71726bd557de94fbd44042d7dba40560eebe8658d1da49eba54499360619f3b2c38e8b5ed6a", - "0xa07b6d641a43e02e7868f30db4dd5069a2f221b4f122ce9b11eac04abadc4f25f3207f1d2d86c7935b1a3d9992ea9814", - "0x8250d4d8ccac846a4b1a9fa392d9279b5bf2283c8b95d8164c3c0d199fec8849eab85755f2a2a99d584a0407742e3200", - "0x8324cf49f56fc14162f9a9ebda1ebda0388d09d8688f1938aef7dbf9505fc119069efc552f68cc7cd9213f96fda2c6de", - "0xa98e6f1e85268dccbe3bf4e92c9f455c58dcb53de1dba3b78589adf2e50e79f8e245f956e0d098eb46f5d3746826c6dd", - "0xb103ec12f266b4153d67b54d8fc079357ee342cbe5008adc3e0689a7f788534c4601e60e939731f49e4a1e24fd589f82", - "0xb2d7681e866420413cc98eae67614d383943e3762d5742cb3c57e26157633c20880eea1209feaf68402d5d33dd699708", - "0x99fed0ae4112ec9ed74baac70d202a885aa51cb555a3886b49016744dd4017640dd5dd564998c4d842a9f38f3e004e68", - "0x95c35401314467219c8bfb1ccd1f1eae6ef4fa9e48fbea14f70d5315e67b16c46cd03554471840e4a5030b077d2a3856", - "0x8d029380e0c294400d6b8673a23aed43697cb6460fc1bcf217aca3b47cf240886644ed09521d6a05f6abf56f99722d84", - "0x8ef54d1dc0b84575d3a01ecba8a249739edfd25513714dd4d1941fbde99dbbc392f7eb9fb96690d7052609af23aa57f7", - "0xb8ad2b7af4812417aa8de8f33a26547f84bb84f39501d4b7c484cc8bb54c7e166c849b95240fbe459a4719a6e3bf1651", - "0x9858545de898721d19930d8b360cacc5ce262c8e004867a050f849f7a2f2aba968c28d51f24a9af56aaba23a9ded4349", - "0x94ea5043b70df1db63f9b66b4f9d8082776f721b559f27d37b45e0a84faf47f948d7c4532dfd854a4bac49fb2ec8e69e", - "0xa2fd88d7b15e3c2778f6c74470d0f9e1a1f979a4d58bd205361eacadab9973d585a6508e685e640b272d6f8a448eae05", - "0x88defd6bccd55db8ca84e3c8d0fc55a3456b41788f1e209d0aec19c9c70febebf3ae32cacaa1dbbf796d7ddea4b17995", - "0x88b8cde2449d5ee7de2ee2f32e845d27e171a51ef64f1d3d8a5fd7dbb9f898ea70eb7f6410cddfd7b7ae70ea8073cc2e", - "0x8e044fff6ec557824866ac76301b6d93ed19b7177aa6baa95046330f5d69b572b59200e3653cf2f2b559455e782e8960", - "0xb5446b4d6741c824885790d2d26258729dc0ba2f469c85a47d38886d933b785a4f38a951d37f3ef4bd5091c03fa3a071", - "0x956c8afa8056e9a71ab2e8be5241ddbb3a8b3cff2110cb0e7389493d9fa45e6c4b769ebef540a952db6dcd8bd55baf64", - "0x925950cae25615246e29d594ebf34fa7d52f78a9867338648158f2131e6eb4dc17e18f9db8a5fdd76d017b3a9798b3a7", - "0xa17ea4b43211ba990270c21562690b3ef154a46c3d669c4674c80bd424cdfa95d8850c8e882b8d06504f929cba3d93af", - "0xb315ec723973a138508afc387ef651fd8a8804f93975fc36c2eeb796a304eeb1508518d8703e666a74d14318253f526f", - "0xa995742d7433b3f230e622de23cb2d81cac76de54831491cc29768eb4a56da60a5cbd573e1da81fddc359b489a98f85c", - "0xadb2e89f0d15294d7118fc06d4fdbd9c51d3ecbcc23c69797e5b8197eea0d6cd1240910cf22fcab4ef1e2dc2dd99da91", - "0xb5ec9f9fcd0b5d176b643df989bb4c4c1c167112373d662fb414875662d1a93160dc0b5cdf540e8a30e5fcbe6cfbbd49", - "0xb1291b53f90aed275df8b540c74a1f9c6f582e16c5df9f5393a453a3e95624ab7552e93d6e2999784e164046e92ef219", - "0x8bc7b7b1a584a12d5ae63d0bbe4dc1b63c9df9c89bdd1095ff4b8e7c822bf8c1994c92310a3644033c7c9689f4b7d2b0", - "0xad7fc45506a10ca48f991714ecc055cea376c0cbe667f3b40ee8dad8446218835439ae59bccc474cf47b053748ceba6d", - "0xb134756828a5f5725c0b95109e09ca450e3834b127163a0aeeb544e63cc0cdcdf66f8ed98c331c7c98758f46af369a84", - "0x94535bf1636be0974b112fcec480ed8eafc529933f3065c40e417e608e43a392206cfde8bb5a87b720263446c90de663", - "0xa4df4f6efbc3701000fb072e5cbed2754b9ef5618386c51ff12f95d281d1b700fea81fc1365f4afc66a7c83bd0228fbf", - "0xb0336b3552b721087c7e2194976a9119aee13ebed9f1c3c494353707fffde52d004a712965f460062ec9443620716302", - "0x99a39d1d1ee4283b75fa8c1fa42b6a3836b734be48bdd48050f9b05e48db6354fef509623c6ec8d447d630a9b3352b77", - "0x8e3dc3583d40956f9e784e8bbd0b5e65671d2ff2a7c387b20fcb7da9b969f2d122aaf7f054d450dc611737604548c03a", - "0xb5068ec5b7bcb5d8583d51cb25345990f50d1f7b82fe535a6a6b17756355885047916f466ea3ab09eef5516bbf2dda90", - "0xa8284ec1eb1d21e693f31a6c074199ee85d8a8da2167bffab5fe240defa2773971c8437e358a18f7e58d1e2954f57f6f", - "0xaa7415639d29081acbaac3e9c6b059d68e8702db3f430b86bb6e220d476fa74841c875e9d471c8a5423c58b6fee3cb54", - "0x8afcfe6f65fa6e07c2cb3e1756c0ef2c589830be96edd50c3c248e3b17f51a4b08ba92ef7eed7991d81667ddfbf2bf7f", - "0x83b9c8dec8ca8f9b85f0e36c08c5523cfeafb15a544398e6f93b48b5fc4b15a0bd05c0f176a9c2469664acab8dffb0a8", - "0x82a128a89ea46b9debe5c903b950c0ab30cd7570b979ca911500b5c2cca5c4ee6b2c2fa414b5f28e367f4671ffce60f4", - "0xb79fd0ccd2629a361cd6f9307c02ecd4d1f07e4ee03ce4b542997e055b07a026cbc0ba05fe3da309efc58db2e401a8fe", - "0xb190751141093823b4b5324cc26c4f3258552f7893241201f2fca1ae9b1a1d4d4964a9abdde8642cf308ded61ce5ef09", - "0x935fd48b95aa6f9eada0cf9a25a573f0ffe039888b3410788c41d173747bf384c0ec40371bb4383ddcc7d9f2db3d386b", - "0xb9affe100d878491ff345636ffd874ce1f27852a92417694afce4163e6a80c78b2f28d78102fd06c3283ef273ad37642", - "0xa877670276d49ec1d16c9f1671e43ade11c0c1a1413755f6b92be9ad56bc283e4bd2ad860367c675d5b32ff567301fc4", - "0x8c660d16464878590761bd1990fd0fc30766e7e49e97b82ec24346937856f43990e45aa8ad37283cb83fa16080d4a818", - "0xae1412087da5a88f3ccc45b1483096aeb4dcf4f519ff3dbe613f63712f484bdd8b2c98a152a9db54cf1a239ae808f075", - "0xad83cead97a9c3d26a141604268f8a627a100c3db7e5eefaf55a1787ddc1dd5ffc7544e4947784cb73b90d1729003c8f", - "0x97c3140ce435512a509e6ff3150da385fdf9e0883a5dc7cb83d616ec8d0a0014e4e0fa57a4d12c7997cd84e07d49a303", - "0xa353773ff68f1615454555bf658eabdcca40a9c7bced8537ea6fa8d54764fd1f032889e910d2a2a342835513352e2d2e", - "0x89e8df0c17a36ffe08149c2ef8b27306d04cdf437135aaeba697abc65e3c8e91bcf1817919a8a826acdbbe7dce79a18a", - "0x9928c2da15ac6cb20b15859c22508cfcd452c5643cd22eb84abf5f0a1a694fdefcd8fc329c9b40babc52630743d6b65a", - "0x99d837b556f8d13108eef6c26333a183f59383b39958dd807b10590c3d37f62ade6c4a320ca2e70567e0218b0ad5807d", - "0x9272da080e4aa18720b634640b01bf1fe506c7c8a89dee8759a53e2ca5cdbbd4a4f3aca54924c46b935362cf1eca066e", - "0xb4d39752c882de1c1daf3854202c1d58c2bcf35c882006eb640fe54a97be2655281cdb91c30d1a41c698617c2cf64b01", - "0x8bf827f4a7d47e07374d338a3d8b5c2cc3183015b5a474b64b6086fcf0cdcf4852046c9e34d7917d69caa65a9f80346c", - "0x901bffc7db9c9416e06f593a76d14f6d9e5dea1c5f9557bd8c93b9e70aa4782bab3518775c2a5b285739323579f7cf0a", - "0xaf7e204388568627ca23e517bcf95112ca8afd4c6056b7f2c77c4da4b838c48791191565fd38398587761c8047d11c47", - "0xab2576b5366e6bd88b347703f9549da7947520d4e9de95d7e49966d98249406ed9270fe69347c7752dad47e42c4ea2f4", - "0xb12e3b228b761dedd99d02928105494ded6d4fea3026d73d65ebffa2e85e2cd75b6d091135d418dd95ac102c22b5ee31", - "0xa20b4a752685d5e31ee7e2353c8a1b9a5265f12bb775004d282a3ecd9deda44831bac1ac5151646428b66909b2a423f5", - "0x91a1d4bc0062a86cc6786a96fd3eb4436d8a4a187b7cbba02190d1cd6ed3c3797d9ae7d6ddc413f1c94a21f62bd04ef5", - "0x977f18da1a5df5cfdd0276f583cfba2b2a0fc6139520664e20068f8dfdde33e29d179abfd722f142448f4677aa47be6c", - "0xabc3ece90f0f7b1d80fd917de27ab0d88cca584ef959da520825e54cb5a71336b15f8b348532d08d47a6fa600527ef25", - "0x888d36a2c7cc13a1c1aa338a183a74a1f57713e76cb825f9837f43279ce4741999b76a16928147537bcc20f2e0195b0f", - "0xaf3f5dfdc2dcfe19de893f385f39f550cb1dab67c2e97f1d5fa735e5ec96d6680066803e8a0eb010dd4399f654195513", - "0xa0fb4e08ff56530a940a86c28830956eb6dec2f020f7faaea7566faf0a4fafe0cffe01480e87763ec22f201be51a6451", - "0x92343c5b107910b203c64a79c93d354f7ee5b7d1e62e56732386776e275285561cb887019cc00d3fdbe3b5d54460bec1", - "0xacfe7df83c4624188a1011ad88c1e1490d31a8a8c8016b40aebcdd7590d9c0793e80d2d7ce6a7048876621c252a06a5e", - "0xa7da001dc1e33e0e129c192d469d2bd6e5d2982eb38f3ba78bae0670690c8e70f40e8114a57bd0718c870ca5dd25b648", - "0xa903de5ff97dc83628290d781e206ef9d7c6b6d00cadc5bacffb31dc8935623ab96ade616413cb196a50f533e63641d6", - "0x8f9658d42ad14a60bbf7263f6bd516cfee6b37b91a8f53715d69f718a090ad92484061c2cef999816760a78552fae45b", - "0x8c15b72b3d5fcb9ffd377fd67d9dfbdd706593fba9629002639973db12aac987bd1db70250ded31c88e19efff612cdb8", - "0x88a2a4034decd854fb557960194ff3404e239953818a8a891bf72a0b26a8e570a65c4a630884de991ae7452b3234f31a", - "0xa09cae5c4c190537bf1dd75bd7bce56f7b799762af865bb9d1ee970f6a133c27cce0dd0f14a0e0516ceac41054e6998f", - "0x9760ebb1b40f9a97530c3b940d4ef772a225e5b63bf18283f8e302b9436c5209f6294980fd37058060e429fb7fdc3a56", - "0xadaa9400eb86d857dc591b25dbe3bc8f207b69e77b03cb5ee01f7e4b006b5c8f6ba2b51b5a45687479885708509363de", - "0x949efe6b00b3248846747a9ad4a934d6e4255994c2b540a59fbbde395fe96d69bb67908441cfadd8c8bbb561fe52da03", - "0xa19a45504b6b1dc3a0fe0e6a1384734a3dcd5a7cb8fb59eb70e49426c4fc44946547443d558e5719a04884ab3a2811ca", - "0x8934c9ee21e8d1435426fd0f64232a0670a7946ec524c054cd4f2cc8b1be9f89cc11002ca8aebae646a2050d91716b10", - "0xb1150ff8ffb34ffdcf7d603348c0aed61e5f90ee0a1b814079fc2a41325c75f2f9ee81542797ede3f947884266a772e0", - "0x86ce8cc7c1f92af68de2bca96ccb732f9b3374dad6657dfd523a95e8a931a0af2a80df74098514a06174406a40c16ba5", - "0x90faabb9ace9e13fd9584932846ab28a618f50958d2ce0d50310a50c3bc6b0da4338288e06e5fcbaa499f24a42c000d5", - "0xaf4a935c2d8df73332a16dc6da490075cf93365bd0e53e2374ef397514c30c250bcac569b6df443985cf3720a4534889", - "0xb7f948ee90f394789eb0644d9f5ad0b700c8e44e5e9ed0e49da4cc18483676d25740710b1c15a557965da635f425b62e", - "0xa917913091245beed6a997ff7043ecf60c4d655c4db0b1ef1c704fd9b0e1ea1335ce8b9f45d6e120f81805ce31555e30", - "0xa48099da8406399bfb1ba834f6f7d864111d0036969a5cb64089947a63dd9467d3857b605e9f57f5ad5f4ec915088d9b", - "0x9784c3f9be42eed354542b1446d734521f8e3f01cd9d495ae98f2e4a3a16767fe2ad909e0def5d9a6267f3fc6a172cd2", - "0x8d9afaa323847a3226ad7d7b60d87322ffcda2e4a8df89f58a076f7972d896588de685a2e155e243bcf9456b0a0d6d1f", - "0x994413faf0b843f4ec1842c706c45ea5f24351c68674a27887bc8b182eda756856e507a4e8bbfd937e2c4c581b629ee6", - "0xb3e72d9d1ddaa00c7d22f25462d6e9f2faf55e30d138dce8bb1517eb0b67132db758668aac26164fd934d732633bdea5", - "0x8e95875e338f714e9e293df104f0ad66833bbd7a49d53a4f7f5fd5b18a66a61aa0a0f65cc31d55e0c075e0d3e412cb90", - "0xb980091862b1a9f9334b428eae14bbf1cecb4849e3a5809773b0d071d609727270f6ad97f329eca896c178ce65883db9", - "0x915d7ae5ae780bdba27ba51a9788a8852a15355b569581d1f18f0d94bcdfed2c1ed5a4f58e049e9825cda11f92b2c2d4", - "0x83e581058edf9259d0b06128282327cacbb6afc939578223cbf93544599f799a8dce1fb21d52464f990a877086f42506", - "0x803612a38b6f6efb97941997e101ac1878e192456f8fbddb3359aa7f3023434ed8fa92e60ec8e7b4473b1948850e4311", - "0x864a1bf4ac046161617dde282e44ab3cc1843da01a09ca58aa00ed00eaea9351a07a9ec16d910819e7dcc28b8d2c8ada", - "0x922eb142845975d5f6f7dcfee6cac8c299b3730400e6bf82cc0bdd9888de21de9d9f1530640f702c003e1ed63b140cc7", - "0xa7db03c5be647dce1385ebc02f4825a654447fa8c4c8d4b22e635dbdd2b3ccdf219384e49a80cfb1e9e6182b6e4227ed", - "0xa167289ff0f0967bbab6479e4a8a6f508b001bbe0d16cad36ab4c105ad44f3f180e39a6694e6cd53bc300fe64dac1e8c", - "0xb7766431f6379ce62cba22ab938cdbb1b0c7903dfb43980a417e0ee96c10b86b447241e9dd4722fa716283061b847fb3", - "0x90cda18c5d66f5945c07c8c7dc453dee1370217ccb851bbea32578599aa669b4dd245dd8a9711b27c5df918eadf9746c", - "0xac690cd2af39932874385fbf73c22b5d0162f371c2d818ec8a83761e0a57d2db2fca1d757343e141e1a0348016d5fc44", - "0xabac820f170ae9daa820661f32a603ed81013c6130d1ca1659137d94835e1546c39a2be898b187108662cdcbb99d24fe", - "0xb2ea5a5950096772f2b210d9f562f1a4cfacc021c2e3801ac3a935f2120d537471307d27b13d538dcbf877a35ff79a2e", - "0xad94af4d0699cd49ba8ca3f15945bd09f3f7d20c3aa282a3113cdf89f943d7793e59468386b067e3c1d53425dfe84db4", - "0x83788367ec97cc4bbc18241cbed465b19baa76fab51759355d5618067009298c79d0a62a22e2a1e6dc63c7b90f21a4a5", - "0xa3e142d879096d90b1e0a778e726351fa71996466c39ee58a964e6b5a29855123d4a8af47e159027e8e6be0ca93d9955", - "0x860831f8d3edaabd41be5d4d79c94921625252aaec806251fb508e364e39fde8808d38b10d557e487603a1b274c9bc3a", - "0x88da39f334bd656a73c414ec17dda532059183664bbbac44eb4686c2601629ef8ff9da992c337a842e3885b684dd0032", - "0xb50addbdf7164e8303f33de5ce854d6f023d39c1c1984b214d9e5fb6f6001cd5bdda816f048a438ff3d696872672f805", - "0x999e58c4c69a912b84561cb09610e415b43832beeb95897eca8c403ef4754f4277754d492eef3673afd4362f50060fc9", - "0xb88ea0f60f8119c5a1fd9294796d387472dfad22442b29659713d1d88e7d854cb7cf5c9ef773627781188626bb2fb573", - "0xa068b3844e9dbcf74b54fd55904d56af754d8ce4c619fead7a07f9bfb9d02118db7c512ccec2489d2a84374ec1d1fb6d", - "0x871dee023768636003c799e6f6fd8d31315a4c0da7286345cd64264a016693b3485e0732be1bbd34dd5fa04dfa58a983", - "0x8021e8f508680df12e4a5a1bd49f2d7142df65158b0a7198ffa83abd16053a542fb93ffc33e5279020ba8c6a26feacf2", - "0xb5d3cd64df5bc965228b0bd4ce9e5797c409f7b64a172ba165e44a8e4b38e3d5fabc3e0b9a19afbfe427f887c40a315d", - "0xa54fdebbb594bafcefb1a03697711e0091c072e1cc24fb441fefd4e0a0518675a1d7b0966cb8294051d7ec0ac175d0cd", - "0x93922202337f72969d6d6e14a29c9c75e0420dfba712029941d1504b9f6f9761d706cbc0652cd09a1aa5d22aec766af1", - "0x9711ebf1c7c7426190d4afd5dd03b014a456bbd9d90ed101623866a280550df26a629dde400c03ee3699f7d827dc0bb9", - "0xb4d686d8bc5c1e822a50124c1cc23c6bc3a1577a3d0b8d4b70d1797418aaa763283c09e8a0d31ae6d4e6115f39e713c4", - "0xa533ea2ac683e4ba07e320501a5d82a1cfc4fa1d65451000c3043f0fdac0a765cc1125d6cc14fe69975f3b346be0fdde", - "0x94ee563134fe233a4a48cf1380df55ead2a8ec3bf58313c208659003fb615a71477e5c994dc4dcfb2a8c6f2d0cb27594", - "0x93e97d3f3f70664d0925be7aee3a358e95ae7da394220928ae48da7251e287a6dfbd3e04003a31fab771c874328ae005", - "0xb57440d34615e2e7b1f676f2a8e379e1d961209fe00a0cf6798f42b7c28dbd03172fce689305e5b83e54424bc3f4a47c", - "0x97644084c6f7b4162bc098bed781dd3af6e49e7661db510975528f1dea8154f3d87e979bcae90c3df3a7752eb0752889", - "0xa923b27b225b2a6dd5bdc2e3d295b101cac5b629a86c483577e073cea1c7d942c457d7ff66b42fcf33e26c510b180bc2", - "0x86698d3b3873ed3f8ab3269556f03ac8d53c6e2c47e5174ec5d14b3ed5c939750245441c00e2e9bb4d6f604179f255ef", - "0x87946826d3aa6c7d53435c78005509b178fdb9befc191c107aee0b48fbe4c88a54cebf1aae08c32c3df103c678bad0ca", - "0x860864896c32b5d4cb075176f4755ea87fea6b9cb541c255a83d56c0a4092f92396a3e2b357c71833979b23508865457", - "0xb78fa75d687349e28b4ddfe9e2d32bb6a3be13220b8f3ff1ded712088bd0643da9b72778bcca9e3b103b80097f48bdd0", - "0x8a188b940446598d1f0e8c6d81d3cada34c4c1ae0118ec7e0eacc70d1bced28ae34b99667d5793d9d315a414601c3b22", - "0x842ac6f7dc14191ab6dddffcbc7cb9effba42700a77584aa6a8e17a855cd444c5d138f9d61bf55f43c6ffbcc83f92bc9", - "0xb6742902c3d145a6af9738c01cf9880dd05c85f0d0ef7dbe93c06fdd6493333d218339ebc2a02be1895436a2f734a866", - "0x98bf18488483c627b7181b049d3e6f849fce1f15794de59dcde6e5a9b0d76fd484a46e48822a6a93001d3aa12f48bc6d", - "0x8769cac10bda8c53a1c19419ef073a5998f73dcf2ba1b849561615a17cbc0a49bfe3eb4ff8801dd36a22fa34b9a3a7e2", - "0xb45c084d58028fdfae792210fcd183abc4ffddeb4cf52ebf3f8a50e4c4eec2a2758f1241b0920bebcb24b757c778577c", - "0x85c1216eec8e1fbc1af9b36b93c5d073a81d5fba86a6daae38748ec1573eacc6bef209e76c87a6efbd7a3f80e11d4c3c", - "0xb8007e34bb3f927ec06a050b51e633d7eb9e9a44715d5b39712e69c36177a03cd68391090cc3293098e54f6cf65f6caf", - "0x8e85527b27c9152b1ba3fdd532a76a79064ab097570508f233e09978761dfe3012d537411b47d0e4b65265eb32cea2ae", - "0x899779f3c31a20b76068ec8d59d97a64d2249588ddfd69dcbaac6bfaee8ce0ff3c5afc4e17c934ae7cd041b760eb555d", - "0xa5dac3d8f5fbef018509612e25d179f60d2a62451c76426bf546e9666fcdc73263d34aa6fa7e2bfd4c9947bbf5095eff", - "0x896900eeef9be2b2e755128e7b1c436af6fb3984f1e66c444bc15fcf3959013b4902c381f0eab1247f878a6ebd1f4ee0", - "0x8cb17f4b0af2e9b2cbb56f46e6a5d6874ea0daf147aae77303020b4e592ddc92e0dd058def7da96258b3a68b223bf22d", - "0xa1b6d3f09a9fa7ecc021ab7c5396541895da6e9bf1f9a156c08fc6f2b815a57f18c337ccfe540b62d79e0d261facb2be", - "0xae70888811434ef93da60aeee44f113510069fd21161e5bb787295492eb8df85103794663fc9305f04adcbcf11ff0c5e", - "0xa84bbc8624100acfae080ba8cfb48fd4d0229a60b62d070bd08fade709efc6914dc232d3f7bed76a59204f9252321aad", - "0xaea47d54652abd8ca213cfc623c8e30780f37b095b59ac4795252a29c2b6bc703a5203acff8831314478b8ee8771d4d7", - "0x8dd438eb8be14935f759aa93021c2b24e1d588f7a162c42c90ec3a647b0ff857f60e24c0a8953eb7bb04e04be70f11ce", - "0x922b07b5469680a10e7532766e099896f4dc3d70c522d8add18f5f7765d4ddb840df109146607b51ceddd2189fa7b9c0", - "0x83ef6ebd0ae6c569d580093e8b0b78daa964760556272d202d343e824c38eccb424262e5b7809d3c586f9e2e9c5c5f22", - "0x97f98bd357db6e093e967fe180cf67ed09fa711580a5ad48f07cf095b2e8fabbe6319f97d1f15d62c0ec2227569d8dbf", - "0xa1953a4a22fe6c2beaf2a5e39666b0eb53018af6976e3a7aab5515550ff2efa89400605a43fb2c4ac1e51961dbd271d8", - "0xa5cbd67f4c0bc98e20aa74c09e6f5fb6f42c08e59aaa477b4b4e61434c8884bc14f17cf11faecf46dc4b6c055affbad2", - "0x87d96818f2c4f12fd7705cf4060a97bd28037c5ac0f0cc38f71189ec49361e438ce863e6617651977708094d5336d1da", - "0x85e7c2daae5fe59f8a1541c94df50402a671a17dbb8838113fa4b7aaff6114cf2bb5969410cf21e6a162857f2f7a83a8", - "0xa19575083e1731bb04bb4a49414e97aaadb36d883aa993d1f6847db50007315444814740e67e10177a14e0e074fd4c7d", - "0xa00ebfb5bcc3a6da835078189038a1e56b7dab6be74332b5ff7440e53b0f9e1eb9973effecbbf37000021fcf50c7c1ff", - "0x8969d7943abd3b1375fdfc7d6124dde82b0f7193068ed6ec83bcf908734daf3487a6a30f7b322e54a4818ae5f86d91c0", - "0xb959c8d210fa43af9b20d1fe0ea8c4921280eb4544ef6ea913309ff9d61c9327096707e84dc1662960519be8e7d080a4", - "0x9011d8ac651c42e0cb03931a9e960f58e02524c6b666047525e3b9097e9f35fb2b4b278efcce2bd5ad463c6d7fd56694", - "0x937e3b22ed0fcdbd9ea5a1b97b84bbe86b7f5b2de3866a930611112f2217f4ee7d9822c4ab1253823f77bceeae0c8e10", - "0x828997e5d121f4c305e018a0a0ba338bd6a34a7b4dc3c5ceab098ee57490311c130e2c045b9238a83908d07098d9fc32", - "0x8d114808eac0f2e1a942d80dad16756ec24f0276763cd6771acb6049472e05a9bb1d3bbd5957f092936b415d25c746b0", - "0xa063c5c26267ae12887387cbebbe51fd31bc604630b3a6e8e177e71d4f26263be89112cd12d139dd4c39f55f0e496be0", - "0xab1e1582c8d67196d10f969eeb44e6e16214f1316aa4a2a821f65ba5834326da6cba04373eabfd3b3072e79e5c9717e6", - "0xa17b1dbaa11d41457e71a9d45d032448091df7a006c1a7836557923ab1a8d7290ec92a7a02b7e2a29fcea8f8e374c096", - "0xa1ed7198da3591771c7c6802a1d547cf4fcd055ca9010756d2a89a49a3581dfe9886e02ee08c4a2f00b2688d0600509a", - "0xaf09aa60c0a185e19b3d99ffdc8c6196d8806169086c8ff577bf3801c8ab371e74165ba0f7329981e9252bfe965be617", - "0x98c04cc8bb26ffce187fa0051d068977c8f09303a08a575175072744e0a5fb61191b1769f663a426c30d405515329986", - "0xa542bf1c9c3262d488ea896f973d62923be982e572172e2461e0146190f2a531f62acd44a5e955a9f1e242b3e46d63ae", - "0xaef7b7f30efd50e4a66c87482386f39f095bff6108e68f74fd3bb92156c71c75757912b111060cdee46a6b3452eed657", - "0x8afe1e0ccd00079702f16ab364a23bbbd3da1889d07c4f8cb04fd994bf9353216360dbd364492932bfe20b8b69ae8028", - "0x9896c690999db3c08cd7b25efb1b912c3e0f976db98a3e830f086aef93222d06ce570a7b2babcd7c81d8f9955169669c", - "0xac7bcab6a281468907ef1ea8a6c1cd624159c88839131bef6aa0c22f331fc87ec6128a2c2a333fb79df549e4587e1a12", - "0x987935c08a30b099d19f96901315a2e60591baf898581c40bf5eddcda806ff24a4536e30ed1e6c0b128a83fc77b6e81d", - "0xa0a6945bbede3bb09a4a09ef27baa20619d3e15af5673b9350601bcebe952597c989870746cf75767ffb73b32c6c9c6f", - "0xb0f5590079f0a0302b08a0cc1b7a5f39cc6900c2a5cdc7baa333d8328a731b2df5dbb67e27a154d3c44ed1a795fc4adb", - "0xa7294bdeea210e528f277f3d50e89e6d79950494478998181ecb38de675020130256f2f2a075899170be964d478458b0", - "0x8ab3041b895a631869b439d5599a66facba919226ca9b39d915f19d59f9fc82393ea781377e9bd3bcc5a310e41376914", - "0x8da399b59151fd48b2579948bb82698e3c9804d70ec7d6f3cc7e82901f9f2de5ee850349a7d6f43e5e9ebd47bd78620f", - "0x80e8c32de83d1083916d768b11a982955614a345d26d85b457f2280ff6c52bb776958add7c1c8878f7d520d815b8e014", - "0x81bbec7bd99d2917d2dcd8a288722fb33ad5a4bf5416fba8609fa215fb80e0f873535349e7dc287f892aa56eb9e39c4a", - "0x9665796fe04c8519206fba58496bc84a8b9113e7ea8e152b65f7f732e88beea271dc97b1ea420dbc8257cc4b18a77463", - "0xa97e342aaaf693ddc87e02790278e4bb50117af4413cd703bdf3b7cad2d1facf31fde1303b43ab2e0265467474f97a8a", - "0x925549ebebed348886e37773b05cd8ad04906eca4536bfed951d1ee41b3d362ddc6e1a302c21ff3a2d1e70e95117922c", - "0x818fdf74d7903502101551bbf48d3c7819786b04b192d9e94362d2fcb85760d8b6f45165a5443aa5221bef400525ddb4", - "0xa9d29de7e8fd31b59f4a087168d062a478b1329cd3c81c31e56de4fb40de7a5be9a5269ef0be452c487443a0b097dd50", - "0xa85286ad573db4c9aa56221135da1e31d742e0f6ff01d6b159086d7258f78b08dad55ec8eb5c91ee9d3404b2eeb67e1e", - "0x92a79b37db5e777f9ebbebde24a95430a199e866e56597c7d0b0e7fb54c7b092c2f6cf61fb24470ddf250cf609898281", - "0x8d79f5ca67ed67d52c82949af342a9fc60fb793c47c76d84b4863c550796fcae2dd59e285897c6fb96fe31cee1efa62c", - "0x8ad2e0bda03415ab86324992bb62dfa3612d2d003765bcad1468087c27971d08bdbae5252681f0115a184f4885d444e4", - "0xa08815af979286538c31b4aa5ec805053790af1ca58a8c4341be51136d094a8a05e569d876a079033298ad355ccb7ca8", - "0xb96c2978d0165d619d08281d295e90df78bc2375d0afbc3142ebff9c2cd4b0f0aa97a9a0e3740bc4dce0ff8a9fac8252", - "0xb7752cd0e582f35ab0d0036ca9c0a9fe893a6ad325164d78d865a604a85d3d23729e0362553e8b8a3d51816beeaa30cf", - "0x99cef1fafc29e7adfe247c753c475ad4bda7a5f9558b79c86e8a65968ede67adb38dc30071925c9d66a13860027a6735", - "0xb9f6c65af178c791b6137d71980651fb09cb5b42f268999c728c6e129985a9c7d77b3dc3b50751bd29ec9ee0b3111dfc", - "0x8d73ae61fff5be883a281782698075c5650083f00399992688738856d76d159803be0059fbd9dec48f4f0432f0590bbb", - "0xa8a4a2865226de9bbf19e12c7e75318439fa6cf1cbf344d5e79a8f363439d3bc5bcf4df91b54581e7866e46db04eaf0d", - "0x894582aeff222e145f092ba15c60d3207340c38f2c6792ee2ab4d82d50fb544ae366c2985cc2b6c2f970bcc5f4b46385", - "0x956014ba2d20a056fd86cb8c7ceeab9a2c6f905dae24fc1c5278fa5b84335148ebdefec5dcde8eb9b084700724fc93d7", - "0xaf217fe2b654eff6d11a2a79fe0339a1d4cb3708b7be9f09d852158b5a44b4f9b04406d6d67c4f144fb6b69a41ae9d0f", - "0xa90752a784bc00df94d960e523f5596695d16a534fc806179e0f878fc0e82a91b25e758e91a165debd815dd1af5f1028", - "0xa697606fb32979549ad822b31df8eaaf50de4ead984439a0a33e955937d326519bb9f62c8243ad37f764655f8d32cc80", - "0xa3ad4a30922e45a3e665551e5611384f1c2d414f6fa806184b0c826af05f014dc872585e255543794ee41e43cdadd856", - "0xb29c255843a82ea74a013bac6c36a694646e61e6b9cefc4c130e2ee261e3bb5da3e0fe3ee7e6fbb009deed0530bc1c82", - "0x87e1cc7febefa829cf050aa2aea59385d1048f8617abba691f7ea9ef58eb90ad12eeb9c439af228b0e34897ba1cf1b47", - "0x994d3222f89e9c8c154362190be7167c8c2662f0cfa9d50eb4d8175b255ff0de09dc548ee312fc8226963c8c16f43e8b", - "0x8f1a980be640820f2d1e953264ca4c30330878971669852be3d5d6b41c488be1628b935388bfa2bd4de484acb0fe661d", - "0x854d90d0721579c8c88e147a4aa83553c960617b18075f8224b975562dccb30b0e02e81fa9df7070f356a0eeffc3b14f", - "0x8e156da9d4330a03e32a25a2f0b861fd3ea5c719fa4f834119baab6e5fa5236a9baaf0d44147bf0841418900037f6eac", - "0x96586fc49e53a6799242ddf617000db5a0ad20c6cb1686af2102623d64a71aaddb8e468b15fa6d100d0384e448548db4", - "0xb44d8d85c8df95d504f82d597f8c515866d4d4a326fa1b816dcc5bb0cc4ef1a52647aa5d2e84c62e194c01cae0885d21", - "0xb75c43e676a7efd199f8b32ae31f176ec667e714df355e9eecee97246f72af5bef9c5b04c11e7e90fc37bb9163f957ec", - "0xa49835ac0565a79f6a9078cf0443c5be20561a68b448289589721fded55188583f1d301925a34eea647f90a6e66c6774", - "0xb47c17ff6824a00b8f29df0adb7f06223208d062bd703b0f763c6eee4ae62d4217eef2da4f4dde33f0b469c2f2db9e42", - "0x957cf039cea6f6d41e368e2bd0cf77315938a0738f15ed9ca342f0a28658b763659ac1d1a85ecb362f13de12b77bb582", - "0x903a52f8d2439fa63f59e1e9aba864d87b0464ded63814474947112375236a6f84e8fa003cc4433c8208d80e05fbd1b0", - "0x8afd524209ff08d1eb6312b078f7afeb8e1155af649e930ab711dedda226dc2db6b0354aab9652eea7f433f90015bf7b", - "0xa95c3c9277b11bc8fe191773bf567641be57c0549913b973fb18740ff9cd7b3f7ce198fa4dc1086b2b8a446012459193", - "0x9455ce8163fce04aeff61e7808ef3aac4725e51404f0858fe5d39d7344f55dcc7871ca332aa5cb1a63a4399529e48907", - "0x809fa35b6958f94e781f2c584438b33f5ed528a6b492d08960cf22ecf63ea3aa1e2d29bc879e17296e0a6cc495439cb6", - "0xb0f50774de212dd33e5837f6b496556215c665437e657f674fc5117e5c07dadbd0d057e6ac4c42d50a8eb81edfebf315", - "0x844c65e263891d0b2fea7db6934cc4b7fb6bee2c1d0b9ab4c47f2eb3e9c5d7197dad828d38c54139123740151420280b", - "0xb13c78c9efcbb3b28eb3fe0b971380b7d5151c80948a99cd93c78b4c3ab0e86df6226a64d91e0a2ea4a1c0a46bc0404e", - "0x90300a541decad460c348b8f4257f7a29687b2362ebee8d92fd03cc0e85b285ccb0ab1cb2ff5e29c5cc5295e351017cd", - "0xac49b409ded770c6d74f6e70104c2cdc95b7b90609da0743c9923179e8e5201ead03becc0ab10d65b3d91a5be0d52371", - "0xa257b815bd8289dfdfc21af218aaba12ccfd84ebf77642cc4cf744d9b0174ca0b0d7ab2a545c2a314fd5f63c140f41ab", - "0xa34778d8446e4d74d8fe33de64b2694ef1e50bc140e252af6eff3ce7b57acf8b6577a02ba94b74a8ae32e5113cf0a29b", - "0xab9e935bcf0d8607e3d66f013d9bce7909962cb7a81174923db02dc89e485c2b1c33d6065bdc7bbbe0450b5c49fbe640", - "0x94d2c5c5c309c9eac04be4636f61bc47fd9579b47aded57cc6c736fefb8dfd8f8a5de32210f7baf2052d04c0219d3b4b", - "0xb8dda9046ae265214086355101be3460421f7cd0ed01bde9c1621da510941d42bc93cd8060fd73f374fb1b0a5f38d45e", - "0xa6674649dab5f92ab9fa811d9da1d342cf89ff6eff13ad49f4d81de45438e81a384098d3ae5ccce4c67bda5dbe246d95", - "0x8d619f7564677bacba29c346c4ef67c211f7a3a14c73433dd1a7692e16a7e2562f1d0532454af62fc04c2fd2bb1789b0", - "0xa2b93d2fd4c707f5908f624a0fc889e20164d3c61850af9125f47a1719757a6ce6375aa1910eafa4c1e8b6e20c312775", - "0xa07d5585447654d82817ef4d199984542328b238157976eb9a267f0bdb2229acc25aee510be68f65a312b68fdd9e0447", - "0x8ef55cf95e2b24d8ec88e4136399a7763bd1b73d5e90ea45e9845123e9d39a625cc336e9b67988374b8ebcbc75f2ed21", - "0xb62c1fc32e27c767c461411b02fe9aa44a86586e1427406f4ef0b346d077db91952abce79318b382ec75b7be23058cac", - "0xb252900345f5fa15a4b77fb6af6a2d04db16e878b7bd98005333f7f6e3c8e6e46cf38fc5d1b2bc399c5c2ff4af730dc6", - "0xa4ab5ac0cc15d3d17b1747c6e3133d586870eae0a0d9c8fa7fd990ebd4fbb62e9090557ca2792a6bc6271856aa3c9a05", - "0x8e706b3f2e902faee10b22742c6c33bea6f670a8937c243db96885143c1db5c979e33ab73a38359b52b8d668ccd092a9", - "0x8a6792190ee6c959d79f60c22980ca140c638d88d75660adaf9bcbe6dc4692ab5f01e0c460170f09f74d5e582e85ff1f", - "0x97ffeedfc94c98ec85ea937e064d7b290a326838e62cebd407facd1ab4f08d9c0c109d79af7cb6170fccfa6c8243c127", - "0xb79970b67c09453614ffd83a0c923c17f857c6ce3c87a356298f8351cab0def7ed83efd4f6638f48df67e07bef4ad9d8", - "0xb90f1931c7cf1822cc0a97401119910cdfd0482daf09a4d7612e4e05046295cfb4cc50d5214b31676bb1a1c9d15f9c7f", - "0x922921ad813c01fb5d12fa7fb7ed8e0b0abbf7b19affa190b36013c55b88fe3c7df0ae663c970eec7725ba37b95a7cb7", - "0xa124f33e7f28feabb4089a063a08d52b7395d24eecd06857a720439dd9414b7073bb86fbd0b04e7bfac62d3dc0fdb2f2", - "0xb252fe50bc6677c004550f240fe670974a33ffe7191ed7675da6ac36c780c2f8d02be7da5d92cbe2d0ce90147847f8b1", - "0xae5f8c9c56070f919f3df2d2284348fa4b2e39881f7bc42c9b2f5b7cb1ebeef8ecac000f37329bbe04cc1680cefc7f4e", - "0xb432a4575caf7337f11eecfcbd34a6705d0f82c216301725ceae2b3c9df20fa53d1ebef65513e305013d1e0c2df522b6", - "0xb7c016fbbc4614cdbb12db1c9ac41f9a45d5e5ce82594d568a30cd2c66c3cc9d91a2c959697b67c582a0913de661505d", - "0x8f6f3e5e0347dddc1b2a34ec0dbbbb7cafbf976f19c9c902efb5c1427d1bbd4b71abd9f3fba20dda75c35a39393c989f", - "0xb0042a1d33a1ee9fdf3fad2299b8d70c4f1862d8393b5ebe3ac2189a2c5a58bb826128cd7a39b70d524a6dd976097e26", - "0x85297c4e8ae8d9b44c3fe51aa926c77d55db766c2a9f91b659040de36e34c9a4fc6f44380f8d61704498f6fd52395a49", - "0x8c61a988b6a00fe5a277450f30bf6daa932e42a2eae844568e3babf8815e09311f3c352dae6eb2d57a98d16b7beb2d22", - "0x990be28aaecd932e7edb2a97b9be2789a3905cb88737b1c79881302585801c69a3dd5fb230808b39db1352fc06e0b4a8", - "0x82fd14bdb335aa46f022dfe0ed4d631911e6b6f5eefb10d11e9e2e02a7df55012ed8162249d10b58eb76ced5a7b06cda", - "0xac39cb058df764e161db9c39b185f09aa210bddbd66f681f1697ddbe6b305735612d5dd321d3ffbb4876771bdb321e2f", - "0x858a3f7e57ccb81387caf8e89f9b6039e9aadeab06886d8688fe6427151a59ab2e77e85ba850c67d099965426c97779a", - "0xb57fb9ea623cec432946819937c6bded0b5d03c8c67b52b44a4b67d34adfb055e6cabca67a48e4d859b4be45162c5083", - "0xb84d2990b563d6d7fe1f4c1894989db25b81745090b94b1fe2ef708ac3b2110ef93d647820b2a51fcf78e3f00fef5412", - "0x817d85b9f5e1521733d2b1fa6d4f4957ac445dc803f97fc495e20b819b14e651332f9e0573d684b854fd47824c53f0e8", - "0xb09e18e97e93a8523101af594422fb71afc5b8826002314269016fcc1b44002d91bcb7c90d923d460f0cc03bddfe9af1", - "0xb867cbede82102de7cf6cd0dae68506869576eaa66c3fc806e73585310602682fc912dc37adf5ff6f0f34a07831735b1", - "0xb1126255798368b692f2796a3470ed16e5ffdee2d8c9e0f7ee3d2e92950c3e6365c32895171c3494aff2a6d6356f7e25", - "0xb05f0a0996dec16335c770a5df3f0b08e20020c838c2caaa1d3a4a2490ede98552f5de349de2ce6e4c4a839731d80919", - "0x98c512bb91c8fa191120ddf5d63c88076581cf41e15eec3c168822f12b3dd0ce4d6df74a7e3093d3e35cad1cb3135421", - "0x84ce38fd97f7f90012c2c1e59a67bf9f465a7ccfb6f308bdd0446cc82b8a26ff7c30e5c7cc375011718cad1b31adaa9f", - "0x93139db52c9fb96dee97a0825f21e34c5d6d36838e1e42f4d12d01eacbe94426c85a811fe16ca78e89e08f1c27383d28", - "0x81454037b1e7a1765f67e4288b8742eebf6d864d9b0f508ab44fa3243168ce0ed30cb5f33dfcdb995cd2c2710ff97a6d", - "0x828deb2a26efb2ff1842f735e2cc27162360f619b6e3e27a85bedf384912d4726bb2759a3016937973092ece1bf90540", - "0x87e5a7d4e7bd301078f625d9a99b99e6e8e1207c9f8a679f8ebbbfb467bfa0b5f7ef4a4d577c7d2670efa88221153012", - "0xb9dc9d0ea48deee201e34379447bec789c8924aecd030eeb93db159af77eff230976ef60ea9f4b4a9e9e95c1f9f4284e", - "0xaa6528268d46bf0627d87d58e243d3ac34b863513c725908a2617e4c6a46ccb1d8c8334bd6dd0eea7ffebec44259dae5", - "0x8d26c9ce07293f6a32a664d31e6df9a7ace47e6c38001635918efd9872aceab62de7757b13b783d422eb67bd28ce7bbb", - "0xb0d3ca88d9829a7459b89b0dcbdb8bbb5180b00d750bd959bd110f53c2dd5d4db554b6005c4765fbe7ec5903669e5ebc", - "0xa94d1c72bf3b2dc6bfebc9dee40f6a89a516b252bd9f4fad96f156e3dbfc151a9b8a02324d764c7656d59230a18eb61f", - "0x88996e79171e30b16505638d8ecb25afd875e5f3cc3e29860937f2b5e751c66e78dc77f744a0cc454a8a655142a93ffb", - "0xaf4d94f342665fe7ecda318de6cf1bc1c40c37dd83d060fedaf827459728152b5f0e280286ff5e6a0012036f6715f53f", - "0x96beaa7a2d565ec14a4e5cb895d33624c69da56b75c8d06ac729cb6d0cb64470ed4f9b0387083cd827b1609c8cabde8c", - "0x96b773fa2fcb7377bf71a7e286f37f1f24ee42cba5b4f33903c4566e5e5bcc501ea360e3c8435749107c3de84e272d8e", - "0xa69ac6218454c3f40ad0beb48821a218fb0a4f33ebade986d2fffd9a3900d8cfa613bc71676c46cfeaa5f644d1f239a9", - "0x857f139c08fcc45370f448ce3e4915bcb30f23daa4134407fc6d78efac7d718b2cd89e9a743eec7bf2cc0eccf55eb907", - "0xadeeba36af137fd3c371a2adbefea614c3ae3a69f8755ce892d0dd7102fb60717f5245d30119c69c582804e7e56f1626", - "0xafa97ca3548b35aeda6bfed7fbb39af907ed82a09348004d5705b4bb000173270ce44eb5d181819088aa5a2f20a547a2", - "0x8423bd2d07073b0e87819b4e81997e4d3188b0a5592621a30981dc0a5a9d0578fde1638a364f015078a001afb00891c2", - "0xb92e9d4ec3966981ee574695d6e4865810b8e75313e48c1e4bc5eebae77eb28740e97ecc3e5c42040f9eb1ee4b13b0ea", - "0xb07b218321d54cecfcd2ed54a5fd588a6be8d7a5b6a66dff7facfe061222c40553e076e57cbdfa0bdb08e0a009c94ba5", - "0xa71e1ae4d6096eac9ea4c21f621c875423de7c620544e520fb6ec3cb41a78554aedd79493cbd2c2ba4f0387f902ddd2a", - "0x807cdac291246a02f60c8937532c8969e689b1cfe811f239bfdee0791e7aa0545e9686cfb9ed0c1df84748e5efa5e3da", - "0xa1faeb4504c057304d27d54fb3ec681462384a354a4f0b6c759d4fa313253a789250c6b0f44f751b0718592637438a19", - "0x996bcd3215182d49f1cd15a05e1e0a4bf57e264400bf14f7253c6611d2571de7130cce81fd28e0411e0a80e9054f4f98", - "0x89d15b38f14bcd46f4b2dcae82b0e7bf9a35e40bf57aa947e9c4a8f87a440b5cea95229708de08ca596762062c34aaa0", - "0x8d8ddcaf79374c750b8b0b3d196acb6bb921e51b4619876a29d09161ba82a42271066187211ef746f9f40a5ca17b75f7", - "0xa3dc7f70f3a6c7edc483e712770abbaa94bfa3174cfee872b2cc011b267e0ef9baa1ab49e4a6c6c30dbba0e0a1237117", - "0xaa9e958bbdcb192b19c43fc6fd34afcd754949fdada98e9f4848e8db0e23acb27d19dd073c951a8819000f2356aa22e1", - "0xa4714e45ec853eadfe5c3bee7f683b81f97857bbd7833192a48936dd1460aee68f700a21658658b74b737c4fecf90c7f", - "0xa1ecab4215c1892e4a8ff3405d710163875e5dfef8a8cb84f5cac4e317d89c7696e3f496ed1747ca6f52b304190f4ba1", - "0xb9b48943eca3686219575026d395b969e6ff8159dc5317005df090e79d26901984e40ae4b1af060ed3ff6f42e0417d76", - "0x9644b9f90a66edb0396abd8c00066886f978ebf56fc22081031fbc9ce371bf9b04aa5a4ef59e59319b3a05bb7fb88b43", - "0xb2bb14f1c055a78596488e4e2d4135a6470c1ee43961952160b8498f674a4d23040606e937c02c1fc23dbd47e9bd4633", - "0x8c61f2fce9a42b94a389c7e52d7d093fc011099d0f4914f6d6f05b631df7b88182826edf9bbb1225971a080ca5c0d15a", - "0xaa6a7b8499cc7d256043eacad18528d38bf3be970bea4c6d4cb886690280bdb373688ceba3e506471e1d9493dc76f3f4", - "0x8127703363b3b35b06762c2353d4de82b7b85bb860db1028d3640f46bdb78f2d104fa77ee3e0d9db83833d2b12a966f8", - "0xb7b01f5909f2c66ae0fab156be5d79954e3a304615e1fe55945049dd4bd95f973bb3821117eb54db7e9ed1ee9a527652", - "0x8be47ba5dfe212420649193490838670c40540e0ea24adbab18c4a66e7ac3dcf94f068dec2533b60e08c1f64e7533e54", - "0x905a6c7e24b86aa54a05c329a6b4616d335bb0b1f1e9987562eee0acf82ad302c7c44981a1dd6b24c6121ca12fb92996", - "0x86969ccfd91deed93b355a2c21319e3bb08cc652b741463bf68c626b7ba2afce3f7cc397f2fb74588c2893477c948ae2", - "0xb5a9d20eb12c331d0d300fd4b85b0ac0bb74573178a5fac8ec9dce5e95acba07fab444260355ece442a846737a2dcd1c", - "0xa13497c11df21b11fc1a63b0ffdcf7f432da4dc2c98f8d07d36da4fa68aceb57af2158088e5b05e334fe0f264aeb7a97", - "0x882e4597cc66498a45e86a2ed9ee24652da4699af00ad35f73b5e74fde6ac3cee70630962d5ddd86162d4aaf11bbc11c", - "0xb748858c2bafa4a14ce44af35195e9c52aa75e109719243bbe278095acbfd6a7ae7e084caf8dae6939039b5a4e8fd675", - "0x83a2e0524507e74f51fe976441108f8226ba1b3a33f4e16ec45c5661ce80cb1840a93d17122cb8ca9e0f80d14f69877d", - "0x846cd2946c93ee5f24243d9ebc69936b3a1a6d59f45fec6c79b1eddf15ce30a8e73ad03cf606ee66baea3d8ff115f70f", - "0x8d98d0a3a94f6efe158f8423c041b546416145c5c2254bfa157efea0d1c99fe58acc7df6424ef29f75960b18d664ea4e", - "0xa39fa47e4b79f54dbf59d0b1726f1e78bc219fcfc56ad238c84b4b610e7892ff1e65d537baf5118a32f5e2eb80d5ee0c", - "0x8c30969a4519131de5e30121c84c04f67b98c8ad109fa4710dd3149cae303d51778add3f258f0482f1c89c169824dffc", - "0xaf7f80d141ceb78b4762015de17fef49d7ff6202d292e9604deb508272ee7569f7fd5be3b2438da1dfecf0c26533ef86", - "0x97cf82f70128251944d79b8845506975405bd720e150d836205b048ff36ba8801eb74cdcc6425f28f6bc0acec0a81463", - "0x8c276c876eb88688957d1868bf3a1462375e608ff72b49870a5dac82cbf6584e00e3f36f236f732348a47502ccf9539d", - "0x964765f1a5c8a41d8025ddf56dc01b78424703d8a64a4e5539e477cb2445cb541c70127c561e717256d13f91a830ba83", - "0xa2aacd9e21b8c8efaf2319611addea1b9f41430aee42e7f2a640cc693aa395287cc8fdc2806b76b577d84fbd05378ead", - "0xab11eabbf5be4345a77323a3b75f9ee93b011fd2a9d0154e88183cafe47f82a7888666af16b40d3cb677c94bcc755ff7", - "0xa0bfe715a7af5a29b1b6148b8cbee585d2b49fa6ce59bcd173ea3bbc60d71a62f9da27ffcbbd5a6da75502112fe44d70", - "0x902e6cc38ee42245103d90b65028a471bc7a48b825599d361aa81d8c56e0fcf9fbe8d4c13802040d2cfb85b7e022eea1", - "0x8832e2b5014fdef4003bdbb87e3298fdbdbbe49673f6b66e2373f1cb2605f9c4af2cdf9bfd45d1993208681d29ee1c9d", - "0xa7d39d3fa1ec1e0c87730fa43d4900e91932d1cafb36c76b2934907becf7d15a1d84d7234591ad4c322b5a24673bba8d", - "0x836ed5f09d99624204aa3aa7ac601980fda223f3b4b96b4a8fb235c574a3545d518787c12f81bd5851987f2860d41886", - "0x94235e94445e6086f6e9331923262070a4c2ed930ec519eabb8a30133bd4fc6debb99185f4b668431fae1b485c5c81b7", - "0x9828ffe20b9405f117dac044159be2d3c6e2b50ecdd1651d6a73f7633e6e2a7ba3d783ae939973604446d3a1ef0fb20f", - "0x92f03dc365dfe9154743ca70e6dd2758f064e3286fc543cf8c50f68effdf7c554bd17b3507c6ff4127046d9bbb5522ef", - "0x91ed07df479d8eb3d31292a0e987672a7f3d45ecafe72935b7abbc3f23493605134ce573f309e226c9efe830b6868220", - "0x93bee582661e6d6cefeff29002afc2f36dd2c13dbf33f0574c35b290ddc426170a5f7f196369ad592efcd72cfb6f8fc0", - "0x89a51467d966f48fed15dea5a12dda54d0015f69e2169b5e34f44c7b5a5d4c282d6f138116a0cd06a8476980e420f8d8", - "0xb8ccebc14b6679ba2399370848864f15f63512fd6139df7359b7b93e82c1007fd85137ecb0597294b46643e1a9e7ab5e", - "0x841fa301567fc57b2cd09508ce75326684e12bfb8add671dc208f579b2500b93d5b641e9f59bba798ed4ed1259757f7d", - "0xb3cb45c15eb00b4ccb7013299f761cb8fefc17adf6db50e9ecb8abe927a3bc7f28e359e64693813e078e1dac800ad55b", - "0x96e55d3b9f445f5679e34fa5425b3e87cb221cfbdd07f8353868c7f7f4ba388ee3841cb9a1d638583bc20d03a9d071f2", - "0xa7dee9377de740270c5b57cf86699004ba8dc2766af56b388b5cb0814ec71bb99ecf43ee3d82a552733854ecc7def0fe", - "0xb129dfff23b3c1c95ddb214c4711961fcb129efe2b6557ec9e116ada909593d0d2eec2c628434493393c58c52aa86847", - "0xaed2670e201cb3e38a8be3c86735a4d76255e1e5a4c67b91df6ed262d09c8d10b0a3891da3e6ab934058cc9a7178931b", - "0xb20b8921ae52e5b3c94fa3a8b46489044174f7b897779e7763d6eb419e808d76705b7e7ba5131576f425aa81b6b0de53", - "0xa7e45bbc3ba1bc36617291ba7663806e247f1b57a89e31520c64a90cbf8d426cac2e2f381338baf78c8f92fdbbcb7026", - "0xa99e651e73a507e9e663e2364fcc193ec77e8afdc08c2bed6ad864e49b537ec31e9114ee72291a7657899f2033a849e2", - "0xaf966033636c2e9e8280d173f556fe07f8b6940bbcf6b2df7e2165c30bea66cced2596f6c17ca7c1aa0e614174953ba9", - "0xb69ca7a79e3d55ef21e0ebdc6f0c4bd17182d30cf6290cccca7d2551c91c12b966020d8e40e4ee4179488c9809c03ae4", - "0xb981cd36244e035fef043f70b1d7188d7cd045b4de0581c459fc5730e10eb7f3d5893b54cc4243849c0855e4e621167a", - "0xb20fea858a36921b35a3051ce787b73f70fdecd3fef283c15a2eb1bffb1dcba5991eee4a047ce4e87802da923fd9457b", - "0xb040e6f2e56dc1860274c263d4045837456f74b354a679f6b5ea70919835ebe5d32bf1f519e218730096c98ff396dc9d", - "0x8d2dd60e702c923a7204b530e7d6c193c6f93ca648c4f7bb38f4edbeb0aaed84184213afafb8db6aeb9197c24364276c", - "0x95dfa7348709e43d71285b28a0bfad3ca805b6ed4ae99753e9f736c79d58a35a3a50b42760ccdd03eda50f6e59494968", - "0xb8585632a13f18c139a411bb2f02df809591834d127cd1ff081e26d0abfe0e3fbb54abea26538b25a0dcb4d7e969590e", - "0xb46ba47858a29c6d523c9982660949567666daf2582b93393a4802a9e077eedbc0d49d454731696bc8e46ca50c7caa40", - "0x84b756b901b98a4404e58d70f39f6ccac877146c866732ae65e7e82727448d1550343bf7cdff1bfd4ee1ed73793db255", - "0x83e5be888eaf877a2c755897410865f64a6d1169a8ccf0336092f3932abab915e542ab75a35ffe016042340d581ee987", - "0x8cb274fc39285aed451a7def72cfbf73168ee10be02affe355a2bf87cf361a81ad284e9334cf00c5bf99a13d9f75e116", - "0x91ff6220924b94ae13f50eeac16a159232e4f16a73fbd5c22c0e185cd1998403904d36bad203baa82b85819ee4a8ac10", - "0x87f46e08e09aea2ab37b55fc300689d9b58ff3e72f1cffe023386035888f714fac4673c7c5193d3f3f3c568c640694f0", - "0x835d7d84ca7641e1b15095830114aa6072fe12260d2202456cafe2308c22651af9ffbcf6b7e56af97167dd0c4e2a4cf2", - "0x91202183f79794f114fd9e3b9bd05553c0e8985919965101a57d97ef666b028863e6cea9735af016dc1864f1542dee51", - "0x81ab2b02a9b0a490a74ae615ddd4fe560734c1bfdde6b8dd13303c1481ba0e8ab14473535a93cfe4e824a0ab29445f8c", - "0x8a32d73f4fc006551d4e2c61eec6130355ec9b8c39a65c24ec1edc00e80155ca83a8ef2455e892521a3d47634d82a987", - "0xaf70d7b8f13bc90193cc1cfb0c400c4224cf10f1887848aa93e6380f7087782fc41a159926ab53c53eb95c2383b1a849", - "0x989bf42f9d357c51774f1c7c0f7c0c46a8cb7398a74497141c32685be098e38b4230ffe833a6d880ec391a35b1a747b6", - "0x94cb6715ee95700020c630b8c19e35f231de970219bd7e6ba7ced01899197da473b6c45cacfab0d652ddaf547b4ea58c", - "0xb12e3331f1f7d7458393a785e22e9a5e1d1daea521b4e78c0ee8ca59b41ade1735a29820e18f6afb2f2c3c56fecc16b6", - "0xad4b7cf654349d136fb41fb0dd65b588199f68b462b05f5c4e5c2b468bfaa6c26329033e3c3f7873dc8ace89cf873ea5", - "0xa3279969e1ab596df0559ffc5ac7a6dc849680354e01c3f4fd34c6413a3f9f046f89c1e1be0b315d8b6dfab3d23d5c14", - "0xac74cc5562836ed89d09a9ae6a3644c936d64bdda9e77659d9982f1be29541b03ef2723236d5465e398373ea19a4ccc6", - "0x98138ebce1af531dd8b631b3e74c84f0c700355a2a9bde31e5e51bb10c8bbd766559c63f6041f4002568803fe08438e0", - "0x9006445da131349fe5714e0777a4f82a82da343612589a0c1596393e8b6894ce1cf42784f95ff67a8384ffe1f1a4ad76", - "0x88502a84a85e4ce54cfed297b5d355867cc770a8ffd0714a6f23b1ab320a9903c6e42809e034bb67dbf94c4fc0d9c790", - "0xaa8b4bf123d1a6ccaa44b86be8f980005f2a0a388a76cb111b0e85cd072ef64167fb0c097c7b23c4bca64c0260f6cce0", - "0xad49eb35dfea9feabb513a78dd1152ad7eba22fbb02a80cefc494a7037699c8df81202dfec12acc1b9e33ad680cb72d2", - "0x8694da730231b29afd5196371ddcb15b4dcc499574bdd063f4864ab80749833ea38ab8b0ca1629a367fe378e87a60a86", - "0x8eca7b488e810c479e7e32e24b8afcd837f7df183fe4f621a0336b53a9ed77603c84bdc365d8be68179a32b71a1deb7e", - "0x8875cd3e23c7e1af55af1b091025a08255743984186770bcd43f30b4a58d175cfdf1984bad97a15e08dac2da27198c3d", - "0xabdafcf58ec72997e494d4714645f40d09dcd0fbd0733e640eca44eeea67c25bb0c270299c459991f2fae59d13b4f4d5", - "0x8f040970141e61489284f3efd907705eae6ec757fe8e1d284eac123d313e9ac1e8dc14ae3f04d281e1effc49d5d2f51d", - "0xa7ff115f0d2dbf66c0e8770b3d05157b37357b9e33e9a447f0f3fa9da69ad04e371fd1e4848cfb9e8d05e3165bd969d8", - "0xa39b1a8c39d317fcc97bf6c396e6ed4a85640aeeadbf45166bd02bc3bdfb6266509159c03afd492e642384c635b824c0", - "0xa2e1b90f3dd2d0038eaa5be52127844ccf35d997143179d95ffd3749c0896398b130094d01eb1bb31ffe80ef34b42b48", - "0xa2bbe31f89b0c3c375ffaf63c8b7831860a921d5e388eb7907dbf61f2601ea40db86bb3952ecaa26a5eca4317a848ff9", - "0x87d885bb0f2ce04b40ce94d2557c15f1698dc652e938f9a2d69a73ccf4899e08eafa1a59a20cae92823795f5b94f04b9", - "0x8f7746370f8a24a2889d351f3e36b8a7d60e75e50e8f5abeea7dafc75441e95915721654e61ceac51bb6f112780d352c", - "0xa7272847526ed3d9e0d0fea1d8685b07b5b908971490bf8a46748c8b1783c629b8644feb5bac772ae615daae383d5e72", - "0x978c9aa2996d8bd6fda7e0393fa8b38747f8f99712427705c00f6e9a12c36f8d8b4cedb03fcb9867155cbddb5200e6e1", - "0xa4dec4a2354b2b32434c5bcdc380bf84580c6f9940f94dc0498a5bfe89c675a0921e66b807a3d859a6059a464cb2a9ac", - "0x99459ddecc7abce437f68722dae556d8ffaf8ed974f459e52e6d4a64f176caa4d42c2f2ec57e8a5b5f2034638e8acb0a", - "0x928c68c0c9213fe6258ab5bb0c693d97203d15da359784de7824dec143212da57d062a1fc70a79172cee31adc7aff382", - "0xaad3f318f1622ea87e12541dfd982d71629b8f1ded4c301f9f6b6af9432716ad057773c33bdaa6f15dc151b0ee4505ea", - "0x8eb8e978f149a983fd6ad01773f9aacf57bd0cc622d8a301e404184b37e610123dd081faeda571a0ab1f149a3960af10", - "0x851e7191d7b94bd422bcece5b92609fc1b1c8556229bc53e32963b2d2fd1cacd8ce5da9040b599eca6e610540f8a7987", - "0x9414157fe9d50e5a0b5a7397417681bcb3a651eec1cab63f2a88d5df68ab1fef6e4c1d7ba657cbaf241a7cb790297633", - "0xb5cb2dafdc5408959780754a58b2da55b2a9136672ebca42f34da4e329ddc89360e7218cde3efdbf784ddb390deacc57", - "0xac6b70f65503a8e94b773fda3e72615745824930114fe72b6d833484285462392617c1b2eea4a250fedbee88f503f3ba", - "0xb0829a5312f9ac6c06fddee2f835a3452fe994f6d42c9edfc390d7d5b3240ca544433b544cbbddd6516b38a6d5d7c21d", - "0x95f8e2c59905957e34d53be3d6fb85732f834e2cb9ab4c333fea2f502452a87ccd035fc9075d7c0bd8530bb0a0c96527", - "0xb93f279b7045f2d97c674495f6e69a3e352f32f43cc60300193b936c2850b2805c15457251f7e3f633f435cb2b60405c", - "0x915abf16cba1a0b655b92a8a70c03e7fb306b86f3bbfb66967ca63e64c003b59c7a5953675efa4fa0bce9bed536b6700", - "0xac2047f50a319d09df1ec44d71afdcec5ac3bd2765dc98aba347734aa780863545df9f6d71214d443e3f37edc0dae45a", - "0xad49c74ddb24c8a26b14ec08bc807313c77c5967fbb36237f55994d7511bbac8d7e7b9b8ec53eb1b3b066989f078dbd9", - "0x961483105f605e959213fe9e8a52b76dac62d7efd2319ec71fc4e92d68fbe44cd2f65d7adefb2eb64d591b91648b8085", - "0xb67fcafc97d8df2b3075bbff7b3d7471dbf1f3048f309e55d5e2c5bcbc7a73aebcb0697859be9f387cbc7ce98041e154", - "0x8da70ac16468cab6066992389cb37c79ff5e0babbe67d76878aef9408b9597a3dc2eb5de87428bc761a0d78957b0eb28", - "0xaec0ce89770d299b631f15ae12f94b1e1014ac57d38fcf037c2c7712d770d074affa06e97c60691bad8733874b6ad2ed", - "0x8b702c85fa4c915a09fc86507f44d7aeda0993b77af87780d70cc98d580c6e996b64b7c16cdb4dd4562cb0f75da36ee7", - "0xaaeb43aa472aac2253e211fd1066c3a5422ea041cef20168702d0618a1a742a44f7fb30a76677640fea1a24e7fae1996", - "0xa8820e92825d6e02b9b4ad5ebc86161d3244cddd3d244333ba1576b6ae10948145b68d9e926bf6b7a2c25dab4cf43f3e", - "0x8ffdae28a1f1d15d7ffa473628a66ee9a739073f59ba781248286b39cb8f7255f66d62337064246713cbb5017e615174", - "0xadfc5dd142b7911326d8424881d5d92006f3b17de4cce91674d6ea37f00fbb266c791ac13f6c7a0f61d04f2a952e6a04", - "0x87f98982444bf661f539bec73a10256f079a4baa88a1cea0351ae3de929e1c500485b2d1b5d933063cd7d9123d5050e4", - "0x8f217ba4dd404c5ee384f0c9a126686db001ff0344c01c82174c5e5ef89d1a241b146008c534b13a0da6c8afe7450fbb", - "0xafc85476dddaf1cbb4ba8b22186789f3818c7964f9f613e55010278800cd95422702248bdf9c73760702ef24854795ec", - "0xa59e0f6ac2ccdfbd01f002008034390c0ea78716f5e0de4e474e3558755705c9c7afb6e3c5c4370e7bbc85958a9c7a63", - "0x97c0695c58d792ec31d9b86d3b2fc1382f0855057b24d5f6a54c41f76f9e2f52882cadc89a8b2f121530e7f1393faa95", - "0x8e49112de0b2649c08a96cf737af68fa8055f1af594846a2d0534c94df6f926f200405edaa6e6ac9db7e380707a2571d", - "0x99a1bd83a7ac5f8d77ddf044c80ebfc5745b998714696d67b94d185c97e9d6db989bacac646d9def463127a8b2febc00", - "0xaba80725f9f9f7abe10760eca73ba427ca8df864a157122eb9af828a05b0199de3add02019a297750bdab5380e505c58", - "0xae18f62573275c1eb268f74c5e54e8958547f9e7d1d36a05b084eb53e5704fafe2200b8aff95cc7e9af5be2391c42b7c", - "0x908b8031d09d22b2aefeaa876a998e0a97c7a1070aad9e9c97836cc5aa6d2d5ef94230e1222074837b5e21b4e6490f01", - "0xb3132282e8b41ca6789ec5c43c1fecf3a65b8eefbc2f3d10f746a843b9ba4ce6db664678e75e424f7b11a00c1440de15", - "0xa1eb49440cc106ebc09cf198c93e8070271eb5a936d31c04858a2b311a037350100c7957d5545c9653f396aa968b91f4", - "0x81df6ad1bdd5eee4cc2f94318467b8602d15cc1be2b48b09ade12cc46ee05cbaaf77a20397e5015030b1f1db5dd9dac0", - "0x87236c68a2a93c8442d15d7f1d1dc01d1fd123439c183e1d843f4ddd2bcf638c128f66f1ef9b710e5d1f64a52726007a", - "0x84f2e7f85563bb2f61b10a712c7605d63f79af5be0dba056814fd3efebc20e9c53227c56577b72c68d185571b775eff6", - "0xa36d4ae06688ece2927aeb2c7f058a3cd2aa1de1601282d4e688e1d76ef20728b892928deda2314eba41675eba3912f1", - "0xb8326dcbcdcfce017b263c456c47692fb476c4225c95981666fff0b7d4522fc23b7f12273f0f47cf0442662124e6648f", - "0x84c66463ab277cda2cc7007d0509269e89cdd41c5e0d3773a92615f0fc5da63811186b05d7a11088048a5d4834a7e0df", - "0xb20d3571d970712ef4699b0e7034fd269c361f53e1572e2ea2676b4245e992d43b8b5931a801439a44d977a988cc360b", - "0x94dba6007e6d4998ca1eb84aa8e2a7e9f5c164b9d80df2825f2208ce5640a05aacac2e4f08918268990f43ae1ccab69a", - "0xa1c25f0b3ef9d1982153207570d9ce8d692e1b6963b509958dc4d9bcd80074bb221c46804a6d9a29e76149cc7787c282", - "0x8857748fcdab1199fc96084323a81d3bd8b5a7f0b1abc5bc3b5252a19268344e2e7d2d086c90fc9b5fa4b92feedb93a4", - "0x8b9c1d841447354b6c086549e4d1d435ab64c13933488c34bc30f0f6eb36c5c5b838b7b6bb018542247edd1ada091045", - "0x8f5b655416da0e719a204fc567e93792c301acb4374cf7bbabc6ce51dbeaaadfd75c2db0e16ce073ab8e91fd3d7ea9d4", - "0x90f2846b19be46a75c5cd0cafefcf9192e6fd80c479e8d6320c4b8d8d7d96703c9e77ff31a67afa9858e6b7bde1f7cce", - "0xa53e383947fd98aa1a55ac956214b46b20a52758461e8ba41341a23a835ebb713038bf048edb1202bbfd0b56a96bf292", - "0x9542d7debbcfb9cda6fa279c699a7b655c03b9a9b456a5d3cfc41a826c94eafa43e01155a29e39ff0bcd965f4c0c512d", - "0xa43792864ec5fc549f7afc02622454afc0e425c310c4039ba615067243ebb26a4c7ebfd19bd4d57ff412a4bb2a7958a0", - "0xb85123950e30c048465bf32365d24a5d4b21fffc6183cdbf71643a07b87463989b72dd9a6a47f134856f704909a6b38f", - "0x944ea689aec1376f855c0bc9c51378ad06ff758a2c075b95a60b535b88b36eca0be11e4edb5152e98cb2137d6e749f27", - "0xa6bef52cda22325e4c62d323e2a0e3fa91c5552fcfce951edfd52ad6f652bfdcc2341f1cd349e6b5d447924dc569bfe2", - "0xb56bff8ffe981bfcb30791836da10b87f2ccbe17ed969e7f7a650af07d27ae0223805b1264d985148208483be50578a6", - "0x8b209cac898dd580c82d854a553e2517497ad1a4cd198e1360b8b50639b380aee70ee4b87625d9b2278228ff644cd25c", - "0x877cce233fec74c7158b3c5bf108365e98238418b8a71f058f1aca44a0fd3a1021e3e9025bd11fe244d9fe0f5034ce7f", - "0xb1b871aeedb03d6f6accc99816b89f5958178738d8d8cd9717527d04363c80fdb5f6848122ae19fdbc450cfa11e753c8", - "0x858aca51b9e5b0a724e88688d5124eb24c9faf01a3d465e74d31de6da315f311143f22f60201ea09f62c92f61f09d889", - "0x8521d409615dfc8c8289e00f6aaa6297c2c4e1439b25952afd76aac641b81c70b9cef07cd58c1c0198382bddd2bd8544", - "0x88647c3e41666b88acca42505f1f5da226937e0522b538fe0cebb724e9a99730ca2522989e94a96cac94109aef675c0f", - "0xb417fdaf719caf38854e89ce52031b30ce61a632e6c3135adec9002280e022d82ab0ea4ac5ebdb21f1f0169e4c37bcda", - "0x9367a6feb5e23ea2eab8ddd5e7bdf32b4d2419fad1c71a1ed327b77362d8942dad971a1c2e6f7073885149cdf0a0c339", - "0xa71c5c08d50c57d094d6a4f02e97d3799bada92f238ffc07bd223bbe8379507b7310d20b28f5bbbf331e5e153515e491", - "0x9630a9a3bcb044b51299c4d3d3388a4ff47308dd27be3229601985478c0f6b55faa7e20815d8694f910611396a9d0d45", - "0xb0bfaf56a5aa59b48960aa7c1617e832e65c823523fb2a5cd44ba606800501cf873e8db1d0dda64065285743dc40786e" - ], - "g1_lagrange": [ - "0xa0413c0dcafec6dbc9f47d66785cf1e8c981044f7d13cfe3e4fcbb71b5408dfde6312493cb3c1d30516cb3ca88c03654", - "0x8b997fb25730d661918371bb41f2a6e899cac23f04fc5365800b75433c0a953250e15e7a98fb5ca5cc56a8cd34c20c57", - "0x83302852db89424d5699f3f157e79e91dc1380f8d5895c5a772bb4ea3a5928e7c26c07db6775203ce33e62a114adaa99", - "0xa759c48b7e4a685e735c01e5aa6ef9c248705001f470f9ad856cd87806983e917a8742a3bd5ee27db8d76080269b7c83", - "0x967f8dc45ebc3be14c8705f43249a30ff48e96205fb02ae28daeab47b72eb3f45df0625928582aa1eb4368381c33e127", - "0xa418eb1e9fb84cb32b370610f56f3cb470706a40ac5a47c411c464299c45c91f25b63ae3fcd623172aa0f273c0526c13", - "0x8f44e3f0387293bc7931e978165abbaed08f53acd72a0a23ac85f6da0091196b886233bcee5b4a194db02f3d5a9b3f78", - "0x97173434b336be73c89412a6d70d416e170ea355bf1956c32d464090b107c090ef2d4e1a467a5632fbc332eeb679bf2d", - "0xa24052ad8d55ad04bc5d951f78e14213435681594110fd18173482609d5019105b8045182d53ffce4fc29fc8810516c1", - "0xb950768136b260277590b5bec3f56bbc2f7a8bc383d44ce8600e85bf8cf19f479898bcc999d96dfbd2001ede01d94949", - "0x92ab8077871037bd3b57b95cbb9fb10eb11efde9191690dcac655356986fd02841d8fdb25396faa0feadfe3f50baf56d", - "0xa79b096dff98038ac30f91112dd14b78f8ad428268af36d20c292e2b3b6d9ed4fb28480bb04e465071cc67d05786b6d1", - "0xb9ff71461328f370ce68bf591aa7fb13027044f42a575517f3319e2be4aa4843fa281e756d0aa5645428d6dfa857cef2", - "0x8d765808c00b3543ff182e2d159c38ae174b12d1314da88ea08e13bd9d1c37184cb515e6bf6420531b5d41767987d7ce", - "0xb8c9a837d20c3b53e6f578e4a257bb7ef8fc43178614ec2a154915b267ad2be135981d01ed2ee1b5fbd9d9bb27f0800a", - "0xa9773d92cf23f65f98ef68f6cf95c72b53d0683af2f9bf886bb9036e4a38184b1131b26fd24397910b494fbef856f3aa", - "0xb41ebe38962d112da4a01bf101cb248d808fbd50aaf749fc7c151cf332032eb3e3bdbd716db899724b734d392f26c412", - "0x90fbb030167fb47dcc13d604a726c0339418567c1d287d1d87423fa0cb92eec3455fbb46bcbe2e697144a2d3972142e4", - "0xb11d298bd167464b35fb923520d14832bd9ed50ed841bf6d7618424fd6f3699190af21759e351b89142d355952149da1", - "0x8bc36066f69dc89f7c4d1e58d67497675050c6aa002244cebd9fc957ec5e364c46bab4735ea3db02b73b3ca43c96e019", - "0xab7ab92c5d4d773068e485aa5831941ebd63db7118674ca38089635f3b4186833af2455a6fb9ed2b745df53b3ce96727", - "0xaf191ca3089892cb943cd97cf11a51f38e38bd9be50844a4e8da99f27e305e876f9ed4ab0628e8ae3939066b7d34a15f", - "0xa3204c1747feabc2c11339a542195e7cb6628fd3964f846e71e2e3f2d6bb379a5e51700682ea1844eba12756adb13216", - "0x903a29883846b7c50c15968b20e30c471aeac07b872c40a4d19eb1a42da18b649d5bbfde4b4cf6225d215a461b0deb6d", - "0x8e6e9c15ffbf1e16e5865a5fef7ed751dc81957a9757b535cb38b649e1098cda25d42381dc4f776778573cdf90c3e6e0", - "0xa8f6dd26100b512a8c96c52e00715c4b2cb9ac457f17aed8ffe1cf1ea524068fe5a1ddf218149845fc1417b789ecfc98", - "0xa5b0ffc819451ea639cfd1c18cbc9365cc79368d3b2e736c0ae54eba2f0801e6eb0ee14a5f373f4a70ca463bdb696c09", - "0x879f91ccd56a1b9736fbfd20d8747354da743fb121f0e308a0d298ff0d9344431890e41da66b5009af3f442c636b4f43", - "0x81bf3a2d9755e206b515a508ac4d1109bf933c282a46a4ae4a1b4cb4a94e1d23642fad6bd452428845afa155742ade7e", - "0x8de778d4742f945df40004964e165592f9c6b1946263adcdd5a88b00244bda46c7bb49098c8eb6b3d97a0dd46148a8ca", - "0xb7a57b21d13121907ee28c5c1f80ee2e3e83a3135a8101e933cf57171209a96173ff5037f5af606e9fd6d066de6ed693", - "0xb0877d1963fd9200414a38753dffd9f23a10eb3198912790d7eddbc9f6b477019d52ddd4ebdcb9f60818db076938a5a9", - "0x88da2d7a6611bc16adc55fc1c377480c828aba4496c645e3efe0e1a67f333c05a0307f7f1d2df8ac013602c655c6e209", - "0x95719eb02e8a9dede1a888c656a778b1c69b7716fbe3d1538fe8afd4a1bc972183c7d32aa7d6073376f7701df80116d8", - "0x8e8a1ca971f2444b35af3376e85dccda3abb8e8e11d095d0a4c37628dfe5d3e043a377c3de68289ef142e4308e9941a0", - "0xb720caaff02f6d798ac84c4f527203e823ff685869e3943c979e388e1c34c3f77f5c242c6daa7e3b30e511aab917b866", - "0x86040d55809afeec10e315d1ad950d269d37cfee8c144cd8dd4126459e3b15a53b3e68df5981df3c2346d23c7b4baaf4", - "0x82d8cabf13ab853db0377504f0aec00dba3a5cd3119787e8ad378ddf2c40b022ecfc67c642b7acc8c1e3dd03ab50993e", - "0xb8d873927936719d2484cd03a6687d65697e17dcf4f0d5aed6f5e4750f52ef2133d4645894e7ebfc4ef6ce6788d404c8", - "0xb1235594dbb15b674a419ff2b2deb644ad2a93791ca05af402823f87114483d6aa1689b7a9bea0f547ad12fe270e4344", - "0xa53fda86571b0651f5affb74312551a082fffc0385cfd24c1d779985b72a5b1cf7c78b42b4f7e51e77055f8e5e915b00", - "0xb579adcfd9c6ef916a5a999e77a0cb21d378c4ea67e13b7c58709d5da23a56c2e54218691fc4ac39a4a3d74f88cc31f7", - "0xab79e584011713e8a2f583e483a91a0c2a40771b77d91475825b5acbea82db4262132901cb3e4a108c46d7c9ee217a4e", - "0xa0fe58ea9eb982d7654c8aaf9366230578fc1362f6faae0594f8b9e659bcb405dff4aac0c7888bbe07f614ecf0d800a6", - "0x867e50e74281f28ecd4925560e2e7a6f8911b135557b688254623acce0dbc41e23ac3e706a184a45d54c586edc416eb0", - "0x89f81b61adda20ea9d0b387a36d0ab073dc7c7cbff518501962038be19867042f11fcc7ff78096e5d3b68c6d8dc04d9b", - "0xa58ee91bb556d43cf01f1398c5811f76dc0f11efdd569eed9ef178b3b0715e122060ec8f945b4dbf6eebfa2b90af6fa6", - "0xac460be540f4c840def2eef19fc754a9af34608d107cbadb53334cf194cc91138d53b9538fcd0ec970b5d4aa455b224a", - "0xb09b91f929de52c09d48ca0893be6eb44e2f5210a6c394689dc1f7729d4be4e11d0474b178e80cea8c2ac0d081f0e811", - "0x8d37a442a76b06a02a4e64c2504aea72c8b9b020ab7bcc94580fe2b9603c7c50d7b1e9d70d2a7daea19c68667e8f8c31", - "0xa9838d4c4e3f3a0075a952cf7dd623307ec633fcc81a7cf9e52e66c31780de33dbb3d74c320dc7f0a4b72f7a49949515", - "0xa44766b6251af458fe4f5f9ed1e02950f35703520b8656f09fc42d9a2d38a700c11a7c8a0436ac2e5e9f053d0bb8ff91", - "0xad78d9481c840f5202546bea0d13c776826feb8b1b7c72e83d99a947622f0bf38a4208551c4c41beb1270d7792075457", - "0xb619ffa8733b470039451e224b777845021e8dc1125f247a4ff2476cc774657d0ff9c5279da841fc1236047de9d81c60", - "0xaf760b0a30a1d6af3bc5cd6686f396bd41779aeeb6e0d70a09349bd5da17ca2e7965afc5c8ec22744198fbe3f02fb331", - "0xa0cc209abdb768b589fcb7b376b6e1cac07743288c95a1cf1a0354b47f0cf91fca78a75c1fcafa6f5926d6c379116608", - "0x864add673c89c41c754eeb3cd8dcff5cdde1d739fce65c30e474a082bb5d813cba6412e61154ce88fdb6c12c5d9be35b", - "0xb091443b0ce279327dc37cb484e9a5b69b257a714ce21895d67539172f95ffa326903747b64a3649e99aea7bb10d03f7", - "0xa8c452b8c4ca8e0a61942a8e08e28f17fb0ef4c5b018b4e6d1a64038280afa2bf1169202f05f14af24a06ca72f448ccd", - "0xa23c24721d18bc48d5dcf70effcbef89a7ae24e67158d70ae1d8169ee75d9a051d34b14e9cf06488bac324fe58549f26", - "0x92a730e30eb5f3231feb85f6720489dbb1afd42c43f05a1610c6b3c67bb949ec8fde507e924498f4ffc646f7b07d9123", - "0x8dbe5abf4031ec9ba6bb06d1a47dd1121fb9e03b652804069250967fd5e9577d0039e233441b7f837a7c9d67ba18c28e", - "0xaa456bcfef6a21bb88181482b279df260297b3778e84594ebddbdf337e85d9e3d46ca1d0b516622fb0b103df8ec519b7", - "0xa3b31ae621bd210a2b767e0e6f22eb28fe3c4943498a7e91753225426168b9a26da0e02f1dc5264da53a5ad240d9f51b", - "0xaa8d66857127e6e71874ce2202923385a7d2818b84cb73a6c42d71afe70972a70c6bdd2aad1a6e8c5e4ca728382a8ea8", - "0xac7e8e7a82f439127a5e40558d90d17990f8229852d21c13d753c2e97facf077cf59582b603984c3dd3faebd80aff4f5", - "0x93a8bcf4159f455d1baa73d2ef2450dcd4100420de84169bbe28b8b7a5d1746273f870091a87a057e834f754f34204b1", - "0x89d0ebb287c3613cdcae7f5acc43f17f09c0213fc40c074660120b755d664109ffb9902ed981ede79e018ddb0c845698", - "0xa87ccbfad431406aadbee878d9cf7d91b13649d5f7e19938b7dfd32645a43b114eef64ff3a13201398bd9b0337832e5a", - "0x833c51d0d0048f70c3eefb4e70e4ff66d0809c41838e8d2c21c288dd3ae9d9dfaf26d1742bf4976dab83a2b381677011", - "0x8bcd6b1c3b02fffead432e8b1680bad0a1ac5a712d4225e220690ee18df3e7406e2769e1f309e2e803b850bc96f0e768", - "0xb61e3dbd88aaf4ff1401521781e2eea9ef8b66d1fac5387c83b1da9e65c2aa2a56c262dea9eceeb4ad86c90211672db0", - "0x866d3090db944ecf190dd0651abf67659caafd31ae861bab9992c1e3915cb0952da7c561cc7e203560a610f48fae633b", - "0xa5e8971543c14274a8dc892b0be188c1b4fbc75c692ed29f166e0ea80874bc5520c2791342b7c1d2fb5dd454b03b8a5b", - "0x8f2f9fc50471bae9ea87487ebd1bc8576ef844cc42d606af5c4c0969670fdf2189afd643e4de3145864e7773d215f37f", - "0xb1bb0f2527db6d51f42b9224383c0f96048bbc03d469bf01fe1383173ef8b1cc9455d9dd8ba04d46057f46949bfc92b5", - "0xaa7c99d906b4d7922296cfe2520473fc50137c03d68b7865c5bfb8adbc316b1034310ec4b5670c47295f4a80fb8d61e9", - "0xa5d1da4d6aba555919df44cbaa8ff79378a1c9e2cfdfbf9d39c63a4a00f284c5a5724e28ecbc2d9dba27fe4ee5018bd5", - "0xa8db53224f70af4d991b9aae4ffe92d2aa5b618ad9137784b55843e9f16cefbfd25ada355d308e9bbf55f6d2f7976fb3", - "0xb6536c4232bb20e22af1a8bb12de76d5fec2ad9a3b48af1f38fa67e0f8504ef60f305a73d19385095bb6a9603fe29889", - "0x87f7e371a1817a63d6838a8cf4ab3a8473d19ce0d4f40fd013c03d5ddd5f4985df2956531cc9f187928ef54c68f4f9a9", - "0xae13530b1dbc5e4dced9d909ea61286ec09e25c12f37a1ed2f309b0eb99863d236c3b25ed3484acc8c076ad2fa8cd430", - "0x98928d850247c6f7606190e687d5c94a627550198dbdbea0161ef9515eacdb1a0f195cae3bb293112179082daccf8b35", - "0x918528bb8e6a055ad4db6230d3a405e9e55866da15c4721f5ddd1f1f37962d4904aad7a419218fe6d906fe191a991806", - "0xb71e31a06afe065773dd3f4a6e9ef81c3292e27a3b7fdfdd452d03e05af3b6dd654c355f7516b2a93553360c6681a73a", - "0x8870b83ab78a98820866f91ac643af9f3ff792a2b7fda34185a9456a63abdce42bfe8ad4dc67f08a6392f250d4062df4", - "0x91eea1b668e52f7a7a5087fabf1cab803b0316f78d9fff469fbfde2162f660c250e4336a9eea4cb0450bd30ac067bc8b", - "0x8b74990946de7b72a92147ceac1bd9d55999a8b576e8df68639e40ed5dc2062cfcd727903133de482b6dca19d0aaed82", - "0x8ebad537fece090ebbab662bdf2618e21ca30cf6329c50935e8346d1217dcbe3c1fe1ea28efca369c6003ce0a94703c1", - "0xa8640479556fb59ebd1c40c5f368fbd960932fdbb782665e4a0e24e2bdb598fc0164ce8c0726d7759cfc59e60a62e182", - "0xa9a52a6bf98ee4d749f6d38be2c60a6d54b64d5cbe4e67266633dc096cf28c97fe998596707d31968cbe2064b72256bf", - "0x847953c48a4ce6032780e9b39d0ed4384e0be202c2bbe2dfda3910f5d87aa5cd3c2ffbfcfae4dddce16d6ab657599b95", - "0xb6f6e1485d3ec2a06abaecd23028b200b2e4a0096c16144d07403e1720ff8f9ba9d919016b5eb8dc5103880a7a77a1d3", - "0x98dfc2065b1622f596dbe27131ea60bef7a193b12922cecb27f8c571404f483014f8014572e86ae2e341ab738e4887ef", - "0xacb0d205566bacc87bbe2e25d10793f63f7a1f27fd9e58f4f653ceae3ffeba511eaf658e068fad289eeb28f9edbeb35b", - "0xae4411ed5b263673cee894c11fe4abc72a4bf642d94022a5c0f3369380fcdfc1c21e277f2902972252503f91ada3029a", - "0xac4a7a27ba390a75d0a247d93d4a8ef1f0485f8d373a4af4e1139369ec274b91b3464d9738eeaceb19cd6f509e2f8262", - "0x87379c3bf231fdafcf6472a79e9e55a938d851d4dd662ab6e0d95fd47a478ed99e2ad1e6e39be3c0fc4f6d996a7dd833", - "0x81316904b035a8bcc2041199a789a2e6879486ba9fddcba0a82c745cc8dd8374a39e523b91792170cd30be7aa3005b85", - "0xb8206809c6cd027ed019f472581b45f7e12288f89047928ba32b4856b6560ad30395830d71e5e30c556f6f182b1fe690", - "0x88d76c028f534a62e019b4a52967bb8642ede6becfa3807be68fdd36d366fc84a4ac8dc176e80a68bc59eb62caf5dff9", - "0x8c3b8be685b0f8aad131ee7544d0e12f223f08a6f8edaf464b385ac644e0ddc9eff7cc7cb5c1b50ab5d71ea0f41d2213", - "0x8d91410e004f76c50fdc05784157b4d839cb5090022c629c7c97a5e0c3536eeafee17a527b54b1165c3cd81774bb54ce", - "0xb25c2863bc28ec5281ce800ddf91a7e1a53f4c6d5da1e6c86ef4616e93bcf55ed49e297216d01379f5c6e7b3c1e46728", - "0x865f7b09ac3ca03f20be90c48f6975dd2588838c2536c7a3532a6aa5187ed0b709cd03d91ff4048061c10d0aa72b69ce", - "0xb3f7477c90c11596eb4f8bbf34adbcb832638c4ff3cdd090d4d477ee50472ac9ddaf5be9ad7eca3f148960d362bbd098", - "0x8db35fd53fca04faecd1c76a8227160b3ab46ac1af070f2492445a19d8ff7c25bbaef6c9fa0c8c088444561e9f7e4eb2", - "0xa478b6e9d058a2e01d2fc053b739092e113c23a6a2770a16afbef044a3709a9e32f425ace9ba7981325f02667c3f9609", - "0x98caa6bd38916c08cf221722a675a4f7577f33452623de801d2b3429595f988090907a7e99960fff7c076d6d8e877b31", - "0xb79aaaacefc49c3038a14d2ac468cfec8c2161e88bdae91798d63552cdbe39e0e02f9225717436b9b8a40a022c633c6e", - "0x845a31006c680ee6a0cc41d3dc6c0c95d833fcf426f2e7c573fa15b2c4c641fbd6fe5ebb0e23720cc3467d6ee1d80dc4", - "0xa1bc287e272cf8b74dbf6405b3a5190883195806aa351f1dc8e525aa342283f0a35ff687e3b434324dedee74946dd185", - "0xa4fd2dc8db75d3783a020856e2b3aa266dc6926e84f5c491ef739a3bddd46dc8e9e0fc1177937839ef1b18d062ffbb9e", - "0xacbf0d3c697f57c202bb8c5dc4f3fc341b8fc509a455d44bd86acc67cad2a04495d5537bcd3e98680185e8aa286f2587", - "0xa5caf423a917352e1b8e844f5968a6da4fdeae467d10c6f4bbd82b5eea46a660b82d2f5440d3641c717b2c3c9ed0be52", - "0x8a39d763c08b926599ab1233219c49c825368fad14d9afc7c0c039224d37c00d8743293fd21645bf0b91eaf579a99867", - "0xb2b53a496def0ba06e80b28f36530fbe0fb5d70a601a2f10722e59abee529369c1ae8fd0f2db9184dd4a2519bb832d94", - "0xa73980fcef053f1b60ebbb5d78ba6332a475e0b96a0c724741a3abf3b59dd344772527f07203cf4c9cb5155ebed81fa0", - "0xa070d20acce42518ece322c9db096f16aed620303a39d8d5735a0df6e70fbeceb940e8d9f5cc38f3314b2240394ec47b", - "0xa50cf591f522f19ca337b73089557f75929d9f645f3e57d4f241e14cdd1ea3fb48d84bcf05e4f0377afbb789fbdb5d20", - "0x82a5ffce451096aca8eeb0cd2ae9d83db3ed76da3f531a80d9a70a346359bf05d74863ce6a7c848522b526156a5e20cd", - "0x88e0e84d358cbb93755a906f329db1537c3894845f32b9b0b691c29cbb455373d9452fadd1e77e20a623f6eaf624de6f", - "0xaa07ac7b84a6d6838826e0b9e350d8ec75e398a52e9824e6b0da6ae4010e5943fec4f00239e96433f291fef9d1d1e609", - "0xac8887bf39366034bc63f6cc5db0c26fd27307cbc3d6cce47894a8a019c22dd51322fb5096edc018227edfafc053a8f6", - "0xb7d26c26c5b33f77422191dca94977588ab1d4b9ce7d0e19c4a3b4cd1c25211b78c328dbf81e755e78cd7d1d622ad23e", - "0x99a676d5af49f0ba44047009298d8474cabf2d5bca1a76ba21eff7ee3c4691a102fdefea27bc948ccad8894a658abd02", - "0xb0d09a91909ab3620c183bdf1d53d43d39eb750dc7a722c661c3de3a1a5d383ad221f71bae374f8a71867505958a3f76", - "0x84681a883de8e4b93d68ac10e91899c2bbb815ce2de74bb48a11a6113b2a3f4df8aceabda1f5f67bc5aacac8c9da7221", - "0x9470259957780fa9b43521fab3644f555f5343281c72582b56d2efd11991d897b3b481cafa48681c5aeb80c9663b68f7", - "0xab1b29f7ece686e6fa968a4815da1d64f3579fed3bc92e1f3e51cd13a3c076b6cf695ed269d373300a62463dc98a4234", - "0x8ab415bfcd5f1061f7687597024c96dd9c7cb4942b5989379a7a3b5742f7d394337886317659cbeacaf030234a24f972", - "0xb9b524aad924f9acc63d002d617488f31b0016e0f0548f050cada285ce7491b74a125621638f19e9c96eabb091d945be", - "0x8c4c373e79415061837dd0def4f28a2d5d74d21cb13a76c9049ad678ca40228405ab0c3941df49249847ecdefc1a5b78", - "0xa8edf4710b5ab2929d3db6c1c0e3e242261bbaa8bcec56908ddadd7d2dad2dca9d6eb9de630b960b122ebeea41040421", - "0x8d66bb3b50b9df8f373163629f9221b3d4b6980a05ea81dc3741bfe9519cf3ebba7ab98e98390bae475e8ede5821bd5c", - "0x8d3c21bae7f0cfb97c56952bb22084b58e7bb718890935b73103f33adf5e4d99cd262f929c6eeab96209814f0dbae50a", - "0xa5c66cfab3d9ebf733c4af24bebc97070e7989fe3c73e79ac85fb0e4d40ae44fb571e0fad4ad72560e13ed453900d14f", - "0x9362e6b50b43dbefbc3254471372297b5dcce809cd3b60bf74a1268ab68bdb50e46e462cbd78f0d6c056330e982846af", - "0x854630d08e3f0243d570cc2e856234cb4c1a158d9c1883bf028a76525aaa34be897fe918d5f6da9764a3735fa9ebd24a", - "0x8c7d246985469ff252c3f4df6c7c9196fc79f05c1c66a609d84725c78001d0837c7a7049394ba5cf7e863e2d58af8417", - "0xae050271e01b528925302e71903f785b782f7bf4e4e7a7f537140219bc352dc7540c657ed03d3a297ad36798ecdb98cd", - "0x8d2ae9179fcf2b0c69850554580b52c1f4a5bd865af5f3028f222f4acad9c1ad69a8ef6c7dc7b03715ee5c506b74325e", - "0xb8ef8de6ce6369a8851cd36db0ccf00a85077e816c14c4e601f533330af9e3acf0743a95d28962ed8bfcfc2520ef3cfe", - "0xa6ecad6fdfb851b40356a8b1060f38235407a0f2706e7b8bb4a13465ca3f81d4f5b99466ac2565c60af15f022d26732e", - "0x819ff14cdea3ab89d98e133cd2d0379361e2e2c67ad94eeddcdb9232efd509f51d12f4f03ebd4dd953bd262a886281f7", - "0x8561cd0f7a6dbcddd83fcd7f472d7dbcba95b2d4fb98276f48fccf69f76d284e626d7e41314b633352df8e6333fd52a1", - "0xb42557ccce32d9a894d538c48712cb3e212d06ac05cd5e0527ccd2db1078ee6ae399bf6a601ffdab1f5913d35fc0b20c", - "0x89b4008d767aad3c6f93c349d3b956e28307311a5b1cec237e8d74bb0dee7e972c24f347fd56afd915a2342bd7bc32f0", - "0x877487384b207e53f5492f4e36c832c2227f92d1bb60542cfeb35e025a4a7afc2b885fae2528b33b40ab09510398f83e", - "0x8c411050b63c9053dd0cd81dacb48753c3d7f162028098e024d17cd6348482703a69df31ad6256e3d25a8bbf7783de39", - "0xa8506b54a88d17ac10fb1b0d1fe4aa40eae7553a064863d7f6b52ccc4236dd4b82d01dca6ba87da9a239e3069ba879fb", - "0xb1a24caef9df64750c1350789bb8d8a0db0f39474a1c74ea9ba064b1516db6923f00af8d57c632d58844fb8786c3d47a", - "0x959d6e255f212b0708c58a2f75cb1fe932248c9d93424612c1b8d1e640149656059737e4db2139afd5556bcdacf3eda2", - "0x84525af21a8d78748680b6535bbc9dc2f0cf9a1d1740d12f382f6ecb2e73811d6c1da2ad9956070b1a617c61fcff9fe5", - "0xb74417d84597a485d0a8e1be07bf78f17ebb2e7b3521b748f73935b9afbbd82f34b710fb7749e7d4ab55b0c7f9de127d", - "0xa4a9aecb19a6bab167af96d8b9d9aa5308eab19e6bfb78f5a580f9bf89bdf250a7b52a09b75f715d651cb73febd08e84", - "0x9777b30be2c5ffe7d29cc2803a562a32fb43b59d8c3f05a707ab60ec05b28293716230a7d264d7cd9dd358fc031cc13e", - "0x95dce7a3d4f23ac0050c510999f5fbf8042f771e8f8f94192e17bcbfa213470802ebdbe33a876cb621cf42e275cbfc8b", - "0xb0b963ebcbbee847ab8ae740478544350b3ac7e86887e4dfb2299ee5096247cd2b03c1de74c774d9bde94ae2ee2dcd59", - "0xa4ab20bafa316030264e13f7ef5891a2c3b29ab62e1668fcb5881f50a9acac6adbe3d706c07e62f2539715db768f6c43", - "0x901478a297669d608e406fe4989be75264b6c8be12169aa9e0ad5234f459ca377f78484ffd2099a2fe2db5e457826427", - "0x88c76e5c250810c057004a03408b85cd918e0c8903dc55a0dd8bb9b4fc2b25c87f9b8cf5943eb19fbbe99d36490050c5", - "0x91607322bbad4a4f03fc0012d0821eff5f8c516fda45d1ec1133bface6f858bf04b25547be24159cab931a7aa08344d4", - "0x843203e07fce3c6c81f84bc6dc5fb5e9d1c50c8811ace522dc66e8658433a0ef9784c947e6a62c11bf705307ef05212e", - "0x91dd8813a5d6dddcda7b0f87f672b83198cd0959d8311b2b26fb1fae745185c01f796fbd03aad9db9b58482483fdadd8", - "0x8d15911aacf76c8bcd7136e958febd6963104addcd751ce5c06b6c37213f9c4fb0ffd4e0d12c8e40c36d658999724bfd", - "0x8a36c5732d3f1b497ebe9250610605ee62a78eaa9e1a45f329d09aaa1061131cf1d9df00f3a7d0fe8ad614a1ff9caaae", - "0xa407d06affae03660881ce20dab5e2d2d6cddc23cd09b95502a9181c465e57597841144cb34d22889902aff23a76d049", - "0xb5fd856d0578620a7e25674d9503be7d97a2222900e1b4738c1d81ff6483b144e19e46802e91161e246271f90270e6cf", - "0x91b7708869cdb5a7317f88c0312d103f8ce90be14fb4f219c2e074045a2a83636fdc3e69e862049fc7c1ef000e832541", - "0xb64719cc5480709d1dae958f1d3082b32a43376da446c8f9f64cb02a301effc9c34d9102051733315a8179aed94d53cc", - "0x94347a9542ff9d18f7d9eaa2f4d9b832d0e535fe49d52aa2de08aa8192400eddabdb6444a2a78883e27c779eed7fdf5a", - "0x840ef44a733ff1376466698cd26f82cf56bb44811e196340467f932efa3ae1ef9958a0701b3b032f50fd9c1d2aed9ab5", - "0x90ab3f6f67688888a31ffc2a882bb37adab32d1a4b278951a21646f90d03385fc976715fc639a785d015751171016f10", - "0xb56f35d164c24b557dbcbc8a4bfa681ec916f8741ffcb27fb389c164f4e3ed2be325210ef5bdaeae7a172ca9599ab442", - "0xa7921a5a80d7cf6ae81ba9ee05e0579b18c20cd2852762c89d6496aa4c8ca9d1ca2434a67b2c16d333ea8e382cdab1e3", - "0xa506bcfbd7e7e5a92f68a1bd87d07ad5fe3b97aeee40af2bf2cae4efcd77fff03f872732c5b7883aa6584bee65d6f8cb", - "0xa8c46cff58931a1ce9cbe1501e1da90b174cddd6d50f3dfdfb759d1d4ad4673c0a8feed6c1f24c7af32865a7d6c984e5", - "0xb45686265a83bff69e312c5149db7bb70ac3ec790dc92e392b54d9c85a656e2bf58596ce269f014a906eafc97461aa5f", - "0x8d4009a75ccb2f29f54a5f16684b93202c570d7a56ec1a8b20173269c5f7115894f210c26b41e8d54d4072de2d1c75d0", - "0xaef8810af4fc676bf84a0d57b189760ddc3375c64e982539107422e3de2580b89bd27aa6da44e827b56db1b5555e4ee8", - "0x888f0e1e4a34f48eb9a18ef4de334c27564d72f2cf8073e3d46d881853ac1424d79e88d8ddb251914890588937c8f711", - "0xb64b0aa7b3a8f6e0d4b3499fe54e751b8c3e946377c0d5a6dbb677be23736b86a7e8a6be022411601dd75012012c3555", - "0x8d57776f519f0dd912ea14f79fbab53a30624e102f9575c0bad08d2dc754e6be54f39b11278c290977d9b9c7c0e1e0ad", - "0xa018fc00d532ceb2e4de908a15606db9b6e0665dd77190e2338da7c87a1713e6b9b61554e7c1462f0f6d4934b960b15c", - "0x8c932be83ace46f65c78e145b384f58e41546dc0395270c1397874d88626fdeda395c8a289d602b4c312fe98c1311856", - "0x89174838e21639d6bdd91a0621f04dc056907b88e305dd66e46a08f6d65f731dea72ae87ca5e3042d609e8de8de9aa26", - "0xb7b7f508bb74f7a827ac8189daa855598ff1d96fa3a02394891fd105d8f0816224cd50ac4bf2ed1cf469ace516c48184", - "0xb31877ad682583283baadd68dc1bebd83f5748b165aadd7fe9ef61a343773b88bcd3a022f36d6c92f339b7bfd72820a9", - "0xb79d77260b25daf9126dab7a193df2d7d30542786fa1733ffaf6261734770275d3ca8bae1d9915d1181a78510b3439db", - "0x91894fb94cd4c1dd2ceaf9c53a7020c5799ba1217cf2d251ea5bc91ed26e1159dd758e98282ebe35a0395ef9f1ed15a0", - "0xab59895cdafd33934ceedfc3f0d5d89880482cba6c99a6db93245f9e41987efd76e0640e80aef31782c9a8c7a83fccec", - "0xaa22ea63654315e033e09d4d4432331904a6fc5fb1732557987846e3c564668ca67c60a324b4af01663a23af11a9ce4b", - "0xb53ba3ef342601467e1f71aa280e100fbabbd38518fa0193e0099505036ee517c1ac78e96e9baeb549bb6879bb698fb0", - "0x943fd69fd656f37487cca3605dc7e5a215fddd811caf228595ec428751fc1de484a0cb84c667fe4d7c35599bfa0e5e34", - "0x9353128b5ebe0dddc555093cf3e5942754f938173541033e8788d7331fafc56f68d9f97b4131e37963ab7f1c8946f5f1", - "0xa76cd3c566691f65cfb86453b5b31dbaf3cab8f84fe1f795dd1e570784b9b01bdd5f0b3c1e233942b1b5838290e00598", - "0x983d84b2e53ffa4ae7f3ba29ef2345247ea2377686b74a10479a0ef105ecf90427bf53b74c96dfa346d0f842b6ffb25b", - "0x92e0fe9063306894a2c6970c001781cff416c87e87cb5fbac927a3192655c3da4063e6fa93539f6ff58efac6adcc5514", - "0xb00a81f03c2b8703acd4e2e4c21e06973aba696415d0ea1a648ace2b0ea19b242fede10e4f9d7dcd61c546ab878bc8f9", - "0xb0d08d880f3b456a10bf65cff983f754f545c840c413aea90ce7101a66eb0a0b9b1549d6c4d57725315828607963f15a", - "0x90cb64d03534f913b411375cce88a9e8b1329ce67a9f89ca5df8a22b8c1c97707fec727dbcbb9737f20c4cf751359277", - "0x8327c2d42590dfcdb78477fc18dcf71608686ad66c49bce64d7ee874668be7e1c17cc1042a754bbc77c9daf50b2dae07", - "0x8532171ea13aa7e37178e51a6c775da469d2e26ec854eb16e60f3307db4acec110d2155832c202e9ba525fc99174e3b0", - "0x83ca44b15393d021de2a511fa5511c5bd4e0ac7d67259dce5a5328f38a3cce9c3a269405959a2486016bc27bb140f9ff", - "0xb1d36e8ca812be545505c8214943b36cabee48112cf0de369957afa796d37f86bf7249d9f36e8e990f26f1076f292b13", - "0x9803abf45be5271e2f3164c328d449efc4b8fc92dfc1225d38e09630909fe92e90a5c77618daa5f592d23fc3ad667094", - "0xb268ad68c7bf432a01039cd889afae815c3e120f57930d463aece10af4fd330b5bd7d8869ef1bcf6b2e78e4229922edc", - "0xa4c91a0d6f16b1553264592b4cbbbf3ca5da32ab053ffbdd3dbb1aed1afb650fb6e0dc5274f71a51d7160856477228db", - "0xad89d043c2f0f17806277ffdf3ecf007448e93968663f8a0b674254f36170447b7527d5906035e5e56f4146b89b5af56", - "0x8b6964f757a72a22a642e4d69102951897e20c21449184e44717bd0681d75f7c5bfa5ee5397f6e53febf85a1810d6ed1", - "0xb08f5cdaabec910856920cd6e836c830b863eb578423edf0b32529488f71fe8257d90aed4a127448204df498b6815d79", - "0xaf26bb3358be9d280d39b21d831bb53145c4527a642446073fee5a86215c4c89ff49a3877a7a549486262f6f57a0f476", - "0xb4010b37ec4d7c2af20800e272539200a6b623ae4636ecbd0e619484f4ab9240d02bc5541ace3a3fb955dc0a3d774212", - "0x82752ab52bdcc3cc2fc405cb05a2e694d3df4a3a68f2179ec0652536d067b43660b96f85f573f26fbd664a9ef899f650", - "0x96d392dde067473a81faf2d1fea55b6429126b88b160e39b4210d31d0a82833ffd3a80e07d24d495aea2d96be7251547", - "0xa76d8236d6671204d440c33ac5b8deb71fa389f6563d80e73be8b043ec77d4c9b06f9a586117c7f957f4af0331cbc871", - "0xb6c90961f68b5e385d85c9830ec765d22a425f506904c4d506b87d8944c2b2c09615e740ed351df0f9321a7b93979cae", - "0xa6ec5ea80c7558403485b3b1869cdc63bde239bafdf936d9b62a37031628402a36a2cfa5cfbb8e26ac922cb0a209b3ba", - "0x8c3195bbdbf9bc0fc95fa7e3d7f739353c947f7767d1e3cb24d8c8602d8ea0a1790ac30b815be2a2ba26caa5227891e2", - "0xa7f8a63d809f1155722c57f375ea00412b00147776ae4444f342550279ef4415450d6f400000a326bf11fea6c77bf941", - "0x97fa404df48433a00c85793440e89bb1af44c7267588ae937a1f5d53e01e1c4d4fc8e4a6d517f3978bfdd6c2dfde012f", - "0xa984a0a3836de3d8d909c4629a2636aacb85393f6f214a2ef68860081e9db05ad608024762db0dc35e895dc00e2d4cdd", - "0x9526cf088ab90335add1db4d3a4ac631b58cbfbe88fa0845a877d33247d1cfeb85994522e1eb8f8874651bfb1df03e2a", - "0xac83443fd0afe99ad49de9bf8230158c118e2814c9c89db5ac951c240d6c2ce45e7677221279d9e97848ec466b99aafe", - "0xaeeefdbaba612e971697798ceaf63b247949dc823a0ad771ae5b988a5e882b338a98d3d0796230f49d533ec5ba411b39", - "0xae3f248b5a7b0f92b7820a6c5ae21e5bd8f4265d4f6e21a22512079b8ee9be06393fd3133ce8ebac0faf23f4f8517e36", - "0xa64a831b908eee784b8388b45447d2885ec0551b26b0c2b15e5f417d0a12c79e867fb7bd3d008d0af98b44336f8ec1ad", - "0xb242238cd8362b6e440ba21806905714dd55172db25ec7195f3fc4937b2aba146d5cbf3cf691a1384b4752dc3b54d627", - "0x819f97f337eea1ffb2a678cc25f556f1aab751c6b048993a1d430fe1a3ddd8bb411c152e12ca60ec6e057c190cd1db9a", - "0xb9d7d187407380df54ee9fef224c54eec1bfabf17dc8abf60765b7951f538f59aa26fffd5846cfe05546c35f59b573f4", - "0xaa6e3c14efa6a5962812e3f94f8ce673a433f4a82d07a67577285ea0eaa07f8be7115853122d12d6d4e1fdf64c504be1", - "0x82268bee9c1662d3ddb5fb785abfae6fb8b774190f30267f1d47091d2cd4b3874db4372625aa36c32f27b0eee986269b", - "0xb236459565b7b966166c4a35b2fa71030b40321821b8e96879d95f0e83a0baf33fa25721f30af4a631df209e25b96061", - "0x8708d752632d2435d2d5b1db4ad1fa2558d776a013655f88e9a3556d86b71976e7dfe5b8834fdec97682cd94560d0d0d", - "0xae1424a68ae2dbfb0f01211f11773732a50510b5585c1fb005cb892b2c6a58f4a55490b5c5b4483c6fce40e9d3236a52", - "0xb3f5f722af9dddb07293c871ce97abbccba0093ca98c8d74b1318fa21396fc1b45b69c15084f63d728f9908442024506", - "0x9606f3ce5e63886853ca476dc0949e7f1051889d529365c0cb0296fdc02abd088f0f0318ecd2cf36740a3634132d36f6", - "0xb11a833a49fa138db46b25ff8cdda665295226595bc212c0931b4931d0a55c99da972c12b4ef753f7e37c6332356e350", - "0xafede34e7dab0a9e074bc19a7daddb27df65735581ca24ad70c891c98b1349fcebbcf3ba6b32c2617fe06a5818dabc2d", - "0x97993d456e459e66322d01f8eb13918979761c3e8590910453944bdff90b24091bb018ac6499792515c9923be289f99f", - "0x977e3e967eff19290a192cd11df3667d511b398fb3ac9a5114a0f3707e25a0edcb56105648b1b85a8b7519fc529fc6f6", - "0xb873a7c88bf58731fe1bf61ff6828bf114cf5228f254083304a4570e854e83748fc98683ddba62d978fff7909f2c5c47", - "0xad4b2691f6f19da1d123aaa23cca3e876247ed9a4ab23c599afdbc0d3aa49776442a7ceaa996ac550d0313d9b9a36cee", - "0xb9210713c78e19685608c6475bfa974b57ac276808a443f8b280945c5d5f9c39da43effa294bfb1a6c6f7b6b9f85bf6c", - "0xa65152f376113e61a0e468759de38d742caa260291b4753391ee408dea55927af08a4d4a9918600a3bdf1df462dffe76", - "0x8bf8c27ad5140dde7f3d2280fd4cc6b29ab76537e8d7aa7011a9d2796ee3e56e9a60c27b5c2da6c5e14fc866301dc195", - "0x92fde8effc9f61393a2771155812b863cff2a0c5423d7d40aa04d621d396b44af94ddd376c28e7d2f53c930aea947484", - "0x97a01d1dd9ee30553ce676011aea97fa93d55038ada95f0057d2362ae9437f3ed13de8290e2ff21e3167dd7ba10b9c3f", - "0x89affffaa63cb2df3490f76f0d1e1d6ca35c221dd34057176ba739fa18d492355e6d2a5a5ad93a136d3b1fed0bb8aa19", - "0x928b8e255a77e1f0495c86d3c63b83677b4561a5fcbbe5d3210f1e0fc947496e426d6bf3b49394a5df796c9f25673fc4", - "0x842a0af91799c9b533e79ee081efe2a634cac6c584c2f054fb7d1db67dde90ae36de36cbf712ec9cd1a0c7ee79e151ea", - "0xa65b946cf637e090baf2107c9a42f354b390e7316beb8913638130dbc67c918926eb87bec3b1fe92ef72bc77a170fa3b", - "0xaafc0f19bfd71ab5ae4a8510c7861458b70ad062a44107b1b1dbacbfa44ba3217028c2824bd7058e2fa32455f624040b", - "0x95269dc787653814e0be899c95dba8cfa384f575a25e671c0806fd80816ad6797dc819d30ae06e1d0ed9cb01c3950d47", - "0xa1e760f7fa5775a1b2964b719ff961a92083c5c617f637fc46e0c9c20ab233f8686f7f38c3cb27d825c54dd95e93a59b", - "0xac3b8a7c2317ea967f229eddc3e23e279427f665c4705c7532ed33443f1243d33453c1088f57088d2ab1e3df690a9cc9", - "0xb787beeddfbfe36dd51ec4efd9cf83e59e84d354c3353cc9c447be53ae53d366ed1c59b686e52a92f002142c8652bfe0", - "0xb7a64198300cb6716aa7ac6b25621f8bdec46ad5c07a27e165b3f774cdf65bcfdbf31e9bae0c16b44de4b00ada7a4244", - "0xb8ae9f1452909e0c412c7a7fe075027691ea8df1347f65a5507bc8848f1d2c833d69748076db1129e5b4fb912f65c86c", - "0x9682e41872456b9fa67def89e71f06d362d6c8ca85c9c48536615bc401442711e1c9803f10ab7f8ab5feaec0f9df20a6", - "0x88889ff4e271dc1c7e21989cc39f73cde2f0475acd98078281591ff6c944fadeb9954e72334319050205d745d4df73df", - "0x8f79b5b8159e7fd0d93b0645f3c416464f39aec353b57d99ecf24f96272df8a068ad67a6c90c78d82c63b40bb73989bb", - "0x838c01a009a3d8558a3f0bdd5e22de21af71ca1aefc8423c91dc577d50920e9516880e87dce3e6d086e11cd45c9052d9", - "0xb97f1c6eee8a78f137c840667cc288256e39294268a3009419298a04a1d0087c9c9077b33c917c65caf76637702dda8a", - "0x972284ce72f96a61c899260203dfa06fc3268981732bef74060641c1a5068ead723e3399431c247ca034b0dae861e8df", - "0x945a8d52d6d3db6663dbd3110c6587f9e9c44132045eeffba15621576d178315cb52870fa5861669f84f0bee646183fe", - "0xa0a547b5f0967b1c3e5ec6c6a9a99f0578521489180dfdfbb5561f4d166baac43a2f06f950f645ce991664e167537eed", - "0xa0592cda5cdddf1340033a745fd13a6eff2021f2e26587116c61c60edead067e0f217bc2bef4172a3c9839b0b978ab35", - "0xb9c223b65a3281587fa44ec829e609154b32f801fd1de6950e01eafb07a8324243b960d5735288d0f89f0078b2c42b5b", - "0x99ebfc3b8f9f98249f4d37a0023149ed85edd7a5abe062c8fb30c8c84555258b998bdcdd1d400bc0fa2a4aaa8b224466", - "0x955b68526e6cb3937b26843270f4e60f9c6c8ece2fa9308fe3e23afa433309c068c66a4bc16ee2cf04220f095e9afce4", - "0xb766caeafcc00378135ae53397f8a67ed586f5e30795462c4a35853de6681b1f17401a1c40958de32b197c083b7279c1", - "0x921bf87cad947c2c33fa596d819423c10337a76fe5a63813c0a9dc78a728207ae7b339407a402fc4d0f7cba3af6da6fc", - "0xa74ba1f3bc3e6c025db411308f49b347ec91da1c916bda9da61e510ec8d71d25e0ac0f124811b7860e5204f93099af27", - "0xa29b4d144e0bf17a7e8353f2824cef0ce85621396babe8a0b873ca1e8a5f8d508b87866cf86da348470649fceefd735c", - "0xa8040e12ffc3480dd83a349d06741d1572ef91932c46f5cf03aee8454254156ee95786fd013d5654725e674c920cec32", - "0x8c4cf34ca60afd33923f219ffed054f90cd3f253ffeb2204a3b61b0183417e366c16c07fae860e362b0f2bfe3e1a1d35", - "0x8195eede4ddb1c950459df6c396b2e99d83059f282b420acc34220cadeed16ab65c856f2c52568d86d3c682818ed7b37", - "0x91fff19e54c15932260aa990c7fcb3c3c3da94845cc5aa8740ef56cf9f58d19b4c3c55596f8d6c877f9f4d22921d93aa", - "0xa3e0bf7e5d02a80b75cf75f2db7e66cb625250c45436e3c136d86297d652590ec97c2311bafe407ad357c79ab29d107b", - "0x81917ff87e5ed2ae4656b481a63ced9e6e5ff653b8aa6b7986911b8bc1ee5b8ef4f4d7882c3f250f2238e141b227e510", - "0x915fdbe5e7de09c66c0416ae14a8750db9412e11dc576cf6158755fdcaf67abdbf0fa79b554cac4fe91c4ec245be073f", - "0x8df27eafb5c3996ba4dc5773c1a45ca77e626b52e454dc1c4058aa94c2067c18332280630cc3d364821ee53bf2b8c130", - "0x934f8a17c5cbb827d7868f5c8ca00cb027728a841000a16a3428ab16aa28733f16b52f58c9c4fbf75ccc45df72d9c4df", - "0xb83f4da811f9183c25de8958bc73b504cf790e0f357cbe74ef696efa7aca97ad3b7ead1faf76e9f982c65b6a4d888fc2", - "0x87188213c8b5c268dc2b6da413f0501c95749e953791b727450af3e43714149c115b596b33b63a2f006a1a271b87efd0", - "0x83e9e888ab9c3e30761de635d9aabd31248cdd92f7675fc43e4b21fd96a03ec1dc4ad2ec94fec857ffb52683ac98e360", - "0xb4b9a1823fe2d983dc4ec4e3aaea297e581c3fc5ab4b4af5fa1370caa37af2d1cc7fc6bfc5e7da60ad8fdce27dfe4b24", - "0x856388bc78aef465dbcdd1f559252e028c9e9a2225c37d645c138e78f008f764124522705822a61326a6d1c79781e189", - "0xa6431b36db93c3b47353ba22e7c9592c9cdfb9cbdd052ecf2cc3793f5b60c1e89bc96e6bae117bfd047f2308da00dd2f", - "0xb619972d48e7e4291542dcde08f7a9cdc883c892986ded2f23ccb216e245cd8d9ad1d285347b0f9d7611d63bf4cee2bc", - "0x8845cca6ff8595955f37440232f8e61d5351500bd016dfadd182b9d39544db77a62f4e0102ff74dd4173ae2c181d24ef", - "0xb2f5f7fa26dcd3b6550879520172db2d64ee6aaa213cbef1a12befbce03f0973a22eb4e5d7b977f466ac2bf8323dcedd", - "0x858b7f7e2d44bdf5235841164aa8b4f3d33934e8cb122794d90e0c1cac726417b220529e4f896d7b77902ab0ccd35b3a", - "0x80b0408a092dae2b287a5e32ea1ad52b78b10e9c12f49282976cd738f5d834e03d1ad59b09c5ccaccc39818b87d06092", - "0xb996b0a9c6a2d14d984edcd6ab56bc941674102980d65b3ad9733455f49473d3f587c8cbf661228a7e125ddbe07e3198", - "0x90224fcebb36865293bd63af786e0c5ade6b67c4938d77eb0cbae730d514fdd0fe2d6632788e858afd29d46310cf86df", - "0xb71351fdfff7168b0a5ec48397ecc27ac36657a8033d9981e97002dcca0303e3715ce6dd3f39423bc8ef286fa2e9e669", - "0xae2a3f078b89fb753ce4ed87e0c1a58bb19b4f0cfb6586dedb9fcab99d097d659a489fb40e14651741e1375cfc4b6c5f", - "0x8ef476b118e0b868caed297c161f4231bbeb863cdfa5e2eaa0fc6b6669425ce7af50dc374abceac154c287de50c22307", - "0x92e46ab472c56cfc6458955270d3c72b7bde563bb32f7d4ab4d959db6f885764a3d864e1aa19802fefaa5e16b0cb0b54", - "0x96a3f68323d1c94e73d5938a18a377af31b782f56212de3f489d22bc289cf24793a95b37f1d6776edf88114b5c1fa695", - "0x962cc068cfce6faaa27213c4e43e44eeff0dfbb6d25b814e82c7da981fb81d7d91868fa2344f05fb552362f98cfd4a72", - "0x895d4e4c4ad670abf66d43d59675b1add7afad7438ada8f42a0360c704cee2060f9ac15b4d27e9b9d0996bb801276fe3", - "0xb3ad18d7ece71f89f2ef749b853c45dc56bf1c796250024b39a1e91ed11ca32713864049c9aaaea60cde309b47486bbf", - "0x8f05404e0c0258fdbae50e97ccb9b72ee17e0bd2400d9102c0dad981dac8c4c71585f03e9b5d50086d0a2d3334cb55d1", - "0x8bd877e9d4591d02c63c6f9fc9976c109de2d0d2df2bfa5f6a3232bab5b0b8b46e255679520480c2d7a318545efa1245", - "0x8d4c16b5d98957c9da13d3f36c46f176e64e5be879f22be3179a2c0e624fe4758a82bf8c8027410002f973a3b84cd55a", - "0x86e2a8dea86427b424fa8eada881bdff896907084a495546e66556cbdf070b78ba312bf441eb1be6a80006d25d5097a3", - "0x8608b0c117fd8652fdab0495b08fadbeba95d9c37068e570de6fddfef1ba4a1773b42ac2be212836141d1bdcdef11a17", - "0xa13d6febf5fb993ae76cae08423ca28da8b818d6ef0fde32976a4db57839cd45b085026b28ee5795f10a9a8e3098c683", - "0x8e261967fa6de96f00bc94a199d7f72896a6ad8a7bbb1d6187cca8fad824e522880e20f766620f4f7e191c53321d70f9", - "0x8b8e8972ac0218d7e3d922c734302803878ad508ca19f5f012bc047babd8a5c5a53deb5fe7c15a4c00fd6d1cb9b1dbd0", - "0xb5616b233fb3574a2717d125a434a2682ff68546dccf116dd8a3b750a096982f185614b9fb6c7678107ff40a451f56fa", - "0xaa6adf9b0c3334b0d0663f583a4914523b2ac2e7adffdb026ab9109295ff6af003ef8357026dbcf789896d2afded8d73", - "0xacb72df56a0b65496cd534448ed4f62950bb1e11e50873b6ed349c088ee364441821294ce0f7c61bd7d38105bea3b442", - "0xabae12df83e01ec947249fedd0115dc501d2b03ff7232092979eda531dbbca29ace1d46923427c7dde4c17bdf3fd7708", - "0x820b4fc2b63a9fda7964acf5caf19a2fc4965007cb6d6b511fcafcb1f71c3f673a1c0791d3f86e3a9a1eb6955b191cc0", - "0xaf277259d78c6b0f4f030a10c53577555df5e83319ddbad91afbd7c30bc58e7671c56d00d66ec3ab5ef56470cd910cee", - "0xad4a861c59f1f5ca1beedd488fb3d131dea924fffd8e038741a1a7371fad7370ca5cf80dc01f177fbb9576713bb9a5b3", - "0xb67a5162982ce6a55ccfb2f177b1ec26b110043cf18abd6a6c451cf140b5af2d634591eb4f28ad92177d8c7e5cd0a5e8", - "0x96176d0a83816330187798072d449cbfccff682561e668faf6b1220c9a6535b32a6e4f852e8abb00f79abb87493df16b", - "0xb0afe6e7cb672e18f0206e4423f51f8bd0017bf464c4b186d46332c5a5847647f89ff7fa4801a41c1b0b42f6135bcc92", - "0x8fc5e7a95ef20c1278c645892811f6fe3f15c431ebc998a32ec0da44e7213ea934ed2be65239f3f49b8ec471e9914160", - "0xb7793e41adda6c82ba1f2a31f656f6205f65bf8a3d50d836ee631bc7ce77c153345a2d0fc5c60edf8b37457c3729c4ec", - "0xa504dd7e4d6b2f4379f22cc867c65535079c75ccc575955f961677fa63ecb9f74026fa2f60c9fb6323c1699259e5e9c8", - "0xab899d00ae693649cc1afdf30fb80d728973d2177c006e428bf61c7be01e183866614e05410041bc82cb14a33330e69c", - "0x8a3bd8b0b1be570b65c4432a0f6dc42f48a2000e30ab089cf781d38f4090467b54f79c0d472fcbf18ef6a00df69cc6f3", - "0xb4d7028f7f76a96a3d7803fca7f507ae11a77c5346e9cdfccb120a833a59bda1f4264e425aa588e7a16f8e7638061d84", - "0xb9c7511a76ea5fb105de905d44b02edb17008335766ee357ed386b7b3cf19640a98b38785cb14603c1192bee5886c9b6", - "0x8563afb12e53aed71ac7103ab8602bfa8371ae095207cb0d59e8fd389b6ad1aff0641147e53cb6a7ca16c7f37c9c5e6b", - "0x8e108be614604e09974a9ed90960c28c4ea330a3d9a0cb4af6dd6f193f84ab282b243ecdf549b3131036bebc8905690c", - "0xb794d127fbedb9c5b58e31822361706ffac55ce023fbfe55716c3c48c2fd2f2c7660a67346864dfe588812d369cb50b6", - "0xb797a3442fc3b44f41baefd30346f9ac7f96e770d010d53c146ce74ce424c10fb62758b7e108b8abfdc5fafd89d745cb", - "0x993bb71e031e8096442e6205625e1bfddfe6dd6a83a81f3e2f84fafa9e5082ab4cad80a099f21eff2e81c83457c725c3", - "0x8711ab833fc03e37acf2e1e74cfd9133b101ff4144fe30260654398ae48912ab46549d552eb9d15d2ea57760d35ac62e", - "0xb21321fd2a12083863a1576c5930e1aecb330391ef83326d9d92e1f6f0d066d1394519284ddab55b2cb77417d4b0292f", - "0x877d98f731ffe3ee94b0b5b72d127630fa8a96f6ca4f913d2aa581f67732df6709493693053b3e22b0181632ac6c1e3b", - "0xae391c12e0eb8c145103c62ea64f41345973311c3bf7281fa6bf9b7faafac87bcf0998e5649b9ef81e288c369c827e07", - "0xb83a2842f36998890492ab1cd5a088d9423d192681b9a3a90ec518d4c541bce63e6c5f4df0f734f31fbfdd87785a2463", - "0xa21b6a790011396e1569ec5b2a423857b9bec16f543e63af28024e116c1ea24a3b96e8e4c75c6537c3e4611fd265e896", - "0xb4251a9c4aab3a495da7a42e684ba4860dbcf940ad1da4b6d5ec46050cbe8dab0ab9ae6b63b5879de97b905723a41576", - "0x8222f70aebfe6ac037f8543a08498f4cadb3edaac00336fc00437eb09f2cba758f6c38e887cc634b4d5b7112b6334836", - "0x86f05038e060594c46b5d94621a1d9620aa8ba59a6995baf448734e21f58e23c1ea2993d3002ad5250d6edd5ba59b34f", - "0xa7c0c749baef811ab31b973c39ceb1d94750e2bc559c90dc5eeb20d8bb6b78586a2b363c599ba2107d6be65cd435f24e", - "0x861d46a5d70b38d6c1cd72817a2813803d9f34c00320c8b62f8b9deb67f5b5687bc0b37c16d28fd017367b92e05da9ca", - "0xb3365d3dab639bffbe38e35383686a435c8c88b397b717cd4aeced2772ea1053ceb670f811f883f4e02975e5f1c4ac58", - "0xa5750285f61ab8f64cd771f6466e2c0395e01b692fd878f2ef2d5c78bdd8212a73a3b1dfa5e4c8d9e1afda7c84857d3b", - "0x835a10809ccf939bc46cf950a33b36d71be418774f51861f1cd98a016ade30f289114a88225a2c11e771b8b346cbe6ef", - "0xa4f59473a037077181a0a62f1856ec271028546ca9452b45cedfcb229d0f4d1aabfc13062b07e536cc8a0d4b113156a2", - "0x95cd14802180b224d44a73cc1ed599d6c4ca62ddcaa503513ccdc80aaa8be050cc98bd4b4f3b639549beb4587ac6caf9", - "0x973b731992a3e69996253d7f36dd7a0af1982b5ed21624b77a7965d69e9a377b010d6dabf88a8a97eec2a476259859cc", - "0xaf8a1655d6f9c78c8eb9a95051aa3baaf9c811adf0ae8c944a8d3fcba87b15f61021f3baf6996fa0aa51c81b3cb69de1", - "0x835aad5c56872d2a2d6c252507b85dd742bf9b8c211ccb6b25b52d15c07245b6d89b2a40f722aeb5083a47cca159c947", - "0xabf4e970b02bef8a102df983e22e97e2541dd3650b46e26be9ee394a3ea8b577019331857241d3d12b41d4eacd29a3ac", - "0xa13c32449dbedf158721c13db9539ae076a6ce5aeaf68491e90e6ad4e20e20d1cdcc4a89ed9fd49cb8c0dd50c17633c1", - "0x8c8f78f88b7e22dd7e9150ab1c000f10c28e696e21d85d6469a6fe315254740f32e73d81ab1f3c1cf8f544c86df506e8", - "0xb4b77f2acfe945abf81f2605f906c10b88fb4d28628487fb4feb3a09f17f28e9780445dfcee4878349d4c6387a9d17d4", - "0x8d255c235f3812c6ecc646f855fa3832be5cb4dbb9c9e544989fafdf3f69f05bfd370732eaf954012f0044aa013fc9c6", - "0xb982efd3f34b47df37c910148ac56a84e8116647bea24145a49e34e0a6c0176e3284d838dae6230cb40d0be91c078b85", - "0x983f365aa09bd85df2a6a2ad8e4318996b1e27d02090755391d4486144e40d80b1fbfe1c798d626db92f52e33aa634da", - "0x95fd1981271f3ea3a41d654cf497e6696730d9ff7369f26bc4d7d15c7adb4823dd0c42e4a005a810af12d234065e5390", - "0xa9f5219bd4b913c186ef30c02f995a08f0f6f1462614ea5f236964e02bdaa33db9d9b816c4aee5829947840a9a07ba60", - "0x9210e6ceb05c09b46fd09d036287ca33c45124ab86315e5d6911ff89054f1101faaa3e83d123b7805056d388bcec6664", - "0x8ed9cbf69c6ff3a5c62dd9fe0d7264578c0f826a29e614bc2fb4d621d90c8c9992438accdd7a614b1dca5d1bb73dc315", - "0x85cf2a8cca93e00da459e3cecd22c342d697eee13c74d5851634844fc215f60053cf84b0e03c327cb395f48d1c71a8a4", - "0x8818a18e9a2ec90a271b784400c1903089ffb0e0b40bc5abbbe12fbebe0f731f91959d98c5519ef1694543e31e2016d4", - "0x8dabc130f296fa7a82870bf9a8405aaf542b222ed9276bba9bd3c3555a0f473acb97d655ee7280baff766a827a8993f0", - "0xac7952b84b0dc60c4d858f034093b4d322c35959605a3dad2b806af9813a4680cb038c6d7f4485b4d6b2ff502aaeca25", - "0xad65cb6d57b48a2602568d2ec8010baed0eb440eec7638c5ec8f02687d764e9de5b5d42ad5582934e592b48471c22d26", - "0xa02ab8bd4c3d114ea23aebdd880952f9495912817da8c0c08eabc4e6755439899d635034413d51134c72a6320f807f1c", - "0x8319567764b8295402ec1ebef4c2930a138480b37e6d7d01c8b4c9cd1f2fc3f6e9a44ae6e380a0c469b25b06db23305f", - "0xafec53b2301dc0caa8034cd9daef78c48905e6068d692ca23d589b84a6fa9ddc2ed24a39480597e19cb3e83eec213b3f", - "0xac0b4ffdb5ae08e586a9cdb98f9fe56f4712af3a97065e89e274feacfb52b53c839565aee93c4cfaaccfe51432c4fab0", - "0x8972cbf07a738549205b1094c5987818124144bf187bc0a85287c94fdb22ce038c0f11df1aa16ec5992e91b44d1af793", - "0xb7267aa6f9e3de864179b7da30319f1d4cb2a3560f2ea980254775963f1523b44c680f917095879bebfa3dc2b603efcf", - "0x80f68f4bfc337952e29504ee5149f15093824ea7ab02507efd1317a670f6cbc3611201848560312e3e52e9d9af72eccf", - "0x8897fee93ce8fc1e1122e46b6d640bba309384dbd92e46e185e6364aa8210ebf5f9ee7e5e604b6ffba99aa80a10dd7d0", - "0xb58ea6c02f2360be60595223d692e82ee64874fda41a9f75930f7d28586f89be34b1083e03bbc1575bbfdda2d30db1ea", - "0x85a523a33d903280d70ac5938770453a58293480170c84926457ac2df45c10d5ff34322ab130ef4a38c916e70d81af53", - "0xa2cbf045e1bed38937492c1f2f93a5ba41875f1f262291914bc1fc40c60bd0740fb3fea428faf6da38b7c180fe8ac109", - "0x8c09328770ed8eb17afc6ac7ddd87bb476de18ed63cab80027234a605806895959990c47bd10d259d7f3e2ecb50074c9", - "0xb4b9e19edb4a33bde8b7289956568a5b6b6557404e0a34584b5721fe6f564821091013fbb158e2858c6d398293bb4b59", - "0x8a47377df61733a2aa5a0e945fce00267f8e950f37e109d4487d92d878fb8b573317bb382d902de515b544e9e233458d", - "0xb5804c9d97efeff5ca94f3689b8088c62422d92a1506fd1d8d3b1b30e8a866ad0d6dad4abfa051dfc4471250cac4c5d9", - "0x9084a6ee8ec22d4881e9dcc8a9eb3c2513523d8bc141942370fd191ad2601bf9537a0b1e84316f3209b3d8a54368051e", - "0x85447eea2fa26656a649f8519fa67279183044791d61cf8563d0783d46d747d96af31d0a93507bbb2242666aa87d3720", - "0x97566a84481027b60116c751aec552adfff2d9038e68d48c4db9811fb0cbfdb3f1d91fc176a0b0d988a765f8a020bce1", - "0xae87e5c1b9e86c49a23dceda4ecfd1dcf08567f1db8e5b6ec752ebd45433c11e7da4988573cdaebbb6f4135814fc059e", - "0xabee05cf9abdbc52897ac1ce9ed157f5466ed6c383d6497de28616238d60409e5e92619e528af8b62cc552bf09970dc2", - "0xae6d31cd7bf9599e5ee0828bab00ceb4856d829bba967278a73706b5f388465367aa8a6c7da24b5e5f1fdd3256ef8e63", - "0xac33e7b1ee47e1ee4af472e37ab9e9175260e506a4e5ce449788075da1b53c44cb035f3792d1eea2aa24b1f688cc6ed3", - "0x80f65b205666b0e089bb62152251c48c380a831e5f277f11f3ef4f0d52533f0851c1b612267042802f019ec900dc0e8f", - "0x858520ad7aa1c9fed738e3b583c84168f2927837ad0e1d326afe9935c26e9b473d7f8c382e82ef1fe37d2b39bb40a1ee", - "0xb842dd4af8befe00a97c2d0f0c33c93974761e2cb9e5ab8331b25170318ddd5e4bdbc02d8f90cbfdd5f348f4f371c1f7", - "0x8bf2cb79bc783cb57088aae7363320cbeaabd078ffdec9d41bc74ff49e0043d0dad0086a30e5112b689fd2f5a606365d", - "0x982eb03bbe563e8850847cd37e6a3306d298ab08c4d63ab6334e6b8c1fa13fce80cf2693b09714c7621d74261a0ff306", - "0xb143edb113dec9f1e5105d4a93fbe502b859e587640d3db2f628c09a17060e6aec9e900e2c8c411cda99bc301ff96625", - "0xaf472d9befa750dcebc5428fe1a024f18ec1c07bca0f95643ce6b5f4189892a910285afb03fd7ed7068fbe614e80d33c", - "0xa97e3bc57ede73ecd1bbf02de8f51b4e7c1a067da68a3cd719f4ba26a0156cbf1cef2169fd35a18c5a4cced50d475998", - "0xa862253c937cf3d75d7183e5f5be6a4385d526aeda5171c1c60a8381fea79f88f5f52a4fab244ecc70765d5765e6dfd5", - "0x90cb776f8e5a108f1719df4a355bebb04bf023349356382cae55991b31720f0fd03206b895fa10c56c98f52453be8778", - "0xa7614e8d0769dccd520ea4b46f7646e12489951efaef5176bc889e9eb65f6e31758df136b5bf1e9107e68472fa9b46ec", - "0xac3a9b80a3254c42e5ed3a090a0dd7aee2352f480de96ad187027a3bb6c791eddfc3074b6ffd74eea825188f107cda4d", - "0x82a01d0168238ef04180d4b6e0a0e39024c02c2d75b065017c2928039e154d093e1af4503f4d1f3d8a948917abb5d09f", - "0x8fab000a2b0eef851a483aec8d2dd85fe60504794411a2f73ed82e116960547ac58766cb73df71aea71079302630258d", - "0x872451a35c6db61c63e9b8bb9f16b217f985c20be4451c14282c814adb29d7fb13f201367c664435c7f1d4d9375d7a58", - "0x887d9ff54cc96b35d562df4a537ff972d7c4b3fd91ab06354969a4cfede0b9fc68bbffb61d0dbf1a58948dc701e54f5a", - "0x8cb5c2a6bd956875d88f41ae24574434f1308514d44057b55c9c70f13a3366ed054150eed0955a38fda3f757be73d55f", - "0x89ad0163cad93e24129d63f8e38422b7674632a8d0a9016ee8636184cab177659a676c4ee7efba3abe1a68807c656d60", - "0xb9ec01c7cab6d00359b5a0b4a1573467d09476e05ca51a9227cd16b589a9943d161eef62dcc73f0de2ec504d81f4d252", - "0x8031d17635d39dfe9705c485d2c94830b6fc9bc67b91300d9d2591b51e36a782e77ab5904662effa9382d9cca201f525", - "0x8be5a5f6bc8d680e5092d6f9a6585acbaaaa2ddc671da560dcf5cfa4472f4f184b9597b5b539438accd40dda885687cc", - "0xb1fc0f052fae038a2e3de3b3a96b0a1024b009de8457b8b3adb2d315ae68a89af905720108a30038e5ab8d0d97087785", - "0x8b8bdc77bd3a6bc7ca5492b6f8c614852c39a70d6c8a74916eaca0aeb4533b11898b8820a4c2620a97bf35e275480029", - "0xaf35f4dc538d4ad5cdf710caa38fd1eb496c3fa890a047b6a659619c5ad3054158371d1e88e0894428282eed9f47f76b", - "0x8166454a7089cc07758ad78724654f4e7a1a13e305bbf88ddb86f1a4b2904c4fc8ab872d7da364cdd6a6c0365239e2ad", - "0xab287c7d3addce74ce40491871c768abe01daaa0833481276ff2e56926b38a7c6d2681ffe837d2cc323045ad1a4414f9", - "0xb90317f4505793094d89365beb35537f55a6b5618904236258dd04ca61f21476837624a2f45fef8168acf732cab65579", - "0x98ae5ea27448e236b6657ab5ef7b1cccb5372f92ab25f5fa651fbac97d08353a1dae1b280b1cd42b17d2c6a70a63ab9d", - "0xadcf54e752d32cbaa6cb98fbca48d8cd087b1db1d131d465705a0d8042c8393c8f4d26b59006eb50129b21e6240f0c06", - "0xb591a3e4db18a7345fa935a8dd7994bbac5cc270b8ebd84c8304c44484c7a74afb45471fdbe4ab22156a30fae1149b40", - "0x806b53ac049a42f1dcc1d6335505371da0bf27c614f441b03bbf2e356be7b2fb4eed7117eabcce9e427a542eaa2bf7d8", - "0x800482e7a772d49210b81c4a907f5ce97f270b959e745621ee293cf8c71e8989363d61f66a98f2d16914439544ca84c7", - "0x99de9eafdad3617445312341644f2bb888680ff01ce95ca9276b1d2e5ef83fa02dab5e948ebf66c17df0752f1bd37b70", - "0x961ee30810aa4c93ae157fbe9009b8e443c082192bd36a73a6764ff9b2ad8b0948fe9a73344556e01399dd77badb4257", - "0xae0a361067c52efbe56c8adf982c00432cd478929459fc7f74052c8ee9531cd031fe1335418fde53f7c2ef34254eb7ac", - "0xa3503d16b6b27eb20c1b177bcf90d13706169220523a6271b85b2ce35a9a2b9c5bed088540031c0a4ebfdae3a4c6ab04", - "0x909420122c3e723289ca4e7b81c2df5aff312972a2203f4c45821b176e7c862bf9cac7f7df3adf1d59278f02694d06e7", - "0x989f42380ae904b982f85d0c6186c1aef5d6bcba29bcfbb658e811b587eb2749c65c6e4a8cc6409c229a107499a4f5d7", - "0x8037a6337195c8e26a27ea4ef218c6e7d79a9720aaab43932d343192abc2320fe72955f5e431c109093bda074103330a", - "0xb312e168663842099b88445e940249cc508f080ab0c94331f672e7760258dbd86be5267e4cf25ea25facb80bff82a7e9", - "0xaaa3ff8639496864fcdbfdda1ac97edc4f08e3c9288b768f6c8073038c9fbbf7e1c4bea169b4d45c31935cdf0680d45e", - "0x97dbd3df37f0b481a311dfc5f40e59227720f367912200d71908ef6650f32cc985cb05b981e3eea38958f7e48d10a15d", - "0xa89d49d1e267bb452d6cb621b9a90826fe55e9b489c0427b94442d02a16f390eed758e209991687f73f6b5a032321f42", - "0x9530dea4e0e19d6496f536f2e75cf7d814d65fde567055eb20db48fd8d20d501cd2a22fb506db566b94c9ee10f413d43", - "0x81a7009b9e67f1965fa7da6a57591c307de91bf0cd35ab4348dc4a98a4961e096d004d7e7ad318000011dc4342c1b809", - "0x83440a9402b766045d7aca61a58bba2aa29cac1cf718199e472ba086f5d48093d9dda4d135292ba51d049a23964eceae", - "0xa06c9ce5e802df14f6b064a3d1a0735d429b452f0e2e276042800b0a4f16df988fd94cf3945921d5dd3802ab2636f867", - "0xb1359e358b89936dee9e678a187aad3e9ab14ac40e96a0a68f70ee2583cdcf467ae03bef4215e92893f4e12f902adec8", - "0x835304f8619188b4d14674d803103d5a3fa594d48e96d9699e653115dd05fdc2dda6ba3641cf7ad53994d448da155f02", - "0x8327cba5a9ff0d3f5cd0ae55e77167448926d5fcf76550c0ad978092a14122723090c51c415e88e42a2b62eb07cc3981", - "0xb373dcdaea85f85ce9978b1426a7ef4945f65f2d3467a9f1cc551a99766aac95df4a09e2251d3f89ca8c9d1a7cfd7b0e", - "0xab1422dc41af2a227b973a6fd124dfcb2367e2a11a21faa1d381d404f51b7257e5bc82e9cf20cd7fe37d7ae761a2ab37", - "0xa93774a03519d2f20fdf2ef46547b0a5b77c137d6a3434b48d56a2cbef9e77120d1b85d0092cf8842909213826699477", - "0x8eb967a495a38130ea28711580b7e61bcd1d051cd9e4f2dbf62f1380bd86e0d60e978d72f6f31e909eb97b3b9a2b867c", - "0xae8213378da1287ba1fe4242e1acaec19b877b6fe872400013c6eac1084b8d03156792fa3020201725b08228a1e80f49", - "0xb143daf6893d674d607772b3b02d8ac48f294237e2f2c87963c0d4e26d9227d94a2a13512457c3d5883544bbc259f0ef", - "0xb343bd2aca8973888e42542218924e2dda2e938fd1150d06878af76f777546213912b7c7a34a0f94186817d80ffa185c", - "0xb188ebc6a8c3007001aa347ae72cc0b15d09bc6c19a80e386ee4b334734ec0cc2fe8b493c2422f38d1e6d133cc3db6fe", - "0xb795f6a8b9b826aaeee18ccd6baf6c5adeeec85f95eb5b6d19450085ec7217e95a2d9e221d77f583b297d0872073ba0e", - "0xb1c7dbd998ad32ae57bfa95deafa147024afd57389e98992c36b6e52df915d3d5a39db585141ec2423173e85d212fed8", - "0x812bcdeb9fe5f12d0e1df9964798056e1f1c3de3b17b6bd2919b6356c4b86d8e763c01933efbe0224c86a96d5198a4be", - "0xb19ebeda61c23d255cbf472ef0b8a441f4c55b70f0d8ed47078c248b1d3c7c62e076b43b95c00a958ec8b16d5a7cb0d7", - "0xb02adc9aaa20e0368a989c2af14ff48b67233d28ebee44ff3418bb0473592e6b681af1cc45450bd4b175df9051df63d9", - "0x8d87f0714acee522eb58cec00360e762adc411901dba46adc9227124fa70ee679f9a47e91a6306d6030dd4eb8de2f3c1", - "0x8be54cec21e74bcc71de29dc621444263737db15f16d0bb13670f64e42f818154e04b484593d19ef95f2ee17e4b3fe21", - "0xab8e20546c1db38d31493b5d5f535758afb17e459645c1b70813b1cf7d242fd5d1f4354a7c929e8f7259f6a25302e351", - "0x89f035a1ed8a1e302ac893349ba8ddf967580fcb6e73d44af09e3929cde445e97ff60c87dafe489e2c0ab9c9986cfa00", - "0x8b2b0851a795c19191a692af55f7e72ad2474efdc5401bc3733cfdd910e34c918aaebe69d5ea951bdddf3c01cabbfc67", - "0xa4edb52c2b51495ccd1ee6450fc14b7b3ede8b3d106808929d02fb31475bacb403e112ba9c818d2857651e508b3a7dd1", - "0x9569341fded45d19f00bcf3cbf3f20eb2b4d82ef92aba3c8abd95866398438a2387437e580d8b646f17cf6fde8c5af23", - "0xaa4b671c6d20f72f2f18a939a6ff21cc37e0084b44b4a717f1be859a80b39fb1be026b3205adec2a66a608ec2bcd578f", - "0x94902e980de23c4de394ad8aec91b46f888d18f045753541492bfbb92c59d3daa8de37ae755a6853744af8472ba7b72b", - "0xaf651ef1b2a0d30a7884557edfad95b6b5d445a7561caebdc46a485aedd25932c62c0798465c340a76f6feaa196dd712", - "0xb7b669b8e5a763452128846dd46b530dca4893ace5cc5881c7ddcd3d45969d7e73fbebdb0e78aa81686e5f7b22ec5759", - "0x82507fd4ebe9fa656a7f2e084d64a1fa6777a2b0bc106d686e2d9d2edafc58997e58cb6bfd0453b2bf415704aa82ae62", - "0xb40bce2b42b88678400ecd52955bbdadd15f8b9e1b3751a1a3375dc0efb5ca3ee258cf201e1140b3c09ad41217d1d49e", - "0xb0210d0cbb3fbf3b8cdb39e862f036b0ff941cd838e7aaf3a8354e24246e64778d22f3de34572e6b2a580614fb6425be", - "0x876693cba4301b251523c7d034108831df3ce133d8be5a514e7a2ca494c268ca0556fa2ad8310a1d92a16b55bcd99ea9", - "0x8660281406d22a4950f5ef050bf71dd3090edb16eff27fa29ef600cdea628315e2054211ed2cc6eaf8f2a1771ef689fd", - "0xa610e7e41e41ab66955b809ba4ade0330b8e9057d8efc9144753caed81995edeb1a42a53f93ce93540feca1fae708dac", - "0xa49e2c176a350251daef1218efaccc07a1e06203386ede59c136699d25ca5cb2ac1b800c25b28dd05678f14e78e51891", - "0x83e0915aa2b09359604566080d411874af8c993beba97d4547782fdbe1a68e59324b800ff1f07b8db30c71adcbd102a8", - "0xa19e84e3541fb6498e9bb8a099c495cbfcad113330e0262a7e4c6544495bb8a754b2208d0c2d895c93463558013a5a32", - "0x87f2bd49859a364912023aca7b19a592c60214b8d6239e2be887ae80b69ebdeb59742bdebcfa73a586ab23b2c945586c", - "0xb8e8fdddae934a14b57bc274b8dcd0d45ebb95ddbaabef4454e0f6ce7d3a5a61c86181929546b3d60c447a15134d08e1", - "0x87e0c31dcb736ea4604727e92dc1d9a3cf00adcff79df3546e02108355260f3dd171531c3c0f57be78d8b28058fcc8c0", - "0x9617d74e8f808a4165a8ac2e30878c349e1c3d40972006f0787b31ea62d248c2d9f3fc3da83181c6e57e95feedfd0e8c", - "0x8949e2cee582a2f8db86e89785a6e46bc1565c2d8627d5b6bf43ba71ffadfab7e3c5710f88dcb5fb2fc6edf6f4fae216", - "0xad3fa7b0edceb83118972a2935a09f409d09a8db3869f30be3a76f67aa9fb379cabb3a3aff805ba023a331cad7d7eb64", - "0x8c95718a4112512c4efbd496be38bf3ca6cdcaad8a0d128f32a3f9aae57f3a57bdf295a3b372a8c549fda8f4707cffed", - "0x88f3261d1e28a58b2dee3fcc799777ad1c0eb68b3560f9b4410d134672d9533532a91ea7be28a041784872632d3c9d80", - "0xb47472a41d72dd2e8b72f5c4f8ad626737dde3717f63d6bc776639ab299e564cbad0a2ad5452a07f02ff49a359c437e5", - "0x9896d21dc2e8aad87b76d6df1654f10cd7bceed4884159d50a818bea391f8e473e01e14684814c7780235f28e69dca6e", - "0x82d47c332bbd31bbe83b5eb44a23da76d4a7a06c45d7f80f395035822bc27f62f59281d5174e6f8e77cc9b5c3193d6f0", - "0x95c74cd46206e7f70c9766117c34c0ec45c2b0f927a15ea167901a160e1530d8522943c29b61e03568aa0f9c55926c53", - "0xa89d7757825ae73a6e81829ff788ea7b3d7409857b378ebccd7df73fdbe62c8d9073741cf038314971b39af6c29c9030", - "0x8c1cd212d0b010905d560688cfc036ae6535bc334fa8b812519d810b7e7dcf1bb7c5f43deaa40f097158358987324a7f", - "0xb86993c383c015ed8d847c6b795164114dd3e9efd25143f509da318bfba89389ea72a420699e339423afd68b6512fafb", - "0x8d06bd379c6d87c6ed841d8c6e9d2d0de21653a073725ff74be1934301cc3a79b81ef6dd0aad4e7a9dc6eac9b73019bc", - "0x81af4d2d87219985b9b1202d724fe39ef988f14fef07dfe3c3b11714e90ffba2a97250838e8535eb63f107abfe645e96", - "0x8c5e0af6330a8becb787e4b502f34f528ef5756e298a77dc0c7467433454347f3a2e0bd2641fbc2a45b95e231c6e1c02", - "0x8e2a8f0f04562820dc8e7da681d5cad9fe2e85dd11c785fb6fba6786c57a857e0b3bd838fb849b0376c34ce1665e4837", - "0xa39be8269449bfdfc61b1f62077033649f18dae9bef7c6163b9314ca8923691fb832f42776f0160b9e8abd4d143aa4e1", - "0x8c154e665706355e1cc98e0a4cabf294ab019545ba9c4c399d666e6ec5c869ca9e1faf8fb06cd9c0a5c2f51a7d51b70a", - "0xa046a7d4de879d3ebd4284f08f24398e9e3bf006cd4e25b5c67273ade248689c69affff92ae810c07941e4904296a563", - "0xafd94c1cb48758e5917804df03fb38a6da0e48cd9b6262413ea13b26973f9e266690a1b7d9d24bbaf7e82718e0e594b0", - "0x859e21080310c8d6a38e12e2ac9f90a156578cdeb4bb2e324700e97d9a5511cd6045dc39d1d0de3f94aeed043a24119d", - "0xa219fb0303c379d0ab50893264919f598e753aac9065e1f23ef2949abc992577ab43c636a1d2c089203ec9ddb941e27d", - "0xb0fdb639d449588a2ca730afcba59334e7c387342d56defdfb7ef79c493f7fd0e5277eff18e7203e756c7bdda5803047", - "0x87f9c3b7ed01f54368aca6dbcf2f6e06bff96e183c4b2c65f8baa23b377988863a0a125d5cdd41a072da8462ced4c070", - "0x99ef7a5d5ac2f1c567160e1f8c95f2f38d41881850f30c461a205f7b1b9fb181277311333839b13fb3ae203447e17727", - "0xaeaca9b1c2afd24e443326cc68de67b4d9cedb22ad7b501a799d30d39c85bb2ea910d4672673e39e154d699e12d9b3dc", - "0xa11675a1721a4ba24dd3d0e4c3c33a6edf4cd1b9f6b471070b4386c61f77452266eae6e3f566a40cfc885eada9a29f23", - "0xb228334445e37b9b49cb4f2cc56b454575e92173ddb01370a553bba665adadd52df353ad74470d512561c2c3473c7bb9", - "0xa18177087c996572d76f81178d18ed1ceebc8362a396348ce289f1d8bd708b9e99539be6fccd4acb1112381cfc5749b4", - "0x8e7b8bf460f0d3c99abb19803b9e43422e91507a1c0c22b29ee8b2c52d1a384da4b87c292e28eff040db5be7b1f8641f", - "0xb03d038d813e29688b6e6f444eb56fec3abba64c3d6f890a6bcf2e916507091cdb2b9d2c7484617be6b26552ed1c56cb", - "0xa1c88ccd30e934adfc5494b72655f8afe1865a84196abfb376968f22ddc07761210b6a9fb7638f1413d1b4073d430290", - "0x961b714faebf172ad2dbc11902461e286e4f24a99a939152a53406117767682a571057044decbeb3d3feef81f4488497", - "0xa03dc4059b46effdd786a0a03cc17cfee8585683faa35bb07936ded3fa3f3a097f518c0b8e2db92fd700149db1937789", - "0xadf60180c99ca574191cbcc23e8d025b2f931f98ca7dfcebfc380226239b6329347100fcb8b0fcb12db108c6ad101c07", - "0x805d4f5ef24d46911cbf942f62cb84b0346e5e712284f82b0db223db26d51aabf43204755eb19519b00e665c7719fcaa", - "0x8dea7243e9c139662a7fe3526c6c601eee72fd8847c54c8e1f2ad93ef7f9e1826b170afe58817dac212427164a88e87f", - "0xa2ba42356606d651b077983de1ad643650997bb2babb188c9a3b27245bb65d2036e46667c37d4ce02cb1be5ae8547abe", - "0xaf2ae50b392bdc013db2d12ce2544883472d72424fc767d3f5cb0ca2d973fc7d1f425880101e61970e1a988d0670c81b", - "0x98e6bec0568d3939b31d00eb1040e9b8b2a35db46ddf4369bdaee41bbb63cc84423d29ee510a170fb5b0e2df434ba589", - "0x822ff3cd12fbef4f508f3ca813c04a2e0b9b799c99848e5ad3563265979e753ee61a48f6adc2984a850f1b46c1a43d35", - "0x891e8b8b92a394f36653d55725ef514bd2e2a46840a0a2975c76c2a935577f85289026aaa74384da0afe26775cbddfb9", - "0xb2a3131a5d2fe7c8967047aa66e4524babae941d90552171cc109527f345f42aa0df06dcbb2fa01b33d0043917bbed69", - "0x80c869469900431f3eeefafdbe07b8afd8cee7739e659e6d0109b397cacff85a88247698f87dc4e2fe39a592f250ac64", - "0x9091594f488b38f9d2bb5df49fd8b4f8829d9c2f11a197dd1431ed5abbc5c954bbde3387088f9ee3a5a834beb7619bce", - "0xb472e241e6956146cca57b97a8a204668d050423b4e76f857bad5b47f43b203a04c8391ba9d9c3e95093c071f9d376a1", - "0xb7dd2de0284844392f7dfb56fe7ca3ede41e27519753ffc579a0a8d2d65ceb8108d06b6b0d4c3c1a2588951297bd1a1e", - "0x902116ce70d0a079ac190321c1f48701318c05f8e69ee09694754885d33a835a849cafe56f499a2f49f6cda413ddf9a7", - "0xb18105cc736787fafaf7c3c11c448bce9466e683159dff52723b7951dff429565e466e4841d982e3aaa9ee2066838666", - "0x97ab9911f3f659691762d568ae0b7faa1047b0aed1009c319fa79d15d0db8db9f808fc385dc9a68fa388c10224985379", - "0xb2a2cba65f5b927e64d2904ba412e2bac1cf18c9c3eda9c72fb70262497ecf505b640827e2afebecf10eebbcf48ccd3e", - "0xb36a3fd677baa0d3ef0dac4f1548ff50a1730286b8c99d276a0a45d576e17b39b3cbadd2fe55e003796d370d4be43ce3", - "0xa5dfec96ca3c272566e89dc453a458909247e3895d3e44831528130bc47cc9d0a0dac78dd3cad680a4351d399d241967", - "0x8029382113909af6340959c3e61db27392531d62d90f92370a432aec3eb1e4c36ae1d4ef2ba8ec6edb4d7320c7a453f6", - "0x971d85121ea108e6769d54f9c51299b0381ece8b51d46d49c89f65bedc123bab4d5a8bc14d6f67f4f680077529cbae4c", - "0x98ff6afc01d0bec80a278f25912e1b1ebff80117adae72e31d5b9fa4d9624db4ba2065b444df49b489b0607c45e26c4c", - "0x8fa29be10fb3ab30ce25920fec0187e6e91e458947009dabb869aade7136c8ba23602682b71e390c251f3743164cbdaa", - "0xb3345c89eb1653418fe3940cf3e56a9a9c66526389b98f45ca02dd62bfb37baa69a4baaa7132d7320695f8ea6ad1fd94", - "0xb72c7f5541c9ac6b60a7ec9f5415e7fb14da03f7164ea529952a29399f3a071576608dbbcc0d45994f21f92ddbeb1e19", - "0xaa3450bb155a5f9043d0ef95f546a2e6ade167280bfb75c9f09c6f9cdb1fffb7ce8181436161a538433afa3681c7a141", - "0x92a18fecaded7854b349f441e7102b638ababa75b1b0281dd0bded6541abe7aa37d96693595be0b01fe0a2e2133d50f9", - "0x980756ddf9d2253cfe6c94960b516c94889d09e612810935150892627d2ecee9a2517e04968eea295d0106850c04ca44", - "0xae68c6ccc454318cdd92f32b11d89116a3b8350207a36d22a0f626718cad671d960090e054c0c77ac3162ae180ecfd4b", - "0x99f31f66eaaa551749ad91d48a0d4e3ff4d82ef0e8b28f3184c54e852422ba1bdafd53b1e753f3a070f3b55f3c23b6a2", - "0xa44eaeaa6589206069e9c0a45ff9fc51c68da38d4edff1d15529b7932e6f403d12b9387019c44a1488a5d5f27782a51f", - "0xb80b5d54d4b344840e45b79e621bd77a3f83fb4ce6d8796b7d6915107b3f3c34d2e7d95bdafd120f285669e5acf2437a", - "0xb36c069ec085a612b5908314d6b84c00a83031780261d1c77a0384c406867c9847d5b0845deddfa512cc04a8df2046fb", - "0xb09dbe501583220f640d201acea7ee3e39bf9eda8b91aa07b5c50b7641d86d71acb619b38d27835ce97c3759787f08e9", - "0x87403d46a2bf63170fff0b857acacf42ee801afe9ccba8e5b4aea967b68eac73a499a65ca46906c2eb4c8f27bc739faa", - "0x82b93669f42a0a2aa5e250ffe6097269da06a9c02fcd1801abbad415a7729a64f830754bafc702e64600ba47671c2208", - "0x8e3a3029be7edb8dd3ab1f8216664c8dc50d395f603736061d802cef77627db7b859ef287ed850382c13b4d22d6a2d80", - "0x968e9ec7194ff424409d182ce0259acd950c384c163c04463bc8700a40b79beba6146d22b7fa7016875a249b7b31c602", - "0x8b42c984bbe4996e0c20862059167c6bdc5164b1ffcd928f29512664459212d263e89f0f0e30eed4e672ffa5ed0b01b5", - "0x96bac54062110dada905363211133f1f15dc7e4fd80a4c6e4a83bc9a0bcbbaba11cd2c7a13debcf0985e1a954c1da66b", - "0xa16dc8a653d67a7cd7ae90b2fffac0bf1ca587005430fe5ba9403edd70ca33e38ba5661d2ed6e9d2864400d997626a62", - "0xa68ab11a570a27853c8d67e491591dcba746bfbee08a2e75ae0790399130d027ed387f41ef1d7de8df38b472df309161", - "0x92532b74886874447c0300d07eda9bbe4b41ed25349a3da2e072a93fe32c89d280f740d8ff70d5816793d7f2b97373cc", - "0x88e35711b471e89218fd5f4d0eadea8a29405af1cd81974427bc4a5fb26ed60798daaf94f726c96e779b403a2cd82820", - "0xb5c72aa4147c19f8c4f3a0a62d32315b0f4606e0a7025edc5445571eaf4daff64f4b7a585464821574dd50dbe1b49d08", - "0x9305d9b4095258e79744338683fd93f9e657367b3ab32d78080e51d54eec331edbc224fad5093ebf8ee4bd4286757eb8", - "0xb2a17abb3f6a05bcb14dc7b98321fa8b46d299626c73d7c6eb12140bf4c3f8e1795250870947af817834f033c88a59d6", - "0xb3477004837dbd8ba594e4296f960fc91ab3f13551458445e6c232eb04b326da803c4d93e2e8dcd268b4413305ff84da", - "0x924b4b2ebaafdcfdfedb2829a8bf46cd32e1407d8d725a5bd28bdc821f1bafb3614f030ea4352c671076a63494275a3f", - "0x8b81b9ef6125c82a9bece6fdcb9888a767ac16e70527753428cc87c56a1236e437da8be4f7ecfe57b9296dc3ae7ba807", - "0x906e19ec8b8edd58bdf9ae05610a86e4ea2282b1bbc1e8b00b7021d093194e0837d74cf27ac9916bdb8ec308b00da3da", - "0xb41c5185869071760ac786078a57a2ab4e2af60a890037ac0c0c28d6826f15c2cf028fddd42a9b6de632c3d550bfbc14", - "0xa646e5dec1b713ae9dfdf7bdc6cd474d5731a320403c7dfcfd666ffc9ae0cff4b5a79530e8df3f4aa9cb80568cb138e9", - "0xb0efad22827e562bd3c3e925acbd0d9425d19057868608d78c2209a531cccd0f2c43dc5673acf9822247428ffa2bb821", - "0xa94c19468d14b6f99002fc52ac06bbe59e5c472e4a0cdb225144a62f8870b3f10593749df7a2de0bd3c9476ce682e148", - "0x803864a91162f0273d49271dafaab632d93d494d1af935aefa522768af058fce52165018512e8d6774976d52bd797e22", - "0xa08711c2f7d45c68fb340ac23597332e1bcaec9198f72967b9921204b9d48a7843561ff318f87908c05a44fc35e3cc9d", - "0x91c3cad94a11a3197ae4f9461faab91a669e0dddb0371d3cab3ed9aeb1267badc797d8375181130e461eadd05099b2a2", - "0x81bdaaf48aae4f7b480fc13f1e7f4dd3023a41439ba231760409ce9292c11128ab2b0bdbbf28b98af4f97b3551f363af", - "0x8d60f9df9fd303f625af90e8272c4ecb95bb94e6efc5da17b8ab663ee3b3f673e9f6420d890ccc94acf4d2cae7a860d8", - "0xa7b75901520c06e9495ab983f70b61483504c7ff2a0980c51115d11e0744683ce022d76e3e09f4e99e698cbd21432a0d", - "0x82956072df0586562fda7e7738226f694e1c73518dd86e0799d2e820d7f79233667192c9236dcb27637e4c65ef19d493", - "0xa586beb9b6ffd06ad200957490803a7cd8c9bf76e782734e0f55e04a3dc38949de75dc607822ec405736c576cf83bca3", - "0xa179a30d00def9b34a7e85607a447eea0401e32ab5abeee1a281f2acd1cf6ec81a178020666f641d9492b1bdf66f05a3", - "0x83e129705c538787ed8e0fdc1275e6466a3f4ee21a1e6abedd239393b1df72244723b92f9d9d9339a0cab6ebf28f5a16", - "0x811bd8d1e3722b64cd2f5b431167e7f91456e8bba2cc669d3fbbce7d553e29c3c19f629fcedd2498bc26d33a24891d17", - "0xa243c030c858f1f60cccd26b45b024698cc6d9d9e6198c1ed4964a235d9f8d0baf9cde10c8e63dfaa47f8e74e51a6e85", - "0xab839eb82e23ca52663281f863b55b0a3d6d4425c33ffb4eeb1d7979488ab068bf99e2a60e82cea4dc42c56c26cbfebe", - "0x8b896f9bb21d49343e67aec6ad175b58c0c81a3ca73d44d113ae4354a0065d98eb1a5cafedaf232a2bb9cdc62152f309", - "0xaf6230340cc0b66f5bf845540ed4fc3e7d6077f361d60762e488d57834c3e7eb7eacc1b0ed73a7d134f174a01410e50c", - "0x88975e1b1af678d1b5179f72300a30900736af580dd748fd9461ef7afccc91ccd9bed33f9da55c8711a7635b800e831f", - "0xa97486bb9047391661718a54b8dd5a5e363964e495eae6c692730264478c927cf3e66dd3602413189a3699fbeae26e15", - "0xa5973c161ab38732885d1d2785fd74bf156ba34881980cba27fe239caef06b24a533ffe6dbbbeca5e6566682cc00300a", - "0xa24776e9a840afda0003fa73b415d5bd6ecd9b5c2cc842b643ee51b8c6087f4eead4d0bfbd987eb174c489a7b952ff2a", - "0xa8a6ee06e3af053b705a12b59777267c546f33ba8a0f49493af8e6df4e15cf8dd2d4fb4daf7e84c6b5d3a7363118ff03", - "0xa28e59ce6ad02c2ce725067c0123117e12ac5a52c8f5af13eec75f4a9efc4f696777db18a374fa33bcae82e0734ebd16", - "0x86dfc3b78e841c708aff677baa8ee654c808e5d257158715097c1025d46ece94993efe12c9d188252ad98a1e0e331fec", - "0xa88d0275510f242eab11fdb0410ff6e1b9d7a3cbd3658333539815f1b450a84816e6613d15aa8a8eb15d87cdad4b27a2", - "0x8440acea2931118a5b481268ff9f180ee4ede85d14a52c026adc882410825b8275caa44aff0b50c2b88d39f21b1a0696", - "0xa7c3182eab25bd6785bacf12079d0afb0a9b165d6ed327814e2177148539f249eb9b5b2554538f54f3c882d37c0a8abe", - "0x85291fbe10538d7da38efdd55a7acebf03b1848428a2f664c3ce55367aece60039f4f320b1771c9c89a35941797f717c", - "0xa2c6414eeb1234728ab0de94aa98fc06433a58efa646ca3fcbd97dbfb8d98ae59f7ce6d528f669c8149e1e13266f69c9", - "0x840c8462785591ee93aee2538d9f1ec44ba2ca61a569ab51d335ac873f5d48099ae8d7a7efa0725d9ff8f9475bfa4f56", - "0xa7065a9d02fb3673acf7702a488fbc01aa69580964932f6f40b6c2d1c386b19e50b0e104fcac24ea26c4e723611d0238", - "0xb72db6d141267438279e032c95e6106c2ccb3164b842ba857a2018f3a35f4b040da92680881eb17cd61d0920d5b8f006", - "0xa8005d6c5960e090374747307ef0be2871a7a43fa4e76a16c35d2baab808e9777b496e9f57a4218b23390887c33a0b55", - "0x8e152cea1e00a451ca47c20a1e8875873419700af15a5f38ee2268d3fbc974d4bd5f4be38008fa6f404dbdedd6e6e710", - "0xa3391aed1fcd68761f06a7d1008ec62a09b1cb3d0203cd04e300a0c91adfed1812d8bc1e4a3fd7976dc0aae0e99f52f1", - "0x967eb57bf2aa503ee0c6e67438098149eac305089c155f1762cf5e84e31f0fbf27c34a9af05621e34645c1ec96afaec8", - "0x88af97ddc4937a95ec0dcd25e4173127260f91c8db2f6eac84afb789b363705fb3196235af631c70cafd09411d233589", - "0xa32df75b3f2c921b8767638fd289bcfc61e08597170186637a7128ffedd52c798c434485ac2c7de07014f9e895c2c3d8", - "0xb0a783832153650aa0d766a3a73ec208b6ce5caeb40b87177ffc035ab03c7705ecdd1090b6456a29f5fb7e90e2fa8930", - "0xb59c8e803b4c3486777d15fc2311b97f9ded1602fa570c7b0200bada36a49ee9ef4d4c1474265af8e1c38a93eb66b18b", - "0x982f2c85f83e852022998ff91bafbb6ff093ef22cf9d5063e083a48b29175ccbd51b9c6557151409e439096300981a6c", - "0x939e3b5989fefebb9d272a954659a4eb125b98c9da6953f5e628d26266bd0525ec38304b8d56f08d65abc4d6da4a8dbb", - "0x8898212fe05bc8de7d18503cb84a1c1337cc2c09d1eeef2b475aa79185b7322bf1f8e065f1bf871c0c927dd19faf1f6d", - "0x94b0393a41cd00f724aee2d4bc72103d626a5aecb4b5486dd1ef8ac27528398edf56df9db5c3d238d8579af368afeb09", - "0x96ac564450d998e7445dd2ea8e3fc7974d575508fa19e1c60c308d83b645864c029f2f6b7396d4ff4c1b24e92e3bac37", - "0x8adf6638e18aff3eb3b47617da696eb6c4bdfbecbbc3c45d3d0ab0b12cbad00e462fdfbe0c35780d21aa973fc150285e", - "0xb53f94612f818571b5565bbb295e74bada9b5f9794b3b91125915e44d6ddcc4da25510eab718e251a09c99534d6042d9", - "0x8b96462508d77ee083c376cd90807aebad8de96bca43983c84a4a6f196d5faf6619a2351f43bfeec101864c3bf255519", - "0xaeadf34657083fc71df33bd44af73bf5281c9ca6d906b9c745536e1819ea90b56107c55e2178ebad08f3ba75b3f81c86", - "0x9784ba29b2f0057b5af1d3ab2796d439b8753f1f749c73e791037461bdfc3f7097394283105b8ab01788ea5255a96710", - "0x8756241bda159d4a33bf74faba0d4594d963c370fb6a18431f279b4a865b070b0547a6d1613cf45b8cfb5f9236bbf831", - "0xb03ebfd6b71421dfd49a30460f9f57063eebfe31b9ceaa2a05c37c61522b35bdc09d7db3ad75c76c253c00ba282d3cd2", - "0xb34e7e6341fa9d854b2d3153bdda0c4ae2b2f442ab7af6f99a0975d45725aa48e36ae5f7011edd249862e91f499687d4", - "0xb462ee09dc3963a14354244313e3444de5cc37ea5ccfbf14cd9aca8027b59c4cb2a949bc30474497cab8123e768460e6", - "0xaea753290e51e2f6a21a9a0ee67d3a2713f95c2a5c17fe41116c87d3aa77b1683761264d704df1ac34f8b873bc88ef7b", - "0x98430592afd414394f98ddfff9f280fcb1c322dbe3510f45e1e9c4bb8ee306b3e0cf0282c0ee73ebb8ba087d4d9e0858", - "0xb95d3b5aaf54ffca11f4be8d57f76e14afdb20afc859dc7c7471e0b42031e8f3d461b726ecb979bdb2f353498dfe95ea", - "0x984d17f9b11a683132e0b5a9ee5945e3ff7054c2d5c716be73b29078db1d36f54c6e652fd2f52a19da313112e97ade07", - "0xab232f756b3fff3262be418a1af61a7e0c95ceebbc775389622a8e10610508cd6784ab7960441917a83cc191c58829ea", - "0xa28f41678d6e60de76b0e36ab10e4516e53e02e9c77d2b5af3cfeee3ce94cfa30c5797bd1daab20c98e1cad83ad0f633", - "0xb55395fca84dd3ccc05dd480cb9b430bf8631ff06e24cb51d54519703d667268c2f8afcde4ba4ed16bece8cc7bc8c6e0", - "0x8a8a5392a0e2ea3c7a8c51328fab11156004e84a9c63483b64e8f8ebf18a58b6ffa8fe8b9d95af0a2f655f601d096396", - "0xab480000fe194d23f08a7a9ec1c392334e9c687e06851f083845121ce502c06b54dda8c43092bcc1035df45cc752fe9b", - "0xb265644c29f628d1c7e8e25a5e845cabb21799371814730a41a363e1bda8a7be50fee7c3996a365b7fcba4642add10db", - "0xb8a915a3c685c2d4728f6931c4d29487cad764c5ce23c25e64b1a3259ac27235e41b23bfe7ae982921b4cb84463097df", - "0x8efa7338442a4b6318145a5440fc213b97869647eeae41b9aa3c0a27ee51285b73e3ae3b4a9423df255e6add58864aa9", - "0x9106d65444f74d217f4187dfc8fcf3810b916d1e4275f94f6a86d1c4f3565b131fd6cde1fa708bc05fe183c49f14941a", - "0x948252dac8026bbbdb0a06b3c9d66ec4cf9532163bab68076fda1bd2357b69e4b514729c15aaa83b5618b1977bbc60c4", - "0xae6596ccfdf5cbbc5782efe3bb0b101bb132dbe1d568854ca24cacc0b2e0e9fabcb2ca7ab42aecec412efd15cf8cb7a2", - "0x84a0b6c198ff64fd7958dfd1b40eac9638e8e0b2c4cd8cf5d8cdf80419baee76a05184bce6c5b635f6bf2d30055476a7", - "0x8893118be4a055c2b3da593dbca51b1ae2ea2469911acfb27ee42faf3e6c3ad0693d3914c508c0b05b36a88c8b312b76", - "0xb097479e967504deb6734785db7e60d1d8034d6ca5ba9552887e937f5e17bb413fccac2c1d1082154ed76609127860ad", - "0xa0294e6b9958f244d29943debf24b00b538b3da1116269b6e452bb12dc742226712fd1a15b9c88195afeb5d2415f505c", - "0xb3cc15f635080bc038f61b615f62b5b5c6f2870586191f59476e8368a73641d6ac2f7d0c1f54621982defdb318020230", - "0x99856f49b9fe1604d917c94d09cc0ed753d13d015d30587a94e6631ffd964b214e607deb8a69a8b5e349a7edf4309206", - "0xa8571e113ea22b4b4fce41a094da8c70de37830ae32e62c65c2fa5ad06a9bc29e884b945e73d448c72b176d6ecebfb58", - "0xa9e9c6e52beb0013273c29844956b3ce291023678107cdc785f7b44eff5003462841ad8780761b86aefc6b734adde7cf", - "0x80a784b0b27edb51ef2bad3aee80e51778dcaa0f3f5d3dcb5dc5d4f4b2cf7ae35b08de6680ea9dac53f8438b92eb09ef", - "0x827b543e609ea328e97e373f70ad72d4915a2d1daae0c60d44ac637231070e164c43a2a58db80a64df1c624a042b38f9", - "0xb449c65e8195202efdcb9bdb4e869a437313b118fef8b510cbbf8b79a4e99376adb749b37e9c20b51b31ed3310169e27", - "0x8ea3028f4548a79a94c717e1ed28ad4d8725b8d6ab18b021063ce46f665c79da3c49440c6577319dab2d036b7e08f387", - "0x897798431cfb17fe39f08f5f854005dc37b1c1ec1edba6c24bc8acb3b88838d0534a75475325a5ea98b326ad47dbad75", - "0x89cf232e6303b0751561960fd4dea5754a28c594daf930326b4541274ffb03c7dd75938e411eb9a375006a70ce38097f", - "0x9727c6ae7f0840f0b6c8bfb3a1a5582ceee705e0b5c59b97def7a7a2283edd4d3f47b7971e902a3a2079e40b53ff69b8", - "0xb76ed72b122c48679d221072efc0eeea063cb205cbf5f9ef0101fd10cb1075b8628166c83577cced654e1c001c7882f7", - "0xae908c42d208759da5ee9b405df85a6532ea35c6f0f6a1288d22870f59d98edc896841b8ac890a538e6c8d1e8b02d359", - "0x809d12fe4039a0ec80dc9be6a89acaab7797e5f7f9b163378f52f9a75a1d73b2e9ae6e3dd49e32ced439783c1cabbef5", - "0xa4149530b7f85d1098ba534d69548c6c612c416e8d35992fc1f64f4deeb41e09e49c6cf7aadbed7e846b91299358fe2d", - "0xa49342eacd1ec1148b8df1e253b1c015f603c39de11fa0a364ccb86ea32d69c34fd7aa6980a1fadcd8e785a57fa46f60", - "0x87d43eff5a006dc4dddcf76cc96c656a1f3a68f19f124181feab86c6cc9a52cb9189cdbb423414defdd9bb0ca8ff1ddc", - "0x861367e87a9aa2f0f68296ba50aa5dbc5713008d260cc2c7e62d407c2063064749324c4e8156dc21b749656cfebce26b", - "0xb5303c2f72e84e170e66ae1b0fbd51b8c7a6f27476eaf5694b64e8737d5c84b51fe90100b256465a4c4156dd873cddb0", - "0xb62849a4f891415d74f434cdc1d23c4a69074487659ca96e1762466b2b7a5d8525b056b891d0feea6fe6845cba8bc7fb", - "0x923dd9e0d6590a9307e8c4c23f13bae3306b580e297a937711a8b13e8de85e41a61462f25b7d352b682e8437bf2b4ab3", - "0x9147379860cd713cd46c94b8cdf75125d36c37517fbecf81ace9680b98ce6291cd1c3e472f84249cc3b2b445e314b1b6", - "0xa808a4f17ac21e3fb5cfef404e61fae3693ca3e688d375f99b6116779696059a146c27b06de3ac36da349b0649befd56", - "0x87787e9322e1b75e66c1f0d9ea0915722a232770930c2d2a95e9478c4b950d15ab767e30cea128f9ed65893bfc2d0743", - "0x9036a6ee2577223be105defe1081c48ea7319e112fff9110eb9f61110c319da25a6cea0464ce65e858635b079691ef1f", - "0xaf5548c7c24e1088c23b57ee14d26c12a83484c9fd9296edf1012d8dcf88243f20039b43c8c548c265ef9a1ffe9c1c88", - "0xa0fff520045e14065965fb8accd17e878d3fcaf9e0af2962c8954e50be6683d31fa0bf4816ab68f08630dbac6bfce52a", - "0xb4c1b249e079f6ae1781af1d97a60b15855f49864c50496c09c91fe1946266915b799f0406084d7783f5b1039116dd8b", - "0x8b0ffa5e7c498cb3879dddca34743b41eee8e2dea3d4317a6e961b58adb699ef0c92400c068d5228881a2b08121226bf", - "0x852ae8b19a1d80aa8ae5382e7ee5c8e7670ceb16640871c56b20b96b66b3b60e00015a3dde039446972e57b49a999ddd", - "0xa49942f04234a7d8492169da232cfff8051df86e8e1ba3db46aede02422c689c87dc1d99699c25f96cb763f5ca0983e5", - "0xb04b597b7760cf5dcf411ef896d1661e6d5b0db3257ac2cf64b20b60c6cc18fa10523bb958a48d010b55bac7b02ab3b1", - "0xa494591b51ea8285daecc194b5e5bd45ae35767d0246ac94fae204d674ee180c8e97ff15f71f28b7aeb175b8aea59710", - "0x97d2624919e78406e7460730680dea8e71c8571cf988e11441aeea54512b95bd820e78562c99372d535d96f7e200d20d", - "0xac693ddb00e48f76e667243b9b6a7008424043fb779e4f2252330285232c3fccac4da25cbd6d95fe9ad959ff305a91f6", - "0x8d20ca0a71a64a3f702a0825bb46bd810d03bebfb227683680d474a52f965716ff99e19a165ebaf6567987f4f9ee3c94", - "0xa5c516a438f916d1d68ca76996404792e0a66e97b7f18fc54c917bf10cf3211b62387932756e39e67e47b0bd6e88385a", - "0xb089614d830abc0afa435034cec7f851f2f095d479cacf1a3fb57272da826c499a52e7dcbc0eb85f4166fb94778e18e9", - "0xa8dacc943765d930848288192f4c69e2461c4b9bc6e79e30eeef9a543318cf9ae9569d6986c65c5668a89d49993f8e07", - "0xab5a9361fa339eec8c621bdad0a58078983abd8942d4282b22835d7a3a47e132d42414b7c359694986f7db39386c2e19", - "0x94230517fb57bd8eb26c6f64129b8b2abd0282323bf7b94b8bac7fab27b4ecc2c4290c294275e1a759de19f2216134f3", - "0xb8f158ea5006bc3b90b285246625faaa6ac9b5f5030dc69701b12f3b79a53ec7e92eeb5a63bbd1f9509a0a3469ff3ffc", - "0x8b6944fd8cb8540957a91a142fdcda827762aa777a31e8810ca6d026e50370ee1636fc351724767e817ca38804ebe005", - "0x82d1ee40fe1569c29644f79fa6c4033b7ed45cd2c3b343881f6eb0de2e79548fded4787fae19bed6ee76ed76ff9f2f11", - "0xa8924c7035e99eaed244ca165607e7e568b6c8085510dcdbaf6ebdbed405af2e6c14ee27d94ffef10d30aa52a60bf66d", - "0x956f82a6c2ae044635e85812581e4866c5fa2f427b01942047d81f6d79a14192f66fbbe77c9ffeaef4e6147097fdd2b5", - "0xb1100255a1bcf5e05b6aff1dfeb6e1d55b5d68d43a7457ba10cc76b61885f67f4d0d5179abda786e037ae95deb8eea45", - "0x99510799025e3e5e8fbf06dedb14c060c6548ba2bda824f687d3999dc395e794b1fb6514b9013f3892b6cf65cb0d65aa", - "0x8f9091cebf5e9c809aab415942172258f894e66e625d7388a05289183f01b8d994d52e05a8e69f784fba41db9ea357f0", - "0xa13d2eeb0776bdee9820ecb6693536720232848c51936bb4ef4fe65588d3f920d08a21907e1fdb881c1ad70b3725e726", - "0xa68b8f18922d550284c5e5dc2dda771f24c21965a6a4d5e7a71678178f46df4d8a421497aad8fcb4c7e241aba26378a0", - "0x8b7601f0a3c6ad27f03f2d23e785c81c1460d60100f91ea9d1cab978aa03b523150206c6d52ce7c7769c71d2c8228e9e", - "0xa8e02926430813caa851bb2b46de7f0420f0a64eb5f6b805401c11c9091d3b6d67d841b5674fa2b1dce0867714124cd8", - "0xb7968ecba568b8193b3058400af02c183f0a6df995a744450b3f7e0af7a772454677c3857f99c140bbdb2a09e832e8e0", - "0x8f20b1e9ba87d0a3f35309b985f3c18d2e8800f1ca7f0c52cadef773f1496b6070c936eea48c4a1cae83fd2524e9d233", - "0x88aef260042db0d641a51f40639dbeeefa9e9811df30bee695f3791f88a2f84d318f04e8926b7f47bf25956cb9e3754f", - "0x9725345893b647e9ba4e6a29e12f96751f1ae25fcaec2173e9a259921a1a7edb7a47159b3c8767e44d9e2689f5aa0f72", - "0x8c281e6f72752cb11e239e4df9341c45106eb7993c160e54423c2bffe10bc39d42624b45a1f673936ef2e1a02fc92f1a", - "0x90aba2f68bddb2fcce6c51430dacdfeec43ea8dc379660c99095df11017691ccf5faa27665cf4b9f0eea7728ae53c327", - "0xb7022695c16521c5704f49b7ddbdbec9b5f57ce0ceebe537bc0ebb0906d8196cc855a9afeb8950a1710f6a654464d93f", - "0x8fe1b9dd3c6a258116415d36e08374e094b22f0afb104385a5da48be17123e86fb8327baacc4f0d9ebae923d55d99bb5", - "0x817e85d8e3d19a4cbc1dec31597142c2daa4871bda89c2177fa719c00eda3344eb08b82eb92d4aa91a9eaacb3fc09783", - "0xb59053e1081d2603f1ca0ba553804d6fa696e1fd996631db8f62087b26a40dfef02098b0326bb75f99ec83b9267ca738", - "0x990a173d857d3ba81ff3789b931bfc9f5609cde0169b7f055fa3cb56451748d593d62d46ba33f80f9cafffe02b68dd14", - "0xb0c538dbba4954b809ab26f9f94a3cf1dcb77ce289eaec1d19f556c0ae4be1fa03af4a9b7057837541c3cc0a80538736", - "0xac3ba42f5f44f9e1fc453ce49c4ab79d0e1d5c42d3b30b1e098f3ab3f414c4c262fa12fb2be249f52d4aaf3c5224beb9", - "0xaf47467eb152e59870e21f0d4da2f43e093daf40180ab01438030684b114d025326928eaab12c41b81a066d94fce8436", - "0x98d1b58ba22e7289b1c45c79a24624f19b1d89e00f778eef327ec4856a9a897278e6f1a9a7e673844b31dde949153000", - "0x97ccb15dfadc7c59dca08cfe0d22df2e52c684cf97de1d94bc00d7ba24e020025130b0a39c0f4d46e4fc872771ee7875", - "0xb699e4ed9a000ff96ca296b2f09dce278832bc8ac96851ff3cff99ed3f6f752cfc0fea8571be28cd9b5a7ec36f1a08ee", - "0xb9f49f0edb7941cc296435ff0a912e3ad16848ee8765ab5f60a050b280d6ea585e5b34051b15f6b8934ef01ceb85f648", - "0xac3893df7b4ceab23c6b9054e48e8ba40d6e5beda8fbe90b814f992f52494186969b35d8c4cdc3c99890a222c9c09008", - "0xa41293ad22fae81dea94467bc1488c3707f3d4765059173980be93995fa4fcc3c9340796e3eed0beeb0ba0d9bb4fa3aa", - "0xa0543e77acd2aeecde13d18d258aeb2c7397b77f17c35a1992e8666ea7abcd8a38ec6c2741bd929abba2f766138618cc", - "0x92e79b22bc40e69f6527c969500ca543899105837b6b1075fa1796755c723462059b3d1b028e0b3df2559fa440e09175", - "0xa1fa1eac8f41a5197a6fb4aa1eae1a031c89f9c13ff9448338b222780cf9022e0b0925d930c37501a0ef7b2b00fdaf83", - "0xb3cb29ff73229f0637335f28a08ad8c5f166066f27c6c175164d0f26766a927f843b987ee9b309ed71cbf0a65d483831", - "0x84d4ab787f0ac00f104f4a734dc693d62d48c2aeb03913153da62c2ae2c27d11b1110dcef8980368dd84682ea2c1a308", - "0xab6a8e4bbc78d4a7b291ad3e9a8fe2d65f640524ba3181123b09d2d18a9e300e2509ccf7000fe47e75b65f3e992a2e7e", - "0xb7805ebe4f1a4df414003dc10bca805f2ab86ca75820012653e8f9b79c405196b0e2cab099f2ab953d67f0d60d31a0f9", - "0xb12c582454148338ea605d22bd00a754109063e22617f1f8ac8ddf5502c22a181c50c216c3617b9852aa5f26af56b323", - "0x86333ad9f898947e31ce747728dc8c887479e18d36ff3013f69ebef807d82c6981543b5c3788af93c4d912ba084d3cba", - "0xb514efa310dc4ad1258add138891e540d8c87142a881b5f46563cc58ecd1488e6d3a2fca54c0b72a929f3364ca8c333e", - "0xaa0a30f92843cf2f484066a783a1d75a7aa6f41f00b421d4baf20a6ac7886c468d0eea7ca8b17dd22f4f74631b62b640", - "0xb3b7dc63baec9a752e8433c0cdee4d0f9bc41f66f2b8d132faf925eef9cf89aae756fc132c45910f057122462605dc10", - "0xb9b8190dac5bfdeb59fd44f4da41a57e7f1e7d2c21faba9da91fa45cbeca06dcf299c9ae22f0c89ece11ac46352d619f", - "0x89f8cf36501ad8bdfeab863752a9090e3bfda57cf8fdeca2944864dc05925f501e252c048221bcc57136ab09a64b64b2", - "0xb0cbfaf317f05f97be47fc9d69eda2dd82500e00d42612f271a1fe24626408c28881f171e855bd5bd67409f9847502b4", - "0xa7c21a8fcede581bfd9847b6835eda62ba250bea81f1bb17372c800a19c732abe03064e64a2f865d974fb636cab4b859", - "0x95f9df524ba7a4667351696c4176b505d8ea3659f5ff2701173064acc624af69a0fad4970963736383b979830cb32260", - "0x856a74fe8b37a2e3afeac858c8632200485d438422a16ae3b29f359e470e8244995c63ad79c7e007ed063f178d0306fd", - "0xb37faa4d78fdc0bb9d403674dbea0176c2014a171c7be8527b54f7d1a32a76883d3422a3e7a5f5fcc5e9b31b57822eeb", - "0x8d37234d8594ec3fe75670b5c9cc1ec3537564d4739b2682a75b18b08401869a4264c0f264354219d8d896cded715db4", - "0xb5289ee5737f0e0bde485d32096d23387d68dab8f01f47821ab4f06cc79a967afe7355e72dc0c751d96b2747b26f6255", - "0x9085e1fdf9f813e9c3b8232d3c8863cd84ab30d45e8e0d3d6a0abd9ebc6fd70cdf749ff4d04390000e14c7d8c6655fc7", - "0x93a388c83630331eca4da37ea4a97b3b453238af474817cc0a0727fd3138dcb4a22de38c04783ec829c22cb459cb4e8e", - "0xa5377116027c5d061dbe24c240b891c08cdd8cd3f0899e848d682c873aff5b8132c1e7cfe76d2e5ed97ee0eb1d42cb68", - "0xa274c84b04338ed28d74683e2a7519c2591a3ce37c294d6f6e678f7d628be2db8eff253ede21823e2df7183e6552f622", - "0x8bc201147a842453a50bec3ac97671397bc086d6dfc9377fa38c2124cdc286abda69b7324f47d64da094ae011d98d9d9", - "0x9842d0c066c524592b76fbec5132bc628e5e1d21c424bec4555efca8619cc1fd8ea3161febcb8b9e8ab54702f4e815e2", - "0xa19191b713a07efe85c266f839d14e25660ee74452e6c691cd9997d85ae4f732052d802d3deb018bdd847caa298a894b", - "0xa24f71fc0db504da4e287dd118a4a74301cbcd16033937ba2abc8417956fcb4ae19b8e63b931795544a978137eff51cb", - "0xa90eec4a6a3a4b8f9a5b93d978b5026fcf812fe65585b008d7e08c4aaf21195a1d0699f12fc16f79b6a18a369af45771", - "0x8b551cf89737d7d06d9b3b9c4c1c73b41f2ea0af4540999c70b82dabff8580797cf0a3caf34c86c59a7069eb2e38f087", - "0xb8d312e6c635e7a216a1cda075ae77ba3e1d2fd501dc31e83496e6e81ed5d9c7799f8e578869c2e0e256fb29f5de10a7", - "0x8d144bdb8cae0b2cdb5b33d44bbc96984a5925202506a8cc65eb67ac904b466f5a7fe3e1cbf04aa785bbb7348c4bb73c", - "0xa101b3d58b7a98659244b88de0b478b3fb87dc5fc6031f6e689b99edf498abd43e151fd32bd4bbd240e0b3e59c440359", - "0x907453abca7d8e7151a05cc3d506c988007692fe7401395dc93177d0d07d114ab6cca0cc658eb94c0223fe8658295cad", - "0x825329ffbe2147ddb68f63a0a67f32d7f309657b8e5d9ab5bb34b3730bfa2c77a23eaaadb05def7d9f94a9e08fdc1e96", - "0x88ee923c95c1dac99ae7ed6067906d734d793c5dc5d26339c1bb3314abe201c5dccb33b9007351885eb2754e9a8ea06c", - "0x98bc9798543f5f1adc9f2cfcfa72331989420e9c3f6598c45269f0dc9b7c8607bbeaf03faa0aea2ddde2b8f17fdceff5", - "0x8ee87877702a79aef923ab970db6fa81561b3c07d5bf1a072af0a7bad765b4cbaec910afe1a91703feacc7822fa38a94", - "0x8060b9584aa294fe8adc2b22f67e988bc6da768eae91e429dcc43ddc53cfcc5d6753fdc1b420b268c7eb2fb50736a970", - "0xb344a5524d80a2f051870c7001f74fcf348a70fcf78dbd20c6ff9ca85d81567d2318c8b8089f2c4f195d6aec9fc15fa6", - "0x8f5a5d893e1936ed062149d20eb73d98b62b7f50ab5d93a6429c03656b36688d1c80cb5010e4977491e51fa0d7dd35d5", - "0x86fa32ebbf97328c5f5f15564e1238297e289ec3219b9a741724e9f3ae8d5c15277008f555863a478b247ba5dc601d44", - "0x9557e55377e279f4b6b5e0ffe01eca037cc13aac242d67dfcd0374a1e775c5ed5cb30c25fe21143fee54e3302d34a3ea", - "0x8cb6bcbc39372d23464a416ea7039f57ba8413cf3f00d9a7a5b356ab20dcb8ed11b3561f7bce372b8534d2870c7ee270", - "0xb5d59075cb5abde5391f64b6c3b8b50adc6e1f654e2a580b6d6d6eff3f4fbdd8fffc92e06809c393f5c8eab37f774c4b", - "0xafcfb6903ef13e493a1f7308675582f15af0403b6553e8c37afb8b2808ad21b88b347dc139464367dc260df075fea1ad", - "0x810fbbe808375735dd22d5bc7fc3828dc49fdd22cc2d7661604e7ac9c4535c1df578780affb3b895a0831640a945bcad", - "0x8056b0c678803b416f924e09a6299a33cf9ad7da6fe1ad7accefe95c179e0077da36815fde3716711c394e2c5ea7127f", - "0x8b67403702d06979be19f1d6dc3ec73cc2e81254d6b7d0cc49cd4fdda8cd51ab0835c1d2d26fc0ecab5df90585c2f351", - "0x87f97f9e6d4be07e8db250e5dd2bffdf1390665bc5709f2b631a6fa69a7fca958f19bd7cc617183da1f50ee63e9352b5", - "0xae151310985940471e6803fcf37600d7fa98830613e381e00dab943aec32c14162d51c4598e8847148148000d6e5af5c", - "0x81eb537b35b7602c45441cfc61b27fa9a30d3998fad35a064e05bc9479e9f10b62eba2b234b348219eea3cadcaac64bb", - "0x8a441434934180ab6f5bc541f86ebd06eadbee01f438836d797e930fa803a51510e005c9248cecc231a775b74d12b5e9", - "0x81f3c250a27ba14d8496a5092b145629eb2c2e6a5298438670375363f57e2798207832c8027c3e9238ad94ecdadfc4df", - "0xa6217c311f2f3db02ceaa5b6096849fe92b6f4b6f1491535ef8525f6ccee6130bed2809e625073ecbaddd4a3eb3df186", - "0x82d1c396f0388b942cf22b119d7ef1ad03d3dad49a74d9d01649ee284f377c8daddd095d596871669e16160299a210db", - "0xa40ddf7043c5d72a7246bd727b07f7fff1549f0e443d611de6f9976c37448b21664c5089c57f20105102d935ab82f27b", - "0xb6c03c1c97adf0c4bf4447ec71366c6c1bff401ba46236cd4a33d39291e7a1f0bb34bd078ba3a18d15c98993b153a279", - "0x8a94f5f632068399c359c4b3a3653cb6df2b207379b3d0cdace51afdf70d6d5cce6b89a2b0fee66744eba86c98fb21c2", - "0xb2f19e78ee85073f680c3bba1f07fd31b057c00b97040357d97855b54a0b5accb0d3b05b2a294568fcd6a4be6f266950", - "0xa74632d13bbe2d64b51d7a9c3ae0a5a971c19f51cf7596a807cea053e6a0f3719700976d4e394b356c0329a2dced9aa2", - "0xafef616d341a9bc94393b8dfba68ff0581436aa3a3adb7c26a1bbf2cf19fa877066191681f71f17f3cd6f9cf6bf70b5a", - "0x8ce96d93ae217408acf7eb0f9cbb9563363e5c7002e19bbe1e80760bc9d449daee2118f3878b955163ed664516b97294", - "0x8414f79b496176bc8b8e25f8e4cfee28f4f1c2ddab099d63d2aca1b6403d26a571152fc3edb97794767a7c4686ad557c", - "0xb6c61d01fd8ce087ef9f079bf25bf10090db483dd4f88c4a786d31c1bdf52065651c1f5523f20c21e75cea17df69ab73", - "0xa5790fd629be70545093631efadddc136661f63b65ec682609c38ef7d3d7fa4e56bdf94f06e263bc055b90cb1c6bcefe", - "0xb515a767e95704fb7597bca9e46f1753abacdc0e56e867ee3c6f4cd382643c2a28e65312c05ad040eaa3a8cbe7217a65", - "0x8135806a02ead6aa92e9adb6fefb91349837ab73105aaa7be488ef966aa8dfaafdfa64bbae30fcbfa55dd135a036a863", - "0x8f22435702716d76b1369750694540742d909d5e72b54d0878245fab7c269953b1c6f2b29c66f08d5e0263ca3a731771", - "0x8e0f8a8e8753e077dac95848212aeffd51c23d9b6d611df8b102f654089401954413ecbedc6367561ca599512ae5dda7", - "0x815a9084e3e2345f24c5fa559deec21ee1352fb60f4025c0779be65057f2d528a3d91593bd30d3a185f5ec53a9950676", - "0x967e6555ccba395b2cc1605f8484c5112c7b263f41ce8439a99fd1c71c5ed14ad02684d6f636364199ca48afbbde13be", - "0x8cd0ccf17682950b34c796a41e2ea7dd5367aba5e80a907e01f4cdc611e4a411918215e5aebf4292f8b24765d73314a6", - "0xa58bf1bbb377e4b3915df6f058a0f53b8fb8130fdec8c391f6bc82065694d0be59bb67ffb540e6c42cc8b380c6e36359", - "0x92af3151d9e6bfb3383d85433e953c0160859f759b0988431ec5893542ba40288f65db43c78a904325ef8d324988f09d", - "0x8011bbb05705167afb47d4425065630f54cb86cd462095e83b81dfebf348f846e4d8fbcf1c13208f5de1931f81da40b9", - "0x81c743c104fc3cb047885c9fa0fb9705c3a83ee24f690f539f4985509c3dafd507af3f6a2128276f45d5939ef70c167f", - "0xa2c9679b151c041aaf5efeac5a737a8f70d1631d931609fca16be1905682f35e291292874cb3b03f14994f98573c6f44", - "0xa4949b86c4e5b1d5c82a337e5ce6b2718b1f7c215148c8bfb7e7c44ec86c5c9476048fc5c01f57cb0920876478c41ad6", - "0x86c2495088bd1772152e527a1da0ef473f924ea9ab0e5b8077df859c28078f73c4e22e3a906b507fdf217c3c80808b5c", - "0x892e0a910dcf162bcea379763c3e2349349e4cda9402949255ac4a78dd5a47e0bf42f5bd0913951576b1d206dc1e536a", - "0xa7009b2c6b396138afe4754b7cc10dee557c51c7f1a357a11486b3253818531f781ea8107360c8d4c3b1cd96282353c0", - "0x911763ef439c086065cc7b4e57484ed6d693ea44acee4b18c9fd998116da55fbe7dcb8d2a0f0f9b32132fca82d73dff6", - "0xa722000b95a4a2d40bed81870793f15ba2af633f9892df507f2842e52452e02b5ea8dea6a043c2b2611d82376e33742a", - "0x9387ac49477bd719c2f92240d0bdfcf9767aad247ca93dc51e56106463206bc343a8ec855eb803471629a66fffb565d6", - "0x92819a1fa48ab4902939bb72a0a4e6143c058ea42b42f9bc6cea5df45f49724e2530daf3fc4f097cceefa2a8b9db0076", - "0x98eac7b04537653bc0f4941aae732e4b1f84bd276c992c64a219b8715eb1fb829b5cbd997d57feb15c7694c468f95f70", - "0xb275e7ba848ce21bf7996e12dbeb8dadb5d0e4f1cb5a0248a4f8f9c9fe6c74e3c93f4b61edbcb0a51af5a141e1c14bc7", - "0x97243189285aba4d49c53770c242f2faf5fd3914451da4931472e3290164f7663c726cf86020f8f181e568c72fd172d1", - "0x839b0b3c25dd412bee3dc24653b873cc65454f8f16186bb707bcd58259c0b6765fa4c195403209179192a4455c95f3b8", - "0x8689d1a870514568a074a38232e2ceb4d7df30fabeb76cff0aed5b42bf7f02baea12c5fadf69f4713464dbd52aafa55f", - "0x8958ae7b290f0b00d17c3e9fdb4dbf168432b457c7676829299dd428984aba892de1966fc106cfc58a772862ecce3976", - "0xa422bc6bd68b8870cfa5bc4ce71781fd7f4368b564d7f1e0917f6013c8bbb5b240a257f89ecfdbecb40fe0f3aa31d310", - "0xaa61f78130cebe09bc9a2c0a37f0dd57ed2d702962e37d38b1df7f17dc554b1d4b7a39a44182a452ce4c5eb31fa4cfcc", - "0xb7918bd114f37869bf1a459023386825821bfadce545201929d13ac3256d92a431e34f690a55d944f77d0b652cefeffc", - "0x819bba35fb6ace1510920d4dcff30aa682a3c9af9022e287751a6a6649b00c5402f14b6309f0aeef8fce312a0402915e", - "0x8b7c9ad446c6f63c11e1c24e24014bd570862b65d53684e107ba9ad381e81a2eaa96731b4b33536efd55e0f055071274", - "0x8fe79b53f06d33386c0ec7d6d521183c13199498594a46d44a8a716932c3ec480c60be398650bbfa044fa791c4e99b65", - "0x9558e10fb81250b9844c99648cf38fa05ec1e65d0ccbb18aa17f2d1f503144baf59d802c25be8cc0879fff82ed5034ad", - "0xb538a7b97fbd702ba84645ca0a63725be1e2891c784b1d599e54e3480e4670d0025526674ef5cf2f87dddf2290ba09f0", - "0x92eafe2e869a3dd8519bbbceb630585c6eb21712b2f31e1b63067c0acb5f9bdbbcbdb612db4ea7f9cc4e7be83d31973f", - "0xb40d21390bb813ab7b70a010dff64c57178418c62685761784e37d327ba3cb9ef62df87ecb84277c325a637fe3709732", - "0xb349e6fbf778c4af35fbed33130bd8a7216ed3ba0a79163ebb556e8eb8e1a7dad3456ddd700dad9d08d202491c51b939", - "0xa8fdaedecb251f892b66c669e34137f2650509ade5d38fbe8a05d9b9184bb3b2d416186a3640429bd1f3e4b903c159dd", - "0xac6167ebfee1dbab338eff7642f5e785fc21ef0b4ddd6660333fe398068cbd6c42585f62e81e4edbb72161ce852a1a4f", - "0x874b1fbf2ebe140c683bd7e4e0ab017afa5d4ad38055aaa83ee6bbef77dbc88a6ce8eb0dcc48f0155244af6f86f34c2d", - "0x903c58e57ddd9c446afab8256a6bb6c911121e6ccfb4f9b4ed3e2ed922a0e500a5cb7fa379d5285bc16e11dac90d1fda", - "0x8dae7a0cffa2fd166859cd1bf10ff82dd1932e488af377366b7efc0d5dec85f85fe5e8150ff86a79a39cefc29631733a", - "0xaa047857a47cc4dfc08585f28640420fcf105b881fd59a6cf7890a36516af0644d143b73f3515ab48faaa621168f8c31", - "0x864508f7077c266cc0cb3f7f001cb6e27125ebfe79ab57a123a8195f2e27d3799ff98413e8483c533b46a816a3557f1f", - "0x8bcd45ab1f9cbab36937a27e724af819838f66dfeb15923f8113654ff877bd8667c54f6307aaf0c35027ca11b6229bfd", - "0xb21aa34da9ab0a48fcfdd291df224697ce0c1ebc0e9b022fdee8750a1a4b5ba421c419541ed5c98b461eecf363047471", - "0xa9a18a2ab2fae14542dc336269fe612e9c1af6cf0c9ac933679a2f2cb77d3c304114f4d219ca66fe288adde30716775b", - "0xb5205989b92c58bdda71817f9a897e84100b5c4e708de1fced5c286f7a6f01ae96b1c8d845f3a320d77c8e2703c0e8b1", - "0xa364059412bbcc17b8907d43ac8e5df90bc87fd1724b5f99832d0d24559fae6fa76a74cff1d1eac8cbac6ec80b44af20", - "0xae709f2c339886b31450834cf29a38b26eb3b0779bd77c9ac269a8a925d1d78ea3837876c654b61a8fe834b3b6940808", - "0x8802581bba66e1952ac4dab36af371f66778958f4612901d95e5cac17f59165e6064371d02de8fb6fccf89c6dc8bd118", - "0xa313252df653e29c672cbcfd2d4f775089cb77be1077381cf4dc9533790e88af6cedc8a119158e7da5bf6806ad9b91a1", - "0x992a065b4152c7ef11515cd54ba9d191fda44032a01aed954acff3443377ee16680c7248d530b746b8c6dee2d634e68c", - "0xb627b683ee2b32c1ab4ccd27b9f6cce2fe097d96386fa0e5c182ad997c4c422ab8dfc03870cd830b8c774feb66537282", - "0xb823cf8a9aee03dadd013eb9efe40a201b4b57ef67efaae9f99683005f5d1bf55e950bf4af0774f50859d743642d3fea", - "0xb8a7449ffac0a3f206677097baf7ce00ca07a4d2bd9b5356fbcb83f3649b0fda07cfebad220c1066afba89e5a52abf4b", - "0xb2dd1a2f986395bb4e3e960fbbe823dbb154f823284ebc9068502c19a7609790ec0073d08bfa63f71e30c7161b6ef966", - "0x98e5236de4281245234f5d40a25b503505af140b503a035fc25a26159a9074ec81512b28f324c56ea2c9a5aa7ce90805", - "0x89070847dc8bbf5bc4ed073aa2e2a1f699cf0c2ca226f185a0671cecc54e7d3e14cd475c7752314a7a8e7476829da4bc", - "0xa9402dc9117fdb39c4734c0688254f23aed3dce94f5f53f5b7ef2b4bf1b71a67f85ab1a38ec224a59691f3bee050aeb3", - "0x957288f9866a4bf56a4204218ccc583f717d7ce45c01ea27142a7e245ad04a07f289cc044f8cf1f21d35e67e39299e9c", - "0xb2fb31ccb4e69113763d7247d0fc8edaae69b550c5c56aecacfd780c7217dc672f9fb7496edf4aba65dacf3361268e5b", - "0xb44a4526b2f1d6eb2aa8dba23bfa385ff7634572ab2afddd0546c3beb630fbfe85a32f42dd287a7fec069041411537f7", - "0x8db5a6660c3ac7fd7a093573940f068ee79a82bc17312af900b51c8c439336bc86ca646c6b7ab13aaaa008a24ca508ab", - "0x8f9899a6d7e8eb4367beb5c060a1f8e94d8a21099033ae582118477265155ba9e72176a67f7f25d7bad75a152b56e21a", - "0xa67de0e91ade8d69a0e00c9ff33ee2909b8a609357095fa12319e6158570c232e5b6f4647522efb7345ce0052aa9d489", - "0x82eb2414898e9c3023d57907a2b17de8e7eea5269029d05a94bfd7bf5685ac4a799110fbb375eb5e0e2bd16acf6458ae", - "0x94451fc7fea3c5a89ba701004a9693bab555cb622caf0896b678faba040409fdfd14a978979038b2a81e8f0abc4994d2", - "0xac879a5bb433998e289809a4a966bd02b4bf6a9c1cc276454e39c886efcf4fc68baebed575826bde577ab5aa71d735a9", - "0x880c0f8f49c875dfd62b4ddedde0f5c8b19f5687e693717f7e5c031bc580e58e13ab497d48b4874130a18743c59fdce3", - "0xb582af8d8ff0bf76f0a3934775e0b54c0e8fed893245d7d89cae65b03c8125b7237edc29dc45b4fe1a3fe6db45d280ee", - "0x89f337882ed3ae060aaee98efa20d79b6822bde9708c1c5fcee365d0ec9297f694cae37d38fd8e3d49717c1e86f078e7", - "0x826d2c1faea54061848b484e288a5f4de0d221258178cf87f72e14baaa4acc21322f8c9eab5dde612ef497f2d2e1d60b", - "0xa5333d4f227543e9cd741ccf3b81db79f2f03ca9e649e40d6a6e8ff9073e06da83683566d3b3c8d7b258c62970fb24d1", - "0xa28f08c473db06aaf4c043a2fae82b3c8cfaa160bce793a4c208e4e168fb1c65115ff8139dea06453c5963d95e922b94", - "0x8162546135cc5e124e9683bdfaa45833c18553ff06a0861c887dc84a5b12ae8cd4697f6794c7ef6230492c32faba7014", - "0xb23f0d05b74c08d6a7df1760792be83a761b36e3f8ae360f3c363fb196e2a9dd2de2e492e49d36561366e14daa77155c", - "0xb6f70d6c546722d3907c708d630dbe289771d2c8bf059c2e32b77f224696d750b4dda9b3a014debda38e7d02c9a77585", - "0x83bf4c4a9f3ca022c631017e7a30ea205ba97f7f5927cba8fc8489a4646eac6712cb821c5668c9ffe94d69d524374a27", - "0xb0371475425a8076d0dd5f733f55aabbe42d20a7c8ea7da352e736d4d35a327b2beb370dfcb05284e22cfd69c5f6c4cc", - "0xa0031ba7522c79211416c2cca3aa5450f96f8fee711552a30889910970ba13608646538781a2c08b834b140aadd7166f", - "0x99d273c80c7f2dc6045d4ed355d9fc6f74e93549d961f4a3b73cd38683f905934d359058cd1fc4da8083c7d75070487f", - "0xb0e4b0efa3237793e9dcce86d75aafe9879c5fa23f0d628649aef2130454dcf72578f9bf227b9d2b9e05617468e82588", - "0xa5ab076fa2e1c5c51f3ae101afdd596ad9d106bba7882b359c43d8548b64f528af19afa76cd6f40da1e6c5fca4def3fa", - "0x8ce2299e570331d60f6a6eff1b271097cd5f1c0e1113fc69b89c6a0f685dabea3e5bc2ac6bd789aa492ab189f89be494", - "0x91b829068874d911a310a5f9dee001021f97471307b5a3de9ec336870ec597413e1d92010ce320b619f38bed7c4f7910", - "0xb14fe91f4b07bf33b046e9285b66cb07927f3a8da0af548ac2569b4c4fb1309d3ced76d733051a20814e90dd5b75ffd1", - "0xabaab92ea6152d40f82940277c725aa768a631ee0b37f5961667f82fb990fc11e6d3a6a2752b0c6f94563ed9bb28265c", - "0xb7fe28543eca2a716859a76ab9092f135337e28109544f6bd2727728d0a7650428af5713171ea60bfc273d1c821d992c", - "0x8a4917b2ab749fc7343fc64bdf51b6c0698ff15d740cc7baf248c030475c097097d5a473bcc00d8c25817563fe0447b4", - "0xaa96156d1379553256350a0a3250166add75948fb9cde62aa555a0a9dc0a9cb7f2f7b8428aff66097bf6bfedaf14bbe2", - "0xae4ffeb9bdc76830d3eca2b705f30c1bdede6412fa064260a21562c8850c7fb611ec62bc68479fe48f692833e6f66d8d", - "0xb96543caaba9d051600a14997765d49e4ab10b07c7a92cccf0c90b309e6da334fdd6d18c96806cbb67a7801024fbd3c7", - "0x97b2b9ad76f19f500fcc94ca8e434176249f542ac66e5881a3dccd07354bdab6a2157018b19f8459437a68d8b86ba8e0", - "0xa8d206f6c5a14c80005849474fde44b1e7bcf0b2d52068f5f97504c3c035b09e65e56d1cf4b5322791ae2c2fdbd61859", - "0x936bad397ad577a70cf99bf9056584a61bd7f02d2d5a6cf219c05d770ae30a5cd902ba38366ce636067fc1dd10108d31", - "0xa77e30195ee402b84f3882e2286bf5380c0ed374a112dbd11e16cef6b6b61ab209d4635e6f35cdaaa72c1a1981d5dabe", - "0xa46ba4d3947188590a43c180757886a453a0503f79cc435322d92490446f37419c7b999fdf868a023601078070e03346", - "0x80d8d4c5542f223d48240b445d4d8cf6a75d120b060bc08c45e99a13028b809d910b534d2ac47fb7068930c54efd8da9", - "0x803be9c68c91b42b68e1f55e58917a477a9a6265e679ca44ee30d3eb92453f8c89c64eafc04c970d6831edd33d066902", - "0xb14b2b3d0dfe2bb57cee4cd72765b60ac33c1056580950be005790176543826c1d4fbd737f6cfeada6c735543244ab57", - "0xa9e480188bba1b8fb7105ff12215706665fd35bf1117bacfb6ab6985f4dbc181229873b82e5e18323c2b8f5de03258e0", - "0xa66a0f0779436a9a3999996d1e6d3000f22c2cac8e0b29cddef9636393c7f1457fb188a293b6c875b05d68d138a7cc4a", - "0x848397366300ab40c52d0dbbdafbafef6cd3dadf1503bb14b430f52bb9724188928ac26f6292a2412bc7d7aa620763c8", - "0x95466cc1a78c9f33a9aaa3829a4c8a690af074916b56f43ae46a67a12bb537a5ac6dbe61590344a25b44e8512355a4a7", - "0x8b5f7a959f818e3baf0887f140f4575cac093d0aece27e23b823cf421f34d6e4ff4bb8384426e33e8ec7b5eed51f6b5c", - "0x8d5e1368ec7e3c65640d216bcc5d076f3d9845924c734a34f3558ac0f16e40597c1a775a25bf38b187213fbdba17c93b", - "0xb4647c1b823516880f60d20c5cc38c7f80b363c19d191e8992226799718ee26b522a12ecb66556ed3d483aa4824f3326", - "0xac3abaea9cd283eb347efda4ed9086ea3acf495043e08d0d19945876329e8675224b685612a6badf8fd72fb6274902b1", - "0x8eae1ce292d317aaa71bcf6e77e654914edd5090e2e1ebab78b18bb41b9b1bc2e697439f54a44c0c8aa0d436ebe6e1a9", - "0x94dc7d1aec2c28eb43d93b111fa59aaa0d77d5a09501220bd411768c3e52208806abf973c6a452fd8292ff6490e0c9e2", - "0x8fd8967f8e506fef27d17b435d6b86b232ec71c1036351f12e6fb8a2e12daf01d0ee04451fb944d0f1bf7fd20e714d02", - "0x824e6865be55d43032f0fec65b3480ea89b0a2bf860872237a19a54bc186a85d2f8f9989cc837fbb325b7c72d9babe2c", - "0x8bd361f5adb27fd6f4e3f5de866e2befda6a8454efeb704aacc606f528c03f0faae888f60310e49440496abd84083ce2", - "0xb098a3c49f2aaa28b6b3e85bc40ce6a9cdd02134ee522ae73771e667ad7629c8d82c393fba9f27f5416986af4c261438", - "0xb385f5ca285ff2cfe64dcaa32dcde869c28996ed091542600a0b46f65f3f5a38428cca46029ede72b6cf43e12279e3d3", - "0x8196b03d011e5be5288196ef7d47137d6f9237a635ab913acdf9c595fa521d9e2df722090ec7eb0203544ee88178fc5f", - "0x8ed1270211ef928db18e502271b7edf24d0bbd11d97f2786aee772d70c2029e28095cf8f650b0328cc8a4c38d045316d", - "0xa52ab60e28d69b333d597a445884d44fd2a7e1923dd60f763951e1e45f83e27a4dac745f3b9eff75977b3280e132c15d", - "0x91e9fe78cdac578f4a4687f71b800b35da54b824b1886dafec073a3c977ce7a25038a2f3a5b1e35c2c8c9d1a7312417c", - "0xa42832173f9d9491c7bd93b21497fbfa4121687cd4d2ab572e80753d7edcbb42cfa49f460026fbde52f420786751a138", - "0x97b947126d84dcc70c97be3c04b3de3f239b1c4914342fa643b1a4bb8c4fe45c0fcb585700d13a7ed50784790c54bef9", - "0x860e407d353eac070e2418ef6cb80b96fc5f6661d6333e634f6f306779651588037be4c2419562c89c61f9aa2c4947f5", - "0xb2c9d93c3ba4e511b0560b55d3501bf28a510745fd666b3cb532db051e6a8617841ea2f071dda6c9f15619c7bfd2737f", - "0x8596f4d239aeeac78311207904d1bd863ef68e769629cc379db60e019aaf05a9d5cd31dc8e630b31e106a3a93e47cbc5", - "0x8b26e14e2e136b65c5e9e5c2022cee8c255834ea427552f780a6ca130a6446102f2a6f334c3f9a0308c53df09e3dba7e", - "0xb54724354eb515a3c8bed0d0677ff1db94ac0a07043459b4358cb90e3e1aa38ac23f2caa3072cf9647275d7cd61d0e80", - "0xb7ce9fe0e515e7a6b2d7ddcb92bc0196416ff04199326aea57996eef8c5b1548bd8569012210da317f7c0074691d01b7", - "0xa1a13549c82c877253ddefa36a29ea6a23695ee401fdd48e65f6f61e5ebd956d5e0edeff99484e9075cb35071fec41e2", - "0x838ba0c1e5bd1a6da05611ff1822b8622457ebd019cb065ece36a2d176bd2d889511328120b8a357e44569e7f640c1e6", - "0xb916eccff2a95519400bbf76b5f576cbe53cf200410370a19d77734dc04c05b585cfe382e8864e67142d548cd3c4c2f4", - "0xa610447cb7ca6eea53a6ff1f5fe562377dcb7f4aaa7300f755a4f5e8eba61e863c51dc2aa9a29b35525b550fbc32a0fe", - "0x9620e8f0f0ee9a4719aa9685eeb1049c5c77659ba6149ec4c158f999cfd09514794b23388879931fe26fea03fa471fd3", - "0xa9dcf8b679e276583cf5b9360702a185470d09aea463dc474ee9c8aee91ef089dacb073e334e47fbc78ec5417c90465c", - "0x8c9adee8410bdd99e5b285744cee61e2593b6300ff31a8a83b0ec28da59475a5c6fb9346fe43aadea2e6c3dad2a8e30a", - "0x97d5afe9b3897d7b8bb628b7220cf02d8ee4e9d0b78f5000d500aaf4c1df9251aaaabfd1601626519f9d66f00a821d4e", - "0x8a382418157b601ce4c3501d3b8409ca98136a4ef6abcbf62885e16e215b76b035c94d149cc41ff92e42ccd7c43b9b3d", - "0xb64b8d11fb3b01abb2646ac99fdb9c02b804ce15d98f9fe0fbf1c9df8440c71417487feb6cdf51e3e81d37104b19e012", - "0x849d7d044f9d8f0aab346a9374f0b3a5d14a9d1faa83dbacccbdc629ad1ef903a990940255564770537f8567521d17f0", - "0x829dbb0c76b996c2a91b4cbbe93ba455ca0d5729755e5f0c92aaee37dff7f36fcdc06f33aca41f1b609c784127b67d88", - "0x85a7c0069047b978422d264d831ab816435f63938015d2e977222b6b5746066c0071b7f89267027f8a975206ed25c1b0", - "0x84b9fbc1cfb302df1acdcf3dc5d66fd1edfe7839f7a3b2fb3a0d5548656249dd556104d7c32b73967bccf0f5bdcf9e3b", - "0x972220ac5b807f53eac37dccfc2ad355d8b21ea6a9c9b011c09fe440ddcdf7513e0b43d7692c09ded80d7040e26aa28f", - "0x855885ed0b21350baeca890811f344c553cf9c21024649c722453138ba29193c6b02c4b4994cd414035486f923472e28", - "0x841874783ae6d9d0e59daea03e96a01cbbe4ecaced91ae4f2c8386e0d87b3128e6d893c98d17c59e4de1098e1ad519dd", - "0x827e50fc9ce56f97a4c3f2f4cbaf0b22f1c3ce6f844ff0ef93a9c57a09b8bf91ebfbd2ba9c7f83c442920bffdaf288cc", - "0xa441f9136c7aa4c08d5b3534921b730e41ee91ab506313e1ba5f7c6f19fd2d2e1594e88c219834e92e6fb95356385aa7", - "0x97d75b144471bf580099dd6842b823ec0e6c1fb86dd0da0db195e65524129ea8b6fd4a7a9bbf37146269e938a6956596", - "0xa4b6fa87f09d5a29252efb2b3aaab6b3b6ea9fab343132a651630206254a25378e3e9d6c96c3d14c150d01817d375a8e", - "0xa31a671876d5d1e95fe2b8858dc69967231190880529d57d3cab7f9f4a2b9b458ac9ee5bdaa3289158141bf18f559efb", - "0x90bee6fff4338ba825974021b3b2a84e36d617e53857321f13d2b3d4a28954e6de3b3c0e629d61823d18a9763313b3bf", - "0x96b622a63153f393bb419bfcf88272ea8b3560dbd46b0aa07ada3a6223990d0abdd6c2adb356ef4be5641688c8d83941", - "0x84c202adeaff9293698022bc0381adba2cd959f9a35a4e8472288fd68f96f6de8be9da314c526d88e291c96b1f3d6db9", - "0x8ca01a143b8d13809e5a8024d03e6bc9492e22226073ef6e327edf1328ef4aff82d0bcccee92cb8e212831fa35fe1204", - "0xb2f970dbad15bfbefb38903c9bcc043d1367055c55dc1100a850f5eb816a4252c8c194b3132c929105511e14ea10a67d", - "0xa5e36556472a95ad57eb90c3b6623671b03eafd842238f01a081997ffc6e2401f76e781d049bb4aa94d899313577a9cf", - "0x8d1057071051772f7c8bedce53a862af6fd530dd56ae6321eaf2b9fc6a68beff5ed745e1c429ad09d5a118650bfd420a", - "0x8aadc4f70ace4fcb8d93a78610779748dcffc36182d45b932c226dc90e48238ea5daa91f137c65ed532352c4c4d57416", - "0xa2ea05ae37e673b4343232ae685ee14e6b88b867aef6dfac35db3589cbcd76f99540fed5c2641d5bb5a4a9f808e9bf0d", - "0x947f1abad982d65648ae4978e094332b4ecb90f482c9be5741d5d1cf5a28acf4680f1977bf6e49dd2174c37f11e01296", - "0xa27b144f1565e4047ba0e3f4840ef19b5095d1e281eaa463c5358f932114cbd018aa6dcf97546465cf2946d014d8e6d6", - "0x8574e1fc3acade47cd4539df578ce9205e745e161b91e59e4d088711a7ab5aa3b410d517d7304b92109924d9e2af8895", - "0xa48ee6b86b88015d6f0d282c1ae01d2a5b9e8c7aa3d0c18b35943dceb1af580d08a65f54dc6903cde82fd0d73ce94722", - "0x8875650cec543a7bf02ea4f2848a61d167a66c91ffaefe31a9e38dc8511c6a25bde431007eefe27a62af3655aca208dc", - "0x999b0a6e040372e61937bf0d68374e230346b654b5a0f591a59d33a4f95bdb2f3581db7c7ccb420cd7699ed709c50713", - "0x878c9e56c7100c5e47bbe77dc8da5c5fe706cec94d37fa729633bca63cace7c40102eee780fcdabb655f5fa47a99600e", - "0x865006fb5b475ada5e935f27b96f9425fc2d5449a3c106aa366e55ebed3b4ee42adc3c3f0ac19fd129b40bc7d6bc4f63", - "0xb7a7da847f1202e7bc1672553e68904715e84fd897d529243e3ecda59faa4e17ba99c649a802d53f6b8dfdd51f01fb74", - "0x8b2fb4432c05653303d8c8436473682933a5cb604da10c118ecfcd2c8a0e3132e125afef562bdbcc3df936164e5ce4f2", - "0x808d95762d33ddfa5d0ee3d7d9f327de21a994d681a5f372e2e3632963ea974da7f1f9e5bac8ccce24293509d1f54d27", - "0x932946532e3c397990a1df0e94c90e1e45133e347a39b6714c695be21aeb2d309504cb6b1dde7228ff6f6353f73e1ca2", - "0x9705e7c93f0cdfaa3fa96821f830fe53402ad0806036cd1b48adc2f022d8e781c1fbdab60215ce85c653203d98426da3", - "0xaa180819531c3ec1feb829d789cb2092964c069974ae4faad60e04a6afcce5c3a59aec9f11291e6d110a788d22532bc6", - "0x88f755097f7e25cb7dd3c449520c89b83ae9e119778efabb54fbd5c5714b6f37c5f9e0346c58c6ab09c1aef2483f895d", - "0x99fc03ab7810e94104c494f7e40b900f475fde65bdec853e60807ffd3f531d74de43335c3b2646b5b8c26804a7448898", - "0xaf2dea9683086bed1a179110efb227c9c00e76cd00a2015b089ccbcee46d1134aa18bda5d6cab6f82ae4c5cd2461ac21", - "0xa500f87ba9744787fdbb8e750702a3fd229de6b8817594348dec9a723b3c4240ddfa066262d002844b9e38240ce55658", - "0x924d0e45c780f5bc1c1f35d15dfc3da28036bdb59e4c5440606750ecc991b85be18bc9a240b6c983bc5430baa4c68287", - "0x865b11e0157b8bf4c5f336024b016a0162fc093069d44ac494723f56648bc4ded13dfb3896e924959ea11c96321afefc", - "0x93672d8607d4143a8f7894f1dcca83fb84906dc8d6dd7dd063bb0049cfc20c1efd933e06ca7bd03ea4cb5a5037990bfe", - "0x826891efbdff0360446825a61cd1fa04326dd90dae8c33dfb1ed97b045e165766dd070bd7105560994d0b2044bdea418", - "0x93c4a4a8bcbc8b190485cc3bc04175b7c0ed002c28c98a540919effd6ed908e540e6594f6db95cd65823017258fb3b1c", - "0xaeb2a0af2d2239fda9aa6b8234b019708e8f792834ff0dd9c487fa09d29800ddceddd6d7929faa9a3edcb9e1b3aa0d6b", - "0x87f11de7236d387863ec660d2b04db9ac08143a9a2c4dfff87727c95b4b1477e3bc473a91e5797313c58754905079643", - "0x80dc1db20067a844fe8baceca77f80db171a5ca967acb24e2d480eae9ceb91a3343c31ad1c95b721f390829084f0eae6", - "0x9825c31f1c18da0de3fa84399c8b40f8002c3cae211fb6a0623c76b097b4d39f5c50058f57a16362f7a575909d0a44a2", - "0xa99fc8de0c38dbf7b9e946de83943a6b46a762167bafe2a603fb9b86f094da30d6de7ed55d639aafc91936923ee414b3", - "0xad594678b407db5d6ea2e90528121f84f2b96a4113a252a30d359a721429857c204c1c1c4ff71d8bb5768c833f82e80e", - "0xb33d985e847b54510b9b007e31053732c8a495e43be158bd2ffcea25c6765bcbc7ca815f7c60b36ad088b955dd6e9350", - "0x815f8dfc6f90b3342ca3fbd968c67f324dae8f74245cbf8bc3bef10e9440c65d3a2151f951e8d18959ba01c1b50b0ec1", - "0x94c608a362dd732a1abc56e338637c900d59013db8668e49398b3c7a0cae3f7e2f1d1bf94c0299eeafe6af7f76c88618", - "0x8ebd8446b23e5adfcc393adc5c52fe172f030a73e63cd2d515245ca0dd02782ceed5bcdd9ccd9c1b4c5953dfac9c340c", - "0x820437f3f6f9ad0f5d7502815b221b83755eb8dc56cd92c29e9535eb0b48fb8d08c9e4fcc26945f9c8cca60d89c44710", - "0x8910e4e8a56bf4be9cc3bbf0bf6b1182a2f48837a2ed3c2aaec7099bfd7f0c83e14e608876b17893a98021ff4ab2f20d", - "0x9633918fde348573eec15ce0ad53ac7e1823aac86429710a376ad661002ae6d049ded879383faaa139435122f64047c6", - "0xa1f5e3fa558a9e89318ca87978492f0fb4f6e54a9735c1b8d2ecfb1d1c57194ded6e0dd82d077b2d54251f3bee1279e1", - "0xb208e22d04896abfd515a95c429ff318e87ff81a5d534c8ac2c33c052d6ffb73ef1dccd39c0bbe0734b596c384014766", - "0x986d5d7d2b5bde6d16336f378bd13d0e671ad23a8ec8a10b3fc09036faeeb069f60662138d7a6df3dfb8e0d36180f770", - "0xa2d4e6c5f5569e9cef1cddb569515d4b6ace38c8aed594f06da7434ba6b24477392cc67ba867c2b079545ca0c625c457", - "0xb5ac32b1d231957d91c8b7fc43115ce3c5c0d8c13ca633374402fa8000b6d9fb19499f9181844f0c10b47357f3f757ce", - "0x96b8bf2504b4d28fa34a4ec378e0e0b684890c5f44b7a6bb6e19d7b3db2ab27b1e2686389d1de9fbd981962833a313ea", - "0x953bfd7f6c3a0469ad432072b9679a25486f5f4828092401eff494cfb46656c958641a4e6d0d97d400bc59d92dba0030", - "0x876ab3cea7484bbfd0db621ec085b9ac885d94ab55c4bb671168d82b92e609754b86aaf472c55df3d81421d768fd108a", - "0x885ff4e67d9ece646d02dd425aa5a087e485c3f280c3471b77532b0db6145b69b0fbefb18aa2e3fa5b64928b43a94e57", - "0xb91931d93f806d0b0e6cc62a53c718c099526140f50f45d94b8bbb57d71e78647e06ee7b42aa5714aed9a5c05ac8533f", - "0xa0313eeadd39c720c9c27b3d671215331ab8d0a794e71e7e690f06bcd87722b531d6525060c358f35f5705dbb7109ccb", - "0x874c0944b7fedc6701e53344100612ddcb495351e29305c00ec40a7276ea5455465ffb7bded898886c1853139dfb1fc7", - "0x8dc31701a01ee8137059ca1874a015130d3024823c0576aa9243e6942ec99d377e7715ed1444cd9b750a64b85dcaa3e5", - "0x836d2a757405e922ec9a2dfdcf489a58bd48b5f9683dd46bf6047688f778c8dee9bc456de806f70464df0b25f3f3d238", - "0xb30b0a1e454a503ea3e2efdec7483eaf20b0a5c3cefc42069e891952b35d4b2c955cf615f3066285ed8fafd9fcfbb8f6", - "0x8e6d4044b55ab747e83ec8762ea86845f1785cc7be0279c075dadf08aca3ccc5a096c015bb3c3f738f647a4eadea3ba5", - "0xad7735d16ab03cbe09c029610aa625133a6daecfc990b297205b6da98eda8c136a7c50db90f426d35069708510d5ae9c", - "0x8d62d858bbb59ec3c8cc9acda002e08addab4d3ad143b3812098f3d9087a1b4a1bb255dcb1635da2402487d8d0249161", - "0x805beec33238b832e8530645a3254aeef957e8f7ea24bcfc1054f8b9c69421145ebb8f9d893237e8a001c857fedfc77e", - "0xb1005644be4b085e3f5775aa9bd3e09a283e87ddada3082c04e7a62d303dcef3b8cf8f92944c200c7ae6bb6bdf63f832", - "0xb4ba0e0790dc29063e577474ffe3b61f5ea2508169f5adc1e394934ebb473e356239413a17962bc3e5d3762d72cce8c2", - "0xa157ba9169c9e3e6748d9f1dd67fbe08b9114ade4c5d8fc475f87a764fb7e6f1d21f66d7905cd730f28a1c2d8378682a", - "0x913e52b5c93989b5d15e0d91aa0f19f78d592bc28bcfdfddc885a9980c732b1f4debb8166a7c4083c42aeda93a702898", - "0x90fbfc1567e7cd4e096a38433704d3f96a2de2f6ed3371515ccc30bc4dd0721a704487d25a97f3c3d7e4344472702d8d", - "0x89646043028ffee4b69d346907586fd12c2c0730f024acb1481abea478e61031966e72072ff1d5e65cb8c64a69ad4eb1", - "0xb125a45e86117ee11d2fb42f680ab4a7894edd67ff927ae2c808920c66c3e55f6a9d4588eee906f33a05d592e5ec3c04", - "0xaad47f5b41eae9be55fb4f67674ff1e4ae2482897676f964a4d2dcb6982252ee4ff56aac49578b23f72d1fced707525e", - "0xb9ddff8986145e33851b4de54d3e81faa3352e8385895f357734085a1616ef61c692d925fe62a5ed3be8ca49f5d66306", - "0xb3cb0963387ed28c0c0adf7fe645f02606e6e1780a24d6cecef5b7c642499109974c81a7c2a198b19862eedcea2c2d8c", - "0xac9c53c885457aaf5cb36c717a6f4077af701e0098eebd7aa600f5e4b14e6c1067255b3a0bc40e4a552025231be7de60", - "0x8e1a8d823c4603f6648ec21d064101094f2a762a4ed37dd2f0a2d9aa97b2d850ce1e76f4a4b8cae58819b058180f7031", - "0xb268b73bf7a179b6d22bd37e5e8cb514e9f5f8968c78e14e4f6d5700ca0d0ca5081d0344bb73b028970eebde3cb4124e", - "0xa7f57d71940f0edbd29ed8473d0149cae71d921dd15d1ff589774003e816b54b24de2620871108cec1ab9fa956ad6ce6", - "0x8053e6416c8b120e2b999cc2fc420a6a55094c61ac7f2a6c6f0a2c108a320890e389af96cbe378936132363c0d551277", - "0xb3823f4511125e5aa0f4269e991b435a0d6ceb523ebd91c04d7add5534e3df5fc951c504b4fd412a309fd3726b7f940b", - "0xae6eb04674d04e982ca9a6add30370ab90e303c71486f43ed3efbe431af1b0e43e9d06c11c3412651f304c473e7dbf39", - "0x96ab55e641ed2e677591f7379a3cd126449614181fce403e93e89b1645d82c4af524381ff986cae7f9cebe676878646d", - "0xb52423b4a8c37d3c3e2eca8f0ddbf7abe0938855f33a0af50f117fab26415fb0a3da5405908ec5fdc22a2c1f2ca64892", - "0x82a69ce1ee92a09cc709d0e3cd22116c9f69d28ea507fe5901f5676000b5179b9abe4c1875d052b0dd42d39925e186bb", - "0xa84c8cb84b9d5cfb69a5414f0a5283a5f2e90739e9362a1e8c784b96381b59ac6c18723a4aa45988ee8ef5c1f45cc97d", - "0xafd7efce6b36813082eb98257aae22a4c1ae97d51cac7ea9c852d4a66d05ef2732116137d8432e3f117119725a817d24", - "0xa0f5fe25af3ce021b706fcff05f3d825384a272284d04735574ce5fb256bf27100fad0b1f1ba0e54ae9dcbb9570ecad3", - "0x8751786cb80e2e1ff819fc7fa31c2833d25086534eb12b373d31f826382430acfd87023d2a688c65b5e983927e146336", - "0x8cf5c4b17fa4f3d35c78ce41e1dc86988fd1135cd5e6b2bb0c108ee13538d0d09ae7102609c6070f39f937b439b31e33", - "0xa9108967a2fedd7c322711eca8159c533dd561bedcb181b646de98bf5c3079449478eab579731bee8d215ae8852c7e21", - "0xb54c5171704f42a6f0f4e70767cdb3d96ffc4888c842eece343a01557da405961d53ffdc34d2f902ea25d3e1ed867cad", - "0xae8d4b764a7a25330ba205bf77e9f46182cd60f94a336bbd96773cf8064e3d39caf04c310680943dc89ed1fbad2c6e0d", - "0xaa5150e911a8e1346868e1b71c5a01e2a4bb8632c195861fb6c3038a0e9b85f0e09b3822e9283654a4d7bb17db2fc5f4", - "0x9685d3756ce9069bf8bb716cf7d5063ebfafe37e15b137fc8c3159633c4e006ff4887ddd0ae90360767a25c3f90cba7f", - "0x82155fd70f107ab3c8e414eadf226c797e07b65911508c76c554445422325e71af8c9a8e77fd52d94412a6fc29417cd3", - "0xabfae52f53a4b6e00760468d973a267f29321997c3dbb5aee36dc1f20619551229c0c45b9d9749f410e7f531b73378e8", - "0x81a76d921f8ef88e774fd985e786a4a330d779b93fad7def718c014685ca0247379e2e2a007ad63ee7f729cd9ed6ce1b", - "0x81947c84bc5e28e26e2e533af5ae8fe10407a7b77436dbf8f1d5b0bbe86fc659eae10f974659dc7c826c6dabd03e3a4b", - "0x92b8c07050d635b8dd4fd09df9054efe4edae6b86a63c292e73cc819a12a21dd7d104ce51fa56af6539dedf6dbe6f7b6", - "0xb44c579e3881f32b32d20c82c207307eca08e44995dd2aac3b2692d2c8eb2a325626c80ac81c26eeb38c4137ff95add5", - "0x97efab8941c90c30860926dea69a841f2dcd02980bf5413b9fd78d85904588bf0c1021798dbc16c8bbb32cce66c82621", - "0x913363012528b50698e904de0588bf55c8ec5cf6f0367cfd42095c4468fcc64954fbf784508073e542fee242d0743867", - "0x8ed203cf215148296454012bd10fddaf119203db1919a7b3d2cdc9f80e66729464fdfae42f1f2fc5af1ed53a42b40024", - "0xab84312db7b87d711e9a60824f4fe50e7a6190bf92e1628688dfcb38930fe87b2d53f9e14dd4de509b2216856d8d9188", - "0x880726def069c160278b12d2258eac8fa63f729cd351a710d28b7e601c6712903c3ac1e7bbd0d21e4a15f13ca49db5aa", - "0x980699cd51bac6283959765f5174e543ed1e5f5584b5127980cbc2ef18d984ecabba45042c6773b447b8e694db066028", - "0xaeb019cb80dc4cb4207430d0f2cd24c9888998b6f21d9bf286cc638449668d2eec0018a4cf3fe6448673cd6729335e2b", - "0xb29852f6aa6c60effdffe96ae88590c88abae732561d35cc19e82d3a51e26cb35ea00986193e07f90060756240f5346e", - "0xa0fa855adc5ba469f35800c48414b8921455950a5c0a49945d1ef6e8f2a1881f2e2dfae47de6417270a6bf49deeb091d", - "0xb6c7332e3b14813641e7272d4f69ecc7e09081df0037d6dab97ce13a9e58510f5c930d300633f208181d9205c5534001", - "0x85a6c050f42fce560b5a8d54a11c3bbb8407abbadd859647a7b0c21c4b579ec65671098b74f10a16245dc779dff7838e", - "0x8f3eb34bb68759d53c6677de4de78a6c24dd32c8962a7fb355ed362572ef8253733e6b52bc21c9f92ecd875020a9b8de", - "0xa17dd44181e5dab4dbc128e1af93ec22624b57a448ca65d2d9e246797e4af7d079e09c6e0dfb62db3a9957ce92f098d5", - "0xa56a1b854c3183082543a8685bb34cae1289f86cfa8123a579049dbd059e77982886bfeb61bf6e05b4b1fe4e620932e7", - "0xaedae3033cb2fb7628cb4803435bdd7757370a86f808ae4cecb9a268ad0e875f308c048c80cbcac523de16b609683887", - "0x9344905376aa3982b1179497fac5a1d74b14b7038fd15e3b002db4c11c8bfc7c39430db492cdaf58b9c47996c9901f28", - "0xa3bfafdae011a19f030c749c3b071f83580dee97dd6f949e790366f95618ca9f828f1daaeabad6dcd664fcef81b6556d", - "0x81c03d8429129e7e04434dee2c529194ddb01b414feda3adee2271eb680f6c85ec872a55c9fa9d2096f517e13ed5abcc", - "0x98205ef3a72dff54c5a9c82d293c3e45d908946fa74bb749c3aabe1ab994ea93c269bcce1a266d2fe67a8f02133c5985", - "0x85a70aeed09fda24412fadbafbbbf5ba1e00ac92885df329e147bfafa97b57629a3582115b780d8549d07d19b7867715", - "0xb0fbe81c719f89a57d9ea3397705f898175808c5f75f8eb81c2193a0b555869ba7bd2e6bc54ee8a60cea11735e21c68c", - "0xb03a0bd160495ee626ff3a5c7d95bc79d7da7e5a96f6d10116600c8fa20bedd1132f5170f25a22371a34a2d763f2d6d0", - "0xa90ab04091fbca9f433b885e6c1d60ab45f6f1daf4b35ec22b09909d493a6aab65ce41a6f30c98239cbca27022f61a8b", - "0xb66f92aa3bf2549f9b60b86f99a0bd19cbdd97036d4ae71ca4b83d669607f275260a497208f6476cde1931d9712c2402", - "0xb08e1fdf20e6a9b0b4942f14fa339551c3175c1ffc5d0ab5b226b6e6a322e9eb0ba96adc5c8d59ca4259e2bdd04a7eb0", - "0xa2812231e92c1ce74d4f5ac3ab6698520288db6a38398bb38a914ac9326519580af17ae3e27cde26607e698294022c81", - "0xabfcbbcf1d3b9e84c02499003e490a1d5d9a2841a9e50c7babbef0b2dd20d7483371d4dc629ba07faf46db659459d296", - "0xb0fe9f98c3da70927c23f2975a9dc4789194d81932d2ad0f3b00843dd9cbd7fb60747a1da8fe5a79f136a601becf279d", - "0xb130a6dba7645165348cb90f023713bed0eefbd90a976b313521c60a36d34f02032e69a2bdcf5361e343ed46911297ec", - "0x862f0cffe3020cea7a5fd4703353aa1eb1be335e3b712b29d079ff9f7090d1d8b12013011e1bdcbaa80c44641fd37c9f", - "0x8c6f11123b26633e1abb9ed857e0bce845b2b3df91cc7b013b2fc77b477eee445da0285fc6fc793e29d5912977f40916", - "0x91381846126ea819d40f84d3005e9fb233dc80071d1f9bb07f102bf015f813f61e5884ffffb4f5cd333c1b1e38a05a58", - "0x8add7d908de6e1775adbd39c29a391f06692b936518db1f8fde74eb4f533fc510673a59afb86e3a9b52ade96e3004c57", - "0x8780e086a244a092206edcde625cafb87c9ab1f89cc3e0d378bc9ee776313836160960a82ec397bc3800c0a0ec3da283", - "0xa6cb4cd9481e22870fdd757fae0785edf4635e7aacb18072fe8dc5876d0bab53fb99ce40964a7d3e8bcfff6f0ab1332f", - "0xaf30ff47ecc5b543efba1ba4706921066ca8bb625f40e530fb668aea0551c7647a9d126e8aba282fbcce168c3e7e0130", - "0x91b0bcf408ce3c11555dcb80c4410b5bc2386d3c05caec0b653352377efdcb6bab4827f2018671fc8e4a0e90d772acc1", - "0xa9430b975ef138b6b2944c7baded8fe102d31da4cfe3bd3d8778bda79189c99d38176a19c848a19e2d1ee0bddd9a13c1", - "0xaa5a4eef849d7c9d2f4b018bd01271c1dd83f771de860c4261f385d3bdcc130218495860a1de298f14b703ec32fa235f", - "0xb0ce79e7f9ae57abe4ff366146c3b9bfb38b0dee09c28c28f5981a5d234c6810ad4d582751948affb480d6ae1c8c31c4", - "0xb75122748560f73d15c01a8907d36d06dc068e82ce22b84b322ac1f727034493572f7907dec34ebc3ddcc976f2f89ed7", - "0xb0fc7836369a3e4411d34792d6bd5617c14f61d9bba023dda64e89dc5fb0f423244e9b48ee64869258931daa9753a56f", - "0x8956d7455ae9009d70c6e4a0bcd7610e55f37494cf9897a8f9e1b904cc8febc3fd2d642ebd09025cfff4609ad7e3bc52", - "0xad741efe9e472026aa49ae3d9914cb9c1a6f37a54f1a6fe6419bebd8c7d68dca105a751c7859f4389505ede40a0de786", - "0xb52f418797d719f0d0d0ffb0846788b5cba5d0454a69a2925de4b0b80fa4dd7e8c445e5eac40afd92897ed28ca650566", - "0xa0ab65fb9d42dd966cd93b1de01d7c822694669dd2b7a0c04d99cd0f3c3de795f387b9c92da11353412f33af5c950e9a", - "0xa0052f44a31e5741a331f7cac515a08b3325666d388880162d9a7b97598fde8b61f9ff35ff220df224eb5c4e40ef0567", - "0xa0101cfdc94e42b2b976c0d89612a720e55d145a5ef6ef6f1f78cf6de084a49973d9b5d45915349c34ce712512191e3c", - "0xa0dd99fcf3f5cead5aaf08e82212df3a8bb543c407a4d6fab88dc5130c1769df3f147e934a46f291d6c1a55d92b86917", - "0xa5939153f0d1931bbda5cf6bdf20562519ea55fbfa978d6dbc6828d298260c0da7a50c37c34f386e59431301a96c2232", - "0x9568269f3f5257200f9ca44afe1174a5d3cf92950a7f553e50e279c239e156a9faaa2a67f288e3d5100b4142efe64856", - "0xb746b0832866c23288e07f24991bbf687cad794e7b794d3d3b79367566ca617d38af586cdc8d6f4a85a34835be41d54f", - "0xa871ce28e39ab467706e32fec1669fda5a4abba2f8c209c6745df9f7a0fa36bbf1919cf14cb89ea26fa214c4c907ae03", - "0xa08dacdd758e523cb8484f6bd070642c0c20e184abdf8e2a601f61507e93952d5b8b0c723c34fcbdd70a8485eec29db2", - "0x85bdb78d501382bb95f1166b8d032941005661aefd17a5ac32df9a3a18e9df2fc5dc2c1f07075f9641af10353cecc0c9", - "0x98d730c28f6fa692a389e97e368b58f4d95382fad8f0baa58e71a3d7baaea1988ead47b13742ce587456f083636fa98e", - "0xa557198c6f3d5382be9fb363feb02e2e243b0c3c61337b3f1801c4a0943f18e38ce1a1c36b5c289c8fa2aa9d58742bab", - "0x89174f79201742220ac689c403fc7b243eed4f8e3f2f8aba0bf183e6f5d4907cb55ade3e238e3623d9885f03155c4d2b", - "0xb891d600132a86709e06f3381158db300975f73ea4c1f7c100358e14e98c5fbe792a9af666b85c4e402707c3f2db321e", - "0xb9e5b2529ef1043278c939373fc0dbafe446def52ddd0a8edecd3e4b736de87e63e187df853c54c28d865de18a358bb6", - "0x8589b2e9770340c64679062c5badb7bbef68f55476289b19511a158a9a721f197da03ece3309e059fc4468b15ac33aa3", - "0xaad8c6cd01d785a881b446f06f1e9cd71bca74ba98674c2dcddc8af01c40aa7a6d469037498b5602e76e9c91a58d3dbd", - "0xabaccb1bd918a8465f1bf8dbe2c9ad4775c620b055550b949a399f30cf0d9eb909f3851f5b55e38f9e461e762f88f499", - "0xae62339d26db46e85f157c0151bd29916d5cc619bd4b832814b3fd2f00af8f38e7f0f09932ffe5bba692005dab2d9a74", - "0x93a6ff30a5c0edf8058c89aba8c3259e0f1b1be1b80e67682de651e5346f7e1b4b4ac3d87cbaebf198cf779524aff6bf", - "0x8980a2b1d8f574af45b459193c952400b10a86122b71fca2acb75ee0dbd492e7e1ef5b959baf609a5172115e371f3177", - "0x8c2f49f3666faee6940c75e8c7f6f8edc3f704cca7a858bbb7ee5e96bba3b0cf0993996f781ba6be3b0821ef4cb75039", - "0xb14b9e348215b278696018330f63c38db100b0542cfc5be11dc33046e3bca6a13034c4ae40d9cef9ea8b34fef0910c4e", - "0xb59bc3d0a30d66c16e6a411cb641f348cb1135186d5f69fda8b0a0934a5a2e7f6199095ba319ec87d3fe8f1ec4a06368", - "0x8874aca2a3767aa198e4c3fec2d9c62d496bc41ff71ce242e9e082b7f38cdf356089295f80a301a3cf1182bde5308c97", - "0xb1820ebd61376d91232423fc20bf008b2ba37e761199f4ef0648ea2bd70282766799b4de814846d2f4d516d525c8daa7", - "0xa6b202e5dedc16a4073e04a11af3a8509b23dfe5a1952f899adeb240e75c3f5bde0c424f811a81ea48d343591faffe46", - "0xa69becee9c93734805523b92150a59a62eed4934f66056b645728740d42223f2925a1ad38359ba644da24d9414f4cdda", - "0xad72f0f1305e37c7e6b48c272323ee883320994cb2e0d850905d6655fafc9f361389bcb9c66b3ff8d2051dbb58c8aa96", - "0xb563600bd56fad7c8853af21c6a02a16ed9d8a8bbeea2c31731d63b976d83cb05b9779372d898233e8fd597a75424797", - "0xb0abb78ce465bf7051f563c62e8be9c57a2cc997f47c82819300f36e301fefd908894bb2053a9d27ce2d0f8c46d88b5b", - "0xa071a85fb8274bac2202e0cb8e0e2028a5e138a82d6e0374d39ca1884a549c7c401312f00071b91f455c3a2afcfe0cda", - "0xb931c271513a0f267b9f41444a5650b1918100b8f1a64959c552aff4e2193cc1b9927906c6fa7b8a8c68ef13d79aaa52", - "0xa6a1bb9c7d32cb0ca44d8b75af7e40479fbce67d216b48a2bb680d3f3a772003a49d3cd675fc64e9e0f8fabeb86d6d61", - "0xb98d609858671543e1c3b8564162ad828808bb50ded261a9f8690ded5b665ed8368c58f947365ed6e84e5a12e27b423d", - "0xb3dca58cd69ec855e2701a1d66cad86717ff103ef862c490399c771ad28f675680f9500cb97be48de34bcdc1e4503ffd", - "0xb34867c6735d3c49865e246ddf6c3b33baf8e6f164db3406a64ebce4768cb46b0309635e11be985fee09ab7a31d81402", - "0xacb966c554188c5b266624208f31fab250b3aa197adbdd14aee5ab27d7fb886eb4350985c553b20fdf66d5d332bfd3fe", - "0x943c36a18223d6c870d54c3b051ef08d802b85e9dd6de37a51c932f90191890656c06adfa883c87b906557ae32d09da0", - "0x81bca7954d0b9b6c3d4528aadf83e4bc2ef9ea143d6209bc45ae9e7ae9787dbcd8333c41f12c0b6deee8dcb6805e826a", - "0xaba176b92256efb68f574e543479e5cf0376889fb48e3db4ebfb7cba91e4d9bcf19dcfec444c6622d9398f06de29e2b9", - "0xb9f743691448053216f6ece7cd699871fff4217a1409ceb8ab7bdf3312d11696d62c74b0664ba0a631b1e0237a8a0361", - "0xa383c2b6276fa9af346b21609326b53fb14fdf6f61676683076e80f375b603645f2051985706d0401e6fbed7eb0666b6", - "0xa9ef2f63ec6d9beb8f3d04e36807d84bda87bdd6b351a3e4a9bf7edcb5618c46c1f58cfbf89e64b40f550915c6988447", - "0xa141b2d7a82f5005eaea7ae7d112c6788b9b95121e5b70b7168d971812f3381de8b0082ac1f0a82c7d365922ebd2d26a", - "0xb1b76ef8120e66e1535c17038b75255a07849935d3128e3e99e56567b842fb1e8d56ef932d508d2fb18b82f7868fe1a9", - "0x8e2e234684c81f21099f5c54f6bbe2dd01e3b172623836c77668a0c49ce1fe218786c3827e4d9ae2ea25c50a8924fb3c", - "0xa5caf5ff948bfd3c4ca3ffbdfcd91eec83214a6c6017235f309a0bbf7061d3b0b466307c00b44a1009cf575163898b43", - "0x986415a82ca16ebb107b4c50b0c023c28714281db0bcdab589f6cb13d80e473a3034b7081b3c358e725833f6d845cb14", - "0xb94836bf406ac2cbacb10e6df5bcdfcc9d9124ae1062767ca4e322d287fd5e353fdcebd0e52407cb3cd68571258a8900", - "0x83c6d70a640b33087454a4788dfd9ef3ed00272da084a8d36be817296f71c086b23b576f98178ab8ca6a74f04524b46b", - "0xad4115182ad784cfe11bcfc5ce21fd56229cc2ce77ac82746e91a2f0aa53ca6593a22efd2dc4ed8d00f84542643d9c58", - "0xab1434c5e5065da826d10c2a2dba0facccab0e52b506ce0ce42fbe47ced5a741797151d9ecc99dc7d6373cfa1779bbf6", - "0x8a8b591d82358d55e6938f67ea87a89097ab5f5496f7260adb9f649abb289da12b498c5b2539c2f9614fb4e21b1f66b0", - "0x964f355d603264bc1f44c64d6d64debca66f37dff39c971d9fc924f2bc68e6c187b48564a6dc82660a98b035f8addb5d", - "0xb66235eaaf47456bc1dc4bde454a028e2ce494ece6b713a94cd6bf27cf18c717fd0c57a5681caaa2ad73a473593cdd7a", - "0x9103e3bb74304186fa4e3e355a02da77da4aca9b7e702982fc2082af67127ebb23a455098313c88465bc9b7d26820dd5", - "0xb6a42ff407c9dd132670cdb83cbad4b20871716e44133b59a932cd1c3f97c7ac8ff7f61acfaf8628372508d8dc8cad7c", - "0x883a9c21c16a167a4171b0f084565c13b6f28ba7c4977a0de69f0a25911f64099e7bbb4da8858f2e93068f4155d04e18", - "0x8dbb3220abc6a43220adf0331e3903d3bfd1d5213aadfbd8dfcdf4b2864ce2e96a71f35ecfb7a07c3bbabf0372b50271", - "0xb4ad08aee48e176bda390b7d9acf2f8d5eb008f30d20994707b757dc6a3974b2902d29cd9b4d85e032810ad25ac49e97", - "0x865bb0f33f7636ec501bb634e5b65751c8a230ae1fa807a961a8289bbf9c7fe8c59e01fbc4c04f8d59b7f539cf79ddd5", - "0x86a54d4c12ad1e3605b9f93d4a37082fd26e888d2329847d89afa7802e815f33f38185c5b7292293d788ad7d7da1df97", - "0xb26c8615c5e47691c9ff3deca3021714662d236c4d8401c5d27b50152ce7e566266b9d512d14eb63e65bc1d38a16f914", - "0x827639d5ce7db43ba40152c8a0eaad443af21dc92636cc8cc2b35f10647da7d475a1e408901cd220552fddad79db74df", - "0xa2b79a582191a85dbe22dc384c9ca3de345e69f6aa370aa6d3ff1e1c3de513e30b72df9555b15a46586bd27ea2854d9d", - "0xae0d74644aba9a49521d3e9553813bcb9e18f0b43515e4c74366e503c52f47236be92dfbd99c7285b3248c267b1de5a0", - "0x80fb0c116e0fd6822a04b9c25f456bdca704e2be7bdc5d141dbf5d1c5eeb0a2c4f5d80db583b03ef3e47517e4f9a1b10", - "0xac3a1fa3b4a2f30ea7e0a114cdc479eb51773573804c2a158d603ad9902ae8e39ffe95df09c0d871725a5d7f9ba71a57", - "0xb56b2b0d601cba7f817fa76102c68c2e518c6f20ff693aad3ff2e07d6c4c76203753f7f91686b1801e8c4659e4d45c48", - "0x89d50c1fc56e656fb9d3915964ebce703cb723fe411ab3c9eaa88ccc5d2b155a9b2e515363d9c600d3c0cee782c43f41", - "0xb24207e61462f6230f3cd8ccf6828357d03e725769f7d1de35099ef9ee4dca57dbce699bb49ed994462bee17059d25ce", - "0xb886f17fcbcbfcd08ac07f04bb9543ef58510189decaccea4b4158c9174a067cb67d14b6be3c934e6e2a18c77efa9c9c", - "0xb9c050ad9cafd41c6e2e192b70d080076eed59ed38ea19a12bd92fa17b5d8947d58d5546aaf5e8e27e1d3b5481a6ce51", - "0xaaf7a34d3267e3b1ddbc54c641e3922e89303f7c86ebebc7347ebca4cffad5b76117dac0cbae1a133053492799cd936f", - "0xa9ee604ada50adef82e29e893070649d2d4b7136cc24fa20e281ce1a07bd736bf0de7c420369676bcbcecff26fb6e900", - "0x9855315a12a4b4cf80ab90b8bd13003223ba25206e52fd4fe6a409232fbed938f30120a3db23eab9c53f308bd8b9db81", - "0x8cd488dd7a24f548a3cf03c54dec7ff61d0685cb0f6e5c46c2d728e3500d8c7bd6bba0156f4bf600466fda53e5b20444", - "0x890ad4942ebac8f5b16c777701ab80c68f56fa542002b0786f8fea0fb073154369920ac3dbfc07ea598b82f4985b8ced", - "0x8de0cf9ddc84c9b92c59b9b044387597799246b30b9f4d7626fc12c51f6e423e08ee4cbfe9289984983c1f9521c3e19d", - "0xb474dfb5b5f4231d7775b3c3a8744956b3f0c7a871d835d7e4fd9cc895222c7b868d6c6ce250de568a65851151fac860", - "0x86433b6135d9ed9b5ee8cb7a6c40e5c9d30a68774cec04988117302b8a02a11a71a1e03fd8e0264ef6611d219f103007", - "0x80b9ed4adbe9538fb1ef69dd44ec0ec5b57cbfea820054d8d445b4261962624b4c70ac330480594bc5168184378379c3", - "0x8b2e83562ccd23b7ad2d17f55b1ab7ef5fbef64b3a284e6725b800f3222b8bdf49937f4a873917ada9c4ddfb090938c2", - "0xabe78cebc0f5a45d754140d1f685e387489acbfa46d297a8592aaa0d676a470654f417a4f7d666fc0b2508fab37d908e", - "0xa9c5f8ff1f8568e252b06d10e1558326db9901840e6b3c26bbd0cd5e850cb5fb3af3f117dbb0f282740276f6fd84126f", - "0x975f8dc4fb55032a5df3b42b96c8c0ffecb75456f01d4aef66f973cb7270d4eff32c71520ceefc1adcf38d77b6b80c67", - "0xb043306ed2c3d8a5b9a056565afd8b5e354c8c4569fda66b0d797a50a3ce2c08cffbae9bbe292da69f39e89d5dc7911e", - "0x8d2afc36b1e44386ba350c14a6c1bb31ff6ea77128a0c5287584ac3584282d18516901ce402b4644a53db1ed8e7fa581", - "0x8c294058bed53d7290325c363fe243f6ec4f4ea2343692f4bac8f0cb86f115c069ccb8334b53d2e42c067691ad110dba", - "0xb92157b926751aaf7ef82c1aa8c654907dccab6376187ee8b3e8c0c82811eae01242832de953faa13ebaff7da8698b3e", - "0xa780c4bdd9e4ba57254b09d745075cecab87feda78c88ffee489625c5a3cf96aa6b3c9503a374a37927d9b78de9bd22b", - "0x811f548ef3a2e6a654f7dcb28ac9378de9515ed61e5a428515d9594a83e80b35c60f96a5cf743e6fab0d3cb526149f49", - "0x85a4dccf6d90ee8e094731eec53bd00b3887aec6bd81a0740efddf812fd35e3e4fe4f983afb49a8588691c202dabf942", - "0xb152c2da6f2e01c8913079ae2b40a09b1f361a80f5408a0237a8131b429677c3157295e11b365b1b1841924b9efb922e", - "0x849b9efee8742502ffd981c4517c88ed33e4dd518a330802caff168abae3cd09956a5ee5eda15900243bc2e829016b74", - "0x955a933f3c18ec0f1c0e38fa931e4427a5372c46a3906ebe95082bcf878c35246523c23f0266644ace1fa590ffa6d119", - "0x911989e9f43e580c886656377c6f856cdd4ff1bd001b6db3bbd86e590a821d34a5c6688a29b8d90f28680e9fdf03ba69", - "0xb73b8b4f1fd6049fb68d47cd96a18fcba3f716e0a1061aa5a2596302795354e0c39dea04d91d232aec86b0bf2ba10522", - "0x90f87456d9156e6a1f029a833bf3c7dbed98ca2f2f147a8564922c25ae197a55f7ea9b2ee1f81bf7383197c4bad2e20c", - "0x903cba8b1e088574cb04a05ca1899ab00d8960580c884bd3c8a4c98d680c2ad11410f2b75739d6050f91d7208cac33a5", - "0x9329987d42529c261bd15ecedd360be0ea8966e7838f32896522c965adfc4febf187db392bd441fb43bbd10c38fdf68b", - "0x8178ee93acf5353baa349285067b20e9bb41aa32d77b5aeb7384fe5220c1fe64a2461bd7a83142694fe673e8bbf61b7c", - "0xa06a8e53abcff271b1394bcc647440f81fb1c1a5f29c27a226e08f961c3353f4891620f2d59b9d1902bf2f5cc07a4553", - "0xaaf5fe493b337810889e777980e6bbea6cac39ac66bc0875c680c4208807ac866e9fda9b5952aa1d04539b9f4a4bec57", - "0xaa058abb1953eceac14ccfa7c0cc482a146e1232905dcecc86dd27f75575285f06bbae16a8c9fe8e35d8713717f5f19f", - "0x8f15dd732799c879ca46d2763453b359ff483ca33adb1d0e0a57262352e0476c235987dc3a8a243c74bc768f93d3014c", - "0xa61cc8263e9bc03cce985f1663b8a72928a607121005a301b28a278e9654727fd1b22bc8a949af73929c56d9d3d4a273", - "0x98d6dc78502d19eb9f921225475a6ebcc7b44f01a2df6f55ccf6908d65b27af1891be2a37735f0315b6e0f1576c1f8d8", - "0x8bd258b883f3b3793ec5be9472ad1ff3dc4b51bc5a58e9f944acfb927349ead8231a523cc2175c1f98e7e1e2b9f363b8", - "0xaeacc2ecb6e807ad09bedd99654b097a6f39840e932873ace02eabd64ccfbb475abdcb62939a698abf17572d2034c51e", - "0xb8ccf78c08ccd8df59fd6eda2e01de328bc6d8a65824d6f1fc0537654e9bc6bf6f89c422dd3a295cce628749da85c864", - "0x8f91fd8cb253ba2e71cc6f13da5e05f62c2c3b485c24f5d68397d04665673167fce1fc1aec6085c69e87e66ec555d3fd", - "0xa254baa10cb26d04136886073bb4c159af8a8532e3fd36b1e9c3a2e41b5b2b6a86c4ebc14dbe624ee07b7ccdaf59f9ab", - "0x94e3286fe5cd68c4c7b9a7d33ae3d714a7f265cf77cd0e9bc19fc51015b1d1c34ad7e3a5221c459e89f5a043ee84e3a9", - "0xa279da8878af8d449a9539bec4b17cea94f0242911f66fab275b5143ab040825f78c89cb32a793930609415cfa3a1078", - "0xac846ceb89c9e5d43a2991c8443079dc32298cd63e370e64149cec98cf48a6351c09c856f2632fd2f2b3d685a18bbf8b", - "0xa847b27995c8a2e2454aaeb983879fb5d3a23105c33175839f7300b7e1e8ec3efd6450e9fa3f10323609dee7b98c6fd5", - "0xa2f432d147d904d185ff4b2de8c6b82fbea278a2956bc406855b44c18041854c4f0ecccd472d1d0dff1d8aa8e281cb1d", - "0x94a48ad40326f95bd63dff4755f863a1b79e1df771a1173b17937f9baba57b39e651e7695be9f66a472f098b339364fc", - "0xa12a0ccd8f96e96e1bc6494341f7ebce959899341b3a084aa1aa87d1c0d489ac908552b7770b887bb47e7b8cbc3d8e66", - "0x81a1f1681bda923bd274bfe0fbb9181d6d164fe738e54e25e8d4849193d311e2c4253614ed673c98af2c798f19a93468", - "0xabf71106a05d501e84cc54610d349d7d5eae21a70bd0250f1bebbf412a130414d1c8dbe673ffdb80208fd72f1defa4d4", - "0x96266dc2e0df18d8136d79f5b59e489978eee0e6b04926687fe389d4293c14f36f055c550657a8e27be4118b64254901", - "0x8df5dcbefbfb4810ae3a413ca6b4bf08619ca53cd50eb1dde2a1c035efffc7b7ac7dff18d403253fd80104bd83dc029e", - "0x9610b87ff02e391a43324a7122736876d5b3af2a137d749c52f75d07b17f19900b151b7f439d564f4529e77aa057ad12", - "0xa90a5572198b40fe2fcf47c422274ff36c9624df7db7a89c0eb47eb48a73a03c985f4ac5016161c76ca317f64339bce1", - "0x98e5e61a6ab6462ba692124dba7794b6c6bde4249ab4fcc98c9edd631592d5bc2fb5e38466691a0970a38e48d87c2e43", - "0x918cefb8f292f78d4db81462c633daf73b395e772f47b3a7d2cea598025b1d8c3ec0cbff46cdb23597e74929981cde40", - "0xa98918a5dc7cf610fe55f725e4fd24ce581d594cb957bb9b4e888672e9c0137003e1041f83e3f1d7b9caab06462c87d4", - "0xb92b74ac015262ca66c33f2d950221e19d940ba3bf4cf17845f961dc1729ae227aa9e1f2017829f2135b489064565c29", - "0xa053ee339f359665feb178b4e7ee30a85df37debd17cacc5a27d6b3369d170b0114e67ad1712ed26d828f1df641bcd99", - "0x8c3c8bad510b35da5ce5bd84b35c958797fbea024ad1c97091d2ff71d9b962e9222f65a9b776e5b3cc29c36e1063d2ee", - "0xaf99dc7330fe7c37e850283eb47cc3257888e7c197cb0d102edf94439e1e02267b6a56306d246c326c4c79f9dc8c6986", - "0xafecb2dc34d57a725efbd7eb93d61eb29dbe8409b668ab9ea040791f5b796d9be6d4fc10d7f627bf693452f330cf0435", - "0x93334fedf19a3727a81a6b6f2459db859186227b96fe7a391263f69f1a0884e4235de64d29edebc7b99c44d19e7c7d7a", - "0x89579c51ac405ad7e9df13c904061670ce4b38372492764170e4d3d667ed52e5d15c7cd5c5991bbfa3a5e4e3fa16363e", - "0x9778f3e8639030f7ef1c344014f124e375acb8045bd13d8e97a92c5265c52de9d1ffebaa5bc3e1ad2719da0083222991", - "0x88f77f34ee92b3d36791bdf3326532524a67d544297dcf1a47ff00b47c1b8219ff11e34034eab7d23b507caa2fd3c6b9", - "0xa699c1e654e7c484431d81d90657892efeb4adcf72c43618e71ca7bd7c7a7ebbb1db7e06e75b75dc4c74efd306b5df3f", - "0x81d13153baebb2ef672b5bdb069d3cd669ce0be96b742c94e04038f689ff92a61376341366b286eee6bf3ae85156f694", - "0x81efb17de94400fdacc1deec2550cbe3eecb27c7af99d8207e2f9be397e26be24a40446d2a09536bb5172c28959318d9", - "0x989b21ebe9ceab02488992673dc071d4d5edec24bff0e17a4306c8cb4b3c83df53a2063d1827edd8ed16d6e837f0d222", - "0x8d6005d6536825661b13c5fdce177cb37c04e8b109b7eb2b6d82ea1cb70efecf6a0022b64f84d753d165edc2bba784a3", - "0xa32607360a71d5e34af2271211652d73d7756d393161f4cf0da000c2d66a84c6826e09e759bd787d4fd0305e2439d342", - "0xaaad8d6f6e260db45d51b2da723be6fa832e76f5fbcb77a9a31e7f090dd38446d3b631b96230d78208cae408c288ac4e", - "0xabcfe425255fd3c5cffd3a818af7650190c957b6b07b632443f9e33e970a8a4c3bf79ac9b71f4d45f238a04d1c049857", - "0xaeabf026d4c783adc4414b5923dbd0be4b039cc7201219f7260d321f55e9a5b166d7b5875af6129c034d0108fdc5d666", - "0xaf49e740c752d7b6f17048014851f437ffd17413c59797e5078eaaa36f73f0017c3e7da020310cfe7d3c85f94a99f203", - "0x8854ca600d842566e3090040cd66bb0b3c46dae6962a13946f0024c4a8aca447e2ccf6f240045f1ceee799a88cb9210c", - "0xb6c03b93b1ab1b88ded8edfa1b487a1ed8bdce8535244dddb558ffb78f89b1c74058f80f4db2320ad060d0c2a9c351cc", - "0xb5bd7d17372faff4898a7517009b61a7c8f6f0e7ed4192c555db264618e3f6e57fb30a472d169fea01bf2bf0362a19a8", - "0x96eb1d38319dc74afe7e7eb076fcd230d19983f645abd14a71e6103545c01301b31c47ae931e025f3ecc01fb3d2f31fa", - "0xb55a8d30d4403067def9b65e16f867299f8f64c9b391d0846d4780bc196569622e7e5b64ce799b5aefac8f965b2a7a7b", - "0x8356d199a991e5cbbff608752b6291731b6b6771aed292f8948b1f41c6543e4ab1bedc82dd26d10206c907c03508df06", - "0x97f4137445c2d98b0d1d478049de952610ad698c91c9d0f0e7227d2aae690e9935e914ec4a2ea1fbf3fc1dddfeeacebb", - "0xaf5621707e0938320b15ddfc87584ab325fbdfd85c30efea36f8f9bd0707d7ec12c344eff3ec21761189518d192df035", - "0x8ac7817e71ea0825b292687928e349da7140285d035e1e1abff0c3704fa8453faaae343a441b7143a74ec56539687cc4", - "0x8a5e0a9e4758449489df10f3386029ada828d1762e4fb0a8ffe6b79e5b6d5d713cb64ed95960e126398b0cdb89002bc9", - "0x81324be4a71208bbb9bca74b77177f8f1abb9d3d5d9db195d1854651f2cf333cd618d35400da0f060f3e1b025124e4b2", - "0x849971d9d095ae067525b3cbc4a7dfae81f739537ade6d6cec1b42fb692d923176197a8770907c58069754b8882822d6", - "0x89f830825416802477cc81fdf11084885865ee6607aa15aa4eb28e351c569c49b8a1b9b5e95ddc04fa0ebafe20071313", - "0x9240aeeaff37a91af55f860b9badd466e8243af9e8c96a7aa8cf348cd270685ab6301bc135b246dca9eda696f8b0e350", - "0xacf74db78cc33138273127599eba35b0fb4e7b9a69fe02dae18fc6692d748ca332bd00b22afa8e654ed587aab11833f3", - "0xb091e6d37b157b50d76bd297ad752220cd5c9390fac16dc838f8557aed6d9833fc920b61519df21265406216315e883f", - "0xa6446c429ebf1c7793c622250e23594c836b2fbcaf6c5b3d0995e1595a37f50ea643f3e549b0be8bbdadd69044d72ab9", - "0x93e675353bd60e996bf1c914d5267eeaa8a52fc3077987ccc796710ef9becc6b7a00e3d82671a6bdfb8145ee3c80245a", - "0xa2f731e43251d04ed3364aa2f072d05355f299626f2d71a8a38b6f76cf08c544133f7d72dd0ab4162814b674b9fc7fa6", - "0x97a8b791a5a8f6e1d0de192d78615d73d0c38f1e557e4e15d15adc663d649e655bc8da3bcc499ef70112eafe7fb45c7a", - "0x98cd624cbbd6c53a94469be4643c13130916b91143425bcb7d7028adbbfede38eff7a21092af43b12d4fab703c116359", - "0x995783ce38fd5f6f9433027f122d4cf1e1ff3caf2d196ce591877f4a544ce9113ead60de2de1827eaff4dd31a20d79a8", - "0x8cf251d6f5229183b7f3fe2f607a90b4e4b6f020fb4ba2459d28eb8872426e7be8761a93d5413640a661d73e34a5b81f", - "0xb9232d99620652a3aa7880cad0876f153ff881c4ed4c0c2e7b4ea81d5d42b70daf1a56b869d752c3743c6d4c947e6641", - "0x849716f938f9d37250cccb1bf77f5f9fde53096cdfc6f2a25536a6187029a8f1331cdbed08909184b201f8d9f04b792f", - "0x80c7c4de098cbf9c6d17b14eba1805e433b5bc905f6096f8f63d34b94734f2e4ebf4bce8a177efd1186842a61204a062", - "0xb790f410cf06b9b8daadceeb4fd5ff40a2deda820c8df2537e0a7554613ae3948e149504e3e79aa84889df50c8678eeb", - "0x813aab8bd000299cd37485b73cd7cba06e205f8efb87f1efc0bae8b70f6db2bc7702eb39510ad734854fb65515fe9d0f", - "0x94f0ab7388ac71cdb67f6b85dfd5945748afb2e5abb622f0b5ad104be1d4d0062b651f134ba22385c9e32c2dfdcccce1", - "0xab6223dca8bd6a4f969e21ccd9f8106fc5251d321f9e90cc42cea2424b3a9c4e5060a47eeef6b23c7976109b548498e8", - "0x859c56b71343fce4d5c5b87814c47bf55d581c50fd1871a17e77b5e1742f5af639d0e94d19d909ec7dfe27919e954e0c", - "0xaae0d632b6191b8ad71b027791735f1578e1b89890b6c22e37de0e4a6074886126988fe8319ae228ac9ef3b3bcccb730", - "0x8ca9f32a27a024c3d595ecfaf96b0461de57befa3b331ab71dc110ec3be5824fed783d9516597537683e77a11d334338", - "0xa061df379fb3f4b24816c9f6cd8a94ecb89b4c6dc6cd81e4b8096fa9784b7f97ab3540259d1de9c02eb91d9945af4823", - "0x998603102ac63001d63eb7347a4bb2bf4cf33b28079bb48a169076a65c20d511ccd3ef696d159e54cc8e772fb5d65d50", - "0x94444d96d39450872ac69e44088c252c71f46be8333a608a475147752dbb99db0e36acfc5198f158509401959c12b709", - "0xac1b51b6c09fe055c1d7c9176eea9adc33f710818c83a1fbfa073c8dc3a7eb3513cbdd3f5960b7845e31e3e83181e6ba", - "0x803d530523fc9e1e0f11040d2412d02baef3f07eeb9b177fa9bfa396af42eea898a4276d56e1db998dc96ae47b644cb2", - "0x85a3c9fc7638f5bf2c3e15ba8c2fa1ae87eb1ceb44c6598c67a2948667a9dfa41e61f66d535b4e7fda62f013a5a8b885", - "0xa961cf5654c46a1a22c29baf7a4e77837a26b7f138f410e9d1883480ed5fa42411d522aba32040b577046c11f007388e", - "0xad1154142344f494e3061ef45a34fab1aaacf5fdf7d1b26adbb5fbc3d795655fa743444e39d9a4119b4a4f82a6f30441", - "0xb1d6c30771130c77806e7ab893b73d4deb590b2ff8f2f8b5e54c2040c1f3e060e2bd99afc668cf706a2df666a508bbf6", - "0xa00361fd440f9decabd98d96c575cd251dc94c60611025095d1201ef2dedde51cb4de7c2ece47732e5ed9b3526c2012c", - "0xa85c5ab4d17d328bda5e6d839a9a6adcc92ff844ec25f84981e4f44a0e8419247c081530f8d9aa629c7eb4ca21affba6", - "0xa4ddd3eab4527a2672cf9463db38bc29f61460e2a162f426b7852b7a7645fbd62084fd39a8e4d60e1958cce436dd8f57", - "0x811648140080fe55b8618f4cf17f3c5a250adb0cd53d885f2ddba835d2b4433188e41fc0661faac88e4ff910b16278c0", - "0xb85c7f1cfb0ed29addccf7546023a79249e8f15ac2d14a20accbfef4dd9dc11355d599815fa09d2b6b4e966e6ea8cff1", - "0xa10b5d8c260b159043b020d5dd62b3467df2671afea6d480ca9087b7e60ed170c82b121819d088315902842d66c8fb45", - "0x917e191df1bcf3f5715419c1e2191da6b8680543b1ba41fe84ed07ef570376e072c081beb67b375fca3565a2565bcabb", - "0x881fd967407390bfd7badc9ab494e8a287559a01eb07861f527207c127eadea626e9bcc5aa9cca2c5112fbac3b3f0e9c", - "0x959fd71149af82cc733619e0e5bf71760ca2650448c82984b3db74030d0e10f8ab1ce1609a6de6f470fe8b5bd90df5b3", - "0xa3370898a1c5f33d15adb4238df9a6c945f18b9ada4ce2624fc32a844f9ece4c916a64e9442225b6592afa06d2e015f2", - "0x817efb8a791435e4236f7d7b278181a5fa34587578c629dbc14fbf9a5c26772290611395eecd20222a4c58649fc256d8", - "0xa04c9876acf2cfdc8ef96de4879742709270fa1d03fe4c8511fbef2d59eb0aaf0336fa2c7dfe41a651157377fa217813", - "0x81e15875d7ea7f123e418edf14099f2e109d4f3a6ce0eb65f67fe9fb10d2f809a864a29f60ad3fc949f89e2596b21783", - "0xb49f529975c09e436e6bc202fdc16e3fdcbe056db45178016ad6fdece9faad4446343e83aed096209690b21a6910724f", - "0x879e8eda589e1a279f7f49f6dd0580788c040d973748ec4942dbe51ea8fbd05983cc919b78f0c6b92ef3292ae29db875", - "0x81a2b74b2118923f34139a102f3d95e7eee11c4c2929c2576dee200a5abfd364606158535a6c9e4178a6a83dbb65f3c4", - "0x8913f281d8927f2b45fc815d0f7104631cb7f5f7278a316f1327d670d15868daadd2a64e3eb98e1f53fe7e300338cc80", - "0xa6f815fba7ef9af7fbf45f93bc952e8b351f5de6568a27c7c47a00cb39a254c6b31753794f67940fc7d2e9cc581529f4", - "0xb3722a15c66a0014ce4d082de118def8d39190c15678a472b846225585f3a83756ae1b255b2e3f86a26168878e4773b2", - "0x817ae61ab3d0dd5b6e24846b5a5364b1a7dc2e77432d9fed587727520ae2f307264ea0948c91ad29f0aea3a11ff38624", - "0xb3db467464415fcad36dc1de2d6ba7686772a577cc2619242ac040d6734881a45d3b40ed4588db124e4289cfeec4bbf6", - "0xad66a14f5a54ac69603b16e5f1529851183da77d3cc60867f10aea41339dd5e06a5257982e9e90a352cdd32750f42ee4", - "0xadafa3681ef45d685555601a25a55cf23358319a17f61e2179e704f63df83a73bdd298d12cf6cef86db89bd17119e11d", - "0xa379dc44cb6dd3b9d378c07b2ec654fec7ca2f272de6ba895e3d00d20c9e4c5550498a843c8ac67e4221db2115bedc1c", - "0xb7bf81c267a78efc6b9e5a904574445a6487678d7ef70054e3e93ea6a23f966c2b68787f9164918e3b16d2175459ed92", - "0xb41d66a13a4afafd5760062b77f79de7e6ab8ccacde9c6c5116a6d886912fb491dc027af435b1b44aacc6af7b3c887f2", - "0x9904d23a7c1c1d2e4bab85d69f283eb0a8e26d46e8b7b30224438015c936729b2f0af7c7c54c03509bb0500acb42d8a4", - "0xae30d65e9e20c3bfd603994ae2b175ff691d51f3e24b2d058b3b8556d12ca4c75087809062dddd4aaac81c94d15d8a17", - "0x9245162fab42ac01527424f6013310c3eb462982518debef6c127f46ba8a06c705d7dc9f0a41e796ba8d35d60ae6cc64", - "0x87fab853638d7a29a20f3ba2b1a7919d023e9415bfa78ebb27973d8cbc7626f584dc5665d2e7ad71f1d760eba9700d88", - "0x85aac46ecd330608e5272430970e6081ff02a571e8ea444f1e11785ea798769634a22a142d0237f67b75369d3c484a8a", - "0x938c85ab14894cc5dfce3d80456f189a2e98eddbc8828f4ff6b1df1dcb7b42b17ca2ff40226a8a1390a95d63dca698dd", - "0xa18ce1f846e3e3c4d846822f60271eecf0f5d7d9f986385ac53c5ace9589dc7c0188910448c19b91341a1ef556652fa9", - "0x8611608a9d844f0e9d7584ad6ccf62a5087a64f764caf108db648a776b5390feb51e5120f0ef0e9e11301af3987dd7dc", - "0x8106333ba4b4de8d1ae43bc9735d3fea047392e88efd6a2fa6f7b924a18a7a265ca6123c3edc0f36307dd7fb7fe89257", - "0xa91426fa500951ff1b051a248c050b7139ca30dde8768690432d597d2b3c4357b11a577be6b455a1c5d145264dcf81fc", - "0xb7f9f90e0e450f37b081297f7f651bad0496a8b9afd2a4cf4120a2671aaaa8536dce1af301258bfbfdb122afa44c5048", - "0x84126da6435699b0c09fa4032dec73d1fca21d2d19f5214e8b0bea43267e9a8dd1fc44f8132d8315e734c8e2e04d7291", - "0xaff064708103884cb4f1a3c1718b3fc40a238d35cf0a7dc24bdf9823693b407c70da50df585bf5bc4e9c07d1c2d203e8", - "0xa8b40fc6533752983a5329c31d376c7a5c13ce6879cc7faee648200075d9cd273537001fb4c86e8576350eaac6ba60c2", - "0xa02db682bdc117a84dcb9312eb28fcbde12d49f4ce915cc92c610bb6965ec3cc38290f8c5b5ec70afe153956692cda95", - "0x86decd22b25d300508472c9ce75d3e465b737e7ce13bc0fcce32835e54646fe12322ba5bc457be18bfd926a1a6ca4a38", - "0xa18666ef65b8c2904fd598791f5627207165315a85ee01d5fb0e6b2e10bdd9b00babc447da5bd63445e3337de33b9b89", - "0x89bb0c06effadefdaf34ffe4b123e1678a90d4451ee856c863df1e752eef41fd984689ded8f0f878bf8916d5dd8e8024", - "0x97cfcba08ebec05d0073992a66b1d7d6fb9d95871f2cdc36db301f78bf8069294d1c259efef5c93d20dc937eedae3a1a", - "0xac2643b14ece79dcb2e289c96776a47e2bebd40dd6dc74fd035df5bb727b5596f40e3dd2d2202141e69b0993717ede09", - "0xa5e6fd88a2f9174d9bd4c6a55d9c30974be414992f22aa852f552c7648f722ed8077acf5aba030abd47939bb451b2c60", - "0x8ad40a612824a7994487731a40b311b7349038c841145865539c6ada75c56de6ac547a1c23df190e0caaafecddd80ccc", - "0x953a7cea1d857e09202c438c6108060961f195f88c32f0e012236d7a4b39d840c61b162ec86436e8c38567328bea0246", - "0x80d8b47a46dae1868a7b8ccfe7029445bbe1009dad4a6c31f9ef081be32e8e1ac1178c3c8fb68d3e536c84990cc035b1", - "0x81ecd99f22b3766ce0aca08a0a9191793f68c754fdec78b82a4c3bdc2db122bbb9ebfd02fc2dcc6e1567a7d42d0cc16a", - "0xb1dd0446bccc25846fb95d08c1c9cc52fb51c72c4c5d169ffde56ecfe800f108dc1106d65d5c5bd1087c656de3940b63", - "0xb87547f0931e164e96de5c550ca5aa81273648fe34f6e193cd9d69cf729cb432e17aa02e25b1c27a8a0d20a3b795e94e", - "0x820a94e69a927e077082aae66f6b292cfbe4589d932edf9e68e268c9bd3d71ef76cf7d169dd445b93967c25db11f58f1", - "0xb0d07ddf2595270c39adfa0c8cf2ab1322979b0546aa4d918f641be53cd97f36c879bb75d205e457c011aca3bbd9f731", - "0x8700b876b35b4b10a8a9372c5230acecd39539c1bb87515640293ad4464a9e02929d7d6a6a11112e8a29564815ac0de4", - "0xa61a601c5bb27dcb97e37c8e2b9ce479c6b192a5e04d9ed5e065833c5a1017ee5f237b77d1a17be5d48f8e7cc0bcacf6", - "0x92fb88fe774c1ba1d4a08cae3c0e05467ad610e7a3f1d2423fd47751759235fe0a3036db4095bd6404716aa03820f484", - "0xb274f140d77a3ce0796f5e09094b516537ccaf27ae1907099bff172e6368ba85e7c3ef8ea2a07457cac48ae334da95b3", - "0xb2292d9181f16581a9a9142490b2bdcdfb218ca6315d1effc8592100d792eb89d5356996c890441f04f2b4a95763503e", - "0x8897e73f576d86bc354baa3bd96e553107c48cf5889dcc23c5ba68ab8bcd4e81f27767be2233fdfa13d39f885087e668", - "0xa29eac6f0829791c728d71abc49569df95a4446ecbfc534b39f24f56c88fe70301838dfc1c19751e7f3c5c1b8c6af6a0", - "0x9346dc3720adc5df500a8df27fd9c75ef38dc5c8f4e8ed66983304750e66d502c3c59b8e955be781b670a0afc70a2167", - "0x9566d534e0e30a5c5f1428665590617e95fd05d45f573715f58157854ad596ece3a3cfec61356aee342308d623e029d5", - "0xa464fb8bffe6bd65f71938c1715c6e296cc6d0311a83858e4e7eb5873b7f2cf0c584d2101e3407b85b64ca78b2ac93ce", - "0xb54088f7217987c87e9498a747569ac5b2f8afd5348f9c45bf3fd9fbf713a20f495f49c8572d087efe778ac7313ad6d3", - "0x91fa9f5f8000fe050f5b224d90b59fcce13c77e903cbf98ded752e5b3db16adb2bc1f8c94be48b69f65f1f1ad81d6264", - "0x92d04a5b0ac5d8c8e313709b432c9434ecd3e73231f01e9b4e7952b87df60cbfa97b5dedd2200bd033b4b9ea8ba45cc1", - "0xa94b90ad3c3d6c4bbe169f8661a790c40645b40f0a9d1c7220f01cf7fc176e04d80bab0ced9323fcafb93643f12b2760", - "0x94d86149b9c8443b46196f7e5a3738206dd6f3be7762df488bcbb9f9ee285a64c997ed875b7b16b26604fa59020a8199", - "0x82efe4ae2c50a2d7645240c173a047f238536598c04a2c0b69c96e96bd18e075a99110f1206bc213f39edca42ba00cc1", - "0xab8667685f831bc14d4610f84a5da27b4ea5b133b4d991741a9e64dceb22cb64a3ce8f1b6e101d52af6296df7127c9ad", - "0x83ba433661c05dcc5d562f4a9a261c8110dac44b8d833ae1514b1fc60d8b4ee395b18804baea04cb10adb428faf713c3", - "0xb5748f6f660cc5277f1211d2b8649493ed8a11085b871cd33a5aea630abd960a740f08c08be5f9c21574600ac9bf5737", - "0xa5c8dd12af48fb710642ad65ebb97ca489e8206741807f7acfc334f8035d3c80593b1ff2090c9bb7bd138f0c48714ca8", - "0xa2b382fd5744e3babf454b1d806cc8783efeb4761bc42b6914ea48a46a2eae835efbe0a18262b6bc034379e03cf1262b", - "0xb3145ffaf603f69f15a64936d32e3219eea5ed49fdfd2f5bf40ea0dfd974b36fb6ff12164d4c2282d892db4cf3ff3ce1", - "0x87a316fb213f4c5e30c5e3face049db66be4f28821bd96034714ec23d3e97849d7b301930f90a4323c7ccf53de23050c", - "0xb9de09a919455070fed6220fc179c8b7a4c753062bcd27acf28f5b9947a659c0b364298daf7c85c4ca6fca7f945add1f", - "0x806fbd98d411b76979464c40ad88bc07a151628a27fcc1012ba1dfbaf5b5cc9d962fb9b3386008978a12515edce934bc", - "0xa15268877fae0d21610ae6a31061ed7c20814723385955fac09fdc9693a94c33dea11db98bb89fdfe68f933490f5c381", - "0x8d633fb0c4da86b2e0b37d8fad5972d62bff2ac663c5ec815d095cd4b7e1fe66ebef2a2590995b57eaf941983c7ad7a4", - "0x8139e5dd9cf405e8ef65f11164f0440827d98389ce1b418b0c9628be983a9ddd6cf4863036ccb1483b40b8a527acd9ed", - "0x88b15fa94a08eac291d2b94a2b30eb851ff24addf2cc30b678e72e32cfcb3424cf4b33aa395d741803f3e578ddf524de", - "0xb5eaf0c8506e101f1646bcf049ee38d99ea1c60169730da893fd6020fd00a289eb2f415947e44677af49e43454a7b1be", - "0x8489822ad0647a7e06aa2aa5595960811858ddd4542acca419dd2308a8c5477648f4dd969a6740bb78aa26db9bfcc555", - "0xb1e9a7b9f3423c220330d45f69e45fa03d7671897cf077f913c252e3e99c7b1b1cf6d30caad65e4228d5d7b80eb86e5e", - "0xb28fe9629592b9e6a55a1406903be76250b1c50c65296c10c5e48c64b539fb08fe11f68cf462a6edcbba71b0cee3feb2", - "0xa41acf96a02c96cd8744ff6577c244fc923810d17ade133587e4c223beb7b4d99fa56eae311a500d7151979267d0895c", - "0x880798938fe4ba70721be90e666dfb62fcab4f3556fdb7b0dc8ec5bc34f6b4513df965eae78527136eb391889fe2caf9", - "0x98d4d89d358e0fb7e212498c73447d94a83c1b66e98fc81427ab13acddb17a20f52308983f3a5a8e0aaacec432359604", - "0x81430b6d2998fc78ba937a1639c6020199c52da499f68109da227882dc26d005b73d54c5bdcac1a04e8356a8ca0f7017", - "0xa8d906a4786455eb74613aba4ce1c963c60095ffb8658d368df9266fdd01e30269ce10bf984e7465f34b4fd83beba26a", - "0xaf54167ac1f954d10131d44a8e0045df00d581dd9e93596a28d157543fbe5fb25d213806ed7fb3cba6b8f5b5423562db", - "0x8511e373a978a12d81266b9afbd55035d7bc736835cfa921903a92969eeba3624437d1346b55382e61415726ab84a448", - "0x8cf43eea93508ae586fa9a0f1354a1e16af659782479c2040874a46317f9e8d572a23238efa318fdfb87cc63932602b7", - "0xb0bdd3bacff077173d302e3a9678d1d37936188c7ecc34950185af6b462b7c679815176f3cce5db19aac8b282f2d60ad", - "0xa355e9b87f2f2672052f5d4d65b8c1c827d24d89b0d8594641fccfb69aef1b94009105f3242058bb31c8bf51caae5a41", - "0xb8baa9e4b950b72ff6b88a6509e8ed1304bc6fd955748b2e59a523a1e0c5e99f52aec3da7fa9ff407a7adf259652466c", - "0x840bc3dbb300ea6f27d1d6dd861f15680bd098be5174f45d6b75b094d0635aced539fa03ddbccb453879de77fb5d1fe9", - "0xb4bc7e7e30686303856472bae07e581a0c0bfc815657c479f9f5931cff208d5c12930d2fd1ff413ebd8424bcd7a9b571", - "0x89b5d514155d7999408334a50822508b9d689add55d44a240ff2bdde2eee419d117031f85e924e2a2c1ca77db9b91eea", - "0xa8604b6196f87a04e1350302e8aa745bba8dc162115d22657b37a1d1a98cb14876ddf7f65840b5dbd77e80cd22b4256c", - "0x83cb7acdb9e03247515bb2ce0227486ccf803426717a14510f0d59d45e998b245797d356f10abca94f7a14e1a2f0d552", - "0xaeb3266a9f16649210ab2df0e1908ac259f34ce1f01162c22b56cf1019096ee4ea5854c36e30bb2feb06c21a71e8a45c", - "0x89e72e86edf2aa032a0fc9acf4d876a40865fbb2c8f87cb7e4d88856295c4ac14583e874142fd0c314a49aba68c0aa3c", - "0x8c3576eba0583c2a7884976b4ed11fe1fda4f6c32f6385d96c47b0e776afa287503b397fa516a455b4b8c3afeedc76db", - "0xa31e5b633bda9ffa174654fee98b5d5930a691c3c42fcf55673d927dbc8d91c58c4e42e615353145431baa646e8bbb30", - "0x89f2f3f7a8da1544f24682f41c68114a8f78c86bd36b066e27da13acb70f18d9f548773a16bd8e24789420e17183f137", - "0xada27fa4e90a086240c9164544d2528621a415a5497badb79f8019dc3dce4d12eb6b599597e47ec6ac39c81efda43520", - "0x90dc1eb21bf21c0187f359566fc4bf5386abea52799306a0e5a1151c0817c5f5bc60c86e76b1929c092c0f3ff48cedd2", - "0xb702a53ebcc17ae35d2e735a347d2c700e9cbef8eadbece33cac83df483b2054c126593e1f462cfc00a3ce9d737e2af5", - "0x9891b06455ec925a6f8eafffba05af6a38cc5e193acaaf74ffbf199df912c5197106c5e06d72942bbb032ce277b6417f", - "0x8c0ee71eb01197b019275bcf96cae94e81d2cdc3115dbf2d8e3080074260318bc9303597e8f72b18f965ad601d31ec43", - "0x8aaf580aaf75c1b7a5f99ccf60503506e62058ef43b28b02f79b8536a96be3f019c9f71caf327b4e6730134730d1bef5", - "0xae6f9fc21dd7dfa672b25a87eb0a41644f7609fab5026d5cedb6e43a06dbbfd6d6e30322a2598c8dedde88c52eaed626", - "0x8159b953ffece5693edadb2e906ebf76ff080ee1ad22698950d2d3bfc36ac5ea78f58284b2ca180664452d55bd54716c", - "0xab7647c32ca5e9856ac283a2f86768d68de75ceeba9e58b74c5324f8298319e52183739aba4340be901699d66ac9eb3f", - "0xa4d85a5701d89bcfaf1572db83258d86a1a0717603d6f24ac2963ffcf80f1265e5ab376a4529ca504f4396498791253c", - "0x816080c0cdbfe61b4d726c305747a9eb58ac26d9a35f501dd32ba43c098082d20faf3ccd41aad24600aa73bfa453dfac", - "0x84f3afac024f576b0fd9acc6f2349c2fcefc3f77dbe5a2d4964d14b861b88e9b1810334b908cf3427d9b67a8aee74b18", - "0x94b390655557b1a09110018e9b5a14490681ade275bdc83510b6465a1218465260d9a7e2a6e4ec700f58c31dc3659962", - "0xa8c66826b1c04a2dd4c682543242e7a57acae37278bd09888a3d17747c5b5fec43548101e6f46d703638337e2fd3277b", - "0x86e6f4608a00007fa533c36a5b054c5768ccafe41ad52521d772dcae4c8a4bcaff8f7609be30d8fab62c5988cbbb6830", - "0x837da4cf09ae8aa0bceb16f8b3bfcc3b3367aecac9eed6b4b56d7b65f55981ef066490764fb4c108792623ecf8cad383", - "0x941ff3011462f9b5bf97d8cbdb0b6f5d37a1b1295b622f5485b7d69f2cb2bcabc83630dae427f0259d0d9539a77d8424", - "0xb99e5d6d82aa9cf7d5970e7f710f4039ac32c2077530e4c2779250c6b9b373bc380adb0a03b892b652f649720672fc8c", - "0xa791c78464b2d65a15440b699e1e30ebd08501d6f2720adbc8255d989a82fcded2f79819b5f8f201bed84a255211b141", - "0x84af7ad4a0e31fcbb3276ab1ad6171429cf39adcf78dc03750dc5deaa46536d15591e26d53e953dfb31e1622bc0743ab", - "0xa833e62fe97e1086fae1d4917fbaf09c345feb6bf1975b5cb863d8b66e8d621c7989ab3dbecda36bc9eaffc5eaa6fa66", - "0xb4ef79a46a2126f53e2ebe62770feb57fd94600be29459d70a77c5e9cc260fa892be06cd60f886bf48459e48eb50d063", - "0xb43b8f61919ea380bf151c294e54d3a3ff98e20d1ee5efbfe38aa2b66fafbc6a49739793bd5cb1c809f8b30466277c3a", - "0xab37735af2412d2550e62df9d8b3b5e6f467f20de3890bf56faf1abf2bf3bd1d98dc3fa0ad5e7ab3fce0fa20409eb392", - "0x82416b74b1551d484250d85bb151fabb67e29cce93d516125533df585bc80779ab057ea6992801a3d7d5c6dcff87a018", - "0x8145d0787f0e3b5325190ae10c1d6bee713e6765fb6a0e9214132c6f78f4582bb2771aaeae40d3dad4bafb56bf7e36d8", - "0xb6935886349ecbdd5774e12196f4275c97ec8279fdf28ccf940f6a022ebb6de8e97d6d2173c3fe402cbe9643bed3883b", - "0x87ef9b4d3dc71ac86369f8ed17e0dd3b91d16d14ae694bc21a35b5ae37211b043d0e36d8ff07dcc513fb9e6481a1f37f", - "0xae1d0ded32f7e6f1dc8fef495879c1d9e01826f449f903c1e5034aeeabc5479a9e323b162b688317d46d35a42d570d86", - "0xa40d16497004db4104c6794e2f4428d75bdf70352685944f3fbe17526df333e46a4ca6de55a4a48c02ecf0bde8ba03c0", - "0x8d45121efba8cc308a498e8ee39ea6fa5cae9fb2e4aab1c2ff9d448aa8494ccbec9a078f978a86fcd97b5d5e7be7522a", - "0xa8173865c64634ba4ac2fa432740f5c05056a9deaf6427cb9b4b8da94ca5ddbc8c0c5d3185a89b8b28878194de9cdfcd", - "0xb6ec06a74d690f6545f0f0efba236e63d1fdfba54639ca2617408e185177ece28901c457d02b849fd00f1a53ae319d0a", - "0xb69a12df293c014a40070e3e760169b6f3c627caf9e50b35a93f11ecf8df98b2bc481b410eecb7ab210bf213bbe944de", - "0x97e7dc121795a533d4224803e591eef3e9008bab16f12472210b73aaf77890cf6e3877e0139403a0d3003c12c8f45636", - "0xacdfa6fdd4a5acb7738cc8768f7cba84dbb95c639399b291ae8e4e63df37d2d4096900a84d2f0606bf534a9ccaa4993f", - "0x86ee253f3a9446a33e4d1169719b7d513c6b50730988415382faaf751988c10a421020609f7bcdef91be136704b906e2", - "0xaac9438382a856caf84c5a8a234282f71b5fc5f65219103b147e7e6cf565522285fbfd7417b513bdad8277a00f652ca1", - "0x83f3799d8e5772527930f5dc071a2e0a65471618993ec8990a96ccdeee65270e490bda9d26bb877612475268711ffd80", - "0x93f28a81ac8c0ec9450b9d762fae9c7f8feaace87a6ee6bd141ef1d2d0697ef1bbd159fe6e1de640dbdab2b0361fca8a", - "0xa0825c95ba69999b90eac3a31a3fd830ea4f4b2b7409bde5f202b61d741d6326852ce790f41de5cb0eccec7af4db30c1", - "0x83924b0e66233edd603c3b813d698daa05751fc34367120e3cf384ea7432e256ccee4d4daf13858950549d75a377107d", - "0x956fd9fa58345277e06ba2ec72f49ed230b8d3d4ff658555c52d6cddeb84dd4e36f1a614f5242d5ca0192e8daf0543c2", - "0x944869912476baae0b114cced4ff65c0e4c90136f73ece5656460626599051b78802df67d7201c55d52725a97f5f29fe", - "0x865cb25b64b4531fb6fe4814d7c8cd26b017a6c6b72232ff53defc18a80fe3b39511b23f9e4c6c7249d06e03b2282ed2", - "0x81e09ff55214960775e1e7f2758b9a6c4e4cd39edf7ec1adfaad51c52141182b79fe2176b23ddc7df9fd153e5f82d668", - "0xb31006896f02bc90641121083f43c3172b1039334501fbaf1672f7bf5d174ddd185f945adf1a9c6cf77be34c5501483d", - "0x88b92f6f42ae45e9f05b16e52852826e933efd0c68b0f2418ac90957fd018df661bc47c8d43c2a7d7bfcf669dab98c3c", - "0x92fc68f595853ee8683930751789b799f397135d002eda244fe63ecef2754e15849edde3ba2f0cc8b865c9777230b712", - "0x99ca06a49c5cd0bb097c447793fcdd809869b216a34c66c78c7e41e8c22f05d09168d46b8b1f3390db9452d91bc96dea", - "0xb48b9490a5d65296802431852d548d81047bbefc74fa7dc1d4e2a2878faacdfcb365ae59209cb0ade01901a283cbd15d", - "0xaff0fdbef7c188b120a02bc9085d7b808e88f73973773fef54707bf2cd772cd066740b1b6f4127b5c349f657bd97e738", - "0x966fd4463b4f43dd8ccba7ad50baa42292f9f8b2e70da23bb6780e14155d9346e275ef03ddaf79e47020dcf43f3738bd", - "0x9330c3e1fadd9e08ac85f4839121ae20bbeb0a5103d84fa5aadbd1213805bdcda67bf2fb75fc301349cbc851b5559d20", - "0x993bb99867bd9041a71a55ad5d397755cfa7ab6a4618fc526179bfc10b7dc8b26e4372fe9a9b4a15d64f2b63c1052dda", - "0xa29b59bcfab51f9b3c490a3b96f0bf1934265c315349b236012adbd64a56d7f6941b2c8cc272b412044bc7731f71e1dc", - "0xa65c9cefe1fc35d089fe8580c2e7671ebefdb43014ac291528ff4deefd4883fd4df274af83711dad610dad0d615f9d65", - "0x944c78c56fb227ae632805d448ca3884cd3d2a89181cead3d2b7835e63297e6d740aa79a112edb1d4727824991636df5", - "0xa73d782da1db7e4e65d7b26717a76e16dd9fab4df65063310b8e917dc0bc24e0d6755df5546c58504d04d9e68c3b474a", - "0xaf80f0b87811ae3124f68108b4ca1937009403f87928bbc53480e7c5408d072053ace5eeaf5a5aba814dab8a45502085", - "0x88aaf1acfc6e2e19b8387c97da707cb171c69812fefdd4650468e9b2c627bd5ccfb459f4d8e56bdfd84b09ddf87e128f", - "0x92c97276ff6f72bab6e9423d02ad6dc127962dbce15a0dd1e4a393b4510c555df6aa27be0f697c0d847033a9ca8b8dfd", - "0xa0e07d43d96e2d85b6276b3c60aadb48f0aedf2de8c415756dc597249ea64d2093731d8735231dadc961e5682ac59479", - "0xadc9e6718a8f9298957d1da3842a7751c5399bbdf56f8de6c1c4bc39428f4aee6f1ba6613d37bf46b9403345e9d6fc81", - "0x951da434da4b20d949b509ceeba02e24da7ed2da964c2fcdf426ec787779c696b385822c7dbea4df3e4a35921f1e912c", - "0xa04cbce0d2b2e87bbf038c798a12ec828423ca6aca08dc8d481cf6466e3c9c73d4d4a7fa47df9a7e2e15aae9e9f67208", - "0x8f855cca2e440d248121c0469de1f94c2a71b8ee2682bbad3a78243a9e03da31d1925e6760dbc48a1957e040fae9abe8", - "0xb642e5b17c1df4a4e101772d73851180b3a92e9e8b26c918050f51e6dd3592f102d20b0a1e96f0e25752c292f4c903ff", - "0xa92454c300781f8ae1766dbbb50a96192da7d48ef4cbdd72dd8cbb44c6eb5913c112cc38e9144615fdc03684deb99420", - "0x8b74f7e6c2304f8e780df4649ef8221795dfe85fdbdaa477a1542d135b75c8be45bf89adbbb6f3ddf54ca40f02e733e9", - "0x85cf66292cbb30cec5fd835ab10c9fcb3aea95e093aebf123e9a83c26f322d76ebc89c4e914524f6c5f6ee7d74fc917d", - "0xae0bfe0cdc97c09542a7431820015f2d16067b30dca56288013876025e81daa8c519e5e347268e19aa1a85fa1dc28793", - "0x921322fc6a47dc091afa0ad6df18ed14cde38e48c6e71550aa513918b056044983aee402de21051235eecf4ce8040fbe", - "0x96c030381e97050a45a318d307dcb3c8377b79b4dd5daf6337cded114de26eb725c14171b9b8e1b3c08fe1f5ea6b49e0", - "0x90c23b86b6111818c8baaf53a13eaee1c89203b50e7f9a994bf0edf851919b48edbac7ceef14ac9414cf70c486174a77", - "0x8bf6c301240d2d1c8d84c71d33a6dfc6d9e8f1cfae66d4d0f7a256d98ae12b0bcebfa94a667735ee89f810bcd7170cff", - "0xa41a4ffbbea0e36874d65c009ee4c3feffff322f6fc0e30d26ee4dbc1f46040d05e25d9d0ecb378cef0d24a7c2c4b850", - "0xa8d4cdd423986bb392a0a92c12a8bd4da3437eec6ef6af34cf5310944899287452a2eb92eb5386086d5063381189d10e", - "0xa81dd26ec057c4032a4ed7ad54d926165273ed51d09a1267b2e477535cf6966835a257c209e4e92d165d74fa75695fa3", - "0x8d7f708c3ee8449515d94fc26b547303b53d8dd55f177bc3b25d3da2768accd9bc8e9f09546090ebb7f15c66e6c9c723", - "0x839ba65cffcd24cfffa7ab3b21faabe3c66d4c06324f07b2729c92f15cad34e474b0f0ddb16cd652870b26a756b731d3", - "0x87f1a3968afec354d92d77e2726b702847c6afcabb8438634f9c6f7766de4c1504317dc4fa9a4a735acdbf985e119564", - "0x91a8a7fd6542f3e0673f07f510d850864b34ac087eb7eef8845a1d14b2b1b651cbdc27fa4049bdbf3fea54221c5c8549", - "0xaef3cf5f5e3a2385ead115728d7059e622146c3457d266c612e778324b6e06fbfb8f98e076624d2f3ce1035d65389a07", - "0x819915d6232e95ccd7693fdd78d00492299b1983bc8f96a08dcb50f9c0a813ed93ae53c0238345d5bea0beda2855a913", - "0x8e9ba68ded0e94935131b392b28218315a185f63bf5e3c1a9a9dd470944509ca0ba8f6122265f8da851b5cc2abce68f1", - "0xb28468e9b04ee9d69003399a3cf4457c9bf9d59f36ab6ceeb8e964672433d06b58beeea198fedc7edbaa1948577e9fa2", - "0xa633005e2c9f2fd94c8bce2dd5bb708fe946b25f1ec561ae65e54e15cdd88dc339f1a083e01f0d39610c8fe24151aaf0", - "0x841d0031e22723f9328dd993805abd13e0c99b0f59435d2426246996b08d00ce73ab906f66c4eab423473b409e972ce0", - "0x85758d1b084263992070ec8943f33073a2d9b86a8606672550c17545507a5b3c88d87382b41916a87ee96ff55a7aa535", - "0x8581b06b0fc41466ef94a76a1d9fb8ae0edca6d018063acf6a8ca5f4b02d76021902feba58972415691b4bdbc33ae3b4", - "0x83539597ff5e327357ee62bc6bf8c0bcaec2f227c55c7c385a4806f0d37fb461f1690bad5066b8a5370950af32fafbef", - "0xaee3557290d2dc10827e4791d00e0259006911f3f3fce4179ed3c514b779160613eca70f720bff7804752715a1266ffa", - "0xb48d2f0c4e90fc307d5995464e3f611a9b0ef5fe426a289071f4168ed5cc4f8770c9332960c2ca5c8c427f40e6bb389f", - "0x847af8973b4e300bb06be69b71b96183fd1a0b9d51b91701bef6fcfde465068f1eb2b1503b07afda380f18d69de5c9e1", - "0xa70a6a80ce407f07804c0051ac21dc24d794b387be94eb24e1db94b58a78e1bcfb48cd0006db8fc1f9bedaece7a44fbe", - "0xb40e942b8fa5336910ff0098347df716bff9d1fa236a1950c16eeb966b3bc1a50b8f7b0980469d42e75ae13ced53cead", - "0xb208fabaa742d7db3148515330eb7a3577487845abdb7bd9ed169d0e081db0a5816595c33d375e56aeac5b51e60e49d3", - "0xb7c8194b30d3d6ef5ab66ec88ad7ebbc732a3b8a41731b153e6f63759a93f3f4a537eab9ad369705bd730184bdbbdc34", - "0x9280096445fe7394d04aa1bc4620c8f9296e991cc4d6c131bd703cb1cc317510e6e5855ac763f4d958c5edfe7eebeed7", - "0xabc2aa4616a521400af1a12440dc544e3c821313d0ab936c86af28468ef8bbe534837e364598396a81cf8d06274ed5a6", - "0xb18ca8a3325adb0c8c18a666d4859535397a1c3fe08f95eebfac916a7a99bbd40b3c37b919e8a8ae91da38bc00fa56c0", - "0x8a40c33109ecea2a8b3558565877082f79121a432c45ec2c5a5e0ec4d1c203a6788e6b69cb37f1fd5b8c9a661bc5476d", - "0x88c47301dd30998e903c84e0b0f2c9af2e1ce6b9f187dab03528d44f834dc991e4c86d0c474a2c63468cf4020a1e24a0", - "0x920c832853e6ab4c851eecfa9c11d3acc7da37c823be7aa1ab15e14dfd8beb5d0b91d62a30cec94763bd8e4594b66600", - "0x98e1addbe2a6b8edc7f12ecb9be81c3250aeeca54a1c6a7225772ca66549827c15f3950d01b8eb44aecb56fe0fff901a", - "0x8cfb0fa1068be0ec088402f5950c4679a2eb9218c729da67050b0d1b2d7079f3ddf4bf0f57d95fe2a8db04bc6bcdb20c", - "0xb70f381aafe336b024120453813aeab70baac85b9c4c0f86918797b6aee206e6ed93244a49950f3d8ec9f81f4ac15808", - "0xa4c8edf4aa33b709a91e1062939512419711c1757084e46f8f4b7ed64f8e682f4e78b7135920c12f0eb0422fe9f87a6a", - "0xb4817e85fd0752d7ebb662d3a51a03367a84bac74ebddfba0e5af5e636a979500f72b148052d333b3dedf9edd2b4031b", - "0xa87430169c6195f5d3e314ff2d1c2f050e766fd5d2de88f5207d72dba4a7745bb86d0baca6e9ae156582d0d89e5838c7", - "0x991b00f8b104566b63a12af4826b61ce7aa40f4e5b8fff3085e7a99815bdb4471b6214da1e480214fac83f86a0b93cc5", - "0xb39966e3076482079de0678477df98578377a094054960ee518ef99504d6851f8bcd3203e8da5e1d4f6f96776e1fe6eb", - "0xa448846d9dc2ab7a0995fa44b8527e27f6b3b74c6e03e95edb64e6baa4f1b866103f0addb97c84bef1d72487b2e21796", - "0x894bec21a453ae84b592286e696c35bc30e820e9c2fd3e63dd4fbe629e07df16439c891056070faa490155f255bf7187", - "0xa9ec652a491b11f6a692064e955f3f3287e7d2764527e58938571469a1e29b5225b9415bd602a45074dfbfe9c131d6ca", - "0xb39d37822e6cbe28244b5f42ce467c65a23765bd16eb6447c5b3e942278069793763483dafd8c4dd864f8917aad357fe", - "0x88dba51133f2019cb266641c56101e3e5987d3b77647a2e608b5ff9113dfc5f85e2b7c365118723131fbc0c9ca833c9c", - "0xb566579d904b54ecf798018efcb824dccbebfc6753a0fd2128ac3b4bd3b038c2284a7c782b5ca6f310eb7ea4d26a3f0a", - "0xa97a55c0a492e53c047e7d6f9d5f3e86fb96f3dddc68389c0561515343b66b4bc02a9c0d5722dff1e3445308240b27f7", - "0xa044028ab4bcb9e1a2b9b4ca4efbf04c5da9e4bf2fff0e8bd57aa1fc12a71e897999c25d9117413faf2f45395dee0f13", - "0xa78dc461decbeaeed8ebd0909369b491a5e764d6a5645a7dac61d3140d7dc0062526f777b0eb866bff27608429ebbdde", - "0xb2c2a8991f94c39ca35fea59f01a92cb3393e0eccb2476dfbf57261d406a68bd34a6cff33ed80209991688c183609ef4", - "0x84189eefb521aff730a4fd3fd5b10ddfd29f0d365664caef63bb015d07e689989e54c33c2141dd64427805d37a7e546e", - "0x85ac80bd734a52235da288ff042dea9a62e085928954e8eacd2c751013f61904ed110e5b3afe1ab770a7e6485efb7b5e", - "0x9183a560393dcb22d0d5063e71182020d0fbabb39e32493eeffeb808df084aa243eb397027f150b55a247d1ed0c8513e", - "0x81c940944df7ecc58d3c43c34996852c3c7915ed185d7654627f7af62abae7e0048dd444a6c09961756455000bd96d09", - "0xaa8c34e164019743fd8284b84f06c3b449aae7996e892f419ee55d82ad548cb300fd651de329da0384243954c0ef6a60", - "0x89a7b7bdfc7e300d06a14d463e573d6296d8e66197491900cc9ae49504c4809ff6e61b758579e9091c61085ba1237b83", - "0x878d21809ba540f50bd11f4c4d9590fb6f3ab9de5692606e6e2ef4ed9d18520119e385be5e1f4b3f2e2b09c319f0e8fc", - "0x8eb248390193189cf0355365e630b782cd15751e672dc478b39d75dc681234dcd9309df0d11f4610dbb249c1e6be7ef9", - "0xa1d7fb3aecb896df3a52d6bd0943838b13f1bd039c936d76d03de2044c371d48865694b6f532393b27fd10a4cf642061", - "0xa34bca58a24979be442238cbb5ece5bee51ae8c0794dd3efb3983d4db713bc6f28a96e976ac3bd9a551d3ed9ba6b3e22", - "0x817c608fc8cacdd178665320b5a7587ca21df8bdd761833c3018b967575d25e3951cf3d498a63619a3cd2ad4406f5f28", - "0x86c95707db0495689afd0c2e39e97f445f7ca0edffad5c8b4cacd1421f2f3cc55049dfd504f728f91534e20383955582", - "0x99c3b0bb15942c301137765d4e19502f65806f3b126dc01a5b7820c87e8979bce6a37289a8f6a4c1e4637227ad5bf3bf", - "0x8aa1518a80ea8b074505a9b3f96829f5d4afa55a30efe7b4de4e5dbf666897fdd2cf31728ca45921e21a78a80f0e0f10", - "0x8d74f46361c79e15128ac399e958a91067ef4cec8983408775a87eca1eed5b7dcbf0ddf30e66f51780457413496c7f07", - "0xa41cde4a786b55387458a1db95171aca4fd146507b81c4da1e6d6e495527c3ec83fc42fad1dfe3d92744084a664fd431", - "0x8c352852c906fae99413a84ad11701f93f292fbf7bd14738814f4c4ceab32db02feb5eb70bc73898b0bc724a39d5d017", - "0xa5993046e8f23b71ba87b7caa7ace2d9023fb48ce4c51838813174880d918e9b4d2b0dc21a2b9c6f612338c31a289df8", - "0x83576d3324bf2d8afbfb6eaecdc5d767c8e22e7d25160414924f0645491df60541948a05e1f4202e612368e78675de8a", - "0xb43749b8df4b15bc9a3697e0f1c518e6b04114171739ef1a0c9c65185d8ec18e40e6954d125cbc14ebc652cf41ad3109", - "0xb4eebd5d80a7327a040cafb9ccdb12b2dfe1aa86e6bc6d3ac8a57fadfb95a5b1a7332c66318ff72ba459f525668af056", - "0x9198be7f1d413c5029b0e1c617bcbc082d21abe2c60ec8ce9b54ca1a85d3dba637b72fda39dae0c0ae40d047eab9f55a", - "0x8d96a0232832e24d45092653e781e7a9c9520766c3989e67bbe86b3a820c4bf621ea911e7cd5270a4bfea78b618411f6", - "0x8d7160d0ea98161a2d14d46ef01dff72d566c330cd4fabd27654d300e1bc7644c68dc8eabf2a20a59bfe7ba276545f9b", - "0xabb60fce29dec7ba37e3056e412e0ec3e05538a1fc0e2c68877378c867605966108bc5742585ab6a405ce0c962b285b6", - "0x8fabffa3ed792f05e414f5839386f6449fd9f7b41a47595c5d71074bd1bb3784cc7a1a7e1ad6b041b455035957e5b2dc", - "0x90ff017b4804c2d0533b72461436b10603ab13a55f86fd4ec11b06a70ef8166f958c110519ca1b4cc7beba440729fe2d", - "0xb340cfd120f6a4623e3a74cf8c32bfd7cd61a280b59dfd17b15ca8fae4d82f64a6f15fbde4c02f424debc72b7db5fe67", - "0x871311c9c7220c932e738d59f0ecc67a34356d1429fe570ca503d340c9996cb5ee2cd188fad0e3bd16e4c468ec1dbebd", - "0xa772470262186e7b94239ba921b29f2412c148d6f97c4412e96d21e55f3be73f992f1ad53c71008f0558ec3f84e2b5a7", - "0xb2a897dcb7ffd6257f3f2947ec966f2077d57d5191a88840b1d4f67effebe8c436641be85524d0a21be734c63ab5965d", - "0xa044f6eacc48a4a061fa149500d96b48cbf14853469aa4d045faf3dca973be1bd4b4ce01646d83e2f24f7c486d03205d", - "0x981af5dc2daa73f7fa9eae35a93d81eb6edba4a7f673b55d41f6ecd87a37685d31bb40ef4f1c469b3d72f2f18b925a17", - "0x912d2597a07864de9020ac77083eff2f15ceb07600f15755aba61251e8ce3c905a758453b417f04d9c38db040954eb65", - "0x9642b7f6f09394ba5e0805734ef6702c3eddf9eea187ba98c676d5bbaec0e360e3e51dc58433aaa1e2da6060c8659cb7", - "0x8ab3836e0a8ac492d5e707d056310c4c8e0489ca85eb771bff35ba1d658360084e836a6f51bb990f9e3d2d9aeb18fbb5", - "0x879e058e72b73bb1f4642c21ffdb90544b846868139c6511f299aafe59c2d0f0b944dffc7990491b7c4edcd6a9889250", - "0xb9e60b737023f61479a4a8fd253ed0d2a944ea6ba0439bbc0a0d3abf09b0ad1f18d75555e4a50405470ae4990626f390", - "0xb9c2535d362796dcd673640a9fa2ebdaec274e6f8b850b023153b0a7a30fffc87f96e0b72696f647ebe7ab63099a6963", - "0x94aeff145386a087b0e91e68a84a5ede01f978f9dd9fe7bebca78941938469495dc30a96bba9508c0d017873aeea9610", - "0x98b179f8a3d9f0d0a983c30682dd425a2ddc7803be59bd626c623c8951a5179117d1d2a68254c95c9952989877d0ee55", - "0x889ecf5f0ee56938273f74eb3e9ecfb5617f04fb58e83fe4c0e4aef51615cf345bc56f3f61b17f6eed3249d4afd54451", - "0xa0f2b2c39bcea4b50883e2587d16559e246248a66ecb4a4b7d9ab3b51fb39fe98d83765e087eee37a0f86b0ba4144c02", - "0xb2a61e247ed595e8a3830f7973b07079cbda510f28ad8c78c220b26cb6acde4fbb5ee90c14a665f329168ee951b08cf0", - "0x95bd0fcfb42f0d6d8a8e73d7458498a85bcddd2fb132fd7989265648d82ac2707d6d203fac045504977af4f0a2aca4b7", - "0x843e5a537c298666e6cf50fcc044f13506499ef83c802e719ff2c90e85003c132024e04711be7234c04d4b0125512d5d", - "0xa46d1797c5959dcd3a5cfc857488f4d96f74277c3d13b98b133620192f79944abcb3a361d939a100187f1b0856eae875", - "0xa1c7786736d6707a48515c38660615fcec67eb8a2598f46657855215f804fd72ab122d17f94fcffad8893f3be658dca7", - "0xb23dc9e610abc7d8bd21d147e22509a0fa49db5be6ea7057b51aae38e31654b3aa044df05b94b718153361371ba2f622", - "0xb00cc8f257d659c22d30e6d641f79166b1e752ea8606f558e4cad6fc01532e8319ea4ee12265ba4140ac45aa4613c004", - "0xac7019af65221b0cc736287b32d7f1a3561405715ba9a6a122342e04e51637ba911c41573de53e4781f2230fdcb2475f", - "0x81a630bc41b3da8b3eb4bf56cba10cd9f93153c3667f009dc332287baeb707d505fb537e6233c8e53d299ec0f013290c", - "0xa6b7aea5c545bb76df0f230548539db92bc26642572cb7dd3d5a30edca2b4c386f44fc8466f056b42de2a452b81aff5b", - "0x8271624ff736b7b238e43943c81de80a1612207d32036d820c11fc830c737972ccc9c60d3c2359922b06652311e3c994", - "0x8a684106458cb6f4db478170b9ad595d4b54c18bf63b9058f095a2fa1b928c15101472c70c648873d5887880059ed402", - "0xa5cc3c35228122f410184e4326cf61a37637206e589fcd245cb5d0cec91031f8f7586b80503070840fdfd8ce75d3c88b", - "0x9443fc631aed8866a7ed220890911057a1f56b0afe0ba15f0a0e295ab97f604b134b1ed9a4245e46ee5f9a93aa74f731", - "0x984b6f7d79835dffde9558c6bb912d992ca1180a2361757bdba4a7b69dc74b056e303adc69fe67414495dd9c2dd91e64", - "0xb15a5c8cba5de080224c274d31c68ed72d2a7126d347796569aef0c4e97ed084afe3da4d4b590b9dda1a07f0c2ff3dfb", - "0x991708fe9650a1f9a4e43938b91d45dc68c230e05ee999c95dbff3bf79b1c1b2bb0e7977de454237c355a73b8438b1d9", - "0xb4f7edc7468b176a4a7c0273700c444fa95c726af6697028bed4f77eee887e3400f9c42ee15b782c0ca861c4c3b8c98a", - "0x8c60dcc16c51087eb477c13e837031d6c6a3dc2b8bf8cb43c23f48006bc7173151807e866ead2234b460c2de93b31956", - "0x83ad63e9c910d1fc44bc114accfb0d4d333b7ebe032f73f62d25d3e172c029d5e34a1c9d547273bf6c0fead5c8801007", - "0x85de73213cc236f00777560756bdbf2b16841ba4b55902cf2cad9742ecaf5d28209b012ceb41f337456dfeca93010cd7", - "0xa7561f8827ccd75b6686ba5398bb8fc3083351c55a589b18984e186820af7e275af04bcd4c28e1dc11be1e8617a0610b", - "0x88c0a4febd4068850557f497ea888035c7fc9f404f6cc7794e7cc8722f048ad2f249e7dc62743e7a339eb7473ad3b0cd", - "0x932b22b1d3e6d5a6409c34980d176feb85ada1bf94332ef5c9fc4d42b907dabea608ceef9b5595ef3feee195151f18d8", - "0xa2867bb3f5ab88fbdae3a16c9143ab8a8f4f476a2643c505bb9f37e5b1fd34d216cab2204c9a017a5a67b7ad2dda10e8", - "0xb573d5f38e4e9e8a3a6fd82f0880dc049efa492a946d00283019bf1d5e5516464cf87039e80aef667cb86fdea5075904", - "0xb948f1b5ab755f3f5f36af27d94f503b070696d793b1240c1bdfd2e8e56890d69e6904688b5f8ff5a4bdf5a6abfe195f", - "0x917eae95ebc4109a2e99ddd8fec7881d2f7aaa0e25fda44dec7ce37458c2ee832f1829db7d2dcfa4ca0f06381c7fe91d", - "0x95751d17ed00a3030bce909333799bb7f4ab641acf585807f355b51d6976dceee410798026a1a004ef4dcdff7ec0f5b8", - "0xb9b7bd266f449a79bbfe075e429613e76c5a42ac61f01c8f0bbbd34669650682efe01ff9dbbc400a1e995616af6aa278", - "0xac1722d097ce9cd7617161f8ec8c23d68f1fb1c9ca533e2a8b4f78516c2fd8fb38f23f834e2b9a03bb06a9d655693ca9", - "0xa7ad9e96ffd98db2ecdb6340c5d592614f3c159abfd832fe27ee9293519d213a578e6246aae51672ee353e3296858873", - "0x989b8814d5de7937c4acafd000eec2b4cd58ba395d7b25f98cafd021e8efa37029b29ad8303a1f6867923f5852a220eb", - "0xa5bfe6282c771bc9e453e964042d44eff4098decacb89aecd3be662ea5b74506e1357ab26f3527110ba377711f3c9f41", - "0x8900a7470b656639721d2abbb7b06af0ac4222ab85a1976386e2a62eb4b88bfb5b72cf7921ddb3cf3a395d7eeb192a2e", - "0x95a71b55cd1f35a438cf5e75f8ff11c5ec6a2ebf2e4dba172f50bfad7d6d5dca5de1b1afc541662c81c858f7604c1163", - "0x82b5d62fea8db8d85c5bc3a76d68dedd25794cf14d4a7bc368938ffca9e09f7e598fdad2a5aac614e0e52f8112ae62b9", - "0x997173f07c729202afcde3028fa7f52cefc90fda2d0c8ac2b58154a5073140683e54c49ed1f254481070d119ce0ce02a", - "0xaeffb91ccc7a72bbd6ffe0f9b99c9e66e67d59cec2e02440465e9636a613ab3017278cfa72ea8bc4aba9a8dc728cb367", - "0x952743b06e8645894aeb6440fc7a5f62dd3acf96dab70a51e20176762c9751ea5f2ba0b9497ccf0114dc4892dc606031", - "0x874c63baeddc56fbbca2ff6031f8634b745f6e34ea6791d7c439201aee8f08ef5ee75f7778700a647f3b21068513fce6", - "0x85128fec9c750c1071edfb15586435cc2f317e3e9a175bb8a9697bcda1eb9375478cf25d01e7fed113483b28f625122d", - "0x85522c9576fd9763e32af8495ae3928ed7116fb70d4378448926bc9790e8a8d08f98cf47648d7da1b6e40d6a210c7924", - "0x97d0f37a13cfb723b848099ca1c14d83e9aaf2f7aeb71829180e664b7968632a08f6a85f557d74b55afe6242f2a36e7c", - "0xabaa472d6ad61a5fccd1a57c01aa1bc081253f95abbcba7f73923f1f11c4e79b904263890eeb66926de3e2652f5d1c70", - "0xb3c04945ba727a141e5e8aec2bf9aa3772b64d8fd0e2a2b07f3a91106a95cbcb249adcd074cbe498caf76fffac20d4ef", - "0x82c46781a3d730d9931bcabd7434a9171372dde57171b6180e5516d4e68db8b23495c8ac3ab96994c17ddb1cf249b9fb", - "0xa202d8b65613c42d01738ccd68ed8c2dbc021631f602d53f751966e04182743ebc8e0747d600b8a8676b1da9ae7f11ab", - "0xae73e7256e9459db04667a899e0d3ea5255211fb486d084e6550b6dd64ca44af6c6b2d59d7aa152de9f96ce9b58d940d", - "0xb67d87b176a9722945ec7593777ee461809861c6cfd1b945dde9ee4ff009ca4f19cf88f4bbb5c80c9cbab2fe25b23ac8", - "0x8f0b7a317a076758b0dac79959ee4a06c08b07d0f10538a4b53d3da2eda16e2af26922feb32c090330dc4d969cf69bd3", - "0x90b36bf56adbd8c4b6cb32febc3a8d5f714370c2ac3305c10fa6d168dffb2a026804517215f9a2d4ec8310cdb6bb459b", - "0xaa80c19b0682ead69934bf18cf476291a0beddd8ef4ed75975d0a472e2ab5c70f119722a8574ae4973aceb733d312e57", - "0xa3fc9abb12574e5c28dcb51750b4339b794b8e558675eef7d26126edf1de920c35e992333bcbffcbf6a5f5c0d383ce62", - "0xa1573ff23ab972acdcd08818853b111fc757fdd35aa070186d3e11e56b172fb49d840bf297ac0dd222e072fc09f26a81", - "0x98306f2be4caa92c2b4392212d0cbf430b409b19ff7d5b899986613bd0e762c909fc01999aa94be3bd529d67f0113d7f", - "0x8c1fc42482a0819074241746d17dc89c0304a2acdae8ed91b5009e9e3e70ff725ba063b4a3e68fdce05b74f5180c545e", - "0xa6c6113ebf72d8cf3163b2b8d7f3fa24303b13f55752522c660a98cd834d85d8c79214d900fa649499365e2e7641f77a", - "0xab95eea424f8a2cfd9fb1c78bb724e5b1d71a0d0d1e4217c5d0f98b0d8bbd3f8400a2002abc0a0e4576d1f93f46fefad", - "0x823c5a4fd8cf4a75fdc71d5f2dd511b6c0f189b82affeacd2b7cfcad8ad1a5551227dcc9bfdb2e34b2097eaa00efbb51", - "0xb97314dfff36d80c46b53d87a61b0e124dc94018a0bb680c32765b9a2d457f833a7c42bbc90b3b1520c33a182580398d", - "0xb17566ee3dcc6bb3b004afe4c0136dfe7dd27df9045ae896dca49fb36987501ae069eb745af81ba3fc19ff037e7b1406", - "0xb0bdc0f55cfd98d331e3a0c4fbb776a131936c3c47c6bffdc3aaf7d8c9fa6803fbc122c2fefbb532e634228687d52174", - "0xaa5d9e60cc9f0598559c28bb9bdd52aa46605ab4ffe3d192ba982398e72cec9a2a44c0d0d938ce69935693cabc0887ea", - "0x802b6459d2354fa1d56c592ac1346c428dadea6b6c0a87bf7d309bab55c94e1cf31dd98a7a86bd92a840dd51f218b91b", - "0xa526914efdc190381bf1a73dd33f392ecf01350b9d3f4ae96b1b1c3d1d064721c7d6eec5788162c933245a3943f5ee51", - "0xb3b8fcf637d8d6628620a1a99dbe619eabb3e5c7ce930d6efd2197e261bf394b74d4e5c26b96c4b8009c7e523ccfd082", - "0x8f7510c732502a93e095aba744535f3928f893f188adc5b16008385fb9e80f695d0435bfc5b91cdad4537e87e9d2551c", - "0x97b90beaa56aa936c3ca45698f79273a68dd3ccd0076eab48d2a4db01782665e63f33c25751c1f2e070f4d1a8525bf96", - "0xb9fb798324b1d1283fdc3e48288e3861a5449b2ab5e884b34ebb8f740225324af86e4711da6b5cc8361c1db15466602f", - "0xb6d52b53cea98f1d1d4c9a759c25bf9d8a50b604b144e4912acbdbdc32aab8b9dbb10d64a29aa33a4f502121a6fb481c", - "0x9174ffff0f2930fc228f0e539f5cfd82c9368d26b074467f39c07a774367ff6cccb5039ac63f107677d77706cd431680", - "0xa33b6250d4ac9e66ec51c063d1a6a31f253eb29bbaed12a0d67e2eccfffb0f3a52750fbf52a1c2aaba8c7692346426e7", - "0xa97025fd5cbcebe8ef865afc39cd3ea707b89d4e765ec817fd021d6438e02fa51e3544b1fd45470c58007a08efac6edd", - "0xb32a78480edd9ff6ba2f1eec4088db5d6ceb2d62d7e59e904ecaef7bb4a2e983a4588e51692b3be76e6ffbc0b5f911a5", - "0xb5ab590ef0bb77191f00495b33d11c53c65a819f7d0c1f9dc4a2caa147a69c77a4fff7366a602d743ee1f395ce934c1e", - "0xb3fb0842f9441fb1d0ee0293b6efbc70a8f58d12d6f769b12872db726b19e16f0f65efbc891cf27a28a248b0ef9c7e75", - "0x9372ad12856fefb928ccb0d34e198df99e2f8973b07e9d417a3134d5f69e12e79ff572c4e03ccd65415d70639bc7c73e", - "0xaa8d6e83d09ce216bfe2009a6b07d0110d98cf305364d5529c170a23e693aabb768b2016befb5ada8dabdd92b4d012bb", - "0xa954a75791eeb0ce41c85200c3763a508ed8214b5945a42c79bfdcfb1ec4f86ad1dd7b2862474a368d4ac31911a2b718", - "0x8e2081cfd1d062fe3ab4dab01f68062bac802795545fede9a188f6c9f802cb5f884e60dbe866710baadbf55dc77c11a4", - "0xa2f06003b9713e7dd5929501ed485436b49d43de80ea5b15170763fd6346badf8da6de8261828913ee0dacd8ff23c0e1", - "0x98eecc34b838e6ffd1931ca65eec27bcdb2fdcb61f33e7e5673a93028c5865e0d1bf6d3bec040c5e96f9bd08089a53a4", - "0x88cc16019741b341060b95498747db4377100d2a5bf0a5f516f7dec71b62bcb6e779de2c269c946d39040e03b3ae12b7", - "0xad1135ccbc3019d5b2faf59a688eef2500697642be8cfbdf211a1ab59abcc1f24483e50d653b55ff1834675ac7b4978f", - "0xa946f05ed9972f71dfde0020bbb086020fa35b482cce8a4cc36dd94355b2d10497d7f2580541bb3e81b71ac8bba3c49f", - "0xa83aeed488f9a19d8cfd743aa9aa1982ab3723560b1cd337fc2f91ad82f07afa412b3993afb845f68d47e91ba4869840", - "0x95eebe006bfc316810cb71da919e5d62c2cebb4ac99d8e8ef67be420302320465f8b69873470982de13a7c2e23516be9", - "0xa55f8961295a11e91d1e5deadc0c06c15dacbfc67f04ccba1d069cba89d72aa3b3d64045579c3ea8991b150ac29366ae", - "0xb321991d12f6ac07a5de3c492841d1a27b0d3446082fbce93e7e1f9e8d8fe3b45d41253556261c21b70f5e189e1a7a6f", - "0xa0b0822f15f652ce7962a4f130104b97bf9529797c13d6bd8e24701c213cc37f18157bd07f3d0f3eae6b7cd1cb40401f", - "0x96e2fa4da378aa782cc2d5e6e465fc9e49b5c805ed01d560e9b98abb5c0de8b74a2e7bec3aa5e2887d25cccb12c66f0c", - "0x97e4ab610d414f9210ed6f35300285eb3ccff5b0b6a95ed33425100d7725e159708ea78704497624ca0a2dcabce3a2f9", - "0x960a375b17bdb325761e01e88a3ea57026b2393e1d887b34b8fa5d2532928079ce88dc9fd06a728b26d2bb41b12b9032", - "0x8328a1647398e832aadc05bd717487a2b6fcdaa0d4850d2c4da230c6a2ed44c3e78ec4837b6094f3813f1ee99414713f", - "0xaa283834ebd18e6c99229ce4b401eda83f01d904f250fedd4e24f1006f8fa0712a6a89a7296a9bf2ce8de30e28d1408e", - "0xb29e097f2caadae3e0f0ae3473c072b0cd0206cf6d2e9b22c1a5ad3e07d433e32bd09ed1f4e4276a2da4268633357b7f", - "0x9539c5cbba14538b2fe077ecf67694ef240da5249950baaabea0340718b882a966f66d97f08556b08a4320ceb2cc2629", - "0xb4529f25e9b42ae8cf8338d2eface6ba5cd4b4d8da73af502d081388135c654c0b3afb3aa779ffc80b8c4c8f4425dd2b", - "0x95be0739c4330619fbe7ee2249c133c91d6c07eab846c18c5d6c85fc21ac5528c5d56dcb0145af68ed0c6a79f68f2ccd", - "0xac0c83ea802227bfc23814a24655c9ff13f729619bcffdb487ccbbf029b8eaee709f8bddb98232ef33cd70e30e45ca47", - "0xb503becb90acc93b1901e939059f93e671900ca52c6f64ae701d11ac891d3a050b505d89324ce267bc43ab8275da6ffe", - "0x98e3811b55b1bacb70aa409100abb1b870f67e6d059475d9f278c751b6e1e2e2d6f2e586c81a9fb6597fda06e7923274", - "0xb0b0f61a44053fa6c715dbb0731e35d48dba257d134f851ee1b81fd49a5c51a90ebf5459ec6e489fce25da4f184fbdb1", - "0xb1d2117fe811720bb997c7c93fe9e4260dc50fca8881b245b5e34f724aaf37ed970cdad4e8fcb68e05ac8cf55a274a53", - "0xa10f502051968f14b02895393271776dee7a06db9de14effa0b3471825ba94c3f805302bdddac4d397d08456f620999d", - "0xa3dbad2ef060ae0bb7b02eaa4a13594f3f900450faa1854fc09620b01ac94ab896321dfb1157cf2374c27e5718e8026a", - "0xb550fdec503195ecb9e079dcdf0cad559d64d3c30818ef369b4907e813e689da316a74ad2422e391b4a8c2a2bef25fc0", - "0xa25ba865e2ac8f28186cea497294c8649a201732ecb4620c4e77b8e887403119910423df061117e5f03fc5ba39042db1", - "0xb3f88174e03fdb443dd6addd01303cf88a4369352520187c739fc5ae6b22fa99629c63c985b4383219dab6acc5f6f532", - "0x97a7503248e31e81b10eb621ba8f5210c537ad11b539c96dfb7cf72b846c7fe81bd7532c5136095652a9618000b7f8d3", - "0xa8bcdc1ce5aa8bfa683a2fc65c1e79de8ff5446695dcb8620f7350c26d2972a23da22889f9e2b1cacb3f688c6a2953dc", - "0x8458c111df2a37f5dd91a9bee6c6f4b79f4f161c93fe78075b24a35f9817da8dde71763218d627917a9f1f0c4709c1ed", - "0xac5f061a0541152b876cbc10640f26f1cc923c9d4ae1b6621e4bb3bf2cec59bbf87363a4eb72fb0e5b6d4e1c269b52d5", - "0xa9a25ca87006e8a9203cbb78a93f50a36694aa4aad468b8d80d3feff9194455ca559fcc63838128a0ab75ad78c07c13a", - "0xa450b85f5dfffa8b34dfd8bc985f921318efacf8857cf7948f93884ba09fb831482ee90a44224b1a41e859e19b74962f", - "0x8ed91e7f92f5c6d7a71708b6132f157ac226ecaf8662af7d7468a4fa25627302efe31e4620ad28719318923e3a59bf82", - "0xab524165fd4c71b1fd395467a14272bd2b568592deafa039d8492e9ef36c6d3f96927c95c72d410a768dc0b6d1fbbc9b", - "0xb662144505aa8432c75ffb8d10318526b6d5777ac7af9ebfad87d9b0866c364f7905a6352743bd8fd79ffd9d5dd4f3e6", - "0xa48f1677550a5cd40663bb3ba8f84caaf8454f332d0ceb1d94dbea52d0412fe69c94997f7749929712fd3995298572f7", - "0x8391cd6e2f6b0c242de1117a612be99776c3dc95cb800b187685ea5bf7e2722275eddb79fd7dfc8be8e389c4524cdf70", - "0x875d3acb9af47833b72900bc0a2448999d638f153c5e97e8a14ec02d0c76f6264353a7e275e1f1a5855daced523d243b", - "0x91f1823657d30b59b2f627880a9a9cb530f5aca28a9fd217fe6f2f5133690dfe7ad5a897872e400512db2e788b3f7628", - "0xad3564332aa56cea84123fc7ca79ea70bb4fef2009fa131cb44e4b15e8613bd11ca1d83b9d9bf456e4b7fee9f2e8b017", - "0x8c530b84001936d5ab366c84c0b105241a26d1fb163669f17c8f2e94776895c2870edf3e1bc8ccd04d5e65531471f695", - "0x932d01fa174fdb0c366f1230cffde2571cc47485f37f23ba5a1825532190cc3b722aeb1f15aed62cf83ccae9403ba713", - "0x88b28c20585aca50d10752e84b901b5c2d58efef5131479fbbe53de7bce2029e1423a494c0298e1497669bd55be97a5d", - "0xb914148ca717721144ebb3d3bf3fcea2cd44c30c5f7051b89d8001502f3856fef30ec167174d5b76265b55d70f8716b5", - "0x81d0173821c6ddd2a068d70766d9103d1ee961c475156e0cbd67d54e668a796310474ef698c7ab55abe6f2cf76c14679", - "0x8f28e8d78e2fe7fa66340c53718e0db4b84823c8cfb159c76eac032a62fb53da0a5d7e24ca656cf9d2a890cb2a216542", - "0x8a26360335c73d1ab51cec3166c3cf23b9ea51e44a0ad631b0b0329ef55aaae555420348a544e18d5760969281759b61", - "0x94f326a32ed287545b0515be9e08149eb0a565025074796d72387cc3a237e87979776410d78339e23ef3172ca43b2544", - "0xa785d2961a2fa5e70bffa137858a92c48fe749fee91b02599a252b0cd50d311991a08efd7fa5e96b78d07e6e66ffe746", - "0x94af9030b5ac792dd1ce517eaadcec1482206848bea4e09e55cc7f40fd64d4c2b3e9197027c5636b70d6122c51d2235d", - "0x9722869f7d1a3992850fe7be405ec93aa17dc4d35e9e257d2e469f46d2c5a59dbd504056c85ab83d541ad8c13e8bcd54", - "0xb13c4088b61a06e2c03ac9813a75ff1f68ffdfee9df6a8f65095179a475e29cc49119cad2ce05862c3b1ac217f3aace9", - "0x8c64d51774753623666b10ca1b0fe63ae42f82ed6aa26b81dc1d48c86937c5772eb1402624c52a154b86031854e1fb9f", - "0xb47e4df18002b7dac3fee945bf9c0503159e1b8aafcce2138818e140753011b6d09ef1b20894e08ba3006b093559061b", - "0x93cb5970076522c5a0483693f6a35ffd4ea2aa7aaf3730c4eccd6af6d1bebfc1122fc4c67d53898ae13eb6db647be7e2", - "0xa68873ef80986795ea5ed1a597d1cd99ed978ec25e0abb57fdcc96e89ef0f50aeb779ff46e3dce21dc83ada3157a8498", - "0x8cab67f50949cc8eee6710e27358aea373aae3c92849f8f0b5531c080a6300cdf2c2094fe6fecfef6148de0d28446919", - "0x993e932bcb616dbaa7ad18a4439e0565211d31071ef1b85a0627db74a05d978c60d507695eaeea5c7bd9868a21d06923", - "0xacdadff26e3132d9478a818ef770e9fa0d2b56c6f5f48bd3bd674436ccce9bdfc34db884a73a30c04c5f5e9764cb2218", - "0xa0d3e64c9c71f84c0eef9d7a9cb4fa184224b969db5514d678e93e00f98b41595588ca802643ea225512a4a272f5f534", - "0x91c9140c9e1ba6e330cb08f6b2ce4809cd0d5a0f0516f70032bf30e912b0ed684d07b413b326ab531ee7e5b4668c799b", - "0x87bc2ee7a0c21ba8334cd098e35cb703f9af57f35e091b8151b9b63c3a5b0f89bd7701dbd44f644ea475901fa6d9ef08", - "0x9325ccbf64bf5d71b303e31ee85d486298f9802c5e55b2c3d75427097bf8f60fa2ab4fcaffa9b60bf922c3e24fbd4b19", - "0x95d0506e898318f3dc8d28d16dfd9f0038b54798838b3c9be2a2ae3c2bf204eb496166353fc042220b0bd4f6673b9285", - "0x811de529416331fe9c416726d45df9434c29dcd7e949045eb15740f47e97dde8f31489242200e19922cac2a8b7c6fd1f", - "0xade632d04a4c8bbab6ca7df370b2213cb9225023e7973f0e29f4f5e52e8aeaabc65171306bbdd12a67b195dfbb96d48f", - "0x88b7f029e079b6ae956042c0ea75d53088c5d0efd750dd018adaeacf46be21bf990897c58578c491f41afd3978d08073", - "0x91f477802de507ffd2be3f4319903119225b277ad24f74eb50f28b66c14d32fae53c7edb8c7590704741af7f7f3e3654", - "0x809838b32bb4f4d0237e98108320d4b079ee16ed80c567e7548bd37e4d7915b1192880f4812ac0e00476d246aec1dbc8", - "0x84183b5fc4a7997a8ae5afedb4d21dce69c480d5966b5cbdafd6dd10d29a9a6377f3b90ce44da0eb8b176ac3af0253bb", - "0x8508abbf6d3739a16b9165caf0f95afb3b3ac1b8c38d6d374cf0c91296e2c1809a99772492b539cda184510bce8a0271", - "0x8722054e59bab2062e6419a6e45fc803af77fde912ef2cd23055ad0484963de65a816a2debe1693d93c18218d2b8e81a", - "0x8e895f80e485a7c4f56827bf53d34b956281cdc74856c21eb3b51f6288c01cc3d08565a11cc6f3e2604775885490e8c5", - "0xafc92714771b7aa6e60f3aee12efd9c2595e9659797452f0c1e99519f67c8bc3ac567119c1ddfe82a3e961ee9defea9a", - "0x818ff0fd9cefd32db87b259e5fa32967201016fc02ef44116cdca3c63ce5e637756f60477a408709928444a8ad69c471", - "0x8251e29af4c61ae806fc5d032347fb332a94d472038149225298389495139ce5678fae739d02dfe53a231598a992e728", - "0xa0ea39574b26643f6f1f48f99f276a8a64b5481989cfb2936f9432a3f8ef5075abfe5c067dc5512143ce8bf933984097", - "0xaf67a73911b372bf04e57e21f289fc6c3dfac366c6a01409b6e76fea4769bdb07a6940e52e8d7d3078f235c6d2f632c6", - "0xb5291484ef336024dd2b9b4cf4d3a6b751133a40656d0a0825bcc6d41c21b1c79cb50b0e8f4693f90c29c8f4358641f9", - "0x8bc0d9754d70f2cb9c63f991902165a87c6535a763d5eece43143b5064ae0bcdce7c7a8f398f2c1c29167b2d5a3e6867", - "0x8d7faff53579ec8f6c92f661c399614cc35276971752ce0623270f88be937c414eddcb0997e14724a783905a026c8883", - "0x9310b5f6e675fdf60796f814dbaa5a6e7e9029a61c395761e330d9348a7efab992e4e115c8be3a43d08e90d21290c892", - "0xb5eb4f3eb646038ad2a020f0a42202532d4932e766da82b2c1002bf9c9c2e5336b54c8c0ffcc0e02d19dde2e6a35b6cc", - "0x91dabfd30a66710f1f37a891136c9be1e23af4abf8cb751f512a40c022a35f8e0a4fb05b17ec36d4208de02d56f0d53a", - "0xb3ded14e82d62ac7a5a036122a62f00ff8308498f3feae57d861babaff5a6628d43f0a0c5fc903f10936bcf4e2758ceb", - "0xa88e8348fed2b26acca6784d19ef27c75963450d99651d11a950ea81d4b93acd2c43e0ecce100eaf7e78508263d5baf3", - "0xb1f5bbf7c4756877b87bb42163ac570e08c6667c4528bf68b5976680e19beeff7c5effd17009b0718797077e2955457a", - "0xad2e7b516243f915d4d1415326e98b1a7390ae88897d0b03b66c2d9bd8c3fba283d7e8fe44ed3333296a736454cef6d8", - "0x8f82eae096d5b11f995de6724a9af895f5e1c58d593845ad16ce8fcae8507e0d8e2b2348a0f50a1f66a17fd6fac51a5c", - "0x890e4404d0657c6c1ee14e1aac132ecf7a568bb3e04137b85ac0f84f1d333bd94993e8750f88eee033a33fb00f85dcc7", - "0x82ac7d3385e035115f1d39a99fc73e5919de44f5e6424579776d118d711c8120b8e5916372c6f27bed4cc64cac170b6c", - "0x85ee16d8901c272cfbbe966e724b7a891c1bd5e68efd5d863043ad8520fc409080af61fd726adc680b3f1186fe0ac8b8", - "0x86dc564c9b545567483b43a38f24c41c6551a49cabeebb58ce86404662a12dbfafd0778d30d26e1c93ce222e547e3898", - "0xa29f5b4522db26d88f5f95f18d459f8feefab02e380c2edb65aa0617a82a3c1a89474727a951cef5f15050bcf7b380fb", - "0xa1ce039c8f6cac53352899edb0e3a72c76da143564ad1a44858bd7ee88552e2fe6858d1593bbd74aeee5a6f8034b9b9d", - "0x97f10d77983f088286bd7ef3e7fdd8fa275a56bec19919adf33cf939a90c8f2967d2b1b6fc51195cb45ad561202a3ed7", - "0xa25e2772e8c911aaf8712bdac1dd40ee061c84d3d224c466cfaae8e5c99604053f940cde259bd1c3b8b69595781dbfec", - "0xb31bb95a0388595149409c48781174c340960d59032ab2b47689911d03c68f77a2273576fbe0c2bf4553e330656058c7", - "0xb8b2e9287ad803fb185a13f0d7456b397d4e3c8ad5078f57f49e8beb2e85f661356a3392dbd7bcf6a900baa5582b86a1", - "0xa3d0893923455eb6e96cc414341cac33d2dbc88fba821ac672708cce131761d85a0e08286663a32828244febfcae6451", - "0x82310cb42f647d99a136014a9f881eb0b9791efd2e01fc1841907ad3fc8a9654d3d1dab6689c3607214b4dc2aca01cee", - "0x874022d99c16f60c22de1b094532a0bc6d4de700ad01a31798fac1d5088b9a42ad02bef8a7339af7ed9c0d4f16b186ee", - "0x94981369e120265aed40910eebc37eded481e90f4596b8d57c3bec790ab7f929784bd33ddd05b7870aad6c02e869603b", - "0xa4f1f50e1e2a73f07095e0dd31cb45154f24968dae967e38962341c1241bcd473102fff1ff668b20c6547e9732d11701", - "0xae2328f3b0ad79fcda807e69a1b5278145225083f150f67511dafc97e079f860c3392675f1752ae7e864c056e592205b", - "0x875d8c971e593ca79552c43d55c8c73b17cd20c81ff2c2fed1eb19b1b91e4a3a83d32df150dbfd5db1092d0aebde1e1f", - "0xadd2e80aa46aae95da73a11f130f4bda339db028e24c9b11e5316e75ba5e63bc991d2a1da172c7c8e8fee038baae3433", - "0xb46dbe1cb3424002aa7de51e82f600852248e251465c440695d52538d3f36828ff46c90ed77fc1d11534fe3c487df8ef", - "0xa5e5045d28b4e83d0055863c30c056628c58d4657e6176fd0536f5933f723d60e851bb726d5bf3c546b8ce4ac4a57ef8", - "0x91fec01e86dd1537e498fff7536ea3ca012058b145f29d9ada49370cd7b7193ac380e116989515df1b94b74a55c45df3", - "0xa7428176d6918cd916a310bdc75483c72de660df48cac4e6e7478eef03205f1827ea55afc0df5d5fa7567d14bbea7fc9", - "0x851d89bef45d9761fe5fdb62972209335193610015e16a675149519f9911373bac0919add226ef118d9f3669cfdf4734", - "0xb74acf5c149d0042021cb2422ea022be4c4f72a77855f42393e71ffd12ebb3eec16bdf16f812159b67b79a9706e7156d", - "0x99f35dce64ec99aa595e7894b55ce7b5a435851b396e79036ffb249c28206087db4c85379df666c4d95857db02e21ff9", - "0xb6b9a384f70db9e298415b8ab394ee625dafff04be2886476e59df8d052ca832d11ac68a9b93fba7ab055b7bc36948a4", - "0x898ee4aefa923ffec9e79f2219c7389663eb11eb5b49014e04ed4a336399f6ea1691051d86991f4c46ca65bcd4fdf359", - "0xb0f948217b0d65df7599a0ba4654a5e43c84db477936276e6f11c8981efc6eaf14c90d3650107ed4c09af4cc8ec11137", - "0xaa6286e27ac54f73e63dbf6f41865dd94d24bc0cf732262fcaff67319d162bb43af909f6f8ee27b1971939cfbba08141", - "0x8bca7cdf730cf56c7b2c8a2c4879d61361a6e1dba5a3681a1a16c17a56e168ace0e99cf0d15826a1f5e67e6b8a8a049a", - "0xa746d876e8b1ce225fcafca603b099b36504846961526589af977a88c60d31ba2cc56e66a3dec8a77b3f3531bf7524c9", - "0xa11e2e1927e6704cdb8874c75e4f1842cef84d7d43d7a38e339e61dc8ba90e61bbb20dd3c12e0b11d2471d58eed245be", - "0xa36395e22bc1d1ba8b0459a235203177737397da5643ce54ded3459d0869ff6d8d89f50c73cb62394bf66a959cde9b90", - "0x8b49f12ba2fdf9aca7e5f81d45c07d47f9302a2655610e7634d1e4bd16048381a45ef2c95a8dd5b0715e4b7cf42273af", - "0x91cffa2a17e64eb7f76bccbe4e87280ee1dd244e04a3c9eac12e15d2d04845d876eb24fe2ec6d6d266cce9efb281077f", - "0xa6b8afabf65f2dee01788114e33a2f3ce25376fb47a50b74da7c3c25ff1fdc8aa9f41307534abbf48acb6f7466068f69", - "0x8d13db896ccfea403bd6441191995c1a65365cab7d0b97fbe9526da3f45a877bd1f4ef2edef160e8a56838cd1586330e", - "0x98c717de9e01bef8842c162a5e757fe8552d53269c84862f4d451e7c656ae6f2ae473767b04290b134773f63be6fdb9d", - "0x8c2036ace1920bd13cf018e82848c49eb511fad65fd0ff51f4e4b50cf3bfc294afb63cba682c16f52fb595a98fa84970", - "0xa3520fdff05dbad9e12551b0896922e375f9e5589368bcb2cc303bde252743b74460cb5caf99629325d3620f13adc796", - "0x8d4f83a5bfec05caf5910e0ce538ee9816ee18d0bd44c1d0da2a87715a23cd2733ad4d47552c6dc0eb397687d611dd19", - "0xa7b39a0a6a02823452d376533f39d35029867b3c9a6ad6bca181f18c54132d675613a700f9db2440fb1b4fa13c8bf18a", - "0x80bcb114b2544b80f404a200fc36860ed5e1ad31fe551acd4661d09730c452831751baa9b19d7d311600d267086a70bc", - "0x90dcce03c6f88fc2b08f2b42771eedde90cc5330fe0336e46c1a7d1b5a6c1641e5fcc4e7b3d5db00bd8afca9ec66ed81", - "0xaec15f40805065c98e2965b1ae12a6c9020cfdb094c2d0549acfc7ea2401a5fb48d3ea7d41133cf37c4e096e7ff53eb9", - "0x80e129b735dba49fa627a615d6c273119acec8e219b2f2c4373a332b5f98d66cbbdd688dfbe72a8f8bfefaccc02c50c1", - "0xa9b596da3bdfe23e6799ece5f7975bf7a1979a75f4f546deeaf8b34dfe3e0d623217cb4cf4ccd504cfa3625b88cd53f1", - "0xabcbbb70b16f6e517c0ab4363ab76b46e4ff58576b5f8340e5c0e8cc0e02621b6e23d742d73b015822a238b17cfd7665", - "0xa046937cc6ea6a2e1adae543353a9fe929c1ae4ad655be1cc051378482cf88b041e28b1e9a577e6ccff2d3570f55e200", - "0x831279437282f315e65a60184ef158f0a3dddc15a648dc552bdc88b3e6fe8288d3cfe9f0031846d81350f5e7874b4b33", - "0x993d7916fa213c6d66e7c4cafafc1eaec9a2a86981f91c31eb8a69c5df076c789cbf498a24c84e0ee77af95b42145026", - "0x823907a3b6719f8d49b3a4b7c181bd9bb29fcf842d7c70660c4f351852a1e197ca46cf5e879b47fa55f616fa2b87ce5e", - "0x8d228244e26132b234930ee14c75d88df0943cdb9c276a8faf167d259b7efc1beec2a87c112a6c608ad1600a239e9aae", - "0xab6e55766e5bfb0cf0764ed909a8473ab5047d3388b4f46faeba2d1425c4754c55c6daf6ad4751e634c618b53e549529", - "0xab0cab6860e55a84c5ad2948a7e0989e2b4b1fd637605634b118361497332df32d9549cb854b2327ca54f2bcb85eed8f", - "0xb086b349ae03ef34f4b25a57bcaa5d1b29bd94f9ebf87e22be475adfe475c51a1230c1ebe13506cb72c4186192451658", - "0x8a0b49d8a254ca6d91500f449cbbfbb69bb516c6948ac06808c65595e46773e346f97a5ce0ef7e5a5e0de278af22709c", - "0xac49de11edaaf04302c73c578cc0824bdd165c0d6321be1c421c1950e68e4f3589aa3995448c9699e93c6ebae8803e27", - "0x884f02d841cb5d8f4c60d1402469216b114ab4e93550b5bc1431756e365c4f870a9853449285384a6fa49e12ce6dc654", - "0xb75f3a28fa2cc8d36b49130cb7448a23d73a7311d0185ba803ad55c8219741d451c110f48b786e96c728bc525903a54f", - "0x80ae04dbd41f4a35e33f9de413b6ad518af0919e5a30cb0fa1b061b260420780bb674f828d37fd3b52b5a31673cbd803", - "0xb9a8011eb5fcea766907029bf743b45262db3e49d24f84503687e838651ed11cb64c66281e20a0ae9f6aa51acc552263", - "0x90bfdd75e2dc9cf013e22a5d55d2d2b8a754c96103a17524488e01206e67f8b6d52b1be8c4e3d5307d4fe06d0e51f54c", - "0xb4af353a19b06203a815ec43e79a88578cc678c46f5a954b85bc5c53b84059dddba731f3d463c23bfd5273885c7c56a4", - "0xaa125e96d4553b64f7140e5453ff5d2330318b69d74d37d283e84c26ad672fa00e3f71e530eb7e28be1e94afb9c4612e", - "0xa18e060aee3d49cde2389b10888696436bb7949a79ca7d728be6456a356ea5541b55492b2138da90108bd1ce0e6f5524", - "0x93e55f92bdbccc2de655d14b1526836ea2e52dba65eb3f87823dd458a4cb5079bf22ce6ef625cb6d6bfdd0995ab9a874", - "0x89f5a683526b90c1c3ceebbb8dc824b21cff851ce3531b164f6626e326d98b27d3e1d50982e507d84a99b1e04e86a915", - "0x83d1c38800361633a3f742b1cb2bfc528129496e80232611682ddbe403e92c2ac5373aea0bca93ecb5128b0b2b7a719e", - "0x8ecba560ac94905e19ce8d9c7af217bf0a145d8c8bd38e2db82f5e94cc3f2f26f55819176376b51f154b4aab22056059", - "0xa7e2a4a002b60291924850642e703232994acb4cfb90f07c94d1e0ecd2257bb583443283c20fc6017c37e6bfe85b7366", - "0x93ed7316fa50b528f1636fc6507683a672f4f4403e55e94663f91221cc198199595bd02eef43d609f451acc9d9b36a24", - "0xa1220a8ebc5c50ceed76a74bc3b7e0aa77f6884c71b64b67c4310ac29ce5526cb8992d6abc13ef6c8413ce62486a6795", - "0xb2f6eac5c869ad7f4a25161d3347093e2f70e66cd925032747e901189355022fab3038bca4d610d2f68feb7e719c110b", - "0xb703fa11a4d511ca01c7462979a94acb40b5d933759199af42670eb48f83df202fa0c943f6ab3b4e1cc54673ea3aab1e", - "0xb5422912afbfcb901f84791b04f1ddb3c3fbdc76d961ee2a00c5c320e06d3cc5b5909c3bb805df66c5f10c47a292b13d", - "0xad0934368da823302e1ac08e3ede74b05dfdbfffca203e97ffb0282c226814b65c142e6e15ec1e754518f221f01b30f7", - "0xa1dd302a02e37df15bf2f1147efe0e3c06933a5a767d2d030e1132f5c3ce6b98e216b6145eb39e1e2f74e76a83165b8d", - "0xa346aab07564432f802ae44738049a36f7ca4056df2d8f110dbe7fef4a3e047684dea609b2d03dc6bf917c9c2a47608f", - "0xb96c5f682a5f5d02123568e50f5d0d186e4b2c4c9b956ec7aabac1b3e4a766d78d19bd111adb5176b898e916e49be2aa", - "0x8a96676d56876fc85538db2e806e1cba20fd01aeb9fa3cb43ca6ca94a2c102639f65660db330e5d74a029bb72d6a0b39", - "0xab0048336bd5c3def1a4064eadd49e66480c1f2abb4df46e03afbd8a3342c2c9d74ee35d79f08f4768c1646681440984", - "0x888427bdf76caec90814c57ee1c3210a97d107dd88f7256f14f883ad0f392334b82be11e36dd8bfec2b37935177c7831", - "0xb622b282becf0094a1916fa658429a5292ba30fb48a4c8066ce1ddcefb71037948262a01c95bab6929ed3a76ba5db9fe", - "0xb5b9e005c1f456b6a368a3097634fb455723abe95433a186e8278dceb79d4ca2fbe21f8002e80027b3c531e5bf494629", - "0xa3c6707117a1e48697ed41062897f55d8119403eea6c2ee88f60180f6526f45172664bfee96bf61d6ec0b7fbae6aa058", - "0xb02a9567386a4fbbdb772d8a27057b0be210447348efe6feb935ceec81f361ed2c0c211e54787dc617cdffed6b4a6652", - "0xa9b8364e40ef15c3b5902e5534998997b8493064fa2bea99600def58279bb0f64574c09ba11e9f6f669a8354dd79dc85", - "0x9998a2e553a9aa9a206518fae2bc8b90329ee59ab23005b10972712389f2ec0ee746033c733092ffe43d73d33abbb8ef", - "0x843a4b34d9039bf79df96d79f2d15e8d755affb4d83d61872daf540b68c0a3888cf8fc00d5b8b247b38524bcb3b5a856", - "0x84f7128920c1b0bb40eee95701d30e6fc3a83b7bb3709f16d97e72acbb6057004ee7ac8e8f575936ca9dcb7866ab45f7", - "0x918d3e2222e10e05edb34728162a899ad5ada0aaa491aeb7c81572a9c0d506e31d5390e1803a91ff3bd8e2bb15d47f31", - "0x9442d18e2489613a7d47bb1cb803c8d6f3259d088cd079460976d87f7905ee07dea8f371b2537f6e1d792d36d7e42723", - "0xb491976970fe091995b2ed86d629126523ccf3e9daf8145302faca71b5a71a5da92e0e05b62d7139d3efac5c4e367584", - "0xaa628006235dc77c14cef4c04a308d66b07ac92d377df3de1a2e6ecfe3144f2219ad6d7795e671e1cb37a3641910b940", - "0x99d386adaea5d4981d7306feecac9a555b74ffdc218c907c5aa7ac04abaead0ec2a8237300d42a3fbc464673e417ceed", - "0x8f78e8b1556f9d739648ea3cab9606f8328b52877fe72f9305545a73b74d49884044ba9c1f1c6db7d9b7c7b7c661caba", - "0x8fb357ae49932d0babdf74fc7aa7464a65d3b6a2b3acf4f550b99601d3c0215900cfd67f2b6651ef94cfc323bac79fae", - "0x9906f2fa25c0290775aa001fb6198113d53804262454ae8b83ef371b5271bde189c0460a645829cb6c59f9ee3a55ce4d", - "0x8f4379b3ebb50e052325b27655ca6a82e6f00b87bf0d2b680d205dd2c7afdc9ff32a9047ae71a1cdf0d0ce6b9474d878", - "0xa85534e88c2bd43c043792eaa75e50914b21741a566635e0e107ae857aed0412035f7576cf04488ade16fd3f35fdbb87", - "0xb4ce93199966d3c23251ca7f28ec5af7efea1763d376b0385352ffb2e0a462ef95c69940950278cf0e3dafd638b7bd36", - "0xb10cb3d0317dd570aa73129f4acf63c256816f007607c19b423fb42f65133ce21f2f517e0afb41a5378cccf893ae14d0", - "0xa9b231c9f739f7f914e5d943ed9bff7eba9e2c333fbd7c34eb1648a362ee01a01af6e2f7c35c9fe962b11152cddf35de", - "0x99ff6a899e156732937fb81c0cced80ae13d2d44c40ba99ac183aa246103b31ec084594b1b7feb96da58f4be2dd5c0ed", - "0x8748d15d18b75ff2596f50d6a9c4ce82f61ecbcee123a6ceae0e43cab3012a29b6f83cf67b48c22f6f9d757c6caf76b2", - "0xb88ab05e4248b7fb634cf640a4e6a945d13e331237410f7217d3d17e3e384ddd48897e7a91e4516f1b9cbd30f35f238b", - "0x8d826deaeeb84a3b2d2c04c2300ca592501f992810582d6ae993e0d52f6283a839dba66c6c72278cff5871802b71173b", - "0xb36fed027c2f05a5ef625ca00b0364b930901e9e4420975b111858d0941f60e205546474bb25d6bfa6928d37305ae95f", - "0xaf2fcfc6b87967567e8b8a13a4ed914478185705724e56ce68fb2df6d1576a0cf34a61e880997a0d35dc2c3276ff7501", - "0xac351b919cd1fbf106feb8af2c67692bfcddc84762d18cea681cfa7470a5644839caace27efee5f38c87d3df306f4211", - "0x8d6665fb1d4d8d1fa23bd9b8a86e043b8555663519caac214d1e3e3effbc6bee7f2bcf21e645f77de0ced279d69a8a8b", - "0xa9fc1c2061756b2a1a169c1b149f212ff7f0d2488acd1c5a0197eba793cffa593fc6d1d1b40718aa75ca3ec77eff10e1", - "0xaff64f0fa009c7a6cf0b8d7a22ddb2c8170c3cb3eec082e60d5aadb00b0040443be8936d728d99581e33c22178c41c87", - "0x82e0b181adc5e3b1c87ff8598447260e839d53debfae941ebea38265575546c3a74a14b4325a030833a62ff6c52d9365", - "0xb7ad43cbb22f6f892c2a1548a41dc120ab1f4e1b8dea0cb6272dd9cb02054c542ecabc582f7e16de709d48f5166cae86", - "0x985e0c61094281532c4afb788ecb2dfcba998e974b5d4257a22040a161883908cdd068fe80f8eb49b8953cfd11acf43a", - "0xae46895c6d67ea6d469b6c9c07b9e5d295d9ae73b22e30da4ba2c973ba83a130d7eef39717ec9d0f36e81d56bf742671", - "0x8600177ea1f7e7ef90514b38b219a37dedfc39cb83297e4c7a5b479817ef56479d48cf6314820960c751183f6edf8b0e", - "0xb9208ec1c1d7a1e99b59c62d3e4e61dfb706b0e940d09d3abfc3454c19749083260614d89cfd7e822596c3cdbcc6bb95", - "0xa1e94042c796c2b48bc724352d2e9f3a22291d9a34705993357ddb6adabd76da6fc25dac200a8cb0b5bbd99ecddb7af6", - "0xb29c3adedd0bcad8a930625bc4dfdc3552a9afd5ca6dd9c0d758f978068c7982b50b711aa0eb5b97f2b84ee784637835", - "0xaf0632a238bb1f413c7ea8e9b4c3d68f2827bd2e38cd56024391fba6446ac5d19a780d0cfd4a78fe497d537b766a591a", - "0xaaf6e7f7d54f8ef5e2e45dd59774ecbeecf8683aa70483b2a75be6a6071b5981bbaf1627512a65d212817acdfab2e428", - "0x8c751496065da2e927cf492aa5ca9013b24f861d5e6c24b30bbf52ec5aaf1905f40f9a28175faef283dd4ed4f2182a09", - "0x8952377d8e80a85cf67d6b45499f3bad5fd452ea7bcd99efc1b066c4720d8e5bff1214cea90fd1f972a7f0baac3d29be", - "0xa1946ee543d1a6e21f380453be4d446e4130950c5fc3d075794eb8260f6f52d0a795c1ff91d028a648dc1ce7d9ab6b47", - "0x89f3fefe37af31e0c17533d2ca1ce0884cc1dc97c15cbfab9c331b8debd94781c9396abef4bb2f163d09277a08d6adf0", - "0xa2753f1e6e1a154fb117100a5bd9052137add85961f8158830ac20541ab12227d83887d10acf7fd36dcaf7c2596d8d23", - "0x814955b4198933ee11c3883863b06ff98c7eceb21fc3e09df5f916107827ccf3323141983e74b025f46ae00284c9513b", - "0x8cc5c6bb429073bfef47cae7b3bfccb0ffa076514d91a1862c6bda4d581e0df87db53cc6c130bf8a7826304960f5a34e", - "0x909f22c1f1cdc87f7be7439c831a73484a49acbf8f23d47087d7cf867c64ef61da3bde85dc57d705682b4c3fc710d36e", - "0x8048fee7f276fcd504aed91284f28e73693615e0eb3858fa44bcf79d7285a9001c373b3ef71d9a3054817ba293ebe28c", - "0x94400e5cf5d2700ca608c5fe35ce14623f71cc24959f2bc27ca3684092850f76b67fb1f07ca9e5b2ca3062cf8ad17bd4", - "0x81c2ae7d4d1b17f8b6de6a0430acc0d58260993980fe48dc2129c4948269cdc74f9dbfbf9c26b19360823fd913083d48", - "0x8c41fe765128e63f6889d6a979f6a4342300327c8b245a8cfe3ecfbcac1e09c3da30e2a1045b24b78efc6d6d50c8c6ac", - "0xa5dd4ae51ae48c8be4b218c312ade226cffce671cf121cb77810f6c0990768d6dd767badecb5c69921d5574d5e8433d3", - "0xb7642e325f4ba97ae2a39c1c9d97b35aafd49d53dba36aed3f3cb0ca816480b3394079f46a48252d46596559c90f4d58", - "0xae87375b40f35519e7bd4b1b2f73cd0b329b0c2cb9d616629342a71c6c304338445eda069b78ea0fbe44087f3de91e09", - "0xb08918cb6f736855e11d3daca1ddfbdd61c9589b203b5493143227bf48e2c77c2e8c94b0d1aa2fab2226e0eae83f2681", - "0xac36b84a4ac2ebd4d6591923a449c564e3be8a664c46092c09e875c2998eba16b5d32bfd0882fd3851762868e669f0b1", - "0xa44800a3bb192066fa17a3f29029a23697240467053b5aa49b9839fb9b9b8b12bcdcbfc557f024b61f4f51a9aacdefcb", - "0x9064c688fec23441a274cdf2075e5a449caf5c7363cc5e8a5dc9747183d2e00a0c69f2e6b3f6a7057079c46014c93b3b", - "0xaa367b021469af9f5b764a79bb3afbe2d87fe1e51862221672d1a66f954b165778b7c27a705e0f93841fab4c8468344d", - "0xa1a8bfc593d4ab71f91640bc824de5c1380ab2591cfdafcbc78a14b32de3c0e15f9d1b461d85c504baa3d4232c16bb53", - "0x97df48da1799430f528184d30b6baa90c2a2f88f34cdfb342d715339c5ebd6d019aa693cea7c4993daafc9849063a3aa", - "0xabd923831fbb427e06e0dd335253178a9e5791395c84d0ab1433c07c53c1209161097e9582fb8736f8a60bde62d8693e", - "0x84cd1a43f1a438b43dc60ffc775f646937c4f6871438163905a3cebf1115f814ccd38a6ccb134130bff226306e412f32", - "0x91426065996b0743c5f689eb3ca68a9f7b9e4d01f6c5a2652b57fa9a03d8dc7cd4bdbdab0ca5a891fee1e97a7f00cf02", - "0xa4bee50249db3df7fd75162b28f04e57c678ba142ce4d3def2bc17bcb29e4670284a45f218dad3969af466c62a903757", - "0x83141ebcc94d4681404e8b67a12a46374fded6df92b506aff3490d875919631408b369823a08b271d006d5b93136f317", - "0xa0ea1c8883d58d5a784da3d8c8a880061adea796d7505c1f903d07c287c5467f71e4563fc0faafbc15b5a5538b0a7559", - "0x89d9d480574f201a87269d26fb114278ed2c446328df431dc3556e3500e80e4cd01fcac196a2459d8646361ebda840df", - "0x8bf302978973632dd464bec819bdb91304712a3ec859be071e662040620422c6e75eba6f864f764cffa2799272efec39", - "0x922f666bc0fd58b6d7d815c0ae4f66d193d32fc8382c631037f59eeaeae9a8ca6c72d08e72944cf9e800b8d639094e77", - "0x81ad8714f491cdff7fe4399f2eb20e32650cff2999dd45b9b3d996d54a4aba24cc6c451212e78c9e5550368a1a38fb3f", - "0xb58fcf4659d73edb73175bd9139d18254e94c3e32031b5d4b026f2ed37aa19dca17ec2eb54c14340231615277a9d347e", - "0xb365ac9c2bfe409b710928c646ea2fb15b28557e0f089d39878e365589b9d1c34baf5566d20bb28b33bb60fa133f6eff", - "0x8fcae1d75b53ab470be805f39630d204853ca1629a14158bac2f52632277d77458dec204ff84b7b2d77e641c2045be65", - "0xa03efa6bebe84f4f958a56e2d76b5ba4f95dd9ed7eb479edc7cc5e646c8d4792e5b0dfc66cc86aa4b4afe2f7a4850760", - "0xaf1c823930a3638975fb0cc5c59651771b2719119c3cd08404fbd4ce77a74d708cefbe3c56ea08c48f5f10e6907f338f", - "0x8260c8299b17898032c761c325ac9cabb4c5b7e735de81eacf244f647a45fb385012f4f8df743128888c29aefcaaad16", - "0xab2f37a573c82e96a8d46198691cd694dfa860615625f477e41f91b879bc58a745784fccd8ffa13065834ffd150d881d", - "0x986c746c9b4249352d8e5c629e8d7d05e716b3c7aab5e529ca969dd1e984a14b5be41528baef4c85d2369a42d7209216", - "0xb25e32da1a8adddf2a6080725818b75bc67240728ad1853d90738485d8924ea1e202df0a3034a60ffae6f965ec55cf63", - "0xa266e627afcebcefea6b6b44cbc50f5c508f7187e87d047b0450871c2a030042c9e376f3ede0afcf9d1952f089582f71", - "0x86c3bbca4c0300606071c0a80dbdec21ce1dd4d8d4309648151c420854032dff1241a1677d1cd5de4e4de4385efda986", - "0xb9a21a1fe2d1f3273a8e4a9185abf2ff86448cc98bfa435e3d68306a2b8b4a6a3ea33a155be3cb62a2170a86f77679a5", - "0xb117b1ea381adce87d8b342cba3a15d492ff2d644afa28f22424cb9cbc820d4f7693dfc1a4d1b3697046c300e1c9b4c8", - "0x9004c425a2e68870d6c69b658c344e3aa3a86a8914ee08d72b2f95c2e2d8a4c7bb0c6e7e271460c0e637cec11117bf8e", - "0x86a18aa4783b9ebd9131580c8b17994825f27f4ac427b0929a1e0236907732a1c8139e98112c605488ee95f48bbefbfc", - "0x84042243b955286482ab6f0b5df4c2d73571ada00716d2f737ca05a0d2e88c6349e8ee9e67934cfee4a1775dbf7f4800", - "0x92c2153a4733a62e4e1d5b60369f3c26777c7d01cd3c8679212660d572bd3bac9b8a8a64e1f10f7dbf5eaa7579c4e423", - "0x918454b6bb8e44a2afa144695ba8d48ae08d0cdfef4ad078f67709eddf3bb31191e8b006f04e82ea45a54715ef4d5817", - "0xacf0b54f6bf34cf6ed6c2b39cf43194a40d68de6bcf1e4b82c34c15a1343e9ac3737885e1a30b78d01fa3a5125463db8", - "0xa7d60dbe4b6a7b054f7afe9ee5cbbfeca0d05dc619e6041fa2296b549322529faddb8a11e949562309aecefb842ac380", - "0x91ffb53e6d7e5f11159eaf13e783d6dbdfdb1698ed1e6dbf3413c6ea23492bbb9e0932230a9e2caac8fe899a17682795", - "0xb6e8d7be5076ee3565d5765a710c5ecf17921dd3cf555c375d01e958a365ae087d4a88da492a5fb81838b7b92bf01143", - "0xa8c6b763de2d4b2ed42102ef64eccfef31e2fb2a8a2776241c82912fa50fc9f77f175b6d109a97ede331307c016a4b1a", - "0x99839f86cb700c297c58bc33e28d46b92931961548deac29ba8df91d3e11721b10ea956c8e16984f9e4acf1298a79b37", - "0x8c2e2c338f25ea5c25756b7131cde0d9a2b35abf5d90781180a00fe4b8e64e62590dc63fe10a57fba3a31c76d784eb01", - "0x9687d7df2f41319ca5469d91978fed0565a5f11f829ebadaa83db92b221755f76c6eacd7700735e75c91e257087512e3", - "0x8795fdfb7ff8439c58b9bf58ed53873d2780d3939b902b9ddaaa4c99447224ced9206c3039a23c2c44bcc461e2bb637f", - "0xa803697b744d2d087f4e2307218d48fa88620cf25529db9ce71e2e3bbcc65bac5e8bb9be04777ef7bfb5ed1a5b8e6170", - "0x80f3d3efbbb9346ddd413f0a8e36b269eb5d7ff6809d5525ff9a47c4bcab2c01b70018b117f6fe05253775612ff70c6b", - "0x9050e0e45bcc83930d4c505af35e5e4d7ca01cd8681cba92eb55821aececcebe32bb692ebe1a4daac4e7472975671067", - "0x8d206812aac42742dbaf233e0c080b3d1b30943b54b60283515da005de05ea5caa90f91fedcfcba72e922f64d7040189", - "0xa2d44faaeb2eff7915c83f32b13ca6f31a6847b1c1ce114ea240bac3595eded89f09b2313b7915ad882292e2b586d5b4", - "0x961776c8576030c39f214ea6e0a3e8b3d32f023d2600958c098c95c8a4e374deeb2b9dc522adfbd6bda5949bdc09e2a2", - "0x993fa7d8447407af0fbcd9e6d77f815fa5233ab00674efbcf74a1f51c37481445ae291cc7b76db7c178f9cb0e570e0fc", - "0xabd5b1c78e05f9d7c8cc99bdaef8b0b6a57f2daf0f02bf492bec48ea4a27a8f1e38b5854da96efff11973326ff980f92", - "0x8f15af4764bc275e6ccb892b3a4362cacb4e175b1526a9a99944e692fe6ccb1b4fc19abf312bb2a089cb1f344d91a779", - "0xa09b27ccd71855512aba1d0c30a79ffbe7f6707a55978f3ced50e674b511a79a446dbc6d7946add421ce111135a460af", - "0x94b2f98ce86a9271fbd4153e1fc37de48421fe3490fb3840c00f2d5a4d0ba8810c6a32880b002f6374b59e0a7952518b", - "0x8650ac644f93bbcb88a6a0f49fee2663297fd4bc6fd47b6a89b9d8038d32370438ab3a4775ec9b58cb10aea8a95ef7b6", - "0x95e5c2f2e84eed88c6980bbba5a1c0bb375d5a628bff006f7516d45bb7d723da676add4fdd45956f312e7bab0f052644", - "0xb3278a3fa377ac93af7cfc9453f8cb594aae04269bbc99d2e0e45472ff4b6a2f97a26c4c57bf675b9d86f5e77a5d55d1", - "0xb4bcbe6eb666a206e2ea2f877912c1d3b5bdbd08a989fc4490eb06013e1a69ad1ba08bcdac048bf29192312be399077b", - "0xa76d70b78c99fffcbf9bb9886eab40f1ea4f99a309710b660b64cbf86057cbcb644d243f6e341711bb7ef0fedf0435a7", - "0xb2093c1ee945dca7ac76ad5aed08eae23af31dd5a77c903fd7b6f051f4ab84425d33a03c3d45bf2907bc93c02d1f3ad8", - "0x904b1f7534e053a265b22d20be859912b9c9ccb303af9a8d6f1d8f6ccdc5c53eb4a45a1762b880d8444d9be0cd55e7f9", - "0x8f664a965d65bc730c9ef1ec7467be984d4b8eb46bd9b0d64e38e48f94e6e55dda19aeac82cbcf4e1473440e64c4ca18", - "0x8bcee65c4cc7a7799353d07b114c718a2aae0cd10a3f22b7eead5185d159dafd64852cb63924bf87627d176228878bce", - "0x8c78f2e3675096fef7ebaa898d2615cd50d39ca3d8f02b9bdfb07e67da648ae4be3da64838dffc5935fd72962c4b96c7", - "0x8c40afd3701629421fec1df1aac4e849384ef2e80472c0e28d36cb1327acdf2826f99b357f3d7afdbc58a6347fc40b3c", - "0xa197813b1c65a8ea5754ef782522a57d63433ef752215ecda1e7da76b0412ee619f58d904abd2e07e0c097048b6ae1dd", - "0xa670542629e4333884ad7410f9ea3bd6f988df4a8f8a424ca74b9add2312586900cf9ae8bd50411f9146e82626b4af56", - "0xa19875cc07ab84e569d98b8b67fb1dbbdfb59093c7b748fae008c8904a6fd931a63ca8d03ab5fea9bc8d263568125a9b", - "0xb57e7f68e4eb1bd04aafa917b1db1bdab759a02aa8a9cdb1cba34ba8852b5890f655645c9b4e15d5f19bf37e9f2ffe9f", - "0x8abe4e2a4f6462b6c64b3f10e45db2a53c2b0d3c5d5443d3f00a453e193df771eda635b098b6c8604ace3557514027af", - "0x8459e4fb378189b22b870a6ef20183deb816cefbf66eca1dc7e86d36a2e011537db893729f500dc154f14ce24633ba47", - "0x930851df4bc7913c0d8c0f7bd3b071a83668987ed7c397d3d042fdc0d9765945a39a3bae83da9c88cb6b686ed8aeeb26", - "0x8078c9e5cd05e1a8c932f8a1d835f61a248b6e7133fcbb3de406bf4ffc0e584f6f9f95062740ba6008d98348886cf76b", - "0xaddff62bb29430983fe578e3709b0949cdc0d47a13a29bc3f50371a2cb5c822ce53e2448cfaa01bcb6e0aa850d5a380e", - "0x9433add687b5a1e12066721789b1db2edf9b6558c3bdc0f452ba33b1da67426abe326e9a34d207bfb1c491c18811bde1", - "0x822beda3389963428cccc4a2918fa9a8a51cf0919640350293af70821967108cded5997adae86b33cb917780b097f1ca", - "0xa7a9f52bda45e4148ed56dd176df7bd672e9b5ed18888ccdb405f47920fdb0844355f8565cefb17010b38324edd8315f", - "0xb35c3a872e18e607b2555c51f9696a17fa18da1f924d503b163b4ec9fe22ed0c110925275cb6c93ce2d013e88f173d6a", - "0xadf34b002b2b26ab84fc1bf94e05bd8616a1d06664799ab149363c56a6e0c807fdc473327d25632416e952ea327fcd95", - "0xae4a6b9d22a4a3183fac29e2551e1124a8ce4a561a9a2afa9b23032b58d444e6155bb2b48f85c7b6d70393274e230db7", - "0xa2ea3be4fc17e9b7ce3110284038d46a09e88a247b6971167a7878d9dcf36925d613c382b400cfa4f37a3ebea3699897", - "0x8e5863786b641ce3140fbfe37124d7ad3925472e924f814ebfc45959aaf3f61dc554a597610b5defaecc85b59a99b50f", - "0xaefde3193d0f700d0f515ab2aaa43e2ef1d7831c4f7859f48e52693d57f97fa9e520090f3ed700e1c966f4b76048e57f", - "0x841a50f772956622798e5cd208dc7534d4e39eddee30d8ce133383d66e5f267e389254a0cdae01b770ecd0a9ca421929", - "0x8fbc2bfd28238c7d47d4c03b1b910946c0d94274a199575e5b23242619b1de3497784e646a92aa03e3e24123ae4fcaba", - "0x926999579c8eec1cc47d7330112586bdca20b4149c8b2d066f527c8b9f609e61ce27feb69db67eea382649c6905efcf9", - "0xb09f31f305efcc65589adf5d3690a76cf339efd67cd43a4e3ced7b839507466e4be72dd91f04e89e4bbef629d46e68c0", - "0xb917361f6b95f759642638e0b1d2b3a29c3bdef0b94faa30de562e6078c7e2d25976159df3edbacbf43614635c2640b4", - "0x8e7e8a1253bbda0e134d62bfe003a2669d471b47bd2b5cde0ff60d385d8e62279d54022f5ac12053b1e2d3aaa6910b4c", - "0xb69671a3c64e0a99d90b0ed108ce1912ff8ed983e4bddd75a370e9babde25ee1f5efb59ec707edddd46793207a8b1fe7", - "0x910b2f4ebd37b7ae94108922b233d0920b4aba0bd94202c70f1314418b548d11d8e9caa91f2cd95aff51b9432d122b7f", - "0x82f645c90dfb52d195c1020346287c43a80233d3538954548604d09fbab7421241cde8593dbc4acc4986e0ea39a27dd9", - "0x8fee895f0a140d88104ce442fed3966f58ff9d275e7373483f6b4249d64a25fb5374bbdc6bce6b5ab0270c2847066f83", - "0x84f5bd7aab27b2509397aeb86510dd5ac0a53f2c8f73799bf720f2f87a52277f8d6b0f77f17bc80739c6a7119b7eb062", - "0x9903ceced81099d7e146e661bcf01cbaccab5ba54366b85e2177f07e2d8621e19d9c9c3eee14b9266de6b3f9b6ea75ae", - "0xb9c16ea2a07afa32dd6c7c06df0dec39bca2067a9339e45475c98917f47e2320f6f235da353fd5e15b477de97ddc68dd", - "0x9820a9bbf8b826bec61ebf886de2c4f404c1ebdc8bab82ee1fea816d9de29127ce1852448ff717a3fe8bbfe9e92012e5", - "0x817224d9359f5da6f2158c2c7bf9165501424f063e67ba9859a07ab72ee2ee62eb00ca6da821cfa19065c3282ca72c74", - "0x94b95c465e6cb00da400558a3c60cfec4b79b27e602ca67cbc91aead08de4b6872d8ea096b0dc06dca4525c8992b8547", - "0xa2b539a5bccd43fa347ba9c15f249b417997c6a38c63517ca38394976baa08e20be384a360969ff54e7e721db536b3e5", - "0x96caf707e34f62811ee8d32ccf28d8d6ec579bc33e424d0473529af5315c456fd026aa910c1fed70c91982d51df7d3ca", - "0x8a77b73e890b644c6a142bdbac59b22d6a676f3b63ddafb52d914bb9d395b8bf5aedcbcc90429337df431ebd758a07a6", - "0x8857830a7351025617a08bc44caec28d2fae07ebf5ffc9f01d979ce2a53839a670e61ae2783e138313929129790a51a1", - "0xaa3e420321ed6f0aa326d28d1a10f13facec6f605b6218a6eb9cbc074801f3467bf013a456d1415a5536f12599efa3d3", - "0x824aed0951957b00ea2f3d423e30328a3527bf6714cf9abbae84cf27e58e5c35452ba89ccc011de7c68c75d6e021d8f1", - "0xa2e87cc06bf202e953fb1081933d8b4445527dde20e38ed1a4f440144fd8fa464a2b73e068b140562e9045e0f4bd3144", - "0xae3b8f06ad97d7ae3a5e5ca839efff3e4824dc238c0c03fc1a8d2fc8aa546cdfd165b784a31bb4dec7c77e9305b99a4b", - "0xb30c3e12395b1fb8b776f3ec9f87c70e35763a7b2ddc68f0f60a4982a84017f27c891a98561c830038deb033698ed7fc", - "0x874e507757cd1177d0dff0b0c62ce90130324442a33da3b2c8ee09dbca5d543e3ecfe707e9f1361e7c7db641c72794bb", - "0xb53012dd10b5e7460b57c092eaa06d6502720df9edbbe3e3f61a9998a272bf5baaac4a5a732ad4efe35d6fac6feca744", - "0x85e6509d711515534d394e6cacbed6c81da710074d16ef3f4950bf2f578d662a494d835674f79c4d6315bced4defc5f0", - "0xb6132b2a34b0905dcadc6119fd215419a7971fe545e52f48b768006944b4a9d7db1a74b149e2951ea48c083b752d0804", - "0x989867da6415036d19b4bacc926ce6f4df7a556f50a1ba5f3c48eea9cefbb1c09da81481c8009331ee83f0859185e164", - "0x960a6c36542876174d3fbc1505413e29f053ed87b8d38fef3af180491c7eff25200b45dd5fe5d4d8e63c7e8c9c00f4c8", - "0x9040b59bd739d9cc2e8f6e894683429e4e876a8106238689ff4c22770ae5fdae1f32d962b30301fa0634ee163b524f35", - "0xaf3fcd0a45fe9e8fe256dc7eab242ef7f582dd832d147444483c62787ac820fafc6ca55d639a73f76bfa5e7f5462ab8f", - "0xb934c799d0736953a73d91e761767fdb78454355c4b15c680ce08accb57ccf941b13a1236980001f9e6195801cffd692", - "0x8871e8e741157c2c326b22cf09551e78da3c1ec0fc0543136f581f1550f8bab03b0a7b80525c1e99812cdbf3a9698f96", - "0xa8a977f51473a91d178ee8cfa45ffef8d6fd93ab1d6e428f96a3c79816d9c6a93cd70f94d4deda0125fd6816e30f3bea", - "0xa7688b3b0a4fc1dd16e8ba6dc758d3cfe1b7cf401c31739484c7fa253cce0967df1b290769bcefc9d23d3e0cb19e6218", - "0x8ae84322662a57c6d729e6ff9d2737698cc2da2daeb1f39e506618750ed23442a6740955f299e4a15dda6db3e534d2c6", - "0xa04a961cdccfa4b7ef83ced17ab221d6a043b2c718a0d6cc8e6f798507a31f10bf70361f70a049bc8058303fa7f96864", - "0xb463e39732a7d9daec8a456fb58e54b30a6e160aa522a18b9a9e836488cce3342bcbb2e1deab0f5e6ec0a8796d77197d", - "0xb1434a11c6750f14018a2d3bcf94390e2948f4f187e93bb22070ca3e5393d339dc328cbfc3e48815f51929465ffe7d81", - "0x84ff81d73f3828340623d7e3345553610aa22a5432217ef0ebd193cbf4a24234b190c65ca0873c22d10ea7b63bd1fbed", - "0xb6fe2723f0c47757932c2ddde7a4f8434f665612f7b87b4009c2635d56b6e16b200859a8ade49276de0ef27a2b6c970a", - "0x9742884ed7cd52b4a4a068a43d3faa02551a424136c85a9313f7cb58ea54c04aa83b0728fd741d1fe39621e931e88f8f", - "0xb7d2d65ea4d1ad07a5dee39e40d6c03a61264a56b1585b4d76fc5b2a68d80a93a42a0181d432528582bf08d144c2d6a9", - "0x88c0f66bada89f8a43e5a6ead2915088173d106c76f724f4a97b0f6758aed6ae5c37c373c6b92cdd4aea8f6261f3a374", - "0x81f9c43582cb42db3900747eb49ec94edb2284999a499d1527f03315fd330e5a509afa3bff659853570e9886aab5b28b", - "0x821f9d27d6beb416abf9aa5c79afb65a50ed276dbda6060103bc808bcd34426b82da5f23e38e88a55e172f5c294b4d40", - "0x8ba307b9e7cb63a6c4f3851b321aebfdb6af34a5a4c3bd949ff7d96603e59b27ff4dc4970715d35f7758260ff942c9e9", - "0xb142eb6c5f846de33227d0bda61d445a7c33c98f0a8365fe6ab4c1fabdc130849be597ef734305894a424ea715372d08", - "0xa732730ae4512e86a741c8e4c87fee8a05ee840fec0e23b2e037d58dba8dde8d10a9bc5191d34d00598941becbbe467f", - "0xadce6f7c30fd221f6b10a0413cc76435c4bb36c2d60bca821e5c67409fe9dbb2f4c36ef85eb3d734695e4be4827e9fd3", - "0xa74f00e0f9b23aff7b2527ce69852f8906dab9d6abe62ecd497498ab21e57542e12af9918d4fd610bb09e10b0929c510", - "0xa593b6b0ef26448ce4eb3ab07e84238fc020b3cb10d542ff4b16d4e2be1bcde3797e45c9cf753b8dc3b0ffdb63984232", - "0xaed3913afccf1aa1ac0eb4980eb8426d0baccebd836d44651fd72af00d09fac488a870223c42aca3ceb39752070405ae", - "0xb2c44c66a5ea7fde626548ba4cef8c8710191343d3dadfd3bb653ce715c0e03056a5303a581d47dde66e70ea5a2d2779", - "0x8e5029b2ccf5128a12327b5103f7532db599846e422531869560ceaff392236434d87159f597937dbf4054f810c114f4", - "0x82beed1a2c4477e5eb39fc5b0e773b30cfec77ef2b1bf17eadaf60eb35b6d0dd9d8cf06315c48d3546badb3f21cd0cca", - "0x90077bd6cc0e4be5fff08e5d07a5a158d36cebd1d1363125bc4fae0866ffe825b26f933d4ee5427ba5cd0c33c19a7b06", - "0xa7ec0d8f079970e8e34f0ef3a53d3e0e45428ddcef9cc776ead5e542ef06f3c86981644f61c5a637e4faf001fb8c6b3e", - "0xae6d4add6d1a6f90b22792bc9d40723ee6850c27d0b97eefafd5b7fd98e424aa97868b5287cc41b4fbd7023bca6a322c", - "0x831aa917533d077da07c01417feaa1408846363ba2b8d22c6116bb858a95801547dd88b7d7fa1d2e3f0a02bdeb2e103d", - "0x96511b860b07c8a5ed773f36d4aa9d02fb5e7882753bf56303595bcb57e37ccc60288887eb83bef08c657ec261a021a2", - "0x921d2a3e7e9790f74068623de327443666b634c8443aba80120a45bba450df920b2374d96df1ce3fb1b06dd06f8cf6e3", - "0xaa74451d51fe82b4581ead8e506ec6cd881010f7e7dd51fc388eb9a557db5d3c6721f81c151d08ebd9c2591689fbc13e", - "0xa972bfbcf4033d5742d08716c927c442119bdae336bf5dff914523b285ccf31953da2733759aacaa246a9af9f698342c", - "0xad1fcd0cae0e76840194ce4150cb8a56ebed728ec9272035f52a799d480dfc85840a4d52d994a18b6edb31e79be6e8ad", - "0xa2c69fe1d36f235215432dad48d75887a44c99dfa0d78149acc74087da215a44bdb5f04e6eef88ff7eff80a5a7decc77", - "0xa94ab2af2b6ee1bc6e0d4e689ca45380d9fbd3c5a65b9bd249d266a4d4c07bf5d5f7ef2ae6000623aee64027892bf8fe", - "0x881ec1fc514e926cdc66480ac59e139148ff8a2a7895a49f0dff45910c90cdda97b66441a25f357d6dd2471cddd99bb3", - "0x884e6d3b894a914c8cef946a76d5a0c8351843b2bffa2d1e56c6b5b99c84104381dd1320c451d551c0b966f4086e60f9", - "0x817c6c10ce2677b9fc5223500322e2b880583254d0bb0d247d728f8716f5e05c9ff39f135854342a1afecd9fbdcf7c46", - "0xaaf4a9cb686a14619aa1fc1ac285dd3843ac3dd99f2b2331c711ec87b03491c02f49101046f3c5c538dc9f8dba2a0ac2", - "0x97ecea5ce53ca720b5d845227ae61d70269a2f53540089305c86af35f0898bfd57356e74a8a5e083fa6e1ea70080bd31", - "0xa22d811e1a20a75feac0157c418a4bfe745ccb5d29466ffa854dca03e395b6c3504a734341746b2846d76583a780b32e", - "0x940cbaa0d2b2db94ae96b6b9cf2deefbfd059e3e5745de9aec4a25f0991b9721e5cd37ef71c631575d1a0c280b01cd5b", - "0xae33cb4951191258a11044682de861bf8d92d90ce751b354932dd9f3913f542b6a0f8a4dc228b3cd9244ac32c4582832", - "0xa580df5e58c4274fe0f52ac2da1837e32f5c9db92be16c170187db4c358f43e5cfdda7c5911dcc79d77a5764e32325f5", - "0x81798178cb9d8affa424f8d3be67576ba94d108a28ccc01d330c51d5a63ca45bb8ca63a2f569b5c5fe1303cecd2d777f", - "0x89975b91b94c25c9c3660e4af4047a8bacf964783010820dbc91ff8281509379cb3b24c25080d5a01174dd9a049118d5", - "0xa7327fcb3710ed3273b048650bde40a32732ef40a7e58cf7f2f400979c177944c8bc54117ba6c80d5d4260801dddab79", - "0x92b475dc8cb5be4b90c482f122a51bcb3b6c70593817e7e2459c28ea54a7845c50272af38119406eaadb9bcb993368d0", - "0x9645173e9ecefc4f2eae8363504f7c0b81d85f8949a9f8a6c01f2d49e0a0764f4eacecf3e94016dd407fc14494fce9f9", - "0x9215fd8983d7de6ae94d35e6698226fc1454977ae58d42d294be9aad13ac821562ad37d5e7ee5cdfe6e87031d45cd197", - "0x810360a1c9b88a9e36f520ab5a1eb8bed93f52deefbe1312a69225c0a08edb10f87cc43b794aced9c74220cefcc57e7d", - "0xad7e810efd61ed4684aeda9ed8bb02fb9ae4b4b63fda8217d37012b94ff1b91c0087043bfa4e376f961fff030c729f3b", - "0x8b07c95c6a06db8738d10bb03ec11b89375c08e77f0cab7e672ce70b2685667ca19c7e1c8b092821d31108ea18dfd4c7", - "0x968825d025ded899ff7c57245250535c732836f7565eab1ae23ee7e513201d413c16e1ba3f5166e7ac6cf74de8ceef4f", - "0x908243370c5788200703ade8164943ad5f8c458219186432e74dbc9904a701ea307fd9b94976c866e6c58595fd891c4b", - "0x959969d16680bc535cdc6339e6186355d0d6c0d53d7bbfb411641b9bf4b770fd5f575beef5deec5c4fa4d192d455c350", - "0xad177f4f826a961adeac76da40e2d930748effff731756c797eddc4e5aa23c91f070fb69b19221748130b0961e68a6bb", - "0x82f8462bcc25448ef7e0739425378e9bb8a05e283ce54aae9dbebaf7a3469f57833c9171672ad43a79778366c72a5e37", - "0xa28fb275b1845706c2814d9638573e9bc32ff552ebaed761fe96fdbce70395891ca41c400ae438369264e31a2713b15f", - "0x8a9c613996b5e51dadb587a787253d6081ea446bf5c71096980bf6bd3c4b69905062a8e8a3792de2d2ece3b177a71089", - "0x8d5aefef9f60cb27c1db2c649221204dda48bb9bf8bf48f965741da051340e8e4cab88b9d15c69f3f84f4c854709f48a", - "0x93ebf2ca6ad85ab6deace6de1a458706285b31877b1b4d7dcb9d126b63047efaf8c06d580115ec9acee30c8a7212fa55", - "0xb3ee46ce189956ca298057fa8223b7fd1128cf52f39159a58bca03c71dd25161ac13f1472301f72aef3e1993fe1ab269", - "0xa24d7a8d066504fc3f5027ccb13120e2f22896860e02c45b5eba1dbd512d6a17c28f39155ea581619f9d33db43a96f92", - "0xae9ceacbfe12137db2c1a271e1b34b8f92e4816bad1b3b9b6feecc34df0f8b3b0f7ed0133acdf59c537d43d33fc8d429", - "0x83967e69bf2b361f86361bd705dce0e1ad26df06da6c52b48176fe8dfcbeb03c462c1a4c9e649eff8c654b18c876fdef", - "0x9148e6b814a7d779c19c31e33a068e97b597de1f8100513db3c581190513edc4d544801ce3dd2cf6b19e0cd6daedd28a", - "0x94ccdafc84920d320ed22de1e754adea072935d3c5f8c2d1378ebe53d140ea29853f056fb3fb1e375846061a038cc9bc", - "0xafb43348498c38b0fa5f971b8cdd3a62c844f0eb52bc33daf2f67850af0880fce84ecfb96201b308d9e6168a0d443ae3", - "0x86d5736520a83538d4cd058cc4b4e84213ed00ebd6e7af79ae787adc17a92ba5359e28ba6c91936d967b4b28d24c3070", - "0xb5210c1ff212c5b1e9ef9126e08fe120a41e386bb12c22266f7538c6d69c7fd8774f11c02b81fd4e88f9137b020801fe", - "0xb78cfd19f94d24e529d0f52e18ce6185cb238edc6bd43086270fd51dd99f664f43dd4c7d2fe506762fbd859028e13fcf", - "0xa6e7220598c554abdcc3fdc587b988617b32c7bb0f82c06205467dbedb58276cc07cae317a190f19d19078773f4c2bbb", - "0xb88862809487ee430368dccd85a5d72fa4d163ca4aad15c78800e19c1a95be2192719801e315d86cff7795e0544a77e4", - "0x87ecb13a03921296f8c42ceb252d04716f10e09c93962239fcaa0a7fef93f19ab3f2680bc406170108bc583e9ff2e721", - "0xa810cd473832b6581c36ec4cb403f2849357ba2d0b54df98ef3004b8a530c078032922a81d40158f5fb0043d56477f6e", - "0xa247b45dd85ca7fbb718b328f30a03f03c84aef2c583fbdc9fcc9eb8b52b34529e8c8f535505c10598b1b4dac3d7c647", - "0x96ee0b91313c68bac4aa9e065ce9e1d77e51ca4cff31d6a438718c58264dee87674bd97fc5c6b8008be709521e4fd008", - "0x837567ad073e42266951a9a54750919280a2ac835a73c158407c3a2b1904cf0d17b7195a393c71a18ad029cbd9cf79ee", - "0xa6a469c44b67ebf02196213e7a63ad0423aab9a6e54acc6fcbdbb915bc043586993454dc3cd9e4be8f27d67c1050879b", - "0x8712d380a843b08b7b294f1f06e2f11f4ad6bcc655fdde86a4d8bc739c23916f6fad2b902fe47d6212f03607907e9f0e", - "0x920adfb644b534789943cdae1bdd6e42828dda1696a440af2f54e6b97f4f97470a1c6ea9fa6a2705d8f04911d055acd1", - "0xa161c73adf584a0061e963b062f59d90faac65c9b3a936b837a10d817f02fcabfa748824607be45a183dd40f991fe83f", - "0x874f4ecd408c76e625ea50bc59c53c2d930ee25baf4b4eca2440bfbffb3b8bc294db579caa7c68629f4d9ec24187c1ba", - "0x8bff18087f112be7f4aa654e85c71fef70eee8ae480f61d0383ff6f5ab1a0508f966183bb3fc4d6f29cb7ca234aa50d3", - "0xb03b46a3ca3bc743a173cbc008f92ab1aedd7466b35a6d1ca11e894b9482ea9dc75f8d6db2ddd1add99bfbe7657518b7", - "0x8b4f3691403c3a8ad9e097f02d130769628feddfa8c2b3dfe8cff64e2bed7d6e5d192c1e2ba0ac348b8585e94acd5fa1", - "0xa0d9ca4a212301f97591bf65d5ef2b2664766b427c9dd342e23cb468426e6a56be66b1cb41fea1889ac5d11a8e3c50a5", - "0x8c93ed74188ca23b3df29e5396974b9cc135c91fdefdea6c0df694c8116410e93509559af55533a3776ac11b228d69b1", - "0x82dd331fb3f9e344ebdeeb557769b86a2cc8cc38f6c298d7572a33aea87c261afa9dbd898989139b9fc16bc1e880a099", - "0xa65faedf326bcfd8ef98a51410c78b021d39206704e8291cd1f09e096a66b9b0486be65ff185ca224c45918ac337ddeb", - "0xa188b37d363ac072a766fd5d6fa27df07363feff1342217b19e3c37385e42ffde55e4be8355aceaa2f267b6d66b4ac41", - "0x810fa3ba3e96d843e3bafd3f2995727f223d3567c8ba77d684c993ba1773c66551eb5009897c51b3fe9b37196984f5ec", - "0x87631537541852da323b4353af45a164f68b304d24c01183bf271782e11687f3fcf528394e1566c2a26cb527b3148e64", - "0xb721cb2b37b3c477a48e3cc0044167d51ff568a5fd2fb606e5aec7a267000f1ddc07d3db919926ae12761a8e017c767c", - "0x904dfad4ba2cc1f6e60d1b708438a70b1743b400164cd981f13c064b8328d5973987d4fb9cf894068f29d3deaf624dfb", - "0xa70491538893552c20939fae6be2f07bfa84d97e2534a6bbcc0f1729246b831103505e9f60e97a8fa7d2e6c1c2384579", - "0x8726cf1b26b41f443ff7485adcfddc39ace2e62f4d65dd0bb927d933e262b66f1a9b367ded5fbdd6f3b0932553ac1735", - "0xae8a11cfdf7aa54c08f80cb645e3339187ab3886babe9fae5239ba507bb3dd1c0d161ca474a2df081dcd3d63e8fe445e", - "0x92328719e97ce60e56110f30a00ac5d9c7a2baaf5f8d22355d53c1c77941e3a1fec7d1405e6fbf8959665fe2ba7a8cad", - "0x8d9d6255b65798d0018a8cccb0b6343efd41dc14ff2058d3eed9451ceaad681e4a0fa6af67b0a04318aa628024e5553d", - "0xb70209090055459296006742d946a513f0cba6d83a05249ee8e7a51052b29c0ca9722dc4af5f9816a1b7938a5dac7f79", - "0xaab7b766b9bf91786dfa801fcef6d575dc6f12b77ecc662eb4498f0312e54d0de9ea820e61508fc8aeee5ab5db529349", - "0xa8104b462337748b7f086a135d0c3f87f8e51b7165ca6611264b8fb639d9a2f519926cb311fa2055b5fadf03da70c678", - "0xb0d2460747d5d8b30fc6c6bd0a87cb343ddb05d90a51b465e8f67d499cfc5e3a9e365da05ae233bbee792cdf90ec67d5", - "0xaa55f5bf3815266b4a149f85ed18e451c93de9163575e3ec75dd610381cc0805bb0a4d7c4af5b1f94d10231255436d2c", - "0x8d4c6a1944ff94426151909eb5b99cfd92167b967dabe2bf3aa66bb3c26c449c13097de881b2cfc1bf052862c1ef7b03", - "0x8862296162451b9b6b77f03bf32e6df71325e8d7485cf3335d66fd48b74c2a8334c241db8263033724f26269ad95b395", - "0x901aa96deb26cda5d9321190ae6624d357a41729d72ef1abfd71bebf6139af6d690798daba53b7bc5923462115ff748a", - "0x96c195ec4992728a1eb38cdde42d89a7bce150db43adbc9e61e279ea839e538deec71326b618dd39c50d589f78fc0614", - "0xb6ff8b8aa0837b99a1a8b46fb37f20ad4aecc6a98381b1308697829a59b8442ffc748637a88cb30c9b1f0f28a926c4f6", - "0x8d807e3dca9e7bef277db1d2cfb372408dd587364e8048b304eff00eacde2c723bfc84be9b98553f83cba5c7b3cba248", - "0x8800c96adb0195c4fc5b24511450dee503c32bf47044f5e2e25bd6651f514d79a2dd9b01cd8c09f3c9d3859338490f57", - "0x89fe366096097e38ec28dd1148887112efa5306cc0c3da09562aafa56f4eb000bf46ff79bf0bdd270cbde6bf0e1c8957", - "0xaf409a90c2776e1e7e3760b2042507b8709e943424606e31e791d42f17873a2710797f5baaab4cc4a19998ef648556b0", - "0x8d761863c9b6edbd232d35ab853d944f5c950c2b643f84a1a1327ebb947290800710ff01dcfa26dc8e9828481240e8b1", - "0x90b95e9be1e55c463ed857c4e0617d6dc3674e99b6aa62ed33c8e79d6dfcf7d122f4f4cc2ee3e7c5a49170cb617d2e2e", - "0xb3ff381efefabc4db38cc4727432e0301949ae4f16f8d1dea9b4f4de611cf5a36d84290a0bef160dac4e1955e516b3b0", - "0xa8a84564b56a9003adcadb3565dc512239fc79572762cda7b5901a255bc82656bb9c01212ad33d6bef4fbbce18dacc87", - "0x90a081890364b222eef54bf0075417f85e340d2fec8b7375995f598aeb33f26b44143ebf56fca7d8b4ebb36b5747b0eb", - "0xade6ee49e1293224ddf2d8ab7f14bb5be6bc6284f60fd5b3a1e0cf147b73cff57cf19763b8a36c5083badc79c606b103", - "0xb2fa99806dd2fa3de09320b615a2570c416c9bcdb052e592b0aead748bbe407ec9475a3d932ae48b71c2627eb81986a6", - "0x91f3b7b73c8ccc9392542711c45fe6f236057e6efad587d661ad5cb4d6e88265f86b807bb1151736b1009ab74fd7acb4", - "0x8800e2a46af96696dfbdcbf2ca2918b3dcf28ad970170d2d1783b52b8d945a9167d052beeb55f56c126da7ffa7059baa", - "0x9862267a1311c385956b977c9aa08548c28d758d7ba82d43dbc3d0a0fd1b7a221d39e8399997fea9014ac509ff510ac4", - "0xb7d24f78886fd3e2d283e18d9ad5a25c1a904e7d9b9104bf47da469d74f34162e27e531380dbbe0a9d051e6ffd51d6e7", - "0xb0f445f9d143e28b9df36b0f2c052da87ee2ca374d9d0fbe2eff66ca6fe5fe0d2c1951b428d58f7314b7e74e45d445ea", - "0xb63fc4083eabb8437dafeb6a904120691dcb53ce2938b820bb553da0e1eecd476f72495aacb72600cf9cad18698fd3db", - "0xb9ffd8108eaebd582d665f8690fe8bb207fd85185e6dd9f0b355a09bac1bbff26e0fdb172bc0498df025414e88fe2eda", - "0x967ed453e1f1a4c5b7b6834cc9f75c13f6889edc0cc91dc445727e9f408487bbf05c337103f61397a10011dfbe25d61d", - "0x98ceb673aff36e1987d5521a3984a07079c3c6155974bb8b413e8ae1ce84095fe4f7862fba7aefa14753eb26f2a5805f", - "0x85f01d28603a8fdf6ce6a50cb5c44f8a36b95b91302e3f4cd95c108ce8f4d212e73aec1b8d936520d9226802a2bd9136", - "0x88118e9703200ca07910345fbb789e7a8f92bd80bbc79f0a9e040e8767d33df39f6eded403a9b636eabf9101e588482a", - "0x90833a51eef1b10ed74e8f9bbd6197e29c5292e469c854eed10b0da663e2bceb92539710b1858bbb21887bd538d28d89", - "0xb513b905ec19191167c6193067b5cfdf5a3d3828375360df1c7e2ced5815437dfd37f0c4c8f009d7fb29ff3c8793f560", - "0xb1b6d405d2d18f9554b8a358cc7e2d78a3b34269737d561992c8de83392ac9a2857be4bf15de5a6c74e0c9d0f31f393c", - "0xb828bd3e452b797323b798186607849f85d1fb20c616833c0619360dfd6b3e3aa000fd09dafe4b62d74abc41072ff1a9", - "0x8efde67d0cca56bb2c464731879c9ac46a52e75bac702a63200a5e192b4f81c641f855ca6747752b84fe469cb7113b6c", - "0xb2762ba1c89ac3c9a983c242e4d1c2610ff0528585ed5c0dfc8a2c0253551142af9b59f43158e8915a1da7cc26b9df67", - "0x8a3f1157fb820d1497ef6b25cd70b7e16bb8b961b0063ad340d82a79ee76eb2359ca9e15e6d42987ed7f154f5eeaa2da", - "0xa75e29f29d38f09c879f971c11beb5368affa084313474a5ecafa2896180b9e47ea1995c2733ec46f421e395a1d9cffe", - "0x8e8c3dd3e7196ef0b4996b531ec79e4a1f211db5d5635e48ceb80ff7568b2ff587e845f97ee703bb23a60945ad64314a", - "0x8e7f32f4a3e3c584af5e3d406924a0aa34024c42eca74ef6cc2a358fd3c9efaf25f1c03aa1e66bb94b023a2ee2a1cace", - "0xab7dce05d59c10a84feb524fcb62478906b3fa045135b23afbede3bb32e0c678d8ebe59feabccb5c8f3550ea76cae44b", - "0xb38bb4b44d827f6fd3bd34e31f9186c59e312dbfadd4a7a88e588da10146a78b1f8716c91ad8b806beb8da65cab80c4c", - "0x9490ce9442bbbd05438c7f5c4dea789f74a7e92b1886a730544b55ba377840740a3ae4f2f146ee73f47c9278b0e233bc", - "0x83c003fab22a7178eed1a668e0f65d4fe38ef3900044e9ec63070c23f2827d36a1e73e5c2b883ec6a2afe2450171b3b3", - "0x9982f02405978ddc4fca9063ebbdb152f524c84e79398955e66fe51bc7c1660ec1afc3a86ec49f58d7b7dde03505731c", - "0xab337bd83ccdd2322088ffa8d005f450ced6b35790f37ab4534313315ee84312adc25e99cce052863a8bedee991729ed", - "0x8312ce4bec94366d88f16127a17419ef64285cd5bf9e5eda010319b48085966ed1252ed2f5a9fd3e0259b91bb65f1827", - "0xa60d5a6327c4041b0c00a1aa2f0af056520f83c9ce9d9ccd03a0bd4d9e6a1511f26a422ea86bd858a1f77438adf07e6c", - "0xb84a0a0b030bdad83cf5202aa9afe58c9820e52483ab41f835f8c582c129ee3f34aa096d11c1cd922eda02ea1196a882", - "0x8077d105317f4a8a8f1aadeb05e0722bb55f11abcb490c36c0904401107eb3372875b0ac233144829e734f0c538d8c1d", - "0x9202503bd29a6ec198823a1e4e098f9cfe359ed51eb5174d1ca41368821bfeebcbd49debfd02952c41359d1c7c06d2b1", - "0xabc28c155e09365cb77ffead8dc8f602335ef93b2f44e4ef767ce8fc8ef9dd707400f3a722e92776c2e0b40192c06354", - "0xb0f6d1442533ca45c9399e0a63a11f85ff288d242cea6cb3b68c02e77bd7d158047cae2d25b3bcd9606f8f66d9b32855", - "0xb01c3d56a0db84dc94575f4b6ee2de4beca3230e86bed63e2066beb22768b0a8efb08ebaf8ac3dedb5fe46708b084807", - "0x8c8634b0432159f66feaabb165842d1c8ac378f79565b1b90c381aa8450eb4231c3dad11ec9317b9fc2b155c3a771e32", - "0x8e67f623d69ecd430c9ee0888520b6038f13a2b6140525b056dc0951f0cfed2822e62cf11d952a483107c5c5acac4826", - "0x9590bb1cba816dd6acd5ac5fba5142c0a19d53573e422c74005e0bcf34993a8138c83124cad35a3df65879dba6134edd", - "0x801cd96cde0749021a253027118d3ea135f3fcdbe895db08a6c145641f95ebd368dd6a1568d995e1d0084146aebe224a", - "0x848b5d196427f6fc1f762ee3d36e832b64a76ec1033cfedc8b985dea93932a7892b8ef1035c653fb9dcd9ab2d9a44ac8", - "0xa1017eb83d5c4e2477e7bd2241b2b98c4951a3b391081cae7d75965cadc1acaec755cf350f1f3d29741b0828e36fedea", - "0x8d6d2785e30f3c29aad17bd677914a752f831e96d46caf54446d967cb2432be2c849e26f0d193a60bee161ea5c6fe90a", - "0x935c0ba4290d4595428e034b5c8001cbd400040d89ab00861108e8f8f4af4258e41f34a7e6b93b04bc253d3b9ffc13bf", - "0xaac02257146246998477921cef2e9892228590d323b839f3e64ea893b991b463bc2f47e1e5092ddb47e70b2f5bce7622", - "0xb921fde9412970a5d4c9a908ae8ce65861d06c7679af577cf0ad0d5344c421166986bee471fd6a6cecb7d591f06ec985", - "0x8ef4c37487b139d6756003060600bb6ebac7ea810b9c4364fc978e842f13ac196d1264fbe5af60d76ff6d9203d8e7d3f", - "0x94b65e14022b5cf6a9b95f94be5ace2711957c96f4211c3f7bb36206bd39cfbd0ea82186cab5ad0577a23214a5c86e9e", - "0xa31c166d2a2ca1d5a75a5920fef7532681f62191a50d8555fdaa63ba4581c3391cc94a536fc09aac89f64eafceec3f90", - "0x919a8cc128de01e9e10f5d83b08b52293fdd41bde2b5ae070f3d95842d4a16e5331cf2f3d61c765570c8022403610fa4", - "0xb23d6f8331eef100152d60483cfa14232a85ee712c8538c9b6417a5a7c5b353c2ac401390c6c215cb101f5cee6b5f43e", - "0xab357160c08a18319510a571eafff154298ce1020de8e1dc6138a09fcb0fcbcdd8359f7e9386bda00b7b9cdea745ffdc", - "0xab55079aea34afa5c0bd1124b9cdfe01f325b402fdfa017301bf87812eaa811ea5798c3aaf818074d420d1c782b10ada", - "0xade616010dc5009e7fc4f8d8b00dc716686a5fa0a7816ad9e503e15839d3b909b69d9dd929b7575376434ffec0d2bea8", - "0x863997b97ed46898a8a014599508fa3079f414b1f4a0c4fdc6d74ae8b444afa350f327f8bfc2a85d27f9e2d049c50135", - "0x8d602ff596334efd4925549ed95f2aa762b0629189f0df6dbb162581657cf3ea6863cd2287b4d9c8ad52813d87fcd235", - "0xb70f68c596dcdeed92ad5c6c348578b26862a51eb5364237b1221e840c47a8702f0fbc56eb520a22c0eed99795d3903e", - "0x9628088f8e0853cefadee305a8bf47fa990c50fa96a82511bbe6e5dc81ef4b794e7918a109070f92fc8384d77ace226f", - "0x97e26a46e068b605ce96007197ecd943c9a23881862f4797a12a3e96ba2b8d07806ad9e2a0646796b1889c6b7d75188c", - "0xb1edf467c068cc163e2d6413cc22b16751e78b3312fe47b7ea82b08a1206d64415b2c8f2a677fa89171e82cc49797150", - "0xa44d15ef18745b251429703e3cab188420e2d974de07251501799b016617f9630643fcd06f895634d8ecdd579e1bf000", - "0xabd126df3917ba48c618ee4dbdf87df506193462f792874439043fa1b844466f6f4e0ff2e42516e63b5b23c0892b2695", - "0xa2a67f57c4aa3c2aa1eeddbfd5009a89c26c2ce8fa3c96a64626aba19514beb125f27df8559506f737de3eae0f1fc18f", - "0xa633e0132197e6038197304b296ab171f1d8e0d0f34dcf66fe9146ac385b0239232a8470b9205a4802ab432389f4836d", - "0xa914b3a28509a906c3821463b936455d58ff45dcbe158922f9efb2037f2eb0ce8e92532d29b5d5a3fcd0d23fa773f272", - "0xa0e1412ce4505daf1a2e59ce4f0fc0e0023e335b50d2b204422f57cd65744cc7a8ed35d5ef131a42c70b27111d3115b7", - "0xa2339e2f2b6072e88816224fdd612c04d64e7967a492b9f8829db15367f565745325d361fd0607b0def1be384d010d9e", - "0xa7309fc41203cb99382e8193a1dcf03ac190a7ce04835304eb7e341d78634e83ea47cb15b885601956736d04cdfcaa01", - "0x81f3ccd6c7f5b39e4e873365f8c37b214e8ab122d04a606fbb7339dc3298c427e922ec7418002561d4106505b5c399ee", - "0x92c121cf914ca549130e352eb297872a63200e99b148d88fbc9506ad882bec9d0203d65f280fb5b0ba92e336b7f932e8", - "0xa4b330cf3f064f5b131578626ad7043ce2a433b6f175feb0b52d36134a454ca219373fd30d5e5796410e005b69082e47", - "0x86fe5774112403ad83f9c55d58317eeb17ad8e1176d9f2f69c2afb7ed83bc718ed4e0245ceab4b377f5f062dcd4c00e7", - "0x809d152a7e2654c7fd175b57f7928365a521be92e1ed06c05188a95864ddb25f7cab4c71db7d61bbf4cae46f3a1d96ce", - "0xb82d663e55c2a5ada7e169e9b1a87bc1c0177baf1ec1c96559b4cb1c5214ce1ddf2ab8d345014cab6402f3774235cf5a", - "0x86580af86df1bd2c385adb8f9a079e925981b7184db66fc5fe5b14cddb82e7d836b06eaeef14924ac529487b23dae111", - "0xb5f5f4c5c94944ecc804df6ab8687d64e27d988cbfeae1ba7394e0f6adbf778c5881ead7cd8082dd7d68542b9bb4ecd5", - "0xa6016916146c2685c46e8fdd24186394e2d5496e77e08c0c6a709d4cd7dfa97f1efcef94922b89196819076a91ad37b5", - "0xb778e7367ded3b6eab53d5fc257f7a87e8faf74a593900f2f517220add2125be3f6142022660d8181df8d164ad9441ce", - "0x8581b2d36abe6f553add4d24be761bec1b8efaa2929519114346615380b3c55b59e6ad86990e312f7e234d0203bdf59b", - "0x9917e74fd45c3f71a829ff5498a7f6b5599b48c098dda2339bf04352bfc7f368ccf1a407f5835901240e76452ae807d7", - "0xafd196ce6f9335069138fd2e3d133134da253978b4ce373152c0f26affe77a336505787594022e610f8feb722f7cc1fb", - "0xa477491a1562e329764645e8f24d8e228e5ef28c9f74c6b5b3abc4b6a562c15ffb0f680d372aed04d9e1bf944dece7be", - "0x9767440d58c57d3077319d3a330e5322b9ba16981ec74a5a14d53462eab59ae7fd2b14025bfc63b268862094acb444e6", - "0x80986d921be3513ef69264423f351a61cb48390c1be8673aee0f089076086aaebea7ebe268fd0aa7182695606116f679", - "0xa9554c5c921c07b450ee04e34ec58e054ac1541b26ce2ce5a393367a97348ba0089f53db6660ad76b60278b66fd12e3e", - "0x95097e7d2999b3e84bf052c775581cf361325325f4a50192521d8f4693c830bed667d88f482dc1e3f833aa2bd22d2cbf", - "0x9014c91d0f85aefd28436b5228c12f6353c055a9326c7efbf5e071e089e2ee7c070fcbc84c5fafc336cbb8fa6fec1ca1", - "0x90f57ba36ee1066b55d37384942d8b57ae00f3cf9a3c1d6a3dfee1d1af42d4b5fa9baeb0cd7e46687d1d6d090ddb931d", - "0x8e4b1db12fd760a17214c9e47f1fce6e43c0dbb4589a827a13ac61aaae93759345697bb438a00edab92e0b7b62414683", - "0x8022a959a513cdc0e9c705e0fc04eafd05ff37c867ae0f31f6d01cddd5df86138a426cab2ff0ac8ff03a62e20f7e8f51", - "0x914e9a38829834c7360443b8ed86137e6f936389488eccf05b4b4db7c9425611705076ecb3f27105d24b85c852be7511", - "0x957fb10783e2bd0db1ba66b18e794df710bc3b2b05776be146fa5863c15b1ebdd39747b1a95d9564e1772cdfc4f37b8a", - "0xb6307028444daed8ed785ac9d0de76bc3fe23ff2cc7e48102553613bbfb5afe0ebe45e4212a27021c8eb870721e62a1f", - "0x8f76143597777d940b15a01b39c5e1b045464d146d9a30a6abe8b5d3907250e6c7f858ff2308f8591e8b0a7b3f3c568a", - "0x96163138ac0ce5fd00ae9a289648fd9300a0ca0f63a88481d703ecd281c06a52a3b5178e849e331f9c85ca4ba398f4cc", - "0xa63ef47c3e18245b0482596a09f488a716df3cbd0f9e5cfabed0d742843e65db8961c556f45f49762f3a6ac8b627b3ef", - "0x8cb595466552e7c4d42909f232d4063e0a663a8ef6f6c9b7ce3a0542b2459cde04e0e54c7623d404acb5b82775ac04f6", - "0xb47fe69960eb45f399368807cff16d941a5a4ebad1f5ec46e3dc8a2e4d598a7e6114d8f0ca791e9720fd786070524e2b", - "0x89eb5ff83eea9df490e5beca1a1fbbbbcf7184a37e2c8c91ede7a1e654c81e8cd41eceece4042ea7918a4f4646b67fd6", - "0xa84f5d155ed08b9054eecb15f689ba81e44589e6e7207a99790c598962837ca99ec12344105b16641ca91165672f7153", - "0xa6cc8f25c2d5b2d2f220ec359e6a37a52b95fa6af6e173c65e7cd55299eff4aa9e6d9e6f2769e6459313f1f2aecb0fab", - "0xafcde944411f017a9f7979755294981e941cc41f03df5e10522ef7c7505e5f1babdd67b3bf5258e8623150062eb41d9b", - "0x8fab39f39c0f40182fcd996ade2012643fe7731808afbc53f9b26900b4d4d1f0f5312d9d40b3df8baa4739970a49c732", - "0xae193af9726da0ebe7df1f9ee1c4846a5b2a7621403baf8e66c66b60f523e719c30c6b4f897bb14b27d3ff3da8392eeb", - "0x8ac5adb82d852eba255764029f42e6da92dcdd0e224d387d1ef94174038db9709ac558d90d7e7c57ad4ce7f89bbfc38c", - "0xa2066b3458fdf678ee487a55dd5bfb74fde03b54620cb0e25412a89ee28ad0d685e309a51e3e4694be2fa6f1593a344c", - "0x88d031745dd0ae07d61a15b594be5d4b2e2a29e715d081649ad63605e3404b0c3a5353f0fd9fad9c05c18e93ce674fa1", - "0x8283cfb0ef743a043f2b77ecaeba3005e2ca50435585b5dd24777ee6bce12332f85e21b446b536da38508807f0f07563", - "0xb376de22d5f6b0af0b59f7d9764561f4244cf8ffe22890ecd3dcf2ff1832130c9b821e068c9d8773136f4796721e5963", - "0xae3afc50c764f406353965363840bf28ee85e7064eb9d5f0bb3c31c64ab10f48c853e942ee2c9b51bae59651eaa08c2f", - "0x948b204d103917461a01a6c57a88f2d66b476eae5b00be20ec8c747650e864bc8a83aee0aff59cb7584b7a3387e0ee48", - "0x81ab098a082b07f896c5ffd1e4446cb7fb44804cbbf38d125208b233fc82f8ec9a6a8d8dd1c9a1162dc28ffeec0dde50", - "0xa149c6f1312821ced2969268789a3151bdda213451760b397139a028da609c4134ac083169feb0ee423a0acafd10eceb", - "0xb0ac9e27a5dadaf523010f730b28f0ebac01f460d3bbbe277dc9d44218abb5686f4fac89ae462682fef9edbba663520a", - "0x8d0e0073cca273daaaa61b6fc54bfe5a009bc3e20ae820f6c93ba77b19eca517d457e948a2de5e77678e4241807157cb", - "0xad61d3a2edf7c7533a04964b97499503fd8374ca64286dba80465e68fe932e96749b476f458c6fc57cb1a7ca85764d11", - "0x90eb5e121ae46bc01a30881eaa556f46bd8457a4e80787cf634aab355082de34ac57d7f497446468225f7721e68e2a47", - "0x8cdac557de7c42d1f3780e33dec1b81889f6352279be81c65566cdd4952d4c15d79e656cbd46035ab090b385e90245ef", - "0x82b67e61b88b84f4f4d4f65df37b3e3dcf8ec91ea1b5c008fdccd52da643adbe6468a1cfdb999e87d195afe2883a3b46", - "0x8503b467e8f5d6048a4a9b78496c58493a462852cab54a70594ae3fd064cfd0deb4b8f336a262155d9fedcaa67d2f6fd", - "0x8db56c5ac763a57b6ce6832930c57117058e3e5a81532b7d19346346205e2ec614eb1a2ee836ef621de50a7bc9b7f040", - "0xad344699198f3c6e8c0a3470f92aaffc805b76266734414c298e10b5b3797ca53578de7ccb2f458f5e0448203f55282b", - "0x80602032c43c9e2a09154cc88b83238343b7a139f566d64cb482d87436b288a98f1ea244fd3bff8da3c398686a900c14", - "0xa6385bd50ecd548cfb37174cdbb89e10025b5cadaf3cff164c95d7aef5a33e3d6a9bf0c681b9e11db9ef54ebeee2a0c1", - "0xabf2d95f4aa34b0581eb9257a0cc8462b2213941a5deb8ba014283293e8b36613951b61261cc67bbd09526a54cbbff76", - "0xa3d5de52f48df72c289ff713e445991f142390798cd42bd9d9dbefaee4af4f5faf09042d126b975cf6b98711c3072553", - "0x8e627302ff3d686cff8872a1b7c2a57b35f45bf2fc9aa42b049d8b4d6996a662b8e7cbac6597f0cb79b0cc4e29fbf133", - "0x8510702e101b39a1efbf4e504e6123540c34b5689645e70d0bac1ecc1baf47d86c05cef6c4317a4e99b4edaeb53f2d00", - "0xaa173f0ecbcc6088f878f8726d317748c81ebf501bba461f163b55d66099b191ec7c55f7702f351a9c8eb42cfa3280e2", - "0xb560a697eafab695bcef1416648a0a664a71e311ecbe5823ae903bd0ed2057b9d7574b9a86d3fe22aa3e6ddce38ea513", - "0x8df6304a3d9cf40100f3f687575419c998cd77e5cc27d579cf4f8e98642de3609af384a0337d145dd7c5635172d26a71", - "0x8105c7f3e4d30a29151849673853b457c1885c186c132d0a98e63096c3774bc9deb956cf957367e633d0913680bda307", - "0x95373fc22c0917c3c2044ac688c4f29a63ed858a45c0d6d2d0fe97afd6f532dcb648670594290c1c89010ecc69259bef", - "0x8c2fae9bcadab341f49b55230310df93cac46be42d4caa0d42e45104148a91e527af1b4209c0d972448162aed28fab64", - "0xb05a77baab70683f76209626eaefdda2d36a0b66c780a20142d23c55bd479ddd4ad95b24579384b6cf62c8eb4c92d021", - "0x8e6bc6a7ea2755b4aaa19c1c1dee93811fcde514f03485fdc3252f0ab7f032c315614f6336e57cea25dcfb8fb6084eeb", - "0xb656a27d06aade55eadae2ad2a1059198918ea6cc3fd22c0ed881294d34d5ac7b5e4700cc24350e27d76646263b223aa", - "0xa296469f24f6f56da92d713afcd4dd606e7da1f79dc4e434593c53695847eefc81c7c446486c4b3b8c8d00c90c166f14", - "0x87a326f57713ac2c9dffeb3af44b9f3c613a8f952676fc46343299122b47ee0f8d792abaa4b5db6451ced5dd153aabd0", - "0xb689e554ba9293b9c1f6344a3c8fcb6951d9f9eac4a2e2df13de021aade7c186be27500e81388e5b8bcab4c80f220a31", - "0x87ae0aa0aa48eac53d1ca5a7b93917de12db9e40ceabf8fdb40884ae771cfdf095411deef7c9f821af0b7070454a2608", - "0xa71ffa7eae8ace94e6c3581d4cb2ad25d48cbd27edc9ec45baa2c8eb932a4773c3272b2ffaf077b40f76942a1f3af7f2", - "0x94c218c91a9b73da6b7a495b3728f3028df8ad9133312fc0c03e8c5253b7ccb83ed14688fd4602e2fd41f29a0bc698bd", - "0xae1e77b90ca33728af07a4c03fb2ef71cd92e2618e7bf8ed4d785ce90097fc4866c29999eb84a6cf1819d75285a03af2", - "0xb7a5945b277dab9993cf761e838b0ac6eaa903d7111fca79f9fde3d4285af7a89bf6634a71909d095d7619d913972c9c", - "0x8c43b37be02f39b22029b20aca31bff661abce4471dca88aa3bddefd9c92304a088b2dfc8c4795acc301ca3160656af2", - "0xb32e5d0fba024554bd5fe8a793ebe8003335ddd7f585876df2048dcf759a01285fecb53daae4950ba57f3a282a4d8495", - "0x85ea7fd5e10c7b659df5289b2978b2c89e244f269e061b9a15fcab7983fc1962b63546e82d5731c97ec74b6804be63ef", - "0x96b89f39181141a7e32986ac02d7586088c5a9662cec39843f397f3178714d02f929af70630c12cbaba0268f8ba2d4fa", - "0x929ab1a2a009b1eb37a2817c89696a06426529ebe3f306c586ab717bd34c35a53eca2d7ddcdef36117872db660024af9", - "0xa696dccf439e9ca41511e16bf3042d7ec0e2f86c099e4fc8879d778a5ea79e33aa7ce96b23dc4332b7ba26859d8e674d", - "0xa8fe69a678f9a194b8670a41e941f0460f6e2dbc60470ab4d6ae2679cc9c6ce2c3a39df2303bee486dbfde6844e6b31a", - "0x95f58f5c82de2f2a927ca99bf63c9fc02e9030c7e46d0bf6b67fe83a448d0ae1c99541b59caf0e1ccab8326231af09a5", - "0xa57badb2c56ca2c45953bd569caf22968f76ed46b9bac389163d6fe22a715c83d5e94ae8759b0e6e8c2f27bff7748f3f", - "0x868726fd49963b24acb5333364dffea147e98f33aa19c7919dc9aca0fd26661cfaded74ede7418a5fadbe7f5ae67b67b", - "0xa8d8550dcc64d9f1dd7bcdab236c4122f2b65ea404bb483256d712c7518f08bb028ff8801f1da6aed6cbfc5c7062e33b", - "0x97e25a87dae23155809476232178538d4bc05d4ff0882916eb29ae515f2a62bfce73083466cc0010ca956aca200aeacc", - "0xb4ea26be3f4bd04aa82d7c4b0913b97bcdf5e88b76c57eb1a336cbd0a3eb29de751e1bc47c0e8258adec3f17426d0c71", - "0x99ee555a4d9b3cf2eb420b2af8e3bc99046880536116d0ce7193464ac40685ef14e0e3c442f604e32f8338cb0ef92558", - "0x8c64efa1da63cd08f319103c5c7a761221080e74227bbc58b8fb35d08aa42078810d7af3e60446cbaff160c319535648", - "0x8d9fd88040076c28420e3395cbdfea402e4077a3808a97b7939d49ecbcf1418fe50a0460e1c1b22ac3f6e7771d65169a", - "0xae3c19882d7a9875d439265a0c7003c8d410367627d21575a864b9cb4918de7dbdb58a364af40c5e045f3df40f95d337", - "0xb4f7bfacab7b2cafe393f1322d6dcc6f21ffe69cd31edc8db18c06f1a2b512c27bd0618091fd207ba8df1808e9d45914", - "0x94f134acd0007c623fb7934bcb65ef853313eb283a889a3ffa79a37a5c8f3665f3d5b4876bc66223610c21dc9b919d37", - "0xaa15f74051171daacdc1f1093d3f8e2d13da2833624b80a934afec86fc02208b8f55d24b7d66076444e7633f46375c6a", - "0xa32d6bb47ef9c836d9d2371807bafbbbbb1ae719530c19d6013f1d1f813c49a60e4fa51d83693586cba3a840b23c0404", - "0xb61b3599145ea8680011aa2366dc511a358b7d67672d5b0c5be6db03b0efb8ca5a8294cf220ea7409621f1664e00e631", - "0x859cafc3ee90b7ececa1ed8ef2b2fc17567126ff10ca712d5ffdd16aa411a5a7d8d32c9cab1fbf63e87dce1c6e2f5f53", - "0xa2fef1b0b2874387010e9ae425f3a9676d01a095d017493648bcdf3b31304b087ccddb5cf76abc4e1548b88919663b6b", - "0x939e18c73befc1ba2932a65ede34c70e4b91e74cc2129d57ace43ed2b3af2a9cc22a40fbf50d79a63681b6d98852866d", - "0xb3b4259d37b1b14aee5b676c9a0dd2d7f679ab95c120cb5f09f9fbf10b0a920cb613655ddb7b9e2ba5af4a221f31303c", - "0x997255fe51aaca6e5a9cb3359bcbf25b2bb9e30649bbd53a8a7c556df07e441c4e27328b38934f09c09d9500b5fabf66", - "0xabb91be2a2d860fd662ed4f1c6edeefd4da8dc10e79251cf87f06029906e7f0be9b486462718f0525d5e049472692cb7", - "0xb2398e593bf340a15f7801e1d1fbda69d93f2a32a889ec7c6ae5e8a37567ac3e5227213c1392ee86cfb3b56ec2787839", - "0x8ddf10ccdd72922bed36829a36073a460c2118fc7a56ff9c1ac72581c799b15c762cb56cb78e3d118bb9f6a7e56cb25e", - "0x93e6bc0a4708d16387cacd44cf59363b994dc67d7ada7b6d6dbd831c606d975247541b42b2a309f814c1bfe205681fc6", - "0xb93fc35c05998cffda2978e12e75812122831523041f10d52f810d34ff71944979054b04de0117e81ddf5b0b4b3e13c0", - "0x92221631c44d60d68c6bc7b287509f37ee44cbe5fdb6935cee36b58b17c7325098f98f7910d2c3ca5dc885ad1d6dabc7", - "0xa230124424a57fad3b1671f404a94d7c05f4c67b7a8fbacfccea28887b78d7c1ed40b92a58348e4d61328891cd2f6cee", - "0xa6a230edb8518a0f49d7231bc3e0bceb5c2ac427f045819f8584ba6f3ae3d63ed107a9a62aad543d7e1fcf1f20605706", - "0x845be1fe94223c7f1f97d74c49d682472585d8f772762baad8a9d341d9c3015534cc83d102113c51a9dea2ab10d8d27b", - "0xb44262515e34f2db597c8128c7614d33858740310a49cdbdf9c8677c5343884b42c1292759f55b8b4abc4c86e4728033", - "0x805592e4a3cd07c1844bc23783408310accfdb769cca882ad4d07d608e590a288b7370c2cb327f5336e72b7083a0e30f", - "0x95153e8b1140df34ee864f4ca601cb873cdd3efa634af0c4093fbaede36f51b55571ab271e6a133020cd34db8411241f", - "0x82878c1285cfa5ea1d32175c9401f3cc99f6bb224d622d3fd98cc7b0a27372f13f7ab463ce3a33ec96f9be38dbe2dfe3", - "0xb7588748f55783077c27fc47d33e20c5c0f5a53fc0ac10194c003aa09b9f055d08ec971effa4b7f760553997a56967b3", - "0xb36b4de6d1883b6951f59cfae381581f9c6352fcfcf1524fccdab1571a20f80441d9152dc6b48bcbbf00371337ca0bd5", - "0x89c5523f2574e1c340a955cbed9c2f7b5fbceb260cb1133160dabb7d41c2f613ec3f6e74bbfab3c4a0a6f0626dbe068f", - "0xa52f58cc39f968a9813b1a8ddc4e83f4219e4dd82c7aa1dd083bea7edf967151d635aa9597457f879771759b876774e4", - "0x8300a67c2e2e123f89704abfde095463045dbd97e20d4c1157bab35e9e1d3d18f1f4aaba9cbe6aa2d544e92578eaa1b6", - "0xac6a7f2918768eb6a43df9d3a8a04f8f72ee52f2e91c064c1c7d75cad1a3e83e5aba9fe55bb94f818099ac91ccf2e961", - "0x8d64a2b0991cf164e29835c8ddef6069993a71ec2a7de8157bbfa2e00f6367be646ed74cbaf524f0e9fe13fb09fa15fd", - "0x8b2ffe5a545f9f680b49d0a9797a4a11700a2e2e348c34a7a985fc278f0f12def6e06710f40f9d48e4b7fbb71e072229", - "0x8ab8f71cd337fa19178924e961958653abf7a598e3f022138b55c228440a2bac4176cea3aea393549c03cd38a13eb3fc", - "0x8419d28318c19ea4a179b7abb43669fe96347426ef3ac06b158d79c0acf777a09e8e770c2fb10e14b3a0421705990b23", - "0x8bacdac310e1e49660359d0a7a17fe3d334eb820e61ae25e84cb52f863a2f74cbe89c2e9fc3283745d93a99b79132354", - "0xb57ace3fa2b9f6b2db60c0d861ace7d7e657c5d35d992588aeed588c6ce3a80b6f0d49f8a26607f0b17167ab21b675e4", - "0x83e265cde477f2ecc164f49ddc7fb255bb05ff6adc347408353b7336dc3a14fdedc86d5a7fb23f36b8423248a7a67ed1", - "0xa60ada971f9f2d79d436de5d3d045f5ab05308cae3098acaf5521115134b2a40d664828bb89895840db7f7fb499edbc5", - "0xa63eea12efd89b62d3952bf0542a73890b104dd1d7ff360d4755ebfa148fd62de668edac9eeb20507967ea37fb220202", - "0xa0275767a270289adc991cc4571eff205b58ad6d3e93778ddbf95b75146d82517e8921bd0d0564e5b75fa0ccdab8e624", - "0xb9b03fd3bf07201ba3a039176a965d736b4ef7912dd9e9bf69fe1b57c330a6aa170e5521fe8be62505f3af81b41d7806", - "0xa95f640e26fb1106ced1729d6053e41a16e4896acac54992279ff873e5a969aad1dcfa10311e28b8f409ac1dab7f03bb", - "0xb144778921742418053cb3c70516c63162c187f00db2062193bb2c14031075dbe055d020cde761b26e8c58d0ea6df2c1", - "0x8432fbb799e0435ef428d4fefc309a05dd589bce74d7a87faf659823e8c9ed51d3e42603d878e80f439a38be4321c2fa", - "0xb08ddef14e42d4fd5d8bf39feb7485848f0060d43b51ed5bdda39c05fe154fb111d29719ee61a23c392141358c0cfcff", - "0x8ae3c5329a5e025b86b5370e06f5e61177df4bda075856fade20a17bfef79c92f54ed495f310130021ba94fb7c33632b", - "0x92b6d3c9444100b4d7391febfc1dddaa224651677c3695c47a289a40d7a96d200b83b64e6d9df51f534564f272a2c6c6", - "0xb432bc2a3f93d28b5e506d68527f1efeb2e2570f6be0794576e2a6ef9138926fdad8dd2eabfa979b79ab7266370e86bc", - "0x8bc315eacedbcfc462ece66a29662ca3dcd451f83de5c7626ef8712c196208fb3d8a0faf80b2e80384f0dd9772f61a23", - "0xa72375b797283f0f4266dec188678e2b2c060dfed5880fc6bb0c996b06e91a5343ea2b695adaab0a6fd183b040b46b56", - "0xa43445036fbaa414621918d6a897d3692fdae7b2961d87e2a03741360e45ebb19fcb1703d23f1e15bb1e2babcafc56ac", - "0xb9636b2ffe305e63a1a84bd44fb402442b1799bd5272638287aa87ca548649b23ce8ce7f67be077caed6aa2dbc454b78", - "0x99a30bf0921d854c282b83d438a79f615424f28c2f99d26a05201c93d10378ab2cd94a792b571ddae5d4e0c0013f4006", - "0x8648e3c2f93d70b392443be116b48a863e4b75991bab5db656a4ef3c1e7f645e8d536771dfe4e8d1ceda3be8d32978b0", - "0xab50dc9e6924c1d2e9d2e335b2d679fc7d1a7632e84964d3bac0c9fe57e85aa5906ec2e7b0399d98ddd022e9b19b5904", - "0xab729328d98d295f8f3272afaf5d8345ff54d58ff9884da14f17ecbdb7371857fdf2f3ef58080054e9874cc919b46224", - "0x83fa5da7592bd451cad3ad7702b4006332b3aae23beab4c4cb887fa6348317d234bf62a359e665b28818e5410c278a09", - "0x8bdbff566ae9d368f114858ef1f009439b3e9f4649f73efa946e678d6c781d52c69af195df0a68170f5f191b2eac286b", - "0x91245e59b4425fd4edb2a61d0d47c1ccc83d3ced8180de34887b9655b5dcda033d48cde0bdc3b7de846d246c053a02e8", - "0xa2cb00721e68f1cad8933947456f07144dc69653f96ceed845bd577d599521ba99cdc02421118971d56d7603ed118cbf", - "0xaf8cd66d303e808b22ec57860dd909ca64c27ec2c60e26ffecfdc1179d8762ffd2739d87b43959496e9fee4108df71df", - "0x9954136812dffcd5d3f167a500e7ab339c15cfc9b3398d83f64b0daa3dd5b9a851204f424a3493b4e326d3de81e50a62", - "0x93252254d12511955f1aa464883ad0da793f84d900fea83e1df8bca0f2f4cf5b5f9acbaec06a24160d33f908ab5fea38", - "0x997cb55c26996586ba436a95566bd535e9c22452ca5d2a0ded2bd175376557fa895f9f4def4519241ff386a063f2e526", - "0xa12c78ad451e0ac911260ade2927a768b50cb4125343025d43474e7f465cdc446e9f52a84609c5e7e87ae6c9b3f56cda", - "0xa789d4ca55cbba327086563831b34487d63d0980ba8cf55197c016702ed6da9b102b1f0709ce3da3c53ff925793a3d73", - "0xa5d76acbb76741ce85be0e655b99baa04f7f587347947c0a30d27f8a49ae78cce06e1cde770a8b618d3db402be1c0c4b", - "0x873c0366668c8faddb0eb7c86f485718d65f8c4734020f1a18efd5fa123d3ea8a990977fe13592cd01d17e60809cb5ff", - "0xb659b71fe70f37573ff7c5970cc095a1dc0da3973979778f80a71a347ef25ad5746b2b9608bad4ab9a4a53a4d7df42d7", - "0xa34cbe05888e5e5f024a2db14cb6dcdc401a9cbd13d73d3c37b348f68688f87c24ca790030b8f84fef9e74b4eab5e412", - "0x94ce8010f85875c045b0f014db93ef5ab9f1f6842e9a5743dce9e4cb872c94affd9e77c1f1d1ab8b8660b52345d9acb9", - "0xadefa9b27a62edc0c5b019ddd3ebf45e4de846165256cf6329331def2e088c5232456d3de470fdce3fa758bfdd387512", - "0xa6b83821ba7c1f83cc9e4529cf4903adb93b26108e3d1f20a753070db072ad5a3689643144bdd9c5ea06bb9a7a515cd0", - "0xa3a9ddedc2a1b183eb1d52de26718151744db6050f86f3580790c51d09226bf05f15111691926151ecdbef683baa992c", - "0xa64bac89e7686932cdc5670d07f0b50830e69bfb8c93791c87c7ffa4913f8da881a9d8a8ce8c1a9ce5b6079358c54136", - "0xa77b5a63452cb1320b61ab6c7c2ef9cfbcade5fd4727583751fb2bf3ea330b5ca67757ec1f517bf4d503ec924fe32fbd", - "0x8746fd8d8eb99639d8cd0ca34c0d9c3230ed5a312aab1d3d925953a17973ee5aeb66e68667e93caf9cb817c868ea8f3d", - "0x88a2462a26558fc1fbd6e31aa8abdc706190a17c27fdc4217ffd2297d1b1f3321016e5c4b2384c5454d5717dc732ed03", - "0xb78893a97e93d730c8201af2e0d3b31cb923d38dc594ffa98a714e627c473d42ea82e0c4d2eeb06862ee22a9b2c54588", - "0x920cc8b5f1297cf215a43f6fc843e379146b4229411c44c0231f6749793d40f07b9af7699fd5d21fd69400b97febe027", - "0xa0f0eafce1e098a6b58c7ad8945e297cd93aaf10bc55e32e2e32503f02e59fc1d5776936577d77c0b1162cb93b88518b", - "0x98480ba0064e97a2e7a6c4769b4d8c2a322cfc9a3b2ca2e67e9317e2ce04c6e1108169a20bd97692e1cb1f1423b14908", - "0x83dbbb2fda7e287288011764a00b8357753a6a44794cc8245a2275237f11affdc38977214e463ad67aec032f3dfa37e9", - "0x86442fff37598ce2b12015ff19b01bb8a780b40ad353d143a0f30a06f6d23afd5c2b0a1253716c855dbf445cc5dd6865", - "0xb8a4c60c5171189414887847b9ed9501bff4e4c107240f063e2d254820d2906b69ef70406c585918c4d24f1dd052142b", - "0x919f33a98e84015b2034b57b5ffe9340220926b2c6e45f86fd79ec879dbe06a148ae68b77b73bf7d01bd638a81165617", - "0x95c13e78d89474a47fbc0664f6f806744b75dede95a479bbf844db4a7f4c3ae410ec721cb6ffcd9fa9c323da5740d5ae", - "0xab7151acc41fffd8ec6e90387700bcd7e1cde291ea669567295bea1b9dd3f1df2e0f31f3588cd1a1c08af8120aca4921", - "0x80e74c5c47414bd6eeef24b6793fb1fa2d8fb397467045fcff887c52476741d5bc4ff8b6d3387cb53ad285485630537f", - "0xa296ad23995268276aa351a7764d36df3a5a3cffd7dbeddbcea6b1f77adc112629fdeffa0918b3242b3ccd5e7587e946", - "0x813d2506a28a2b01cb60f49d6bd5e63c9b056aa56946faf2f33bd4f28a8d947569cfead3ae53166fc65285740b210f86", - "0x924b265385e1646287d8c09f6c855b094daaee74b9e64a0dddcf9ad88c6979f8280ba30c8597b911ef58ddb6c67e9fe3", - "0x8d531513c70c2d3566039f7ca47cd2352fd2d55b25675a65250bdb8b06c3843db7b2d29c626eed6391c238fc651cf350", - "0x82b338181b62fdc81ceb558a6843df767b6a6e3ceedc5485664b4ea2f555904b1a45fbb35f6cf5d96f27da10df82a325", - "0x92e62faaedea83a37f314e1d3cb4faaa200178371d917938e59ac35090be1db4b4f4e0edb78b9c991de202efe4f313d8", - "0x99d645e1b642c2dc065bac9aaa0621bc648c9a8351efb6891559c3a41ba737bd155fb32d7731950514e3ecf4d75980e4", - "0xb34a13968b9e414172fb5d5ece9a39cf2eb656128c3f2f6cc7a9f0c69c6bae34f555ecc8f8837dc34b5e470e29055c78", - "0xa2a0bb7f3a0b23a2cbc6585d59f87cd7e56b2bbcb0ae48f828685edd9f7af0f5edb4c8e9718a0aaf6ef04553ba71f3b7", - "0x8e1a94bec053ed378e524b6685152d2b52d428266f2b6eadd4bcb7c4e162ed21ab3e1364879673442ee2162635b7a4d8", - "0x9944adaff14a85eab81c73f38f386701713b52513c4d4b838d58d4ffa1d17260a6d056b02334850ea9a31677c4b078bd", - "0xa450067c7eceb0854b3eca3db6cf38669d72cb7143c3a68787833cbca44f02c0be9bfbe082896f8a57debb13deb2afb1", - "0x8be4ad3ac9ef02f7df09254d569939757101ee2eda8586fefcd8c847adc1efe5bdcb963a0cafa17651befaafb376a531", - "0x90f6de91ea50255f148ac435e08cf2ac00c772a466e38155bd7e8acf9197af55662c7b5227f88589b71abe9dcf7ba343", - "0x86e5a24f0748b106dee2d4d54e14a3b0af45a96cbee69cac811a4196403ebbee17fd24946d7e7e1b962ac7f66dbaf610", - "0xafdd96fbcda7aa73bf9eeb2292e036c25753d249caee3b9c013009cc22e10d3ec29e2aa6ddbb21c4e949b0c0bccaa7f4", - "0xb5a4e7436d5473647c002120a2cb436b9b28e27ad4ebdd7c5f122b91597c507d256d0cbd889d65b3a908531936e53053", - "0xb632414c3da704d80ac2f3e5e0e9f18a3637cdc2ebeb613c29300745582427138819c4e7b0bec3099c1b8739dac1807b", - "0xa28df1464d3372ce9f37ef1db33cc010f752156afae6f76949d98cd799c0cf225c20228ae86a4da592d65f0cffe3951b", - "0x898b93d0a31f7d3f11f253cb7a102db54b669fd150da302d8354d8e02b1739a47cb9bd88015f3baf12b00b879442464e", - "0x96fb88d89a12049091070cb0048a381902965e67a8493e3991eaabe5d3b7ff7eecd5c94493a93b174df3d9b2c9511755", - "0xb899cb2176f59a5cfba3e3d346813da7a82b03417cad6342f19cc8f12f28985b03bf031e856a4743fd7ebe16324805b0", - "0xa60e2d31bc48e0c0579db15516718a03b73f5138f15037491f4dae336c904e312eda82d50862f4debd1622bb0e56d866", - "0x979fc8b987b5cef7d4f4b58b53a2c278bd25a5c0ea6f41c715142ea5ff224c707de38451b0ad3aa5e749aa219256650a", - "0xb2a75bff18e1a6b9cf2a4079572e41205741979f57e7631654a3c0fcec57c876c6df44733c9da3d863db8dff392b44a3", - "0xb7a0f0e811222c91e3df98ff7f286b750bc3b20d2083966d713a84a2281744199e664879401e77470d44e5a90f3e5181", - "0x82b74ba21c9d147fbc338730e8f1f8a6e7fc847c3110944eb17a48bea5e06eecded84595d485506d15a3e675fd0e5e62", - "0xa7f44eef817d5556f0d1abcf420301217d23c69dd2988f44d91ea1f1a16c322263cbacd0f190b9ba22b0f141b9267b4f", - "0xaadb68164ede84fc1cb3334b3194d84ba868d5a88e4c9a27519eef4923bc4abf81aab8114449496c073c2a6a0eb24114", - "0xb5378605fabe9a8c12a5dc55ef2b1de7f51aedb61960735c08767a565793cea1922a603a6983dc25f7cea738d0f7c40d", - "0xa97a4a5cd8d51302e5e670aee78fe6b5723f6cc892902bbb4f131e82ca1dfd5de820731e7e3367fb0c4c1922a02196e3", - "0x8bdfeb15c29244d4a28896f2b2cb211243cd6a1984a3f5e3b0ebe5341c419beeab3304b390a009ffb47588018034b0ea", - "0xa9af3022727f2aa2fca3b096968e97edad3f08edcbd0dbca107b892ae8f746a9c0485e0d6eb5f267999b23a845923ed0", - "0x8e7594034feef412f055590fbb15b6322dc4c6ab7a4baef4685bd13d71a83f7d682b5781bdfa0d1c659489ce9c2b8000", - "0x84977ca6c865ebee021c58106c1a4ad0c745949ecc5332948002fd09bd9b890524878d0c29da96fd11207621136421fe", - "0x8687551a79158e56b2375a271136756313122132a6670fa51f99a1b5c229ed8eea1655a734abae13228b3ebfd2a825dd", - "0xa0227d6708979d99edfc10f7d9d3719fd3fc68b0d815a7185b60307e4c9146ad2f9be2b8b4f242e320d4288ceeb9504c", - "0x89f75583a16735f9dd8b7782a130437805b34280ccea8dac6ecaee4b83fe96947e7b53598b06fecfffdf57ffc12cc445", - "0xa0056c3353227f6dd9cfc8e3399aa5a8f1d71edf25d3d64c982910f50786b1e395c508d3e3727ac360e3e040c64b5298", - "0xb070e61a6d813626144b312ded1788a6d0c7cec650a762b2f8df6e4743941dd82a2511cd956a3f141fc81e15f4e092da", - "0xb4e6db232e028a1f989bb5fc13416711f42d389f63564d60851f009dcffac01acfd54efa307aa6d4c0f932892d4e62b0", - "0x89b5991a67db90024ddd844e5e1a03ef9b943ad54194ae0a97df775dde1addf31561874f4e40fbc37a896630f3bbda58", - "0xad0e8442cb8c77d891df49cdb9efcf2b0d15ac93ec9be1ad5c3b3cca1f4647b675e79c075335c1f681d56f14dc250d76", - "0xb5d55a6ae65bb34dd8306806cb49b5ccb1c83a282ee47085cf26c4e648e19a52d9c422f65c1cd7e03ca63e926c5e92ea", - "0xb749501347e5ec07e13a79f0cb112f1b6534393458b3678a77f02ca89dca973fa7b30e55f0b25d8b92b97f6cb0120056", - "0x94144b4a3ffc5eec6ba35ce9c245c148b39372d19a928e236a60e27d7bc227d18a8cac9983851071935d8ffb64b3a34f", - "0x92bb4f9f85bc8c028a3391306603151c6896673135f8a7aefedd27acb322c04ef5dac982fc47b455d6740023e0dd3ea3", - "0xb9633a4a101461a782fc2aa092e9dbe4e2ad00987578f18cd7cf0021a909951d60fe79654eb7897806795f93c8ff4d1c", - "0x809f0196753024821b48a016eca5dbb449a7c55750f25981bb7a4b4c0e0846c09b8f6128137905055fc43a3f0deb4a74", - "0xa27dc9cdd1e78737a443570194a03d89285576d3d7f3a3cf15cc55b3013e42635d4723e2e8fe1d0b274428604b630db9", - "0x861f60f0462e04cd84924c36a28163def63e777318d00884ab8cb64c8df1df0bce5900342163edb60449296484a6c5bf", - "0xb7bc23fb4e14af4c4704a944253e760adefeca8caee0882b6bbd572c84434042236f39ae07a8f21a560f486b15d82819", - "0xb9a6eb492d6dd448654214bd01d6dc5ff12067a11537ab82023fc16167507ee25eed2c91693912f4155d1c07ed9650b3", - "0x97678af29c68f9a5e213bf0fb85c265303714482cfc4c2c00b4a1e8a76ed08834ee6af52357b143a1ca590fb0265ea5a", - "0x8a15b499e9eca5b6cac3070b5409e8296778222018ad8b53a5d1f6b70ad9bb10c68a015d105c941ed657bf3499299e33", - "0xb487fefede2e8091f2c7bfe85770db2edff1db83d4effe7f7d87bff5ab1ace35e9b823a71adfec6737fede8d67b3c467", - "0x8b51b916402aa2c437fce3bcad6dad3be8301a1a7eab9d163085b322ffb6c62abf28637636fe6114573950117fc92898", - "0xb06a2106d031a45a494adec0881cb2f82275dff9dcdd2bc16807e76f3bec28a6734edd3d54f0be8199799a78cd6228ad", - "0xaf0a185391bbe2315eb97feac98ad6dd2e5d931d012c621abd6e404a31cc188b286fef14871762190acf086482b2b5e2", - "0x8e78ee8206506dd06eb7729e32fceda3bebd8924a64e4d8621c72e36758fda3d0001af42443851d6c0aea58562870b43", - "0xa1ba52a569f0461aaf90b49b92be976c0e73ec4a2c884752ee52ffb62dd137770c985123d405dfb5de70692db454b54a", - "0x8d51b692fa1543c51f6b62b9acb8625ed94b746ef96c944ca02859a4133a5629da2e2ce84e111a7af8d9a5b836401c64", - "0xa7a20d45044cf6492e0531d0b8b26ffbae6232fa05a96ed7f06bdb64c2b0f5ca7ec59d5477038096a02579e633c7a3ff", - "0x84df867b98c53c1fcd4620fef133ee18849c78d3809d6aca0fb6f50ff993a053a455993f216c42ab6090fa5356b8d564", - "0xa7227c439f14c48e2577d5713c97a5205feb69acb0b449152842e278fa71e8046adfab468089c8b2288af1fc51fa945b", - "0x855189b3a105670779997690876dfaa512b4a25a24931a912c2f0f1936971d2882fb4d9f0b3d9daba77eaf660e9d05d5", - "0xb5696bd6706de51c502f40385f87f43040a5abf99df705d6aac74d88c913b8ecf7a99a63d7a37d9bdf3a941b9e432ff5", - "0xab997beb0d6df9c98d5b49864ef0b41a2a2f407e1687dfd6089959757ba30ed02228940b0e841afe6911990c74d536c4", - "0xb36b65f85546ebfdbe98823d5555144f96b4ab39279facd19c0de3b8919f105ba0315a0784dce4344b1bc62d8bb4a5a3", - "0xb8371f0e4450788720ac5e0f6cd3ecc5413d33895083b2c168d961ec2b5c3de411a4cc0712481cbe8df8c2fa1a7af006", - "0x98325d8026b810a8b7a114171ae59a57e8bbc9848e7c3df992efc523621729fd8c9f52114ce01d7730541a1ada6f1df1", - "0x8d0e76dbd37806259486cd9a31bc8b2306c2b95452dc395546a1042d1d17863ef7a74c636b782e214d3aa0e8d717f94a", - "0xa4e15ead76da0214d702c859fb4a8accdcdad75ed08b865842bd203391ec4cba2dcc916455e685f662923b96ee0c023f", - "0x8618190972086ebb0c4c1b4a6c94421a13f378bc961cc8267a301de7390c5e73c3333864b3b7696d81148f9d4843fd02", - "0x85369d6cc7342e1aa15b59141517d8db8baaaeb7ab9670f3ba3905353948d575923d283b7e5a05b13a30e7baf1208a86", - "0x87c51ef42233c24a6da901f28c9a075d9ba3c625687c387ad6757b72ca6b5a8885e6902a3082da7281611728b1e45f26", - "0xaa6348a4f71927a3106ad0ea8b02fc8d8c65531e4ab0bd0a17243e66f35afe252e40ab8eef9f13ae55a72566ffdaff5c", - "0x96a3bc976e9d03765cc3fee275fa05b4a84c94fed6b767e23ca689394501e96f56f7a97cffddc579a6abff632bf153be", - "0x97dbf96c6176379fdb2b888be4e757b2bca54e74124bd068d3fa1dbd82a011bbeb75079da38e0cd22a761fe208ecad9b", - "0xb70cf0a1d14089a4129ec4e295313863a59da8c7e26bf74cc0e704ed7f0ee4d7760090d0ddf7728180f1bf2c5ac64955", - "0x882d664714cc0ffe53cbc9bef21f23f3649824f423c4dbad1f893d22c4687ab29583688699efc4d5101aa08b0c3e267a", - "0x80ecb7cc963e677ccaddbe3320831dd6ee41209acf4ed41b16dc4817121a3d86a1aac9c4db3d8c08a55d28257088af32", - "0xa25ba667d832b145f9ce18c3f9b1bd00737aa36db020e1b99752c8ef7d27c6c448982bd8d352e1b6df266b8d8358a8d5", - "0x83734841c13dee12759d40bdd209b277e743b0d08cc0dd1e0b7afd2d65bfa640400eefcf6be4a52e463e5b3d885eeac6", - "0x848d16505b04804afc773aebabb51b36fd8aacfbb0e09b36c0d5d57df3c0a3b92f33e7d5ad0a7006ec46ebb91df42b8c", - "0x909a8d793f599e33bb9f1dc4792a507a97169c87cd5c087310bc05f30afcd247470b4b56dec59894c0fb1d48d39bb54e", - "0x8e558a8559df84a1ba8b244ece667f858095c50bb33a5381e60fcc6ba586b69693566d8819b4246a27287f16846c1dfa", - "0x84d6b69729f5aaa000cd710c2352087592cfbdf20d5e1166977e195818e593fa1a50d1e04566be23163a2523dc1612f1", - "0x9536d262b7a42125d89f4f32b407d737ba8d9242acfc99d965913ab3e043dcac9f7072a43708553562cac4cba841df30", - "0x9598548923ca119d6a15fd10861596601dd1dedbcccca97bb208cdc1153cf82991ea8cc17686fbaa867921065265970c", - "0xb87f2d4af6d026e4d2836bc3d390a4a18e98a6e386282ce96744603bab74974272e97ac2da281afa21885e2cbb3a8001", - "0x991ece62bf07d1a348dd22191868372904b9f8cf065ae7aa4e44fd24a53faf6d851842e35fb472895963aa1992894918", - "0xa8c53dea4c665b30e51d22ca6bc1bc78aaf172b0a48e64a1d4b93439b053877ec26cb5221c55efd64fa841bbf7d5aff4", - "0x93487ec939ed8e740f15335b58617c3f917f72d07b7a369befd479ae2554d04deb240d4a14394b26192efae4d2f4f35d", - "0xa44793ab4035443f8f2968a40e043b4555960193ffa3358d22112093aadfe2c136587e4139ffd46d91ed4107f61ea5e0", - "0xb13fe033da5f0d227c75927d3dacb06dbaf3e1322f9d5c7c009de75cdcba5e308232838785ab69a70f0bedea755e003f", - "0x970a29b075faccd0700fe60d1f726bdebf82d2cc8252f4a84543ebd3b16f91be42a75c9719a39c4096139f0f31393d58", - "0xa4c3eb1f7160f8216fc176fb244df53008ff32f2892363d85254002e66e2de21ccfe1f3b1047589abee50f29b9d507e3", - "0x8c552885eab04ba40922a8f0c3c38c96089c95ff1405258d3f1efe8d179e39e1295cbf67677894c607ae986e4e6b1fb0", - "0xb3671746fa7f848c4e2ae6946894defadd815230b906b419143523cc0597bc1d6c0a4c1e09d49b66b4a2c11cde3a4de3", - "0x937a249a95813a5e2ef428e355efd202e15a37d73e56cfb7e57ea9f943f2ce5ca8026f2f1fd25bf164ba89d07077d858", - "0x83646bdf6053a04aa9e2f112499769e5bd5d0d10f2e13db3ca89bd45c0b3b7a2d752b7d137fb3909f9c62b78166c9339", - "0xb4eac4b91e763666696811b7ed45e97fd78310377ebea1674b58a2250973f80492ac35110ed1240cd9bb2d17493d708c", - "0x82db43a99bc6573e9d92a3fd6635dbbb249ac66ba53099c3c0c8c8080b121dd8243cd5c6e36ba0a4d2525bae57f5c89c", - "0xa64d6a264a681b49d134c655d5fc7756127f1ee7c93d328820f32bca68869f53115c0d27fef35fe71f7bc4fdaed97348", - "0x8739b7a9e2b4bc1831e7f04517771bc7cde683a5e74e052542517f8375a2f64e53e0d5ac925ef722327e7bb195b4d1d9", - "0x8f337cdd29918a2493515ebb5cf702bbe8ecb23b53c6d18920cc22f519e276ca9b991d3313e2d38ae17ae8bdfa4f8b7e", - "0xb0edeab9850e193a61f138ef2739fc42ceec98f25e7e8403bfd5fa34a7bc956b9d0898250d18a69fa4625a9b3d6129da", - "0xa9920f26fe0a6d51044e623665d998745c9eca5bce12051198b88a77d728c8238f97d4196f26e43b24f8841500b998d0", - "0x86e655d61502b979eeeeb6f9a7e1d0074f936451d0a1b0d2fa4fb3225b439a3770767b649256fe481361f481a8dbc276", - "0x84d3b32fa62096831cc3bf013488a9f3f481dfe293ae209ed19585a03f7db8d961a7a9dd0db82bd7f62d612707575d9c", - "0x81c827826ec9346995ffccf62a241e3b2d32f7357acd1b1f8f7a7dbc97022d3eb51b8a1230e23ce0b401d2e535e8cd78", - "0x94a1e40c151191c5b055b21e86f32e69cbc751dcbdf759a48580951834b96a1eed75914c0d19a38aefd21fb6c8d43d0c", - "0xab890222b44bc21b71f7c75e15b6c6e16bb03371acce4f8d4353ff3b8fcd42a14026589c5ed19555a3e15e4d18bfc3a3", - "0xaccb0be851e93c6c8cc64724cdb86887eea284194b10e7a43c90528ed97e9ec71ca69c6fac13899530593756dd49eab2", - "0xb630220aa9e1829c233331413ee28c5efe94ea8ea08d0c6bfd781955078b43a4f92915257187d8526873e6c919c6a1de", - "0xadd389a4d358c585f1274b73f6c3c45b58ef8df11f9d11221f620e241bf3579fba07427b288c0c682885a700cc1fa28d", - "0xa9fe6ca8bf2961a3386e8b8dcecc29c0567b5c0b3bcf3b0f9169f88e372b80151af883871fc5229815f94f43a6f5b2b0", - "0xad839ae003b92b37ea431fa35998b46a0afc3f9c0dd54c3b3bf7a262467b13ff3c323ada1c1ae02ac7716528bdf39e3e", - "0x9356d3fd0edcbbb65713c0f2a214394f831b26f792124b08c5f26e7f734b8711a87b7c4623408da6a091c9aef1f6af3c", - "0x896b25b083c35ac67f0af3784a6a82435b0e27433d4d74cd6d1eafe11e6827827799490fb1c77c11de25f0d75f14e047", - "0x8bfa019391c9627e8e5f05c213db625f0f1e51ec68816455f876c7e55b8f17a4f13e5aae9e3fb9e1cf920b1402ee2b40", - "0x8ba3a6faa6a860a8f3ce1e884aa8769ceded86380a86520ab177ab83043d380a4f535fe13884346c5e51bee68da6ab41", - "0xa8292d0844084e4e3bb7af92b1989f841a46640288c5b220fecfad063ee94e86e13d3d08038ec2ac82f41c96a3bfe14d", - "0x8229bb030b2fc566e11fd33c7eab7a1bb7b49fed872ea1f815004f7398cb03b85ea14e310ec19e1f23e0bdaf60f8f76c", - "0x8cfbf869ade3ec551562ff7f63c2745cc3a1f4d4dc853a0cd42dd5f6fe54228f86195ea8fe217643b32e9f513f34a545", - "0xac52a3c8d3270ddfe1b5630159da9290a5ccf9ccbdef43b58fc0a191a6c03b8a5974cf6e2bbc7bd98d4a40a3581482d7", - "0xab13decb9e2669e33a7049b8eca3ca327c40dea15ad6e0e7fa63ed506db1d258bc36ac88b35f65cae0984e937eb6575d", - "0xb5e748eb1a7a1e274ff0cc56311c198f2c076fe4b7e73e5f80396fe85358549df906584e6bb2c8195b3e2be7736850a5", - "0xb5cb911325d8f963c41f691a60c37831c7d3bbd92736efa33d1f77a22b3fde7f283127256c2f47e197571e6fe0b46149", - "0x8a01dc6ed1b55f26427a014faa347130738b191a06b800e32042a46c13f60b49534520214359d68eb2e170c31e2b8672", - "0xa72fa874866e19b2efb8e069328362bf7921ec375e3bcd6b1619384c3f7ee980f6cf686f3544e9374ff54b4d17a1629c", - "0x8db21092f7c5f110fba63650b119e82f4b42a997095d65f08f8237b02dd66fdf959f788df2c35124db1dbd330a235671", - "0x8c65d50433d9954fe28a09fa7ba91a70a590fe7ba6b3060f5e4be0f6cef860b9897fa935fb4ebc42133524eb071dd169", - "0xb4614058e8fa21138fc5e4592623e78b8982ed72aa35ee4391b164f00c68d277fa9f9eba2eeefc890b4e86eba5124591", - "0xab2ad3a1bce2fbd55ca6b7c23786171fe1440a97d99d6df4d80d07dd56ac2d7203c294b32fc9e10a6c259381a73f24a1", - "0x812ae3315fdc18774a8da3713a4679e8ed10b9405edc548c00cacbe25a587d32040566676f135e4723c5dc25df5a22e9", - "0xa464b75f95d01e5655b54730334f443c8ff27c3cb79ec7af4b2f9da3c2039c609908cd128572e1fd0552eb597e8cef8d", - "0xa0db3172e93ca5138fe419e1c49a1925140999f6eff7c593e5681951ee0ec1c7e454c851782cbd2b8c9bc90d466e90e0", - "0x806db23ba7d00b87d544eed926b3443f5f9c60da6b41b1c489fba8f73593b6e3b46ebfcab671ee009396cd77d5e68aa1", - "0x8bfdf2c0044cc80260994e1c0374588b6653947b178e8b312be5c2a05e05767e98ea15077278506aee7df4fee1aaf89e", - "0x827f6558c16841b5592ff089c9c31e31eb03097623524394813a2e4093ad2d3f8f845504e2af92195aaa8a1679d8d692", - "0x925c4f8eab2531135cd71a4ec88e7035b5eea34ba9d799c5898856080256b4a15ed1a746e002552e2a86c9c157e22e83", - "0xa9f9a368f0e0b24d00a35b325964c85b69533013f9c2cfad9708be5fb87ff455210f8cb8d2ce3ba58ca3f27495552899", - "0x8ac0d3bebc1cae534024187e7c71f8927ba8fcc6a1926cb61c2b6c8f26bb7831019e635a376146c29872a506784a4aaa", - "0x97c577be2cbbfdb37ad754fae9df2ada5fc5889869efc7e18a13f8e502fbf3f4067a509efbd46fd990ab47ce9a70f5a8", - "0x935e7d82bca19f16614aa43b4a3474e4d20d064e4bfdf1cea2909e5c9ab72cfe3e54dc50030e41ee84f3588cebc524e9", - "0x941aafc08f7c0d94cebfbb1f0aad5202c02e6e37f2c12614f57e727efa275f3926348f567107ee6d8914dd71e6060271", - "0xaf0fbc1ba05b4b5b63399686df3619968be5d40073de0313cbf5f913d3d4b518d4c249cdd2176468ccaa36040a484f58", - "0xa0c414f23f46ca6d69ce74c6f8a00c036cb0edd098af0c1a7d39c802b52cfb2d5dbdf93fb0295453d4646e2af7954d45", - "0x909cf39e11b3875bb63b39687ae1b5d1f5a15445e39bf164a0b14691b4ddb39a8e4363f584ef42213616abc4785b5d66", - "0xa92bac085d1194fbd1c88299f07a061d0bdd3f980b663e81e6254dbb288bf11478c0ee880e28e01560f12c5ccb3c0103", - "0x841705cd5cd76b943e2b7c5e845b9dd3c8defe8ef67e93078d6d5e67ade33ad4b0fd413bc196f93b0a4073c855cd97d4", - "0x8e7eb8364f384a9161e81d3f1d52ceca9b65536ae49cc35b48c3e2236322ba4ae9973e0840802d9fa4f4d82ea833544f", - "0xaed3ab927548bc8bec31467ba80689c71a168e34f50dcb6892f19a33a099f5aa6b3f9cb79f5c0699e837b9a8c7f27efe", - "0xb8fbf7696210a36e20edabd77839f4dfdf50d6d015cdf81d587f90284a9bcef7d2a1ff520728d7cc69a4843d6c20dedd", - "0xa9d533769ce6830211c884ae50a82a7bf259b44ac71f9fb11f0296fdb3981e6b4c1753fe744647b247ebc433a5a61436", - "0x8b4bdf90d33360b7f428c71cde0a49fb733badba8c726876945f58c620ce7768ae0e98fc8c31fa59d8955a4823336bb1", - "0x808d42238e440e6571c59e52a35ae32547d502dc24fd1759d8ea70a7231a95859baf30b490a4ba55fa2f3aaa11204597", - "0x85594701f1d2fee6dc1956bc44c7b31db93bdeec2f3a7d622c1a08b26994760773e3d57521a44cfd7e407ac3fd430429", - "0xa66de045ce7173043a6825e9dc440ac957e2efb6df0a337f4f8003eb0c719d873a52e6eba3cb0d69d977ca37d9187674", - "0x87a1c6a1fdff993fa51efa5c3ba034c079c0928a7d599b906336af7c2dcab9721ceaf3108c646490af9dff9a754f54b3", - "0x926424223e462ceb75aed7c22ade8a7911a903b7e5dd4bc49746ddce8657f4616325cd12667d4393ac52cdd866396d0e", - "0xb5dc96106593b42b30f06f0b0a1e0c1aafc70432e31807252d3674f0b1ea5e58eac8424879d655c9488d85a879a3e572", - "0x997ca0987735cc716507cb0124b1d266d218b40c9d8e0ecbf26a1d65719c82a637ce7e8be4b4815d307df717bde7c72a", - "0x92994d3f57a569b7760324bb5ae4e8e14e1633d175dab06aa57b8e391540e05f662fdc08b8830f489a063f59b689a688", - "0xa8087fcc6aa4642cb998bea11facfe87eb33b90a9aa428ab86a4124ad032fc7d2e57795311a54ec9f55cc120ebe42df1", - "0xa9bd7d1de6c0706052ca0b362e2e70e8c8f70f1f026ea189b4f87a08ce810297ebfe781cc8004430776c54c1a05ae90c", - "0x856d33282e8a8e33a3d237fb0a0cbabaf77ba9edf2fa35a831fdafcadf620561846aa6cbb6bdc5e681118e1245834165", - "0x9524a7aa8e97a31a6958439c5f3339b19370f03e86b89b1d02d87e4887309dbbe9a3a8d2befd3b7ed5143c8da7e0a8ad", - "0x824fdf433e090f8acbd258ac7429b21f36f9f3b337c6d0b71d1416a5c88a767883e255b2888b7c906dd2e9560c4af24c", - "0x88c7fee662ca7844f42ed5527996b35723abffd0d22d4ca203b9452c639a5066031207a5ae763dbc0865b3299d19b1ec", - "0x919dca5c5595082c221d5ab3a5bc230f45da7f6dec4eb389371e142c1b9c6a2c919074842479c2844b72c0d806170c0c", - "0xb939be8175715e55a684578d8be3ceff3087f60fa875fff48e52a6e6e9979c955efef8ff67cfa2b79499ea23778e33b0", - "0x873b6db725e7397d11bc9bed9ac4468e36619135be686790a79bc6ed4249058f1387c9a802ea86499f692cf635851066", - "0xaeae06db3ec47e9e5647323fa02fac44e06e59b885ad8506bf71b184ab3895510c82f78b6b22a5d978e8218e7f761e9f", - "0xb99c0a8359c72ab88448bae45d4bf98797a26bca48b0d4460cd6cf65a4e8c3dd823970ac3eb774ae5d0cea4e7fadf33e", - "0x8f10c8ec41cdfb986a1647463076a533e6b0eec08520c1562401b36bb063ac972aa6b28a0b6ce717254e35940b900e3c", - "0xa106d9be199636d7add43b942290269351578500d8245d4aae4c083954e4f27f64740a3138a66230391f2d0e6043a8de", - "0xa469997908244578e8909ff57cffc070f1dbd86f0098df3cfeb46b7a085cfecc93dc69ee7cad90ff1dc5a34d50fe580c", - "0xa4ef087bea9c20eb0afc0ee4caba7a9d29dfa872137828c721391273e402fb6714afc80c40e98bbd8276d3836bffa080", - "0xb07a013f73cd5b98dae0d0f9c1c0f35bff8a9f019975c4e1499e9bee736ca6fcd504f9bc32df1655ff333062382cff04", - "0xb0a77188673e87cc83348c4cc5db1eecf6b5184e236220c8eeed7585e4b928db849944a76ec60ef7708ef6dac02d5592", - "0xb1284b37e59b529f0084c0dacf0af6c0b91fc0f387bf649a8c74819debf606f7b07fc3e572500016fb145ec2b24e9f17", - "0x97b20b5b4d6b9129da185adfbf0d3d0b0faeba5b9715f10299e48ea0521709a8296a9264ce77c275a59c012b50b6519a", - "0xb9d37e946fae5e4d65c1fbfacc8a62e445a1c9d0f882e60cca649125af303b3b23af53c81d7bac544fb7fcfc7a314665", - "0x8e5acaac379f4bb0127efbef26180f91ff60e4c525bc9b798fc50dfaf4fe8a5aa84f18f3d3cfb8baead7d1e0499af753", - "0xb0c0b8ab1235bf1cda43d4152e71efc1a06c548edb964eb4afceb201c8af24240bf8ab5cae30a08604e77432b0a5faf0", - "0x8cc28d75d5c8d062d649cbc218e31c4d327e067e6dbd737ec0a35c91db44fbbd0d40ec424f5ed79814add16947417572", - "0x95ae6219e9fd47efaa9cb088753df06bc101405ba50a179d7c9f7c85679e182d3033f35b00dbba71fdcd186cd775c52e", - "0xb5d28fa09f186ebc5aa37453c9b4d9474a7997b8ae92748ecb940c14868792292ac7d10ade01e2f8069242b308cf97e5", - "0x8c922a0faa14cc6b7221f302df3342f38fc8521ec6c653f2587890192732c6da289777a6cd310747ea7b7d104af95995", - "0xb9ad5f660b65230de54de535d4c0fcae5bc6b59db21dea5500fdc12eea4470fb8ea003690fdd16d052523418d5e01e8c", - "0xa39a9dd41a0ff78c82979483731f1cd68d3921c3e9965869662c22e02dde3877802e180ba93f06e7346f96d9fa9261d2", - "0x8b32875977ec372c583b24234c27ed73aef00cdff61eb3c3776e073afbdeade548de9497c32ec6d703ff8ad0a5cb7fe4", - "0x9644cbe755a5642fe9d26cfecf170d3164f1848c2c2e271d5b6574a01755f3980b3fc870b98cf8528fef6ecef4210c16", - "0x81ea9d1fdd9dd66d60f40ce0712764b99da9448ae0b300f8324e1c52f154e472a086dda840cb2e0b9813dc8ce8afd4b5", - "0x906aaa4a7a7cdf01909c5cfbc7ded2abc4b869213cbf7c922d4171a4f2e637e56f17020b852ad339d83b8ac92f111666", - "0x939b5f11acbdeff998f2a080393033c9b9d8d5c70912ea651c53815c572d36ee822a98d6dfffb2e339f29201264f2cf4", - "0xaba4898bf1ccea9b9e2df1ff19001e05891581659c1cbbde7ee76c349c7fc7857261d9785823c9463a8aea3f40e86b38", - "0x83ca1a56b8a0be4820bdb5a9346357c68f9772e43f0b887729a50d2eb2a326bbcede676c8bf2e51d7c89bbd8fdb778a6", - "0x94e86e9fe6addfe2c3ee3a547267ed921f4230d877a85bb4442c2d9350c2fa9a9c54e6fe662de82d1a2407e4ab1691c2", - "0xa0cc3bdef671a59d77c6984338b023fa2b431b32e9ed2abe80484d73edc6540979d6f10812ecc06d4d0c5d4eaca7183c", - "0xb5343413c1b5776b55ea3c7cdd1f3af1f6bd802ea95effe3f2b91a523817719d2ecc3f8d5f3cc2623ace7e35f99ca967", - "0x92085d1ed0ed28d8cabe3e7ff1905ed52c7ceb1eac5503760c52fb5ee3a726aba7c90b483c032acc3f166b083d7ec370", - "0x8ec679520455275cd957fca8122724d287db5df7d29f1702a322879b127bff215e5b71d9c191901465d19c86c8d8d404", - "0xb65eb2c63d8a30332eb24ee8a0c70156fc89325ebbb38bacac7cf3f8636ad8a472d81ccca80423772abc00192d886d8a", - "0xa9fe1c060b974bee4d590f2873b28635b61bfcf614e61ff88b1be3eee4320f4874e21e8d666d8ac8c9aba672efc6ecae", - "0xb3fe2a9a389c006a831dea7e777062df84b5c2803f9574d7fbe10b7e1c125817986af8b6454d6be9d931a5ac94cfe963", - "0x95418ad13b734b6f0d33822d9912c4c49b558f68d08c1b34a0127fcfa666bcae8e6fda8832d2c75bb9170794a20e4d7c", - "0xa9a7df761e7f18b79494bf429572140c8c6e9d456c4d4e336184f3f51525a65eb9582bea1e601bdb6ef8150b7ca736a5", - "0xa0de03b1e75edf7998c8c1ac69b4a1544a6fa675a1941950297917366682e5644a4bda9cdeedfaf9473d7fccd9080b0c", - "0xa61838af8d95c95edf32663a68f007d95167bf6e41b0c784a30b22d8300cfdd5703bd6d16e86396638f6db6ae7e42a85", - "0x8866d62084d905c145ff2d41025299d8b702ac1814a7dec4e277412c161bc9a62fed735536789cb43c88693c6b423882", - "0x91da22c378c81497fe363e7f695c0268443abee50f8a6625b8a41e865638a643f07b157ee566de09ba09846934b4e2d7", - "0x941d21dd57c9496aa68f0c0c05507405fdd413acb59bc668ce7e92e1936c68ec4b065c3c30123319884149e88228f0b2", - "0xa77af9b094bc26966ddf2bf9e1520c898194a5ccb694915950dadc204facbe3066d3d89f50972642d76b14884cfbaa21", - "0x8e76162932346869f4618bde744647f7ab52ab498ad654bdf2a4feeb986ac6e51370841e5acbb589e38b6e7142bb3049", - "0xb60979ace17d6937ece72e4f015da4657a443dd01cebc7143ef11c09e42d4aa8855999a65a79e2ea0067f31c9fc2ab0f", - "0xb3e2ffdd5ee6fd110b982fd4fad4b93d0fca65478f986d086eeccb0804960bfaa1919afa743c2239973ea65091fe57d2", - "0x8ce0ce05e7d7160d44574011da687454dbd3c8b8290aa671731b066e2c82f8cf2d63cb8e932d78c6122ec610e44660e6", - "0xab005dd8d297045c39e2f72fb1c48edb501ccf3575d3d04b9817b3afee3f0bb0f3f53f64bda37d1d9cde545aae999bae", - "0x95bd7edb4c4cd60e3cb8a72558845a3cce6bb7032ccdf33d5a49ebb6ddf203bc3c79e7b7e550735d2d75b04c8b2441e8", - "0x889953ee256206284094e4735dbbb17975bafc7c3cb94c9fbfee4c3e653857bfd49e818f64a47567f721b98411a3b454", - "0xb188423e707640ab0e75a061e0b62830cde8afab8e1ad3dae30db69ffae4e2fc005bababbdcbd7213b918ed4f70e0c14", - "0xa97e0fafe011abd70d4f99a0b36638b3d6e7354284588f17a88970ed48f348f88392779e9a038c6cbc9208d998485072", - "0x87db11014a91cb9b63e8dfaa82cdebca98272d89eb445ee1e3ff9dbaf2b3fad1a03b888cffc128e4fe208ed0dddece0f", - "0xaad2e40364edd905d66ea4ac9d51f9640d6fda9a54957d26ba233809851529b32c85660fa401dbee3679ec54fa6dd966", - "0x863e99336ca6edf03a5a259e59a2d0f308206e8a2fb320cfc0be06057366df8e0f94b33a28f574092736b3c5ada84270", - "0xb34bcc56a057589f34939a1adc51de4ff6a9f4fee9c7fa9aa131e28d0cf0759a0c871b640162acdfbf91f3f1b59a3703", - "0x935dd28f2896092995c5eff1618e5b6efe7a40178888d7826da9b0503c2d6e68a28e7fac1a334e166d0205f0695ef614", - "0xb842cd5f8f5de5ca6c68cb4a5c1d7b451984930eb4cc18fd0934d52fdc9c3d2d451b1c395594d73bc3451432bfba653f", - "0x9014537885ce2debad736bc1926b25fdab9f69b216bf024f589c49dc7e6478c71d595c3647c9f65ff980b14f4bb2283b", - "0x8e827ccca1dd4cd21707140d10703177d722be0bbe5cac578db26f1ef8ad2909103af3c601a53795435b27bf95d0c9ed", - "0x8a0b8ad4d466c09d4f1e9167410dbe2edc6e0e6229d4b3036d30f85eb6a333a18b1c968f6ca6d6889bb08fecde017ef4", - "0x9241ee66c0191b06266332dc9161dede384c4bb4e116dbd0890f3c3790ec5566da4568243665c4725b718ac0f6b5c179", - "0xaeb4d5fad81d2b505d47958a08262b6f1b1de9373c2c9ba6362594194dea3e002ab03b8cbb43f867be83065d3d370f19", - "0x8781bc83bb73f7760628629fe19e4714b494dbed444c4e4e4729b7f6a8d12ee347841a199888794c2234f51fa26fc2b9", - "0xb58864f0acd1c2afa29367e637cbde1968d18589245d9936c9a489c6c495f54f0113ecdcbe4680ac085dd3c397c4d0c3", - "0x94a24284afaeead61e70f3e30f87248d76e9726759445ca18cdb9360586c60cc9f0ec1c397f9675083e0b56459784e2e", - "0xaed358853f2b54dcbddf865e1816c2e89be12e940e1abfa661e2ee63ffc24a8c8096be2072fa83556482c0d89e975124", - "0xb95374e6b4fc0765708e370bc881e271abf2e35c08b056a03b847e089831ef4fe3124b9c5849d9c276eb2e35b3daf264", - "0xb834cdbcfb24c8f84bfa4c552e7fadc0028a140952fd69ed13a516e1314a4cd35d4b954a77d51a1b93e1f5d657d0315d", - "0x8fb6d09d23bfa90e7443753d45a918d91d75d8e12ec7d016c0dfe94e5c592ba6aaf483d2f16108d190822d955ad9cdc3", - "0xaa315cd3c60247a6ad4b04f26c5404c2713b95972843e4b87b5a36a89f201667d70f0adf20757ebe1de1b29ae27dda50", - "0xa116862dca409db8beff5b1ccd6301cdd0c92ca29a3d6d20eb8b87f25965f42699ca66974dd1a355200157476b998f3b", - "0xb4c2f5fe173c4dc8311b60d04a65ce1be87f070ac42e13cd19c6559a2931c6ee104859cc2520edebbc66a13dc7d30693", - "0x8d4a02bf99b2260c334e7d81775c5cf582b00b0c982ce7745e5a90624919028278f5e9b098573bad5515ce7fa92a80c8", - "0x8543493bf564ce6d97bd23be9bff1aba08bd5821ca834f311a26c9139c92a48f0c2d9dfe645afa95fec07d675d1fd53b", - "0x9344239d13fde08f98cb48f1f87d34cf6abe8faecd0b682955382a975e6eed64e863fa19043290c0736261622e00045c", - "0xaa49d0518f343005ca72b9e6c7dcaa97225ce6bb8b908ebbe7b1a22884ff8bfb090890364e325a0d414ad180b8f161d1", - "0x907d7fd3e009355ab326847c4a2431f688627faa698c13c03ffdd476ecf988678407f029b8543a475dcb3dafdf2e7a9c", - "0x845f1f10c6c5dad2adc7935f5cd2e2b32f169a99091d4f1b05babe7317b9b1cdce29b5e62f947dc621b9acbfe517a258", - "0x8f3be8e3b380ea6cdf9e9c237f5e88fd5a357e5ded80ea1fc2019810814de82501273b4da38916881125b6fa0cfd4459", - "0xb9c7f487c089bf1d20c822e579628db91ed9c82d6ca652983aa16d98b4270c4da19757f216a71b9c13ddee3e6e43705f", - "0x8ba2d8c88ad2b872db104ea8ddbb006ec2f3749fd0e19298a804bb3a5d94de19285cc7fb19fee58a66f7851d1a66c39f", - "0x9375ecd3ed16786fe161af5d5c908f56eeb467a144d3bbddfc767e90065b7c94fc53431adebecba2b6c9b5821184d36e", - "0xa49e069bfadb1e2e8bff6a4286872e2a9765d62f0eaa4fcb0e5af4bbbed8be3510fb19849125a40a8a81d1e33e81c3eb", - "0x9522cc66757b386aa6b88619525c8ce47a5c346d590bb3647d12f991e6c65c3ab3c0cfc28f0726b6756c892eae1672be", - "0xa9a0f1f51ff877406fa83a807aeb17b92a283879f447b8a2159653db577848cc451cbadd01f70441e351e9ed433c18bc", - "0x8ff7533dcff6be8714df573e33f82cf8e9f2bcaaa43e939c4759d52b754e502717950de4b4252fb904560fc31dce94a4", - "0x959724671e265a28d67c29d95210e97b894b360da55e4cf16e6682e7912491ed8ca14bfaa4dce9c25a25b16af580494f", - "0x92566730c3002f4046c737032487d0833c971e775de59fe02d9835c9858e2e3bc37f157424a69764596c625c482a2219", - "0xa84b47ceff13ed9c3e5e9cdf6739a66d3e7c2bd8a6ba318fefb1a9aecf653bb2981da6733ddb33c4b0a4523acc429d23", - "0xb4ddf571317e44f859386d6140828a42cf94994e2f1dcbcc9777f4eebbfc64fc1e160b49379acc27c4672b8e41835c5d", - "0x8ab95c94072b853d1603fdd0a43b30db617d13c1d1255b99075198e1947bfa5f59aed2b1147548a1b5e986cd9173d15c", - "0x89511f2eab33894fd4b3753d24249f410ff7263052c1fef6166fc63a79816656b0d24c529e45ccce6be28de6e375d916", - "0xa0866160ca63d4f2be1b4ea050dac6b59db554e2ebb4e5b592859d8df339b46fd7cb89aaed0951c3ee540aee982c238a", - "0x8fcc5cbba1b94970f5ff2eb1922322f5b0aa7d918d4b380c9e7abfd57afd8b247c346bff7b87af82efbce3052511cd1b", - "0x99aeb2a5e846b0a2874cca02c66ed40d5569eb65ab2495bc3f964a092e91e1517941f2688e79f8cca49cd3674c4e06dc", - "0xb7a096dc3bad5ca49bee94efd884aa3ff5615cf3825cf95fbe0ce132e35f46581d6482fa82666c7ef5f1643eaee8f1ca", - "0x94393b1da6eaac2ffd186b7725eca582f1ddc8cdd916004657f8a564a7c588175cb443fc6943b39029f5bbe0add3fad8", - "0x884b85fe012ccbcd849cb68c3ad832d83b3ef1c40c3954ffdc97f103b1ed582c801e1a41d9950f6bddc1d11f19d5ec76", - "0xb00061c00131eded8305a7ce76362163deb33596569afb46fe499a7c9d7a0734c084d336b38d168024c2bb42b58e7660", - "0xa439153ac8e6ca037381e3240e7ba08d056c83d7090f16ed538df25901835e09e27de2073646e7d7f3c65056af6e4ce7", - "0x830fc9ca099097d1f38b90e6843dc86f702be9d20bdacc3e52cae659dc41df5b8d2c970effa6f83a5229b0244a86fe22", - "0xb81ea2ffaaff2bb00dd59a9ab825ba5eed4db0d8ac9c8ed1a632ce8f086328a1cddd045fbe1ace289083c1325881b7e7", - "0xb51ea03c58daf2db32c99b9c4789b183365168cb5019c72c4cc91ac30b5fb7311d3db76e6fa41b7cd4a8c81e2f6cdc94", - "0xa4170b2c6d09ca5beb08318730419b6f19215ce6c631c854116f904be3bc30dd85a80c946a8ab054d3e307afaa3f8fbc", - "0x897cc42ff28971ff54d2a55dd6b35cfb8610ac902f3c06e3a5cea0e0a257e870c471236a8e84709211c742a09c5601a6", - "0xa18f2e98d389dace36641621488664ecbb422088ab03b74e67009b8b8acacaaa24fdcf42093935f355207d934adc52a8", - "0x92adcfb678cc2ba19c866f3f2b988fdcb4610567f3ab436cc0cb9acaf5a88414848d71133ebdbec1983e38e6190f1b5f", - "0xa86d43c2ce01b366330d3b36b3ca85f000c3548b8297e48478da1ee7d70d8576d4650cba7852ed125c0d7cb6109aa7f3", - "0x8ed31ceed9445437d7732dce78a762d72ff32a7636bfb3fd7974b7ae15db414d8184a1766915244355deb354fbc5803b", - "0x9268f70032584f416e92225d65af9ea18c466ebc7ae30952d56a4e36fd9ea811dde0a126da9220ba3c596ec54d8a335e", - "0x9433b99ee94f2d3fbdd63b163a2bdf440379334c52308bd24537f7defd807145a062ff255a50d119a7f29f4b85d250e3", - "0x90ce664f5e4628a02278f5cf5060d1a34f123854634b1870906e5723ac9afd044d48289be283b267d45fcbf3f4656aaf", - "0xaaf21c4d59378bb835d42ae5c5e5ab7a3c8c36a59e75997989313197752b79a472d866a23683b329ea69b048b87fa13e", - "0xb83c0589b304cec9ede549fde54f8a7c2a468c6657da8c02169a6351605261202610b2055c639b9ed2d5b8c401fb8f56", - "0x9370f326ea0f170c2c05fe2c5a49189f20aec93b6b18a5572a818cd4c2a6adb359e68975557b349fb54f065d572f4c92", - "0xac3232fa5ce6f03fca238bef1ce902432a90b8afce1c85457a6bee5571c033d4bceefafc863af04d4e85ac72a4d94d51", - "0x80d9ea168ff821b22c30e93e4c7960ce3ad3c1e6deeebedd342a36d01bd942419b187e2f382dbfd8caa34cca08d06a48", - "0xa387a3c61676fb3381eefa2a45d82625635a666e999aba30e3b037ec9e040f414f9e1ad9652abd3bcad63f95d85038db", - "0xa1b229fe32121e0b391b0f6e0180670b9dc89d79f7337de4c77ea7ad0073e9593846f06797c20e923092a08263204416", - "0x92164a9d841a2b828cedf2511213268b698520f8d1285852186644e9a0c97512cafa4bfbe29af892c929ebccd102e998", - "0x82ee2fa56308a67c7db4fd7ef539b5a9f26a1c2cc36da8c3206ba4b08258fbb3cec6fe5cdbd111433fb1ba2a1e275927", - "0x8c77bfe9e191f190a49d46f05600603fa42345592539b82923388d72392404e0b29a493a15e75e8b068dddcd444c2928", - "0x80b927f93ccf79dcf5c5b20bcf5a7d91d7a17bc0401bb7cc9b53a6797feac31026eb114257621f5a64a52876e4474cc1", - "0xb6b68b6501c37804d4833d5a063dd108a46310b1400549074e3cac84acc6d88f73948b7ad48d686de89c1ec043ae8c1a", - "0xab3da00f9bdc13e3f77624f58a3a18fc3728956f84b5b549d62f1033ae4b300538e53896e2d943f160618e05af265117", - "0xb6830e87233b8eace65327fdc764159645b75d2fd4024bf8f313b2dd5f45617d7ecfb4a0b53ccafb5429815a9a1adde6", - "0xb9251cfe32a6dc0440615aadcd98b6b1b46e3f4e44324e8f5142912b597ee3526bea2431e2b0282bb58f71be5b63f65e", - "0xaf8d70711e81cdddfb39e67a1b76643292652584c1ce7ce4feb1641431ad596e75c9120e85f1a341e7a4da920a9cdd94", - "0x98cd4e996594e89495c078bfd52a4586b932c50a449a7c8dfdd16043ca4cda94dafbaa8ad1b44249c99bbcc52152506e", - "0xb9fc6d1c24f48404a4a64fbe3e43342738797905db46e4132aee5f086aaa4c704918ad508aaefa455cfe1b36572e6242", - "0xa365e871d30ba9291cedaba1be7b04e968905d003e9e1af7e3b55c5eb048818ae5b913514fb08b24fb4fbdccbb35d0b8", - "0x93bf99510971ea9af9f1e364f1234c898380677c8e8de9b0dd24432760164e46c787bc9ec42a7ad450500706cf247b2d", - "0xb872f825a5b6e7b9c7a9ddfeded3516f0b1449acc9b4fd29fc6eba162051c17416a31e5be6d3563f424d28e65bab8b8f", - "0xb06b780e5a5e8eb4f4c9dc040f749cf9709c8a4c9ef15e925f442b696e41e5095db0778a6c73bcd329b265f2c6955c8b", - "0x848f1a981f5fc6cd9180cdddb8d032ad32cdfa614fc750d690dbae36cc0cd355cbf1574af9b3ffc8b878f1b2fafb9544", - "0xa03f48cbff3e9e8a3a655578051a5ae37567433093ac500ed0021c6250a51b767afac9bdb194ee1e3eac38a08c0eaf45", - "0xb5be78ce638ff8c4aa84352b536628231d3f7558c5be3bf010b28feac3022e64691fa672f358c8b663904aebe24a54ed", - "0xa9d4da70ff676fa55d1728ba6ab03b471fa38b08854d99e985d88c2d050102d8ccffbe1c90249a5607fa7520b15fe791", - "0x8fe9f7092ffb0b69862c8e972fb1ecf54308c96d41354ed0569638bb0364f1749838d6d32051fff1599112978c6e229c", - "0xae6083e95f37770ecae0df1e010456f165d96cfe9a7278c85c15cffd61034081ce5723e25e2bede719dc9341ec8ed481", - "0xa260891891103089a7afbd9081ea116cfd596fd1015f5b65e10b0961eb37fab7d09c69b7ce4be8bf35e4131848fb3fe4", - "0x8d729fa32f6eb9fd2f6a140bef34e8299a2f3111bffd0fe463aa8622c9d98bfd31a1df3f3e87cd5abc52a595f96b970e", - "0xa30ec6047ae4bc7da4daa7f4c28c93aedb1112cfe240e681d07e1a183782c9ff6783ac077c155af23c69643b712a533f", - "0xac830726544bfe7b5467339e5114c1a75f2a2a8d89453ce86115e6a789387e23551cd64620ead6283dfa4538eb313d86", - "0x8445c135b7a48068d8ed3e011c6d818cfe462b445095e2fbf940301e50ded23f272d799eea47683fc027430ce14613ef", - "0x95785411715c9ae9d8293ce16a693a2aa83e3cb1b4aa9f76333d0da2bf00c55f65e21e42e50e6c5772ce213dd7b4f7a0", - "0xb273b024fa18b7568c0d1c4d2f0c4e79ec509dafac8c5951f14192d63ddbcf2d8a7512c1c1b615cc38fa3e336618e0c5", - "0xa78b9d3ea4b6a90572eb27956f411f1d105fdb577ee2ffeec9f221da9b45db84bfe866af1f29597220c75e0c37a628d8", - "0xa4be2bf058c36699c41513c4d667681ce161a437c09d81383244fc55e1c44e8b1363439d0cce90a3e44581fb31d49493", - "0xb6eef13040f17dd4eba22aaf284d2f988a4a0c4605db44b8d2f4bf9567ac794550b543cc513c5f3e2820242dd704152e", - "0x87eb00489071fa95d008c5244b88e317a3454652dcb1c441213aa16b28cd3ecaa9b22fec0bdd483c1df71c37119100b1", - "0x92d388acdcb49793afca329cd06e645544d2269234e8b0b27d2818c809c21726bc9cf725651b951e358a63c83dedee24", - "0xae27e219277a73030da27ab5603c72c8bd81b6224b7e488d7193806a41343dff2456132274991a4722fdb0ef265d04cd", - "0x97583e08ecb82bbc27c0c8476d710389fa9ffbead5c43001bd36c1b018f29faa98de778644883e51870b69c5ffb558b5", - "0x90a799a8ce73387599babf6b7da12767c0591cadd36c20a7990e7c05ea1aa2b9645654ec65308ee008816623a2757a6a", - "0xa1b47841a0a2b06efd9ab8c111309cc5fc9e1d5896b3e42ed531f6057e5ade8977c29831ce08dbda40348386b1dcc06d", - "0xb92b8ef59bbddb50c9457691bc023d63dfcc54e0fd88bd5d27a09e0d98ac290fc90e6a8f6b88492043bf7c87fac8f3e4", - "0xa9d6240b07d62e22ec8ab9b1f6007c975a77b7320f02504fc7c468b4ee9cfcfd945456ff0128bc0ef2174d9e09333f8d", - "0x8e96534c94693226dc32bca79a595ca6de503af635f802e86442c67e77564829756961d9b701187fe91318da515bf0e6", - "0xb6ba290623cd8dd5c2f50931c0045d1cfb0c30877bc8fe58cbc3ff61ee8da100045a39153916efa1936f4aee0892b473", - "0xb43baa7717fac02d4294f5b3bb5e58a65b3557747e3188b482410388daac7a9c177f762d943fd5dcf871273921213da8", - "0xb9cf00f8fb5e2ef2b836659fece15e735060b2ea39b8e901d3dcbdcf612be8bf82d013833718c04cd46ffaa70b85f42e", - "0x8017d0c57419e414cbba504368723e751ef990cc6f05dad7b3c2de6360adc774ad95512875ab8337d110bf39a42026fa", - "0xae7401048b838c0dcd4b26bb6c56d79d51964a0daba780970b6c97daee4ea45854ea0ac0e4139b3fe60dac189f84df65", - "0x887b237b0cd0f816b749b21db0b40072f9145f7896c36916296973f9e6990ede110f14e5976c906d08987c9836cca57f", - "0xa88c3d5770148aee59930561ca1223aceb2c832fb5417e188dca935905301fc4c6c2c9270bc1dff7add490a125eb81c6", - "0xb6cf9b02c0cd91895ad209e38c54039523f137b5848b9d3ad33ae43af6c20c98434952db375fe378de7866f2d0e8b18a", - "0x84ef3d322ff580c8ad584b1fe4fe346c60866eb6a56e982ba2cf3b021ecb1fdb75ecc6c29747adda86d9264430b3f816", - "0xa0561c27224baf0927ad144cb71e31e54a064c598373fcf0d66aebf98ab7af1d8e2f343f77baefff69a6da750a219e11", - "0xaa5cc43f5b8162b016f5e1b61214c0c9d15b1078911c650b75e6cdfb49b85ee04c6739f5b1687d15908444f691f732de", - "0xad4ac099b935589c7b8fdfdf3db332b7b82bb948e13a5beb121ebd7db81a87d278024a1434bcf0115c54ca5109585c3d", - "0x8a00466abf3f109a1dcd19e643b603d3af23d42794ef8ca2514dd507ecea44a031ac6dbc18bd02f99701168b25c1791e", - "0xb00b5900dfad79645f8bee4e5adc7b84eb22e5b1e67df77ccb505b7fc044a6c08a8ea5faca662414eb945f874f884cea", - "0x950e204e5f17112250b22ea6bb8423baf522fc0af494366f18fe0f949f51d6e6812074a80875cf1ed9c8e7420058d541", - "0x91e5cbf8bb1a1d50c81608c9727b414d0dd2fb467ebc92f100882a3772e54f94979cfdf8e373fdef7c7fcdd60fec9e00", - "0xa093f6a857b8caaff80599c2e89c962b415ecbaa70d8fd973155fa976a284c6b29a855f5f7a3521134d00d2972755188", - "0xb4d55a3551b00da54cc010f80d99ddd2544bde9219a3173dfaadf3848edc7e4056ab532fb75ac26f5f7141e724267663", - "0xa03ea050fc9b011d1b04041b5765d6f6453a93a1819cd9bd6328637d0b428f08526466912895dcc2e3008ee58822e9a7", - "0x99b12b3665e473d01bc6985844f8994fb65cb15745024fb7af518398c4a37ff215da8f054e8fdf3286984ae36a73ca5e", - "0x9972c7e7a7fb12e15f78d55abcaf322c11249cd44a08f62c95288f34f66b51f146302bce750ff4d591707075d9123bd2", - "0xa64b4a6d72354e596d87cda213c4fc2814009461570ccb27d455bbe131f8d948421a71925425b546d8cf63d5458cd64b", - "0x91c215c73b195795ede2228b7ed1f6e37892e0c6b0f4a0b5a16c57aa1100c84df9239054a173b6110d6c2b7f4bf1ce52", - "0x88807198910ec1303480f76a3683870246a995e36adaeadc29c22f0bdba8152fe705bd070b75de657b04934f7d0ccf80", - "0xb37c0026c7b32eb02cacac5b55cb5fe784b8e48b2945c64d3037af83ece556a117f0ff053a5968c2f5fa230e291c1238", - "0x94c768384ce212bc2387e91ce8b45e4ff120987e42472888a317abc9dcdf3563b62e7a61c8e98d7cdcbe272167d91fc6", - "0xa10c2564936e967a390cb14ef6e8f8b04ea9ece5214a38837eda09e79e0c7970b1f83adf017c10efd6faa8b7ffa2c567", - "0xa5085eed3a95f9d4b1269182ea1e0d719b7809bf5009096557a0674bde4201b0ddc1f0f16a908fc468846b3721748ce3", - "0x87468eb620b79a0a455a259a6b4dfbc297d0d53336537b771254dd956b145dc816b195b7002647ea218552e345818a3f", - "0xace2b77ffb87366af0a9cb5d27d6fc4a14323dbbf1643f5f3c4559306330d86461bb008894054394cbfaefeaa0bc2745", - "0xb27f56e840a54fbd793f0b7a7631aa4cee64b5947e4382b2dfb5eb1790270288884c2a19afebe5dc0c6ef335d4531c1c", - "0x876e438633931f7f895062ee16c4b9d10428875f7bc79a8e156a64d379a77a2c45bf5430c5ab94330f03da352f1e9006", - "0xa2512a252587d200d2092b44c914df54e04ff8bcef36bf631f84bde0cf5a732e3dc7f00f662842cfd74b0b0f7f24180e", - "0x827f1bc8f54a35b7a4bd8154f79bcc055e45faed2e74adf7cf21cca95df44d96899e847bd70ead6bb27b9c0ed97bbd8b", - "0xa0c92cf5a9ed843714f3aea9fe7b880f622d0b4a3bf66de291d1b745279accf6ba35097849691370f41732ba64b5966b", - "0xa63f5c1e222775658421c487b1256b52626c6f79cb55a9b7deb2352622cedffb08502042d622eb3b02c97f9c09f9c957", - "0x8cc093d52651e65fb390e186db6cc4de559176af4624d1c44cb9b0e836832419dacac7b8db0627b96288977b738d785d", - "0xaa7b6a17dfcec146134562d32a12f7bd7fe9522e300859202a02939e69dbd345ed7ff164a184296268f9984f9312e8fc", - "0x8ac76721f0d2b679f023d06cbd28c85ae5f4b43c614867ccee88651d4101d4fd352dbdb65bf36bfc3ebc0109e4b0c6f9", - "0x8d350f7c05fc0dcd9a1170748846fb1f5d39453e4cb31e6d1457bed287d96fc393b2ecc53793ca729906a33e59c6834a", - "0xb9913510dfc5056d7ec5309f0b631d1ec53e3a776412ada9aefdaf033c90da9a49fdde6719e7c76340e86599b1f0eec2", - "0x94955626bf4ce87612c5cfffcf73bf1c46a4c11a736602b9ba066328dc52ad6d51e6d4f53453d4ed55a51e0aad810271", - "0xb0fcab384fd4016b2f1e53f1aafd160ae3b1a8865cd6c155d7073ecc1664e05b1d8bca1def39c158c7086c4e1103345e", - "0x827de3f03edfbde08570b72de6662c8bfa499b066a0a27ebad9b481c273097d17a5a0a67f01553da5392ec3f149b2a78", - "0xab7940384c25e9027c55c40df20bd2a0d479a165ced9b1046958353cd69015eeb1e44ed2fd64e407805ba42df10fc7bf", - "0x8ad456f6ff8cd58bd57567d931f923d0c99141978511b17e03cab7390a72b9f62498b2893e1b05c7c22dd274e9a31919", - "0xac75399e999effe564672db426faa17a839e57c5ef735985c70cd559a377adec23928382767b55ed5a52f7b11b54b756", - "0xb17f975a00b817299ac7af5f2024ea820351805df58b43724393bfb3920a8cd747a3bbd4b8286e795521489db3657168", - "0xa2bed800a6d95501674d9ee866e7314063407231491d794f8cf57d5be020452729c1c7cefd8c50dc1540181f5caab248", - "0x9743f5473171271ffdd3cc59a3ae50545901a7b45cd4bc3570db487865f3b73c0595bebabbfe79268809ee1862e86e4a", - "0xb7eab77c2d4687b60d9d7b04e842b3880c7940140012583898d39fcc22d9b9b0a9be2c2e3788b3e6f30319b39c338f09", - "0x8e2b8f797a436a1b661140e9569dcf3e1eea0a77c7ff2bc4ff0f3e49af04ed2de95e255df8765f1d0927fb456a9926b1", - "0x8aefea201d4a1f4ff98ffce94e540bb313f2d4dfe7e9db484a41f13fc316ed02b282e1acc9bc6f56cad2dc2e393a44c9", - "0xb950c17c0e5ca6607d182144aa7556bb0efe24c68f06d79d6413a973b493bfdf04fd147a4f1ab03033a32004cc3ea66f", - "0xb7b8dcbb179a07165f2dc6aa829fad09f582a71b05c3e3ea0396bf9e6fe73076f47035c031c2101e8e38e0d597eadd30", - "0xa9d77ed89c77ec1bf8335d08d41c3c94dcca9fd1c54f22837b4e54506b212aa38d7440126c80648ab7723ff18e65ed72", - "0xa819d6dfd4aef70e52b8402fe5d135f8082d40eb7d3bb5c4d7997395b621e2bb10682a1bad2c9caa33dd818550fc3ec6", - "0x8f6ee34128fac8bbf13ce2d68b2bb363eb4fd65b297075f88e1446ddeac242500eeb4ef0735e105882ff5ba8c44c139b", - "0xb4440e48255c1644bcecf3a1e9958f1ec4901cb5b1122ee5b56ffd02cad1c29c4266999dbb85aa2605c1b125490074d4", - "0xa43304a067bede5f347775d5811cf65a6380a8d552a652a0063580b5c5ef12a0867a39c7912fa219e184f4538eba1251", - "0xa891ad67a790089ffc9f6d53e6a3d63d3556f5f693e0cd8a7d0131db06fd4520e719cfcc3934f0a8f62a95f90840f1d4", - "0xaea6df8e9bb871081aa0fc5a9bafb00be7d54012c5baf653791907d5042a326aeee966fd9012a582cc16695f5baf7042", - "0x8ffa2660dc52ed1cd4eff67d6a84a8404f358a5f713d04328922269bee1e75e9d49afeec0c8ad751620f22352a438e25", - "0x87ec6108e2d63b06abed350f8b363b7489d642486f879a6c3aa90e5b0f335efc2ff2834eef9353951a42136f8e6a1b32", - "0x865619436076c2760d9e87ddc905023c6de0a8d56eef12c98a98c87837f2ca3f27fd26a2ad752252dbcbe2b9f1d5a032", - "0x980437dce55964293cb315c650c5586ffd97e7a944a83f6618af31c9d92c37b53ca7a21bb5bc557c151b9a9e217e7098", - "0x95d128fc369df4ad8316b72aea0ca363cbc7b0620d6d7bb18f7076a8717a6a46956ff140948b0cc4f6d2ce33b5c10054", - "0x8c7212d4a67b9ec70ebbca04358ad2d36494618d2859609163526d7b3acc2fc935ca98519380f55e6550f70a9bc76862", - "0x893a2968819401bf355e85eee0f0ed0406a6d4a7d7f172d0017420f71e00bb0ba984f6020999a3cdf874d3cd8ebcd371", - "0x9103c1af82dece25d87274e89ea0acd7e68c2921c4af3d8d7c82ab0ed9990a5811231b5b06113e7fa43a6bd492b4564f", - "0x99cfd87a94eab7d35466caa4ed7d7bb45e5c932b2ec094258fb14bf205659f83c209b83b2f2c9ccb175974b2a33e7746", - "0x874b6b93e4ee61be3f00c32dd84c897ccd6855c4b6251eb0953b4023634490ed17753cd3223472873cbc6095b2945075", - "0x84a32c0dc4ea60d33aac3e03e70d6d639cc9c4cc435c539eff915017be3b7bdaba33349562a87746291ebe9bc5671f24", - "0xa7057b24208928ad67914e653f5ac1792c417f413d9176ba635502c3f9c688f7e2ee81800d7e3dc0a340c464da2fd9c5", - "0xa03fb9ed8286aacfa69fbd5d953bec591c2ae4153400983d5dbb6cd9ea37fff46ca9e5cceb9d117f73e9992a6c055ad2", - "0x863b2de04e89936c9a4a2b40380f42f20aefbae18d03750fd816c658aee9c4a03df7b12121f795c85d01f415baaeaa59", - "0x8526eb9bd31790fe8292360d7a4c3eed23be23dd6b8b8f01d2309dbfdc0cfd33ad1568ddd7f8a610f3f85a9dfafc6a92", - "0xb46ab8c5091a493d6d4d60490c40aa27950574a338ea5bbc045be3a114af87bdcb160a8c80435a9b7ad815f3cb56a3f3", - "0xaeadc47b41a8d8b4176629557646202f868b1d728b2dda58a347d937e7ffc8303f20d26d6c00b34c851b8aeec547885d", - "0xaebb19fc424d72c1f1822aa7adc744cd0ef7e55727186f8df8771c784925058c248406ebeeaf3c1a9ee005a26e9a10c6", - "0x8ff96e81c1a4a2ab1b4476c21018fae0a67e92129ee36120cae8699f2d7e57e891f5c624902cb1b845b944926a605cc3", - "0x8251b8d2c43fadcaa049a9e7aff838dae4fb32884018d58d46403ac5f3beb5c518bfd45f03b8abb710369186075eb71c", - "0xa8b2a64f865f51a5e5e86a66455c093407933d9d255d6b61e1fd81ffafc9538d73caaf342338a66ba8ee166372a3d105", - "0xaad915f31c6ba7fdc04e2aaac62e84ef434b7ee76a325f07dc430d12c84081999720181067b87d792efd0117d7ee1eab", - "0xa13db3bb60389883fd41d565c54fb5180d9c47ce2fe7a169ae96e01d17495f7f4fa928d7e556e7c74319c4c25d653eb2", - "0xa4491b0198459b3f552855d680a59214eb74e6a4d6c5fa3b309887dc50ebea2ecf6d26c040550f7dc478b452481466fb", - "0x8f017f13d4b1e3f0c087843582b52d5f8d13240912254d826dd11f8703a99a2f3166dfbdfdffd9a3492979d77524276b", - "0x96c3d5dcd032660d50d7cd9db2914f117240a63439966162b10c8f1f3cf74bc83b0f15451a43b31dbd85e4a7ce0e4bb1", - "0xb479ec4bb79573d32e0ec93b92bdd7ec8c26ddb5a2d3865e7d4209d119fd3499eaac527615ffac78c440e60ef3867ae0", - "0xb2c49c4a33aa94b52b6410b599e81ff15490aafa7e43c8031c865a84e4676354a9c81eb4e7b8be6825fdcefd1e317d44", - "0x906dc51d6a90c089b6704b47592805578a6eed106608eeb276832f127e1b8e858b72e448edcbefb497d152447e0e68ff", - "0xb0e81c63b764d7dfbe3f3fddc9905aef50f3633e5d6a4af6b340495124abedcff5700dfd1577bbbed7b6bf97d02719cb", - "0x9304c64701e3b4ed6d146e48a881f7d83a17f58357cca0c073b2bb593afd2d94f6e2a7a1ec511d0a67ad6ff4c3be5937", - "0xb6fdbd12ba05aa598d80b83f70a15ef90e5cba7e6e75fa038540ee741b644cd1f408a6cecfd2a891ef8d902de586c6b5", - "0xb80557871a6521b1b3c74a1ba083ae055b575df607f1f7b04c867ba8c8c181ea68f8d90be6031f4d25002cca27c44da2", - "0xaa7285b8e9712e06b091f64163f1266926a36607f9d624af9996856ed2aaf03a580cb22ce407d1ade436c28b44ca173f", - "0x8148d72b975238b51e6ea389e5486940d22641b48637d7dfadfa603a605bfc6d74a016480023945d0b85935e396aea5d", - "0x8a014933a6aea2684b5762af43dcf4bdbb633cd0428d42d71167a2b6fc563ece5e618bff22f1db2ddb69b845b9a2db19", - "0x990d91740041db770d0e0eb9d9d97d826f09fd354b91c41e0716c29f8420e0e8aac0d575231efba12fe831091ec38d5a", - "0x9454d0d32e7e308ddec57cf2522fb1b67a2706e33fb3895e9e1f18284129ab4f4c0b7e51af25681d248d7832c05eb698", - "0xa5bd434e75bac105cb3e329665a35bce6a12f71dd90c15165777d64d4c13a82bceedb9b48e762bd24034e0fc9fbe45f4", - "0xb09e3b95e41800d4dc29c6ffdaab2cd611a0050347f6414f154a47ee20ee59bf8cf7181454169d479ebce1eb5c777c46", - "0xb193e341d6a047d15eea33766d656d807b89393665a783a316e9ba10518e5515c8e0ade3d6e15641d917a8a172a5a635", - "0xade435ec0671b3621dde69e07ead596014f6e1daa1152707a8c18877a8b067bde2895dd47444ffa69db2bbef1f1d8816", - "0xa7fd3d6d87522dfc56fb47aef9ce781a1597c56a8bbfd796baba907afdc872f753d732bfda1d3402aee6c4e0c189f52d", - "0xa298cb4f4218d0464b2fab393e512bbc477c3225aa449743299b2c3572f065bc3a42d07e29546167ed9e1b6b3b3a3af3", - "0xa9ee57540e1fd9c27f4f0430d194b91401d0c642456c18527127d1f95e2dba41c2c86d1990432eb38a692fda058fafde", - "0x81d6c1a5f93c04e6d8e5a7e0678c1fc89a1c47a5c920bcd36180125c49fcf7c114866b90e90a165823560b19898a7c16", - "0xa4b7a1ec9e93c899b9fd9aaf264c50e42c36c0788d68296a471f7a3447af4dbc81e4fa96070139941564083ec5b5b5a1", - "0xb3364e327d381f46940c0e11e29f9d994efc6978bf37a32586636c0070b03e4e23d00650c1440f448809e1018ef9f6d8", - "0x8056e0913a60155348300e3a62e28b5e30629a90f7dd4fe11289097076708110a1d70f7855601782a3cdc5bdb1ca9626", - "0xb4980fd3ea17bac0ba9ee1c470b17e575bb52e83ebdd7d40c93f4f87bebeaff1c8a679f9d3d09d635f068d37d5bd28bd", - "0x905a9299e7e1853648e398901dfcd437aa575c826551f83520df62984f5679cb5f0ea86aa45ed3e18b67ddc0dfafe809", - "0xab99553bf31a84f2e0264eb34a08e13d8d15e2484aa9352354becf9a15999c76cc568d68274b70a65e49703fc23540d0", - "0xa43681597bc574d2dae8964c9a8dc1a07613d7a1272bdcb818d98c85d44e16d744250c33f3b5e4d552d97396b55e601f", - "0xa54e5a31716fccb50245898c99865644405b8dc920ded7a11f3d19bdc255996054b268e16f2e40273f11480e7145f41e", - "0x8134f3ad5ef2ad4ba12a8a4e4d8508d91394d2bcdc38b7c8c8c0b0a820357ac9f79d286c65220f471eb1adca1d98fc68", - "0x94e2f755e60471578ab2c1adb9e9cea28d4eec9b0e92e0140770bca7002c365fcabfe1e5fb4fe6cfe79a0413712aa3ef", - "0xad48f8d0ce7eb3cc6e2a3086ad96f562e5bed98a360721492ae2e74dc158586e77ec8c35d5fd5927376301b7741bad2b", - "0x8614f0630bdd7fbad3a31f55afd9789f1c605dc85e7dc67e2edfd77f5105f878bb79beded6e9f0b109e38ea7da67e8d5", - "0x9804c284c4c5e77dabb73f655b12181534ca877c3e1e134aa3f47c23b7ec92277db34d2b0a5d38d2b69e5d1c3008a3e3", - "0xa51b99c3088e473afdaa9e0a9f7e75a373530d3b04e44e1148da0726b95e9f5f0c7e571b2da000310817c36f84b19f7f", - "0xac4ff909933b3b76c726b0a382157cdc74ab851a1ac6cef76953c6444441804cc43abb883363f416592e8f6cfbc4550b", - "0xae7d915eb9fc928b65a29d6edbc75682d08584d0014f7bcf17d59118421ae07d26a02137d1e4de6938bcd1ab8ef48fad", - "0x852f7e453b1af89b754df6d11a40d5d41ea057376e8ecacd705aacd2f917457f4a093d6b9a8801837fa0f62986ad7149", - "0x92c6bf5ada5d0c3d4dd8058483de36c215fa98edab9d75242f3eff9db07c734ad67337da6f0eefe23a487bf75a600dee", - "0xa2b42c09d0db615853763552a48d2e704542bbd786aae016eb58acbf6c0226c844f5fb31e428cb6450b9db855f8f2a6f", - "0x880cc07968266dbfdcfbc21815cd69e0eddfee239167ac693fb0413912d816f2578a74f7716eecd6deefa68c6eccd394", - "0xb885b3ace736cd373e8098bf75ba66fa1c6943ca1bc4408cd98ac7074775c4478594f91154b8a743d9c697e1b29f5840", - "0xa51ce78de512bd87bfa0835de819941dffbf18bec23221b61d8096fc9436af64e0693c335b54e7bfc763f287bdca2db6", - "0xa3c76166a3bdb9b06ef696e57603b58871bc72883ee9d45171a30fe6e1d50e30bc9c51b4a0f5a7270e19a77b89733850", - "0xacefc5c6f8a1e7c24d7b41e0fc7f6f3dc0ede6cf3115ffb9a6e54b1d954cbca9bda8ad7a084be9be245a1b8e9770d141", - "0xb420ed079941842510e31cfad117fa11fb6b4f97dfbc6298cb840f27ebaceba23eeaf3f513bcffbf5e4aae946310182d", - "0x95c3bb5ef26c5ed2f035aa5d389c6b3c15a6705b9818a3fefaed28922158b35642b2e8e5a1a620fdad07e75ad4b43af4", - "0x825149f9081ecf07a2a4e3e8b5d21bade86c1a882475d51c55ee909330b70c5a2ac63771c8600c6f38df716af61a3ea1", - "0x873b935aae16d9f08adbc25353cee18af2f1b8d5f26dec6538d6bbddc515f2217ed7d235dcfea59ae61b428798b28637", - "0x9294150843a2bedcedb3bb74c43eb28e759cf9499582c5430bccefb574a8ddd4f11f9929257ff4c153990f9970a2558f", - "0xb619563a811cc531da07f4f04e5c4c6423010ff9f8ed7e6ec9449162e3d501b269fb1c564c09c0429431879b0f45df02", - "0x91b509b87eb09f007d839627514658c7341bc76d468920fe8a740a8cb96a7e7e631e0ea584a7e3dc1172266f641d0f5c", - "0x8b8aceace9a7b9b4317f1f01308c3904d7663856946afbcea141a1c615e21ccad06b71217413e832166e9dd915fbe098", - "0x87b3b36e725833ea0b0f54753c3728c0dbc87c52d44d705ffc709f2d2394414c652d3283bab28dcce09799504996cee0", - "0xb2670aad5691cbf308e4a6a77a075c4422e6cbe86fdba24e9f84a313e90b0696afb6a067eebb42ba2d10340d6a2f6e51", - "0x876784a9aff3d54faa89b2bacd3ff5862f70195d0b2edc58e8d1068b3c9074c0da1cfa23671fe12f35e33b8a329c0ccd", - "0x8b48b9e758e8a8eae182f5cbec96f67d20cca6d3eee80a2d09208eb1d5d872e09ef23d0df8ebbb9b01c7449d0e3e3650", - "0xb79303453100654c04a487bdcadc9e3578bc80930c489a7069a52e8ca1dba36c492c8c899ce025f8364599899baa287d", - "0x961b35a6111da54ece6494f24dacd5ea46181f55775b5f03df0e370c34a5046ac2b4082925855325bb42bc2a2c98381d", - "0xa31feb1be3f5a0247a1f7d487987eb622e34fca817832904c6ee3ee60277e5847945a6f6ea1ac24542c72e47bdf647df", - "0xa12a2aa3e7327e457e1aae30e9612715dd2cfed32892c1cd6dcda4e9a18203af8a44afb46d03b2eed89f6b9c5a2c0c23", - "0xa08265a838e69a2ca2f80fead6ccf16f6366415b920c0b22ee359bcd8d4464ecf156f400a16a7918d52e6d733dd64211", - "0xb723d6344e938d801cca1a00032af200e541d4471fd6cbd38fb9130daa83f6a1dffbbe7e67fc20f9577f884acd7594b2", - "0xa6733d83ec78ba98e72ddd1e7ff79b7adb0e559e256760d0c590a986e742445e8cdf560d44b29439c26d87edd0b07c8c", - "0xa61c2c27d3f7b9ff4695a17afedf63818d4bfba390507e1f4d0d806ce8778d9418784430ce3d4199fd3bdbc2504d2af3", - "0x8332f3b63a6dc985376e8b1b25eeae68be6160fbe40053ba7bcf6f073204f682da72321786e422d3482fd60c9e5aa034", - "0xa280f44877583fbb6b860d500b1a3f572e3ee833ec8f06476b3d8002058e25964062feaa1e5bec1536d734a5cfa09145", - "0xa4026a52d277fcea512440d2204f53047718ebfcae7b48ac57ea7f6bfbc5de9d7304db9a9a6cbb273612281049ddaec5", - "0x95cdf69c831ab2fad6c2535ede9c07e663d2ddccc936b64e0843d2df2a7b1c31f1759c3c20f1e7a57b1c8f0dbb21b540", - "0x95c96cec88806469c277ab567863c5209027cecc06c7012358e5f555689c0d9a5ffb219a464f086b45817e8536b86d2f", - "0xafe38d4684132a0f03d806a4c8df556bf589b25271fbc6fe2e1ed16de7962b341c5003755da758d0959d2e6499b06c68", - "0xa9b77784fda64987f97c3a23c5e8f61b918be0f7c59ba285084116d60465c4a2aaafc8857eb16823282cc83143eb9126", - "0xa830f05881ad3ce532a55685877f529d32a5dbe56cea57ffad52c4128ee0fad0eeaf0da4362b55075e77eda7babe70e5", - "0x992b3ad190d6578033c13ed5abfee4ef49cbc492babb90061e3c51ee4b5790cdd4c8fc1abff1fa2c00183b6b64f0bbbe", - "0xb1015424d9364aeff75de191652dc66484fdbec3e98199a9eb9671ec57bec6a13ff4b38446e28e4d8aedb58dd619cd90", - "0xa745304604075d60c9db36cada4063ac7558e7ec2835d7da8485e58d8422e817457b8da069f56511b02601289fbb8981", - "0xa5ba4330bc5cb3dbe0486ddf995632a7260a46180a08f42ae51a2e47778142132463cc9f10021a9ad36986108fefa1a9", - "0xb419e9fd4babcaf8180d5479db188bb3da232ae77a1c4ed65687c306e6262f8083070a9ac32220cddb3af2ec73114092", - "0xa49e23dc5f3468f3bf3a0bb7e4a114a788b951ff6f23a3396ae9e12cbff0abd1240878a3d1892105413dbc38818e807c", - "0xb7ecc7b4831f650202987e85b86bc0053f40d983f252e9832ef503aea81c51221ce93279da4aa7466c026b2d2070e55d", - "0x96a8c35cb87f84fa84dcd6399cc2a0fd79cc9158ef4bdde4bae31a129616c8a9f2576cd19baa3f497ca34060979aed7d", - "0x8681b2c00aa62c2b519f664a95dcb8faef601a3b961bb4ce5d85a75030f40965e2983871d41ea394aee934e859581548", - "0x85c229a07efa54a713d0790963a392400f55fbb1a43995a535dc6c929f20d6a65cf4efb434e0ad1cb61f689b8011a3bc", - "0x90856f7f3444e5ad44651c28e24cc085a5db4d2ffe79aa53228c26718cf53a6e44615f3c5cda5aa752d5f762c4623c66", - "0x978999b7d8aa3f28a04076f74d11c41ef9c89fdfe514936c4238e0f13c38ec97e51a5c078ebc6409e517bfe7ccb42630", - "0xa099914dd7ed934d8e0d363a648e9038eb7c1ec03fa04dbcaa40f7721c618c3ef947afef7a16b4d7ac8c12aa46637f03", - "0xab2a104fed3c83d16f2cda06878fa5f30c8c9411de71bfb67fd2fc9aa454dcbcf3d299d72f8cc12e919466a50fcf7426", - "0xa4471d111db4418f56915689482f6144efc4664cfb0311727f36c864648d35734351becc48875df96f4abd3cfcf820f9", - "0x83be11727cd30ea94ccc8fa31b09b81c9d6a9a5d3a4686af9da99587332fe78c1f94282f9755854bafd6033549afec91", - "0x88020ff971dc1a01a9e993cd50a5d2131ffdcbb990c1a6aaa54b20d8f23f9546a70918ea57a21530dcc440c1509c24ad", - "0xae24547623465e87905eaffa1fa5d52bb7c453a8dbd89614fa8819a2abcedaf455c2345099b7324ae36eb0ad7c8ef977", - "0xb59b0c60997de1ee00b7c388bc7101d136c9803bf5437b1d589ba57c213f4f835a3e4125b54738e78abbc21b000f2016", - "0xa584c434dfe194546526691b68fa968c831c31da42303a1d735d960901c74011d522246f37f299555416b8cf25c5a548", - "0x80408ce3724f4837d4d52376d255e10f69eb8558399ae5ca6c11b78b98fe67d4b93157d2b9b639f1b5b64198bfe87713", - "0xabb941e8d406c2606e0ddc35c113604fdd9d249eacc51cb64e2991e551b8639ce44d288cc92afa7a1e7fc599cfc84b22", - "0xb223173f560cacb1c21dba0f1713839e348ad02cbfdef0626748604c86f89e0f4c919ed40b583343795bdd519ba952c8", - "0xaf1c70512ec3a19d98b8a1fc3ff7f7f5048a27d17d438d43f561974bbdd116fcd5d5c21040f3447af3f0266848d47a15", - "0x8a44809568ebe50405bede19b4d2607199159b26a1b33e03d180e6840c5cf59d991a4fb150d111443235d75ecad085b7", - "0xb06207cdca46b125a27b3221b5b50cf27af4c527dd7c80e2dbcebbb09778a96df3af67e50f07725239ce3583dad60660", - "0x993352d9278814ec89b26a11c4a7c4941bf8f0e6781ae79559d14749ee5def672259792db4587f85f0100c7bb812f933", - "0x9180b8a718b971fd27bc82c8582d19c4b4f012453e8c0ffeeeffe745581fc6c07875ab28be3af3fa3896d19f0c89ac5b", - "0x8b8e1263eb48d0fe304032dd5ea1f30e73f0121265f7458ba9054d3626894e8a5fef665340abd2ede9653045c2665938", - "0x99a2beee4a10b7941c24b2092192faf52b819afd033e4a2de050fd6c7f56d364d0cf5f99764c3357cf32399e60fc5d74", - "0x946a4aad7f8647ea60bee2c5fcdeb6f9a58fb2cfca70c4d10e458027a04846e13798c66506151be3df9454b1e417893f", - "0xa672a88847652d260b5472d6908d1d57e200f1e492d30dd1cecc441cdfc9b76e016d9bab560efd4d7f3c30801de884a9", - "0x9414e1959c156cde1eb24e628395744db75fc24b9df4595350aaad0bc38e0246c9b4148f6443ef68b8e253a4a6bcf11c", - "0x9316e9e4ec5fab4f80d6540df0e3a4774db52f1d759d2e5b5bcd3d7b53597bb007eb1887cb7dc61f62497d51ffc8d996", - "0x902d6d77bb49492c7a00bc4b70277bc28c8bf9888f4307bb017ac75a962decdedf3a4e2cf6c1ea9f9ba551f4610cbbd7", - "0xb07025a18b0e32dd5e12ec6a85781aa3554329ea12c4cd0d3b2c22e43d777ef6f89876dd90a9c8fb097ddf61cf18adc5", - "0xb355a849ad3227caa4476759137e813505ec523cbc2d4105bc7148a4630f9e81918d110479a2d5f5e4cd9ccec9d9d3e3", - "0xb49532cfdf02ee760109881ad030b89c48ee3bb7f219ccafc13c93aead754d29bdafe345be54c482e9d5672bd4505080", - "0x9477802410e263e4f938d57fa8f2a6cac7754c5d38505b73ee35ea3f057aad958cb9722ba6b7b3cfc4524e9ca93f9cdc", - "0x9148ea83b4436339580f3dbc9ba51509e9ab13c03063587a57e125432dd0915f5d2a8f456a68f8fff57d5f08c8f34d6e", - "0xb00b6b5392b1930b54352c02b1b3b4f6186d20bf21698689bbfc7d13e86538a4397b90e9d5c93fd2054640c4dbe52a4f", - "0x926a9702500441243cd446e7cbf15dde16400259726794694b1d9a40263a9fc9e12f7bcbf12a27cb9aaba9e2d5848ddc", - "0xa0c6155f42686cbe7684a1dc327100962e13bafcf3db97971fc116d9f5c0c8355377e3d70979cdbd58fd3ea52440901c", - "0xa277f899f99edb8791889d0817ea6a96c24a61acfda3ad8c3379e7c62b9d4facc4b965020b588651672fd261a77f1bfc", - "0x8f528cebb866b501f91afa50e995234bef5bf20bff13005de99cb51eaac7b4f0bf38580cfd0470de40f577ead5d9ba0f", - "0x963fc03a44e9d502cc1d23250efef44d299befd03b898d07ce63ca607bb474b5cf7c965a7b9b0f32198b04a8393821f7", - "0xab087438d0a51078c378bf4a93bd48ef933ff0f1fa68d02d4460820df564e6642a663b5e50a5fe509527d55cb510ae04", - "0xb0592e1f2c54746bb076be0fa480e1c4bebc4225e1236bcda3b299aa3853e3afb401233bdbcfc4a007b0523a720fbf62", - "0x851613517966de76c1c55a94dc4595f299398a9808f2d2f0a84330ba657ab1f357701d0895f658c18a44cb00547f6f57", - "0xa2fe9a1dd251e72b0fe4db27be508bb55208f8f1616b13d8be288363ec722826b1a1fd729fc561c3369bf13950bf1fd6", - "0xb896cb2bc2d0c77739853bc59b0f89b2e008ba1f701c9cbe3bef035f499e1baee8f0ff1e794854a48c320586a2dfc81a", - "0xa1b60f98e5e5106785a9b81a85423452ee9ef980fa7fa8464f4366e73f89c50435a0c37b2906052b8e58e212ebd366cf", - "0xa853b0ebd9609656636df2e6acd5d8839c0fda56f7bf9288a943b06f0b67901a32b95e016ca8bc99bd7b5eab31347e72", - "0xb290fa4c1346963bd5225235e6bdf7c542174dab4c908ab483d1745b9b3a6015525e398e1761c90e4b49968d05e30eea", - "0xb0f65a33ad18f154f1351f07879a183ad62e5144ad9f3241c2d06533dad09cbb2253949daff1bb02d24d16a3569f7ef0", - "0xa00db59b8d4218faf5aeafcd39231027324408f208ec1f54d55a1c41228b463b88304d909d16b718cfc784213917b71e", - "0xb8d695dd33dc2c3bc73d98248c535b2770ad7fa31aa726f0aa4b3299efb0295ba9b4a51c71d314a4a1bd5872307534d1", - "0xb848057cca2ca837ee49c42b88422303e58ea7d2fc76535260eb5bd609255e430514e927cc188324faa8e657396d63ec", - "0x92677836061364685c2aaf0313fa32322746074ed5666fd5f142a7e8f87135f45cd10e78a17557a4067a51dfde890371", - "0xa854b22c9056a3a24ab164a53e5c5cf388616c33e67d8ebb4590cb16b2e7d88b54b1393c93760d154208b5ca822dc68f", - "0x86fff174920388bfab841118fb076b2b0cdec3fdb6c3d9a476262f82689fb0ed3f1897f7be9dbf0932bb14d346815c63", - "0x99661cf4c94a74e182752bcc4b98a8c2218a8f2765642025048e12e88ba776f14f7be73a2d79bd21a61def757f47f904", - "0x8a8893144d771dca28760cba0f950a5d634195fd401ec8cf1145146286caffb0b1a6ba0c4c1828d0a5480ce49073c64c", - "0x938a59ae761359ee2688571e7b7d54692848eb5dde57ffc572b473001ea199786886f8c6346a226209484afb61d2e526", - "0x923f68a6aa6616714cf077cf548aeb845bfdd78f2f6851d8148cba9e33a374017f2f3da186c39b82d14785a093313222", - "0xac923a93d7da7013e73ce8b4a2b14b8fd0cc93dc29d5de941a70285bdd19be4740fedfe0c56b046689252a3696e9c5bc", - "0xb49b32c76d4ec1a2c68d4989285a920a805993bc6fcce6dacd3d2ddae73373050a5c44ba8422a3781050682fa0ef6ba2", - "0x8a367941c07c3bdca5712524a1411bad7945c7c48ffc7103b1d4dff2c25751b0624219d1ccde8c3f70c465f954be5445", - "0xb838f029df455efb6c530d0e370bbbf7d87d61a9aea3d2fe5474c5fe0a39cf235ceecf9693c5c6c5820b1ba8f820bd31", - "0xa8983b7c715eaac7f13a001d2abc462dfc1559dab4a6b554119c271aa8fe00ffcf6b6949a1121f324d6d26cb877bcbae", - "0xa2afb24ad95a6f14a6796315fbe0d8d7700d08f0cfaf7a2abe841f5f18d4fecf094406cbd54da7232a159f9c5b6e805e", - "0x87e8e95ad2d62f947b2766ff405a23f7a8afba14e7f718a691d95369c79955cdebe24c54662553c60a3f55e6322c0f6f", - "0x87c2cbcecb754e0cc96128e707e5c5005c9de07ffd899efa3437cadc23362f5a1d3fcdd30a1f5bdc72af3fb594398c2a", - "0x91afd6ee04f0496dc633db88b9370d41c428b04fd991002502da2e9a0ef051bcd7b760e860829a44fbe5539fa65f8525", - "0x8c50e5d1a24515a9dd624fe08b12223a75ca55196f769f24748686315329b337efadca1c63f88bee0ac292dd0a587440", - "0x8a07e8f912a38d94309f317c32068e87f68f51bdfa082d96026f5f5f8a2211621f8a3856dda8069386bf15fb2d28c18f", - "0x94ad1dbe341c44eeaf4dc133eed47d8dbfe752575e836c075745770a6679ff1f0e7883b6aa917462993a7f469d74cab5", - "0x8745f8bd86c2bb30efa7efb7725489f2654f3e1ac4ea95bd7ad0f3cfa223055d06c187a16192d9d7bdaea7b050c6a324", - "0x900d149c8d79418cda5955974c450a70845e02e5a4ecbcc584a3ca64d237df73987c303e3eeb79da1af83bf62d9e579f", - "0x8f652ab565f677fb1a7ba03b08004e3cda06b86c6f1b0b9ab932e0834acf1370abb2914c15b0d08327b5504e5990681c", - "0x9103097d088be1f75ab9d3da879106c2f597e2cc91ec31e73430647bdd5c33bcfd771530d5521e7e14df6acda44f38a6", - "0xb0fec7791cfb0f96e60601e1aeced9a92446b61fedab832539d1d1037558612d78419efa87ff5f6b7aab8fd697d4d9de", - "0xb9d2945bdb188b98958854ba287eb0480ef614199c4235ce5f15fc670b8c5ffe8eeb120c09c53ea8a543a022e6a321ac", - "0xa9461bb7d5490973ebaa51afc0bb4a5e42acdccb80e2f939e88b77ac28a98870e103e1042899750f8667a8cc9123bae9", - "0xa37fdf11d4bcb2aed74b9f460a30aa34afea93386fa4cdb690f0a71bc58f0b8df60bec56e7a24f225978b862626fa00e", - "0xa214420e183e03d531cf91661466ea2187d84b6e814b8b20b3730a9400a7d25cf23181bb85589ebc982cec414f5c2923", - "0xad09a45a698a6beb3e0915f540ef16e9af7087f53328972532d6b5dfe98ce4020555ece65c6cbad8bd6be8a4dfefe6fd", - "0xab6742800b02728c92d806976764cb027413d6f86edd08ad8bb5922a2969ee9836878cd39db70db0bd9a2646862acc4f", - "0x974ca9305bd5ea1dc1755dff3b63e8bfe9f744321046c1395659bcea2a987b528e64d5aa96ac7b015650b2253b37888d", - "0x84eee9d6bce039c52c2ebc4fccc0ad70e20c82f47c558098da4be2f386a493cbc76adc795b5488c8d11b6518c2c4fab8", - "0x875d7bda46efcb63944e1ccf760a20144df3b00d53282b781e95f12bfc8f8316dfe6492c2efbf796f1150e36e436e9df", - "0xb68a2208e0c587b5c31b5f6cb32d3e6058a9642e2d9855da4f85566e1412db528475892060bb932c55b3a80877ad7b4a", - "0xba006368ecab5febb6ab348644d9b63de202293085ed468df8bc24d992ae8ce468470aa37f36a73630c789fb9c819b30", - "0x90a196035150846cd2b482c7b17027471372a8ce7d914c4d82b6ea7fa705d8ed5817bd42d63886242585baf7d1397a1c", - "0xa223b4c85e0daa8434b015fd9170b5561fe676664b67064974a1e9325066ecf88fc81f97ab5011c59fad28cedd04b240", - "0x82e8ec43139cf15c6bbeed484b62e06cded8a39b5ce0389e4cbe9c9e9c02f2f0275d8d8d4e8dfec8f69a191bef220408", - "0x81a3fc07a7b68d92c6ee4b6d28f5653ee9ec85f7e2ee1c51c075c1b130a8c5097dc661cf10c5aff1c7114b1a6a19f11a", - "0x8ed2ef8331546d98819a5dd0e6c9f8cb2630d0847671314a28f277faf68da080b53891dd75c82cbcf7788b255490785d", - "0xacecabf84a6f9bbed6b2fc2e7e4b48f02ef2f15e597538a73aea8f98addc6badda15e4695a67ecdb505c1554e8f345ec", - "0xb8f51019b2aa575f8476e03dcadf86cc8391f007e5f922c2a36b2daa63f5a503646a468990cd5c65148d323942193051", - "0xaaa595a84b403ec65729bc1c8055a94f874bf9adddc6c507b3e1f24f79d3ad359595a672b93aab3394db4e2d4a7d8970", - "0x895144c55fcbd0f64d7dd69e6855cfb956e02b5658eadf0f026a70703f3643037268fdd673b0d21b288578a83c6338dd", - "0xa2e92ae6d0d237d1274259a8f99d4ea4912a299816350b876fba5ebc60b714490e198a916e1c38c6e020a792496fa23c", - "0xa45795fda3b5bb0ad1d3c628f6add5b2a4473a1414c1a232e80e70d1cfffd7f8a8d9861f8df2946999d7dbb56bf60113", - "0xb6659bf7f6f2fef61c39923e8c23b8c70e9c903028d8f62516d16755cd3fba2fe41c285aa9432dc75ab08f8a1d8a81fc", - "0xa735609a6bc5bfd85e58234fc439ff1f58f1ff1dd966c5921d8b649e21f006bf2b8642ad8a75063c159aaf6935789293", - "0xa3c622eb387c9d15e7bda2e3e84d007cb13a6d50d655c3f2f289758e49d3b37b9a35e4535d3cc53d8efd51f407281f19", - "0x8afe147b53ad99220f5ef9d763bfc91f9c20caecbcf823564236fb0e6ede49414c57d71eec4772c8715cc65a81af0047", - "0xb5f0203233cf71913951e9c9c4e10d9243e3e4a1f2cb235bf3f42009120ba96e04aa414c9938ea8873b63148478927e8", - "0x93c52493361b458d196172d7ba982a90a4f79f03aa8008edc322950de3ce6acf4c3977807a2ffa9e924047e02072b229", - "0xb9e72b805c8ac56503f4a86c82720afbd5c73654408a22a2ac0b2e5caccdfb0e20b59807433a6233bc97ae58cf14c70a", - "0xaf0475779b5cee278cca14c82da2a9f9c8ef222eb885e8c50cca2315fea420de6e04146590ed0dd5a29c0e0812964df5", - "0xb430ccab85690db02c2d0eb610f3197884ca12bc5f23c51e282bf3a6aa7e4a79222c3d8761454caf55d6c01a327595f9", - "0x830032937418b26ee6da9b5206f3e24dc76acd98589e37937e963a8333e5430abd6ce3dd93ef4b8997bd41440eed75d6", - "0x8820a6d73180f3fe255199f3f175c5eb770461ad5cfdde2fb11508041ed19b8c4ce66ad6ecebf7d7e836cc2318df47ca", - "0xaef1393e7d97278e77bbf52ef6e1c1d5db721ccf75fe753cf47a881fa034ca61eaa5098ee5a344c156d2b14ff9e284ad", - "0x8a4a26c07218948c1196c45d927ef4d2c42ade5e29fe7a91eaebe34a29900072ce5194cf28d51f746f4c4c649daf4396", - "0x84011dc150b7177abdcb715efbd8c201f9cb39c36e6069af5c50a096021768ba40cef45b659c70915af209f904ede3b6", - "0xb1bd90675411389bb66910b21a4bbb50edce5330850c5ab0b682393950124252766fc81f5ecfc72fb7184387238c402e", - "0x8dfdcd30583b696d2c7744655f79809f451a60c9ad5bf1226dc078b19f4585d7b3ef7fa9d54e1ac09520d95cbfd20928", - "0xb351b4dc6d98f75b8e5a48eb7c6f6e4b78451991c9ba630e5a1b9874c15ac450cd409c1a024713bf2cf82dc400e025ef", - "0xa462b8bc97ac668b97b28b3ae24b9f5de60e098d7b23ecb600d2194cd35827fb79f77c3e50d358f5bd72ee83fef18fa0", - "0xa183753265c5f7890270821880cce5f9b2965b115ba783c6dba9769536f57a04465d7da5049c7cf8b3fcf48146173c18", - "0xa8a771b81ed0d09e0da4d79f990e58eabcd2be3a2680419502dd592783fe52f657fe55125b385c41d0ba3b9b9cf54a83", - "0xa71ec577db46011689d073245e3b1c3222a9b1fe6aa5b83629adec5733dd48617ebea91346f0dd0e6cdaa86e4931b168", - "0xa334b8b244f0d598a02da6ae0f918a7857a54dce928376c4c85df15f3b0f2ba3ac321296b8b7c9dd47d770daf16c8f8c", - "0xa29037f8ef925c417c90c4df4f9fb27fb977d04e2b3dd5e8547d33e92ab72e7a00f5461de21e28835319eae5db145eb7", - "0xb91054108ae78b00e3298d667b913ebc44d8f26e531eae78a8fe26fdfb60271c97efb2dee5f47ef5a3c15c8228138927", - "0x926c13efbe90604f6244be9315a34f72a1f8d1aab7572df431998949c378cddbf2fe393502c930fff614ff06ae98a0ce", - "0x995c758fd5600e6537089b1baa4fbe0376ab274ff3e82a17768b40df6f91c2e443411de9cafa1e65ea88fb8b87d504f4", - "0x9245ba307a7a90847da75fca8d77ec03fdfc812c871e7a2529c56a0a79a6de16084258e7a9ac4ae8a3756f394336e21c", - "0x99e0cfa2bb57a7e624231317044c15e52196ecce020db567c8e8cb960354a0be9862ee0c128c60b44777e65ac315e59f", - "0xad4f6b3d27bbbb744126601053c3dc98c07ff0eb0b38a898bd80dce778372846d67e5ab8fb34fb3ad0ef3f235d77ba7f", - "0xa0f12cae3722bbbca2e539eb9cc7614632a2aefe51410430070a12b5bc5314ecec5857b7ff8f41e9980cac23064f7c56", - "0xb487f1bc59485848c98222fd3bc36c8c9bb3d2912e2911f4ceca32c840a7921477f9b1fe00877e05c96c75d3eecae061", - "0xa6033db53925654e18ecb3ce715715c36165d7035db9397087ac3a0585e587998a53973d011ac6d48af439493029cee6", - "0xa6b4d09cd01c70a3311fd131d3710ccf97bde3e7b80efd5a8c0eaeffeb48cca0f951ced905290267b115b06d46f2693b", - "0xa9dff1df0a8f4f218a98b6f818a693fb0d611fed0fc3143537cbd6578d479af13a653a8155e535548a2a0628ae24fa58", - "0xa58e469f65d366b519f9a394cacb7edaddac214463b7b6d62c2dbc1316e11c6c5184ce45c16de2d77f990dcdd8b55430", - "0x989e71734f8119103586dc9a3c5f5033ddc815a21018b34c1f876cdfc112efa868d5751bf6419323e4e59fa6a03ece1c", - "0xa2da00e05036c884369e04cf55f3de7d659cd5fa3f849092b2519dd263694efe0f051953d9d94b7e121f0aee8b6174d7", - "0x968f3c029f57ee31c4e1adea89a7f92e28483af9a74f30fbdb995dc2d40e8e657dff8f8d340d4a92bf65f54440f2859f", - "0x932778df6f60ac1639c1453ef0cbd2bf67592759dcccb3e96dcc743ff01679e4c7dd0ef2b0833dda548d32cb4eba49e2", - "0xa805a31139f8e0d6dae1ac87d454b23a3dc9fc653d4ca18d4f8ebab30fc189c16e73981c2cb7dd6f8c30454a5208109d", - "0xa9ba0991296caa2aaa4a1ceacfb205544c2a2ec97088eace1d84ee5e2767656a172f75d2f0c4e16a3640a0e0dec316e0", - "0xb1e49055c968dced47ec95ae934cf45023836d180702e20e2df57e0f62fb85d7ac60d657ba3ae13b8560b67210449459", - "0xa94e1da570a38809c71e37571066acabff7bf5632737c9ab6e4a32856924bf6211139ab3cedbf083850ff2d0e0c0fcfc", - "0x88ef1bb322000c5a5515b310c838c9af4c1cdbb32eab1c83ac3b2283191cd40e9573747d663763a28dad0d64adc13840", - "0xa987ce205f923100df0fbd5a85f22c9b99b9b9cbe6ddfa8dfda1b8fe95b4f71ff01d6c5b64ca02eb24edb2b255a14ef0", - "0x84fe8221a9e95d9178359918a108de4763ebfa7a6487facb9c963406882a08a9a93f492f8e77cf9e7ea41ae079c45993", - "0xaa1cf3dc7c5dcfa15bbbc811a4bb6dbac4fba4f97fb1ed344ab60264d7051f6eef19ea9773441d89929ee942ed089319", - "0x8f6a7d610d59d9f54689bbe6a41f92d9f6096cde919c1ab94c3c7fcecf0851423bc191e5612349e10f855121c0570f56", - "0xb5af1fa7894428a53ea520f260f3dc3726da245026b6d5d240625380bfb9c7c186df0204bb604efac5e613a70af5106e", - "0xa5bce6055ff812e72ce105f147147c7d48d7a2313884dd1f488b1240ee320f13e8a33f5441953a8e7a3209f65b673ce1", - "0xb9b55b4a1422677d95821e1d042ab81bbf0bf087496504021ec2e17e238c2ca6b44fb3b635a5c9eac0871a724b8d47c3", - "0x941c38e533ce4a673a3830845b56786585e5fe49c427f2e5c279fc6db08530c8f91db3e6c7822ec6bb4f956940052d18", - "0xa38e191d66c625f975313c7007bbe7431b5a06ed2da1290a7d5d0f2ec73770d476efd07b8e632de64597d47df175cbb0", - "0x94ba76b667abf055621db4c4145d18743a368d951565632ed4e743dd50dd3333507c0c34f286a5c5fdbf38191a2255cd", - "0xa5ca38c60be5602f2bfa6e00c687ac96ac36d517145018ddbee6f12eb0faa63dd57909b9eeed26085fe5ac44e55d10ab", - "0xb00fea3b825e60c1ed1c5deb4b551aa65a340e5af36b17d5262c9cd2c508711e4dc50dc2521a2c16c7c901902266e64a", - "0x971b86fc4033485e235ccb0997a236206ba25c6859075edbcdf3c943116a5030b7f75ebca9753d863a522ba21a215a90", - "0xb3b31f52370de246ee215400975b674f6da39b2f32514fe6bd54e747752eedca22bb840493b44a67df42a3639c5f901f", - "0xaffbbfac9c1ba7cbfa1839d2ae271dd6149869b75790bf103230637da41857fc326ef3552ff31c15bda0694080198143", - "0xa95d42aa7ef1962520845aa3688f2752d291926f7b0d73ea2ee24f0612c03b43f2b0fe3c9a9a99620ffc8d487b981bc2", - "0x914a266065caf64985e8c5b1cb2e3f4e3fe94d7d085a1881b1fefa435afef4e1b39a98551d096a62e4f5cc1a7f0fdc2e", - "0x81a0b4a96e2b75bc1bf2dbd165d58d55cfd259000a35504d1ffb18bc346a3e6f07602c683723864ffb980f840836fd8d", - "0x91c1556631cddd4c00b65b67962b39e4a33429029d311c8acf73a18600e362304fb68bccb56fde40f49e95b7829e0b87", - "0x8befbacc19e57f7c885d1b7a6028359eb3d80792fe13b92a8400df21ce48deb0bb60f2ddb50e3d74f39f85d7eab23adc", - "0x92f9458d674df6e990789690ec9ca73dacb67fc9255b58c417c555a8cc1208ace56e8e538f86ba0f3615573a0fbac00d", - "0xb4b1b3062512d6ae7417850c08c13f707d5838e43d48eb98dd4621baf62eee9e82348f80fe9b888a12874bfa538771f8", - "0xa13c4a3ac642ede37d9c883f5319e748d2b938f708c9d779714108a449b343f7b71a6e3ef4080fee125b416762920273", - "0xaf44983d5fc8cceee0551ef934e6e653f2d3efa385e5c8a27a272463a6f333e290378cc307c2b664eb923c78994e706e", - "0xa389fd6c59fe2b4031cc244e22d3991e541bd203dd5b5e73a6159e72df1ab41d49994961500dcde7989e945213184778", - "0x8d2141e4a17836c548de9598d7b298b03f0e6c73b7364979a411c464e0628e21cff6ac3d6decdba5d1c4909eff479761", - "0x980b22ef53b7bdf188a3f14bc51b0dbfdf9c758826daa3cbc1e3986022406a8aa9a6a79e400567120b88c67faa35ce5f", - "0xa28882f0a055f96df3711de5d0aa69473e71245f4f3e9aa944e9d1fb166e02caa50832e46da6d3a03b4801735fd01b29", - "0x8db106a37d7b88f5d995c126abb563934dd8de516af48e85695d02b1aea07f79217e3cdd03c6f5ca57421830186c772b", - "0xb5a7e50da0559a675c472f7dfaee456caab6695ab7870541b2be8c2b118c63752427184aad81f0e1afc61aef1f28c46f", - "0x9962118780e20fe291d10b64f28d09442a8e1b5cffd0f3dd68d980d0614050a626c616b44e9807fbee7accecae00686a", - "0xb38ddf33745e8d2ad6a991aefaf656a33c5f8cbe5d5b6b6fd03bd962153d8fd0e01b5f8f96d80ae53ab28d593ab1d4e7", - "0x857dc12c0544ff2c0c703761d901aba636415dee45618aba2e3454ff9cbc634a85c8b05565e88520ff9be2d097c8b2b1", - "0xa80d465c3f8cc63af6d74a6a5086b626c1cb4a8c0fee425964c3bd203d9d7094e299f81ce96d58afc20c8c9a029d9dae", - "0x89e1c8fbde8563763be483123a3ed702efac189c6d8ab4d16c85e74bbaf856048cc42d5d6e138633a38572ba5ec3f594", - "0x893a594cf495535f6d216508f8d03c317dcf03446668cba688da90f52d0111ac83d76ad09bf5ea47056846585ee5c791", - "0xaadbd8be0ae452f7f9450c7d2957598a20cbf10139a4023a78b4438172d62b18b0de39754dd2f8862dbd50a3a0815e53", - "0xae7d39670ecca3eb6db2095da2517a581b0e8853bdfef619b1fad9aacd443e7e6a40f18209fadd44038a55085c5fe8b2", - "0x866ef241520eacb6331593cfcb206f7409d2f33d04542e6e52cba5447934e02d44c471f6c9a45963f9307e9809ab91d9", - "0xb1a09911ad3864678f7be79a9c3c3eb5c84a0a45f8dcb52c67148f43439aeaaa9fd3ed3471276b7e588b49d6ebe3033a", - "0xadd07b7f0dbb34049cd8feeb3c18da5944bf706871cfd9f14ff72f6c59ad217ebb1f0258b13b167851929387e4e34cfe", - "0xae048892d5c328eefbdd4fba67d95901e3c14d974bfc0a1fc68155ca9f0d59e61d7ba17c6c9948b120cf35fd26e6fee9", - "0x9185b4f3b7da0ddb4e0d0f09b8a9e0d6943a4611e43f13c3e2a767ed8592d31e0ba3ebe1914026a3627680274291f6e5", - "0xa9c022d4e37b0802284ce3b7ee9258628ab4044f0db4de53d1c3efba9de19d15d65cc5e608dbe149c21c2af47d0b07b5", - "0xb24dbd5852f8f24921a4e27013b6c3fa8885b973266cb839b9c388efad95821d5d746348179dcc07542bd0d0aefad1ce", - "0xb5fb4f279300876a539a27a441348764908bc0051ebd66dc51739807305e73db3d2f6f0f294ffb91b508ab150eaf8527", - "0xace50841e718265b290c3483ed4b0fdd1175338c5f1f7530ae9a0e75d5f80216f4de37536adcbc8d8c95982e88808cd0", - "0xb19cadcde0f63bd1a9c24bd9c2806f53c14c0b9735bf351601498408ba503ddbd2037c891041cbba47f58b8c483f3b21", - "0xb6061e63558d312eb891b97b39aa552fa218568d79ee26fe6dd5b864aea9e3216d8f2e2f3b093503be274766dac41426", - "0x89730fdb2876ab6f0fe780d695f6e12090259027e789b819956d786e977518057e5d1d7f5ab24a3ae3d5d4c97773bd2b", - "0xb6fa841e81f9f2cad0163a02a63ae96dc341f7ae803b616efc6e1da2fbea551c1b96b11ad02c4afbdf6d0cc9f23da172", - "0x8fb66187182629c861ddb6896d7ed3caf2ad050c3dba8ab8eb0d7a2c924c3d44c48d1a148f9e33fb1f061b86972f8d21", - "0x86022ac339c1f84a7fa9e05358c1a5b316b4fc0b83dbe9c8c7225dc514f709d66490b539359b084ce776e301024345fa", - "0xb50b9c321468da950f01480bb62b6edafd42f83c0001d6e97f2bd523a1c49a0e8574fb66380ea28d23a7c4d54784f9f0", - "0xa31c05f7032f30d1dac06678be64d0250a071fd655e557400e4a7f4c152be4d5c7aa32529baf3e5be7c4bd49820054f6", - "0xb95ac0848cd322684772119f5b682d90a66bbf9dac411d9d86d2c34844bbd944dbaf8e47aa41380455abd51687931a78", - "0xae4a6a5ce9553b65a05f7935e61e496a4a0f6fd8203367a2c627394c9ce1e280750297b74cdc48fd1d9a31e93f97bef4", - "0xa22daf35f6e9b05e52e0b07f7bd1dbbebd2c263033fb0e1b2c804e2d964e2f11bc0ece6aca6af079dd3a9939c9c80674", - "0x902150e0cb1f16b9b59690db35281e28998ce275acb313900da8b2d8dfd29fa1795f8ca3ff820c31d0697de29df347c1", - "0xb17b5104a5dc665cdd7d47e476153d715eb78c6e5199303e4b5445c21a7fa7cf85fe7cfd08d7570f4e84e579b005428c", - "0xa03f49b81c15433f121680aa02d734bb9e363af2156654a62bcb5b2ba2218398ccb0ff61104ea5d7df5b16ea18623b1e", - "0x802101abd5d3c88876e75a27ffc2f9ddcce75e6b24f23dba03e5201281a7bd5cc7530b6a003be92d225093ca17d3c3bb", - "0xa4d183f63c1b4521a6b52226fc19106158fc8ea402461a5cccdaa35fee93669df6a8661f45c1750cd01308149b7bf08e", - "0x8d17c22e0c8403b69736364d460b3014775c591032604413d20a5096a94d4030d7c50b9fe3240e31d0311efcf9816a47", - "0x947225acfcce5992eab96276f668c3cbe5f298b90a59f2bb213be9997d8850919e8f496f182689b5cbd54084a7332482", - "0x8df6f4ed216fc8d1905e06163ba1c90d336ab991a18564b0169623eb39b84e627fa267397da15d3ed754d1f3423bff07", - "0x83480007a88f1a36dea464c32b849a3a999316044f12281e2e1c25f07d495f9b1710b4ba0d88e9560e72433addd50bc2", - "0xb3019d6e591cf5b33eb972e49e06c6d0a82a73a75d78d383dd6f6a4269838289e6e07c245f54fed67f5c9bb0fd5e1c5f", - "0x92e8ce05e94927a9fb02debadb99cf30a26172b2705003a2c0c47b3d8002bf1060edb0f6a5750aad827c98a656b19199", - "0xac2aff801448dbbfc13cca7d603fd9c69e82100d997faf11f465323b97255504f10c0c77401e4d1890339d8b224f5803", - "0xb0453d9903d08f508ee27e577445dc098baed6cde0ac984b42e0f0efed62760bd58d5816cf1e109d204607b7b175e30c", - "0xae68dc4ba5067e825d46d2c7c67f1009ceb49d68e8d3e4c57f4bcd299eb2de3575d42ea45e8722f8f28497a6e14a1cfe", - "0xb22486c2f5b51d72335ce819bbafb7fa25eb1c28a378a658f13f9fc79cd20083a7e573248d911231b45a5cf23b561ca7", - "0x89d1201d1dbd6921867341471488b4d2fd0fc773ae1d4d074c78ae2eb779a59b64c00452c2a0255826fca6b3d03be2b1", - "0xa2998977c91c7a53dc6104f5bc0a5b675e5350f835e2f0af69825db8af4aeb68435bdbcc795f3dd1f55e1dd50bc0507f", - "0xb0be4937a925b3c05056ed621910d535ccabf5ab99fd3b9335080b0e51d9607d0fd36cb5781ff340018f6acfca4a9736", - "0xaea145a0f6e0ba9df8e52e84bb9c9de2c2dc822f70d2724029b153eb68ee9c17de7d35063dcd6a39c37c59fdd12138f7", - "0x91cb4545d7165ee8ffbc74c874baceca11fdebbc7387908d1a25877ca3c57f2c5def424dab24148826832f1e880bede0", - "0xb3b579cb77573f19c571ad5eeeb21f65548d7dff9d298b8d7418c11f3e8cd3727c5b467f013cb87d6861cfaceee0d2e3", - "0xb98a1eeec2b19fecc8378c876d73645aa52fb99e4819903735b2c7a885b242787a30d1269a04bfb8573d72d9bbc5f0f0", - "0x940c1f01ed362bd588b950c27f8cc1d52276c71bb153d47f07ec85b038c11d9a8424b7904f424423e714454d5e80d1cd", - "0xaa343a8ecf09ce11599b8cf22f7279cf80f06dbf9f6d62cb05308dbbb39c46fd0a4a1240b032665fbb488a767379b91b", - "0x87c3ac72084aca5974599d3232e11d416348719e08443acaba2b328923af945031f86432e170dcdd103774ec92e988c9", - "0x91d6486eb5e61d2b9a9e742c20ec974a47627c6096b3da56209c2b4e4757f007e793ebb63b2b246857c9839b64dc0233", - "0xaebcd3257d295747dd6fc4ff910d839dd80c51c173ae59b8b2ec937747c2072fa85e3017f9060aa509af88dfc7529481", - "0xb3075ba6668ca04eff19efbfa3356b92f0ab12632dcda99cf8c655f35b7928c304218e0f9799d68ef9f809a1492ff7db", - "0x93ba7468bb325639ec2abd4d55179c69fd04eaaf39fc5340709227bbaa4ad0a54ea8b480a1a3c8d44684e3be0f8d1980", - "0xa6aef86c8c0d92839f38544d91b767c582568b391071228ff5a5a6b859c87bf4f81a7d926094a4ada1993ddbd677a920", - "0x91dcd6d14207aa569194aa224d1e5037b999b69ade52843315ca61ba26abe9a76412c9e88259bc5cf5d7b95b97d9c3bc", - "0xb3b483d31c88f78d49bd065893bc1e3d2aa637e27dedb46d9a7d60be7660ce7a10aaaa7deead362284a52e6d14021178", - "0x8e5730070acf8371461ef301cc4523e8e672aa0e3d945d438a0e0aa6bdf8cb9c685dcf38df429037b0c8aff3955c6f5b", - "0xb8c6d769890a8ee18dc4f9e917993315877c97549549b34785a92543cbeec96a08ae3a28d6e809c4aacd69de356c0012", - "0x95ca86cd384eaceaa7c077c5615736ca31f36824bd6451a16142a1edc129fa42b50724aeed7c738f08d7b157f78b569e", - "0x94df609c6d71e8eee7ab74226e371ccc77e01738fe0ef1a6424435b4570fe1e5d15797b66ed0f64eb88d4a3a37631f0e", - "0x89057b9783212add6a0690d6bb99097b182738deff2bd9e147d7fd7d6c8eacb4c219923633e6309ad993c24572289901", - "0x83a0f9f5f265c5a0e54defa87128240235e24498f20965009fef664f505a360b6fb4020f2742565dfc7746eb185bcec0", - "0x91170da5306128931349bc3ed50d7df0e48a68b8cc8420975170723ac79d8773e4fa13c5f14dc6e3fafcad78379050b1", - "0xb7178484d1b55f7e56a4cc250b6b2ec6040437d96bdfddfa7b35ed27435860f3855c2eb86c636f2911b012eb83b00db8", - "0xac0b00c4322d1e4208e09cd977b4e54d221133ff09551f75b32b0b55d0e2be80941dda26257b0e288c162e63c7e9cf68", - "0x9690ed9e7e53ed37ff362930e4096b878b12234c332fd19d5d064824084245952eda9f979e0098110d6963e468cf513e", - "0xb6fa547bb0bb83e5c5be0ed462a8783fba119041c136a250045c09d0d2af330c604331e7de960df976ff76d67f8000cd", - "0x814603907c21463bcf4e59cfb43066dfe1a50344ae04ef03c87c0f61b30836c3f4dea0851d6fa358c620045b7f9214c8", - "0x9495639e3939fad2a3df00a88603a5a180f3c3a0fe4d424c35060e2043e0921788003689887b1ed5be424d9a89bb18bb", - "0xaba4c02d8d57f2c92d5bc765885849e9ff8393d6554f5e5f3e907e5bfac041193a0d8716d7861104a4295d5a03c36b03", - "0x8ead0b56c1ca49723f94a998ba113b9058059321da72d9e395a667e6a63d5a9dac0f5717cec343f021695e8ced1f72af", - "0xb43037f7e3852c34ed918c5854cd74e9d5799eeddfe457d4f93bb494801a064735e326a76e1f5e50a339844a2f4a8ec9", - "0x99db8422bb7302199eb0ff3c3d08821f8c32f53a600c5b6fb43e41205d96adae72be5b460773d1280ad1acb806af9be8", - "0x8a9be08eae0086c0f020838925984df345c5512ff32e37120b644512b1d9d4fecf0fd30639ca90fc6cf334a86770d536", - "0x81b43614f1c28aa3713a309a88a782fb2bdfc4261dd52ddc204687791a40cf5fd6a263a8179388596582cccf0162efc2", - "0xa9f3a8b76912deb61d966c75daf5ddb868702ebec91bd4033471c8e533183df548742a81a2671de5be63a502d827437d", - "0x902e2415077f063e638207dc7e14109652e42ab47caccd6204e2870115791c9defac5425fd360b37ac0f7bd8fe7011f8", - "0xaa18e4fdc1381b59c18503ae6f6f2d6943445bd00dd7d4a2ad7e5adad7027f2263832690be30d456e6d772ad76f22350", - "0xa348b40ba3ba7d81c5d4631f038186ebd5e5f314f1ea737259151b07c3cc8cf0c6ed4201e71bcc1c22fefda81a20cde6", - "0xaa1306f7ac1acbfc47dc6f7a0cb6d03786cec8c8dc8060388ccda777bca24bdc634d03e53512c23dba79709ff64f8620", - "0x818ccfe46e700567b7f3eb400e5a35f6a5e39b3db3aa8bc07f58ace35d9ae5a242faf8dbccd08d9a9175bbce15612155", - "0xb7e3da2282b65dc8333592bb345a473f03bd6df69170055fec60222de9897184536bf22b9388b08160321144d0940279", - "0xa4d976be0f0568f4e57de1460a1729129252b44c552a69fceec44e5b97c96c711763360d11f9e5bf6d86b4976bf40d69", - "0x85d185f0397c24c2b875b09b6328a23b87982b84ee880f2677a22ff4c9a1ba9f0fea000bb3f7f66375a00d98ebafce17", - "0xb4ccbb8c3a2606bd9b87ce022704663af71d418351575f3b350d294f4efc68c26f9a2ce49ff81e6ff29c3b63d746294e", - "0x93ffd3265fddb63724dfde261d1f9e22f15ecf39df28e4d89e9fea03221e8e88b5dd9b77628bacaa783c6f91802d47cc", - "0xb1fd0f8d7a01378e693da98d03a2d2fda6b099d03454b6f2b1fa6472ff6bb092751ce6290059826b74ac0361eab00e1e", - "0xa89f440c71c561641589796994dd2769616b9088766e983c873fae0716b95c386c8483ab8a4f367b6a68b72b7456dd32", - "0xaf4fe92b01d42d03dd5d1e7fa55e96d4bbcb7bf7d4c8c197acd16b3e0f3455807199f683dcd263d74547ef9c244b35cc", - "0xa8227f6e0a344dfe76bfbe7a1861be32c4f4bed587ccce09f9ce2cf481b2dda8ae4f566154bc663d15f962f2d41761bd", - "0xa7b361663f7495939ed7f518ba45ea9ff576c4e628995b7aea026480c17a71d63fc2c922319f0502eb7ef8f14a406882", - "0x8ddcf382a9f39f75777160967c07012cfa89e67b19714a7191f0c68eaf263935e5504e1104aaabd0899348c972a8d3c6", - "0x98c95b9f6f5c91f805fb185eedd06c6fc4457d37dd248d0be45a6a168a70031715165ea20606245cbdf8815dc0ac697f", - "0x805b44f96e001e5909834f70c09be3efcd3b43632bcac5b6b66b6d227a03a758e4b1768ce2a723045681a1d34562aaeb", - "0xb0e81b07cdc45b3dca60882676d9badb99f25c461b7efe56e3043b80100bb62d29e1873ae25eb83087273160ece72a55", - "0xb0c53f0abe78ee86c7b78c82ae1f7c070bb0b9c45c563a8b3baa2c515d482d7507bb80771e60b38ac13f78b8af92b4a9", - "0xa7838ef6696a9e4d2e5dfd581f6c8d6a700467e8fd4e85adabb5f7a56f514785dd4ab64f6f1b48366f7d94728359441b", - "0x88c76f7700a1d23c30366a1d8612a796da57b2500f97f88fdf2d76b045a9d24e7426a8ffa2f4e86d3046937a841dad58", - "0xad8964baf98c1f02e088d1d9fcb3af6b1dfa44cdfe0ed2eae684e7187c33d3a3c28c38e8f4e015f9c04d451ed6f85ff6", - "0x90e9d00a098317ececaa9574da91fc149eda5b772dedb3e5a39636da6603aa007804fa86358550cfeff9be5a2cb7845e", - "0xa56ff4ddd73d9a6f5ab23bb77efa25977917df63571b269f6a999e1ad6681a88387fcc4ca3b26d57badf91b236503a29", - "0x97ad839a6302c410a47e245df84c01fb9c4dfef86751af3f9340e86ff8fc3cd52fa5ff0b9a0bd1d9f453e02ca80658a6", - "0xa4c8c44cbffa804129e123474854645107d1f0f463c45c30fd168848ebea94880f7c0c5a45183e9eb837f346270bdb35", - "0xa72e53d0a1586d736e86427a93569f52edd2f42b01e78aee7e1961c2b63522423877ae3ac1227a2cf1e69f8e1ff15bc3", - "0x8559f88a7ef13b4f09ac82ae458bbae6ab25671cfbf52dae7eac7280d6565dd3f0c3286aec1a56a8a16dc3b61d78ce47", - "0x8221503f4cdbed550876c5dc118a3f2f17800c04e8be000266633c83777b039a432d576f3a36c8a01e8fd18289ebc10b", - "0x99bfbe5f3e46d4d898a578ba86ed26de7ed23914bd3bcdf3c791c0bcd49398a52419077354a5ab75cea63b6c871c6e96", - "0xaa134416d8ff46f2acd866c1074af67566cfcf4e8be8d97329dfa0f603e1ff208488831ce5948ac8d75bfcba058ddcaa", - "0xb02609d65ebfe1fe8e52f21224a022ea4b5ea8c1bd6e7b9792eed8975fc387cdf9e3b419b8dd5bcce80703ab3a12a45f", - "0xa4f14798508698fa3852e5cac42a9db9797ecee7672a54988aa74037d334819aa7b2ac7b14efea6b81c509134a6b7ad2", - "0x884f01afecbcb987cb3e7c489c43155c416ed41340f61ecb651d8cba884fb9274f6d9e7e4a46dd220253ae561614e44c", - "0xa05523c9e71dce1fe5307cc71bd721feb3e1a0f57a7d17c7d1c9fb080d44527b7dbaa1f817b1af1c0b4322e37bc4bb1e", - "0x8560aec176a4242b39f39433dd5a02d554248c9e49d3179530815f5031fee78ba9c71a35ceeb2b9d1f04c3617c13d8f0", - "0x996aefd402748d8472477cae76d5a2b92e3f092fc834d5222ae50194dd884c9fb8b6ed8e5ccf8f6ed483ddbb4e80c747", - "0x8fd09900320000cbabc40e16893e2fcf08815d288ec19345ad7b6bb22f7d78a52b6575a3ca1ca2f8bc252d2eafc928ec", - "0x939e51f73022bc5dc6862a0adf8fb8a3246b7bfb9943cbb4b27c73743926cc20f615a036c7e5b90c80840e7f1bfee0e7", - "0xa0a6258700cadbb9e241f50766573bf9bdb7ad380b1079dc3afb4054363d838e177b869cad000314186936e40359b1f2", - "0x972699a4131c8ed27a2d0e2104d54a65a7ff1c450ad9da3a325c662ab26869c21b0a84d0700b98c8b5f6ce3b746873d7", - "0xa454c7fe870cb8aa6491eafbfb5f7872d6e696033f92e4991d057b59d70671f2acdabef533e229878b60c7fff8f748b1", - "0xa167969477214201f09c79027b10221e4707662e0c0fde81a0f628249f2f8a859ce3d30a7dcc03b8ecca8f7828ad85c7", - "0x8ff6b7265175beb8a63e1dbf18c9153fb2578c207c781282374f51b40d57a84fd2ef2ea2b9c6df4a54646788a62fd17f", - "0xa3d7ebeccde69d73d8b3e76af0da1a30884bb59729503ff0fb0c3bccf9221651b974a6e72ea33b7956fc3ae758226495", - "0xb71ef144c9a98ce5935620cb86c1590bd4f48e5a2815d25c0cdb008fde628cf628c31450d3d4f67abbfeb16178a74cfd", - "0xb5e0a16d115134f4e2503990e3f2035ed66b9ccf767063fe6747870d97d73b10bc76ed668550cb82eedc9a2ca6f75524", - "0xb30ffaaf94ee8cbc42aa2c413175b68afdb207dbf351fb20be3852cb7961b635c22838da97eaf43b103aff37e9e725cc", - "0x98aa7d52284f6c1f22e272fbddd8c8698cf8f5fbb702d5de96452141fafb559622815981e50b87a72c2b1190f59a7deb", - "0x81fbacda3905cfaf7780bb4850730c44166ed26a7c8d07197a5d4dcd969c09e94a0461638431476c16397dd7bdc449f9", - "0x95e47021c1726eac2e5853f570d6225332c6e48e04c9738690d53e07c6b979283ebae31e2af1fc9c9b3e59f87e5195b1", - "0xac024a661ba568426bb8fce21780406537f518075c066276197300841e811860696f7588188bc01d90bace7bc73d56e3", - "0xa4ebcaf668a888dd404988ab978594dee193dad2d0aec5cdc0ccaf4ec9a7a8228aa663db1da8ddc52ec8472178e40c32", - "0xa20421b8eaf2199d93b083f2aff37fb662670bd18689d046ae976d1db1fedd2c2ff897985ecc6277b396db7da68bcb27", - "0x8bc33d4b40197fd4d49d1de47489d10b90d9b346828f53a82256f3e9212b0cbc6930b895e879da9cec9fedf026aadb3e", - "0xaaafdd1bec8b757f55a0433eddc0a39f818591954fd4e982003437fcceb317423ad7ee74dbf17a2960380e7067a6b4e2", - "0xaad34277ebaed81a6ec154d16736866f95832803af28aa5625bf0461a71d02b1faba02d9d9e002be51c8356425a56867", - "0x976e9c8b150d08706079945bd0e84ab09a648ecc6f64ded9eb5329e57213149ae409ae93e8fbd8eda5b5c69f5212b883", - "0x8097fae1653247d2aed4111533bc378171d6b2c6d09cbc7baa9b52f188d150d645941f46d19f7f5e27b7f073c1ebd079", - "0x83905f93b250d3184eaba8ea7d727c4464b6bdb027e5cbe4f597d8b9dc741dcbea709630bd4fd59ce24023bec32fc0f3", - "0x8095030b7045cff28f34271386e4752f9a9a0312f8df75de4f424366d78534be2b8e1720a19cb1f9a2d21105d790a225", - "0xa7b7b73a6ae2ed1009c49960374b0790f93c74ee03b917642f33420498c188a169724945a975e5adec0a1e83e07fb1b2", - "0x856a41c54df393b6660b7f6354572a4e71c8bfca9cabaffb3d4ef2632c015e7ee2bc10056f3eccb3dbed1ad17d939178", - "0xa8f7a55cf04b38cd4e330394ee6589da3a07dc9673f74804fdf67b364e0b233f14aec42e783200a2e4666f7c5ff62490", - "0x82c529f4e543c6bca60016dc93232c115b359eaee2798a9cf669a654b800aafe6ab4ba58ea8b9cdda2b371c8d62fa845", - "0x8caab020c1baddce77a6794113ef1dfeafc5f5000f48e97f4351b588bf02f1f208101745463c480d37f588d5887e6d8c", - "0x8fa91b3cc400f48b77b6fd77f3b3fbfb3f10cdff408e1fd22d38f77e087b7683adad258804409ba099f1235b4b4d6fea", - "0x8aa02787663d6be9a35677d9d8188b725d5fcd770e61b11b64e3def8808ea5c71c0a9afd7f6630c48634546088fcd8e2", - "0xb5635b7b972e195cab878b97dea62237c7f77eb57298538582a330b1082f6207a359f2923864630136d8b1f27c41b9aa", - "0x8257bb14583551a65975946980c714ecd6e5b629672bb950b9caacd886fbd22704bc9e3ba7d30778adab65dc74f0203a", - "0xab5fe1cd12634bfa4e5c60d946e2005cbd38f1063ec9a5668994a2463c02449a0a185ef331bd86b68b6e23a8780cb3ba", - "0xa7d3487da56cda93570cc70215d438204f6a2709bfb5fda6c5df1e77e2efc80f4235c787e57fbf2c74aaff8cbb510a14", - "0xb61cff7b4c49d010e133319fb828eb900f8a7e55114fc86b39c261a339c74f630e1a7d7e1350244ada566a0ff3d46c4b", - "0x8d4d1d55d321d278db7a85522ccceca09510374ca81d4d73e3bb5249ace7674b73900c35a531ec4fa6448fabf7ad00dc", - "0x966492248aee24f0f56c8cfca3c8ec6ba3b19abb69ae642041d4c3be8523d22c65c4dafcab4c58989ccc4e0bd2f77919", - "0xb20c320a90cb220b86e1af651cdc1e21315cd215da69f6787e28157172f93fc8285dcd59b039c626ed8ca4633cba1a47", - "0xaae9e6b22f018ceb5c0950210bb8182cb8cb61014b7e14581a09d36ebd1bbfebdb2b82afb7fdb0cf75e58a293d9c456d", - "0x875547fb67951ad37b02466b79f0c9b985ccbc500cfb431b17823457dc79fb9597ec42cd9f198e15523fcd88652e63a4", - "0x92afce49773cb2e20fb21e4f86f18e0959ebb9c33361547ddb30454ee8e36b1e234019cbdca0e964cb292f7f77df6b90", - "0x8af85343dfe1821464c76ba11c216cbef697b5afc69c4d821342e55afdac047081ec2e3f7b09fc14b518d9a23b78c003", - "0xb7de4a1648fd63f3a918096ea669502af5357438e69dac77cb8102b6e6c15c76e033cfaa80dafc806e535ede5c1a20aa", - "0xac80e9b545e8bd762951d96c9ce87f629d01ffcde07efc2ef7879ca011f1d0d8a745abf26c9d452541008871304fac00", - "0xa4cf0f7ed724e481368016c38ea5816698a5f68eb21af4d3c422d2ba55f96a33e427c2aa40de1b56a7cfac7f7cf43ab0", - "0x899b0a678bb2db2cae1b44e75a661284844ebcdd87abf308fedeb2e4dbe5c5920c07db4db7284a7af806a2382e8b111a", - "0xaf0588a2a4afce2b1b13c1230816f59e8264177e774e4a341b289a101dcf6af813638fed14fb4d09cb45f35d5d032609", - "0xa4b8df79e2be76e9f5fc5845f06fe745a724cf37c82fcdb72719b77bdebea3c0e763f37909373e3a94480cc5e875cba0", - "0x83e42c46d88930c8f386b19fd999288f142d325e2ebc86a74907d6d77112cb0d449bc511c95422cc810574031a8cbba9", - "0xb5e39534070de1e5f6e27efbdd3dc917d966c2a9b8cf2d893f964256e95e954330f2442027dc148c776d63a95bcde955", - "0x958607569dc28c075e658cd4ae3927055c6bc456eef6212a6fea8205e48ed8777a8064f584cda38fe5639c371e2e7fba", - "0x812adf409fa63575113662966f5078a903212ffb65c9b0bbe62da0f13a133443a7062cb8fd70f5e5dd5559a32c26d2c8", - "0xa679f673e5ce6a3cce7fa31f22ee3785e96bcb55e5a776e2dd3467bef7440e3555d1a9b87cb215e86ee9ed13a090344b", - "0xafedbb34508b159eb25eb2248d7fe328f86ef8c7d84c62d5b5607d74aae27cc2cc45ee148eb22153b09898a835c58df4", - "0xb75505d4f6b67d31e665cfaf5e4acdb5838ae069166b7fbcd48937c0608a59e40a25302fcc1873d2e81c1782808c70f0", - "0xb62515d539ec21a155d94fc00ea3c6b7e5f6636937bce18ed5b618c12257fb82571886287fd5d1da495296c663ebc512", - "0xab8e1a9446bbdd588d1690243b1549d230e6149c28f59662b66a8391a138d37ab594df38e7720fae53217e5c3573b5be", - "0xb31e8abf4212e03c3287bb2c0a153065a7290a16764a0bac8f112a72e632185a654bb4e88fdd6053e6c7515d9719fadb", - "0xb55165477fe15b6abd2d0f4fddaa9c411710dcc4dd712daba3d30e303c9a3ee5415c256f9dc917ecf18c725b4dbab059", - "0xa0939d4f57cacaae549b78e87cc234de4ff6a35dc0d9cd5d7410abc30ebcd34c135e008651c756e5a9d2ca79c40ef42b", - "0x8cf10e50769f3443340844aad4d56ec790850fed5a41fcbd739abac4c3015f0a085a038fbe7fae9f5ad899cce5069f6b", - "0x924055e804d82a99ea4bb160041ea4dc14b568abf379010bc1922fde5d664718c31d103b8b807e3a1ae809390e708c73", - "0x8ec0f9d26f71b0f2e60a179e4fd1778452e2ffb129d50815e5d7c7cb9415fa69ae5890578086e8ef6bfde35ad2a74661", - "0x98c7f12b15ec4426b59f737f73bf5faea4572340f4550b7590dfb7f7ffedb2372e3e555977c63946d579544c53210ad0", - "0x8a935f7a955c78f69d66f18eee0092e5e833fa621781c9581058e219af4d7ceee48b84e472e159dda6199715fb2f9acf", - "0xb78d4219f95a2dbfaa7d0c8a610c57c358754f4f43c2af312ab0fe8f10a5f0177e475332fb8fd23604e474fc2abeb051", - "0x8d086a14803392b7318c28f1039a17e3cfdcece8abcaca3657ec3d0ac330842098a85c0212f889fabb296dfb133ce9aa", - "0xa53249f417aac82f2c2a50c244ce21d3e08a5e5a8bd33bec2a5ab0d6cd17793e34a17edfa3690899244ce201e2fb9986", - "0x8619b0264f9182867a1425be514dc4f1ababc1093138a728a28bd7e4ecc99b9faaff68c23792264bc6e4dce5f52a5c52", - "0x8c171edbbbde551ec19e31b2091eb6956107dd9b1f853e1df23bff3c10a3469ac77a58335eee2b79112502e8e163f3de", - "0xa9d19ec40f0ca07c238e9337c6d6a319190bdba2db76fb63902f3fb459aeeb50a1ac30db5b25ee1b4201f3ca7164a7f4", - "0xb9c6ec14b1581a03520b8d2c1fbbc31fb8ceaef2c0f1a0d0080b6b96e18442f1734bea7ef7b635d787c691de4765d469", - "0x8cb437beb4cfa013096f40ccc169a713dc17afee6daa229a398e45fd5c0645a9ad2795c3f0cd439531a7151945d7064d", - "0xa6e8740cc509126e146775157c2eb278003e5bb6c48465c160ed27888ca803fa12eee1f6a8dd7f444f571664ed87fdc1", - "0xb75c1fecc85b2732e96b3f23aefb491dbd0206a21d682aee0225838dc057d7ed3b576176353e8e90ae55663f79e986e4", - "0xad8d249b0aea9597b08358bce6c77c1fd552ef3fbc197d6a1cfe44e5e6f89b628b12a6fb04d5dcfcbacc51f46e4ae7bb", - "0xb998b2269932cbd58d04b8e898d373ac4bb1a62e8567484f4f83e224061bc0f212459f1daae95abdbc63816ae6486a55", - "0x827988ef6c1101cddc96b98f4a30365ff08eea2471dd949d2c0a9b35c3bbfa8c07054ad1f4c88c8fbf829b20bb5a9a4f", - "0x8692e638dd60babf7d9f2f2d2ce58e0ac689e1326d88311416357298c6a2bffbfebf55d5253563e7b3fbbf5072264146", - "0xa685d75b91aea04dbc14ab3c1b1588e6de96dae414c8e37b8388766029631b28dd860688079b12d09cd27f2c5af11adf", - "0xb57eced93eec3371c56679c259b34ac0992286be4f4ff9489d81cf9712403509932e47404ddd86f89d7c1c3b6391b28c", - "0xa1c8b4e42ebcbd8927669a97f1b72e236fb19249325659e72be7ddaaa1d9e81ca2abb643295d41a8c04a2c01f9c0efd7", - "0x877c33de20d4ed31674a671ba3e8f01a316581e32503136a70c9c15bf0b7cb7b1cba6cd4eb641fad165fb3c3c6c235fd", - "0xa2a469d84ec478da40838f775d11ad38f6596eb41caa139cc190d6a10b5108c09febae34ffdafac92271d2e73c143693", - "0x972f817caedb254055d52e963ed28c206848b6c4cfdb69dbc961c891f8458eaf582a6d4403ce1177d87bc2ea410ef60a", - "0xaccbd739e138007422f28536381decc54bb6bd71d93edf3890e54f9ef339f83d2821697d1a4ac1f5a98175f9a9ecb9b5", - "0x8940f8772e05389f823b62b3adc3ed541f91647f0318d7a0d3f293aeeb421013de0d0a3664ea53dd24e5fbe02d7efef6", - "0x8ecce20f3ef6212edef07ec4d6183fda8e0e8cad2c6ccd0b325e75c425ee1faba00b5c26b4d95204238931598d78f49d", - "0x97cc72c36335bd008afbed34a3b0c7225933faba87f7916d0a6d2161e6f82e0cdcda7959573a366f638ca75d30e9dab1", - "0x9105f5de8699b5bdb6bd3bb6cc1992d1eac23929c29837985f83b22efdda92af64d9c574aa9640475087201bbbe5fd73", - "0x8ffb33c4f6d05c413b9647eb6933526a350ed2e4278ca2ecc06b0e8026d8dbe829c476a40e45a6df63a633090a3f82ef", - "0x8bfc6421fdc9c2d2aaa68d2a69b1a2728c25b84944cc3e6a57ff0c94bfd210d1cbf4ff3f06702d2a8257024d8be7de63", - "0xa80e1dc1dddfb41a70220939b96dc6935e00b32fb8be5dff4eed1f1c650002ff95e4af481c43292e3827363b7ec4768a", - "0x96f714ebd54617198bd636ba7f7a7f8995a61db20962f2165078d9ed8ee764d5946ef3cbdc7ebf8435bb8d5dd4c1deac", - "0x8cdb0890e33144d66391d2ae73f5c71f5a861f72bc93bff6cc399fc25dd1f9e17d8772592b44593429718784802ac377", - "0x8ccf9a7f80800ee770b92add734ed45a73ecc31e2af0e04364eefc6056a8223834c7c0dc9dfc52495bdec6e74ce69994", - "0xaa0875f423bd68b5f10ba978ddb79d3b96ec093bfbac9ff366323193e339ed7c4578760fb60f60e93598bdf1e5cc4995", - "0xa9214f523957b59c7a4cb61a40251ad72aba0b57573163b0dc0f33e41d2df483fb9a1b85a5e7c080e9376c866790f8cb", - "0xb6224b605028c6673a536cc8ff9aeb94e7a22e686fda82cf16068d326469172f511219b68b2b3affb7933af0c1f80d07", - "0xb6d58968d8a017c6a34e24c2c09852f736515a2c50f37232ac6b43a38f8faa7572cc31dade543b594b61b5761c4781d0", - "0x8a97cefe5120020c38deeb861d394404e6c993c6cbd5989b6c9ebffe24f46ad11b4ba6348e2991cbf3949c28cfc3c99d", - "0x95bf046f8c3a9c0ce2634be4de3713024daec3fc4083e808903b25ce3ac971145af90686b451efcc72f6b22df0216667", - "0xa6a4e2f71b8fa28801f553231eff2794c0f10d12e7e414276995e21195abc9c2983a8997e41af41e78d19ff6fbb2680b", - "0x8e5e62a7ca9c2f58ebaab63db2ff1fb1ff0877ae94b7f5e2897f273f684ae639dff44cc65718f78a9c894787602ab26a", - "0x8542784383eec4f565fcb8b9fc2ad8d7a644267d8d7612a0f476fc8df3aff458897a38003d506d24142ad18f93554f2b", - "0xb7db68ba4616ea072b37925ec4fb39096358c2832cc6d35169e032326b2d6614479f765ae98913c267105b84afcb9bf2", - "0x8b31dbb9457d23d416c47542c786e07a489af35c4a87dadb8ee91bea5ac4a5315e65625d78dad2cf8f9561af31b45390", - "0xa8545a1d91ac17257732033d89e6b7111db8242e9c6ebb0213a88906d5ef407a2c6fdb444e29504b06368b6efb4f4839", - "0xb1bd85d29ebb28ccfb05779aad8674906b267c2bf8cdb1f9a0591dd621b53a4ee9f2942687ee3476740c0b4a7621a3ae", - "0xa2b54534e152e46c50d91fff03ae9cd019ff7cd9f4168b2fe7ac08ef8c3bbc134cadd3f9d6bd33d20ae476c2a8596c8a", - "0xb19b571ff4ae3e9f5d95acda133c455e72c9ea9973cae360732859836c0341c4c29ab039224dc5bc3deb824e031675d8", - "0x940b5f80478648bac025a30f3efeb47023ce20ee98be833948a248bca6979f206bb28fc0f17b90acf3bb4abd3d14d731", - "0x8f106b40588586ac11629b96d57808ad2808915d89539409c97414aded90b4ff23286a692608230a52bff696055ba5d6", - "0xae6bda03aa10da3d2abbc66d764ca6c8d0993e7304a1bdd413eb9622f3ca1913baa6da1e9f4f9e6cf847f14f44d6924d", - "0xa18e7796054a340ef826c4d6b5a117b80927afaf2ebd547794c400204ae2caf277692e2eabb55bc2f620763c9e9da66d", - "0x8d2d25180dc2c65a4844d3e66819ccfcf48858f0cc89e1c77553b463ec0f7feb9a4002ce26bc618d1142549b9850f232", - "0x863f413a394de42cc8166c1c75d513b91d545fff1de6b359037a742c70b008d34bf8e587afa2d62c844d0c6f0ea753e7", - "0x83cd0cf62d63475e7fcad18a2e74108499cdbf28af2113cfe005e3b5887794422da450b1944d0a986eb7e1f4c3b18f25", - "0xb4f8b350a6d88fea5ab2e44715a292efb12eb52df738c9b2393da3f1ddee68d0a75b476733ccf93642154bceb208f2b8", - "0xb3f52aaa4cd4221cb9fc45936cc67fd3864bf6d26bf3dd86aa85aa55ecfc05f5e392ecce5e7cf9406b4b1c4fce0398c8", - "0xb33137084422fb643123f40a6df2b498065e65230fc65dc31791c330e898c51c3a65ff738930f32c63d78f3c9315f85b", - "0x91452bfa75019363976bb7337fe3a73f1c10f01637428c135536b0cdc7da5ce558dae3dfc792aa55022292600814a8ef", - "0xad6ba94c787cd4361ca642c20793ea44f1f127d4de0bb4a77c7fbfebae0fcadbf28e2cb6f0c12c12a07324ec8c19761d", - "0x890aa6248b17f1501b0f869c556be7bf2b1d31a176f9978bb97ab7a6bd4138eed32467951c5ef1871944b7f620542f43", - "0x82111db2052194ee7dd22ff1eafffac0443cf969d3762cceae046c9a11561c0fdce9c0711f88ac01d1bed165f8a7cee3", - "0xb1527b71df2b42b55832f72e772a466e0fa05743aacc7814f4414e4bcc8d42a4010c9e0fd940e6f254cafedff3cd6543", - "0x922370fa49903679fc565f09c16a5917f8125e72acfeb060fcdbadbd1644eb9f4016229756019c93c6d609cda5d5d174", - "0xaa4c7d98a96cab138d2a53d4aee8ebff6ef903e3b629a92519608d88b3bbd94de5522291a1097e6acf830270e64c8ee1", - "0xb3dc21608a389a72d3a752883a382baaafc61ecc44083b832610a237f6a2363f24195acce529eb4aed4ef0e27a12b66e", - "0x94619f5de05e07b32291e1d7ab1d8b7337a2235e49d4fb5f3055f090a65e932e829efa95db886b32b153bdd05a53ec8c", - "0xade1e92722c2ffa85865d2426fb3d1654a16477d3abf580cfc45ea4b92d5668afc9d09275d3b79283e13e6b39e47424d", - "0xb7201589de7bed094911dd62fcd25c459a8e327ac447b69f541cdba30233063e5ddffad0b67e9c3e34adcffedfd0e13d", - "0x809d325310f862d6549e7cb40f7e5fc9b7544bd751dd28c4f363c724a0378c0e2adcb5e42ec8f912f5f49f18f3365c07", - "0xa79c20aa533de7a5d671c99eb9eb454803ba54dd4f2efa3c8fec1a38f8308e9905c71e9282955225f686146388506ff6", - "0xa85eeacb5e8fc9f3ed06a3fe2dc3108ab9f8c5877b148c73cf26e4e979bf5795edbe2e63a8d452565fd1176ed40402b2", - "0x97ef55662f8a1ec0842b22ee21391227540adf7708f491436044f3a2eb18c471525e78e1e14fa292507c99d74d7437c6", - "0x93110d64ed5886f3d16ce83b11425576a3a7a9bb831cd0de3f9a0b0f2270a730d68136b4ef7ff035ede004358f419b5c", - "0xac9ed0a071517f0ae4f61ce95916a90ba9a77a3f84b0ec50ef7298acdcd44d1b94525d191c39d6bd1bb68f4471428760", - "0x98abd6a02c7690f5a339adf292b8c9368dfc12e0f8069cf26a5e0ce54b4441638f5c66ea735142f3c28e00a0024267e6", - "0xb51efb73ba6d44146f047d69b19c0722227a7748b0e8f644d0fc9551324cf034c041a2378c56ce8b58d06038fb8a78de", - "0x8f115af274ef75c1662b588b0896b97d71f8d67986ae846792702c4742ab855952865ce236b27e2321967ce36ff93357", - "0xb3c4548f14d58b3ab03c222da09e4381a0afe47a72d18d50a94e0008797f78e39e99990e5b4757be62310d400746e35a", - "0xa9b1883bd5f31f909b8b1b6dcb48c1c60ed20aa7374b3ffa7f5b2ed036599b5bef33289d23c80a5e6420d191723b92f7", - "0x85d38dffd99487ae5bb41ab4a44d80a46157bbbe8ef9497e68f061721f74e4da513ccc3422936b059575975f6787c936", - "0xadf870fcb96e972c033ab7a35d28ae79ee795f82bc49c3bd69138f0e338103118d5529c53f2d72a9c0d947bf7d312af2", - "0xab4c7a44e2d9446c6ff303eb49aef0e367a58b22cc3bb27b4e69b55d1d9ee639c9234148d2ee95f9ca8079b1457d5a75", - "0xa386420b738aba2d7145eb4cba6d643d96bda3f2ca55bb11980b318d43b289d55a108f4bc23a9606fb0bccdeb3b3bb30", - "0x847020e0a440d9c4109773ecca5d8268b44d523389993b1f5e60e541187f7c597d79ebd6e318871815e26c96b4a4dbb1", - "0xa530aa7e5ca86fcd1bec4b072b55cc793781f38a666c2033b510a69e110eeabb54c7d8cbcb9c61fee531a6f635ffa972", - "0x87364a5ea1d270632a44269d686b2402da737948dac27f51b7a97af80b66728b0256547a5103d2227005541ca4b7ed04", - "0x8816fc6e16ea277de93a6d793d0eb5c15e9e93eb958c5ef30adaf8241805adeb4da8ce19c3c2167f971f61e0b361077d", - "0x8836a72d301c42510367181bb091e4be377777aed57b73c29ef2ce1d475feedd7e0f31676284d9a94f6db01cc4de81a2", - "0xb0d9d8b7116156d9dde138d28aa05a33e61f8a85839c1e9071ccd517b46a5b4b53acb32c2edd7150c15bc1b4bd8db9e3", - "0xae931b6eaeda790ba7f1cd674e53dc87f6306ff44951fa0df88d506316a5da240df9794ccbd7215a6470e6b31c5ea193", - "0x8c6d5bdf87bd7f645419d7c6444e244fe054d437ed1ba0c122fde7800603a5fadc061e5b836cb22a6cfb2b466f20f013", - "0x90d530c6d0cb654999fa771b8d11d723f54b8a8233d1052dc1e839ea6e314fbed3697084601f3e9bbb71d2b4eaa596df", - "0xb0d341a1422588c983f767b1ed36c18b141774f67ef6a43cff8e18b73a009da10fc12120938b8bba27f225bdfd3138f9", - "0xa131b56f9537f460d304e9a1dd75702ace8abd68cb45419695cb8dee76998139058336c87b7afd6239dc20d7f8f940cc", - "0xaa6c51fa28975f709329adee1bbd35d49c6b878041841a94465e8218338e4371f5cb6c17f44a63ac93644bf28f15d20f", - "0x88440fb584a99ebd7f9ea04aaf622f6e44e2b43bbb49fb5de548d24a238dc8f26c8da2ccf03dd43102bda9f16623f609", - "0x9777b8695b790e702159a4a750d5e7ff865425b95fa0a3c15495af385b91c90c00a6bd01d1b77bffe8c47d01baae846f", - "0x8b9d764ece7799079e63c7f01690c8eff00896a26a0d095773dea7a35967a8c40db7a6a74692f0118bf0460c26739af4", - "0x85808c65c485520609c9e61fa1bb67b28f4611d3608a9f7a5030ee61c3aa3c7e7dc17fff48af76b4aecee2cb0dbd22ac", - "0xad2783a76f5b3db008ef5f7e67391fda4e7e36abde6b3b089fc4835b5c339370287935af6bd53998bed4e399eda1136d", - "0x96f18ec03ae47c205cc4242ca58e2eff185c9dca86d5158817e2e5dc2207ab84aadda78725f8dc080a231efdc093b940", - "0x97de1ab6c6cc646ae60cf7b86df73b9cf56cc0cd1f31b966951ebf79fc153531af55ca643b20b773daa7cab784b832f7", - "0x870ba266a9bfa86ef644b1ef025a0f1b7609a60de170fe9508de8fd53170c0b48adb37f19397ee8019b041ce29a16576", - "0xad990e888d279ac4e8db90619d663d5ae027f994a3992c2fbc7d262b5990ae8a243e19157f3565671d1cb0de17fe6e55", - "0x8d9d5adcdd94c5ba3be4d9a7428133b42e485f040a28d16ee2384758e87d35528f7f9868de9bd23d1a42a594ce50a567", - "0x85a33ed75d514ece6ad78440e42f7fcdb59b6f4cff821188236d20edae9050b3a042ce9bc7d2054296e133d033e45022", - "0x92afd2f49a124aaba90de59be85ff269457f982b54c91b06650c1b8055f9b4b0640fd378df02a00e4fc91f7d226ab980", - "0x8c0ee09ec64bd831e544785e3d65418fe83ed9c920d9bb4d0bf6dd162c1264eb9d6652d2def0722e223915615931581c", - "0x8369bedfa17b24e9ad48ebd9c5afea4b66b3296d5770e09b00446c5b0a8a373d39d300780c01dcc1c6752792bccf5fd0", - "0x8b9e960782576a59b2eb2250d346030daa50bbbec114e95cdb9e4b1ba18c3d34525ae388f859708131984976ca439d94", - "0xb682bface862008fea2b5a07812ca6a28a58fd151a1d54c708fc2f8572916e0d678a9cb8dc1c10c0470025c8a605249e", - "0xa38d5e189bea540a824b36815fc41e3750760a52be0862c4cac68214febdc1a754fb194a7415a8fb7f96f6836196d82a", - "0xb9e7fbda650f18c7eb8b40e42cc42273a7298e65e8be524292369581861075c55299ce69309710e5b843cb884de171bd", - "0xb6657e5e31b3193874a1bace08f42faccbd3c502fb73ad87d15d18a1b6c2a146f1baa929e6f517db390a5a47b66c0acf", - "0xae15487312f84ed6265e4c28327d24a8a0f4d2d17d4a5b7c29b974139cf93223435aaebe3af918f5b4bb20911799715f", - "0x8bb4608beb06bc394e1a70739b872ce5a2a3ffc98c7547bf2698c893ca399d6c13686f6663f483894bccaabc3b9c56ad", - "0xb58ac36bc6847077584308d952c5f3663e3001af5ecf2e19cb162e1c58bd6c49510205d453cffc876ca1dc6b8e04a578", - "0x924f65ced61266a79a671ffb49b300f0ea44c50a0b4e3b02064faa99fcc3e4f6061ea8f38168ab118c5d47bd7804590e", - "0x8d67d43b8a06b0ff4fafd7f0483fa9ed1a9e3e658a03fb49d9d9b74e2e24858dc1bed065c12392037b467f255d4e5643", - "0xb4d4f87813125a6b355e4519a81657fa97c43a6115817b819a6caf4823f1d6a1169683fd68f8d025cdfa40ebf3069acb", - "0xa7fd4d2c8e7b59b8eed3d4332ae94b77a89a2616347402f880bc81bde072220131e6dbec8a605be3a1c760b775375879", - "0x8d4a7d8fa6f55a30df37bcf74952e2fa4fd6676a2e4606185cf154bdd84643fd01619f8fb8813a564f72e3f574f8ce30", - "0x8086fb88e6260e9a9c42e9560fde76315ff5e5680ec7140f2a18438f15bc2cc7d7d43bfb5880b180b738c20a834e6134", - "0x916c4c54721de03934fee6f43de50bb04c81f6f8dd4f6781e159e71c40c60408aa54251d457369d133d4ba3ed7c12cb4", - "0x902e5bf468f11ed9954e2a4a595c27e34abe512f1d6dc08bbca1c2441063f9af3dc5a8075ab910a10ff6c05c1c644a35", - "0xa1302953015e164bf4c15f7d4d35e3633425a78294406b861675667eec77765ff88472306531e5d3a4ec0a2ff0dd6a9e", - "0x87874461df3c9aa6c0fa91325576c0590f367075f2f0ecfeb34afe162c04c14f8ce9d608c37ac1adc8b9985bc036e366", - "0x84b50a8a61d3cc609bfb0417348133e698fe09a6d37357ce3358de189efcf35773d78c57635c2d26c3542b13cc371752", - "0xacaed2cff8633d12c1d12bb7270c54d65b0b0733ab084fd47f81d0a6e1e9b6f300e615e79538239e6160c566d8bb8d29", - "0x889e6a0e136372ca4bac90d1ab220d4e1cad425a710e8cdd48b400b73bb8137291ceb36a39440fa84305783b1d42c72f", - "0x90952e5becec45b2b73719c228429a2c364991cf1d5a9d6845ae5b38018c2626f4308daa322cab1c72e0f6c621bb2b35", - "0x8f5a97a801b6e9dcd66ccb80d337562c96f7914e7169e8ff0fda71534054c64bf2a9493bb830623d612cfe998789be65", - "0x84f3df8b9847dcf1d63ca470dc623154898f83c25a6983e9b78c6d2d90a97bf5e622445be835f32c1e55e6a0a562ea78", - "0x91d12095cd7a88e7f57f254f02fdb1a1ab18984871dead2f107404bcf8069fe68258c4e6f6ebd2477bddf738135400bb", - "0xb771a28bc04baef68604d4723791d3712f82b5e4fe316d7adc2fc01b935d8e644c06d59b83bcb542afc40ebafbee0683", - "0x872f6341476e387604a7e93ae6d6117e72d164e38ebc2b825bc6df4fcce815004d7516423c190c1575946b5de438c08d", - "0x90d6b4aa7d40a020cdcd04e8b016d041795961a8e532a0e1f4041252131089114a251791bf57794cadb7d636342f5d1c", - "0x899023ba6096a181448d927fed7a0fe858be4eac4082a42e30b3050ee065278d72fa9b9d5ce3bc1372d4cbd30a2f2976", - "0xa28f176571e1a9124f95973f414d5bdbf5794d41c3839d8b917100902ac4e2171eb940431236cec93928a60a77ede793", - "0x838dbe5bcd29c4e465d02350270fa0036cd46f8730b13d91e77afb7f5ed16525d0021d3b2ae173a76c378516a903e0cb", - "0x8e105d012dd3f5d20f0f1c4a7e7f09f0fdd74ce554c3032e48da8cce0a77260d7d47a454851387770f5c256fa29bcb88", - "0x8f4df0f9feeb7a487e1d138d13ea961459a6402fd8f8cabb226a92249a0d04ded5971f3242b9f90d08da5ff66da28af6", - "0xad1cfda4f2122a20935aa32fb17c536a3653a18617a65c6836700b5537122af5a8206befe9eaea781c1244c43778e7f1", - "0x832c6f01d6571964ea383292efc8c8fa11e61c0634a25fa180737cc7ab57bc77f25e614aac9a2a03d98f27b3c1c29de2", - "0x903f89cc13ec6685ac7728521898781fecb300e9094ef913d530bf875c18bcc3ceed7ed51e7b482d45619ab4b025c2e9", - "0xa03c474bb915aad94f171e8d96f46abb2a19c9470601f4c915512ec8b9e743c3938450a2a5b077b4618b9df8809e1dc1", - "0x83536c8456f306045a5f38ae4be2e350878fa7e164ea408d467f8c3bc4c2ee396bd5868008c089183868e4dfad7aa50b", - "0x88f26b4ea1b236cb326cd7ad7e2517ec8c4919598691474fe15d09cabcfc37a8d8b1b818f4d112432ee3a716b0f37871", - "0xa44324e3fe96e9c12b40ded4f0f3397c8c7ee8ff5e96441118d8a6bfad712d3ac990b2a6a23231a8f691491ac1fd480f", - "0xb0de4693b4b9f932191a21ee88629964878680152a82996c0019ffc39f8d9369bbe2fe5844b68d6d9589ace54af947e4", - "0x8e5d8ba948aea5fd26035351a960e87f0d23efddd8e13236cc8e4545a3dda2e9a85e6521efb8577e03772d3637d213d9", - "0x93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556", - "0x8731176363ad7658a2862426ee47a5dce9434216cef60e6045fa57c40bb3ce1e78dac4510ae40f1f31db5967022ced32", - "0xb10c9a96745722c85bdb1a693100104d560433d45b9ac4add54c7646a7310d8e9b3ca9abd1039d473ae768a18e489845", - "0xa2ac374dfbb464bf850b4a2caf15b112634a6428e8395f9c9243baefd2452b4b4c61b0cb2836d8eae2d57d4900bf407e", - "0xb69fe3ded0c4f5d44a09a0e0f398221b6d1bf5dbb8bc4e338b93c64f1a3cac1e4b5f73c2b8117158030ec03787f4b452", - "0x8852cdbaf7d0447a8c6f211b4830711b3b5c105c0f316e3a6a18dcfbb9be08bd6f4e5c8ae0c3692da08a2dfa532f9d5c", - "0x93bbf6d7432a7d98ade3f94b57bf9f4da9bc221a180a370b113066dd42601bb9e09edd79e2e6e04e00423399339eebda", - "0xa80941c391f1eeafc1451c59e4775d6a383946ff22997aeaadf806542ba451d3b0f0c6864eeba954174a296efe2c1550", - "0xa045fe2bb011c2a2f71a0181a8f457a3078470fb74c628eab8b59aef69ffd0d649723bf74d6885af3f028bc5a104fb39", - "0xb9d8c35911009c4c8cad64692139bf3fc16b78f5a19980790cb6a7aea650a25df4231a4437ae0c351676a7e42c16134f", - "0x94c79501ded0cfcbab99e1841abe4a00a0252b3870e20774c3da16c982d74c501916ec28304e71194845be6e3113c7ab", - "0x900a66418b082a24c6348d8644ddb1817df5b25cb33044a519ef47cc8e1f7f1e38d2465b7b96d32ed472d2d17f8414c6", - "0xb26f45d393b8b2fcb29bdbb16323dc7f4b81c09618519ab3a39f8ee5bd148d0d9f3c0b5dfab55b5ce14a1cb9206d777b", - "0xaa1a87735fc493a80a96a9a57ca40a6d9c32702bfcaa9869ce1a116ae65d69cefe2f3e79a12454b4590353e96f8912b4", - "0xa922b188d3d0b69b4e4ea2a2aa076566962844637da12c0832105d7b31dea4a309eee15d12b7a336be3ea36fcbd3e3b7", - "0x8f3841fcf4105131d8c4d9885e6e11a46c448226401cf99356c291fadb864da9fa9d30f3a73c327f23f9fd99a11d633e", - "0x9791d1183fae270e226379af6c497e7da803ea854bb20afa74b253239b744c15f670ee808f708ede873e78d79a626c9a", - "0xa4cad52e3369491ada61bf28ada9e85de4516d21c882e5f1cd845bea9c06e0b2887b0c5527fcff6fc28acd3c04f0a796", - "0xb9ac86a900899603452bd11a7892a9bfed8054970bfcbeaa8c9d1930db891169e38d6977f5258c25734f96c8462eee3b", - "0xa3a154c28e5580656a859f4efc2f5ebfa7eaa84ca40e3f134fa7865e8581586db74992dbfa4036aa252fba103773ddde", - "0x95cc2a0c1885a029e094f5d737e3ecf4d26b99036453a8773c77e360101f9f98676ee246f6f732a377a996702d55691f", - "0x842651bbe99720438d8d4b0218feb60481280c05beb17750e9ca0d8c0599a60f873b7fbdcc7d8835ba9a6d57b16eec03", - "0x81ee54699da98f5620307893dcea8f64670609fa20e5622265d66283adeac122d458b3308c5898e6c57c298db2c8b24f", - "0xb97868b0b2bc98032d68352a535a1b341b9ff3c7af4e3a7f3ebc82d3419daa1b5859d6aedc39994939623c7cd878bd9b", - "0xb60325cd5d36461d07ef253d826f37f9ee6474a760f2fff80f9873d01fd2b57711543cdc8d7afa1c350aa753c2e33dea", - "0x8c205326c11d25a46717b780c639d89714c7736c974ae71287e3f4b02e6605ac2d9b4928967b1684f12be040b7bf2dd3", - "0x95a392d82db51e26ade6c2ccd3396d7e40aff68fa570b5951466580d6e56dda51775dce5cf3a74a7f28c3cb2eb551c4d", - "0x8f2cc8071eb56dffb70bda6dd433b556221dc8bba21c53353c865f00e7d4d86c9e39f119ea9a8a12ef583e9a55d9a6b6", - "0x9449a71af9672aaf8856896d7e3d788b22991a7103f75b08c0abbcc2bfe60fda4ed8ce502cea4511ff0ea52a93e81222", - "0x857090ab9fdb7d59632d068f3cc8cf27e61f0d8322d30e6b38e780a1f05227199b4cd746aac1311c36c659ef20931f28", - "0x98a891f4973e7d9aaf9ac70854608d4f7493dffc7e0987d7be9dd6029f6ea5636d24ef3a83205615ca1ff403750058e1", - "0xa486e1365bbc278dd66a2a25d258dc82f46b911103cb16aab3945b9c95ae87b386313a12b566df5b22322ede0afe25ad", - "0xa9a1eb399ed95d396dccd8d1ac718043446f8b979ec62bdce51c617c97a312f01376ab7fb87d27034e5f5570797b3c33", - "0xb7abc3858d7a74bb446218d2f5a037e0fae11871ed9caf44b29b69c500c1fa1dcfad64c9cdccc9d80d5e584f06213deb", - "0x8cfb09fe2e202faa4cebad932b1d35f5ca204e1c2a0c740a57812ac9a6792130d1312aabd9e9d4c58ca168bfebd4c177", - "0xa90a305c2cd0f184787c6be596fa67f436afd1f9b93f30e875f817ac2aae8bdd2e6e656f6be809467e6b3ad84adb86b1", - "0x80a9ef993c2b009ae172cc8f7ec036f5734cf4f4dfa06a7db4d54725e7fbfae5e3bc6f22687bdbb6961939d6f0c87537", - "0x848ade1901931e72b955d7db1893f07003e1708ff5d93174bac5930b9a732640f0578839203e9b77eb27965c700032d3", - "0x93fdf4697609c5ae9c33b9ca2f5f1af44abeb2b98dc4fdf732cf7388de086f410730dc384d9b7a7f447bb009653c8381", - "0x89ce3fb805aea618b5715c0d22a9f46da696b6fa86794f56fdf1d44155a33d42daf1920bcbe36cbacf3cf4c92df9cbc7", - "0x829ce2c342cf82aa469c65f724f308f7a750bd1494adc264609cd790c8718b8b25b5cab5858cf4ee2f8f651d569eea67", - "0xaf2f0cee7bf413204be8b9df59b9e4991bc9009e0d6dbe6815181df0ec2ca93ab8f4f3135b1c14d8f53d74bff0bd6f27", - "0xb87998cecf7b88cde93d1779f10a521edd5574a2fbd240102978639ec57433ba08cdb53849038a329cebbe74657268d2", - "0xa64542a1261a6ed3d720c2c3a802303aad8c4c110c95d0f12e05c1065e66f42da494792b6bfc5b9272363f3b1d457f58", - "0x86a6fd042e4f282fadf07a4bfee03fc96a3aea49f7a00f52bf249a20f1ec892326855410e61f37fbb27d9305eb2fc713", - "0x967ea5bc403b6db269682f7fd0df90659350d7e1aa66bc4fab4c9dfcd75ed0bba4b52f1cebc5f34dc8ba810793727629", - "0xa52990f9f3b8616ce3cdc2c74cd195029e6a969753dcf2d1630438700e7d6ebde36538532b3525ac516f5f2ce9dd27a3", - "0xa64f7ff870bab4a8bf0d4ef6f5c744e9bf1021ed08b4c80903c7ad318e80ba1817c3180cc45cb5a1cae1170f0241655f", - "0xb00f706fa4de1f663f021e8ad3d155e84ce6084a409374b6e6cd0f924a0a0b51bebaaaf1d228c77233a73b0a5a0df0e9", - "0x8b882cc3bff3e42babdb96df95fb780faded84887a0a9bab896bef371cdcf169d909f5658649e93006aa3c6e1146d62e", - "0x9332663ef1d1dcf805c3d0e4ce7a07d9863fb1731172e766b3cde030bf81682cc011e26b773fb9c68e0477b4ae2cfb79", - "0xa8aa8151348dbd4ef40aaeb699b71b4c4bfd3218560c120d85036d14f678f6736f0ec68e80ce1459d3d35feccc575164", - "0xa16cd8b729768f51881c213434aa28301fa78fcb554ddd5f9012ee1e4eae7b5cb3dd88d269d53146dea92d10790faf0b", - "0x86844f0ef9d37142faf3b1e196e44fbe280a3ba4189aa05c356778cb9e3b388a2bff95eed305ada8769935c9974e4c57", - "0xae2eec6b328fccf3b47bcdac32901ac2744a51beb410b04c81dea34dee4912b619466a4f5e2780d87ecefaebbe77b46d", - "0x915df4c38d301c8a4eb2dc5b1ba0ffaad67cbb177e0a80095614e9c711f4ef24a4cef133f9d982a63d2a943ba6c8669d", - "0xae6a2a4dedfc2d1811711a8946991fede972fdf2a389b282471280737536ffc0ac3a6d885b1f8bda0366eb0b229b9979", - "0xa9b628c63d08b8aba6b1317f6e91c34b2382a6c85376e8ef2410a463c6796740ae936fc4e9e0737cb9455d1daa287bd8", - "0x848e30bf7edf2546670b390d5cf9ab71f98fcb6add3c0b582cb34996c26a446dee5d1bde4fdcde4fc80c10936e117b29", - "0x907d6096c7c8c087d1808dd995d5d2b9169b3768c3f433475b50c2e2bd4b082f4d543afd8b0b0ddffa9c66222a72d51d", - "0xa59970a2493b07339124d763ac9d793c60a03354539ecbcf6035bc43d1ea6e35718202ae6d7060b7d388f483d971573c", - "0xb9cfef2af9681b2318f119d8611ff6d9485a68d8044581b1959ab1840cbca576dbb53eec17863d2149966e9feb21122f", - "0xad47271806161f61d3afa45cdfe2babceef5e90031a21779f83dc8562e6076680525b4970b2f11fe9b2b23c382768323", - "0x8e425a99b71677b04fe044625d338811fbb8ee32368a424f6ab2381c52e86ee7a6cecedf777dc97181519d41c351bc22", - "0x86b55b54d7adefc12954a9252ee23ae83efe8b5b4b9a7dc307904413e5d69868c7087a818b2833f9b004213d629be8ad", - "0xa14fda6b93923dd11e564ae4457a66f397741527166e0b16a8eb91c6701c244fd1c4b63f9dd3515193ec88fa6c266b35", - "0xa9b17c36ae6cd85a0ed7f6cabc5b47dc8f80ced605db327c47826476dc1fb8f8669aa7a7dc679fbd4ee3d8e8b4bd6a6f", - "0x82a0829469c1458d959c821148f15dacae9ea94bf56c59a6ab2d4dd8b3d16d73e313b5a3912a6c1f131d73a8f06730c4", - "0xb22d56d549a53eaef549595924bdb621ff807aa4513feedf3fdcbf7ba8b6b9cfa4481c2f67fc642db397a6b794a8b63a", - "0x974c59c24392e2cb9294006cbe3c52163e255f3bd0c2b457bdc68a6338e6d5b6f87f716854492f8d880a6b896ccf757c", - "0xb70d247ba7cad97c50b57f526c2ba915786e926a94e8f8c3eebc2e1be6f4255411b9670e382060049c8f4184302c40b2", - "0xad80201fe75ef21c3ddbd98cf23591e0d7a3ba1036dfe77785c32f44755a212c31f0ceb0a0b6f5ee9b6dc81f358d30c3", - "0x8c656e841f9bb90b9a42d425251f3fdbc022a604d75f5845f479ed4be23e02aaf9e6e56cde351dd7449c50574818a199", - "0x8b88dd3fa209d3063b7c5b058f7249ee9900fbc2287d16da61a0704a0a1d71e45d9c96e1cda7fdf9654534ec44558b22", - "0x961da00cc8750bd84d253c08f011970ae1b1158ad6778e8ed943d547bceaf52d6d5a212a7de3bf2706688c4389b827d2", - "0xa5dd379922549a956033e3d51a986a4b1508e575042b8eaa1df007aa77cf0b8c2ab23212f9c075702788fa9c53696133", - "0xac8fcfde3a349d1e93fc8cf450814e842005c545c4844c0401bc80e6b96cdb77f29285a14455e167c191d4f312e866cd", - "0xac63d79c799783a8466617030c59dd5a8f92ee6c5204676fd8d881ce5f7f8663bdbeb0379e480ea9b6340ab0dc88e574", - "0x805874fde19ce359041ae2bd52a39e2841acabfd31f965792f2737d7137f36d4e4722ede8340d8c95afa6af278af8acb", - "0x8d2f323a228aa8ba7b7dc1399138f9e6b41df1a16a7069003ab8104b8b68506a45141bc5fe66acf430e23e13a545190b", - "0xa1610c721a2d9af882bb6b39bea97cff1527a3aea041d25934de080214ae77c959e79957164440686d15ab301e897d4d", - "0xaba16d29a47fc36f12b654fde513896723e2c700c4190f11b26aa4011da57737ad717daa02794aa3246e4ae5f0b0cc3a", - "0xa406db2f15fdd135f346cc4846623c47edd195e80ba8c7cb447332095314d565e4040694ca924696bb5ee7f8996ea0ba", - "0x8b30e2cd9b47d75ba57b83630e40f832249af6c058d4f490416562af451993eec46f3e1f90bc4d389e4c06abd1b32a46", - "0xaacf9eb7036e248e209adbfc3dd7ce386569ea9b312caa4b240726549db3c68c4f1c8cbf8ed5ea9ea60c7e57c9df3b8e", - "0xb20fcac63bf6f5ee638a42d7f89be847f348c085ddcbec3fa318f4323592d136c230495f188ef2022aa355cc2b0da6f9", - "0x811eff750456a79ec1b1249d76d7c1547065b839d8d4aaad860f6d4528eb5b669473dcceeeea676cddbc3980b68461b7", - "0xb52d14ae33f4ab422f953392ae76a19c618cc31afc96290bd3fe2fb44c954b5c92c4789f3f16e8793f2c0c1691ade444", - "0xa7826dafeeba0db5b66c4dfcf2b17fd7b40507a5a53ac2e42942633a2cb30b95ba1739a6e9f3b7a0e0f1ec729bf274e2", - "0x8acfd83ddf7c60dd7c8b20c706a3b972c65d336b8f9b3d907bdd8926ced271430479448100050b1ef17578a49c8fa616", - "0xaf0c69f65184bb06868029ad46f8465d75c36814c621ac20a5c0b06a900d59305584f5a6709683d9c0e4b6cd08d650a6", - "0xb6cc8588191e00680ee6c3339bd0f0a17ad8fd7f4be57d5d7075bede0ea593a19e67f3d7c1a20114894ee5bfcab71063", - "0xa82fd4f58635129dbb6cc3eb9391cf2d28400018b105fc41500fbbd12bd890b918f97d3d359c29dd3b4c4e34391dfab0", - "0x92fc544ed65b4a3625cf03c41ddff7c039bc22d22c0d59dcc00efd5438401f2606adb125a1d5de294cca216ec8ac35a3", - "0x906f67e4a32582b71f15940523c0c7ce370336935e2646bdaea16a06995256d25e99df57297e39d6c39535e180456407", - "0x97510337ea5bbd5977287339197db55c60533b2ec35c94d0a460a416ae9f60e85cee39be82abeeacd5813cf54df05862", - "0x87e6894643815c0ea48cb96c607266c5ee4f1f82ba5fe352fb77f9b6ed14bfc2b8e09e80a99ac9047dfcf62b2ae26795", - "0xb6fd55dd156622ad7d5d51b7dde75e47bd052d4e542dd6449e72411f68275775c846dde301e84613312be8c7bce58b07", - "0xb98461ac71f554b2f03a94e429b255af89eec917e208a8e60edf5fc43b65f1d17a20de3f31d2ce9f0cb573c25f2f4d98", - "0x96f0dea40ca61cefbee41c4e1fe9a7d81fbe1f49bb153d083ab70f5d0488a1f717fd28cedcf6aa18d07cce2c62801898", - "0x8d7c3ab310184f7dc34b6ce4684e4d29a31e77b09940448ea4daac730b7eb308063125d4dd229046cf11bfd521b771e0", - "0x96f0564898fe96687918bbf0a6adead99cf72e3a35ea3347e124af9d006221f8e82e5a9d2fe80094d5e8d48e610f415e", - "0xad50fcb92c2675a398cf07d4c40a579e44bf8d35f27cc330b57e54d5ea59f7d898af0f75dccfe3726e5471133d70f92b", - "0x828beed62020361689ae7481dd8f116902b522fb0c6c122678e7f949fdef70ead011e0e6bffd25678e388744e17cdb69", - "0x8349decac1ca16599eee2efc95bcaabf67631107da1d34a2f917884bd70dfec9b4b08ab7bc4379d6c73b19c0b6e54fb8", - "0xb2a6a2e50230c05613ace9e58bb2e98d94127f196f02d9dddc53c43fc68c184549ca12d713cb1b025d8260a41e947155", - "0x94ff52181aadae832aed52fc3b7794536e2a31a21fc8be3ea312ca5c695750d37f08002f286b33f4023dba1e3253ecfa", - "0xa21d56153c7e5972ee9a319501be4faff199fdf09bb821ea9ce64aa815289676c00f105e6f00311b3a5b627091b0d0fc", - "0xa27a60d219f1f0c971db73a7f563b371b5c9fc3ed1f72883b2eac8a0df6698400c9954f4ca17d7e94e44bd4f95532afb", - "0xa2fc56fae99b1f18ba5e4fe838402164ce82f8a7f3193d0bbd360c2bac07c46f9330c4c7681ffb47074c6f81ee6e7ac6", - "0xb748e530cd3afb96d879b83e89c9f1a444f54e55372ab1dcd46a0872f95ce8f49cf2363fc61be82259e04f555937ed16", - "0x8bf8993e81080c7cbba1e14a798504af1e4950b2f186ab3335b771d6acaee4ffe92131ae9c53d74379d957cb6344d9cd", - "0x96774d0ef730d22d7ab6d9fb7f90b9ead44285219d076584a901960542756700a2a1603cdf72be4708b267200f6c36a9", - "0xb47703c2ab17be1e823cc7bf3460db1d6760c0e33862c90ca058845b2ff234b0f9834ddba2efb2ee1770eb261e7d8ffd", - "0x84319e67c37a9581f8b09b5e4d4ae88d0a7fb4cbb6908971ab5be28070c3830f040b1de83ee663c573e0f2f6198640e4", - "0x96811875fa83133e0b3c0e0290f9e0e28bca6178b77fdf5350eb19344d453dbd0d71e55a0ef749025a5a2ca0ad251e81", - "0x81a423423e9438343879f2bfd7ee9f1c74ebebe7ce3cfffc8a11da6f040cc4145c3b527bd3cf63f9137e714dbcb474ef", - "0xb8c3535701ddbeec2db08e17a4fa99ba6752d32ece5331a0b8743676f421fcb14798afc7c783815484f14693d2f70db8", - "0x81aee980c876949bf40782835eec8817d535f6f3f7e00bf402ddd61101fdcd60173961ae90a1cf7c5d060339a18c959d", - "0x87e67b928d97b62c49dac321ce6cb680233f3a394d4c9a899ac2e8db8ccd8e00418e66cdfd68691aa3cb8559723b580c", - "0x8eac204208d99a2b738648df96353bbb1b1065e33ee4f6bba174b540bbbd37d205855e1f1e69a6b7ff043ca377651126", - "0x848e6e7a54ad64d18009300b93ea6f459ce855971dddb419b101f5ac4c159215626fadc20cc3b9ab1701d8f6dfaddd8b", - "0x88aa123d9e0cf309d46dddb6acf634b1ade3b090a2826d6e5e78669fa1220d6df9a6697d7778cd9b627db17eea846126", - "0x9200c2a629b9144d88a61151b661b6c4256cc5dadfd1e59a8ce17a013c2d8f7e754aabe61663c3b30f1bc47784c1f8cf", - "0xb6e1a2827c3bdda91715b0e1b1f10dd363cef337e7c80cac1f34165fc0dea7c8b69747e310563db5818390146ce3e231", - "0x92c333e694f89f0d306d54105b2a5dcc912dbe7654d9e733edab12e8537350815be472b063e56cfde5286df8922fdecb", - "0xa6fac04b6d86091158ebb286586ccfec2a95c9786e14d91a9c743f5f05546073e5e3cc717635a0c602cad8334e922346", - "0xa581b4af77feebc1fb897d49b5b507c6ad513d8f09b273328efbb24ef0d91eb740d01b4d398f2738125dacfe550330cd", - "0x81c4860cccf76a34f8a2bc3f464b7bfd3e909e975cce0d28979f457738a56e60a4af8e68a3992cf273b5946e8d7f76e2", - "0x8d1eaa09a3180d8af1cbaee673db5223363cc7229a69565f592fa38ba0f9d582cedf91e15dabd06ebbf2862fc0feba54", - "0x9832f49b0147f4552402e54593cfa51f99540bffada12759b71fcb86734be8e500eea2d8b3d036710bdf04c901432de9", - "0x8bdb0e8ec93b11e5718e8c13cb4f5de545d24829fd76161216340108098dfe5148ed25e3b57a89a516f09fa79043734d", - "0xab96f06c4b9b0b2c0571740b24fca758e6976315053a7ecb20119150a9fa416db2d3a2e0f8168b390bb063f0c1caf785", - "0xab777f5c52acd62ecf4d1f168b9cc8e1a9b45d4ec6a8ff52c583e867c2239aba98d7d3af977289b367edce03d9c2dfb1", - "0xa09d3ce5e748da84802436951acc3d3ea5d8ec1d6933505ed724d6b4b0d69973ab0930daec9c6606960f6e541e4a3ce2", - "0x8ef94f7be4d85d5ad3d779a5cf4d7b2fc3e65c52fb8e1c3c112509a4af77a0b5be994f251e5e40fabeeb1f7d5615c22b", - "0xa7406a5bf5708d9e10922d3c5c45c03ef891b8d0d74ec9f28328a72be4cdc05b4f2703fa99366426659dfca25d007535", - "0xb7f52709669bf92a2e070bfe740f422f0b7127392c5589c7f0af71bb5a8428697c762d3c0d74532899da24ea7d8695c2", - "0xb9dfb0c8df84104dbf9239ccefa4672ef95ddabb8801b74997935d1b81a78a6a5669a3c553767ec19a1281f6e570f4ff", - "0xae4d5c872156061ce9195ac640190d8d71dd406055ee43ffa6f9893eb24b870075b74c94d65bc1d5a07a6573282b5520", - "0xafe6bd3eb72266d333f1807164900dcfa02a7eb5b1744bb3c86b34b3ee91e3f05e38fa52a50dc64eeb4bdb1dd62874b8", - "0x948043cf1bc2ef3c01105f6a78dc06487f57548a3e6ef30e6ebc51c94b71e4bf3ff6d0058c72b6f3ecc37efd7c7fa8c0", - "0xa22fd17c2f7ffe552bb0f23fa135584e8d2d8d75e3f742d94d04aded2a79e22a00dfe7acbb57d44e1cdb962fb22ae170", - "0x8cd0f4e9e4fb4a37c02c1bde0f69359c43ab012eb662d346487be0c3758293f1ca560122b059b091fddce626383c3a8f", - "0x90499e45f5b9c81426f3d735a52a564cafbed72711d9279fdd88de8038e953bc48c57b58cba85c3b2e4ce56f1ddb0e11", - "0x8c30e4c034c02958384564cac4f85022ef36ab5697a3d2feaf6bf105049675bbf23d01b4b6814711d3d9271abff04cac", - "0x81f7999e7eeea30f3e1075e6780bbf054f2fb6f27628a2afa4d41872a385b4216dd5f549da7ce6cf39049b2251f27fb7", - "0xb36a7191f82fc39c283ffe53fc1f5a9a00b4c64eee7792a8443475da9a4d226cf257f226ea9d66e329af15d8f04984ec", - "0xaad4da528fdbb4db504f3041c747455baff5fcd459a2efd78f15bdf3aea0bdb808343e49df88fe7a7c8620009b7964a3", - "0x99ebd8c6dd5dd299517fb6381cfc2a7f443e6e04a351440260dd7c2aee3f1d8ef06eb6c18820b394366ecdfd2a3ce264", - "0x8873725b81871db72e4ec3643084b1cdce3cbf80b40b834b092767728605825c19b6847ad3dcf328438607e8f88b4410", - "0xb008ee2f895daa6abd35bd39b6f7901ae4611a11a3271194e19da1cdcc7f1e1ea008fe5c5440e50d2c273784541ad9c5", - "0x9036feafb4218d1f576ef89d0e99124e45dacaa6d816988e34d80f454d10e96809791d5b78f7fd65f569e90d4d7238c5", - "0x92073c1d11b168e4fa50988b0288638b4868e48bbc668c5a6dddf5499875d53be23a285acb5e4bad60114f6cf6c556e9", - "0x88c87dfcb8ba6cbfe7e1be081ccfadbd589301db2cb7c99f9ee5d7db90aa297ed1538d5a867678a763f2deede5fd219a", - "0xb42a562805c661a50f5dea63108002c0f27c0da113da6a9864c9feb5552225417c0356c4209e8e012d9bcc9d182c7611", - "0x8e6317d00a504e3b79cd47feb4c60f9df186467fe9ca0f35b55c0364db30528f5ff071109dabb2fc80bb9cd4949f0c24", - "0xb7b1ea6a88694f8d2f539e52a47466695e39e43a5eb9c6f23bca15305fe52939d8755cc3ac9d6725e60f82f994a3772f", - "0xa3cd55161befe795af93a38d33290fb642b8d80da8b786c6e6fb02d393ea308fbe87f486994039cbd7c7b390414594b6", - "0xb416d2d45b44ead3b1424e92c73c2cf510801897b05d1724ff31cbd741920cd858282fb5d6040fe1f0aa97a65bc49424", - "0x950ee01291754feace97c2e933e4681e7ddfbc4fcd079eb6ff830b0e481d929c93d0c7fb479c9939c28ca1945c40da09", - "0x869bd916aee8d86efe362a49010382674825d49195b413b4b4018e88ce43fe091b475d0b863ff0ba2259400f280c2b23", - "0x9782f38cd9c9d3385ec286ebbc7cba5b718d2e65a5890b0a5906b10a89dc8ed80d417d71d7c213bf52f2af1a1f513ea7", - "0x91cd33bc2628d096269b23faf47ee15e14cb7fdc6a8e3a98b55e1031ea0b68d10ba30d97e660f7e967d24436d40fad73", - "0x8becc978129cc96737034c577ae7225372dd855da8811ae4e46328e020c803833b5bdbc4a20a93270e2b8bd1a2feae52", - "0xa36b1d8076783a9522476ce17f799d78008967728ce920531fdaf88303321bcaf97ecaa08e0c01f77bc32e53c5f09525", - "0xb4720e744943f70467983aa34499e76de6d59aa6fadf86f6b787fdce32a2f5b535b55db38fe2da95825c51002cfe142d", - "0x91ad21fc502eda3945f6de874d1b6bf9a9a7711f4d61354f9e5634fc73f9c06ada848de15ab0a75811d3250be862827d", - "0x84f78e2ebf5fc077d78635f981712daf17e2475e14c2a96d187913006ad69e234746184a51a06ef510c9455b38acb0d7", - "0x960aa7906e9a2f11db64a26b5892ac45f20d2ccb5480f4888d89973beb6fa0dfdc06d68d241ff5ffc7f1b82b1aac242d", - "0xa99365dcd1a00c66c9db6924b97c920f5c723380e823b250db85c07631b320ec4e92e586f7319e67a522a0578f7b6d6c", - "0xa25d92d7f70cf6a88ff317cfec071e13774516da664f5fac0d4ecaa65b8bf4eb87a64a4d5ef2bd97dfae98d388dbf5cc", - "0xa7af47cd0041295798f9779020a44653007444e8b4ef0712982b06d0dcdd434ec4e1f7c5f7a049326602cb605c9105b7", - "0xaefe172eac5568369a05980931cc476bebd9dea573ba276d59b9d8c4420784299df5a910033b7e324a6c2dfc62e3ef05", - "0xb69bc9d22ffa645baa55e3e02522e9892bb2daa7fff7c15846f13517d0799766883ee09ae0869df4139150c5b843ca8a", - "0x95a10856140e493354fdd12722c7fdded21b6a2ffbc78aa2697104af8ad0c8e2206f44b0bfee077ef3949d46bbf7c16b", - "0x891f2fcd2c47cbea36b7fa715968540c233313f05333f09d29aba23c193f462ed490dd4d00969656e89c53155fdfe710", - "0xa6c33e18115e64e385c843dde34e8a228222795c7ca90bc2cc085705d609025f3351d9be61822c69035a49fb3e48f2d5", - "0xb87fb12f12c0533b005adad0487f03393ff682e13575e3cb57280c3873b2c38ba96a63c49eef7a442753d26b7005230b", - "0xb905c02ba451bfd411c135036d92c27af3b0b1c9c2f1309d6948544a264b125f39dd41afeff4666b12146c545adc168a", - "0x8b29c513f43a78951cf742231cf5457a6d9d55edf45df5481a0f299a418d94effef561b15d2c1a01d1b8067e7153fda9", - "0xb9941cccd51dc645920d2781c81a317e5a33cb7cf76427b60396735912cb6d2ca9292bb4d36b6392467d390d2c58d9f3", - "0xa8546b627c76b6ef5c93c6a98538d8593dbe21cb7673fd383d5401b0c935eea0bdeeefeb1af6ad41bad8464fb87bbc48", - "0xaa286b27de2812de63108a1aec29d171775b69538dc6198640ac1e96767c2b83a50391f49259195957d457b493b667c9", - "0xa932fb229f641e9abbd8eb2bd874015d97b6658ab6d29769fc23b7db9e41dd4f850382d4c1f08af8f156c5937d524473", - "0xa1412840fcc86e2aeec175526f2fb36e8b3b8d21a78412b7266daf81e51b3f68584ed8bd42a66a43afdd8c297b320520", - "0x89c78be9efb624c97ebca4fe04c7704fa52311d183ffd87737f76b7dadc187c12c982bd8e9ed7cd8beb48cdaafd2fd01", - "0xa3f5ddec412a5bec0ce15e3bcb41c6214c2b05d4e9135a0d33c8e50a78eaba71e0a5a6ea8b45854dec5c2ed300971fc2", - "0x9721f9cec7a68b7758e3887548790de49fa6a442d0396739efa20c2f50352a7f91d300867556d11a703866def2d5f7b5", - "0xa23764e140a87e5991573521af039630dd28128bf56eed2edbed130fd4278e090b60cf5a1dca9de2910603d44b9f6d45", - "0xa1a6494a994215e48ab55c70efa8ffdddce6e92403c38ae7e8dd2f8288cad460c6c7db526bbdf578e96ca04d9fe12797", - "0xb1705ea4cb7e074efe0405fc7b8ee2ec789af0426142f3ec81241cacd4f7edcd88e39435e4e4d8e7b1df64f3880d6613", - "0x85595d061d677116089a6064418b93eb44ff79e68d12bd9625078d3bbc440a60d0b02944eff6054433ee34710ae6fbb4", - "0x9978d5e30bedb7526734f9a1febd973a70bfa20890490e7cc6f2f9328feab1e24f991285dbc3711d892514e2d7d005ad", - "0xaf30243c66ea43b9f87a061f947f7bce745f09194f6e95f379c7582b9fead920e5d6957eaf05c12ae1282ada4670652f", - "0xa1930efb473f88001e47aa0b2b2a7566848cccf295792e4544096ecd14ee5d7927c173a8576b405bfa2eec551cd67eb5", - "0xb0446d1c590ee5a45f7e22d269c044f3848c97aec1d226b44bfd0e94d9729c28a38bccddc3a1006cc5fe4e3c24f001f2", - "0xb8a8380172df3d84b06176df916cf557966d4f2f716d3e9437e415d75b646810f79f2b2b71d857181b7fc944018883a3", - "0xa563afec25b7817bfa26e19dc9908bc00aa8fc3d19be7d6de23648701659009d10e3e4486c28e9c6b13d48231ae29ac5", - "0xa5a8e80579de886fb7d6408f542791876885947b27ad6fa99a8a26e381f052598d7b4e647b0115d4b5c64297e00ce28e", - "0x8f87afcc7ad33c51ac719bade3cd92da671a37a82c14446b0a2073f4a0a23085e2c8d31913ed2d0be928f053297de8f6", - "0xa43c455ce377e0bc434386c53c752880687e017b2f5ae7f8a15c044895b242dffde4c92fb8f8bb50b18470b17351b156", - "0x8368f8b12a5bceb1dba25adb3a2e9c7dc9b1a77a1f328e5a693f5aec195cd1e06b0fe9476b554c1c25dac6c4a5b640a3", - "0x919878b27f3671fc78396f11531c032f3e2bd132d04cc234fa4858676b15fb1db3051c0b1db9b4fc49038216f11321ce", - "0xb48cd67fb7f1242696c1f877da4bdf188eac676cd0e561fbac1a537f7b8229aff5a043922441d603a26aae56a15faee4", - "0xa3e0fdfd4d29ea996517a16f0370b54787fefe543c2fe73bfc6f9e560c1fd30dad8409859e2d7fa2d44316f24746c712", - "0x8bb156ade8faf149df7bea02c140c7e392a4742ae6d0394d880a849127943e6f26312033336d3b9fdc0092d71b5efe87", - "0x8845e5d5cc555ca3e0523244300f2c8d7e4d02aaebcb5bd749d791208856c209a6f84dd99fd55968c9f0ab5f82916707", - "0xa3e90bb5c97b07789c2f32dff1aec61d0a2220928202f5ad5355ae71f8249237799d6c8a22602e32e572cb12eabe0c17", - "0xb150bcc391884c996149dc3779ce71f15dda63a759ee9cc05871f5a8379dcb62b047098922c0f26c7bd04deb394c33f9", - "0x95cd4ad88d51f0f2efcfd0c2df802fe252bb9704d1afbf9c26a248df22d55da87bdfaf41d7bc6e5df38bd848f0b13f42", - "0xa05a49a31e91dff6a52ac8b9c2cfdd646a43f0d488253f9e3cfbce52f26667166bbb9b608fc358763a65cbf066cd6d05", - "0xa59c3c1227fdd7c2e81f5e11ef5c406da44662987bac33caed72314081e2eed66055d38137e01b2268e58ec85dd986c0", - "0xb7020ec3bd73a99861f0f1d88cf5a19abab1cbe14b7de77c9868398c84bb8e18dbbe9831838a96b6d6ca06e82451c67b", - "0x98d1ff2525e9718ee59a21d8900621636fcd873d9a564b8dceb4be80a194a0148daf1232742730b3341514b2e5a5436c", - "0x886d97b635975fc638c1b6afc493e5998ca139edba131b75b65cfe5a8e814f11bb678e0eeee5e6e5cd913ad3f2fefdfc", - "0x8fb9fd928d38d5d813b671c924edd56601dd7163b686c13f158645c2f869d9250f3859aa5463a39258c90fef0f41190a", - "0xaac35e1cd655c94dec3580bb3800bd9c2946c4a9856f7d725af15fbea6a2d8ca51c8ad2772abed60ee0e3fb9cb24046b", - "0xb8d71fa0fa05ac9e443c9b4929df9e7f09a919be679692682e614d24227e04894bfc14a5c73a62fb927fedff4a0e4aa7", - "0xa45a19f11fbbb531a704badbb813ed8088ab827c884ee4e4ebf363fa1132ff7cfa9d28be9c85b143e4f7cdbc94e7cf1a", - "0x82b54703a4f295f5471b255ab59dce00f0fe90c9fb6e06b9ee48b15c91d43f4e2ef4a96c3118aeb03b08767be58181bb", - "0x8283264c8e6d2a36558f0d145c18576b6600ff45ff99cc93eca54b6c6422993cf392668633e5df396b9331e873d457e5", - "0x8c549c03131ead601bc30eb6b9537b5d3beb7472f5bb1bcbbfd1e9f3704477f7840ab3ab7f7dc13bbbbcdff886a462d4", - "0xafbb0c520ac1b5486513587700ad53e314cb74bfbc12e0b5fbdcfdaac36d342e8b59856196a0d84a25cff6e6e1d17e76", - "0x89e4c22ffb51f2829061b3c7c1983c5c750cad158e3a825d46f7cf875677da5d63f653d8a297022b5db5845c9271b32b", - "0xafb27a86c4c2373088c96b9adf4433f2ebfc78ac5c526e9f0510670b6e4e5e0057c0a4f75b185e1a30331b9e805c1c15", - "0xa18e16b57445f88730fc5d3567bf5a176861dc14c7a08ed2996fe80eed27a0e7628501bcb78a1727c5e9ac55f29c12c4", - "0x93d61bf88b192d6825cf4e1120af1c17aa0f994d158b405e25437eaeefae049f7b721a206e7cc8a04fdc29d3c42580a1", - "0xa99f2995a2e3ed2fd1228d64166112038de2f516410aa439f4c507044e2017ea388604e2d0f7121256fadf7fbe7023d1", - "0x914fd91cffc23c32f1c6d0e98bf660925090d873367d543034654389916f65f552e445b0300b71b61b721a72e9a5983c", - "0xb42a578a7787b71f924e7def425d849c1c777156b1d4170a8ee7709a4a914e816935131afd9a0412c4cb952957b20828", - "0x82fb30590e84b9e45db1ec475a39971cf554dc01bcc7050bc89265740725c02e2be5a972168c5170c86ae83e5b0ad2c0", - "0xb14f8d8e1e93a84976289e0cf0dfa6f3a1809e98da16ee5c4932d0e1ed6bf8a07697fdd4dd86a3df84fb0003353cdcc0", - "0x85d7a2f4bda31aa2cb208b771fe03291a4ebdaf6f1dc944c27775af5caec412584c1f45bc741fca2a6a85acb3f26ad7d", - "0xaf02e56ce886ff2253bc0a68faad76f25ead84b2144e5364f3fb9b648f03a50ee9dc0b2c33ebacf7c61e9e43201ef9ef", - "0x87e025558c8a0b0abd06dfc350016847ea5ced7af2d135a5c9eec9324a4858c4b21510fb0992ec52a73447f24945058e", - "0x80fff0bafcd058118f5e7a4d4f1ae0912efeb281d2cbe4d34ba8945cc3dbe5d8baf47fb077343b90b8d895c90b297aca", - "0xb6edcf3a40e7b1c3c0148f47a263cd819e585a51ef31c2e35a29ce6f04c53e413f743034c0d998d9c00a08ba00166f31", - "0xabb87ed86098c0c70a76e557262a494ff51a30fb193f1c1a32f8e35eafa34a43fcc07aa93a3b7a077d9e35afa07b1a3d", - "0xa280214cd3bb0fb7ecd2d8bcf518cbd9078417f2b91d2533ec2717563f090fb84f2a5fcfdbbeb2a2a1f8a71cc5aa5941", - "0xa63083ca7238ea2b57d15a475963cf1d4f550d8cd76db290014a0461b90351f1f26a67d674c837b0b773b330c7c3d534", - "0xa8fa39064cb585ece5263e2f42f430206476bf261bd50f18d2b694889bd79d04d56410664cecad62690e5c5a20b3f6ff", - "0x85ba52ce9d700a5dcf6c5b00559acbe599d671ce5512467ff4b6179d7fad550567ce2a9c126a50964e3096458ea87920", - "0xb913501e1008f076e5eac6d883105174f88b248e1c9801e568fefaffa1558e4909364fc6d9512aa4d125cbd7cc895f05", - "0x8eb33b5266c8f2ed4725a6ad147a322e44c9264cf261c933cbbe230a43d47fca0f29ec39756b20561dabafadd5796494", - "0x850ebc8b661a04318c9db5a0515066e6454fa73865aa4908767a837857ecd717387f614acb614a88e075d4edc53a2f5a", - "0xa08d6b92d866270f29f4ce23a3f5d99b36b1e241a01271ede02817c8ec3f552a5c562db400766c07b104a331835c0c64", - "0x8131804c89bb3e74e9718bfc4afa547c1005ff676bd4db9604335032b203390cfa54478d45c6c78d1fe31a436ed4be9f", - "0x9106d94f23cc1eacec8316f16d6f0a1cc160967c886f51981fdb9f3f12ee1182407d2bb24e5b873de58cb1a3ee915a6b", - "0xa13806bfc3eae7a7000c9d9f1bd25e10218d4e67f59ae798b145b098bca3edad2b1040e3fc1e6310e612fb8818f459ac", - "0x8c69fbca502046cb5f6db99900a47b34117aef3f4b241690cdb3b84ca2a2fc7833e149361995dc41fa78892525bce746", - "0x852c473150c91912d58ecb05769222fa18312800c3f56605ad29eec9e2d8667b0b81c379048d3d29100ed2773bb1f3c5", - "0xb1767f6074426a00e01095dbb1795beb4e4050c6411792cbad6537bc444c3165d1058bafd1487451f9c5ddd209e0ae7e", - "0x80c600a5fe99354ce59ff0f84c760923dc8ff66a30bf47dc0a086181785ceb01f9b951c4e66df800ea6d705e8bc47055", - "0xb5cf19002fbc88a0764865b82afcb4d64a50196ea361e5c71dff7de084f4dcbbc34ec94a45cc9e0247bd51da565981aa", - "0x93e67a254ea8ce25e112d93cc927fadaa814152a2c4ec7d9a56eaa1ed47aec99b7e9916b02e64452cc724a6641729bbb", - "0xace70b32491bda18eee4a4d041c3bc9effae9340fe7e6c2f5ad975ee0874c17f1a7da7c96bd85fccff9312c518fac6e9", - "0xab4cfa02065017dd7f1aadc66f2c92f78f0f11b8597c03a5d69d82cb2eaf95a4476a836ac102908f137662472c8d914b", - "0xa40b8cd8deb8ae503d20364d64cab7c2801b7728a9646ed19c65edea6a842756a2f636283494299584ad57f4bb12cd0b", - "0x8594e11d5fc2396bcd9dbf5509ce4816dbb2b7305168021c426171fb444d111da5a152d6835ad8034542277011c26c0e", - "0x8024de98c26b4c994a66628dc304bb737f4b6859c86ded552c5abb81fd4c6c2e19d5a30beed398a694b9b2fdea1dd06a", - "0x8843f5872f33f54df8d0e06166c1857d733995f67bc54abb8dfa94ad92407cf0179bc91b0a50bbb56cdc2b350d950329", - "0xb8bab44c7dd53ef9edf497dcb228e2a41282c90f00ba052fc52d57e87b5c8ab132d227af1fcdff9a12713d1f980bcaae", - "0x982b4d7b29aff22d527fd82d2a52601d95549bfb000429bb20789ed45e5abf1f4b7416c7b7c4b79431eb3574b29be658", - "0x8eb1f571b6a1878e11e8c1c757e0bc084bab5e82e897ca9be9b7f4b47b91679a8190bf0fc8f799d9b487da5442415857", - "0xa6e74b588e5af935c8b243e888582ef7718f8714569dd4992920740227518305eb35fab674d21a5551cca44b3e511ef2", - "0xa30fc2f3a4cb4f50566e82307de73cd7bd8fe2c1184e9293c136a9b9e926a018d57c6e4f308c95b9eb8299e94d90a2a1", - "0xa50c5869ca5d2b40722c056a32f918d47e0b65ca9d7863ca7d2fb4a7b64fe523fe9365cf0573733ceaadebf20b48fff8", - "0x83bbdd32c04d17581418cf360749c7a169b55d54f2427390defd9f751f100897b2d800ce6636c5bbc046c47508d60c8c", - "0xa82904bdf614de5d8deaff688c8a5e7ac5b3431687acbcda8fa53960b7c417a39c8b2e462d7af91ce6d79260f412db8e", - "0xa4362e31ff4b05d278b033cf5eebea20de01714ae16d4115d04c1da4754269873afc8171a6f56c5104bfd7b0db93c3e7", - "0xb5b8daa63a3735581e74a021b684a1038cea77168fdb7fdf83c670c2cfabcfc3ab2fc7359069b5f9048188351aef26b5", - "0xb48d723894b7782d96ac8433c48faca1bdfa5238019c451a7f47d958097cce3ae599b876cf274269236b9d6ff8b6d7ca", - "0x98ffff6a61a3a6205c7820a91ca2e7176fab5dba02bc194c4d14942ac421cb254183c705506ab279e4f8db066f941c6c", - "0xae7db24731da2eaa6efc4f7fcba2ecc26940ddd68038dce43acf2cee15b72dc4ef42a7bfdd32946d1ed78786dd7696b3", - "0xa656db14f1de9a7eb84f6301b4acb2fbf78bfe867f48a270e416c974ab92821eb4df1cb881b2d600cfed0034ac784641", - "0xaa315f8ecba85a5535e9a49e558b15f39520fce5d4bf43131bfbf2e2c9dfccc829074f9083e8d49f405fb221d0bc4c3c", - "0x90bffba5d9ff40a62f6c8e9fc402d5b95f6077ed58d030c93e321b8081b77d6b8dac3f63a92a7ddc01585cf2c127d66c", - "0xabdd733a36e0e0f05a570d0504e73801bf9b5a25ff2c78786f8b805704997acb2e6069af342538c581144d53149fa6d3", - "0xb4a723bb19e8c18a01bd449b1bb3440ddb2017f10bb153da27deb7a6a60e9bb37619d6d5435fbb1ba617687838e01dd0", - "0x870016b4678bab3375516db0187a2108b2e840bae4d264b9f4f27dbbc7cc9cac1d7dc582d7a04d6fd1ed588238e5e513", - "0x80d33d2e20e8fc170aa3cb4f69fffb72aeafb3b5bb4ea0bc79ab55da14142ca19b2d8b617a6b24d537366e3b49cb67c3", - "0xa7ee76aec273aaae03b3b87015789289551969fb175c11557da3ab77e39ab49d24634726f92affae9f4d24003050d974", - "0x8415ea4ab69d779ebd42d0fe0c6aef531d6a465a5739e429b1fcf433ec45aa8296c527e965a20f0ec9f340c9273ea3cf", - "0x8c7662520794e8b4405d0b33b5cac839784bc86a5868766c06cbc1fa306dbe334978177417b31baf90ce7b0052a29c56", - "0x902b2abecc053a3dbdea9897ee21e74821f3a1b98b2d560a514a35799f4680322550fd3a728d4f6d64e1de98033c32b8", - "0xa05e84ed9ecab8d508d670c39f2db61ad6e08d2795ec32a3c9d0d3737ef3801618f4fc2a95f90ec2f068606131e076c5", - "0x8b9208ff4d5af0c2e3f53c9375da666773ac57197dfabb0d25b1c8d0588ba7f3c15ee9661bb001297f322ea2fbf6928b", - "0xa3c827741b34a03254d4451b5ab74a96f2b9f7fb069e2f5adaf54fd97cc7a4d516d378db5ca07da87d8566d6eef13726", - "0x8509d8a3f4a0ed378e0a1e28ea02f6bf1d7f6c819c6c2f5297c7df54c895b848f841653e32ba2a2c22c2ff739571acb8", - "0xa0ce988b7d3c40b4e496aa83a09e4b5472a2d98679622f32bea23e6d607bc7de1a5374fb162bce0549a67dad948519be", - "0xaa8a3dd12bd60e3d2e05f9c683cdcb8eab17fc59134815f8d197681b1bcf65108cba63ac5c58ee632b1e5ed6bba5d474", - "0x8b955f1d894b3aefd883fb4b65f14cd37fc2b9db77db79273f1700bef9973bf3fd123897ea2b7989f50003733f8f7f21", - "0xac79c00ddac47f5daf8d9418d798d8af89fc6f1682e7e451f71ea3a405b0d36af35388dd2a332af790bc83ca7b819328", - "0xa0d44dd2a4438b809522b130d0938c3fe7c5c46379365dbd1810a170a9aa5818e1c783470dd5d0b6d4ac7edbb7330910", - "0xa30b69e39ad43dd540a43c521f05b51b5f1b9c4eed54b8162374ae11eac25da4f5756e7b70ce9f3c92c2eeceee7431ed", - "0xac43220b762c299c7951222ea19761ab938bf38e4972deef58ed84f4f9c68c230647cf7506d7cbfc08562fcca55f0485", - "0xb28233b46a8fb424cfa386a845a3b5399d8489ceb83c8f3e05c22c934798d639c93718b7b68ab3ce24c5358339e41cbb", - "0xac30d50ee8ce59a10d4b37a3a35e62cdb2273e5e52232e202ca7d7b8d09d28958ee667fae41a7bb6cdc6fe8f6e6c9c85", - "0xb199842d9141ad169f35cc7ff782b274cbaa645fdb727761e0a89edbf0d781a15f8218b4bf4eead326f2903dd88a9cc1", - "0x85e018c7ddcad34bb8285a737c578bf741ccd547e68c734bdb3808380e12c5d4ef60fc896b497a87d443ff9abd063b38", - "0x8c856e6ba4a815bdb891e1276f93545b7072f6cb1a9aa6aa5cf240976f29f4dee01878638500a6bf1daf677b96b54343", - "0xb8a47555fa8710534150e1a3f13eab33666017be6b41005397afa647ea49708565f2b86b77ad4964d140d9ced6b4d585", - "0x8cd1f1db1b2f4c85a3f46211599caf512d5439e2d8e184663d7d50166fd3008f0e9253272f898d81007988435f715881", - "0xb1f34b14612c973a3eceb716dc102b82ab18afef9de7630172c2780776679a7706a4874e1df3eaadf541fb009731807f", - "0xb25464af9cff883b55be2ff8daf610052c02df9a5e147a2cf4df6ce63edcdee6dc535c533590084cc177da85c5dc0baa", - "0x91c3c4b658b42d8d3448ae1415d4541d02379a40dc51e36a59bd6e7b9ba3ea51533f480c7c6e8405250ee9b96a466c29", - "0x86dc027b95deb74c36a58a1333a03e63cb5ae22d3b29d114cfd2271badb05268c9d0c819a977f5e0c6014b00c1512e3a", - "0xae0e6ff58eb5fa35da5107ebeacf222ab8f52a22bb1e13504247c1dfa65320f40d97b0e6b201cb6613476687cb2f0681", - "0x8f13415d960b9d7a1d93ef28afc2223e926639b63bdefce0f85e945dfc81670a55df288893a0d8b3abe13c5708f82f91", - "0x956f67ca49ad27c1e3a68c1faad5e7baf0160c459094bf6b7baf36b112de935fdfd79fa4a9ea87ea8de0ac07272969f4", - "0x835e45e4a67df9fb51b645d37840b3a15c171d571a10b03a406dd69d3c2f22df3aa9c5cbe1e73f8d767ce01c4914ea9a", - "0x919b938e56d4b32e2667469d0bdccb95d9dda3341aa907683ee70a14bbbe623035014511c261f4f59b318b610ac90aa3", - "0x96b48182121ccd9d689bf1dfdc228175564cd68dc904a99c808a7f0053a6f636c9d953e12198bdf2ea49ea92772f2e18", - "0xac5e5a941d567fa38fdbcfa8cf7f85bb304e3401c52d88752bcd516d1fa9bac4572534ea2205e38423c1df065990790f", - "0xac0bd594fb85a8d4fc26d6df0fa81f11919401f1ecf9168b891ec7f061a2d9368af99f7fd8d9b43b2ce361e7b8482159", - "0x83d92c69ca540d298fe80d8162a1c7af3fa9b49dfb69e85c1d136a3ec39fe419c9fa78e0bb6d96878771fbd37fe92e40", - "0xb35443ae8aa66c763c2db9273f908552fe458e96696b90e41dd509c17a5c04ee178e3490d9c6ba2dc0b8f793c433c134", - "0x923b2d25aa45b2e580ffd94cbb37dc8110f340f0f011217ee1bd81afb0714c0b1d5fb4db86006cdd2457563276f59c59", - "0x96c9125d38fca1a61ac21257b696f8ac3dae78def50285e44d90ea293d591d1c58f703540a7e4e99e070afe4646bbe15", - "0xb57946b2332077fbcdcb406b811779aefd54473b5559a163cd65cb8310679b7e2028aa55c12a1401fdcfcac0e6fae29a", - "0x845daedc5cf972883835d7e13c937b63753c2200324a3b8082a6c4abb4be06c5f7c629d4abe4bfaf1d80a1f073eb6ce6", - "0x91a55dfd0efefcd03dc6dacc64ec93b8d296cb83c0ee72400a36f27246e7f2a60e73b7b70ba65819e9cfb73edb7bd297", - "0x8874606b93266455fe8fdd25df9f8d2994e927460af06f2e97dd4d2d90db1e6b06d441b72c2e76504d753badca87fb37", - "0x8ee99e6d231274ff9252c0f4e84549da173041299ad1230929c3e3d32399731c4f20a502b4a307642cac9306ccd49d3c", - "0x8836497714a525118e20849d6933bb8535fb6f72b96337d49e3133d936999c90a398a740f42e772353b5f1c63581df6d", - "0xa6916945e10628f7497a6cdc5e2de113d25f7ade3e41e74d3de48ccd4fce9f2fa9ab69645275002e6f49399b798c40af", - "0x9597706983107eb23883e0812e1a2c58af7f3499d50c6e29b455946cb9812fde1aa323d9ed30d1c0ffd455abe32303cd", - "0xa24ee89f7f515cc33bdbdb822e7d5c1877d337f3b2162303cfc2dae028011c3a267c5cb4194afa63a4856a6e1c213448", - "0x8cd25315e4318801c2776824ae6e7d543cb85ed3bc2498ba5752df2e8142b37653cf9e60104d674be3aeb0a66912e97a", - "0xb5085ecbe793180b40dbeb879f4c976eaaccaca3a5246807dced5890e0ed24d35f3f86955e2460e14fb44ff5081c07ba", - "0x960188cc0b4f908633a6840963a6fa2205fc42c511c6c309685234911c5304ef4c304e3ae9c9c69daa2fb6a73560c256", - "0xa32d0a70bf15d569b4cda5aebe3e41e03c28bf99cdd34ffa6c5d58a097f322772acca904b3a47addb6c7492a7126ebac", - "0x977f72d06ad72d4aa4765e0f1f9f4a3231d9f030501f320fe7714cc5d329d08112789fa918c60dd7fdb5837d56bb7fc6", - "0x99fa038bb0470d45852bb871620d8d88520adb701712fcb1f278fed2882722b9e729e6cdce44c82caafad95e37d0e6f7", - "0xb855e8f4fc7634ada07e83b6c719a1e37acb06394bc8c7dcab7747a8c54e5df3943915f021364bd019fdea103864e55f", - "0x88bc2cd7458532e98c596ef59ea2cf640d7cc31b4c33cef9ed065c078d1d4eb49677a67de8e6229cc17ea48bace8ee5a", - "0xaaa78a3feaa836d944d987d813f9b9741afb076e6aca1ffa42682ab06d46d66e0c07b8f40b9dbd63e75e81efa1ef7b08", - "0xb7b080420cc4d808723b98b2a5b7b59c81e624ab568ecdfdeb8bf3aa151a581b6f56e983ef1b6f909661e25db40b0c69", - "0xabee85c462ac9a2c58e54f06c91b3e5cd8c5f9ab5b5deb602b53763c54826ed6deb0d6db315a8d7ad88733407e8d35e2", - "0x994d075c1527407547590df53e9d72dd31f037c763848d1662eebd4cefec93a24328c986802efa80e038cb760a5300f5", - "0xab8777640116dfb6678e8c7d5b36d01265dfb16321abbfc277da71556a34bb3be04bc4ae90124ed9c55386d2bfb3bda0", - "0x967e3a828bc59409144463bcf883a3a276b5f24bf3cbfdd7a42343348cba91e00b46ac285835a9b91eef171202974204", - "0x875a9f0c4ffe5bb1d8da5e3c8e41d0397aa6248422a628bd60bfae536a651417d4e8a7d2fb98e13f2dad3680f7bd86d3", - "0xacaa330c3e8f95d46b1880126572b238dbb6d04484d2cd4f257ab9642d8c9fc7b212188b9c7ac9e0fd135c520d46b1bf", - "0xaceb762edbb0f0c43dfcdb01ea7a1ac5918ca3882b1e7ebc4373521742f1ed5250d8966b498c00b2b0f4d13212e6dd0b", - "0x81d072b4ad258b3646f52f399bced97c613b22e7ad76373453d80b1650c0ca87edb291a041f8253b649b6e5429bb4cff", - "0x980a47d27416ac39c7c3a0ebe50c492f8c776ea1de44d5159ac7d889b6d554357f0a77f0e5d9d0ff41aae4369eba1fc2", - "0x8b4dfd5ef5573db1476d5e43aacfb5941e45d6297794508f29c454fe50ea622e6f068b28b3debe8635cf6036007de2e3", - "0xa60831559d6305839515b68f8c3bc7abbd8212cc4083502e19dd682d56ca37c9780fc3ce4ec2eae81ab23b221452dc57", - "0x951f6b2c1848ced9e8a2339c65918e00d3d22d3e59a0a660b1eca667d18f8430d737884e9805865ef3ed0fe1638a22d9", - "0xb02e38fe790b492aa5e89257c4986c9033a8b67010fa2add9787de857d53759170fdd67715ca658220b4e14b0ca48124", - "0xa51007e4346060746e6b0e4797fc08ef17f04a34fe24f307f6b6817edbb8ce2b176f40771d4ae8a60d6152cbebe62653", - "0xa510005b05c0b305075b27b243c9d64bcdce85146b6ed0e75a3178b5ff9608213f08c8c9246f2ca6035a0c3e31619860", - "0xaaff4ef27a7a23be3419d22197e13676d6e3810ceb06a9e920d38125745dc68a930f1741c9c2d9d5c875968e30f34ab5", - "0x864522a9af9857de9814e61383bebad1ba9a881696925a0ea6bfc6eff520d42c506bbe5685a9946ed710e889765be4a0", - "0xb63258c080d13f3b7d5b9f3ca9929f8982a6960bdb1b0f8676f4dca823971601672f15e653917bf5d3746bb220504913", - "0xb51ce0cb10869121ae310c7159ee1f3e3a9f8ad498827f72c3d56864808c1f21fa2881788f19ece884d3f705cd7bd0c5", - "0x95d9cecfc018c6ed510e441cf84c712d9909c778c16734706c93222257f64dcd2a9f1bd0b400ca271e22c9c487014274", - "0x8beff4d7d0140b86380ff4842a9bda94c2d2be638e20ac68a4912cb47dbe01a261857536375208040c0554929ced1ddc", - "0x891ff49258749e2b57c1e9b8e04b12c77d79c3308b1fb615a081f2aacdfb4b39e32d53e069ed136fdbd43c53b87418fa", - "0x9625cad224e163d387738825982d1e40eeff35fe816d10d7541d15fdc4d3eee48009090f3faef4024b249205b0b28f72", - "0x8f3947433d9bd01aa335895484b540a9025a19481a1c40b4f72dd676bfcf332713714fd4010bde936eaf9470fd239ed0", - "0xa00ec2d67789a7054b53f0e858a8a232706ccc29a9f3e389df7455f1a51a2e75801fd78469a13dbc25d28399ae4c6182", - "0xa3f65884506d4a62b8775a0ea0e3d78f5f46bc07910a93cd604022154eabdf1d73591e304d61edc869e91462951975e1", - "0xa14eef4fd5dfac311713f0faa9a60415e3d30b95a4590cbf95f2033dffb4d16c02e7ceff3dcd42148a4e3bc49cce2dd4", - "0x8afa11c0eef3c540e1e3460bc759bb2b6ea90743623f88e62950c94e370fe4fd01c22b6729beba4dcd4d581198d9358f", - "0xafb05548a69f0845ffcc5f5dc63e3cdb93cd270f5655173b9a950394b0583663f2b7164ba6df8d60c2e775c1d9f120af", - "0x97f179e01a947a906e1cbeafa083960bc9f1bade45742a3afee488dfb6011c1c6e2db09a355d77f5228a42ccaa7bdf8e", - "0x8447fca4d35f74b3efcbd96774f41874ca376bf85b79b6e66c92fa3f14bdd6e743a051f12a7fbfd87f319d1c6a5ce217", - "0xa57ca39c23617cd2cf32ff93b02161bd7baf52c4effb4679d9d5166406e103bc8f3c6b5209e17c37dbb02deb8bc72ddd", - "0x9667c7300ff80f0140be002b0e36caab07aaee7cce72679197c64d355e20d96196acaf54e06e1382167d081fe6f739c1", - "0x828126bb0559ce748809b622677267ca896fa2ee76360fd2c02990e6477e06a667241379ca7e65d61a5b64b96d7867de", - "0x8b8835dea6ba8cf61c91f01a4b3d2f8150b687a4ee09b45f2e5fc8f80f208ae5d142d8e3a18153f0722b90214e60c5a7", - "0xa98e8ff02049b4da386e3ee93db23bbb13dfeb72f1cfde72587c7e6d962780b7671c63e8ac3fbaeb1a6605e8d79e2f29", - "0x87a4892a0026d7e39ef3af632172b88337cb03669dea564bcdb70653b52d744730ebb5d642e20cb627acc9dbb547a26b", - "0x877352a22fc8052878a57effc159dac4d75fe08c84d3d5324c0bab6d564cdf868f33ceee515eee747e5856b62cfa0cc7", - "0x8b801ba8e2ff019ee62f64b8cb8a5f601fc35423eb0f9494b401050103e1307dc584e4e4b21249cd2c686e32475e96c3", - "0xa9e7338d6d4d9bfec91b2af28a8ed13b09415f57a3a00e5e777c93d768fdb3f8e4456ae48a2c6626b264226e911a0e28", - "0x99c05fedf40ac4726ed585d7c1544c6e79619a0d3fb6bda75a08c7f3c0008e8d5e19ed4da48de3216135f34a15eba17c", - "0xa61cce8a1a8b13a4a650fdbec0eeea8297c352a8238fb7cac95a0df18ed16ee02a3daa2de108fa122aca733bd8ad7855", - "0xb97f37da9005b440b4cb05870dd881bf8491fe735844f2d5c8281818583b38e02286e653d9f2e7fa5e74c3c3eb616540", - "0xa72164a8554da8e103f692ac5ebb4aece55d5194302b9f74b6f2a05335b6e39beede0bf7bf8c5bfd4d324a784c5fb08c", - "0xb87e8221c5341cd9cc8bb99c10fe730bc105550f25ed4b96c0d45e6142193a1b2e72f1b3857373a659b8c09be17b3d91", - "0xa41fb1f327ef91dcb7ac0787918376584890dd9a9675c297c45796e32d6e5985b12f9b80be47fc3a8596c245f419d395", - "0x90dafa3592bdbb3465c92e2a54c2531822ba0459d45d3e7a7092fa6b823f55af28357cb51896d4ec2d66029c82f08e26", - "0xa0a9adc872ebc396557f484f1dd21954d4f4a21c4aa5eec543f5fa386fe590839735c01f236574f7ff95407cd12de103", - "0xb8c5c940d58be7538acf8672852b5da3af34f82405ef2ce8e4c923f1362f97fc50921568d0fd2fe846edfb0823e62979", - "0x85aaf06a8b2d0dac89dafd00c28533f35dbd074978c2aaa5bef75db44a7b12aeb222e724f395513b9a535809a275e30b", - "0x81f3cbe82fbc7028c26a6c1808c604c63ba023a30c9f78a4c581340008dbda5ec07497ee849a2183fcd9124f7936af32", - "0xa11ac738de75fd60f15a34209d3825d5e23385796a4c7fc5931822f3f380af977dd0f7b59fbd58eed7777a071e21b680", - "0x85a279c493de03db6fa6c3e3c1b1b29adc9a8c4effc12400ae1128da8421954fa8b75ad19e5388fe4543b76fb0812813", - "0x83a217b395d59ab20db6c4adb1e9713fc9267f5f31a6c936042fe051ce8b541f579442f3dcf0fa16b9e6de9fd3518191", - "0x83a0b86e7d4ed8f9ccdc6dfc8ff1484509a6378fa6f09ed908e6ab9d1073f03011dc497e14304e4e3d181b57de06a5ab", - "0xa63ad69c9d25704ce1cc8e74f67818e5ed985f8f851afa8412248b2df5f833f83b95b27180e9e7273833ed0d07113d3b", - "0x99b1bc2021e63b561fe44ddd0af81fcc8627a91bfeecbbc989b642bc859abc0c8d636399701aad7bbaf6a385d5f27d61", - "0xb53434adb66f4a807a6ad917c6e856321753e559b1add70824e5c1e88191bf6993fccb9b8b911fc0f473fb11743acacd", - "0x97ed3b9e6fb99bf5f945d4a41f198161294866aa23f2327818cdd55cb5dc4c1a8eff29dd8b8d04902d6cd43a71835c82", - "0xb1e808260e368a18d9d10bdea5d60223ba1713b948c782285a27a99ae50cc5fc2c53d407de07155ecc16fb8a36d744a0", - "0xa3eb4665f18f71833fec43802730e56b3ee5a357ea30a888ad482725b169d6f1f6ade6e208ee081b2e2633079b82ba7d", - "0xab8beb2c8353fc9f571c18fdd02bdb977fc883313469e1277b0372fbbb33b80dcff354ca41de436d98d2ed710faa467e", - "0xaa9071cfa971e4a335a91ad634c98f2be51544cb21f040f2471d01bb97e1df2277ae1646e1ea8f55b7ba9f5c8c599b39", - "0x80b7dbfdcaf40f0678012acc634eba44ea51181475180d9deb2050dc4f2de395289edd0223018c81057ec79b04b04c49", - "0x89623d7f6cb17aa877af14de842c2d4ab7fd576d61ddd7518b5878620a01ded40b6010de0da3cdf31d837eecf30e9847", - "0xa773bb024ae74dd24761f266d4fb27d6fd366a8634febe8235376b1ae9065c2fe12c769f1d0407867dfbe9f5272c352f", - "0x8455a561c3aaa6ba64c881a5e13921c592b3a02e968f4fb24a2243c36202795d0366d9cc1a24e916f84d6e158b7aeac7", - "0x81d8bfc4b283cf702a40b87a2b96b275bdbf0def17e67d04842598610b67ea08c804d400c3e69fa09ea001eaf345b276", - "0xb8f8f82cb11fea1c99467013d7e167ff03deb0c65a677fab76ded58826d1ba29aa7cf9fcd7763615735ea3ad38e28719", - "0x89a6a04baf9cccc1db55179e1650b1a195dd91fb0aebc197a25143f0f393524d2589975e3fbfc2547126f0bced7fd6f2", - "0xb81b2162df045390f04df07cbd0962e6b6ca94275a63edded58001a2f28b2ae2af2c7a6cba4ecd753869684e77e7e799", - "0xa3757f722776e50de45c62d9c4a2ee0f5655a512344c4cbec542d8045332806568dd626a719ef21a4eb06792ca70f204", - "0x8c5590df96ec22179a4e8786de41beb44f987a1dcc508eb341eecbc0b39236fdfad47f108f852e87179ccf4e10091e59", - "0x87502f026ed4e10167419130b88c3737635c5b9074c364e1dd247cef5ef0fc064b4ae99b187e33301e438bbd2fe7d032", - "0xaf925a2165e980ced620ff12289129fe17670a90ae0f4db9d4b39bd887ccb1f5d2514ac9ecf910f6390a8fc66bd5be17", - "0x857fca899828cf5c65d26e3e8a6e658542782fc72762b3b9c73514919f83259e0f849a9d4838b40dc905fe43024d0d23", - "0x87ffebdbfb69a9e1007ebac4ffcb4090ff13705967b73937063719aa97908986effcb7262fdadc1ae0f95c3690e3245d", - "0xa9ff6c347ac6f4c6ab993b748802e96982eaf489dc69032269568412fc9a79e7c2850dfc991b28211b3522ee4454344b", - "0xa65b3159df4ec48bebb67cb3663cd744027ad98d970d620e05bf6c48f230fa45bf17527fe726fdf705419bb7a1bb913e", - "0x84b97b1e6408b6791831997b03cd91f027e7660fd492a93d95daafe61f02427371c0e237c75706412f442991dfdff989", - "0xab761c26527439b209af0ae6afccd9340bbed5fbe098734c3145b76c5d2cd7115d9227b2eb523882b7317fbb09180498", - "0xa0479a8da06d7a69c0b0fee60df4e691c19c551f5e7da286dab430bfbcabf31726508e20d26ea48c53365a7f00a3ad34", - "0xa732dfc9baa0f4f40b5756d2e8d8937742999623477458e0bc81431a7b633eefc6f53b3b7939fe0a020018549c954054", - "0x901502436a1169ba51dc479a5abe7c8d84e0943b16bc3c6a627b49b92cd46263c0005bc324c67509edd693f28e612af1", - "0xb627aee83474e7f84d1bab9b7f6b605e33b26297ac6bbf52d110d38ba10749032bd551641e73a383a303882367af429b", - "0x95108866745760baef4a46ef56f82da6de7e81c58b10126ebd2ba2cd13d339f91303bf2fb4dd104a6956aa3b13739503", - "0x899ed2ade37236cec90056f3569bc50f984f2247792defafcceb49ad0ca5f6f8a2f06573705300e07f0de0c759289ff5", - "0xa9f5eee196d608efe4bcef9bf71c646d27feb615e21252cf839a44a49fd89da8d26a758419e0085a05b1d59600e2dc42", - "0xb36c6f68fed6e6c85f1f4a162485f24817f2843ec5cbee45a1ebfa367d44892e464949c6669f7972dc7167af08d55d25", - "0xaaaede243a9a1b6162afbc8f571a52671a5a4519b4062e3f26777664e245ba873ed13b0492c5dbf0258c788c397a0e9e", - "0x972b4fb39c31cbe127bf9a32a5cc10d621ebdd9411df5e5da3d457f03b2ab2cd1f6372d8284a4a9400f0b06ecdbfd38e", - "0x8f6ca1e110e959a4b1d9a5ce5f212893cec21db40d64d5ac4d524f352d72198f923416a850bf845bc5a22a79c0ea2619", - "0xa0f3c93b22134f66f04b2553a53b738644d1665ceb196b8494b315a4c28236fb492017e4a0de4224827c78e42f9908b7", - "0x807fb5ee74f6c8735b0b5ca07e28506214fe4047dbeb00045d7c24f7849e98706aea79771241224939cb749cf1366c7d", - "0x915eb1ff034224c0b645442cdb7d669303fdc00ca464f91aaf0b6fde0b220a3a74ff0cb043c26c9f3a5667b3fdaa9420", - "0x8fda6cef56ed33fefffa9e6ac8e6f76b1af379f89761945c63dd448801f7bb8ca970504a7105fac2f74f652ccff32327", - "0x87380cffdcffb1d0820fa36b63cc081e72187f86d487315177d4d04da4533eb19a0e2ff6115ceab528887819c44a5164", - "0x8cd89e03411a18e7f16f968b89fb500c36d47d229f6487b99e62403a980058db5925ce249206743333538adfad168330", - "0x974451b1df33522ce7056de9f03e10c70bf302c44b0741a59df3d6877d53d61a7394dcee1dd46e013d7cb9d73419c092", - "0x98c35ddf645940260c490f384a49496a7352bb8e3f686feed815b1d38f59ded17b1ad6e84a209e773ed08f7b8ff1e4c2", - "0x963f386cf944bb9b2ddebb97171b64253ea0a2894ac40049bdd86cda392292315f3a3d490ca5d9628c890cfb669f0acb", - "0x8d507712152babd6d142ee682638da8495a6f3838136088df9424ef50d5ec28d815a198c9a4963610b22e49b4cdf95e9", - "0x83d4bc6b0be87c8a4f1e9c53f257719de0c73d85b490a41f7420e777311640937320557ff2f1d9bafd1daaa54f932356", - "0x82f5381c965b7a0718441131c4d13999f4cdce637698989a17ed97c8ea2e5bdb5d07719c5f7be8688edb081b23ede0f4", - "0xa6ebecab0b72a49dfd01d69fa37a7f74d34fb1d4fef0aa10e3d6fceb9eccd671225c230af89f6eb514250e41a5f91f52", - "0x846d185bdad6e11e604df7f753b7a08a28b643674221f0e750ebdb6b86ec584a29c869e131bca868972a507e61403f6a", - "0x85a98332292acb744bd1c0fd6fdcf1f889a78a2c9624d79413ffa194cc8dfa7821a4b60cde8081d4b5f71f51168dd67f", - "0x8f7d97c3b4597880d73200d074eb813d95432306e82dafc70b580b8e08cb8098b70f2d07b4b3ac6a4d77e92d57035031", - "0x8185439c8751e595825d7053518cbe121f191846a38d4dbcb558c3f9d7a3104f3153401adaaaf27843bbe2edb504bfe3", - "0xb3c00d8ece1518fca6b1215a139b0a0e26d9cba1b3a424f7ee59f30ce800a5db967279ed60958dd1f3ee69cf4dd1b204", - "0xa2e6cb6978e883f9719c3c0d44cfe8de0cc6f644b98f98858433bea8bbe7b612c8aca5952fccce4f195f9d54f9722dc2", - "0x99663087e3d5000abbec0fbda4e7342ec38846cc6a1505191fb3f1a337cb369455b7f8531a6eb8b0f7b2c4baf83cbe2b", - "0xab0836c6377a4dbc7ca6a4d6cf021d4cd60013877314dd05f351706b128d4af6337711ed3443cb6ca976f40d74070a9a", - "0x87abfd5126152fd3bac3c56230579b489436755ea89e0566aa349490b36a5d7b85028e9fb0710907042bcde6a6f5d7e3", - "0x974ba1033f75f60e0cf7c718a57ae1da3721cf9d0fb925714c46f027632bdd84cd9e6de4cf4d00bc55465b1c5ebb7384", - "0xa607b49d73689ac64f25cec71221d30d53e781e1100d19a2114a21da6507a60166166369d860bd314acb226596525670", - "0xa7c2b0b915d7beba94954f2aa7dd08ec075813661e2a3ecca5d28a0733e59583247fed9528eb28aba55b972cdbaf06eb", - "0xb8b3123e44128cc8efbe3270f2f94e50ca214a4294c71c3b851f8cbb70cb67fe9536cf07d04bf7fe380e5e3a29dd3c15", - "0xa59a07e343b62ad6445a0859a32b58c21a593f9ddbfe52049650f59628c93715aa1f4e1f45b109321756d0eeec8a5429", - "0x94f51f8a4ed18a6030d0aaa8899056744bd0e9dc9ac68f62b00355cddab11da5da16798db75f0bfbce0e5bdfe750c0b6", - "0x97460a97ca1e1fa5ce243b81425edc0ec19b7448e93f0b55bc9785eedeeafe194a3c8b33a61a5c72990edf375f122777", - "0x8fa859a089bc17d698a7ee381f37ce9beadf4e5b44fce5f6f29762bc04f96faff5d58c48c73631290325f05e9a1ecf49", - "0xabdf38f3b20fc95eff31de5aa9ef1031abfa48f1305ee57e4d507594570401503476d3bcc493838fc24d6967a3082c7f", - "0xb8914bfb82815abb86da35c64d39ab838581bc0bf08967192697d9663877825f2b9d6fbdcf9b410463482b3731361aef", - "0xa8187f9d22b193a5f578999954d6ec9aa9b32338ccadb8a3e1ce5bad5ea361d69016e1cdfac44e9d6c54e49dd88561b9", - "0xaac262cb7cba7fd62c14daa7b39677cabc1ef0947dd06dd89cac8570006a200f90d5f0353e84f5ff03179e3bebe14231", - "0xa630ef5ece9733b8c46c0a2df14a0f37647a85e69c63148e79ffdcc145707053f9f9d305c3f1cf3c7915cb46d33abd07", - "0xb102c237cb2e254588b6d53350dfda6901bd99493a3fbddb4121d45e0b475cf2663a40d7b9a75325eda83e4ba1e68cb3", - "0x86a930dd1ddcc16d1dfa00aa292cb6c2607d42c367e470aa920964b7c17ab6232a7108d1c2c11fc40fb7496547d0bbf8", - "0xa832fdc4500683e72a96cce61e62ac9ee812c37fe03527ad4cf893915ca1962cee80e72d4f82b20c8fc0b764376635a1", - "0x88ad985f448dabb04f8808efd90f273f11f5e6d0468b5489a1a6a3d77de342992a73eb842d419034968d733f101ff683", - "0x98a8538145f0d86f7fbf9a81c9140f6095c5bdd8960b1c6f3a1716428cd9cca1bf8322e6d0af24e6169abcf7df2b0ff6", - "0x9048c6eba5e062519011e177e955a200b2c00b3a0b8615bdecdebc217559d41058d3315f6d05617be531ef0f6aef0e51", - "0x833bf225ab6fc68cdcacf1ec1b50f9d05f5410e6cdcd8d56a3081dc2be8a8d07b81534d1ec93a25c2e270313dfb99e3b", - "0xa84bcd24c3da5e537e64a811b93c91bfc84d7729b9ead7f79078989a6eb76717d620c1fad17466a0519208651e92f5ff", - "0xb7cdd0a3fbd79aed93e1b5a44ca44a94e7af5ed911e4492f332e3a5ed146c7286bde01b52276a2fcc02780d2109874dd", - "0x8a19a09854e627cb95750d83c20c67442b66b35896a476358f993ba9ac114d32c59c1b3d0b8787ee3224cf3888b56c64", - "0xa9abd5afb8659ee52ada8fa5d57e7dd355f0a7350276f6160bec5fbf70d5f99234dd179eb221c913e22a49ec6d267846", - "0x8c13c4274c0d30d184e73eaf812200094bbbd57293780bdadbceb262e34dee5b453991e7f37c7333a654fc71c69d6445", - "0xa4320d73296ff8176ce0127ca1921c450e2a9c06eff936681ebaffb5a0b05b17fded24e548454de89aca2dcf6d7a9de4", - "0xb2b8b3e15c1f645f07783e5628aba614e60157889db41d8161d977606788842b67f83f361eae91815dc0abd84e09abd5", - "0xad26c3aa35ddfddc15719b8bb6c264aaec7065e88ac29ba820eb61f220fef451609a7bb037f3722d022e6c86e4f1dc88", - "0xb8615bf43e13ae5d7b8dd903ce37190800cd490f441c09b22aa29d7a29ed2c0417b7a08ead417868f1de2589deaadd80", - "0x8d3425e1482cd1e76750a76239d33c06b3554c3c3c87c15cb7ab58b1cee86a4c5c4178b44e23f36928365a1b484bde02", - "0x806893a62e38c941a7dd6f249c83af16596f69877cc737d8f73f6b8cd93cbc01177a7a276b2b8c6b0e5f2ad864db5994", - "0x86618f17fa4b0d65496b661bbb5ba3bc3a87129d30a4b7d4f515b904f4206ca5253a41f49fd52095861e5e065ec54f21", - "0x9551915da1304051e55717f4c31db761dcdcf3a1366c89a4af800a9e99aca93a357bf928307f098e62b44a02cb689a46", - "0x8f79c4ec0ec1146cb2a523b52fe33def90d7b5652a0cb9c2d1c8808a32293e00aec6969f5b1538e3a94cd1efa3937f86", - "0xa0c03e329a707300081780f1e310671315b4c6a4cedcb29697aedfabb07a9d5df83f27b20e9c44cf6b16e39d9ded5b98", - "0x86a7cfa7c8e7ce2c01dd0baec2139e97e8e090ad4e7b5f51518f83d564765003c65968f85481bbb97cb18f005ccc7d9f", - "0xa33811770c6dfda3f7f74e6ad0107a187fe622d61b444bbd84fd7ef6e03302e693b093df76f6ab39bb4e02afd84a575a", - "0x85480f5c10d4162a8e6702b5e04f801874d572a62a130be94b0c02b58c3c59bdcd48cd05f0a1c2839f88f06b6e3cd337", - "0x8e181011564b17f7d787fe0e7f3c87f6b62da9083c54c74fd6c357a1f464c123c1d3d8ade3cf72475000b464b14e2be3", - "0x8ee178937294b8c991337e0621ab37e9ffa4ca2bdb3284065c5e9c08aad6785d50cf156270ff9daf9a9127289710f55b", - "0x8bd1e8e2d37379d4b172f1aec96f2e41a6e1393158d7a3dbd9a95c8dd4f8e0b05336a42efc11a732e5f22b47fc5c271d", - "0x8f3da353cd487c13136a85677de8cedf306faae0edec733cf4f0046f82fa4639db4745b0095ff33a9766aba50de0cbcf", - "0x8d187c1e97638df0e4792b78e8c23967dac43d98ea268ca4aabea4e0fa06cb93183fd92d4c9df74118d7cc27bf54415e", - "0xa4c992f08c2f8bac0b74b3702fb0c75c9838d2ce90b28812019553d47613c14d8ce514d15443159d700b218c5a312c49", - "0xa6fd1874034a34c3ea962a316c018d9493d2b3719bb0ec4edbc7c56b240802b2228ab49bee6f04c8a3e9f6f24a48c1c2", - "0xb2efed8e799f8a15999020900dc2c58ece5a3641c90811b86a5198e593d7318b9d53b167818ccdfbe7df2414c9c34011", - "0x995ff7de6181ddf95e3ead746089c6148da3508e4e7a2323c81785718b754d356789b902e7e78e2edc6b0cbd4ff22c78", - "0x944073d24750a9068cbd020b834afc72d2dde87efac04482b3287b40678ad07588519a4176b10f2172a2c463d063a5cd", - "0x99db4b1bb76475a6fd75289986ef40367960279524378cc917525fb6ba02a145a218c1e9caeb99332332ab486a125ac0", - "0x89fce4ecd420f8e477af4353b16faabb39e063f3f3c98fde2858b1f2d1ef6eed46f0975a7c08f233b97899bf60ccd60a", - "0x8c09a4f07a02b80654798bc63aada39fd638d3e3c4236ccd8a5ca280350c31e4a89e5f4c9aafb34116e71da18c1226b8", - "0x85325cfa7ded346cc51a2894257eab56e7488dbff504f10f99f4cd2b630d913003761a50f175ed167e8073f1b6b63fb0", - "0xb678b4fbec09a8cc794dcbca185f133578f29e354e99c05f6d07ac323be20aecb11f781d12898168e86f2e0f09aca15e", - "0xa249cfcbca4d9ba0a13b5f6aac72bf9b899adf582f9746bb2ad043742b28915607467eb794fca3704278f9136f7642be", - "0x9438e036c836a990c5e17af3d78367a75b23c37f807228362b4d13e3ddcb9e431348a7b552d09d11a2e9680704a4514f", - "0x925ab70450af28c21a488bfb5d38ac994f784cf249d7fd9ad251bb7fd897a23e23d2528308c03415074d43330dc37ef4", - "0xa290563904d5a8c0058fc8330120365bdd2ba1fdbaef7a14bc65d4961bb4217acfaed11ab82669e359531f8bf589b8db", - "0xa7e07a7801b871fc9b981a71e195a3b4ba6b6313bc132b04796a125157e78fe5c11a3a46cf731a255ac2d78a4ae78cd0", - "0xb26cd2501ee72718b0eebab6fb24d955a71f363f36e0f6dff0ab1d2d7836dab88474c0cef43a2cc32701fca7e82f7df3", - "0xa1dc3b6c968f3de00f11275092290afab65b2200afbcfa8ddc70e751fa19dbbc300445d6d479a81bda3880729007e496", - "0xa9bc213e28b630889476a095947d323b9ac6461dea726f2dc9084473ae8e196d66fb792a21905ad4ec52a6d757863e7d", - "0xb25d178df8c2df8051e7c888e9fa677fde5922e602a95e966db9e4a3d6b23ce043d7dc48a5b375c6b7c78e966893e8c3", - "0xa1c8d88d72303692eaa7adf68ea41de4febec40cc14ae551bb4012afd786d7b6444a3196b5d9d5040655a3366d96b7cd", - "0xb22bd44f9235a47118a9bbe2ba5a2ba9ec62476061be2e8e57806c1a17a02f9a51403e849e2e589520b759abd0117683", - "0xb8add766050c0d69fe81d8d9ea73e1ed05f0135d093ff01debd7247e42dbb86ad950aceb3b50b9af6cdc14ab443b238f", - "0xaf2cf95f30ef478f018cf81d70d47d742120b09193d8bb77f0d41a5d2e1a80bfb467793d9e2471b4e0ad0cb2c3b42271", - "0x8af5ef2107ad284e246bb56e20fef2a255954f72de791cbdfd3be09f825298d8466064f3c98a50496c7277af32b5c0bc", - "0x85dc19558572844c2849e729395a0c125096476388bd1b14fa7f54a7c38008fc93e578da3aac6a52ff1504d6ca82db05", - "0xae8c9b43c49572e2e166d704caf5b4b621a3b47827bb2a3bcd71cdc599bba90396fd9a405261b13e831bb5d44c0827d7", - "0xa7ba7efede25f02e88f6f4cbf70643e76784a03d97e0fbd5d9437c2485283ad7ca3abb638a5f826cd9f6193e5dec0b6c", - "0x94a9d122f2f06ef709fd8016fd4b712d88052245a65a301f5f177ce22992f74ad05552b1f1af4e70d1eac62cef309752", - "0x82d999b3e7cf563833b8bc028ff63a6b26eb357dfdb3fd5f10e33a1f80a9b2cfa7814d871b32a7ebfbaa09e753e37c02", - "0xaec6edcde234df502a3268dd2c26f4a36a2e0db730afa83173f9c78fcb2b2f75510a02b80194327b792811caefda2725", - "0x94c0bfa66c9f91d462e9194144fdd12d96f9bbe745737e73bab8130607ee6ea9d740e2cfcbbd00a195746edb6369ee61", - "0xab7573dab8c9d46d339e3f491cb2826cabe8b49f85f1ede78d845fc3995537d1b4ab85140b7d0238d9c24daf0e5e2a7e", - "0x87e8b16832843251fe952dadfd01d41890ed4bb4b8fa0254550d92c8cced44368225eca83a6c3ad47a7f81ff8a80c984", - "0x9189d2d9a7c64791b19c0773ad4f0564ce6bea94aa275a917f78ad987f150fdb3e5e26e7fef9982ac184897ecc04683f", - "0xb3661bf19e2da41415396ae4dd051a9272e8a2580b06f1a1118f57b901fa237616a9f8075af1129af4eabfefedbe2f1c", - "0xaf43c86661fb15daf5d910a4e06837225e100fb5680bd3e4b10f79a2144c6ec48b1f8d6e6b98e067d36609a5d038889a", - "0x82ac0c7acaa83ddc86c5b4249aae12f28155989c7c6b91e5137a4ce05113c6cbc16f6c44948b0efd8665362d3162f16a", - "0x8f268d1195ab465beeeb112cd7ffd5d5548559a8bc01261106d3555533fc1971081b25558d884d552df0db1cddda89d8", - "0x8ef7caa5521f3e037586ce8ac872a4182ee20c7921c0065ed9986c047e3dda08294da1165f385d008b40d500f07d895f", - "0x8c2f98f6880550573fad46075d3eba26634b5b025ce25a0b4d6e0193352c8a1f0661064027a70fe8190b522405f9f4e3", - "0xb7653f353564feb164f0f89ec7949da475b8dad4a4d396d252fc2a884f6932d027b7eb2dc4d280702c74569319ed701a", - "0xa026904f4066333befd9b87a8fad791d014096af60cdd668ef919c24dbe295ff31f7a790e1e721ba40cf5105abca67f4", - "0x988f982004ada07a22dd345f2412a228d7a96b9cae2c487de42e392afe1e35c2655f829ce07a14629148ce7079a1f142", - "0x9616add009067ed135295fb74d5b223b006b312bf14663e547a0d306694ff3a8a7bb9cfc466986707192a26c0bce599f", - "0xad4c425de9855f6968a17ee9ae5b15e0a5b596411388cf976df62ecc6c847a6e2ddb2cea792a5f6e9113c2445dba3e5c", - "0xb698ac9d86afa3dc69ff8375061f88e3b0cff92ff6dfe747cebaf142e813c011851e7a2830c10993b715e7fd594604a9", - "0xa386fa189847bb3b798efca917461e38ead61a08b101948def0f82cd258b945ed4d45b53774b400af500670149e601b7", - "0x905c95abda2c68a6559d8a39b6db081c68cef1e1b4be63498004e1b2f408409be9350b5b5d86a30fd443e2b3e445640a", - "0x9116dade969e7ce8954afcdd43e5cab64dc15f6c1b8da9d2d69de3f02ba79e6c4f6c7f54d6bf586d30256ae405cd1e41", - "0xa3084d173eacd08c9b5084a196719b57e47a0179826fda73466758235d7ecdb87cbcf097bd6b510517d163a85a7c7edd", - "0x85bb00415ad3c9be99ff9ba83672cc59fdd24356b661ab93713a3c8eab34e125d8867f628a3c3891b8dc056e69cd0e83", - "0x8d58541f9f39ed2ee4478acce5d58d124031338ec11b0d55551f00a5a9a6351faa903a5d7c132dc5e4bb026e9cbd18e4", - "0xa622adf72dc250e54f672e14e128c700166168dbe0474cecb340da175346e89917c400677b1bc1c11fcc4cc26591d9db", - "0xb3f865014754b688ca8372e8448114fff87bf3ca99856ab9168894d0c4679782c1ced703f5b74e851b370630f5e6ee86", - "0xa7e490b2c40c2446fcd91861c020da9742c326a81180e38110558bb5d9f2341f1c1885e79b364e6419023d1cbdc47380", - "0xb3748d472b1062e54572badbb8e87ac36534407f74932e7fc5b8392d008e8e89758f1671d1e4d30ab0fa40551b13bb5e", - "0x89898a5c5ec4313aabc607b0049fd1ebad0e0c074920cf503c9275b564d91916c2c446d3096491c950b7af3ac5e4b0ed", - "0x8eb8c83fef2c9dd30ea44e286e9599ec5c20aba983f702e5438afe2e5b921884327ad8d1566c72395587efac79ca7d56", - "0xb92479599e806516ce21fb0bd422a1d1d925335ebe2b4a0a7e044dd275f30985a72b97292477053ac5f00e081430da80", - "0xa34ae450a324fe8a3c25a4d653a654f9580ed56bbea213b8096987bbad0f5701d809a17076435e18017fea4d69f414bc", - "0x81381afe6433d62faf62ea488f39675e0091835892ecc238e02acf1662669c6d3962a71a3db652f6fe3bc5f42a0e5dc5", - "0xa430d475bf8580c59111103316fe1aa79c523ea12f1d47a976bbfae76894717c20220e31cf259f08e84a693da6688d70", - "0xb842814c359754ece614deb7d184d679d05d16f18a14b288a401cef5dad2cf0d5ee90bad487b80923fc5573779d4e4e8", - "0x971d9a2627ff2a6d0dcf2af3d895dfbafca28b1c09610c466e4e2bff2746f8369de7f40d65b70aed135fe1d72564aa88", - "0x8f4ce1c59e22b1ce7a0664caaa7e53735b154cfba8d2c5cc4159f2385843de82ab58ed901be876c6f7fce69cb4130950", - "0x86cc9dc321b6264297987000d344fa297ef45bcc2a4df04e458fe2d907ad304c0ea2318e32c3179af639a9a56f3263cf", - "0x8229e0876dfe8f665c3fb19b250bd89d40f039bbf1b331468b403655be7be2e104c2fd07b9983580c742d5462ca39a43", - "0x99299d73066e8eb128f698e56a9f8506dfe4bd014931e86b6b487d6195d2198c6c5bf15cccb40ccf1f8ddb57e9da44a2", - "0xa3a3be37ac554c574b393b2f33d0a32a116c1a7cfeaf88c54299a4da2267149a5ecca71f94e6c0ef6e2f472b802f5189", - "0xa91700d1a00387502cdba98c90f75fbc4066fefe7cc221c8f0e660994c936badd7d2695893fde2260c8c11d5bdcdd951", - "0x8e03cae725b7f9562c5c5ab6361644b976a68bada3d7ca508abca8dfc80a469975689af1fba1abcf21bc2a190dab397d", - "0xb01461ad23b2a8fa8a6d241e1675855d23bc977dbf4714add8c4b4b7469ccf2375cec20e80cedfe49361d1a30414ac5b", - "0xa2673bf9bc621e3892c3d7dd4f1a9497f369add8cbaa3472409f4f86bd21ac67cfac357604828adfee6ada1835365029", - "0xa042dff4bf0dfc33c178ba1b335e798e6308915128de91b12e5dbbab7c4ac8d60a01f6aea028c3a6d87b9b01e4e74c01", - "0x86339e8a75293e4b3ae66b5630d375736b6e6b6b05c5cda5e73fbf7b2f2bd34c18a1d6cefede08625ce3046e77905cb8", - "0xaf2ebe1b7d073d03e3d98bc61af83bf26f7a8c130fd607aa92b75db22d14d016481b8aa231e2c9757695f55b7224a27f", - "0xa00ee882c9685e978041fd74a2c465f06e2a42ffd3db659053519925be5b454d6f401e3c12c746e49d910e4c5c9c5e8c", - "0x978a781c0e4e264e0dad57e438f1097d447d891a1e2aa0d5928f79a9d5c3faae6f258bc94fdc530b7b2fa6a9932bb193", - "0xaa4b7ce2e0c2c9e9655bf21e3e5651c8503bce27483017b0bf476be743ba06db10228b3a4c721219c0779747f11ca282", - "0xb003d1c459dacbcf1a715551311e45d7dbca83a185a65748ac74d1800bbeaba37765d9f5a1a221805c571910b34ebca8", - "0x95b6e531b38648049f0d19de09b881baa1f7ea3b2130816b006ad5703901a05da57467d1a3d9d2e7c73fb3f2e409363c", - "0xa6cf9c06593432d8eba23a4f131bb7f72b9bd51ab6b4b772a749fe03ed72b5ced835a349c6d9920dba2a39669cb7c684", - "0xaa3d59f6e2e96fbb66195bc58c8704e139fa76cd15e4d61035470bd6e305db9f98bcbf61ac1b95e95b69ba330454c1b3", - "0xb57f97959c208361de6d7e86dff2b873068adb0f158066e646f42ae90e650079798f165b5cd713141cd3a2a90a961d9a", - "0xa76ee8ed9052f6a7a8c69774bb2597be182942f08115baba03bf8faaeaee526feba86120039fe8ca7b9354c3b6e0a8e6", - "0x95689d78c867724823f564627d22d25010f278674c6d2d0cdb10329169a47580818995d1d727ce46c38a1e47943ebb89", - "0xab676d2256c6288a88e044b3d9ffd43eb9d5aaee00e8fc60ac921395fb835044c71a26ca948e557fed770f52d711e057", - "0x96351c72785c32e5d004b6f4a1259fb8153d631f0c93fed172f18e8ba438fbc5585c1618deeabd0d6d0b82173c2e6170", - "0x93dd8d3db576418e22536eba45ab7f56967c6c97c64260d6cddf38fb19c88f2ec5cd0e0156f50e70855eee8a2b879ffd", - "0xad6ff16f40f6de3d7a737f8e6cebd8416920c4ff89dbdcd75eabab414af9a6087f83ceb9aff7680aa86bff98bd09c8cc", - "0x84de53b11671abc9c38710e19540c5c403817562aeb22a88404cdaff792c1180f717dbdfe8f54940c062c4d032897429", - "0x872231b9efa1cdd447b312099a5c164c560440a9441d904e70f5abfc3b2a0d16be9a01aca5e0a2599a61e19407587e3d", - "0x88f44ac27094a2aa14e9dc40b099ee6d68f97385950f303969d889ee93d4635e34dff9239103bdf66a4b7cbba3e7eb7a", - "0xa59afebadf0260e832f6f44468443562f53fbaf7bcb5e46e1462d3f328ac437ce56edbca617659ac9883f9e13261fad7", - "0xb1990e42743a88de4deeacfd55fafeab3bc380cb95de43ed623d021a4f2353530bcab9594389c1844b1c5ea6634c4555", - "0x85051e841149a10e83f56764e042182208591396d0ce78c762c4a413e6836906df67f38c69793e158d64fef111407ba3", - "0x9778172bbd9b1f2ec6bbdd61829d7b39a7df494a818e31c654bf7f6a30139899c4822c1bf418dd4f923243067759ce63", - "0x9355005b4878c87804fc966e7d24f3e4b02bed35b4a77369d01f25a3dcbff7621b08306b1ac85b76fe7b4a3eb5f839b1", - "0x8f9dc6a54fac052e236f8f0e1f571ac4b5308a43acbe4cc8183bce26262ddaf7994e41cf3034a4cbeca2c505a151e3b1", - "0x8cc59c17307111723fe313046a09e0e32ea0cce62c13814ab7c6408c142d6a0311d801be4af53fc9240523f12045f9ef", - "0x8e6057975ed40a1932e47dd3ac778f72ee2a868d8540271301b1aa6858de1a5450f596466494a3e0488be4fbeb41c840", - "0x812145efbd6559ae13325d56a15940ca4253b17e72a9728986b563bb5acc13ec86453796506ac1a8f12bd6f9e4a288c3", - "0x911da0a6d6489eb3dab2ec4a16e36127e8a291ae68a6c2c9de33e97f3a9b1f00da57a94e270a0de79ecc5ecb45d19e83", - "0xb72ea85973f4b2a7e6e71962b0502024e979a73c18a9111130e158541fa47bbaaf53940c8f846913a517dc69982ba9e1", - "0xa7a56ad1dbdc55f177a7ad1d0af78447dc2673291e34e8ab74b26e2e2e7d8c5fe5dc89e7ef60f04a9508847b5b3a8188", - "0xb52503f6e5411db5d1e70f5fb72ccd6463fa0f197b3e51ca79c7b5a8ab2e894f0030476ada72534fa4eb4e06c3880f90", - "0xb51c7957a3d18c4e38f6358f2237b3904618d58b1de5dec53387d25a63772e675a5b714ad35a38185409931157d4b529", - "0xb86b4266e719d29c043d7ec091547aa6f65bbf2d8d831d1515957c5c06513b72aa82113e9645ad38a7bc3f5383504fa6", - "0xb95b547357e6601667b0f5f61f261800a44c2879cf94e879def6a105b1ad2bbf1795c3b98a90d588388e81789bd02681", - "0xa58fd4c5ae4673fa350da6777e13313d5d37ed1dafeeb8f4f171549765b84c895875d9d3ae6a9741f3d51006ef81d962", - "0x9398dc348d078a604aadc154e6eef2c0be1a93bb93ba7fe8976edc2840a3a318941338cc4d5f743310e539d9b46613d2", - "0x902c9f0095014c4a2f0dccaaab543debba6f4cc82c345a10aaf4e72511725dbed7a34cd393a5f4e48a3e5142b7be84ed", - "0xa7c0447849bb44d04a0393a680f6cd390093484a79a147dd238f5d878030d1c26646d88211108e59fe08b58ad20c6fbd", - "0x80db045535d6e67a422519f5c89699e37098449d249698a7cc173a26ccd06f60238ae6cc7242eb780a340705c906790c", - "0x8e52b451a299f30124505de2e74d5341e1b5597bdd13301cc39b05536c96e4380e7f1b5c7ef076f5b3005a868657f17c", - "0x824499e89701036037571761e977654d2760b8ce21f184f2879fda55d3cda1e7a95306b8abacf1caa79d3cc075b9d27f", - "0x9049b956b77f8453d2070607610b79db795588c0cec12943a0f5fe76f358dea81e4f57a4692112afda0e2c05c142b26f", - "0x81911647d818a4b5f4990bfd4bc13bf7be7b0059afcf1b6839333e8569cdb0172fd2945410d88879349f677abaed5eb3", - "0xad4048f19b8194ed45b6317d9492b71a89a66928353072659f5ce6c816d8f21e69b9d1817d793effe49ca1874daa1096", - "0x8d22f7b2ddb31458661abd34b65819a374a1f68c01fc6c9887edeba8b80c65bceadb8f57a3eb686374004b836261ef67", - "0x92637280c259bc6842884db3d6e32602a62252811ae9b019b3c1df664e8809ffe86db88cfdeb8af9f46435c9ee790267", - "0xa2f416379e52e3f5edc21641ea73dc76c99f7e29ea75b487e18bd233856f4c0183429f378d2bfc6cd736d29d6cadfa49", - "0x882cb6b76dbdc188615dcf1a8439eba05ffca637dd25197508156e03c930b17b9fed2938506fdd7b77567cb488f96222", - "0xb68b621bb198a763fb0634eddb93ed4b5156e59b96c88ca2246fd1aea3e6b77ed651e112ac41b30cd361fadc011d385e", - "0xa3cb22f6b675a29b2d1f827cacd30df14d463c93c3502ef965166f20d046af7f9ab7b2586a9c64f4eae4fad2d808a164", - "0x8302d9ce4403f48ca217079762ce42cee8bc30168686bb8d3a945fbd5acd53b39f028dce757b825eb63af2d5ae41169d", - "0xb2eef1fbd1a176f1f4cd10f2988c7329abe4eb16c7405099fb92baa724ab397bc98734ef7d4b24c0f53dd90f57520d04", - "0xa1bbef0bd684a3f0364a66bde9b29326bac7aa3dde4caed67f14fb84fed3de45c55e406702f1495a3e2864d4ee975030", - "0x976acdb0efb73e3a3b65633197692dedc2adaed674291ae3df76b827fc866d214e9cac9ca46baefc4405ff13f953d936", - "0xb9fbf71cc7b6690f601f0b1c74a19b7d14254183a2daaafec7dc3830cba5ae173d854bbfebeca985d1d908abe5ef0cda", - "0x90591d7b483598c94e38969c4dbb92710a1a894bcf147807f1bcbd8aa3ac210b9f2be65519aa829f8e1ccdc83ad9b8cf", - "0xa30568577c91866b9c40f0719d46b7b3b2e0b4a95e56196ac80898a2d89cc67880e1229933f2cd28ee3286f8d03414d7", - "0x97589a88c3850556b359ec5e891f0937f922a751ac7c95949d3bbc7058c172c387611c0f4cb06351ef02e5178b3dd9e4", - "0x98e7bbe27a1711f4545df742f17e3233fbcc63659d7419e1ca633f104cb02a32c84f2fac23ca2b84145c2672f68077ab", - "0xa7ddb91636e4506d8b7e92aa9f4720491bb71a72dadc47c7f4410e15f93e43d07d2b371951a0e6a18d1bd087aa96a5c4", - "0xa7c006692227a06db40bceac3d5b1daae60b5692dd9b54772bedb5fea0bcc91cbcdb530cac31900ffc70c5b3ffadc969", - "0x8d3ec6032778420dfa8be52066ba0e623467df33e4e1901dbadd586c5d750f4ccde499b5197e26b9ea43931214060f69", - "0x8d9a8410518ea64f89df319bfd1fc97a0971cdb9ad9b11d1f8fe834042ea7f8dce4db56eeaf179ff8dda93b6db93e5ce", - "0xa3c533e9b3aa04df20b9ff635cb1154ce303e045278fcf3f10f609064a5445552a1f93989c52ce852fd0bbd6e2b6c22e", - "0x81934f3a7f8c1ae60ec6e4f212986bcc316118c760a74155d06ce0a8c00a9b9669ec4e143ca214e1b995e41271774fd9", - "0xab8e2d01a71192093ef8fafa7485e795567cc9db95a93fb7cc4cf63a391ef89af5e2bfad4b827fffe02b89271300407f", - "0x83064a1eaa937a84e392226f1a60b7cfad4efaa802f66de5df7498962f7b2649924f63cd9962d47906380b97b9fe80e1", - "0xb4f5e64a15c6672e4b55417ee5dc292dcf93d7ea99965a888b1cc4f5474a11e5b6520eacbcf066840b343f4ceeb6bf33", - "0xa63d278b842456ef15c278b37a6ea0f27c7b3ffffefca77c7a66d2ea06c33c4631eb242bbb064d730e70a8262a7b848a", - "0x83a41a83dbcdf0d22dc049de082296204e848c453c5ab1ba75aa4067984e053acf6f8b6909a2e1f0009ed051a828a73b", - "0x819485b036b7958508f15f3c19436da069cbe635b0318ebe8c014cf1ef9ab2df038c81161b7027475bcfa6fff8dd9faf", - "0xaa40e38172806e1e045e167f3d1677ef12d5dcdc89b43639a170f68054bd196c4fae34c675c1644d198907a03f76ba57", - "0x969bae484883a9ed1fbed53b26b3d4ee4b0e39a6c93ece5b3a49daa01444a1c25727dabe62518546f36b047b311b177c", - "0x80a9e73a65da99664988b238096a090d313a0ee8e4235bc102fa79bb337b51bb08c4507814eb5baec22103ec512eaab0", - "0x86604379aec5bddda6cbe3ef99c0ac3a3c285b0b1a15b50451c7242cd42ae6b6c8acb717dcca7917838432df93a28502", - "0xa23407ee02a495bed06aa7e15f94cfb05c83e6d6fba64456a9bbabfa76b2b68c5c47de00ba169e710681f6a29bb41a22", - "0x98cff5ecc73b366c6a01b34ac9066cb34f7eeaf4f38a5429bad2d07e84a237047e2a065c7e8a0a6581017dadb4695deb", - "0x8de9f68a938f441f3b7ab84bb1f473c5f9e5c9e139e42b7ccee1d254bd57d0e99c2ccda0f3198f1fc5737f6023dd204e", - "0xb0ce48d815c2768fb472a315cad86aa033d0e9ca506f146656e2941829e0acb735590b4fbc713c2d18d3676db0a954ac", - "0x82f485cdefd5642a6af58ac6817991c49fac9c10ace60f90b27f1788cc026c2fe8afc83cf499b3444118f9f0103598a8", - "0x82c24550ed512a0d53fc56f64cc36b553823ae8766d75d772dacf038c460f16f108f87a39ceef7c66389790f799dbab3", - "0x859ffcf1fe9166388316149b9acc35694c0ea534d43f09dae9b86f4aa00a23b27144dda6a352e74b9516e8c8d6fc809c", - "0xb8f7f353eec45da77fb27742405e5ad08d95ec0f5b6842025be9def3d9892f85eb5dd0921b41e6eff373618dba215bca", - "0x8ccca4436f9017e426229290f5cd05eac3f16571a4713141a7461acfe8ae99cd5a95bf5b6df129148693c533966145da", - "0xa2c67ecc19c0178b2994846fea4c34c327a5d786ac4b09d1d13549d5be5996d8a89021d63d65cb814923388f47cc3a03", - "0xaa0ff87d676b418ec08f5cbf577ac7e744d1d0e9ebd14615b550eb86931eafd2a36d4732cc5d6fab1713fd7ab2f6f7c0", - "0x8aef4730bb65e44efd6bb9441c0ae897363a2f3054867590a2c2ecf4f0224e578c7a67f10b40f8453d9f492ac15a9b2d", - "0x86a187e13d8fba5addcfdd5b0410cedd352016c930f913addd769ee09faa6be5ca3e4b1bdb417a965c643a99bd92be42", - "0xa0a4e9632a7a094b14b29b78cd9c894218cdf6783e61671e0203865dc2a835350f465fbaf86168f28af7c478ca17bc89", - "0xa8c7b02d8deff2cd657d8447689a9c5e2cd74ef57c1314ac4d69084ac24a7471954d9ff43fe0907d875dcb65fd0d3ce5", - "0x97ded38760aa7be6b6960b5b50e83b618fe413cbf2bcc1da64c05140bcc32f5e0e709cd05bf8007949953fac5716bad9", - "0xb0d293835a24d64c2ae48ce26e550b71a8c94a0883103757fb6b07e30747f1a871707d23389ba2b2065fa6bafe220095", - "0x8f9e291bf849feaa575592e28e3c8d4b7283f733d41827262367ea1c40f298c7bcc16505255a906b62bf15d9f1ba85fb", - "0x998f4e2d12708b4fd85a61597ca2eddd750f73c9e0c9b3cf0825d8f8e01f1628fd19797dcaed3b16dc50331fc6b8b821", - "0xb30d1f8c115d0e63bf48f595dd10908416774c78b3bbb3194192995154d80ea042d2e94d858de5f8aa0261b093c401fd", - "0xb5d9c75bb41f964cbff3f00e96d9f1480c91df8913f139f0d385d27a19f57a820f838eb728e46823cbff00e21c660996", - "0xa6edec90b5d25350e2f5f0518777634f9e661ec9d30674cf5b156c4801746d62517751d90074830ac0f4b09911c262f1", - "0x82f98da1264b6b75b8fbeb6a4d96d6a05b25c24db0d57ba3a38efe3a82d0d4e331b9fc4237d6494ccfe4727206457519", - "0xb89511843453cf4ecd24669572d6371b1e529c8e284300c43e0d5bb6b3aaf35aeb634b3cb5c0a2868f0d5e959c1d0772", - "0xa82bf065676583e5c1d3b81987aaae5542f522ba39538263a944bb33ea5b514c649344a96c0205a3b197a3f930fcda6c", - "0xa37b47ea527b7e06c460776aa662d9a49ff4149d3993f1a974b0dd165f7171770d189b0e2ea54fd5fccb6a14b116e68a", - "0xa1017677f97dda818274d47556d09d0e4ccacb23a252f82a6cfe78c630ad46fb9806307445a59fb61262182de3a2b29c", - "0xb01e9fcac239ba270e6877b79273ddd768bf8a51d2ed8a051b1c11e18eff3de5920e2fcbfbd26f06d381eddd3b1f1e1b", - "0x82fcd53d803b1c8e4ed76adc339b7f3a5962d37042b9683aabac7513ac68775d4a566a9460183926a6a95dbe7d551a1f", - "0xa763e78995d55cd21cdb7ef75d9642d6e1c72453945e346ab6690c20a4e1eeec61bb848ef830ae4b56182535e3c71d8f", - "0xb769f4db602251d4b0a1186782799bdcef66de33c110999a5775c50b349666ffd83d4c89714c4e376f2efe021a5cfdb2", - "0xa59cbd1b785efcfa6e83fc3b1d8cf638820bc0c119726b5368f3fba9dce8e3414204fb1f1a88f6c1ff52e87961252f97", - "0x95c8c458fd01aa23ecf120481a9c6332ebec2e8bb70a308d0576926a858457021c277958cf79017ddd86a56cacc2d7db", - "0x82eb41390800287ae56e77f2e87709de5b871c8bdb67c10a80fc65f3acb9f7c29e8fa43047436e8933f27449ea61d94d", - "0xb3ec25e3545eb83aed2a1f3558d1a31c7edde4be145ecc13b33802654b77dc049b4f0065069dd9047b051e52ab11dcdd", - "0xb78a0c715738f56f0dc459ab99e252e3b579b208142836b3c416b704ca1de640ca082f29ebbcee648c8c127df06f6b1e", - "0xa4083149432eaaf9520188ebf4607d09cf664acd1f471d4fb654476e77a9eaae2251424ffda78d09b6cb880df35c1219", - "0x8c52857d68d6e9672df3db2df2dbf46b516a21a0e8a18eec09a6ae13c1ef8f369d03233320dd1c2c0bbe00abfc1ea18b", - "0x8c856089488803066bff3f8d8e09afb9baf20cecc33c8823c1c0836c3d45498c3de37e87c016b705207f60d2b00f8609", - "0x831a3df39be959047b2aead06b4dcd3012d7b29417f642b83c9e8ce8de24a3dbbd29c6fdf55e2db3f7ea04636c94e403", - "0xaed84d009f66544addabe404bf6d65af7779ce140dc561ff0c86a4078557b96b2053b7b8a43432ffb18cd814f143b9da", - "0x93282e4d72b0aa85212a77b336007d8ba071eea17492da19860f1ad16c1ea8867ccc27ef5c37c74b052465cc11ea4f52", - "0xa7b78b8c8d057194e8d68767f1488363f77c77bddd56c3da2bc70b6354c7aa76247c86d51f7371aa38a4aa7f7e3c0bb7", - "0xb1c77283d01dcd1bde649b5b044eac26befc98ff57cbee379fb5b8e420134a88f2fc7f0bf04d15e1fbd45d29e7590fe6", - "0xa4aa8de70330a73b2c6458f20a1067eed4b3474829b36970a8df125d53bbdda4f4a2c60063b7cccb0c80fc155527652f", - "0x948a6c79ba1b8ad7e0bed2fae2f0481c4e41b4d9bbdd9b58164e28e9065700e83f210c8d5351d0212e0b0b68b345b3a5", - "0x86a48c31dcbbf7b082c92d28e1f613a2378a910677d7db3a349dc089e4a1e24b12eee8e8206777a3a8c64748840b7387", - "0x976adb1af21e0fc34148917cf43d933d7bfd3fd12ed6c37039dcd5a4520e3c6cf5868539ba5bf082326430deb8a4458d", - "0xb93e1a4476f2c51864bb4037e7145f0635eb2827ab91732b98d49b6c07f6ac443111aa1f1da76d1888665cb897c3834e", - "0x8afd46fb23bf869999fa19784b18a432a1f252d09506b8dbb756af900518d3f5f244989b3d7c823d9029218c655d3dc6", - "0x83f1e59e3abeed18cdc632921672673f1cb6e330326e11c4e600e13e0d5bc11bdc970ae12952e15103a706fe720bf4d6", - "0x90ce4cc660714b0b673d48010641c09c00fc92a2c596208f65c46073d7f349dd8e6e077ba7dcef9403084971c3295b76", - "0x8b09b0f431a7c796561ecf1549b85048564de428dac0474522e9558b6065fede231886bc108539c104ce88ebd9b5d1b0", - "0x85d6e742e2fb16a7b0ba0df64bc2c0dbff9549be691f46a6669bca05e89c884af16822b85faefefb604ec48c8705a309", - "0xa87989ee231e468a712c66513746fcf03c14f103aadca0eac28e9732487deb56d7532e407953ab87a4bf8961588ef7b0", - "0xb00da10efe1c29ee03c9d37d5918e391ae30e48304e294696b81b434f65cf8c8b95b9d1758c64c25e534d045ba28696f", - "0x91c0e1fb49afe46c7056400baa06dbb5f6e479db78ee37e2d76c1f4e88994357e257b83b78624c4ef6091a6c0eb8254d", - "0x883fb797c498297ccbf9411a3e727c3614af4eccde41619b773dc7f3259950835ee79453debf178e11dec4d3ada687a0", - "0xa14703347e44eb5059070b2759297fcfcfc60e6893c0373eea069388eba3950aa06f1c57cd2c30984a2d6f9e9c92c79e", - "0xafebc7585b304ceba9a769634adff35940e89cd32682c78002822aab25eec3edc29342b7f5a42a56a1fec67821172ad5", - "0xaea3ff3822d09dba1425084ca95fd359718d856f6c133c5fabe2b2eed8303b6e0ba0d8698b48b93136a673baac174fd9", - "0xaf2456a09aa777d9e67aa6c7c49a1845ea5cdda2e39f4c935c34a5f8280d69d4eec570446998cbbe31ede69a91e90b06", - "0x82cada19fed16b891ef3442bafd49e1f07c00c2f57b2492dd4ee36af2bd6fd877d6cb41188a4d6ce9ec8d48e8133d697", - "0x82a21034c832287f616619a37c122cee265cc34ae75e881fcaea4ea7f689f3c2bc8150bbf7dbcfd123522bfb7f7b1d68", - "0x86877217105f5d0ec3eeff0289fc2a70d505c9fdf7862e8159553ef60908fb1a27bdaf899381356a4ef4649072a9796c", - "0x82b196e49c6e861089a427c0b4671d464e9d15555ffb90954cd0d630d7ae02eb3d98ceb529d00719c2526cd96481355a", - "0xa29b41d0d43d26ce76d4358e0db2b77df11f56e389f3b084d8af70a636218bd3ac86b36a9fe46ec9058c26a490f887f7", - "0xa4311c4c20c4d7dd943765099c50f2fd423e203ccfe98ff00087d205467a7873762510cac5fdce7a308913ed07991ed7", - "0xb1f040fc5cc51550cb2c25cf1fd418ecdd961635a11f365515f0cb4ffb31da71f48128c233e9cc7c0cf3978d757ec84e", - "0xa9ebae46f86d3bd543c5f207ed0d1aed94b8375dc991161d7a271f01592912072e083e2daf30c146430894e37325a1b9", - "0x826418c8e17ad902b5fe88736323a47e0ca7a44bce4cbe27846ec8fe81de1e8942455dda6d30e192cdcc73e11df31256", - "0x85199db563427c5edcbac21f3d39fec2357be91fb571982ddcdc4646b446ad5ced84410de008cb47b3477ee0d532daf8", - "0xb7eed9cd400b2ca12bf1d9ae008214b8561fb09c8ad9ff959e626ffde00fee5ff2f5b6612e231f2a1a9b1646fcc575e3", - "0x8b40bf12501dcbac78f5a314941326bfcddf7907c83d8d887d0bb149207f85d80cd4dfbd7935439ea7b14ea39a3fded7", - "0x83e3041af302485399ba6cd5120e17af61043977083887e8d26b15feec4a6b11171ac5c06e6ad0971d4b58a81ff12af3", - "0x8f5b9a0eecc589dbf8c35a65d5e996a659277ef6ea509739c0cb7b3e2da9895e8c8012de662e5b23c5fa85d4a8f48904", - "0x835d71ed5e919d89d8e6455f234f3ff215462c4e3720c371ac8c75e83b19dfe3ae15a81547e4dc1138e5f5997f413cc9", - "0x8b7d2e4614716b1db18e9370176ea483e6abe8acdcc3dcdf5fb1f4d22ca55d652feebdccc171c6de38398d9f7bfdec7a", - "0x93eace72036fe57d019676a02acf3d224cf376f166658c1bf705db4f24295881d477d6fdd7916efcfceff8c7a063deda", - "0xb1ac460b3d516879a84bc886c54f020a9d799e7c49af3e4d7de5bf0d2793c852254c5d8fe5616147e6659512e5ccb012", - "0xacd0947a35cb167a48bcd9667620464b54ac0e78f9316b4aa92dcaab5422d7a732087e52e1c827faa847c6b2fe6e7766", - "0x94ac33d21c3d12ff762d32557860e911cd94d666609ddcc42161b9c16f28d24a526e8b10bb03137257a92cec25ae637d", - "0x832e02058b6b994eadd8702921486241f9a19e68ed1406dad545e000a491ae510f525ccf9d10a4bba91c68f2c53a0f58", - "0x9471035d14f78ff8f463b9901dd476b587bb07225c351161915c2e9c6114c3c78a501379ab6fb4eb03194c457cbd22bf", - "0xab64593e034c6241d357fcbc32d8ea5593445a5e7c24cac81ad12bd2ef01843d477a36dc1ba21dbe63b440750d72096a", - "0x9850f3b30045e927ad3ec4123a32ed2eb4c911f572b6abb79121873f91016f0d80268de8b12e2093a4904f6e6cab7642", - "0x987212c36b4722fe2e54fa30c52b1e54474439f9f35ca6ad33c5130cd305b8b54b532dd80ffd2c274105f20ce6d79f6e", - "0x8b4d0c6abcb239b5ed47bef63bc17efe558a27462c8208fa652b056e9eae9665787cd1aee34fbb55beb045c8bfdb882b", - "0xa9f3483c6fee2fe41312d89dd4355d5b2193ac413258993805c5cbbf0a59221f879386d3e7a28e73014f10e65dd503d9", - "0xa2225da3119b9b7c83d514b9f3aeb9a6d9e32d9cbf9309cbb971fd53c4b2c001d10d880a8ad8a7c281b21d85ceca0b7c", - "0xa050be52e54e676c151f7a54453bbb707232f849beab4f3bf504b4d620f59ed214409d7c2bd3000f3ff13184ccda1c35", - "0xadbccf681e15b3edb6455a68d292b0a1d0f5a4cb135613f5e6db9943f02181341d5755875db6ee474e19ace1c0634a28", - "0x8b6eff675632a6fad0111ec72aacc61c7387380eb87933fd1d098856387d418bd38e77d897e65d6fe35951d0627c550b", - "0xaabe2328ddf90989b15e409b91ef055cb02757d34987849ae6d60bef2c902bf8251ed21ab30acf39e500d1d511e90845", - "0x92ba4eb1f796bc3d8b03515f65c045b66e2734c2da3fc507fdd9d6b5d1e19ab3893726816a32141db7a31099ca817d96", - "0x8a98b3cf353138a1810beb60e946183803ef1d39ac4ea92f5a1e03060d35a4774a6e52b14ead54f6794d5f4022b8685c", - "0x909f8a5c13ec4a59b649ed3bee9f5d13b21d7f3e2636fd2bb3413c0646573fdf9243d63083356f12f5147545339fcd55", - "0x9359d914d1267633141328ed0790d81c695fea3ddd2d406c0df3d81d0c64931cf316fe4d92f4353c99ff63e2aefc4e34", - "0xb88302031681b54415fe8fbfa161c032ea345c6af63d2fb8ad97615103fd4d4281c5a9cae5b0794c4657b97571a81d3b", - "0x992c80192a519038082446b1fb947323005b275e25f2c14c33cc7269e0ec038581cc43705894f94bad62ae33a8b7f965", - "0xa78253e3e3eece124bef84a0a8807ce76573509f6861d0b6f70d0aa35a30a123a9da5e01e84969708c40b0669eb70aa6", - "0x8d5724de45270ca91c94792e8584e676547d7ac1ac816a6bb9982ee854eb5df071d20545cdfd3771cd40f90e5ba04c8e", - "0x825a6f586726c68d45f00ad0f5a4436523317939a47713f78fd4fe81cd74236fdac1b04ecd97c2d0267d6f4981d7beb1" - ], - "g2_monomial": [ - "0x93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8", - "0xb5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2", - "0xb5337ba0ce5d37224290916e268e2060e5c14f3f9fc9e1ec3af5a958e7a0303122500ce18f1a4640bf66525bd10e763501fe986d86649d8d45143c08c3209db3411802c226e9fe9a55716ac4a0c14f9dcef9e70b2bb309553880dc5025eab3cc", - "0xb3c1dcdc1f62046c786f0b82242ef283e7ed8f5626f72542aa2c7a40f14d9094dd1ebdbd7457ffdcdac45fd7da7e16c51200b06d791e5e43e257e45efdf0bd5b06cd2333beca2a3a84354eb48662d83aef5ecf4e67658c851c10b13d8d87c874", - "0x954d91c7688983382609fca9e211e461f488a5971fd4e40d7e2892037268eacdfd495cfa0a7ed6eb0eb11ac3ae6f651716757e7526abe1e06c64649d80996fd3105c20c4c94bc2b22d97045356fe9d791f21ea6428ac48db6f9e68e30d875280", - "0x88a6b6bb26c51cf9812260795523973bb90ce80f6820b6c9048ab366f0fb96e48437a7f7cb62aedf64b11eb4dfefebb0147608793133d32003cb1f2dc47b13b5ff45f1bb1b2408ea45770a08dbfaec60961acb8119c47b139a13b8641e2c9487", - "0x85cd7be9728bd925d12f47fb04b32d9fad7cab88788b559f053e69ca18e463113ecc8bbb6dbfb024835f901b3a957d3108d6770fb26d4c8be0a9a619f6e3a4bf15cbfd48e61593490885f6cee30e4300c5f9cf5e1c08e60a2d5b023ee94fcad0", - "0x80477dba360f04399821a48ca388c0fa81102dd15687fea792ee8c1114e00d1bc4839ad37ac58900a118d863723acfbe08126ea883be87f50e4eabe3b5e72f5d9e041db8d9b186409fd4df4a7dde38c0e0a3b1ae29b098e5697e7f110b6b27e4", - "0xb7a6aec08715a9f8672a2b8c367e407be37e59514ac19dd4f0942a68007bba3923df22da48702c63c0d6b3efd3c2d04e0fe042d8b5a54d562f9f33afc4865dcbcc16e99029e25925580e87920c399e710d438ac1ce3a6dc9b0d76c064a01f6f7", - "0xac1b001edcea02c8258aeffbf9203114c1c874ad88dae1184fadd7d94cd09053649efd0ca413400e6e9b5fa4eac33261000af88b6bd0d2abf877a4f0355d2fb4d6007adb181695201c5432e50b850b51b3969f893bddf82126c5a71b042b7686", - "0x90043fda4de53fb364fab2c04be5296c215599105ecff0c12e4917c549257125775c29f2507124d15f56e30447f367db0596c33237242c02d83dfd058735f1e3c1ff99069af55773b6d51d32a68bf75763f59ec4ee7267932ae426522b8aaab6", - "0xa8660ce853e9dc08271bf882e29cd53397d63b739584dda5263da4c7cc1878d0cf6f3e403557885f557e184700575fee016ee8542dec22c97befe1d10f414d22e84560741cdb3e74c30dda9b42eeaaf53e27822de2ee06e24e912bf764a9a533", - "0x8fe3921a96d0d065e8aa8fce9aa42c8e1461ca0470688c137be89396dd05103606dab6cdd2a4591efd6addf72026c12e065da7be276dee27a7e30afa2bd81c18f1516e7f068f324d0bad9570b95f6bd02c727cd2343e26db0887c3e4e26dceda", - "0x8ae1ad97dcb9c192c9a3933541b40447d1dc4eebf380151440bbaae1e120cc5cdf1bcea55180b128d8e180e3af623815191d063cc0d7a47d55fb7687b9d87040bf7bc1a7546b07c61db5ccf1841372d7c2fe4a5431ffff829f3c2eb590b0b710", - "0x8c2fa96870a88150f7876c931e2d3cc2adeaaaf5c73ef5fa1cf9dfa0991ae4819f9321af7e916e5057d87338e630a2f21242c29d76963cf26035b548d2a63d8ad7bd6efefa01c1df502cbdfdfe0334fb21ceb9f686887440f713bf17a89b8081", - "0xb9aa98e2f02bb616e22ee5dd74c7d1049321ac9214d093a738159850a1dbcc7138cb8d26ce09d8296368fd5b291d74fa17ac7cc1b80840fdd4ee35e111501e3fa8485b508baecda7c1ab7bd703872b7d64a2a40b3210b6a70e8a6ffe0e5127e3", - "0x9292db67f8771cdc86854a3f614a73805bf3012b48f1541e704ea4015d2b6b9c9aaed36419769c87c49f9e3165f03edb159c23b3a49c4390951f78e1d9b0ad997129b17cdb57ea1a6638794c0cca7d239f229e589c5ae4f9fe6979f7f8cba1d7", - "0x91cd9e86550f230d128664f7312591fee6a84c34f5fc7aed557bcf986a409a6de722c4330453a305f06911d2728626e611acfdf81284f77f60a3a1595053a9479964fd713117e27c0222cc679674b03bc8001501aaf9b506196c56de29429b46", - "0xa9516b73f605cc31b89c68b7675dc451e6364595243d235339437f556cf22d745d4250c1376182273be2d99e02c10eee047410a43eff634d051aeb784e76cb3605d8e079b9eb6ad1957dfdf77e1cd32ce4a573c9dfcc207ca65af6eb187f6c3d", - "0xa9667271f7d191935cc8ad59ef3ec50229945faea85bfdfb0d582090f524436b348aaa0183b16a6231c00332fdac2826125b8c857a2ed9ec66821cfe02b3a2279be2412441bc2e369b255eb98614e4be8490799c4df22f18d47d24ec70bba5f7", - "0xa4371144d2aa44d70d3cb9789096d3aa411149a6f800cb46f506461ee8363c8724667974252f28aea61b6030c05930ac039c1ee64bb4bd56532a685cae182bf2ab935eee34718cffcb46cae214c77aaca11dbb1320faf23c47247db1da04d8dc", - "0x89a7eb441892260b7e81168c386899cd84ffc4a2c5cad2eae0d1ab9e8b5524662e6f660fe3f8bfe4c92f60b060811bc605b14c5631d16709266886d7885a5eb5930097127ec6fb2ebbaf2df65909cf48f253b3d5e22ae48d3e9a2fd2b01f447e", - "0x9648c42ca97665b5eccb49580d8532df05eb5a68db07f391a2340769b55119eaf4c52fe4f650c09250fa78a76c3a1e271799b8333cc2628e3d4b4a6a3e03da1f771ecf6516dd63236574a7864ff07e319a6f11f153406280d63af9e2b5713283", - "0x9663bf6dd446ea7a90658ee458578d4196dc0b175ef7fcfa75f44d41670850774c2e46c5a6be132a2c072a3c0180a24f0305d1acac49d2d79878e5cda80c57feda3d01a6af12e78b5874e2a4b3717f11c97503b41a4474e2e95b179113726199", - "0xb212aeb4814e0915b432711b317923ed2b09e076aaf558c3ae8ef83f9e15a83f9ea3f47805b2750ab9e8106cb4dc6ad003522c84b03dc02829978a097899c773f6fb31f7fe6b8f2d836d96580f216fec20158f1590c3e0d7850622e15194db05", - "0x925f005059bf07e9ceccbe66c711b048e236ade775720d0fe479aebe6e23e8af281225ad18e62458dc1b03b42ad4ca290d4aa176260604a7aad0d9791337006fbdebe23746f8060d42876f45e4c83c3643931392fde1cd13ff8bddf8111ef974", - "0x9553edb22b4330c568e156a59ef03b26f5c326424f830fe3e8c0b602f08c124730ffc40bc745bec1a22417adb22a1a960243a10565c2be3066bfdb841d1cd14c624cd06e0008f4beb83f972ce6182a303bee3fcbcabc6cfe48ec5ae4b7941bfc", - "0x935f5a404f0a78bdcce709899eda0631169b366a669e9b58eacbbd86d7b5016d044b8dfc59ce7ed8de743ae16c2343b50e2f925e88ba6319e33c3fc76b314043abad7813677b4615c8a97eb83cc79de4fedf6ccbcfa4d4cbf759a5a84e4d9742", - "0xa5b014ab936eb4be113204490e8b61cd38d71da0dec7215125bcd131bf3ab22d0a32ce645bca93e7b3637cf0c2db3d6601a0ddd330dc46f9fae82abe864ffc12d656c88eb50c20782e5bb6f75d18760666f43943abb644b881639083e122f557", - "0x935b7298ae52862fa22bf03bfc1795b34c70b181679ae27de08a9f5b4b884f824ef1b276b7600efa0d2f1d79e4a470d51692fd565c5cf8343dd80e5d3336968fc21c09ba9348590f6206d4424eb229e767547daefa98bc3aa9f421158dee3f2a", - "0x9830f92446e708a8f6b091cc3c38b653505414f8b6507504010a96ffda3bcf763d5331eb749301e2a1437f00e2415efb01b799ad4c03f4b02de077569626255ac1165f96ea408915d4cf7955047620da573e5c439671d1fa5c833fb11de7afe6", - "0x840dcc44f673fff3e387af2bb41e89640f2a70bcd2b92544876daa92143f67c7512faf5f90a04b7191de01f3e2b1bde00622a20dc62ca23bbbfaa6ad220613deff43908382642d4d6a86999f662efd64b1df448b68c847cfa87630a3ffd2ec76", - "0x92950c895ed54f7f876b2fda17ecc9c41b7accfbdd42c210cc5b475e0737a7279f558148531b5c916e310604a1de25a80940c94fe5389ae5d6a5e9c371be67bceea1877f5401725a6595bcf77ece60905151b6dfcb68b75ed2e708c73632f4fd", - "0x8010246bf8e94c25fd029b346b5fbadb404ef6f44a58fd9dd75acf62433d8cc6db66974f139a76e0c26dddc1f329a88214dbb63276516cf325c7869e855d07e0852d622c332ac55609ba1ec9258c45746a2aeb1af0800141ee011da80af175d4", - "0xb0f1bad257ebd187bdc3f37b23f33c6a5d6a8e1f2de586080d6ada19087b0e2bf23b79c1b6da1ee82271323f5bdf3e1b018586b54a5b92ab6a1a16bb3315190a3584a05e6c37d5ca1e05d702b9869e27f513472bcdd00f4d0502a107773097da", - "0x9636d24f1ede773ce919f309448dd7ce023f424afd6b4b69cb98c2a988d849a283646dc3e469879daa1b1edae91ae41f009887518e7eb5578f88469321117303cd3ac2d7aee4d9cb5f82ab9ae3458e796dfe7c24284b05815acfcaa270ff22e2", - "0xb373feb5d7012fd60578d7d00834c5c81df2a23d42794fed91aa9535a4771fde0341c4da882261785e0caca40bf83405143085e7f17e55b64f6c5c809680c20b050409bf3702c574769127c854d27388b144b05624a0e24a1cbcc4d08467005b", - "0xb15680648949ce69f82526e9b67d9b55ce5c537dc6ab7f3089091a9a19a6b90df7656794f6edc87fb387d21573ffc847062623685931c2790a508cbc8c6b231dd2c34f4d37d4706237b1407673605a604bcf6a50cc0b1a2db20485e22b02c17e", - "0x8817e46672d40c8f748081567b038a3165f87994788ec77ee8daea8587f5540df3422f9e120e94339be67f186f50952504cb44f61e30a5241f1827e501b2de53c4c64473bcc79ab887dd277f282fbfe47997a930dd140ac08b03efac88d81075", - "0xa6e4ef6c1d1098f95aae119905f87eb49b909d17f9c41bcfe51127aa25fee20782ea884a7fdf7d5e9c245b5a5b32230b07e0dbf7c6743bf52ee20e2acc0b269422bd6cf3c07115df4aa85b11b2c16630a07c974492d9cdd0ec325a3fabd95044", - "0x8634aa7c3d00e7f17150009698ce440d8e1b0f13042b624a722ace68ead870c3d2212fbee549a2c190e384d7d6ac37ce14ab962c299ea1218ef1b1489c98906c91323b94c587f1d205a6edd5e9d05b42d591c26494a6f6a029a2aadb5f8b6f67", - "0x821a58092900bdb73decf48e13e7a5012a3f88b06288a97b855ef51306406e7d867d613d9ec738ebacfa6db344b677d21509d93f3b55c2ebf3a2f2a6356f875150554c6fff52e62e3e46f7859be971bf7dd9d5b3e1d799749c8a97c2e04325df", - "0x8dba356577a3a388f782e90edb1a7f3619759f4de314ad5d95c7cc6e197211446819c4955f99c5fc67f79450d2934e3c09adefc91b724887e005c5190362245eec48ce117d0a94d6fa6db12eda4ba8dde608fbbd0051f54dcf3bb057adfb2493", - "0xa32a690dc95c23ed9fb46443d9b7d4c2e27053a7fcc216d2b0020a8cf279729c46114d2cda5772fd60a97016a07d6c5a0a7eb085a18307d34194596f5b541cdf01b2ceb31d62d6b55515acfd2b9eec92b27d082fbc4dc59fc63b551eccdb8468", - "0xa040f7f4be67eaf0a1d658a3175d65df21a7dbde99bfa893469b9b43b9d150fc2e333148b1cb88cfd0447d88fa1a501d126987e9fdccb2852ecf1ba907c2ca3d6f97b055e354a9789854a64ecc8c2e928382cf09dda9abde42bbdf92280cdd96", - "0x864baff97fa60164f91f334e0c9be00a152a416556b462f96d7c43b59fe1ebaff42f0471d0bf264976f8aa6431176eb905bd875024cf4f76c13a70bede51dc3e47e10b9d5652d30d2663b3af3f08d5d11b9709a0321aba371d2ef13174dcfcaf", - "0x95a46f32c994133ecc22db49bad2c36a281d6b574c83cfee6680b8c8100466ca034b815cfaedfbf54f4e75188e661df901abd089524e1e0eb0bf48d48caa9dd97482d2e8c1253e7e8ac250a32fd066d5b5cb08a8641bdd64ecfa48289dca83a3", - "0xa2cce2be4d12144138cb91066e0cd0542c80b478bf467867ebef9ddaf3bd64e918294043500bf5a9f45ee089a8d6ace917108d9ce9e4f41e7e860cbce19ac52e791db3b6dde1c4b0367377b581f999f340e1d6814d724edc94cb07f9c4730774", - "0xb145f203eee1ac0a1a1731113ffa7a8b0b694ef2312dabc4d431660f5e0645ef5838e3e624cfe1228cfa248d48b5760501f93e6ab13d3159fc241427116c4b90359599a4cb0a86d0bb9190aa7fabff482c812db966fd2ce0a1b48cb8ac8b3bca", - "0xadabe5d215c608696e03861cbd5f7401869c756b3a5aadc55f41745ad9478145d44393fec8bb6dfc4ad9236dc62b9ada0f7ca57fe2bae1b71565dbf9536d33a68b8e2090b233422313cc96afc7f1f7e0907dc7787806671541d6de8ce47c4cd0", - "0xae7845fa6b06db53201c1080e01e629781817f421f28956589c6df3091ec33754f8a4bd4647a6bb1c141ac22731e3c1014865d13f3ed538dcb0f7b7576435133d9d03be655f8fbb4c9f7d83e06d1210aedd45128c2b0c9bab45a9ddde1c862a5", - "0x9159eaa826a24adfa7adf6e8d2832120ebb6eccbeb3d0459ffdc338548813a2d239d22b26451fda98cc0c204d8e1ac69150b5498e0be3045300e789bcb4e210d5cd431da4bdd915a21f407ea296c20c96608ded0b70d07188e96e6c1a7b9b86b", - "0xa9fc6281e2d54b46458ef564ffaed6944bff71e389d0acc11fa35d3fcd8e10c1066e0dde5b9b6516f691bb478e81c6b20865281104dcb640e29dc116daae2e884f1fe6730d639dbe0e19a532be4fb337bf52ae8408446deb393d224eee7cfa50", - "0x84291a42f991bfb36358eedead3699d9176a38f6f63757742fdbb7f631f2c70178b1aedef4912fed7b6cf27e88ddc7eb0e2a6aa4b999f3eb4b662b93f386c8d78e9ac9929e21f4c5e63b12991fcde93aa64a735b75b535e730ff8dd2abb16e04", - "0xa1b7fcacae181495d91765dfddf26581e8e39421579c9cbd0dd27a40ea4c54af3444a36bf85a11dda2114246eaddbdd619397424bb1eb41b5a15004b902a590ede5742cd850cf312555be24d2df8becf48f5afba5a8cd087cb7be0a521728386", - "0x92feaaf540dbd84719a4889a87cdd125b7e995a6782911931fef26da9afcfbe6f86aaf5328fe1f77631491ce6239c5470f44c7791506c6ef1626803a5794e76d2be0af92f7052c29ac6264b7b9b51f267ad820afc6f881460521428496c6a5f1", - "0xa525c925bfae1b89320a5054acc1fa11820f73d0cf28d273092b305467b2831fab53b6daf75fb926f332782d50e2522a19edcd85be5eb72f1497193c952d8cd0bcc5d43b39363b206eae4cb1e61668bde28a3fb2fc1e0d3d113f6dfadb799717", - "0x98752bb6f5a44213f40eda6aa4ff124057c1b13b6529ab42fe575b9afa66e59b9c0ed563fb20dff62130c436c3e905ee17dd8433ba02c445b1d67182ab6504a90bbe12c26a754bbf734665c622f76c62fe2e11dd43ce04fd2b91a8463679058b", - "0xa9aa9a84729f7c44219ff9e00e651e50ddea3735ef2a73fdf8ed8cd271961d8ed7af5cd724b713a89a097a3fe65a3c0202f69458a8b4c157c62a85668b12fc0d3957774bc9b35f86c184dd03bfefd5c325da717d74192cc9751c2073fe9d170e", - "0xb221c1fd335a4362eff504cd95145f122bf93ea02ae162a3fb39c75583fc13a932d26050e164da97cff3e91f9a7f6ff80302c19dd1916f24acf6b93b62f36e9665a8785413b0c7d930c7f1668549910f849bca319b00e59dd01e5dec8d2edacc", - "0xa71e2b1e0b16d754b848f05eda90f67bedab37709550171551050c94efba0bfc282f72aeaaa1f0330041461f5e6aa4d11537237e955e1609a469d38ed17f5c2a35a1752f546db89bfeff9eab78ec944266f1cb94c1db3334ab48df716ce408ef", - "0xb990ae72768779ba0b2e66df4dd29b3dbd00f901c23b2b4a53419226ef9232acedeb498b0d0687c463e3f1eead58b20b09efcefa566fbfdfe1c6e48d32367936142d0a734143e5e63cdf86be7457723535b787a9cfcfa32fe1d61ad5a2617220", - "0x8d27e7fbff77d5b9b9bbc864d5231fecf817238a6433db668d5a62a2c1ee1e5694fdd90c3293c06cc0cb15f7cbeab44d0d42be632cb9ff41fc3f6628b4b62897797d7b56126d65b694dcf3e298e3561ac8813fbd7296593ced33850426df42db", - "0xa92039a08b5502d5b211a7744099c9f93fa8c90cedcb1d05e92f01886219dd464eb5fb0337496ad96ed09c987da4e5f019035c5b01cc09b2a18b8a8dd419bc5895388a07e26958f6bd26751929c25f89b8eb4a299d822e2d26fec9ef350e0d3c", - "0x92dcc5a1c8c3e1b28b1524e3dd6dbecd63017c9201da9dbe077f1b82adc08c50169f56fc7b5a3b28ec6b89254de3e2fd12838a761053437883c3e01ba616670cea843754548ef84bcc397de2369adcca2ab54cd73c55dc68d87aec3fc2fe4f10" - ] -} \ No newline at end of file diff --git a/arbitrator/prover/src/kzg-trusted-setup.txt b/arbitrator/prover/src/kzg-trusted-setup.txt new file mode 100644 index 0000000000..47d1778405 --- /dev/null +++ b/arbitrator/prover/src/kzg-trusted-setup.txt @@ -0,0 +1,8259 @@ +4096 +65 +a0413c0dcafec6dbc9f47d66785cf1e8c981044f7d13cfe3e4fcbb71b5408dfde6312493cb3c1d30516cb3ca88c03654 +8b997fb25730d661918371bb41f2a6e899cac23f04fc5365800b75433c0a953250e15e7a98fb5ca5cc56a8cd34c20c57 +83302852db89424d5699f3f157e79e91dc1380f8d5895c5a772bb4ea3a5928e7c26c07db6775203ce33e62a114adaa99 +a759c48b7e4a685e735c01e5aa6ef9c248705001f470f9ad856cd87806983e917a8742a3bd5ee27db8d76080269b7c83 +967f8dc45ebc3be14c8705f43249a30ff48e96205fb02ae28daeab47b72eb3f45df0625928582aa1eb4368381c33e127 +a418eb1e9fb84cb32b370610f56f3cb470706a40ac5a47c411c464299c45c91f25b63ae3fcd623172aa0f273c0526c13 +8f44e3f0387293bc7931e978165abbaed08f53acd72a0a23ac85f6da0091196b886233bcee5b4a194db02f3d5a9b3f78 +97173434b336be73c89412a6d70d416e170ea355bf1956c32d464090b107c090ef2d4e1a467a5632fbc332eeb679bf2d +a24052ad8d55ad04bc5d951f78e14213435681594110fd18173482609d5019105b8045182d53ffce4fc29fc8810516c1 +b950768136b260277590b5bec3f56bbc2f7a8bc383d44ce8600e85bf8cf19f479898bcc999d96dfbd2001ede01d94949 +92ab8077871037bd3b57b95cbb9fb10eb11efde9191690dcac655356986fd02841d8fdb25396faa0feadfe3f50baf56d +a79b096dff98038ac30f91112dd14b78f8ad428268af36d20c292e2b3b6d9ed4fb28480bb04e465071cc67d05786b6d1 +b9ff71461328f370ce68bf591aa7fb13027044f42a575517f3319e2be4aa4843fa281e756d0aa5645428d6dfa857cef2 +8d765808c00b3543ff182e2d159c38ae174b12d1314da88ea08e13bd9d1c37184cb515e6bf6420531b5d41767987d7ce +b8c9a837d20c3b53e6f578e4a257bb7ef8fc43178614ec2a154915b267ad2be135981d01ed2ee1b5fbd9d9bb27f0800a +a9773d92cf23f65f98ef68f6cf95c72b53d0683af2f9bf886bb9036e4a38184b1131b26fd24397910b494fbef856f3aa +b41ebe38962d112da4a01bf101cb248d808fbd50aaf749fc7c151cf332032eb3e3bdbd716db899724b734d392f26c412 +90fbb030167fb47dcc13d604a726c0339418567c1d287d1d87423fa0cb92eec3455fbb46bcbe2e697144a2d3972142e4 +b11d298bd167464b35fb923520d14832bd9ed50ed841bf6d7618424fd6f3699190af21759e351b89142d355952149da1 +8bc36066f69dc89f7c4d1e58d67497675050c6aa002244cebd9fc957ec5e364c46bab4735ea3db02b73b3ca43c96e019 +ab7ab92c5d4d773068e485aa5831941ebd63db7118674ca38089635f3b4186833af2455a6fb9ed2b745df53b3ce96727 +af191ca3089892cb943cd97cf11a51f38e38bd9be50844a4e8da99f27e305e876f9ed4ab0628e8ae3939066b7d34a15f +a3204c1747feabc2c11339a542195e7cb6628fd3964f846e71e2e3f2d6bb379a5e51700682ea1844eba12756adb13216 +903a29883846b7c50c15968b20e30c471aeac07b872c40a4d19eb1a42da18b649d5bbfde4b4cf6225d215a461b0deb6d +8e6e9c15ffbf1e16e5865a5fef7ed751dc81957a9757b535cb38b649e1098cda25d42381dc4f776778573cdf90c3e6e0 +a8f6dd26100b512a8c96c52e00715c4b2cb9ac457f17aed8ffe1cf1ea524068fe5a1ddf218149845fc1417b789ecfc98 +a5b0ffc819451ea639cfd1c18cbc9365cc79368d3b2e736c0ae54eba2f0801e6eb0ee14a5f373f4a70ca463bdb696c09 +879f91ccd56a1b9736fbfd20d8747354da743fb121f0e308a0d298ff0d9344431890e41da66b5009af3f442c636b4f43 +81bf3a2d9755e206b515a508ac4d1109bf933c282a46a4ae4a1b4cb4a94e1d23642fad6bd452428845afa155742ade7e +8de778d4742f945df40004964e165592f9c6b1946263adcdd5a88b00244bda46c7bb49098c8eb6b3d97a0dd46148a8ca +b7a57b21d13121907ee28c5c1f80ee2e3e83a3135a8101e933cf57171209a96173ff5037f5af606e9fd6d066de6ed693 +b0877d1963fd9200414a38753dffd9f23a10eb3198912790d7eddbc9f6b477019d52ddd4ebdcb9f60818db076938a5a9 +88da2d7a6611bc16adc55fc1c377480c828aba4496c645e3efe0e1a67f333c05a0307f7f1d2df8ac013602c655c6e209 +95719eb02e8a9dede1a888c656a778b1c69b7716fbe3d1538fe8afd4a1bc972183c7d32aa7d6073376f7701df80116d8 +8e8a1ca971f2444b35af3376e85dccda3abb8e8e11d095d0a4c37628dfe5d3e043a377c3de68289ef142e4308e9941a0 +b720caaff02f6d798ac84c4f527203e823ff685869e3943c979e388e1c34c3f77f5c242c6daa7e3b30e511aab917b866 +86040d55809afeec10e315d1ad950d269d37cfee8c144cd8dd4126459e3b15a53b3e68df5981df3c2346d23c7b4baaf4 +82d8cabf13ab853db0377504f0aec00dba3a5cd3119787e8ad378ddf2c40b022ecfc67c642b7acc8c1e3dd03ab50993e +b8d873927936719d2484cd03a6687d65697e17dcf4f0d5aed6f5e4750f52ef2133d4645894e7ebfc4ef6ce6788d404c8 +b1235594dbb15b674a419ff2b2deb644ad2a93791ca05af402823f87114483d6aa1689b7a9bea0f547ad12fe270e4344 +a53fda86571b0651f5affb74312551a082fffc0385cfd24c1d779985b72a5b1cf7c78b42b4f7e51e77055f8e5e915b00 +b579adcfd9c6ef916a5a999e77a0cb21d378c4ea67e13b7c58709d5da23a56c2e54218691fc4ac39a4a3d74f88cc31f7 +ab79e584011713e8a2f583e483a91a0c2a40771b77d91475825b5acbea82db4262132901cb3e4a108c46d7c9ee217a4e +a0fe58ea9eb982d7654c8aaf9366230578fc1362f6faae0594f8b9e659bcb405dff4aac0c7888bbe07f614ecf0d800a6 +867e50e74281f28ecd4925560e2e7a6f8911b135557b688254623acce0dbc41e23ac3e706a184a45d54c586edc416eb0 +89f81b61adda20ea9d0b387a36d0ab073dc7c7cbff518501962038be19867042f11fcc7ff78096e5d3b68c6d8dc04d9b +a58ee91bb556d43cf01f1398c5811f76dc0f11efdd569eed9ef178b3b0715e122060ec8f945b4dbf6eebfa2b90af6fa6 +ac460be540f4c840def2eef19fc754a9af34608d107cbadb53334cf194cc91138d53b9538fcd0ec970b5d4aa455b224a +b09b91f929de52c09d48ca0893be6eb44e2f5210a6c394689dc1f7729d4be4e11d0474b178e80cea8c2ac0d081f0e811 +8d37a442a76b06a02a4e64c2504aea72c8b9b020ab7bcc94580fe2b9603c7c50d7b1e9d70d2a7daea19c68667e8f8c31 +a9838d4c4e3f3a0075a952cf7dd623307ec633fcc81a7cf9e52e66c31780de33dbb3d74c320dc7f0a4b72f7a49949515 +a44766b6251af458fe4f5f9ed1e02950f35703520b8656f09fc42d9a2d38a700c11a7c8a0436ac2e5e9f053d0bb8ff91 +ad78d9481c840f5202546bea0d13c776826feb8b1b7c72e83d99a947622f0bf38a4208551c4c41beb1270d7792075457 +b619ffa8733b470039451e224b777845021e8dc1125f247a4ff2476cc774657d0ff9c5279da841fc1236047de9d81c60 +af760b0a30a1d6af3bc5cd6686f396bd41779aeeb6e0d70a09349bd5da17ca2e7965afc5c8ec22744198fbe3f02fb331 +a0cc209abdb768b589fcb7b376b6e1cac07743288c95a1cf1a0354b47f0cf91fca78a75c1fcafa6f5926d6c379116608 +864add673c89c41c754eeb3cd8dcff5cdde1d739fce65c30e474a082bb5d813cba6412e61154ce88fdb6c12c5d9be35b +b091443b0ce279327dc37cb484e9a5b69b257a714ce21895d67539172f95ffa326903747b64a3649e99aea7bb10d03f7 +a8c452b8c4ca8e0a61942a8e08e28f17fb0ef4c5b018b4e6d1a64038280afa2bf1169202f05f14af24a06ca72f448ccd +a23c24721d18bc48d5dcf70effcbef89a7ae24e67158d70ae1d8169ee75d9a051d34b14e9cf06488bac324fe58549f26 +92a730e30eb5f3231feb85f6720489dbb1afd42c43f05a1610c6b3c67bb949ec8fde507e924498f4ffc646f7b07d9123 +8dbe5abf4031ec9ba6bb06d1a47dd1121fb9e03b652804069250967fd5e9577d0039e233441b7f837a7c9d67ba18c28e +aa456bcfef6a21bb88181482b279df260297b3778e84594ebddbdf337e85d9e3d46ca1d0b516622fb0b103df8ec519b7 +a3b31ae621bd210a2b767e0e6f22eb28fe3c4943498a7e91753225426168b9a26da0e02f1dc5264da53a5ad240d9f51b +aa8d66857127e6e71874ce2202923385a7d2818b84cb73a6c42d71afe70972a70c6bdd2aad1a6e8c5e4ca728382a8ea8 +ac7e8e7a82f439127a5e40558d90d17990f8229852d21c13d753c2e97facf077cf59582b603984c3dd3faebd80aff4f5 +93a8bcf4159f455d1baa73d2ef2450dcd4100420de84169bbe28b8b7a5d1746273f870091a87a057e834f754f34204b1 +89d0ebb287c3613cdcae7f5acc43f17f09c0213fc40c074660120b755d664109ffb9902ed981ede79e018ddb0c845698 +a87ccbfad431406aadbee878d9cf7d91b13649d5f7e19938b7dfd32645a43b114eef64ff3a13201398bd9b0337832e5a +833c51d0d0048f70c3eefb4e70e4ff66d0809c41838e8d2c21c288dd3ae9d9dfaf26d1742bf4976dab83a2b381677011 +8bcd6b1c3b02fffead432e8b1680bad0a1ac5a712d4225e220690ee18df3e7406e2769e1f309e2e803b850bc96f0e768 +b61e3dbd88aaf4ff1401521781e2eea9ef8b66d1fac5387c83b1da9e65c2aa2a56c262dea9eceeb4ad86c90211672db0 +866d3090db944ecf190dd0651abf67659caafd31ae861bab9992c1e3915cb0952da7c561cc7e203560a610f48fae633b +a5e8971543c14274a8dc892b0be188c1b4fbc75c692ed29f166e0ea80874bc5520c2791342b7c1d2fb5dd454b03b8a5b +8f2f9fc50471bae9ea87487ebd1bc8576ef844cc42d606af5c4c0969670fdf2189afd643e4de3145864e7773d215f37f +b1bb0f2527db6d51f42b9224383c0f96048bbc03d469bf01fe1383173ef8b1cc9455d9dd8ba04d46057f46949bfc92b5 +aa7c99d906b4d7922296cfe2520473fc50137c03d68b7865c5bfb8adbc316b1034310ec4b5670c47295f4a80fb8d61e9 +a5d1da4d6aba555919df44cbaa8ff79378a1c9e2cfdfbf9d39c63a4a00f284c5a5724e28ecbc2d9dba27fe4ee5018bd5 +a8db53224f70af4d991b9aae4ffe92d2aa5b618ad9137784b55843e9f16cefbfd25ada355d308e9bbf55f6d2f7976fb3 +b6536c4232bb20e22af1a8bb12de76d5fec2ad9a3b48af1f38fa67e0f8504ef60f305a73d19385095bb6a9603fe29889 +87f7e371a1817a63d6838a8cf4ab3a8473d19ce0d4f40fd013c03d5ddd5f4985df2956531cc9f187928ef54c68f4f9a9 +ae13530b1dbc5e4dced9d909ea61286ec09e25c12f37a1ed2f309b0eb99863d236c3b25ed3484acc8c076ad2fa8cd430 +98928d850247c6f7606190e687d5c94a627550198dbdbea0161ef9515eacdb1a0f195cae3bb293112179082daccf8b35 +918528bb8e6a055ad4db6230d3a405e9e55866da15c4721f5ddd1f1f37962d4904aad7a419218fe6d906fe191a991806 +b71e31a06afe065773dd3f4a6e9ef81c3292e27a3b7fdfdd452d03e05af3b6dd654c355f7516b2a93553360c6681a73a +8870b83ab78a98820866f91ac643af9f3ff792a2b7fda34185a9456a63abdce42bfe8ad4dc67f08a6392f250d4062df4 +91eea1b668e52f7a7a5087fabf1cab803b0316f78d9fff469fbfde2162f660c250e4336a9eea4cb0450bd30ac067bc8b +8b74990946de7b72a92147ceac1bd9d55999a8b576e8df68639e40ed5dc2062cfcd727903133de482b6dca19d0aaed82 +8ebad537fece090ebbab662bdf2618e21ca30cf6329c50935e8346d1217dcbe3c1fe1ea28efca369c6003ce0a94703c1 +a8640479556fb59ebd1c40c5f368fbd960932fdbb782665e4a0e24e2bdb598fc0164ce8c0726d7759cfc59e60a62e182 +a9a52a6bf98ee4d749f6d38be2c60a6d54b64d5cbe4e67266633dc096cf28c97fe998596707d31968cbe2064b72256bf +847953c48a4ce6032780e9b39d0ed4384e0be202c2bbe2dfda3910f5d87aa5cd3c2ffbfcfae4dddce16d6ab657599b95 +b6f6e1485d3ec2a06abaecd23028b200b2e4a0096c16144d07403e1720ff8f9ba9d919016b5eb8dc5103880a7a77a1d3 +98dfc2065b1622f596dbe27131ea60bef7a193b12922cecb27f8c571404f483014f8014572e86ae2e341ab738e4887ef +acb0d205566bacc87bbe2e25d10793f63f7a1f27fd9e58f4f653ceae3ffeba511eaf658e068fad289eeb28f9edbeb35b +ae4411ed5b263673cee894c11fe4abc72a4bf642d94022a5c0f3369380fcdfc1c21e277f2902972252503f91ada3029a +ac4a7a27ba390a75d0a247d93d4a8ef1f0485f8d373a4af4e1139369ec274b91b3464d9738eeaceb19cd6f509e2f8262 +87379c3bf231fdafcf6472a79e9e55a938d851d4dd662ab6e0d95fd47a478ed99e2ad1e6e39be3c0fc4f6d996a7dd833 +81316904b035a8bcc2041199a789a2e6879486ba9fddcba0a82c745cc8dd8374a39e523b91792170cd30be7aa3005b85 +b8206809c6cd027ed019f472581b45f7e12288f89047928ba32b4856b6560ad30395830d71e5e30c556f6f182b1fe690 +88d76c028f534a62e019b4a52967bb8642ede6becfa3807be68fdd36d366fc84a4ac8dc176e80a68bc59eb62caf5dff9 +8c3b8be685b0f8aad131ee7544d0e12f223f08a6f8edaf464b385ac644e0ddc9eff7cc7cb5c1b50ab5d71ea0f41d2213 +8d91410e004f76c50fdc05784157b4d839cb5090022c629c7c97a5e0c3536eeafee17a527b54b1165c3cd81774bb54ce +b25c2863bc28ec5281ce800ddf91a7e1a53f4c6d5da1e6c86ef4616e93bcf55ed49e297216d01379f5c6e7b3c1e46728 +865f7b09ac3ca03f20be90c48f6975dd2588838c2536c7a3532a6aa5187ed0b709cd03d91ff4048061c10d0aa72b69ce +b3f7477c90c11596eb4f8bbf34adbcb832638c4ff3cdd090d4d477ee50472ac9ddaf5be9ad7eca3f148960d362bbd098 +8db35fd53fca04faecd1c76a8227160b3ab46ac1af070f2492445a19d8ff7c25bbaef6c9fa0c8c088444561e9f7e4eb2 +a478b6e9d058a2e01d2fc053b739092e113c23a6a2770a16afbef044a3709a9e32f425ace9ba7981325f02667c3f9609 +98caa6bd38916c08cf221722a675a4f7577f33452623de801d2b3429595f988090907a7e99960fff7c076d6d8e877b31 +b79aaaacefc49c3038a14d2ac468cfec8c2161e88bdae91798d63552cdbe39e0e02f9225717436b9b8a40a022c633c6e +845a31006c680ee6a0cc41d3dc6c0c95d833fcf426f2e7c573fa15b2c4c641fbd6fe5ebb0e23720cc3467d6ee1d80dc4 +a1bc287e272cf8b74dbf6405b3a5190883195806aa351f1dc8e525aa342283f0a35ff687e3b434324dedee74946dd185 +a4fd2dc8db75d3783a020856e2b3aa266dc6926e84f5c491ef739a3bddd46dc8e9e0fc1177937839ef1b18d062ffbb9e +acbf0d3c697f57c202bb8c5dc4f3fc341b8fc509a455d44bd86acc67cad2a04495d5537bcd3e98680185e8aa286f2587 +a5caf423a917352e1b8e844f5968a6da4fdeae467d10c6f4bbd82b5eea46a660b82d2f5440d3641c717b2c3c9ed0be52 +8a39d763c08b926599ab1233219c49c825368fad14d9afc7c0c039224d37c00d8743293fd21645bf0b91eaf579a99867 +b2b53a496def0ba06e80b28f36530fbe0fb5d70a601a2f10722e59abee529369c1ae8fd0f2db9184dd4a2519bb832d94 +a73980fcef053f1b60ebbb5d78ba6332a475e0b96a0c724741a3abf3b59dd344772527f07203cf4c9cb5155ebed81fa0 +a070d20acce42518ece322c9db096f16aed620303a39d8d5735a0df6e70fbeceb940e8d9f5cc38f3314b2240394ec47b +a50cf591f522f19ca337b73089557f75929d9f645f3e57d4f241e14cdd1ea3fb48d84bcf05e4f0377afbb789fbdb5d20 +82a5ffce451096aca8eeb0cd2ae9d83db3ed76da3f531a80d9a70a346359bf05d74863ce6a7c848522b526156a5e20cd +88e0e84d358cbb93755a906f329db1537c3894845f32b9b0b691c29cbb455373d9452fadd1e77e20a623f6eaf624de6f +aa07ac7b84a6d6838826e0b9e350d8ec75e398a52e9824e6b0da6ae4010e5943fec4f00239e96433f291fef9d1d1e609 +ac8887bf39366034bc63f6cc5db0c26fd27307cbc3d6cce47894a8a019c22dd51322fb5096edc018227edfafc053a8f6 +b7d26c26c5b33f77422191dca94977588ab1d4b9ce7d0e19c4a3b4cd1c25211b78c328dbf81e755e78cd7d1d622ad23e +99a676d5af49f0ba44047009298d8474cabf2d5bca1a76ba21eff7ee3c4691a102fdefea27bc948ccad8894a658abd02 +b0d09a91909ab3620c183bdf1d53d43d39eb750dc7a722c661c3de3a1a5d383ad221f71bae374f8a71867505958a3f76 +84681a883de8e4b93d68ac10e91899c2bbb815ce2de74bb48a11a6113b2a3f4df8aceabda1f5f67bc5aacac8c9da7221 +9470259957780fa9b43521fab3644f555f5343281c72582b56d2efd11991d897b3b481cafa48681c5aeb80c9663b68f7 +ab1b29f7ece686e6fa968a4815da1d64f3579fed3bc92e1f3e51cd13a3c076b6cf695ed269d373300a62463dc98a4234 +8ab415bfcd5f1061f7687597024c96dd9c7cb4942b5989379a7a3b5742f7d394337886317659cbeacaf030234a24f972 +b9b524aad924f9acc63d002d617488f31b0016e0f0548f050cada285ce7491b74a125621638f19e9c96eabb091d945be +8c4c373e79415061837dd0def4f28a2d5d74d21cb13a76c9049ad678ca40228405ab0c3941df49249847ecdefc1a5b78 +a8edf4710b5ab2929d3db6c1c0e3e242261bbaa8bcec56908ddadd7d2dad2dca9d6eb9de630b960b122ebeea41040421 +8d66bb3b50b9df8f373163629f9221b3d4b6980a05ea81dc3741bfe9519cf3ebba7ab98e98390bae475e8ede5821bd5c +8d3c21bae7f0cfb97c56952bb22084b58e7bb718890935b73103f33adf5e4d99cd262f929c6eeab96209814f0dbae50a +a5c66cfab3d9ebf733c4af24bebc97070e7989fe3c73e79ac85fb0e4d40ae44fb571e0fad4ad72560e13ed453900d14f +9362e6b50b43dbefbc3254471372297b5dcce809cd3b60bf74a1268ab68bdb50e46e462cbd78f0d6c056330e982846af +854630d08e3f0243d570cc2e856234cb4c1a158d9c1883bf028a76525aaa34be897fe918d5f6da9764a3735fa9ebd24a +8c7d246985469ff252c3f4df6c7c9196fc79f05c1c66a609d84725c78001d0837c7a7049394ba5cf7e863e2d58af8417 +ae050271e01b528925302e71903f785b782f7bf4e4e7a7f537140219bc352dc7540c657ed03d3a297ad36798ecdb98cd +8d2ae9179fcf2b0c69850554580b52c1f4a5bd865af5f3028f222f4acad9c1ad69a8ef6c7dc7b03715ee5c506b74325e +b8ef8de6ce6369a8851cd36db0ccf00a85077e816c14c4e601f533330af9e3acf0743a95d28962ed8bfcfc2520ef3cfe +a6ecad6fdfb851b40356a8b1060f38235407a0f2706e7b8bb4a13465ca3f81d4f5b99466ac2565c60af15f022d26732e +819ff14cdea3ab89d98e133cd2d0379361e2e2c67ad94eeddcdb9232efd509f51d12f4f03ebd4dd953bd262a886281f7 +8561cd0f7a6dbcddd83fcd7f472d7dbcba95b2d4fb98276f48fccf69f76d284e626d7e41314b633352df8e6333fd52a1 +b42557ccce32d9a894d538c48712cb3e212d06ac05cd5e0527ccd2db1078ee6ae399bf6a601ffdab1f5913d35fc0b20c +89b4008d767aad3c6f93c349d3b956e28307311a5b1cec237e8d74bb0dee7e972c24f347fd56afd915a2342bd7bc32f0 +877487384b207e53f5492f4e36c832c2227f92d1bb60542cfeb35e025a4a7afc2b885fae2528b33b40ab09510398f83e +8c411050b63c9053dd0cd81dacb48753c3d7f162028098e024d17cd6348482703a69df31ad6256e3d25a8bbf7783de39 +a8506b54a88d17ac10fb1b0d1fe4aa40eae7553a064863d7f6b52ccc4236dd4b82d01dca6ba87da9a239e3069ba879fb +b1a24caef9df64750c1350789bb8d8a0db0f39474a1c74ea9ba064b1516db6923f00af8d57c632d58844fb8786c3d47a +959d6e255f212b0708c58a2f75cb1fe932248c9d93424612c1b8d1e640149656059737e4db2139afd5556bcdacf3eda2 +84525af21a8d78748680b6535bbc9dc2f0cf9a1d1740d12f382f6ecb2e73811d6c1da2ad9956070b1a617c61fcff9fe5 +b74417d84597a485d0a8e1be07bf78f17ebb2e7b3521b748f73935b9afbbd82f34b710fb7749e7d4ab55b0c7f9de127d +a4a9aecb19a6bab167af96d8b9d9aa5308eab19e6bfb78f5a580f9bf89bdf250a7b52a09b75f715d651cb73febd08e84 +9777b30be2c5ffe7d29cc2803a562a32fb43b59d8c3f05a707ab60ec05b28293716230a7d264d7cd9dd358fc031cc13e +95dce7a3d4f23ac0050c510999f5fbf8042f771e8f8f94192e17bcbfa213470802ebdbe33a876cb621cf42e275cbfc8b +b0b963ebcbbee847ab8ae740478544350b3ac7e86887e4dfb2299ee5096247cd2b03c1de74c774d9bde94ae2ee2dcd59 +a4ab20bafa316030264e13f7ef5891a2c3b29ab62e1668fcb5881f50a9acac6adbe3d706c07e62f2539715db768f6c43 +901478a297669d608e406fe4989be75264b6c8be12169aa9e0ad5234f459ca377f78484ffd2099a2fe2db5e457826427 +88c76e5c250810c057004a03408b85cd918e0c8903dc55a0dd8bb9b4fc2b25c87f9b8cf5943eb19fbbe99d36490050c5 +91607322bbad4a4f03fc0012d0821eff5f8c516fda45d1ec1133bface6f858bf04b25547be24159cab931a7aa08344d4 +843203e07fce3c6c81f84bc6dc5fb5e9d1c50c8811ace522dc66e8658433a0ef9784c947e6a62c11bf705307ef05212e +91dd8813a5d6dddcda7b0f87f672b83198cd0959d8311b2b26fb1fae745185c01f796fbd03aad9db9b58482483fdadd8 +8d15911aacf76c8bcd7136e958febd6963104addcd751ce5c06b6c37213f9c4fb0ffd4e0d12c8e40c36d658999724bfd +8a36c5732d3f1b497ebe9250610605ee62a78eaa9e1a45f329d09aaa1061131cf1d9df00f3a7d0fe8ad614a1ff9caaae +a407d06affae03660881ce20dab5e2d2d6cddc23cd09b95502a9181c465e57597841144cb34d22889902aff23a76d049 +b5fd856d0578620a7e25674d9503be7d97a2222900e1b4738c1d81ff6483b144e19e46802e91161e246271f90270e6cf +91b7708869cdb5a7317f88c0312d103f8ce90be14fb4f219c2e074045a2a83636fdc3e69e862049fc7c1ef000e832541 +b64719cc5480709d1dae958f1d3082b32a43376da446c8f9f64cb02a301effc9c34d9102051733315a8179aed94d53cc +94347a9542ff9d18f7d9eaa2f4d9b832d0e535fe49d52aa2de08aa8192400eddabdb6444a2a78883e27c779eed7fdf5a +840ef44a733ff1376466698cd26f82cf56bb44811e196340467f932efa3ae1ef9958a0701b3b032f50fd9c1d2aed9ab5 +90ab3f6f67688888a31ffc2a882bb37adab32d1a4b278951a21646f90d03385fc976715fc639a785d015751171016f10 +b56f35d164c24b557dbcbc8a4bfa681ec916f8741ffcb27fb389c164f4e3ed2be325210ef5bdaeae7a172ca9599ab442 +a7921a5a80d7cf6ae81ba9ee05e0579b18c20cd2852762c89d6496aa4c8ca9d1ca2434a67b2c16d333ea8e382cdab1e3 +a506bcfbd7e7e5a92f68a1bd87d07ad5fe3b97aeee40af2bf2cae4efcd77fff03f872732c5b7883aa6584bee65d6f8cb +a8c46cff58931a1ce9cbe1501e1da90b174cddd6d50f3dfdfb759d1d4ad4673c0a8feed6c1f24c7af32865a7d6c984e5 +b45686265a83bff69e312c5149db7bb70ac3ec790dc92e392b54d9c85a656e2bf58596ce269f014a906eafc97461aa5f +8d4009a75ccb2f29f54a5f16684b93202c570d7a56ec1a8b20173269c5f7115894f210c26b41e8d54d4072de2d1c75d0 +aef8810af4fc676bf84a0d57b189760ddc3375c64e982539107422e3de2580b89bd27aa6da44e827b56db1b5555e4ee8 +888f0e1e4a34f48eb9a18ef4de334c27564d72f2cf8073e3d46d881853ac1424d79e88d8ddb251914890588937c8f711 +b64b0aa7b3a8f6e0d4b3499fe54e751b8c3e946377c0d5a6dbb677be23736b86a7e8a6be022411601dd75012012c3555 +8d57776f519f0dd912ea14f79fbab53a30624e102f9575c0bad08d2dc754e6be54f39b11278c290977d9b9c7c0e1e0ad +a018fc00d532ceb2e4de908a15606db9b6e0665dd77190e2338da7c87a1713e6b9b61554e7c1462f0f6d4934b960b15c +8c932be83ace46f65c78e145b384f58e41546dc0395270c1397874d88626fdeda395c8a289d602b4c312fe98c1311856 +89174838e21639d6bdd91a0621f04dc056907b88e305dd66e46a08f6d65f731dea72ae87ca5e3042d609e8de8de9aa26 +b7b7f508bb74f7a827ac8189daa855598ff1d96fa3a02394891fd105d8f0816224cd50ac4bf2ed1cf469ace516c48184 +b31877ad682583283baadd68dc1bebd83f5748b165aadd7fe9ef61a343773b88bcd3a022f36d6c92f339b7bfd72820a9 +b79d77260b25daf9126dab7a193df2d7d30542786fa1733ffaf6261734770275d3ca8bae1d9915d1181a78510b3439db +91894fb94cd4c1dd2ceaf9c53a7020c5799ba1217cf2d251ea5bc91ed26e1159dd758e98282ebe35a0395ef9f1ed15a0 +ab59895cdafd33934ceedfc3f0d5d89880482cba6c99a6db93245f9e41987efd76e0640e80aef31782c9a8c7a83fccec +aa22ea63654315e033e09d4d4432331904a6fc5fb1732557987846e3c564668ca67c60a324b4af01663a23af11a9ce4b +b53ba3ef342601467e1f71aa280e100fbabbd38518fa0193e0099505036ee517c1ac78e96e9baeb549bb6879bb698fb0 +943fd69fd656f37487cca3605dc7e5a215fddd811caf228595ec428751fc1de484a0cb84c667fe4d7c35599bfa0e5e34 +9353128b5ebe0dddc555093cf3e5942754f938173541033e8788d7331fafc56f68d9f97b4131e37963ab7f1c8946f5f1 +a76cd3c566691f65cfb86453b5b31dbaf3cab8f84fe1f795dd1e570784b9b01bdd5f0b3c1e233942b1b5838290e00598 +983d84b2e53ffa4ae7f3ba29ef2345247ea2377686b74a10479a0ef105ecf90427bf53b74c96dfa346d0f842b6ffb25b +92e0fe9063306894a2c6970c001781cff416c87e87cb5fbac927a3192655c3da4063e6fa93539f6ff58efac6adcc5514 +b00a81f03c2b8703acd4e2e4c21e06973aba696415d0ea1a648ace2b0ea19b242fede10e4f9d7dcd61c546ab878bc8f9 +b0d08d880f3b456a10bf65cff983f754f545c840c413aea90ce7101a66eb0a0b9b1549d6c4d57725315828607963f15a +90cb64d03534f913b411375cce88a9e8b1329ce67a9f89ca5df8a22b8c1c97707fec727dbcbb9737f20c4cf751359277 +8327c2d42590dfcdb78477fc18dcf71608686ad66c49bce64d7ee874668be7e1c17cc1042a754bbc77c9daf50b2dae07 +8532171ea13aa7e37178e51a6c775da469d2e26ec854eb16e60f3307db4acec110d2155832c202e9ba525fc99174e3b0 +83ca44b15393d021de2a511fa5511c5bd4e0ac7d67259dce5a5328f38a3cce9c3a269405959a2486016bc27bb140f9ff +b1d36e8ca812be545505c8214943b36cabee48112cf0de369957afa796d37f86bf7249d9f36e8e990f26f1076f292b13 +9803abf45be5271e2f3164c328d449efc4b8fc92dfc1225d38e09630909fe92e90a5c77618daa5f592d23fc3ad667094 +b268ad68c7bf432a01039cd889afae815c3e120f57930d463aece10af4fd330b5bd7d8869ef1bcf6b2e78e4229922edc +a4c91a0d6f16b1553264592b4cbbbf3ca5da32ab053ffbdd3dbb1aed1afb650fb6e0dc5274f71a51d7160856477228db +ad89d043c2f0f17806277ffdf3ecf007448e93968663f8a0b674254f36170447b7527d5906035e5e56f4146b89b5af56 +8b6964f757a72a22a642e4d69102951897e20c21449184e44717bd0681d75f7c5bfa5ee5397f6e53febf85a1810d6ed1 +b08f5cdaabec910856920cd6e836c830b863eb578423edf0b32529488f71fe8257d90aed4a127448204df498b6815d79 +af26bb3358be9d280d39b21d831bb53145c4527a642446073fee5a86215c4c89ff49a3877a7a549486262f6f57a0f476 +b4010b37ec4d7c2af20800e272539200a6b623ae4636ecbd0e619484f4ab9240d02bc5541ace3a3fb955dc0a3d774212 +82752ab52bdcc3cc2fc405cb05a2e694d3df4a3a68f2179ec0652536d067b43660b96f85f573f26fbd664a9ef899f650 +96d392dde067473a81faf2d1fea55b6429126b88b160e39b4210d31d0a82833ffd3a80e07d24d495aea2d96be7251547 +a76d8236d6671204d440c33ac5b8deb71fa389f6563d80e73be8b043ec77d4c9b06f9a586117c7f957f4af0331cbc871 +b6c90961f68b5e385d85c9830ec765d22a425f506904c4d506b87d8944c2b2c09615e740ed351df0f9321a7b93979cae +a6ec5ea80c7558403485b3b1869cdc63bde239bafdf936d9b62a37031628402a36a2cfa5cfbb8e26ac922cb0a209b3ba +8c3195bbdbf9bc0fc95fa7e3d7f739353c947f7767d1e3cb24d8c8602d8ea0a1790ac30b815be2a2ba26caa5227891e2 +a7f8a63d809f1155722c57f375ea00412b00147776ae4444f342550279ef4415450d6f400000a326bf11fea6c77bf941 +97fa404df48433a00c85793440e89bb1af44c7267588ae937a1f5d53e01e1c4d4fc8e4a6d517f3978bfdd6c2dfde012f +a984a0a3836de3d8d909c4629a2636aacb85393f6f214a2ef68860081e9db05ad608024762db0dc35e895dc00e2d4cdd +9526cf088ab90335add1db4d3a4ac631b58cbfbe88fa0845a877d33247d1cfeb85994522e1eb8f8874651bfb1df03e2a +ac83443fd0afe99ad49de9bf8230158c118e2814c9c89db5ac951c240d6c2ce45e7677221279d9e97848ec466b99aafe +aeeefdbaba612e971697798ceaf63b247949dc823a0ad771ae5b988a5e882b338a98d3d0796230f49d533ec5ba411b39 +ae3f248b5a7b0f92b7820a6c5ae21e5bd8f4265d4f6e21a22512079b8ee9be06393fd3133ce8ebac0faf23f4f8517e36 +a64a831b908eee784b8388b45447d2885ec0551b26b0c2b15e5f417d0a12c79e867fb7bd3d008d0af98b44336f8ec1ad +b242238cd8362b6e440ba21806905714dd55172db25ec7195f3fc4937b2aba146d5cbf3cf691a1384b4752dc3b54d627 +819f97f337eea1ffb2a678cc25f556f1aab751c6b048993a1d430fe1a3ddd8bb411c152e12ca60ec6e057c190cd1db9a +b9d7d187407380df54ee9fef224c54eec1bfabf17dc8abf60765b7951f538f59aa26fffd5846cfe05546c35f59b573f4 +aa6e3c14efa6a5962812e3f94f8ce673a433f4a82d07a67577285ea0eaa07f8be7115853122d12d6d4e1fdf64c504be1 +82268bee9c1662d3ddb5fb785abfae6fb8b774190f30267f1d47091d2cd4b3874db4372625aa36c32f27b0eee986269b +b236459565b7b966166c4a35b2fa71030b40321821b8e96879d95f0e83a0baf33fa25721f30af4a631df209e25b96061 +8708d752632d2435d2d5b1db4ad1fa2558d776a013655f88e9a3556d86b71976e7dfe5b8834fdec97682cd94560d0d0d +ae1424a68ae2dbfb0f01211f11773732a50510b5585c1fb005cb892b2c6a58f4a55490b5c5b4483c6fce40e9d3236a52 +b3f5f722af9dddb07293c871ce97abbccba0093ca98c8d74b1318fa21396fc1b45b69c15084f63d728f9908442024506 +9606f3ce5e63886853ca476dc0949e7f1051889d529365c0cb0296fdc02abd088f0f0318ecd2cf36740a3634132d36f6 +b11a833a49fa138db46b25ff8cdda665295226595bc212c0931b4931d0a55c99da972c12b4ef753f7e37c6332356e350 +afede34e7dab0a9e074bc19a7daddb27df65735581ca24ad70c891c98b1349fcebbcf3ba6b32c2617fe06a5818dabc2d +97993d456e459e66322d01f8eb13918979761c3e8590910453944bdff90b24091bb018ac6499792515c9923be289f99f +977e3e967eff19290a192cd11df3667d511b398fb3ac9a5114a0f3707e25a0edcb56105648b1b85a8b7519fc529fc6f6 +b873a7c88bf58731fe1bf61ff6828bf114cf5228f254083304a4570e854e83748fc98683ddba62d978fff7909f2c5c47 +ad4b2691f6f19da1d123aaa23cca3e876247ed9a4ab23c599afdbc0d3aa49776442a7ceaa996ac550d0313d9b9a36cee +b9210713c78e19685608c6475bfa974b57ac276808a443f8b280945c5d5f9c39da43effa294bfb1a6c6f7b6b9f85bf6c +a65152f376113e61a0e468759de38d742caa260291b4753391ee408dea55927af08a4d4a9918600a3bdf1df462dffe76 +8bf8c27ad5140dde7f3d2280fd4cc6b29ab76537e8d7aa7011a9d2796ee3e56e9a60c27b5c2da6c5e14fc866301dc195 +92fde8effc9f61393a2771155812b863cff2a0c5423d7d40aa04d621d396b44af94ddd376c28e7d2f53c930aea947484 +97a01d1dd9ee30553ce676011aea97fa93d55038ada95f0057d2362ae9437f3ed13de8290e2ff21e3167dd7ba10b9c3f +89affffaa63cb2df3490f76f0d1e1d6ca35c221dd34057176ba739fa18d492355e6d2a5a5ad93a136d3b1fed0bb8aa19 +928b8e255a77e1f0495c86d3c63b83677b4561a5fcbbe5d3210f1e0fc947496e426d6bf3b49394a5df796c9f25673fc4 +842a0af91799c9b533e79ee081efe2a634cac6c584c2f054fb7d1db67dde90ae36de36cbf712ec9cd1a0c7ee79e151ea +a65b946cf637e090baf2107c9a42f354b390e7316beb8913638130dbc67c918926eb87bec3b1fe92ef72bc77a170fa3b +aafc0f19bfd71ab5ae4a8510c7861458b70ad062a44107b1b1dbacbfa44ba3217028c2824bd7058e2fa32455f624040b +95269dc787653814e0be899c95dba8cfa384f575a25e671c0806fd80816ad6797dc819d30ae06e1d0ed9cb01c3950d47 +a1e760f7fa5775a1b2964b719ff961a92083c5c617f637fc46e0c9c20ab233f8686f7f38c3cb27d825c54dd95e93a59b +ac3b8a7c2317ea967f229eddc3e23e279427f665c4705c7532ed33443f1243d33453c1088f57088d2ab1e3df690a9cc9 +b787beeddfbfe36dd51ec4efd9cf83e59e84d354c3353cc9c447be53ae53d366ed1c59b686e52a92f002142c8652bfe0 +b7a64198300cb6716aa7ac6b25621f8bdec46ad5c07a27e165b3f774cdf65bcfdbf31e9bae0c16b44de4b00ada7a4244 +b8ae9f1452909e0c412c7a7fe075027691ea8df1347f65a5507bc8848f1d2c833d69748076db1129e5b4fb912f65c86c +9682e41872456b9fa67def89e71f06d362d6c8ca85c9c48536615bc401442711e1c9803f10ab7f8ab5feaec0f9df20a6 +88889ff4e271dc1c7e21989cc39f73cde2f0475acd98078281591ff6c944fadeb9954e72334319050205d745d4df73df +8f79b5b8159e7fd0d93b0645f3c416464f39aec353b57d99ecf24f96272df8a068ad67a6c90c78d82c63b40bb73989bb +838c01a009a3d8558a3f0bdd5e22de21af71ca1aefc8423c91dc577d50920e9516880e87dce3e6d086e11cd45c9052d9 +b97f1c6eee8a78f137c840667cc288256e39294268a3009419298a04a1d0087c9c9077b33c917c65caf76637702dda8a +972284ce72f96a61c899260203dfa06fc3268981732bef74060641c1a5068ead723e3399431c247ca034b0dae861e8df +945a8d52d6d3db6663dbd3110c6587f9e9c44132045eeffba15621576d178315cb52870fa5861669f84f0bee646183fe +a0a547b5f0967b1c3e5ec6c6a9a99f0578521489180dfdfbb5561f4d166baac43a2f06f950f645ce991664e167537eed +a0592cda5cdddf1340033a745fd13a6eff2021f2e26587116c61c60edead067e0f217bc2bef4172a3c9839b0b978ab35 +b9c223b65a3281587fa44ec829e609154b32f801fd1de6950e01eafb07a8324243b960d5735288d0f89f0078b2c42b5b +99ebfc3b8f9f98249f4d37a0023149ed85edd7a5abe062c8fb30c8c84555258b998bdcdd1d400bc0fa2a4aaa8b224466 +955b68526e6cb3937b26843270f4e60f9c6c8ece2fa9308fe3e23afa433309c068c66a4bc16ee2cf04220f095e9afce4 +b766caeafcc00378135ae53397f8a67ed586f5e30795462c4a35853de6681b1f17401a1c40958de32b197c083b7279c1 +921bf87cad947c2c33fa596d819423c10337a76fe5a63813c0a9dc78a728207ae7b339407a402fc4d0f7cba3af6da6fc +a74ba1f3bc3e6c025db411308f49b347ec91da1c916bda9da61e510ec8d71d25e0ac0f124811b7860e5204f93099af27 +a29b4d144e0bf17a7e8353f2824cef0ce85621396babe8a0b873ca1e8a5f8d508b87866cf86da348470649fceefd735c +a8040e12ffc3480dd83a349d06741d1572ef91932c46f5cf03aee8454254156ee95786fd013d5654725e674c920cec32 +8c4cf34ca60afd33923f219ffed054f90cd3f253ffeb2204a3b61b0183417e366c16c07fae860e362b0f2bfe3e1a1d35 +8195eede4ddb1c950459df6c396b2e99d83059f282b420acc34220cadeed16ab65c856f2c52568d86d3c682818ed7b37 +91fff19e54c15932260aa990c7fcb3c3c3da94845cc5aa8740ef56cf9f58d19b4c3c55596f8d6c877f9f4d22921d93aa +a3e0bf7e5d02a80b75cf75f2db7e66cb625250c45436e3c136d86297d652590ec97c2311bafe407ad357c79ab29d107b +81917ff87e5ed2ae4656b481a63ced9e6e5ff653b8aa6b7986911b8bc1ee5b8ef4f4d7882c3f250f2238e141b227e510 +915fdbe5e7de09c66c0416ae14a8750db9412e11dc576cf6158755fdcaf67abdbf0fa79b554cac4fe91c4ec245be073f +8df27eafb5c3996ba4dc5773c1a45ca77e626b52e454dc1c4058aa94c2067c18332280630cc3d364821ee53bf2b8c130 +934f8a17c5cbb827d7868f5c8ca00cb027728a841000a16a3428ab16aa28733f16b52f58c9c4fbf75ccc45df72d9c4df +b83f4da811f9183c25de8958bc73b504cf790e0f357cbe74ef696efa7aca97ad3b7ead1faf76e9f982c65b6a4d888fc2 +87188213c8b5c268dc2b6da413f0501c95749e953791b727450af3e43714149c115b596b33b63a2f006a1a271b87efd0 +83e9e888ab9c3e30761de635d9aabd31248cdd92f7675fc43e4b21fd96a03ec1dc4ad2ec94fec857ffb52683ac98e360 +b4b9a1823fe2d983dc4ec4e3aaea297e581c3fc5ab4b4af5fa1370caa37af2d1cc7fc6bfc5e7da60ad8fdce27dfe4b24 +856388bc78aef465dbcdd1f559252e028c9e9a2225c37d645c138e78f008f764124522705822a61326a6d1c79781e189 +a6431b36db93c3b47353ba22e7c9592c9cdfb9cbdd052ecf2cc3793f5b60c1e89bc96e6bae117bfd047f2308da00dd2f +b619972d48e7e4291542dcde08f7a9cdc883c892986ded2f23ccb216e245cd8d9ad1d285347b0f9d7611d63bf4cee2bc +8845cca6ff8595955f37440232f8e61d5351500bd016dfadd182b9d39544db77a62f4e0102ff74dd4173ae2c181d24ef +b2f5f7fa26dcd3b6550879520172db2d64ee6aaa213cbef1a12befbce03f0973a22eb4e5d7b977f466ac2bf8323dcedd +858b7f7e2d44bdf5235841164aa8b4f3d33934e8cb122794d90e0c1cac726417b220529e4f896d7b77902ab0ccd35b3a +80b0408a092dae2b287a5e32ea1ad52b78b10e9c12f49282976cd738f5d834e03d1ad59b09c5ccaccc39818b87d06092 +b996b0a9c6a2d14d984edcd6ab56bc941674102980d65b3ad9733455f49473d3f587c8cbf661228a7e125ddbe07e3198 +90224fcebb36865293bd63af786e0c5ade6b67c4938d77eb0cbae730d514fdd0fe2d6632788e858afd29d46310cf86df +b71351fdfff7168b0a5ec48397ecc27ac36657a8033d9981e97002dcca0303e3715ce6dd3f39423bc8ef286fa2e9e669 +ae2a3f078b89fb753ce4ed87e0c1a58bb19b4f0cfb6586dedb9fcab99d097d659a489fb40e14651741e1375cfc4b6c5f +8ef476b118e0b868caed297c161f4231bbeb863cdfa5e2eaa0fc6b6669425ce7af50dc374abceac154c287de50c22307 +92e46ab472c56cfc6458955270d3c72b7bde563bb32f7d4ab4d959db6f885764a3d864e1aa19802fefaa5e16b0cb0b54 +96a3f68323d1c94e73d5938a18a377af31b782f56212de3f489d22bc289cf24793a95b37f1d6776edf88114b5c1fa695 +962cc068cfce6faaa27213c4e43e44eeff0dfbb6d25b814e82c7da981fb81d7d91868fa2344f05fb552362f98cfd4a72 +895d4e4c4ad670abf66d43d59675b1add7afad7438ada8f42a0360c704cee2060f9ac15b4d27e9b9d0996bb801276fe3 +b3ad18d7ece71f89f2ef749b853c45dc56bf1c796250024b39a1e91ed11ca32713864049c9aaaea60cde309b47486bbf +8f05404e0c0258fdbae50e97ccb9b72ee17e0bd2400d9102c0dad981dac8c4c71585f03e9b5d50086d0a2d3334cb55d1 +8bd877e9d4591d02c63c6f9fc9976c109de2d0d2df2bfa5f6a3232bab5b0b8b46e255679520480c2d7a318545efa1245 +8d4c16b5d98957c9da13d3f36c46f176e64e5be879f22be3179a2c0e624fe4758a82bf8c8027410002f973a3b84cd55a +86e2a8dea86427b424fa8eada881bdff896907084a495546e66556cbdf070b78ba312bf441eb1be6a80006d25d5097a3 +8608b0c117fd8652fdab0495b08fadbeba95d9c37068e570de6fddfef1ba4a1773b42ac2be212836141d1bdcdef11a17 +a13d6febf5fb993ae76cae08423ca28da8b818d6ef0fde32976a4db57839cd45b085026b28ee5795f10a9a8e3098c683 +8e261967fa6de96f00bc94a199d7f72896a6ad8a7bbb1d6187cca8fad824e522880e20f766620f4f7e191c53321d70f9 +8b8e8972ac0218d7e3d922c734302803878ad508ca19f5f012bc047babd8a5c5a53deb5fe7c15a4c00fd6d1cb9b1dbd0 +b5616b233fb3574a2717d125a434a2682ff68546dccf116dd8a3b750a096982f185614b9fb6c7678107ff40a451f56fa +aa6adf9b0c3334b0d0663f583a4914523b2ac2e7adffdb026ab9109295ff6af003ef8357026dbcf789896d2afded8d73 +acb72df56a0b65496cd534448ed4f62950bb1e11e50873b6ed349c088ee364441821294ce0f7c61bd7d38105bea3b442 +abae12df83e01ec947249fedd0115dc501d2b03ff7232092979eda531dbbca29ace1d46923427c7dde4c17bdf3fd7708 +820b4fc2b63a9fda7964acf5caf19a2fc4965007cb6d6b511fcafcb1f71c3f673a1c0791d3f86e3a9a1eb6955b191cc0 +af277259d78c6b0f4f030a10c53577555df5e83319ddbad91afbd7c30bc58e7671c56d00d66ec3ab5ef56470cd910cee +ad4a861c59f1f5ca1beedd488fb3d131dea924fffd8e038741a1a7371fad7370ca5cf80dc01f177fbb9576713bb9a5b3 +b67a5162982ce6a55ccfb2f177b1ec26b110043cf18abd6a6c451cf140b5af2d634591eb4f28ad92177d8c7e5cd0a5e8 +96176d0a83816330187798072d449cbfccff682561e668faf6b1220c9a6535b32a6e4f852e8abb00f79abb87493df16b +b0afe6e7cb672e18f0206e4423f51f8bd0017bf464c4b186d46332c5a5847647f89ff7fa4801a41c1b0b42f6135bcc92 +8fc5e7a95ef20c1278c645892811f6fe3f15c431ebc998a32ec0da44e7213ea934ed2be65239f3f49b8ec471e9914160 +b7793e41adda6c82ba1f2a31f656f6205f65bf8a3d50d836ee631bc7ce77c153345a2d0fc5c60edf8b37457c3729c4ec +a504dd7e4d6b2f4379f22cc867c65535079c75ccc575955f961677fa63ecb9f74026fa2f60c9fb6323c1699259e5e9c8 +ab899d00ae693649cc1afdf30fb80d728973d2177c006e428bf61c7be01e183866614e05410041bc82cb14a33330e69c +8a3bd8b0b1be570b65c4432a0f6dc42f48a2000e30ab089cf781d38f4090467b54f79c0d472fcbf18ef6a00df69cc6f3 +b4d7028f7f76a96a3d7803fca7f507ae11a77c5346e9cdfccb120a833a59bda1f4264e425aa588e7a16f8e7638061d84 +b9c7511a76ea5fb105de905d44b02edb17008335766ee357ed386b7b3cf19640a98b38785cb14603c1192bee5886c9b6 +8563afb12e53aed71ac7103ab8602bfa8371ae095207cb0d59e8fd389b6ad1aff0641147e53cb6a7ca16c7f37c9c5e6b +8e108be614604e09974a9ed90960c28c4ea330a3d9a0cb4af6dd6f193f84ab282b243ecdf549b3131036bebc8905690c +b794d127fbedb9c5b58e31822361706ffac55ce023fbfe55716c3c48c2fd2f2c7660a67346864dfe588812d369cb50b6 +b797a3442fc3b44f41baefd30346f9ac7f96e770d010d53c146ce74ce424c10fb62758b7e108b8abfdc5fafd89d745cb +993bb71e031e8096442e6205625e1bfddfe6dd6a83a81f3e2f84fafa9e5082ab4cad80a099f21eff2e81c83457c725c3 +8711ab833fc03e37acf2e1e74cfd9133b101ff4144fe30260654398ae48912ab46549d552eb9d15d2ea57760d35ac62e +b21321fd2a12083863a1576c5930e1aecb330391ef83326d9d92e1f6f0d066d1394519284ddab55b2cb77417d4b0292f +877d98f731ffe3ee94b0b5b72d127630fa8a96f6ca4f913d2aa581f67732df6709493693053b3e22b0181632ac6c1e3b +ae391c12e0eb8c145103c62ea64f41345973311c3bf7281fa6bf9b7faafac87bcf0998e5649b9ef81e288c369c827e07 +b83a2842f36998890492ab1cd5a088d9423d192681b9a3a90ec518d4c541bce63e6c5f4df0f734f31fbfdd87785a2463 +a21b6a790011396e1569ec5b2a423857b9bec16f543e63af28024e116c1ea24a3b96e8e4c75c6537c3e4611fd265e896 +b4251a9c4aab3a495da7a42e684ba4860dbcf940ad1da4b6d5ec46050cbe8dab0ab9ae6b63b5879de97b905723a41576 +8222f70aebfe6ac037f8543a08498f4cadb3edaac00336fc00437eb09f2cba758f6c38e887cc634b4d5b7112b6334836 +86f05038e060594c46b5d94621a1d9620aa8ba59a6995baf448734e21f58e23c1ea2993d3002ad5250d6edd5ba59b34f +a7c0c749baef811ab31b973c39ceb1d94750e2bc559c90dc5eeb20d8bb6b78586a2b363c599ba2107d6be65cd435f24e +861d46a5d70b38d6c1cd72817a2813803d9f34c00320c8b62f8b9deb67f5b5687bc0b37c16d28fd017367b92e05da9ca +b3365d3dab639bffbe38e35383686a435c8c88b397b717cd4aeced2772ea1053ceb670f811f883f4e02975e5f1c4ac58 +a5750285f61ab8f64cd771f6466e2c0395e01b692fd878f2ef2d5c78bdd8212a73a3b1dfa5e4c8d9e1afda7c84857d3b +835a10809ccf939bc46cf950a33b36d71be418774f51861f1cd98a016ade30f289114a88225a2c11e771b8b346cbe6ef +a4f59473a037077181a0a62f1856ec271028546ca9452b45cedfcb229d0f4d1aabfc13062b07e536cc8a0d4b113156a2 +95cd14802180b224d44a73cc1ed599d6c4ca62ddcaa503513ccdc80aaa8be050cc98bd4b4f3b639549beb4587ac6caf9 +973b731992a3e69996253d7f36dd7a0af1982b5ed21624b77a7965d69e9a377b010d6dabf88a8a97eec2a476259859cc +af8a1655d6f9c78c8eb9a95051aa3baaf9c811adf0ae8c944a8d3fcba87b15f61021f3baf6996fa0aa51c81b3cb69de1 +835aad5c56872d2a2d6c252507b85dd742bf9b8c211ccb6b25b52d15c07245b6d89b2a40f722aeb5083a47cca159c947 +abf4e970b02bef8a102df983e22e97e2541dd3650b46e26be9ee394a3ea8b577019331857241d3d12b41d4eacd29a3ac +a13c32449dbedf158721c13db9539ae076a6ce5aeaf68491e90e6ad4e20e20d1cdcc4a89ed9fd49cb8c0dd50c17633c1 +8c8f78f88b7e22dd7e9150ab1c000f10c28e696e21d85d6469a6fe315254740f32e73d81ab1f3c1cf8f544c86df506e8 +b4b77f2acfe945abf81f2605f906c10b88fb4d28628487fb4feb3a09f17f28e9780445dfcee4878349d4c6387a9d17d4 +8d255c235f3812c6ecc646f855fa3832be5cb4dbb9c9e544989fafdf3f69f05bfd370732eaf954012f0044aa013fc9c6 +b982efd3f34b47df37c910148ac56a84e8116647bea24145a49e34e0a6c0176e3284d838dae6230cb40d0be91c078b85 +983f365aa09bd85df2a6a2ad8e4318996b1e27d02090755391d4486144e40d80b1fbfe1c798d626db92f52e33aa634da +95fd1981271f3ea3a41d654cf497e6696730d9ff7369f26bc4d7d15c7adb4823dd0c42e4a005a810af12d234065e5390 +a9f5219bd4b913c186ef30c02f995a08f0f6f1462614ea5f236964e02bdaa33db9d9b816c4aee5829947840a9a07ba60 +9210e6ceb05c09b46fd09d036287ca33c45124ab86315e5d6911ff89054f1101faaa3e83d123b7805056d388bcec6664 +8ed9cbf69c6ff3a5c62dd9fe0d7264578c0f826a29e614bc2fb4d621d90c8c9992438accdd7a614b1dca5d1bb73dc315 +85cf2a8cca93e00da459e3cecd22c342d697eee13c74d5851634844fc215f60053cf84b0e03c327cb395f48d1c71a8a4 +8818a18e9a2ec90a271b784400c1903089ffb0e0b40bc5abbbe12fbebe0f731f91959d98c5519ef1694543e31e2016d4 +8dabc130f296fa7a82870bf9a8405aaf542b222ed9276bba9bd3c3555a0f473acb97d655ee7280baff766a827a8993f0 +ac7952b84b0dc60c4d858f034093b4d322c35959605a3dad2b806af9813a4680cb038c6d7f4485b4d6b2ff502aaeca25 +ad65cb6d57b48a2602568d2ec8010baed0eb440eec7638c5ec8f02687d764e9de5b5d42ad5582934e592b48471c22d26 +a02ab8bd4c3d114ea23aebdd880952f9495912817da8c0c08eabc4e6755439899d635034413d51134c72a6320f807f1c +8319567764b8295402ec1ebef4c2930a138480b37e6d7d01c8b4c9cd1f2fc3f6e9a44ae6e380a0c469b25b06db23305f +afec53b2301dc0caa8034cd9daef78c48905e6068d692ca23d589b84a6fa9ddc2ed24a39480597e19cb3e83eec213b3f +ac0b4ffdb5ae08e586a9cdb98f9fe56f4712af3a97065e89e274feacfb52b53c839565aee93c4cfaaccfe51432c4fab0 +8972cbf07a738549205b1094c5987818124144bf187bc0a85287c94fdb22ce038c0f11df1aa16ec5992e91b44d1af793 +b7267aa6f9e3de864179b7da30319f1d4cb2a3560f2ea980254775963f1523b44c680f917095879bebfa3dc2b603efcf +80f68f4bfc337952e29504ee5149f15093824ea7ab02507efd1317a670f6cbc3611201848560312e3e52e9d9af72eccf +8897fee93ce8fc1e1122e46b6d640bba309384dbd92e46e185e6364aa8210ebf5f9ee7e5e604b6ffba99aa80a10dd7d0 +b58ea6c02f2360be60595223d692e82ee64874fda41a9f75930f7d28586f89be34b1083e03bbc1575bbfdda2d30db1ea +85a523a33d903280d70ac5938770453a58293480170c84926457ac2df45c10d5ff34322ab130ef4a38c916e70d81af53 +a2cbf045e1bed38937492c1f2f93a5ba41875f1f262291914bc1fc40c60bd0740fb3fea428faf6da38b7c180fe8ac109 +8c09328770ed8eb17afc6ac7ddd87bb476de18ed63cab80027234a605806895959990c47bd10d259d7f3e2ecb50074c9 +b4b9e19edb4a33bde8b7289956568a5b6b6557404e0a34584b5721fe6f564821091013fbb158e2858c6d398293bb4b59 +8a47377df61733a2aa5a0e945fce00267f8e950f37e109d4487d92d878fb8b573317bb382d902de515b544e9e233458d +b5804c9d97efeff5ca94f3689b8088c62422d92a1506fd1d8d3b1b30e8a866ad0d6dad4abfa051dfc4471250cac4c5d9 +9084a6ee8ec22d4881e9dcc8a9eb3c2513523d8bc141942370fd191ad2601bf9537a0b1e84316f3209b3d8a54368051e +85447eea2fa26656a649f8519fa67279183044791d61cf8563d0783d46d747d96af31d0a93507bbb2242666aa87d3720 +97566a84481027b60116c751aec552adfff2d9038e68d48c4db9811fb0cbfdb3f1d91fc176a0b0d988a765f8a020bce1 +ae87e5c1b9e86c49a23dceda4ecfd1dcf08567f1db8e5b6ec752ebd45433c11e7da4988573cdaebbb6f4135814fc059e +abee05cf9abdbc52897ac1ce9ed157f5466ed6c383d6497de28616238d60409e5e92619e528af8b62cc552bf09970dc2 +ae6d31cd7bf9599e5ee0828bab00ceb4856d829bba967278a73706b5f388465367aa8a6c7da24b5e5f1fdd3256ef8e63 +ac33e7b1ee47e1ee4af472e37ab9e9175260e506a4e5ce449788075da1b53c44cb035f3792d1eea2aa24b1f688cc6ed3 +80f65b205666b0e089bb62152251c48c380a831e5f277f11f3ef4f0d52533f0851c1b612267042802f019ec900dc0e8f +858520ad7aa1c9fed738e3b583c84168f2927837ad0e1d326afe9935c26e9b473d7f8c382e82ef1fe37d2b39bb40a1ee +b842dd4af8befe00a97c2d0f0c33c93974761e2cb9e5ab8331b25170318ddd5e4bdbc02d8f90cbfdd5f348f4f371c1f7 +8bf2cb79bc783cb57088aae7363320cbeaabd078ffdec9d41bc74ff49e0043d0dad0086a30e5112b689fd2f5a606365d +982eb03bbe563e8850847cd37e6a3306d298ab08c4d63ab6334e6b8c1fa13fce80cf2693b09714c7621d74261a0ff306 +b143edb113dec9f1e5105d4a93fbe502b859e587640d3db2f628c09a17060e6aec9e900e2c8c411cda99bc301ff96625 +af472d9befa750dcebc5428fe1a024f18ec1c07bca0f95643ce6b5f4189892a910285afb03fd7ed7068fbe614e80d33c +a97e3bc57ede73ecd1bbf02de8f51b4e7c1a067da68a3cd719f4ba26a0156cbf1cef2169fd35a18c5a4cced50d475998 +a862253c937cf3d75d7183e5f5be6a4385d526aeda5171c1c60a8381fea79f88f5f52a4fab244ecc70765d5765e6dfd5 +90cb776f8e5a108f1719df4a355bebb04bf023349356382cae55991b31720f0fd03206b895fa10c56c98f52453be8778 +a7614e8d0769dccd520ea4b46f7646e12489951efaef5176bc889e9eb65f6e31758df136b5bf1e9107e68472fa9b46ec +ac3a9b80a3254c42e5ed3a090a0dd7aee2352f480de96ad187027a3bb6c791eddfc3074b6ffd74eea825188f107cda4d +82a01d0168238ef04180d4b6e0a0e39024c02c2d75b065017c2928039e154d093e1af4503f4d1f3d8a948917abb5d09f +8fab000a2b0eef851a483aec8d2dd85fe60504794411a2f73ed82e116960547ac58766cb73df71aea71079302630258d +872451a35c6db61c63e9b8bb9f16b217f985c20be4451c14282c814adb29d7fb13f201367c664435c7f1d4d9375d7a58 +887d9ff54cc96b35d562df4a537ff972d7c4b3fd91ab06354969a4cfede0b9fc68bbffb61d0dbf1a58948dc701e54f5a +8cb5c2a6bd956875d88f41ae24574434f1308514d44057b55c9c70f13a3366ed054150eed0955a38fda3f757be73d55f +89ad0163cad93e24129d63f8e38422b7674632a8d0a9016ee8636184cab177659a676c4ee7efba3abe1a68807c656d60 +b9ec01c7cab6d00359b5a0b4a1573467d09476e05ca51a9227cd16b589a9943d161eef62dcc73f0de2ec504d81f4d252 +8031d17635d39dfe9705c485d2c94830b6fc9bc67b91300d9d2591b51e36a782e77ab5904662effa9382d9cca201f525 +8be5a5f6bc8d680e5092d6f9a6585acbaaaa2ddc671da560dcf5cfa4472f4f184b9597b5b539438accd40dda885687cc +b1fc0f052fae038a2e3de3b3a96b0a1024b009de8457b8b3adb2d315ae68a89af905720108a30038e5ab8d0d97087785 +8b8bdc77bd3a6bc7ca5492b6f8c614852c39a70d6c8a74916eaca0aeb4533b11898b8820a4c2620a97bf35e275480029 +af35f4dc538d4ad5cdf710caa38fd1eb496c3fa890a047b6a659619c5ad3054158371d1e88e0894428282eed9f47f76b +8166454a7089cc07758ad78724654f4e7a1a13e305bbf88ddb86f1a4b2904c4fc8ab872d7da364cdd6a6c0365239e2ad +ab287c7d3addce74ce40491871c768abe01daaa0833481276ff2e56926b38a7c6d2681ffe837d2cc323045ad1a4414f9 +b90317f4505793094d89365beb35537f55a6b5618904236258dd04ca61f21476837624a2f45fef8168acf732cab65579 +98ae5ea27448e236b6657ab5ef7b1cccb5372f92ab25f5fa651fbac97d08353a1dae1b280b1cd42b17d2c6a70a63ab9d +adcf54e752d32cbaa6cb98fbca48d8cd087b1db1d131d465705a0d8042c8393c8f4d26b59006eb50129b21e6240f0c06 +b591a3e4db18a7345fa935a8dd7994bbac5cc270b8ebd84c8304c44484c7a74afb45471fdbe4ab22156a30fae1149b40 +806b53ac049a42f1dcc1d6335505371da0bf27c614f441b03bbf2e356be7b2fb4eed7117eabcce9e427a542eaa2bf7d8 +800482e7a772d49210b81c4a907f5ce97f270b959e745621ee293cf8c71e8989363d61f66a98f2d16914439544ca84c7 +99de9eafdad3617445312341644f2bb888680ff01ce95ca9276b1d2e5ef83fa02dab5e948ebf66c17df0752f1bd37b70 +961ee30810aa4c93ae157fbe9009b8e443c082192bd36a73a6764ff9b2ad8b0948fe9a73344556e01399dd77badb4257 +ae0a361067c52efbe56c8adf982c00432cd478929459fc7f74052c8ee9531cd031fe1335418fde53f7c2ef34254eb7ac +a3503d16b6b27eb20c1b177bcf90d13706169220523a6271b85b2ce35a9a2b9c5bed088540031c0a4ebfdae3a4c6ab04 +909420122c3e723289ca4e7b81c2df5aff312972a2203f4c45821b176e7c862bf9cac7f7df3adf1d59278f02694d06e7 +989f42380ae904b982f85d0c6186c1aef5d6bcba29bcfbb658e811b587eb2749c65c6e4a8cc6409c229a107499a4f5d7 +8037a6337195c8e26a27ea4ef218c6e7d79a9720aaab43932d343192abc2320fe72955f5e431c109093bda074103330a +b312e168663842099b88445e940249cc508f080ab0c94331f672e7760258dbd86be5267e4cf25ea25facb80bff82a7e9 +aaa3ff8639496864fcdbfdda1ac97edc4f08e3c9288b768f6c8073038c9fbbf7e1c4bea169b4d45c31935cdf0680d45e +97dbd3df37f0b481a311dfc5f40e59227720f367912200d71908ef6650f32cc985cb05b981e3eea38958f7e48d10a15d +a89d49d1e267bb452d6cb621b9a90826fe55e9b489c0427b94442d02a16f390eed758e209991687f73f6b5a032321f42 +9530dea4e0e19d6496f536f2e75cf7d814d65fde567055eb20db48fd8d20d501cd2a22fb506db566b94c9ee10f413d43 +81a7009b9e67f1965fa7da6a57591c307de91bf0cd35ab4348dc4a98a4961e096d004d7e7ad318000011dc4342c1b809 +83440a9402b766045d7aca61a58bba2aa29cac1cf718199e472ba086f5d48093d9dda4d135292ba51d049a23964eceae +a06c9ce5e802df14f6b064a3d1a0735d429b452f0e2e276042800b0a4f16df988fd94cf3945921d5dd3802ab2636f867 +b1359e358b89936dee9e678a187aad3e9ab14ac40e96a0a68f70ee2583cdcf467ae03bef4215e92893f4e12f902adec8 +835304f8619188b4d14674d803103d5a3fa594d48e96d9699e653115dd05fdc2dda6ba3641cf7ad53994d448da155f02 +8327cba5a9ff0d3f5cd0ae55e77167448926d5fcf76550c0ad978092a14122723090c51c415e88e42a2b62eb07cc3981 +b373dcdaea85f85ce9978b1426a7ef4945f65f2d3467a9f1cc551a99766aac95df4a09e2251d3f89ca8c9d1a7cfd7b0e +ab1422dc41af2a227b973a6fd124dfcb2367e2a11a21faa1d381d404f51b7257e5bc82e9cf20cd7fe37d7ae761a2ab37 +a93774a03519d2f20fdf2ef46547b0a5b77c137d6a3434b48d56a2cbef9e77120d1b85d0092cf8842909213826699477 +8eb967a495a38130ea28711580b7e61bcd1d051cd9e4f2dbf62f1380bd86e0d60e978d72f6f31e909eb97b3b9a2b867c +ae8213378da1287ba1fe4242e1acaec19b877b6fe872400013c6eac1084b8d03156792fa3020201725b08228a1e80f49 +b143daf6893d674d607772b3b02d8ac48f294237e2f2c87963c0d4e26d9227d94a2a13512457c3d5883544bbc259f0ef +b343bd2aca8973888e42542218924e2dda2e938fd1150d06878af76f777546213912b7c7a34a0f94186817d80ffa185c +b188ebc6a8c3007001aa347ae72cc0b15d09bc6c19a80e386ee4b334734ec0cc2fe8b493c2422f38d1e6d133cc3db6fe +b795f6a8b9b826aaeee18ccd6baf6c5adeeec85f95eb5b6d19450085ec7217e95a2d9e221d77f583b297d0872073ba0e +b1c7dbd998ad32ae57bfa95deafa147024afd57389e98992c36b6e52df915d3d5a39db585141ec2423173e85d212fed8 +812bcdeb9fe5f12d0e1df9964798056e1f1c3de3b17b6bd2919b6356c4b86d8e763c01933efbe0224c86a96d5198a4be +b19ebeda61c23d255cbf472ef0b8a441f4c55b70f0d8ed47078c248b1d3c7c62e076b43b95c00a958ec8b16d5a7cb0d7 +b02adc9aaa20e0368a989c2af14ff48b67233d28ebee44ff3418bb0473592e6b681af1cc45450bd4b175df9051df63d9 +8d87f0714acee522eb58cec00360e762adc411901dba46adc9227124fa70ee679f9a47e91a6306d6030dd4eb8de2f3c1 +8be54cec21e74bcc71de29dc621444263737db15f16d0bb13670f64e42f818154e04b484593d19ef95f2ee17e4b3fe21 +ab8e20546c1db38d31493b5d5f535758afb17e459645c1b70813b1cf7d242fd5d1f4354a7c929e8f7259f6a25302e351 +89f035a1ed8a1e302ac893349ba8ddf967580fcb6e73d44af09e3929cde445e97ff60c87dafe489e2c0ab9c9986cfa00 +8b2b0851a795c19191a692af55f7e72ad2474efdc5401bc3733cfdd910e34c918aaebe69d5ea951bdddf3c01cabbfc67 +a4edb52c2b51495ccd1ee6450fc14b7b3ede8b3d106808929d02fb31475bacb403e112ba9c818d2857651e508b3a7dd1 +9569341fded45d19f00bcf3cbf3f20eb2b4d82ef92aba3c8abd95866398438a2387437e580d8b646f17cf6fde8c5af23 +aa4b671c6d20f72f2f18a939a6ff21cc37e0084b44b4a717f1be859a80b39fb1be026b3205adec2a66a608ec2bcd578f +94902e980de23c4de394ad8aec91b46f888d18f045753541492bfbb92c59d3daa8de37ae755a6853744af8472ba7b72b +af651ef1b2a0d30a7884557edfad95b6b5d445a7561caebdc46a485aedd25932c62c0798465c340a76f6feaa196dd712 +b7b669b8e5a763452128846dd46b530dca4893ace5cc5881c7ddcd3d45969d7e73fbebdb0e78aa81686e5f7b22ec5759 +82507fd4ebe9fa656a7f2e084d64a1fa6777a2b0bc106d686e2d9d2edafc58997e58cb6bfd0453b2bf415704aa82ae62 +b40bce2b42b88678400ecd52955bbdadd15f8b9e1b3751a1a3375dc0efb5ca3ee258cf201e1140b3c09ad41217d1d49e +b0210d0cbb3fbf3b8cdb39e862f036b0ff941cd838e7aaf3a8354e24246e64778d22f3de34572e6b2a580614fb6425be +876693cba4301b251523c7d034108831df3ce133d8be5a514e7a2ca494c268ca0556fa2ad8310a1d92a16b55bcd99ea9 +8660281406d22a4950f5ef050bf71dd3090edb16eff27fa29ef600cdea628315e2054211ed2cc6eaf8f2a1771ef689fd +a610e7e41e41ab66955b809ba4ade0330b8e9057d8efc9144753caed81995edeb1a42a53f93ce93540feca1fae708dac +a49e2c176a350251daef1218efaccc07a1e06203386ede59c136699d25ca5cb2ac1b800c25b28dd05678f14e78e51891 +83e0915aa2b09359604566080d411874af8c993beba97d4547782fdbe1a68e59324b800ff1f07b8db30c71adcbd102a8 +a19e84e3541fb6498e9bb8a099c495cbfcad113330e0262a7e4c6544495bb8a754b2208d0c2d895c93463558013a5a32 +87f2bd49859a364912023aca7b19a592c60214b8d6239e2be887ae80b69ebdeb59742bdebcfa73a586ab23b2c945586c +b8e8fdddae934a14b57bc274b8dcd0d45ebb95ddbaabef4454e0f6ce7d3a5a61c86181929546b3d60c447a15134d08e1 +87e0c31dcb736ea4604727e92dc1d9a3cf00adcff79df3546e02108355260f3dd171531c3c0f57be78d8b28058fcc8c0 +9617d74e8f808a4165a8ac2e30878c349e1c3d40972006f0787b31ea62d248c2d9f3fc3da83181c6e57e95feedfd0e8c +8949e2cee582a2f8db86e89785a6e46bc1565c2d8627d5b6bf43ba71ffadfab7e3c5710f88dcb5fb2fc6edf6f4fae216 +ad3fa7b0edceb83118972a2935a09f409d09a8db3869f30be3a76f67aa9fb379cabb3a3aff805ba023a331cad7d7eb64 +8c95718a4112512c4efbd496be38bf3ca6cdcaad8a0d128f32a3f9aae57f3a57bdf295a3b372a8c549fda8f4707cffed +88f3261d1e28a58b2dee3fcc799777ad1c0eb68b3560f9b4410d134672d9533532a91ea7be28a041784872632d3c9d80 +b47472a41d72dd2e8b72f5c4f8ad626737dde3717f63d6bc776639ab299e564cbad0a2ad5452a07f02ff49a359c437e5 +9896d21dc2e8aad87b76d6df1654f10cd7bceed4884159d50a818bea391f8e473e01e14684814c7780235f28e69dca6e +82d47c332bbd31bbe83b5eb44a23da76d4a7a06c45d7f80f395035822bc27f62f59281d5174e6f8e77cc9b5c3193d6f0 +95c74cd46206e7f70c9766117c34c0ec45c2b0f927a15ea167901a160e1530d8522943c29b61e03568aa0f9c55926c53 +a89d7757825ae73a6e81829ff788ea7b3d7409857b378ebccd7df73fdbe62c8d9073741cf038314971b39af6c29c9030 +8c1cd212d0b010905d560688cfc036ae6535bc334fa8b812519d810b7e7dcf1bb7c5f43deaa40f097158358987324a7f +b86993c383c015ed8d847c6b795164114dd3e9efd25143f509da318bfba89389ea72a420699e339423afd68b6512fafb +8d06bd379c6d87c6ed841d8c6e9d2d0de21653a073725ff74be1934301cc3a79b81ef6dd0aad4e7a9dc6eac9b73019bc +81af4d2d87219985b9b1202d724fe39ef988f14fef07dfe3c3b11714e90ffba2a97250838e8535eb63f107abfe645e96 +8c5e0af6330a8becb787e4b502f34f528ef5756e298a77dc0c7467433454347f3a2e0bd2641fbc2a45b95e231c6e1c02 +8e2a8f0f04562820dc8e7da681d5cad9fe2e85dd11c785fb6fba6786c57a857e0b3bd838fb849b0376c34ce1665e4837 +a39be8269449bfdfc61b1f62077033649f18dae9bef7c6163b9314ca8923691fb832f42776f0160b9e8abd4d143aa4e1 +8c154e665706355e1cc98e0a4cabf294ab019545ba9c4c399d666e6ec5c869ca9e1faf8fb06cd9c0a5c2f51a7d51b70a +a046a7d4de879d3ebd4284f08f24398e9e3bf006cd4e25b5c67273ade248689c69affff92ae810c07941e4904296a563 +afd94c1cb48758e5917804df03fb38a6da0e48cd9b6262413ea13b26973f9e266690a1b7d9d24bbaf7e82718e0e594b0 +859e21080310c8d6a38e12e2ac9f90a156578cdeb4bb2e324700e97d9a5511cd6045dc39d1d0de3f94aeed043a24119d +a219fb0303c379d0ab50893264919f598e753aac9065e1f23ef2949abc992577ab43c636a1d2c089203ec9ddb941e27d +b0fdb639d449588a2ca730afcba59334e7c387342d56defdfb7ef79c493f7fd0e5277eff18e7203e756c7bdda5803047 +87f9c3b7ed01f54368aca6dbcf2f6e06bff96e183c4b2c65f8baa23b377988863a0a125d5cdd41a072da8462ced4c070 +99ef7a5d5ac2f1c567160e1f8c95f2f38d41881850f30c461a205f7b1b9fb181277311333839b13fb3ae203447e17727 +aeaca9b1c2afd24e443326cc68de67b4d9cedb22ad7b501a799d30d39c85bb2ea910d4672673e39e154d699e12d9b3dc +a11675a1721a4ba24dd3d0e4c3c33a6edf4cd1b9f6b471070b4386c61f77452266eae6e3f566a40cfc885eada9a29f23 +b228334445e37b9b49cb4f2cc56b454575e92173ddb01370a553bba665adadd52df353ad74470d512561c2c3473c7bb9 +a18177087c996572d76f81178d18ed1ceebc8362a396348ce289f1d8bd708b9e99539be6fccd4acb1112381cfc5749b4 +8e7b8bf460f0d3c99abb19803b9e43422e91507a1c0c22b29ee8b2c52d1a384da4b87c292e28eff040db5be7b1f8641f +b03d038d813e29688b6e6f444eb56fec3abba64c3d6f890a6bcf2e916507091cdb2b9d2c7484617be6b26552ed1c56cb +a1c88ccd30e934adfc5494b72655f8afe1865a84196abfb376968f22ddc07761210b6a9fb7638f1413d1b4073d430290 +961b714faebf172ad2dbc11902461e286e4f24a99a939152a53406117767682a571057044decbeb3d3feef81f4488497 +a03dc4059b46effdd786a0a03cc17cfee8585683faa35bb07936ded3fa3f3a097f518c0b8e2db92fd700149db1937789 +adf60180c99ca574191cbcc23e8d025b2f931f98ca7dfcebfc380226239b6329347100fcb8b0fcb12db108c6ad101c07 +805d4f5ef24d46911cbf942f62cb84b0346e5e712284f82b0db223db26d51aabf43204755eb19519b00e665c7719fcaa +8dea7243e9c139662a7fe3526c6c601eee72fd8847c54c8e1f2ad93ef7f9e1826b170afe58817dac212427164a88e87f +a2ba42356606d651b077983de1ad643650997bb2babb188c9a3b27245bb65d2036e46667c37d4ce02cb1be5ae8547abe +af2ae50b392bdc013db2d12ce2544883472d72424fc767d3f5cb0ca2d973fc7d1f425880101e61970e1a988d0670c81b +98e6bec0568d3939b31d00eb1040e9b8b2a35db46ddf4369bdaee41bbb63cc84423d29ee510a170fb5b0e2df434ba589 +822ff3cd12fbef4f508f3ca813c04a2e0b9b799c99848e5ad3563265979e753ee61a48f6adc2984a850f1b46c1a43d35 +891e8b8b92a394f36653d55725ef514bd2e2a46840a0a2975c76c2a935577f85289026aaa74384da0afe26775cbddfb9 +b2a3131a5d2fe7c8967047aa66e4524babae941d90552171cc109527f345f42aa0df06dcbb2fa01b33d0043917bbed69 +80c869469900431f3eeefafdbe07b8afd8cee7739e659e6d0109b397cacff85a88247698f87dc4e2fe39a592f250ac64 +9091594f488b38f9d2bb5df49fd8b4f8829d9c2f11a197dd1431ed5abbc5c954bbde3387088f9ee3a5a834beb7619bce +b472e241e6956146cca57b97a8a204668d050423b4e76f857bad5b47f43b203a04c8391ba9d9c3e95093c071f9d376a1 +b7dd2de0284844392f7dfb56fe7ca3ede41e27519753ffc579a0a8d2d65ceb8108d06b6b0d4c3c1a2588951297bd1a1e +902116ce70d0a079ac190321c1f48701318c05f8e69ee09694754885d33a835a849cafe56f499a2f49f6cda413ddf9a7 +b18105cc736787fafaf7c3c11c448bce9466e683159dff52723b7951dff429565e466e4841d982e3aaa9ee2066838666 +97ab9911f3f659691762d568ae0b7faa1047b0aed1009c319fa79d15d0db8db9f808fc385dc9a68fa388c10224985379 +b2a2cba65f5b927e64d2904ba412e2bac1cf18c9c3eda9c72fb70262497ecf505b640827e2afebecf10eebbcf48ccd3e +b36a3fd677baa0d3ef0dac4f1548ff50a1730286b8c99d276a0a45d576e17b39b3cbadd2fe55e003796d370d4be43ce3 +a5dfec96ca3c272566e89dc453a458909247e3895d3e44831528130bc47cc9d0a0dac78dd3cad680a4351d399d241967 +8029382113909af6340959c3e61db27392531d62d90f92370a432aec3eb1e4c36ae1d4ef2ba8ec6edb4d7320c7a453f6 +971d85121ea108e6769d54f9c51299b0381ece8b51d46d49c89f65bedc123bab4d5a8bc14d6f67f4f680077529cbae4c +98ff6afc01d0bec80a278f25912e1b1ebff80117adae72e31d5b9fa4d9624db4ba2065b444df49b489b0607c45e26c4c +8fa29be10fb3ab30ce25920fec0187e6e91e458947009dabb869aade7136c8ba23602682b71e390c251f3743164cbdaa +b3345c89eb1653418fe3940cf3e56a9a9c66526389b98f45ca02dd62bfb37baa69a4baaa7132d7320695f8ea6ad1fd94 +b72c7f5541c9ac6b60a7ec9f5415e7fb14da03f7164ea529952a29399f3a071576608dbbcc0d45994f21f92ddbeb1e19 +aa3450bb155a5f9043d0ef95f546a2e6ade167280bfb75c9f09c6f9cdb1fffb7ce8181436161a538433afa3681c7a141 +92a18fecaded7854b349f441e7102b638ababa75b1b0281dd0bded6541abe7aa37d96693595be0b01fe0a2e2133d50f9 +980756ddf9d2253cfe6c94960b516c94889d09e612810935150892627d2ecee9a2517e04968eea295d0106850c04ca44 +ae68c6ccc454318cdd92f32b11d89116a3b8350207a36d22a0f626718cad671d960090e054c0c77ac3162ae180ecfd4b +99f31f66eaaa551749ad91d48a0d4e3ff4d82ef0e8b28f3184c54e852422ba1bdafd53b1e753f3a070f3b55f3c23b6a2 +a44eaeaa6589206069e9c0a45ff9fc51c68da38d4edff1d15529b7932e6f403d12b9387019c44a1488a5d5f27782a51f +b80b5d54d4b344840e45b79e621bd77a3f83fb4ce6d8796b7d6915107b3f3c34d2e7d95bdafd120f285669e5acf2437a +b36c069ec085a612b5908314d6b84c00a83031780261d1c77a0384c406867c9847d5b0845deddfa512cc04a8df2046fb +b09dbe501583220f640d201acea7ee3e39bf9eda8b91aa07b5c50b7641d86d71acb619b38d27835ce97c3759787f08e9 +87403d46a2bf63170fff0b857acacf42ee801afe9ccba8e5b4aea967b68eac73a499a65ca46906c2eb4c8f27bc739faa +82b93669f42a0a2aa5e250ffe6097269da06a9c02fcd1801abbad415a7729a64f830754bafc702e64600ba47671c2208 +8e3a3029be7edb8dd3ab1f8216664c8dc50d395f603736061d802cef77627db7b859ef287ed850382c13b4d22d6a2d80 +968e9ec7194ff424409d182ce0259acd950c384c163c04463bc8700a40b79beba6146d22b7fa7016875a249b7b31c602 +8b42c984bbe4996e0c20862059167c6bdc5164b1ffcd928f29512664459212d263e89f0f0e30eed4e672ffa5ed0b01b5 +96bac54062110dada905363211133f1f15dc7e4fd80a4c6e4a83bc9a0bcbbaba11cd2c7a13debcf0985e1a954c1da66b +a16dc8a653d67a7cd7ae90b2fffac0bf1ca587005430fe5ba9403edd70ca33e38ba5661d2ed6e9d2864400d997626a62 +a68ab11a570a27853c8d67e491591dcba746bfbee08a2e75ae0790399130d027ed387f41ef1d7de8df38b472df309161 +92532b74886874447c0300d07eda9bbe4b41ed25349a3da2e072a93fe32c89d280f740d8ff70d5816793d7f2b97373cc +88e35711b471e89218fd5f4d0eadea8a29405af1cd81974427bc4a5fb26ed60798daaf94f726c96e779b403a2cd82820 +b5c72aa4147c19f8c4f3a0a62d32315b0f4606e0a7025edc5445571eaf4daff64f4b7a585464821574dd50dbe1b49d08 +9305d9b4095258e79744338683fd93f9e657367b3ab32d78080e51d54eec331edbc224fad5093ebf8ee4bd4286757eb8 +b2a17abb3f6a05bcb14dc7b98321fa8b46d299626c73d7c6eb12140bf4c3f8e1795250870947af817834f033c88a59d6 +b3477004837dbd8ba594e4296f960fc91ab3f13551458445e6c232eb04b326da803c4d93e2e8dcd268b4413305ff84da +924b4b2ebaafdcfdfedb2829a8bf46cd32e1407d8d725a5bd28bdc821f1bafb3614f030ea4352c671076a63494275a3f +8b81b9ef6125c82a9bece6fdcb9888a767ac16e70527753428cc87c56a1236e437da8be4f7ecfe57b9296dc3ae7ba807 +906e19ec8b8edd58bdf9ae05610a86e4ea2282b1bbc1e8b00b7021d093194e0837d74cf27ac9916bdb8ec308b00da3da +b41c5185869071760ac786078a57a2ab4e2af60a890037ac0c0c28d6826f15c2cf028fddd42a9b6de632c3d550bfbc14 +a646e5dec1b713ae9dfdf7bdc6cd474d5731a320403c7dfcfd666ffc9ae0cff4b5a79530e8df3f4aa9cb80568cb138e9 +b0efad22827e562bd3c3e925acbd0d9425d19057868608d78c2209a531cccd0f2c43dc5673acf9822247428ffa2bb821 +a94c19468d14b6f99002fc52ac06bbe59e5c472e4a0cdb225144a62f8870b3f10593749df7a2de0bd3c9476ce682e148 +803864a91162f0273d49271dafaab632d93d494d1af935aefa522768af058fce52165018512e8d6774976d52bd797e22 +a08711c2f7d45c68fb340ac23597332e1bcaec9198f72967b9921204b9d48a7843561ff318f87908c05a44fc35e3cc9d +91c3cad94a11a3197ae4f9461faab91a669e0dddb0371d3cab3ed9aeb1267badc797d8375181130e461eadd05099b2a2 +81bdaaf48aae4f7b480fc13f1e7f4dd3023a41439ba231760409ce9292c11128ab2b0bdbbf28b98af4f97b3551f363af +8d60f9df9fd303f625af90e8272c4ecb95bb94e6efc5da17b8ab663ee3b3f673e9f6420d890ccc94acf4d2cae7a860d8 +a7b75901520c06e9495ab983f70b61483504c7ff2a0980c51115d11e0744683ce022d76e3e09f4e99e698cbd21432a0d +82956072df0586562fda7e7738226f694e1c73518dd86e0799d2e820d7f79233667192c9236dcb27637e4c65ef19d493 +a586beb9b6ffd06ad200957490803a7cd8c9bf76e782734e0f55e04a3dc38949de75dc607822ec405736c576cf83bca3 +a179a30d00def9b34a7e85607a447eea0401e32ab5abeee1a281f2acd1cf6ec81a178020666f641d9492b1bdf66f05a3 +83e129705c538787ed8e0fdc1275e6466a3f4ee21a1e6abedd239393b1df72244723b92f9d9d9339a0cab6ebf28f5a16 +811bd8d1e3722b64cd2f5b431167e7f91456e8bba2cc669d3fbbce7d553e29c3c19f629fcedd2498bc26d33a24891d17 +a243c030c858f1f60cccd26b45b024698cc6d9d9e6198c1ed4964a235d9f8d0baf9cde10c8e63dfaa47f8e74e51a6e85 +ab839eb82e23ca52663281f863b55b0a3d6d4425c33ffb4eeb1d7979488ab068bf99e2a60e82cea4dc42c56c26cbfebe +8b896f9bb21d49343e67aec6ad175b58c0c81a3ca73d44d113ae4354a0065d98eb1a5cafedaf232a2bb9cdc62152f309 +af6230340cc0b66f5bf845540ed4fc3e7d6077f361d60762e488d57834c3e7eb7eacc1b0ed73a7d134f174a01410e50c +88975e1b1af678d1b5179f72300a30900736af580dd748fd9461ef7afccc91ccd9bed33f9da55c8711a7635b800e831f +a97486bb9047391661718a54b8dd5a5e363964e495eae6c692730264478c927cf3e66dd3602413189a3699fbeae26e15 +a5973c161ab38732885d1d2785fd74bf156ba34881980cba27fe239caef06b24a533ffe6dbbbeca5e6566682cc00300a +a24776e9a840afda0003fa73b415d5bd6ecd9b5c2cc842b643ee51b8c6087f4eead4d0bfbd987eb174c489a7b952ff2a +a8a6ee06e3af053b705a12b59777267c546f33ba8a0f49493af8e6df4e15cf8dd2d4fb4daf7e84c6b5d3a7363118ff03 +a28e59ce6ad02c2ce725067c0123117e12ac5a52c8f5af13eec75f4a9efc4f696777db18a374fa33bcae82e0734ebd16 +86dfc3b78e841c708aff677baa8ee654c808e5d257158715097c1025d46ece94993efe12c9d188252ad98a1e0e331fec +a88d0275510f242eab11fdb0410ff6e1b9d7a3cbd3658333539815f1b450a84816e6613d15aa8a8eb15d87cdad4b27a2 +8440acea2931118a5b481268ff9f180ee4ede85d14a52c026adc882410825b8275caa44aff0b50c2b88d39f21b1a0696 +a7c3182eab25bd6785bacf12079d0afb0a9b165d6ed327814e2177148539f249eb9b5b2554538f54f3c882d37c0a8abe +85291fbe10538d7da38efdd55a7acebf03b1848428a2f664c3ce55367aece60039f4f320b1771c9c89a35941797f717c +a2c6414eeb1234728ab0de94aa98fc06433a58efa646ca3fcbd97dbfb8d98ae59f7ce6d528f669c8149e1e13266f69c9 +840c8462785591ee93aee2538d9f1ec44ba2ca61a569ab51d335ac873f5d48099ae8d7a7efa0725d9ff8f9475bfa4f56 +a7065a9d02fb3673acf7702a488fbc01aa69580964932f6f40b6c2d1c386b19e50b0e104fcac24ea26c4e723611d0238 +b72db6d141267438279e032c95e6106c2ccb3164b842ba857a2018f3a35f4b040da92680881eb17cd61d0920d5b8f006 +a8005d6c5960e090374747307ef0be2871a7a43fa4e76a16c35d2baab808e9777b496e9f57a4218b23390887c33a0b55 +8e152cea1e00a451ca47c20a1e8875873419700af15a5f38ee2268d3fbc974d4bd5f4be38008fa6f404dbdedd6e6e710 +a3391aed1fcd68761f06a7d1008ec62a09b1cb3d0203cd04e300a0c91adfed1812d8bc1e4a3fd7976dc0aae0e99f52f1 +967eb57bf2aa503ee0c6e67438098149eac305089c155f1762cf5e84e31f0fbf27c34a9af05621e34645c1ec96afaec8 +88af97ddc4937a95ec0dcd25e4173127260f91c8db2f6eac84afb789b363705fb3196235af631c70cafd09411d233589 +a32df75b3f2c921b8767638fd289bcfc61e08597170186637a7128ffedd52c798c434485ac2c7de07014f9e895c2c3d8 +b0a783832153650aa0d766a3a73ec208b6ce5caeb40b87177ffc035ab03c7705ecdd1090b6456a29f5fb7e90e2fa8930 +b59c8e803b4c3486777d15fc2311b97f9ded1602fa570c7b0200bada36a49ee9ef4d4c1474265af8e1c38a93eb66b18b +982f2c85f83e852022998ff91bafbb6ff093ef22cf9d5063e083a48b29175ccbd51b9c6557151409e439096300981a6c +939e3b5989fefebb9d272a954659a4eb125b98c9da6953f5e628d26266bd0525ec38304b8d56f08d65abc4d6da4a8dbb +8898212fe05bc8de7d18503cb84a1c1337cc2c09d1eeef2b475aa79185b7322bf1f8e065f1bf871c0c927dd19faf1f6d +94b0393a41cd00f724aee2d4bc72103d626a5aecb4b5486dd1ef8ac27528398edf56df9db5c3d238d8579af368afeb09 +96ac564450d998e7445dd2ea8e3fc7974d575508fa19e1c60c308d83b645864c029f2f6b7396d4ff4c1b24e92e3bac37 +8adf6638e18aff3eb3b47617da696eb6c4bdfbecbbc3c45d3d0ab0b12cbad00e462fdfbe0c35780d21aa973fc150285e +b53f94612f818571b5565bbb295e74bada9b5f9794b3b91125915e44d6ddcc4da25510eab718e251a09c99534d6042d9 +8b96462508d77ee083c376cd90807aebad8de96bca43983c84a4a6f196d5faf6619a2351f43bfeec101864c3bf255519 +aeadf34657083fc71df33bd44af73bf5281c9ca6d906b9c745536e1819ea90b56107c55e2178ebad08f3ba75b3f81c86 +9784ba29b2f0057b5af1d3ab2796d439b8753f1f749c73e791037461bdfc3f7097394283105b8ab01788ea5255a96710 +8756241bda159d4a33bf74faba0d4594d963c370fb6a18431f279b4a865b070b0547a6d1613cf45b8cfb5f9236bbf831 +b03ebfd6b71421dfd49a30460f9f57063eebfe31b9ceaa2a05c37c61522b35bdc09d7db3ad75c76c253c00ba282d3cd2 +b34e7e6341fa9d854b2d3153bdda0c4ae2b2f442ab7af6f99a0975d45725aa48e36ae5f7011edd249862e91f499687d4 +b462ee09dc3963a14354244313e3444de5cc37ea5ccfbf14cd9aca8027b59c4cb2a949bc30474497cab8123e768460e6 +aea753290e51e2f6a21a9a0ee67d3a2713f95c2a5c17fe41116c87d3aa77b1683761264d704df1ac34f8b873bc88ef7b +98430592afd414394f98ddfff9f280fcb1c322dbe3510f45e1e9c4bb8ee306b3e0cf0282c0ee73ebb8ba087d4d9e0858 +b95d3b5aaf54ffca11f4be8d57f76e14afdb20afc859dc7c7471e0b42031e8f3d461b726ecb979bdb2f353498dfe95ea +984d17f9b11a683132e0b5a9ee5945e3ff7054c2d5c716be73b29078db1d36f54c6e652fd2f52a19da313112e97ade07 +ab232f756b3fff3262be418a1af61a7e0c95ceebbc775389622a8e10610508cd6784ab7960441917a83cc191c58829ea +a28f41678d6e60de76b0e36ab10e4516e53e02e9c77d2b5af3cfeee3ce94cfa30c5797bd1daab20c98e1cad83ad0f633 +b55395fca84dd3ccc05dd480cb9b430bf8631ff06e24cb51d54519703d667268c2f8afcde4ba4ed16bece8cc7bc8c6e0 +8a8a5392a0e2ea3c7a8c51328fab11156004e84a9c63483b64e8f8ebf18a58b6ffa8fe8b9d95af0a2f655f601d096396 +ab480000fe194d23f08a7a9ec1c392334e9c687e06851f083845121ce502c06b54dda8c43092bcc1035df45cc752fe9b +b265644c29f628d1c7e8e25a5e845cabb21799371814730a41a363e1bda8a7be50fee7c3996a365b7fcba4642add10db +b8a915a3c685c2d4728f6931c4d29487cad764c5ce23c25e64b1a3259ac27235e41b23bfe7ae982921b4cb84463097df +8efa7338442a4b6318145a5440fc213b97869647eeae41b9aa3c0a27ee51285b73e3ae3b4a9423df255e6add58864aa9 +9106d65444f74d217f4187dfc8fcf3810b916d1e4275f94f6a86d1c4f3565b131fd6cde1fa708bc05fe183c49f14941a +948252dac8026bbbdb0a06b3c9d66ec4cf9532163bab68076fda1bd2357b69e4b514729c15aaa83b5618b1977bbc60c4 +ae6596ccfdf5cbbc5782efe3bb0b101bb132dbe1d568854ca24cacc0b2e0e9fabcb2ca7ab42aecec412efd15cf8cb7a2 +84a0b6c198ff64fd7958dfd1b40eac9638e8e0b2c4cd8cf5d8cdf80419baee76a05184bce6c5b635f6bf2d30055476a7 +8893118be4a055c2b3da593dbca51b1ae2ea2469911acfb27ee42faf3e6c3ad0693d3914c508c0b05b36a88c8b312b76 +b097479e967504deb6734785db7e60d1d8034d6ca5ba9552887e937f5e17bb413fccac2c1d1082154ed76609127860ad +a0294e6b9958f244d29943debf24b00b538b3da1116269b6e452bb12dc742226712fd1a15b9c88195afeb5d2415f505c +b3cc15f635080bc038f61b615f62b5b5c6f2870586191f59476e8368a73641d6ac2f7d0c1f54621982defdb318020230 +99856f49b9fe1604d917c94d09cc0ed753d13d015d30587a94e6631ffd964b214e607deb8a69a8b5e349a7edf4309206 +a8571e113ea22b4b4fce41a094da8c70de37830ae32e62c65c2fa5ad06a9bc29e884b945e73d448c72b176d6ecebfb58 +a9e9c6e52beb0013273c29844956b3ce291023678107cdc785f7b44eff5003462841ad8780761b86aefc6b734adde7cf +80a784b0b27edb51ef2bad3aee80e51778dcaa0f3f5d3dcb5dc5d4f4b2cf7ae35b08de6680ea9dac53f8438b92eb09ef +827b543e609ea328e97e373f70ad72d4915a2d1daae0c60d44ac637231070e164c43a2a58db80a64df1c624a042b38f9 +b449c65e8195202efdcb9bdb4e869a437313b118fef8b510cbbf8b79a4e99376adb749b37e9c20b51b31ed3310169e27 +8ea3028f4548a79a94c717e1ed28ad4d8725b8d6ab18b021063ce46f665c79da3c49440c6577319dab2d036b7e08f387 +897798431cfb17fe39f08f5f854005dc37b1c1ec1edba6c24bc8acb3b88838d0534a75475325a5ea98b326ad47dbad75 +89cf232e6303b0751561960fd4dea5754a28c594daf930326b4541274ffb03c7dd75938e411eb9a375006a70ce38097f +9727c6ae7f0840f0b6c8bfb3a1a5582ceee705e0b5c59b97def7a7a2283edd4d3f47b7971e902a3a2079e40b53ff69b8 +b76ed72b122c48679d221072efc0eeea063cb205cbf5f9ef0101fd10cb1075b8628166c83577cced654e1c001c7882f7 +ae908c42d208759da5ee9b405df85a6532ea35c6f0f6a1288d22870f59d98edc896841b8ac890a538e6c8d1e8b02d359 +809d12fe4039a0ec80dc9be6a89acaab7797e5f7f9b163378f52f9a75a1d73b2e9ae6e3dd49e32ced439783c1cabbef5 +a4149530b7f85d1098ba534d69548c6c612c416e8d35992fc1f64f4deeb41e09e49c6cf7aadbed7e846b91299358fe2d +a49342eacd1ec1148b8df1e253b1c015f603c39de11fa0a364ccb86ea32d69c34fd7aa6980a1fadcd8e785a57fa46f60 +87d43eff5a006dc4dddcf76cc96c656a1f3a68f19f124181feab86c6cc9a52cb9189cdbb423414defdd9bb0ca8ff1ddc +861367e87a9aa2f0f68296ba50aa5dbc5713008d260cc2c7e62d407c2063064749324c4e8156dc21b749656cfebce26b +b5303c2f72e84e170e66ae1b0fbd51b8c7a6f27476eaf5694b64e8737d5c84b51fe90100b256465a4c4156dd873cddb0 +b62849a4f891415d74f434cdc1d23c4a69074487659ca96e1762466b2b7a5d8525b056b891d0feea6fe6845cba8bc7fb +923dd9e0d6590a9307e8c4c23f13bae3306b580e297a937711a8b13e8de85e41a61462f25b7d352b682e8437bf2b4ab3 +9147379860cd713cd46c94b8cdf75125d36c37517fbecf81ace9680b98ce6291cd1c3e472f84249cc3b2b445e314b1b6 +a808a4f17ac21e3fb5cfef404e61fae3693ca3e688d375f99b6116779696059a146c27b06de3ac36da349b0649befd56 +87787e9322e1b75e66c1f0d9ea0915722a232770930c2d2a95e9478c4b950d15ab767e30cea128f9ed65893bfc2d0743 +9036a6ee2577223be105defe1081c48ea7319e112fff9110eb9f61110c319da25a6cea0464ce65e858635b079691ef1f +af5548c7c24e1088c23b57ee14d26c12a83484c9fd9296edf1012d8dcf88243f20039b43c8c548c265ef9a1ffe9c1c88 +a0fff520045e14065965fb8accd17e878d3fcaf9e0af2962c8954e50be6683d31fa0bf4816ab68f08630dbac6bfce52a +b4c1b249e079f6ae1781af1d97a60b15855f49864c50496c09c91fe1946266915b799f0406084d7783f5b1039116dd8b +8b0ffa5e7c498cb3879dddca34743b41eee8e2dea3d4317a6e961b58adb699ef0c92400c068d5228881a2b08121226bf +852ae8b19a1d80aa8ae5382e7ee5c8e7670ceb16640871c56b20b96b66b3b60e00015a3dde039446972e57b49a999ddd +a49942f04234a7d8492169da232cfff8051df86e8e1ba3db46aede02422c689c87dc1d99699c25f96cb763f5ca0983e5 +b04b597b7760cf5dcf411ef896d1661e6d5b0db3257ac2cf64b20b60c6cc18fa10523bb958a48d010b55bac7b02ab3b1 +a494591b51ea8285daecc194b5e5bd45ae35767d0246ac94fae204d674ee180c8e97ff15f71f28b7aeb175b8aea59710 +97d2624919e78406e7460730680dea8e71c8571cf988e11441aeea54512b95bd820e78562c99372d535d96f7e200d20d +ac693ddb00e48f76e667243b9b6a7008424043fb779e4f2252330285232c3fccac4da25cbd6d95fe9ad959ff305a91f6 +8d20ca0a71a64a3f702a0825bb46bd810d03bebfb227683680d474a52f965716ff99e19a165ebaf6567987f4f9ee3c94 +a5c516a438f916d1d68ca76996404792e0a66e97b7f18fc54c917bf10cf3211b62387932756e39e67e47b0bd6e88385a +b089614d830abc0afa435034cec7f851f2f095d479cacf1a3fb57272da826c499a52e7dcbc0eb85f4166fb94778e18e9 +a8dacc943765d930848288192f4c69e2461c4b9bc6e79e30eeef9a543318cf9ae9569d6986c65c5668a89d49993f8e07 +ab5a9361fa339eec8c621bdad0a58078983abd8942d4282b22835d7a3a47e132d42414b7c359694986f7db39386c2e19 +94230517fb57bd8eb26c6f64129b8b2abd0282323bf7b94b8bac7fab27b4ecc2c4290c294275e1a759de19f2216134f3 +b8f158ea5006bc3b90b285246625faaa6ac9b5f5030dc69701b12f3b79a53ec7e92eeb5a63bbd1f9509a0a3469ff3ffc +8b6944fd8cb8540957a91a142fdcda827762aa777a31e8810ca6d026e50370ee1636fc351724767e817ca38804ebe005 +82d1ee40fe1569c29644f79fa6c4033b7ed45cd2c3b343881f6eb0de2e79548fded4787fae19bed6ee76ed76ff9f2f11 +a8924c7035e99eaed244ca165607e7e568b6c8085510dcdbaf6ebdbed405af2e6c14ee27d94ffef10d30aa52a60bf66d +956f82a6c2ae044635e85812581e4866c5fa2f427b01942047d81f6d79a14192f66fbbe77c9ffeaef4e6147097fdd2b5 +b1100255a1bcf5e05b6aff1dfeb6e1d55b5d68d43a7457ba10cc76b61885f67f4d0d5179abda786e037ae95deb8eea45 +99510799025e3e5e8fbf06dedb14c060c6548ba2bda824f687d3999dc395e794b1fb6514b9013f3892b6cf65cb0d65aa +8f9091cebf5e9c809aab415942172258f894e66e625d7388a05289183f01b8d994d52e05a8e69f784fba41db9ea357f0 +a13d2eeb0776bdee9820ecb6693536720232848c51936bb4ef4fe65588d3f920d08a21907e1fdb881c1ad70b3725e726 +a68b8f18922d550284c5e5dc2dda771f24c21965a6a4d5e7a71678178f46df4d8a421497aad8fcb4c7e241aba26378a0 +8b7601f0a3c6ad27f03f2d23e785c81c1460d60100f91ea9d1cab978aa03b523150206c6d52ce7c7769c71d2c8228e9e +a8e02926430813caa851bb2b46de7f0420f0a64eb5f6b805401c11c9091d3b6d67d841b5674fa2b1dce0867714124cd8 +b7968ecba568b8193b3058400af02c183f0a6df995a744450b3f7e0af7a772454677c3857f99c140bbdb2a09e832e8e0 +8f20b1e9ba87d0a3f35309b985f3c18d2e8800f1ca7f0c52cadef773f1496b6070c936eea48c4a1cae83fd2524e9d233 +88aef260042db0d641a51f40639dbeeefa9e9811df30bee695f3791f88a2f84d318f04e8926b7f47bf25956cb9e3754f +9725345893b647e9ba4e6a29e12f96751f1ae25fcaec2173e9a259921a1a7edb7a47159b3c8767e44d9e2689f5aa0f72 +8c281e6f72752cb11e239e4df9341c45106eb7993c160e54423c2bffe10bc39d42624b45a1f673936ef2e1a02fc92f1a +90aba2f68bddb2fcce6c51430dacdfeec43ea8dc379660c99095df11017691ccf5faa27665cf4b9f0eea7728ae53c327 +b7022695c16521c5704f49b7ddbdbec9b5f57ce0ceebe537bc0ebb0906d8196cc855a9afeb8950a1710f6a654464d93f +8fe1b9dd3c6a258116415d36e08374e094b22f0afb104385a5da48be17123e86fb8327baacc4f0d9ebae923d55d99bb5 +817e85d8e3d19a4cbc1dec31597142c2daa4871bda89c2177fa719c00eda3344eb08b82eb92d4aa91a9eaacb3fc09783 +b59053e1081d2603f1ca0ba553804d6fa696e1fd996631db8f62087b26a40dfef02098b0326bb75f99ec83b9267ca738 +990a173d857d3ba81ff3789b931bfc9f5609cde0169b7f055fa3cb56451748d593d62d46ba33f80f9cafffe02b68dd14 +b0c538dbba4954b809ab26f9f94a3cf1dcb77ce289eaec1d19f556c0ae4be1fa03af4a9b7057837541c3cc0a80538736 +ac3ba42f5f44f9e1fc453ce49c4ab79d0e1d5c42d3b30b1e098f3ab3f414c4c262fa12fb2be249f52d4aaf3c5224beb9 +af47467eb152e59870e21f0d4da2f43e093daf40180ab01438030684b114d025326928eaab12c41b81a066d94fce8436 +98d1b58ba22e7289b1c45c79a24624f19b1d89e00f778eef327ec4856a9a897278e6f1a9a7e673844b31dde949153000 +97ccb15dfadc7c59dca08cfe0d22df2e52c684cf97de1d94bc00d7ba24e020025130b0a39c0f4d46e4fc872771ee7875 +b699e4ed9a000ff96ca296b2f09dce278832bc8ac96851ff3cff99ed3f6f752cfc0fea8571be28cd9b5a7ec36f1a08ee +b9f49f0edb7941cc296435ff0a912e3ad16848ee8765ab5f60a050b280d6ea585e5b34051b15f6b8934ef01ceb85f648 +ac3893df7b4ceab23c6b9054e48e8ba40d6e5beda8fbe90b814f992f52494186969b35d8c4cdc3c99890a222c9c09008 +a41293ad22fae81dea94467bc1488c3707f3d4765059173980be93995fa4fcc3c9340796e3eed0beeb0ba0d9bb4fa3aa +a0543e77acd2aeecde13d18d258aeb2c7397b77f17c35a1992e8666ea7abcd8a38ec6c2741bd929abba2f766138618cc +92e79b22bc40e69f6527c969500ca543899105837b6b1075fa1796755c723462059b3d1b028e0b3df2559fa440e09175 +a1fa1eac8f41a5197a6fb4aa1eae1a031c89f9c13ff9448338b222780cf9022e0b0925d930c37501a0ef7b2b00fdaf83 +b3cb29ff73229f0637335f28a08ad8c5f166066f27c6c175164d0f26766a927f843b987ee9b309ed71cbf0a65d483831 +84d4ab787f0ac00f104f4a734dc693d62d48c2aeb03913153da62c2ae2c27d11b1110dcef8980368dd84682ea2c1a308 +ab6a8e4bbc78d4a7b291ad3e9a8fe2d65f640524ba3181123b09d2d18a9e300e2509ccf7000fe47e75b65f3e992a2e7e +b7805ebe4f1a4df414003dc10bca805f2ab86ca75820012653e8f9b79c405196b0e2cab099f2ab953d67f0d60d31a0f9 +b12c582454148338ea605d22bd00a754109063e22617f1f8ac8ddf5502c22a181c50c216c3617b9852aa5f26af56b323 +86333ad9f898947e31ce747728dc8c887479e18d36ff3013f69ebef807d82c6981543b5c3788af93c4d912ba084d3cba +b514efa310dc4ad1258add138891e540d8c87142a881b5f46563cc58ecd1488e6d3a2fca54c0b72a929f3364ca8c333e +aa0a30f92843cf2f484066a783a1d75a7aa6f41f00b421d4baf20a6ac7886c468d0eea7ca8b17dd22f4f74631b62b640 +b3b7dc63baec9a752e8433c0cdee4d0f9bc41f66f2b8d132faf925eef9cf89aae756fc132c45910f057122462605dc10 +b9b8190dac5bfdeb59fd44f4da41a57e7f1e7d2c21faba9da91fa45cbeca06dcf299c9ae22f0c89ece11ac46352d619f +89f8cf36501ad8bdfeab863752a9090e3bfda57cf8fdeca2944864dc05925f501e252c048221bcc57136ab09a64b64b2 +b0cbfaf317f05f97be47fc9d69eda2dd82500e00d42612f271a1fe24626408c28881f171e855bd5bd67409f9847502b4 +a7c21a8fcede581bfd9847b6835eda62ba250bea81f1bb17372c800a19c732abe03064e64a2f865d974fb636cab4b859 +95f9df524ba7a4667351696c4176b505d8ea3659f5ff2701173064acc624af69a0fad4970963736383b979830cb32260 +856a74fe8b37a2e3afeac858c8632200485d438422a16ae3b29f359e470e8244995c63ad79c7e007ed063f178d0306fd +b37faa4d78fdc0bb9d403674dbea0176c2014a171c7be8527b54f7d1a32a76883d3422a3e7a5f5fcc5e9b31b57822eeb +8d37234d8594ec3fe75670b5c9cc1ec3537564d4739b2682a75b18b08401869a4264c0f264354219d8d896cded715db4 +b5289ee5737f0e0bde485d32096d23387d68dab8f01f47821ab4f06cc79a967afe7355e72dc0c751d96b2747b26f6255 +9085e1fdf9f813e9c3b8232d3c8863cd84ab30d45e8e0d3d6a0abd9ebc6fd70cdf749ff4d04390000e14c7d8c6655fc7 +93a388c83630331eca4da37ea4a97b3b453238af474817cc0a0727fd3138dcb4a22de38c04783ec829c22cb459cb4e8e +a5377116027c5d061dbe24c240b891c08cdd8cd3f0899e848d682c873aff5b8132c1e7cfe76d2e5ed97ee0eb1d42cb68 +a274c84b04338ed28d74683e2a7519c2591a3ce37c294d6f6e678f7d628be2db8eff253ede21823e2df7183e6552f622 +8bc201147a842453a50bec3ac97671397bc086d6dfc9377fa38c2124cdc286abda69b7324f47d64da094ae011d98d9d9 +9842d0c066c524592b76fbec5132bc628e5e1d21c424bec4555efca8619cc1fd8ea3161febcb8b9e8ab54702f4e815e2 +a19191b713a07efe85c266f839d14e25660ee74452e6c691cd9997d85ae4f732052d802d3deb018bdd847caa298a894b +a24f71fc0db504da4e287dd118a4a74301cbcd16033937ba2abc8417956fcb4ae19b8e63b931795544a978137eff51cb +a90eec4a6a3a4b8f9a5b93d978b5026fcf812fe65585b008d7e08c4aaf21195a1d0699f12fc16f79b6a18a369af45771 +8b551cf89737d7d06d9b3b9c4c1c73b41f2ea0af4540999c70b82dabff8580797cf0a3caf34c86c59a7069eb2e38f087 +b8d312e6c635e7a216a1cda075ae77ba3e1d2fd501dc31e83496e6e81ed5d9c7799f8e578869c2e0e256fb29f5de10a7 +8d144bdb8cae0b2cdb5b33d44bbc96984a5925202506a8cc65eb67ac904b466f5a7fe3e1cbf04aa785bbb7348c4bb73c +a101b3d58b7a98659244b88de0b478b3fb87dc5fc6031f6e689b99edf498abd43e151fd32bd4bbd240e0b3e59c440359 +907453abca7d8e7151a05cc3d506c988007692fe7401395dc93177d0d07d114ab6cca0cc658eb94c0223fe8658295cad +825329ffbe2147ddb68f63a0a67f32d7f309657b8e5d9ab5bb34b3730bfa2c77a23eaaadb05def7d9f94a9e08fdc1e96 +88ee923c95c1dac99ae7ed6067906d734d793c5dc5d26339c1bb3314abe201c5dccb33b9007351885eb2754e9a8ea06c +98bc9798543f5f1adc9f2cfcfa72331989420e9c3f6598c45269f0dc9b7c8607bbeaf03faa0aea2ddde2b8f17fdceff5 +8ee87877702a79aef923ab970db6fa81561b3c07d5bf1a072af0a7bad765b4cbaec910afe1a91703feacc7822fa38a94 +8060b9584aa294fe8adc2b22f67e988bc6da768eae91e429dcc43ddc53cfcc5d6753fdc1b420b268c7eb2fb50736a970 +b344a5524d80a2f051870c7001f74fcf348a70fcf78dbd20c6ff9ca85d81567d2318c8b8089f2c4f195d6aec9fc15fa6 +8f5a5d893e1936ed062149d20eb73d98b62b7f50ab5d93a6429c03656b36688d1c80cb5010e4977491e51fa0d7dd35d5 +86fa32ebbf97328c5f5f15564e1238297e289ec3219b9a741724e9f3ae8d5c15277008f555863a478b247ba5dc601d44 +9557e55377e279f4b6b5e0ffe01eca037cc13aac242d67dfcd0374a1e775c5ed5cb30c25fe21143fee54e3302d34a3ea +8cb6bcbc39372d23464a416ea7039f57ba8413cf3f00d9a7a5b356ab20dcb8ed11b3561f7bce372b8534d2870c7ee270 +b5d59075cb5abde5391f64b6c3b8b50adc6e1f654e2a580b6d6d6eff3f4fbdd8fffc92e06809c393f5c8eab37f774c4b +afcfb6903ef13e493a1f7308675582f15af0403b6553e8c37afb8b2808ad21b88b347dc139464367dc260df075fea1ad +810fbbe808375735dd22d5bc7fc3828dc49fdd22cc2d7661604e7ac9c4535c1df578780affb3b895a0831640a945bcad +8056b0c678803b416f924e09a6299a33cf9ad7da6fe1ad7accefe95c179e0077da36815fde3716711c394e2c5ea7127f +8b67403702d06979be19f1d6dc3ec73cc2e81254d6b7d0cc49cd4fdda8cd51ab0835c1d2d26fc0ecab5df90585c2f351 +87f97f9e6d4be07e8db250e5dd2bffdf1390665bc5709f2b631a6fa69a7fca958f19bd7cc617183da1f50ee63e9352b5 +ae151310985940471e6803fcf37600d7fa98830613e381e00dab943aec32c14162d51c4598e8847148148000d6e5af5c +81eb537b35b7602c45441cfc61b27fa9a30d3998fad35a064e05bc9479e9f10b62eba2b234b348219eea3cadcaac64bb +8a441434934180ab6f5bc541f86ebd06eadbee01f438836d797e930fa803a51510e005c9248cecc231a775b74d12b5e9 +81f3c250a27ba14d8496a5092b145629eb2c2e6a5298438670375363f57e2798207832c8027c3e9238ad94ecdadfc4df +a6217c311f2f3db02ceaa5b6096849fe92b6f4b6f1491535ef8525f6ccee6130bed2809e625073ecbaddd4a3eb3df186 +82d1c396f0388b942cf22b119d7ef1ad03d3dad49a74d9d01649ee284f377c8daddd095d596871669e16160299a210db +a40ddf7043c5d72a7246bd727b07f7fff1549f0e443d611de6f9976c37448b21664c5089c57f20105102d935ab82f27b +b6c03c1c97adf0c4bf4447ec71366c6c1bff401ba46236cd4a33d39291e7a1f0bb34bd078ba3a18d15c98993b153a279 +8a94f5f632068399c359c4b3a3653cb6df2b207379b3d0cdace51afdf70d6d5cce6b89a2b0fee66744eba86c98fb21c2 +b2f19e78ee85073f680c3bba1f07fd31b057c00b97040357d97855b54a0b5accb0d3b05b2a294568fcd6a4be6f266950 +a74632d13bbe2d64b51d7a9c3ae0a5a971c19f51cf7596a807cea053e6a0f3719700976d4e394b356c0329a2dced9aa2 +afef616d341a9bc94393b8dfba68ff0581436aa3a3adb7c26a1bbf2cf19fa877066191681f71f17f3cd6f9cf6bf70b5a +8ce96d93ae217408acf7eb0f9cbb9563363e5c7002e19bbe1e80760bc9d449daee2118f3878b955163ed664516b97294 +8414f79b496176bc8b8e25f8e4cfee28f4f1c2ddab099d63d2aca1b6403d26a571152fc3edb97794767a7c4686ad557c +b6c61d01fd8ce087ef9f079bf25bf10090db483dd4f88c4a786d31c1bdf52065651c1f5523f20c21e75cea17df69ab73 +a5790fd629be70545093631efadddc136661f63b65ec682609c38ef7d3d7fa4e56bdf94f06e263bc055b90cb1c6bcefe +b515a767e95704fb7597bca9e46f1753abacdc0e56e867ee3c6f4cd382643c2a28e65312c05ad040eaa3a8cbe7217a65 +8135806a02ead6aa92e9adb6fefb91349837ab73105aaa7be488ef966aa8dfaafdfa64bbae30fcbfa55dd135a036a863 +8f22435702716d76b1369750694540742d909d5e72b54d0878245fab7c269953b1c6f2b29c66f08d5e0263ca3a731771 +8e0f8a8e8753e077dac95848212aeffd51c23d9b6d611df8b102f654089401954413ecbedc6367561ca599512ae5dda7 +815a9084e3e2345f24c5fa559deec21ee1352fb60f4025c0779be65057f2d528a3d91593bd30d3a185f5ec53a9950676 +967e6555ccba395b2cc1605f8484c5112c7b263f41ce8439a99fd1c71c5ed14ad02684d6f636364199ca48afbbde13be +8cd0ccf17682950b34c796a41e2ea7dd5367aba5e80a907e01f4cdc611e4a411918215e5aebf4292f8b24765d73314a6 +a58bf1bbb377e4b3915df6f058a0f53b8fb8130fdec8c391f6bc82065694d0be59bb67ffb540e6c42cc8b380c6e36359 +92af3151d9e6bfb3383d85433e953c0160859f759b0988431ec5893542ba40288f65db43c78a904325ef8d324988f09d +8011bbb05705167afb47d4425065630f54cb86cd462095e83b81dfebf348f846e4d8fbcf1c13208f5de1931f81da40b9 +81c743c104fc3cb047885c9fa0fb9705c3a83ee24f690f539f4985509c3dafd507af3f6a2128276f45d5939ef70c167f +a2c9679b151c041aaf5efeac5a737a8f70d1631d931609fca16be1905682f35e291292874cb3b03f14994f98573c6f44 +a4949b86c4e5b1d5c82a337e5ce6b2718b1f7c215148c8bfb7e7c44ec86c5c9476048fc5c01f57cb0920876478c41ad6 +86c2495088bd1772152e527a1da0ef473f924ea9ab0e5b8077df859c28078f73c4e22e3a906b507fdf217c3c80808b5c +892e0a910dcf162bcea379763c3e2349349e4cda9402949255ac4a78dd5a47e0bf42f5bd0913951576b1d206dc1e536a +a7009b2c6b396138afe4754b7cc10dee557c51c7f1a357a11486b3253818531f781ea8107360c8d4c3b1cd96282353c0 +911763ef439c086065cc7b4e57484ed6d693ea44acee4b18c9fd998116da55fbe7dcb8d2a0f0f9b32132fca82d73dff6 +a722000b95a4a2d40bed81870793f15ba2af633f9892df507f2842e52452e02b5ea8dea6a043c2b2611d82376e33742a +9387ac49477bd719c2f92240d0bdfcf9767aad247ca93dc51e56106463206bc343a8ec855eb803471629a66fffb565d6 +92819a1fa48ab4902939bb72a0a4e6143c058ea42b42f9bc6cea5df45f49724e2530daf3fc4f097cceefa2a8b9db0076 +98eac7b04537653bc0f4941aae732e4b1f84bd276c992c64a219b8715eb1fb829b5cbd997d57feb15c7694c468f95f70 +b275e7ba848ce21bf7996e12dbeb8dadb5d0e4f1cb5a0248a4f8f9c9fe6c74e3c93f4b61edbcb0a51af5a141e1c14bc7 +97243189285aba4d49c53770c242f2faf5fd3914451da4931472e3290164f7663c726cf86020f8f181e568c72fd172d1 +839b0b3c25dd412bee3dc24653b873cc65454f8f16186bb707bcd58259c0b6765fa4c195403209179192a4455c95f3b8 +8689d1a870514568a074a38232e2ceb4d7df30fabeb76cff0aed5b42bf7f02baea12c5fadf69f4713464dbd52aafa55f +8958ae7b290f0b00d17c3e9fdb4dbf168432b457c7676829299dd428984aba892de1966fc106cfc58a772862ecce3976 +a422bc6bd68b8870cfa5bc4ce71781fd7f4368b564d7f1e0917f6013c8bbb5b240a257f89ecfdbecb40fe0f3aa31d310 +aa61f78130cebe09bc9a2c0a37f0dd57ed2d702962e37d38b1df7f17dc554b1d4b7a39a44182a452ce4c5eb31fa4cfcc +b7918bd114f37869bf1a459023386825821bfadce545201929d13ac3256d92a431e34f690a55d944f77d0b652cefeffc +819bba35fb6ace1510920d4dcff30aa682a3c9af9022e287751a6a6649b00c5402f14b6309f0aeef8fce312a0402915e +8b7c9ad446c6f63c11e1c24e24014bd570862b65d53684e107ba9ad381e81a2eaa96731b4b33536efd55e0f055071274 +8fe79b53f06d33386c0ec7d6d521183c13199498594a46d44a8a716932c3ec480c60be398650bbfa044fa791c4e99b65 +9558e10fb81250b9844c99648cf38fa05ec1e65d0ccbb18aa17f2d1f503144baf59d802c25be8cc0879fff82ed5034ad +b538a7b97fbd702ba84645ca0a63725be1e2891c784b1d599e54e3480e4670d0025526674ef5cf2f87dddf2290ba09f0 +92eafe2e869a3dd8519bbbceb630585c6eb21712b2f31e1b63067c0acb5f9bdbbcbdb612db4ea7f9cc4e7be83d31973f +b40d21390bb813ab7b70a010dff64c57178418c62685761784e37d327ba3cb9ef62df87ecb84277c325a637fe3709732 +b349e6fbf778c4af35fbed33130bd8a7216ed3ba0a79163ebb556e8eb8e1a7dad3456ddd700dad9d08d202491c51b939 +a8fdaedecb251f892b66c669e34137f2650509ade5d38fbe8a05d9b9184bb3b2d416186a3640429bd1f3e4b903c159dd +ac6167ebfee1dbab338eff7642f5e785fc21ef0b4ddd6660333fe398068cbd6c42585f62e81e4edbb72161ce852a1a4f +874b1fbf2ebe140c683bd7e4e0ab017afa5d4ad38055aaa83ee6bbef77dbc88a6ce8eb0dcc48f0155244af6f86f34c2d +903c58e57ddd9c446afab8256a6bb6c911121e6ccfb4f9b4ed3e2ed922a0e500a5cb7fa379d5285bc16e11dac90d1fda +8dae7a0cffa2fd166859cd1bf10ff82dd1932e488af377366b7efc0d5dec85f85fe5e8150ff86a79a39cefc29631733a +aa047857a47cc4dfc08585f28640420fcf105b881fd59a6cf7890a36516af0644d143b73f3515ab48faaa621168f8c31 +864508f7077c266cc0cb3f7f001cb6e27125ebfe79ab57a123a8195f2e27d3799ff98413e8483c533b46a816a3557f1f +8bcd45ab1f9cbab36937a27e724af819838f66dfeb15923f8113654ff877bd8667c54f6307aaf0c35027ca11b6229bfd +b21aa34da9ab0a48fcfdd291df224697ce0c1ebc0e9b022fdee8750a1a4b5ba421c419541ed5c98b461eecf363047471 +a9a18a2ab2fae14542dc336269fe612e9c1af6cf0c9ac933679a2f2cb77d3c304114f4d219ca66fe288adde30716775b +b5205989b92c58bdda71817f9a897e84100b5c4e708de1fced5c286f7a6f01ae96b1c8d845f3a320d77c8e2703c0e8b1 +a364059412bbcc17b8907d43ac8e5df90bc87fd1724b5f99832d0d24559fae6fa76a74cff1d1eac8cbac6ec80b44af20 +ae709f2c339886b31450834cf29a38b26eb3b0779bd77c9ac269a8a925d1d78ea3837876c654b61a8fe834b3b6940808 +8802581bba66e1952ac4dab36af371f66778958f4612901d95e5cac17f59165e6064371d02de8fb6fccf89c6dc8bd118 +a313252df653e29c672cbcfd2d4f775089cb77be1077381cf4dc9533790e88af6cedc8a119158e7da5bf6806ad9b91a1 +992a065b4152c7ef11515cd54ba9d191fda44032a01aed954acff3443377ee16680c7248d530b746b8c6dee2d634e68c +b627b683ee2b32c1ab4ccd27b9f6cce2fe097d96386fa0e5c182ad997c4c422ab8dfc03870cd830b8c774feb66537282 +b823cf8a9aee03dadd013eb9efe40a201b4b57ef67efaae9f99683005f5d1bf55e950bf4af0774f50859d743642d3fea +b8a7449ffac0a3f206677097baf7ce00ca07a4d2bd9b5356fbcb83f3649b0fda07cfebad220c1066afba89e5a52abf4b +b2dd1a2f986395bb4e3e960fbbe823dbb154f823284ebc9068502c19a7609790ec0073d08bfa63f71e30c7161b6ef966 +98e5236de4281245234f5d40a25b503505af140b503a035fc25a26159a9074ec81512b28f324c56ea2c9a5aa7ce90805 +89070847dc8bbf5bc4ed073aa2e2a1f699cf0c2ca226f185a0671cecc54e7d3e14cd475c7752314a7a8e7476829da4bc +a9402dc9117fdb39c4734c0688254f23aed3dce94f5f53f5b7ef2b4bf1b71a67f85ab1a38ec224a59691f3bee050aeb3 +957288f9866a4bf56a4204218ccc583f717d7ce45c01ea27142a7e245ad04a07f289cc044f8cf1f21d35e67e39299e9c +b2fb31ccb4e69113763d7247d0fc8edaae69b550c5c56aecacfd780c7217dc672f9fb7496edf4aba65dacf3361268e5b +b44a4526b2f1d6eb2aa8dba23bfa385ff7634572ab2afddd0546c3beb630fbfe85a32f42dd287a7fec069041411537f7 +8db5a6660c3ac7fd7a093573940f068ee79a82bc17312af900b51c8c439336bc86ca646c6b7ab13aaaa008a24ca508ab +8f9899a6d7e8eb4367beb5c060a1f8e94d8a21099033ae582118477265155ba9e72176a67f7f25d7bad75a152b56e21a +a67de0e91ade8d69a0e00c9ff33ee2909b8a609357095fa12319e6158570c232e5b6f4647522efb7345ce0052aa9d489 +82eb2414898e9c3023d57907a2b17de8e7eea5269029d05a94bfd7bf5685ac4a799110fbb375eb5e0e2bd16acf6458ae +94451fc7fea3c5a89ba701004a9693bab555cb622caf0896b678faba040409fdfd14a978979038b2a81e8f0abc4994d2 +ac879a5bb433998e289809a4a966bd02b4bf6a9c1cc276454e39c886efcf4fc68baebed575826bde577ab5aa71d735a9 +880c0f8f49c875dfd62b4ddedde0f5c8b19f5687e693717f7e5c031bc580e58e13ab497d48b4874130a18743c59fdce3 +b582af8d8ff0bf76f0a3934775e0b54c0e8fed893245d7d89cae65b03c8125b7237edc29dc45b4fe1a3fe6db45d280ee +89f337882ed3ae060aaee98efa20d79b6822bde9708c1c5fcee365d0ec9297f694cae37d38fd8e3d49717c1e86f078e7 +826d2c1faea54061848b484e288a5f4de0d221258178cf87f72e14baaa4acc21322f8c9eab5dde612ef497f2d2e1d60b +a5333d4f227543e9cd741ccf3b81db79f2f03ca9e649e40d6a6e8ff9073e06da83683566d3b3c8d7b258c62970fb24d1 +a28f08c473db06aaf4c043a2fae82b3c8cfaa160bce793a4c208e4e168fb1c65115ff8139dea06453c5963d95e922b94 +8162546135cc5e124e9683bdfaa45833c18553ff06a0861c887dc84a5b12ae8cd4697f6794c7ef6230492c32faba7014 +b23f0d05b74c08d6a7df1760792be83a761b36e3f8ae360f3c363fb196e2a9dd2de2e492e49d36561366e14daa77155c +b6f70d6c546722d3907c708d630dbe289771d2c8bf059c2e32b77f224696d750b4dda9b3a014debda38e7d02c9a77585 +83bf4c4a9f3ca022c631017e7a30ea205ba97f7f5927cba8fc8489a4646eac6712cb821c5668c9ffe94d69d524374a27 +b0371475425a8076d0dd5f733f55aabbe42d20a7c8ea7da352e736d4d35a327b2beb370dfcb05284e22cfd69c5f6c4cc +a0031ba7522c79211416c2cca3aa5450f96f8fee711552a30889910970ba13608646538781a2c08b834b140aadd7166f +99d273c80c7f2dc6045d4ed355d9fc6f74e93549d961f4a3b73cd38683f905934d359058cd1fc4da8083c7d75070487f +b0e4b0efa3237793e9dcce86d75aafe9879c5fa23f0d628649aef2130454dcf72578f9bf227b9d2b9e05617468e82588 +a5ab076fa2e1c5c51f3ae101afdd596ad9d106bba7882b359c43d8548b64f528af19afa76cd6f40da1e6c5fca4def3fa +8ce2299e570331d60f6a6eff1b271097cd5f1c0e1113fc69b89c6a0f685dabea3e5bc2ac6bd789aa492ab189f89be494 +91b829068874d911a310a5f9dee001021f97471307b5a3de9ec336870ec597413e1d92010ce320b619f38bed7c4f7910 +b14fe91f4b07bf33b046e9285b66cb07927f3a8da0af548ac2569b4c4fb1309d3ced76d733051a20814e90dd5b75ffd1 +abaab92ea6152d40f82940277c725aa768a631ee0b37f5961667f82fb990fc11e6d3a6a2752b0c6f94563ed9bb28265c +b7fe28543eca2a716859a76ab9092f135337e28109544f6bd2727728d0a7650428af5713171ea60bfc273d1c821d992c +8a4917b2ab749fc7343fc64bdf51b6c0698ff15d740cc7baf248c030475c097097d5a473bcc00d8c25817563fe0447b4 +aa96156d1379553256350a0a3250166add75948fb9cde62aa555a0a9dc0a9cb7f2f7b8428aff66097bf6bfedaf14bbe2 +ae4ffeb9bdc76830d3eca2b705f30c1bdede6412fa064260a21562c8850c7fb611ec62bc68479fe48f692833e6f66d8d +b96543caaba9d051600a14997765d49e4ab10b07c7a92cccf0c90b309e6da334fdd6d18c96806cbb67a7801024fbd3c7 +97b2b9ad76f19f500fcc94ca8e434176249f542ac66e5881a3dccd07354bdab6a2157018b19f8459437a68d8b86ba8e0 +a8d206f6c5a14c80005849474fde44b1e7bcf0b2d52068f5f97504c3c035b09e65e56d1cf4b5322791ae2c2fdbd61859 +936bad397ad577a70cf99bf9056584a61bd7f02d2d5a6cf219c05d770ae30a5cd902ba38366ce636067fc1dd10108d31 +a77e30195ee402b84f3882e2286bf5380c0ed374a112dbd11e16cef6b6b61ab209d4635e6f35cdaaa72c1a1981d5dabe +a46ba4d3947188590a43c180757886a453a0503f79cc435322d92490446f37419c7b999fdf868a023601078070e03346 +80d8d4c5542f223d48240b445d4d8cf6a75d120b060bc08c45e99a13028b809d910b534d2ac47fb7068930c54efd8da9 +803be9c68c91b42b68e1f55e58917a477a9a6265e679ca44ee30d3eb92453f8c89c64eafc04c970d6831edd33d066902 +b14b2b3d0dfe2bb57cee4cd72765b60ac33c1056580950be005790176543826c1d4fbd737f6cfeada6c735543244ab57 +a9e480188bba1b8fb7105ff12215706665fd35bf1117bacfb6ab6985f4dbc181229873b82e5e18323c2b8f5de03258e0 +a66a0f0779436a9a3999996d1e6d3000f22c2cac8e0b29cddef9636393c7f1457fb188a293b6c875b05d68d138a7cc4a +848397366300ab40c52d0dbbdafbafef6cd3dadf1503bb14b430f52bb9724188928ac26f6292a2412bc7d7aa620763c8 +95466cc1a78c9f33a9aaa3829a4c8a690af074916b56f43ae46a67a12bb537a5ac6dbe61590344a25b44e8512355a4a7 +8b5f7a959f818e3baf0887f140f4575cac093d0aece27e23b823cf421f34d6e4ff4bb8384426e33e8ec7b5eed51f6b5c +8d5e1368ec7e3c65640d216bcc5d076f3d9845924c734a34f3558ac0f16e40597c1a775a25bf38b187213fbdba17c93b +b4647c1b823516880f60d20c5cc38c7f80b363c19d191e8992226799718ee26b522a12ecb66556ed3d483aa4824f3326 +ac3abaea9cd283eb347efda4ed9086ea3acf495043e08d0d19945876329e8675224b685612a6badf8fd72fb6274902b1 +8eae1ce292d317aaa71bcf6e77e654914edd5090e2e1ebab78b18bb41b9b1bc2e697439f54a44c0c8aa0d436ebe6e1a9 +94dc7d1aec2c28eb43d93b111fa59aaa0d77d5a09501220bd411768c3e52208806abf973c6a452fd8292ff6490e0c9e2 +8fd8967f8e506fef27d17b435d6b86b232ec71c1036351f12e6fb8a2e12daf01d0ee04451fb944d0f1bf7fd20e714d02 +824e6865be55d43032f0fec65b3480ea89b0a2bf860872237a19a54bc186a85d2f8f9989cc837fbb325b7c72d9babe2c +8bd361f5adb27fd6f4e3f5de866e2befda6a8454efeb704aacc606f528c03f0faae888f60310e49440496abd84083ce2 +b098a3c49f2aaa28b6b3e85bc40ce6a9cdd02134ee522ae73771e667ad7629c8d82c393fba9f27f5416986af4c261438 +b385f5ca285ff2cfe64dcaa32dcde869c28996ed091542600a0b46f65f3f5a38428cca46029ede72b6cf43e12279e3d3 +8196b03d011e5be5288196ef7d47137d6f9237a635ab913acdf9c595fa521d9e2df722090ec7eb0203544ee88178fc5f +8ed1270211ef928db18e502271b7edf24d0bbd11d97f2786aee772d70c2029e28095cf8f650b0328cc8a4c38d045316d +a52ab60e28d69b333d597a445884d44fd2a7e1923dd60f763951e1e45f83e27a4dac745f3b9eff75977b3280e132c15d +91e9fe78cdac578f4a4687f71b800b35da54b824b1886dafec073a3c977ce7a25038a2f3a5b1e35c2c8c9d1a7312417c +a42832173f9d9491c7bd93b21497fbfa4121687cd4d2ab572e80753d7edcbb42cfa49f460026fbde52f420786751a138 +97b947126d84dcc70c97be3c04b3de3f239b1c4914342fa643b1a4bb8c4fe45c0fcb585700d13a7ed50784790c54bef9 +860e407d353eac070e2418ef6cb80b96fc5f6661d6333e634f6f306779651588037be4c2419562c89c61f9aa2c4947f5 +b2c9d93c3ba4e511b0560b55d3501bf28a510745fd666b3cb532db051e6a8617841ea2f071dda6c9f15619c7bfd2737f +8596f4d239aeeac78311207904d1bd863ef68e769629cc379db60e019aaf05a9d5cd31dc8e630b31e106a3a93e47cbc5 +8b26e14e2e136b65c5e9e5c2022cee8c255834ea427552f780a6ca130a6446102f2a6f334c3f9a0308c53df09e3dba7e +b54724354eb515a3c8bed0d0677ff1db94ac0a07043459b4358cb90e3e1aa38ac23f2caa3072cf9647275d7cd61d0e80 +b7ce9fe0e515e7a6b2d7ddcb92bc0196416ff04199326aea57996eef8c5b1548bd8569012210da317f7c0074691d01b7 +a1a13549c82c877253ddefa36a29ea6a23695ee401fdd48e65f6f61e5ebd956d5e0edeff99484e9075cb35071fec41e2 +838ba0c1e5bd1a6da05611ff1822b8622457ebd019cb065ece36a2d176bd2d889511328120b8a357e44569e7f640c1e6 +b916eccff2a95519400bbf76b5f576cbe53cf200410370a19d77734dc04c05b585cfe382e8864e67142d548cd3c4c2f4 +a610447cb7ca6eea53a6ff1f5fe562377dcb7f4aaa7300f755a4f5e8eba61e863c51dc2aa9a29b35525b550fbc32a0fe +9620e8f0f0ee9a4719aa9685eeb1049c5c77659ba6149ec4c158f999cfd09514794b23388879931fe26fea03fa471fd3 +a9dcf8b679e276583cf5b9360702a185470d09aea463dc474ee9c8aee91ef089dacb073e334e47fbc78ec5417c90465c +8c9adee8410bdd99e5b285744cee61e2593b6300ff31a8a83b0ec28da59475a5c6fb9346fe43aadea2e6c3dad2a8e30a +97d5afe9b3897d7b8bb628b7220cf02d8ee4e9d0b78f5000d500aaf4c1df9251aaaabfd1601626519f9d66f00a821d4e +8a382418157b601ce4c3501d3b8409ca98136a4ef6abcbf62885e16e215b76b035c94d149cc41ff92e42ccd7c43b9b3d +b64b8d11fb3b01abb2646ac99fdb9c02b804ce15d98f9fe0fbf1c9df8440c71417487feb6cdf51e3e81d37104b19e012 +849d7d044f9d8f0aab346a9374f0b3a5d14a9d1faa83dbacccbdc629ad1ef903a990940255564770537f8567521d17f0 +829dbb0c76b996c2a91b4cbbe93ba455ca0d5729755e5f0c92aaee37dff7f36fcdc06f33aca41f1b609c784127b67d88 +85a7c0069047b978422d264d831ab816435f63938015d2e977222b6b5746066c0071b7f89267027f8a975206ed25c1b0 +84b9fbc1cfb302df1acdcf3dc5d66fd1edfe7839f7a3b2fb3a0d5548656249dd556104d7c32b73967bccf0f5bdcf9e3b +972220ac5b807f53eac37dccfc2ad355d8b21ea6a9c9b011c09fe440ddcdf7513e0b43d7692c09ded80d7040e26aa28f +855885ed0b21350baeca890811f344c553cf9c21024649c722453138ba29193c6b02c4b4994cd414035486f923472e28 +841874783ae6d9d0e59daea03e96a01cbbe4ecaced91ae4f2c8386e0d87b3128e6d893c98d17c59e4de1098e1ad519dd +827e50fc9ce56f97a4c3f2f4cbaf0b22f1c3ce6f844ff0ef93a9c57a09b8bf91ebfbd2ba9c7f83c442920bffdaf288cc +a441f9136c7aa4c08d5b3534921b730e41ee91ab506313e1ba5f7c6f19fd2d2e1594e88c219834e92e6fb95356385aa7 +97d75b144471bf580099dd6842b823ec0e6c1fb86dd0da0db195e65524129ea8b6fd4a7a9bbf37146269e938a6956596 +a4b6fa87f09d5a29252efb2b3aaab6b3b6ea9fab343132a651630206254a25378e3e9d6c96c3d14c150d01817d375a8e +a31a671876d5d1e95fe2b8858dc69967231190880529d57d3cab7f9f4a2b9b458ac9ee5bdaa3289158141bf18f559efb +90bee6fff4338ba825974021b3b2a84e36d617e53857321f13d2b3d4a28954e6de3b3c0e629d61823d18a9763313b3bf +96b622a63153f393bb419bfcf88272ea8b3560dbd46b0aa07ada3a6223990d0abdd6c2adb356ef4be5641688c8d83941 +84c202adeaff9293698022bc0381adba2cd959f9a35a4e8472288fd68f96f6de8be9da314c526d88e291c96b1f3d6db9 +8ca01a143b8d13809e5a8024d03e6bc9492e22226073ef6e327edf1328ef4aff82d0bcccee92cb8e212831fa35fe1204 +b2f970dbad15bfbefb38903c9bcc043d1367055c55dc1100a850f5eb816a4252c8c194b3132c929105511e14ea10a67d +a5e36556472a95ad57eb90c3b6623671b03eafd842238f01a081997ffc6e2401f76e781d049bb4aa94d899313577a9cf +8d1057071051772f7c8bedce53a862af6fd530dd56ae6321eaf2b9fc6a68beff5ed745e1c429ad09d5a118650bfd420a +8aadc4f70ace4fcb8d93a78610779748dcffc36182d45b932c226dc90e48238ea5daa91f137c65ed532352c4c4d57416 +a2ea05ae37e673b4343232ae685ee14e6b88b867aef6dfac35db3589cbcd76f99540fed5c2641d5bb5a4a9f808e9bf0d +947f1abad982d65648ae4978e094332b4ecb90f482c9be5741d5d1cf5a28acf4680f1977bf6e49dd2174c37f11e01296 +a27b144f1565e4047ba0e3f4840ef19b5095d1e281eaa463c5358f932114cbd018aa6dcf97546465cf2946d014d8e6d6 +8574e1fc3acade47cd4539df578ce9205e745e161b91e59e4d088711a7ab5aa3b410d517d7304b92109924d9e2af8895 +a48ee6b86b88015d6f0d282c1ae01d2a5b9e8c7aa3d0c18b35943dceb1af580d08a65f54dc6903cde82fd0d73ce94722 +8875650cec543a7bf02ea4f2848a61d167a66c91ffaefe31a9e38dc8511c6a25bde431007eefe27a62af3655aca208dc +999b0a6e040372e61937bf0d68374e230346b654b5a0f591a59d33a4f95bdb2f3581db7c7ccb420cd7699ed709c50713 +878c9e56c7100c5e47bbe77dc8da5c5fe706cec94d37fa729633bca63cace7c40102eee780fcdabb655f5fa47a99600e +865006fb5b475ada5e935f27b96f9425fc2d5449a3c106aa366e55ebed3b4ee42adc3c3f0ac19fd129b40bc7d6bc4f63 +b7a7da847f1202e7bc1672553e68904715e84fd897d529243e3ecda59faa4e17ba99c649a802d53f6b8dfdd51f01fb74 +8b2fb4432c05653303d8c8436473682933a5cb604da10c118ecfcd2c8a0e3132e125afef562bdbcc3df936164e5ce4f2 +808d95762d33ddfa5d0ee3d7d9f327de21a994d681a5f372e2e3632963ea974da7f1f9e5bac8ccce24293509d1f54d27 +932946532e3c397990a1df0e94c90e1e45133e347a39b6714c695be21aeb2d309504cb6b1dde7228ff6f6353f73e1ca2 +9705e7c93f0cdfaa3fa96821f830fe53402ad0806036cd1b48adc2f022d8e781c1fbdab60215ce85c653203d98426da3 +aa180819531c3ec1feb829d789cb2092964c069974ae4faad60e04a6afcce5c3a59aec9f11291e6d110a788d22532bc6 +88f755097f7e25cb7dd3c449520c89b83ae9e119778efabb54fbd5c5714b6f37c5f9e0346c58c6ab09c1aef2483f895d +99fc03ab7810e94104c494f7e40b900f475fde65bdec853e60807ffd3f531d74de43335c3b2646b5b8c26804a7448898 +af2dea9683086bed1a179110efb227c9c00e76cd00a2015b089ccbcee46d1134aa18bda5d6cab6f82ae4c5cd2461ac21 +a500f87ba9744787fdbb8e750702a3fd229de6b8817594348dec9a723b3c4240ddfa066262d002844b9e38240ce55658 +924d0e45c780f5bc1c1f35d15dfc3da28036bdb59e4c5440606750ecc991b85be18bc9a240b6c983bc5430baa4c68287 +865b11e0157b8bf4c5f336024b016a0162fc093069d44ac494723f56648bc4ded13dfb3896e924959ea11c96321afefc +93672d8607d4143a8f7894f1dcca83fb84906dc8d6dd7dd063bb0049cfc20c1efd933e06ca7bd03ea4cb5a5037990bfe +826891efbdff0360446825a61cd1fa04326dd90dae8c33dfb1ed97b045e165766dd070bd7105560994d0b2044bdea418 +93c4a4a8bcbc8b190485cc3bc04175b7c0ed002c28c98a540919effd6ed908e540e6594f6db95cd65823017258fb3b1c +aeb2a0af2d2239fda9aa6b8234b019708e8f792834ff0dd9c487fa09d29800ddceddd6d7929faa9a3edcb9e1b3aa0d6b +87f11de7236d387863ec660d2b04db9ac08143a9a2c4dfff87727c95b4b1477e3bc473a91e5797313c58754905079643 +80dc1db20067a844fe8baceca77f80db171a5ca967acb24e2d480eae9ceb91a3343c31ad1c95b721f390829084f0eae6 +9825c31f1c18da0de3fa84399c8b40f8002c3cae211fb6a0623c76b097b4d39f5c50058f57a16362f7a575909d0a44a2 +a99fc8de0c38dbf7b9e946de83943a6b46a762167bafe2a603fb9b86f094da30d6de7ed55d639aafc91936923ee414b3 +ad594678b407db5d6ea2e90528121f84f2b96a4113a252a30d359a721429857c204c1c1c4ff71d8bb5768c833f82e80e +b33d985e847b54510b9b007e31053732c8a495e43be158bd2ffcea25c6765bcbc7ca815f7c60b36ad088b955dd6e9350 +815f8dfc6f90b3342ca3fbd968c67f324dae8f74245cbf8bc3bef10e9440c65d3a2151f951e8d18959ba01c1b50b0ec1 +94c608a362dd732a1abc56e338637c900d59013db8668e49398b3c7a0cae3f7e2f1d1bf94c0299eeafe6af7f76c88618 +8ebd8446b23e5adfcc393adc5c52fe172f030a73e63cd2d515245ca0dd02782ceed5bcdd9ccd9c1b4c5953dfac9c340c +820437f3f6f9ad0f5d7502815b221b83755eb8dc56cd92c29e9535eb0b48fb8d08c9e4fcc26945f9c8cca60d89c44710 +8910e4e8a56bf4be9cc3bbf0bf6b1182a2f48837a2ed3c2aaec7099bfd7f0c83e14e608876b17893a98021ff4ab2f20d +9633918fde348573eec15ce0ad53ac7e1823aac86429710a376ad661002ae6d049ded879383faaa139435122f64047c6 +a1f5e3fa558a9e89318ca87978492f0fb4f6e54a9735c1b8d2ecfb1d1c57194ded6e0dd82d077b2d54251f3bee1279e1 +b208e22d04896abfd515a95c429ff318e87ff81a5d534c8ac2c33c052d6ffb73ef1dccd39c0bbe0734b596c384014766 +986d5d7d2b5bde6d16336f378bd13d0e671ad23a8ec8a10b3fc09036faeeb069f60662138d7a6df3dfb8e0d36180f770 +a2d4e6c5f5569e9cef1cddb569515d4b6ace38c8aed594f06da7434ba6b24477392cc67ba867c2b079545ca0c625c457 +b5ac32b1d231957d91c8b7fc43115ce3c5c0d8c13ca633374402fa8000b6d9fb19499f9181844f0c10b47357f3f757ce +96b8bf2504b4d28fa34a4ec378e0e0b684890c5f44b7a6bb6e19d7b3db2ab27b1e2686389d1de9fbd981962833a313ea +953bfd7f6c3a0469ad432072b9679a25486f5f4828092401eff494cfb46656c958641a4e6d0d97d400bc59d92dba0030 +876ab3cea7484bbfd0db621ec085b9ac885d94ab55c4bb671168d82b92e609754b86aaf472c55df3d81421d768fd108a +885ff4e67d9ece646d02dd425aa5a087e485c3f280c3471b77532b0db6145b69b0fbefb18aa2e3fa5b64928b43a94e57 +b91931d93f806d0b0e6cc62a53c718c099526140f50f45d94b8bbb57d71e78647e06ee7b42aa5714aed9a5c05ac8533f +a0313eeadd39c720c9c27b3d671215331ab8d0a794e71e7e690f06bcd87722b531d6525060c358f35f5705dbb7109ccb +874c0944b7fedc6701e53344100612ddcb495351e29305c00ec40a7276ea5455465ffb7bded898886c1853139dfb1fc7 +8dc31701a01ee8137059ca1874a015130d3024823c0576aa9243e6942ec99d377e7715ed1444cd9b750a64b85dcaa3e5 +836d2a757405e922ec9a2dfdcf489a58bd48b5f9683dd46bf6047688f778c8dee9bc456de806f70464df0b25f3f3d238 +b30b0a1e454a503ea3e2efdec7483eaf20b0a5c3cefc42069e891952b35d4b2c955cf615f3066285ed8fafd9fcfbb8f6 +8e6d4044b55ab747e83ec8762ea86845f1785cc7be0279c075dadf08aca3ccc5a096c015bb3c3f738f647a4eadea3ba5 +ad7735d16ab03cbe09c029610aa625133a6daecfc990b297205b6da98eda8c136a7c50db90f426d35069708510d5ae9c +8d62d858bbb59ec3c8cc9acda002e08addab4d3ad143b3812098f3d9087a1b4a1bb255dcb1635da2402487d8d0249161 +805beec33238b832e8530645a3254aeef957e8f7ea24bcfc1054f8b9c69421145ebb8f9d893237e8a001c857fedfc77e +b1005644be4b085e3f5775aa9bd3e09a283e87ddada3082c04e7a62d303dcef3b8cf8f92944c200c7ae6bb6bdf63f832 +b4ba0e0790dc29063e577474ffe3b61f5ea2508169f5adc1e394934ebb473e356239413a17962bc3e5d3762d72cce8c2 +a157ba9169c9e3e6748d9f1dd67fbe08b9114ade4c5d8fc475f87a764fb7e6f1d21f66d7905cd730f28a1c2d8378682a +913e52b5c93989b5d15e0d91aa0f19f78d592bc28bcfdfddc885a9980c732b1f4debb8166a7c4083c42aeda93a702898 +90fbfc1567e7cd4e096a38433704d3f96a2de2f6ed3371515ccc30bc4dd0721a704487d25a97f3c3d7e4344472702d8d +89646043028ffee4b69d346907586fd12c2c0730f024acb1481abea478e61031966e72072ff1d5e65cb8c64a69ad4eb1 +b125a45e86117ee11d2fb42f680ab4a7894edd67ff927ae2c808920c66c3e55f6a9d4588eee906f33a05d592e5ec3c04 +aad47f5b41eae9be55fb4f67674ff1e4ae2482897676f964a4d2dcb6982252ee4ff56aac49578b23f72d1fced707525e +b9ddff8986145e33851b4de54d3e81faa3352e8385895f357734085a1616ef61c692d925fe62a5ed3be8ca49f5d66306 +b3cb0963387ed28c0c0adf7fe645f02606e6e1780a24d6cecef5b7c642499109974c81a7c2a198b19862eedcea2c2d8c +ac9c53c885457aaf5cb36c717a6f4077af701e0098eebd7aa600f5e4b14e6c1067255b3a0bc40e4a552025231be7de60 +8e1a8d823c4603f6648ec21d064101094f2a762a4ed37dd2f0a2d9aa97b2d850ce1e76f4a4b8cae58819b058180f7031 +b268b73bf7a179b6d22bd37e5e8cb514e9f5f8968c78e14e4f6d5700ca0d0ca5081d0344bb73b028970eebde3cb4124e +a7f57d71940f0edbd29ed8473d0149cae71d921dd15d1ff589774003e816b54b24de2620871108cec1ab9fa956ad6ce6 +8053e6416c8b120e2b999cc2fc420a6a55094c61ac7f2a6c6f0a2c108a320890e389af96cbe378936132363c0d551277 +b3823f4511125e5aa0f4269e991b435a0d6ceb523ebd91c04d7add5534e3df5fc951c504b4fd412a309fd3726b7f940b +ae6eb04674d04e982ca9a6add30370ab90e303c71486f43ed3efbe431af1b0e43e9d06c11c3412651f304c473e7dbf39 +96ab55e641ed2e677591f7379a3cd126449614181fce403e93e89b1645d82c4af524381ff986cae7f9cebe676878646d +b52423b4a8c37d3c3e2eca8f0ddbf7abe0938855f33a0af50f117fab26415fb0a3da5405908ec5fdc22a2c1f2ca64892 +82a69ce1ee92a09cc709d0e3cd22116c9f69d28ea507fe5901f5676000b5179b9abe4c1875d052b0dd42d39925e186bb +a84c8cb84b9d5cfb69a5414f0a5283a5f2e90739e9362a1e8c784b96381b59ac6c18723a4aa45988ee8ef5c1f45cc97d +afd7efce6b36813082eb98257aae22a4c1ae97d51cac7ea9c852d4a66d05ef2732116137d8432e3f117119725a817d24 +a0f5fe25af3ce021b706fcff05f3d825384a272284d04735574ce5fb256bf27100fad0b1f1ba0e54ae9dcbb9570ecad3 +8751786cb80e2e1ff819fc7fa31c2833d25086534eb12b373d31f826382430acfd87023d2a688c65b5e983927e146336 +8cf5c4b17fa4f3d35c78ce41e1dc86988fd1135cd5e6b2bb0c108ee13538d0d09ae7102609c6070f39f937b439b31e33 +a9108967a2fedd7c322711eca8159c533dd561bedcb181b646de98bf5c3079449478eab579731bee8d215ae8852c7e21 +b54c5171704f42a6f0f4e70767cdb3d96ffc4888c842eece343a01557da405961d53ffdc34d2f902ea25d3e1ed867cad +ae8d4b764a7a25330ba205bf77e9f46182cd60f94a336bbd96773cf8064e3d39caf04c310680943dc89ed1fbad2c6e0d +aa5150e911a8e1346868e1b71c5a01e2a4bb8632c195861fb6c3038a0e9b85f0e09b3822e9283654a4d7bb17db2fc5f4 +9685d3756ce9069bf8bb716cf7d5063ebfafe37e15b137fc8c3159633c4e006ff4887ddd0ae90360767a25c3f90cba7f +82155fd70f107ab3c8e414eadf226c797e07b65911508c76c554445422325e71af8c9a8e77fd52d94412a6fc29417cd3 +abfae52f53a4b6e00760468d973a267f29321997c3dbb5aee36dc1f20619551229c0c45b9d9749f410e7f531b73378e8 +81a76d921f8ef88e774fd985e786a4a330d779b93fad7def718c014685ca0247379e2e2a007ad63ee7f729cd9ed6ce1b +81947c84bc5e28e26e2e533af5ae8fe10407a7b77436dbf8f1d5b0bbe86fc659eae10f974659dc7c826c6dabd03e3a4b +92b8c07050d635b8dd4fd09df9054efe4edae6b86a63c292e73cc819a12a21dd7d104ce51fa56af6539dedf6dbe6f7b6 +b44c579e3881f32b32d20c82c207307eca08e44995dd2aac3b2692d2c8eb2a325626c80ac81c26eeb38c4137ff95add5 +97efab8941c90c30860926dea69a841f2dcd02980bf5413b9fd78d85904588bf0c1021798dbc16c8bbb32cce66c82621 +913363012528b50698e904de0588bf55c8ec5cf6f0367cfd42095c4468fcc64954fbf784508073e542fee242d0743867 +8ed203cf215148296454012bd10fddaf119203db1919a7b3d2cdc9f80e66729464fdfae42f1f2fc5af1ed53a42b40024 +ab84312db7b87d711e9a60824f4fe50e7a6190bf92e1628688dfcb38930fe87b2d53f9e14dd4de509b2216856d8d9188 +880726def069c160278b12d2258eac8fa63f729cd351a710d28b7e601c6712903c3ac1e7bbd0d21e4a15f13ca49db5aa +980699cd51bac6283959765f5174e543ed1e5f5584b5127980cbc2ef18d984ecabba45042c6773b447b8e694db066028 +aeb019cb80dc4cb4207430d0f2cd24c9888998b6f21d9bf286cc638449668d2eec0018a4cf3fe6448673cd6729335e2b +b29852f6aa6c60effdffe96ae88590c88abae732561d35cc19e82d3a51e26cb35ea00986193e07f90060756240f5346e +a0fa855adc5ba469f35800c48414b8921455950a5c0a49945d1ef6e8f2a1881f2e2dfae47de6417270a6bf49deeb091d +b6c7332e3b14813641e7272d4f69ecc7e09081df0037d6dab97ce13a9e58510f5c930d300633f208181d9205c5534001 +85a6c050f42fce560b5a8d54a11c3bbb8407abbadd859647a7b0c21c4b579ec65671098b74f10a16245dc779dff7838e +8f3eb34bb68759d53c6677de4de78a6c24dd32c8962a7fb355ed362572ef8253733e6b52bc21c9f92ecd875020a9b8de +a17dd44181e5dab4dbc128e1af93ec22624b57a448ca65d2d9e246797e4af7d079e09c6e0dfb62db3a9957ce92f098d5 +a56a1b854c3183082543a8685bb34cae1289f86cfa8123a579049dbd059e77982886bfeb61bf6e05b4b1fe4e620932e7 +aedae3033cb2fb7628cb4803435bdd7757370a86f808ae4cecb9a268ad0e875f308c048c80cbcac523de16b609683887 +9344905376aa3982b1179497fac5a1d74b14b7038fd15e3b002db4c11c8bfc7c39430db492cdaf58b9c47996c9901f28 +a3bfafdae011a19f030c749c3b071f83580dee97dd6f949e790366f95618ca9f828f1daaeabad6dcd664fcef81b6556d +81c03d8429129e7e04434dee2c529194ddb01b414feda3adee2271eb680f6c85ec872a55c9fa9d2096f517e13ed5abcc +98205ef3a72dff54c5a9c82d293c3e45d908946fa74bb749c3aabe1ab994ea93c269bcce1a266d2fe67a8f02133c5985 +85a70aeed09fda24412fadbafbbbf5ba1e00ac92885df329e147bfafa97b57629a3582115b780d8549d07d19b7867715 +b0fbe81c719f89a57d9ea3397705f898175808c5f75f8eb81c2193a0b555869ba7bd2e6bc54ee8a60cea11735e21c68c +b03a0bd160495ee626ff3a5c7d95bc79d7da7e5a96f6d10116600c8fa20bedd1132f5170f25a22371a34a2d763f2d6d0 +a90ab04091fbca9f433b885e6c1d60ab45f6f1daf4b35ec22b09909d493a6aab65ce41a6f30c98239cbca27022f61a8b +b66f92aa3bf2549f9b60b86f99a0bd19cbdd97036d4ae71ca4b83d669607f275260a497208f6476cde1931d9712c2402 +b08e1fdf20e6a9b0b4942f14fa339551c3175c1ffc5d0ab5b226b6e6a322e9eb0ba96adc5c8d59ca4259e2bdd04a7eb0 +a2812231e92c1ce74d4f5ac3ab6698520288db6a38398bb38a914ac9326519580af17ae3e27cde26607e698294022c81 +abfcbbcf1d3b9e84c02499003e490a1d5d9a2841a9e50c7babbef0b2dd20d7483371d4dc629ba07faf46db659459d296 +b0fe9f98c3da70927c23f2975a9dc4789194d81932d2ad0f3b00843dd9cbd7fb60747a1da8fe5a79f136a601becf279d +b130a6dba7645165348cb90f023713bed0eefbd90a976b313521c60a36d34f02032e69a2bdcf5361e343ed46911297ec +862f0cffe3020cea7a5fd4703353aa1eb1be335e3b712b29d079ff9f7090d1d8b12013011e1bdcbaa80c44641fd37c9f +8c6f11123b26633e1abb9ed857e0bce845b2b3df91cc7b013b2fc77b477eee445da0285fc6fc793e29d5912977f40916 +91381846126ea819d40f84d3005e9fb233dc80071d1f9bb07f102bf015f813f61e5884ffffb4f5cd333c1b1e38a05a58 +8add7d908de6e1775adbd39c29a391f06692b936518db1f8fde74eb4f533fc510673a59afb86e3a9b52ade96e3004c57 +8780e086a244a092206edcde625cafb87c9ab1f89cc3e0d378bc9ee776313836160960a82ec397bc3800c0a0ec3da283 +a6cb4cd9481e22870fdd757fae0785edf4635e7aacb18072fe8dc5876d0bab53fb99ce40964a7d3e8bcfff6f0ab1332f +af30ff47ecc5b543efba1ba4706921066ca8bb625f40e530fb668aea0551c7647a9d126e8aba282fbcce168c3e7e0130 +91b0bcf408ce3c11555dcb80c4410b5bc2386d3c05caec0b653352377efdcb6bab4827f2018671fc8e4a0e90d772acc1 +a9430b975ef138b6b2944c7baded8fe102d31da4cfe3bd3d8778bda79189c99d38176a19c848a19e2d1ee0bddd9a13c1 +aa5a4eef849d7c9d2f4b018bd01271c1dd83f771de860c4261f385d3bdcc130218495860a1de298f14b703ec32fa235f +b0ce79e7f9ae57abe4ff366146c3b9bfb38b0dee09c28c28f5981a5d234c6810ad4d582751948affb480d6ae1c8c31c4 +b75122748560f73d15c01a8907d36d06dc068e82ce22b84b322ac1f727034493572f7907dec34ebc3ddcc976f2f89ed7 +b0fc7836369a3e4411d34792d6bd5617c14f61d9bba023dda64e89dc5fb0f423244e9b48ee64869258931daa9753a56f +8956d7455ae9009d70c6e4a0bcd7610e55f37494cf9897a8f9e1b904cc8febc3fd2d642ebd09025cfff4609ad7e3bc52 +ad741efe9e472026aa49ae3d9914cb9c1a6f37a54f1a6fe6419bebd8c7d68dca105a751c7859f4389505ede40a0de786 +b52f418797d719f0d0d0ffb0846788b5cba5d0454a69a2925de4b0b80fa4dd7e8c445e5eac40afd92897ed28ca650566 +a0ab65fb9d42dd966cd93b1de01d7c822694669dd2b7a0c04d99cd0f3c3de795f387b9c92da11353412f33af5c950e9a +a0052f44a31e5741a331f7cac515a08b3325666d388880162d9a7b97598fde8b61f9ff35ff220df224eb5c4e40ef0567 +a0101cfdc94e42b2b976c0d89612a720e55d145a5ef6ef6f1f78cf6de084a49973d9b5d45915349c34ce712512191e3c +a0dd99fcf3f5cead5aaf08e82212df3a8bb543c407a4d6fab88dc5130c1769df3f147e934a46f291d6c1a55d92b86917 +a5939153f0d1931bbda5cf6bdf20562519ea55fbfa978d6dbc6828d298260c0da7a50c37c34f386e59431301a96c2232 +9568269f3f5257200f9ca44afe1174a5d3cf92950a7f553e50e279c239e156a9faaa2a67f288e3d5100b4142efe64856 +b746b0832866c23288e07f24991bbf687cad794e7b794d3d3b79367566ca617d38af586cdc8d6f4a85a34835be41d54f +a871ce28e39ab467706e32fec1669fda5a4abba2f8c209c6745df9f7a0fa36bbf1919cf14cb89ea26fa214c4c907ae03 +a08dacdd758e523cb8484f6bd070642c0c20e184abdf8e2a601f61507e93952d5b8b0c723c34fcbdd70a8485eec29db2 +85bdb78d501382bb95f1166b8d032941005661aefd17a5ac32df9a3a18e9df2fc5dc2c1f07075f9641af10353cecc0c9 +98d730c28f6fa692a389e97e368b58f4d95382fad8f0baa58e71a3d7baaea1988ead47b13742ce587456f083636fa98e +a557198c6f3d5382be9fb363feb02e2e243b0c3c61337b3f1801c4a0943f18e38ce1a1c36b5c289c8fa2aa9d58742bab +89174f79201742220ac689c403fc7b243eed4f8e3f2f8aba0bf183e6f5d4907cb55ade3e238e3623d9885f03155c4d2b +b891d600132a86709e06f3381158db300975f73ea4c1f7c100358e14e98c5fbe792a9af666b85c4e402707c3f2db321e +b9e5b2529ef1043278c939373fc0dbafe446def52ddd0a8edecd3e4b736de87e63e187df853c54c28d865de18a358bb6 +8589b2e9770340c64679062c5badb7bbef68f55476289b19511a158a9a721f197da03ece3309e059fc4468b15ac33aa3 +aad8c6cd01d785a881b446f06f1e9cd71bca74ba98674c2dcddc8af01c40aa7a6d469037498b5602e76e9c91a58d3dbd +abaccb1bd918a8465f1bf8dbe2c9ad4775c620b055550b949a399f30cf0d9eb909f3851f5b55e38f9e461e762f88f499 +ae62339d26db46e85f157c0151bd29916d5cc619bd4b832814b3fd2f00af8f38e7f0f09932ffe5bba692005dab2d9a74 +93a6ff30a5c0edf8058c89aba8c3259e0f1b1be1b80e67682de651e5346f7e1b4b4ac3d87cbaebf198cf779524aff6bf +8980a2b1d8f574af45b459193c952400b10a86122b71fca2acb75ee0dbd492e7e1ef5b959baf609a5172115e371f3177 +8c2f49f3666faee6940c75e8c7f6f8edc3f704cca7a858bbb7ee5e96bba3b0cf0993996f781ba6be3b0821ef4cb75039 +b14b9e348215b278696018330f63c38db100b0542cfc5be11dc33046e3bca6a13034c4ae40d9cef9ea8b34fef0910c4e +b59bc3d0a30d66c16e6a411cb641f348cb1135186d5f69fda8b0a0934a5a2e7f6199095ba319ec87d3fe8f1ec4a06368 +8874aca2a3767aa198e4c3fec2d9c62d496bc41ff71ce242e9e082b7f38cdf356089295f80a301a3cf1182bde5308c97 +b1820ebd61376d91232423fc20bf008b2ba37e761199f4ef0648ea2bd70282766799b4de814846d2f4d516d525c8daa7 +a6b202e5dedc16a4073e04a11af3a8509b23dfe5a1952f899adeb240e75c3f5bde0c424f811a81ea48d343591faffe46 +a69becee9c93734805523b92150a59a62eed4934f66056b645728740d42223f2925a1ad38359ba644da24d9414f4cdda +ad72f0f1305e37c7e6b48c272323ee883320994cb2e0d850905d6655fafc9f361389bcb9c66b3ff8d2051dbb58c8aa96 +b563600bd56fad7c8853af21c6a02a16ed9d8a8bbeea2c31731d63b976d83cb05b9779372d898233e8fd597a75424797 +b0abb78ce465bf7051f563c62e8be9c57a2cc997f47c82819300f36e301fefd908894bb2053a9d27ce2d0f8c46d88b5b +a071a85fb8274bac2202e0cb8e0e2028a5e138a82d6e0374d39ca1884a549c7c401312f00071b91f455c3a2afcfe0cda +b931c271513a0f267b9f41444a5650b1918100b8f1a64959c552aff4e2193cc1b9927906c6fa7b8a8c68ef13d79aaa52 +a6a1bb9c7d32cb0ca44d8b75af7e40479fbce67d216b48a2bb680d3f3a772003a49d3cd675fc64e9e0f8fabeb86d6d61 +b98d609858671543e1c3b8564162ad828808bb50ded261a9f8690ded5b665ed8368c58f947365ed6e84e5a12e27b423d +b3dca58cd69ec855e2701a1d66cad86717ff103ef862c490399c771ad28f675680f9500cb97be48de34bcdc1e4503ffd +b34867c6735d3c49865e246ddf6c3b33baf8e6f164db3406a64ebce4768cb46b0309635e11be985fee09ab7a31d81402 +acb966c554188c5b266624208f31fab250b3aa197adbdd14aee5ab27d7fb886eb4350985c553b20fdf66d5d332bfd3fe +943c36a18223d6c870d54c3b051ef08d802b85e9dd6de37a51c932f90191890656c06adfa883c87b906557ae32d09da0 +81bca7954d0b9b6c3d4528aadf83e4bc2ef9ea143d6209bc45ae9e7ae9787dbcd8333c41f12c0b6deee8dcb6805e826a +aba176b92256efb68f574e543479e5cf0376889fb48e3db4ebfb7cba91e4d9bcf19dcfec444c6622d9398f06de29e2b9 +b9f743691448053216f6ece7cd699871fff4217a1409ceb8ab7bdf3312d11696d62c74b0664ba0a631b1e0237a8a0361 +a383c2b6276fa9af346b21609326b53fb14fdf6f61676683076e80f375b603645f2051985706d0401e6fbed7eb0666b6 +a9ef2f63ec6d9beb8f3d04e36807d84bda87bdd6b351a3e4a9bf7edcb5618c46c1f58cfbf89e64b40f550915c6988447 +a141b2d7a82f5005eaea7ae7d112c6788b9b95121e5b70b7168d971812f3381de8b0082ac1f0a82c7d365922ebd2d26a +b1b76ef8120e66e1535c17038b75255a07849935d3128e3e99e56567b842fb1e8d56ef932d508d2fb18b82f7868fe1a9 +8e2e234684c81f21099f5c54f6bbe2dd01e3b172623836c77668a0c49ce1fe218786c3827e4d9ae2ea25c50a8924fb3c +a5caf5ff948bfd3c4ca3ffbdfcd91eec83214a6c6017235f309a0bbf7061d3b0b466307c00b44a1009cf575163898b43 +986415a82ca16ebb107b4c50b0c023c28714281db0bcdab589f6cb13d80e473a3034b7081b3c358e725833f6d845cb14 +b94836bf406ac2cbacb10e6df5bcdfcc9d9124ae1062767ca4e322d287fd5e353fdcebd0e52407cb3cd68571258a8900 +83c6d70a640b33087454a4788dfd9ef3ed00272da084a8d36be817296f71c086b23b576f98178ab8ca6a74f04524b46b +ad4115182ad784cfe11bcfc5ce21fd56229cc2ce77ac82746e91a2f0aa53ca6593a22efd2dc4ed8d00f84542643d9c58 +ab1434c5e5065da826d10c2a2dba0facccab0e52b506ce0ce42fbe47ced5a741797151d9ecc99dc7d6373cfa1779bbf6 +8a8b591d82358d55e6938f67ea87a89097ab5f5496f7260adb9f649abb289da12b498c5b2539c2f9614fb4e21b1f66b0 +964f355d603264bc1f44c64d6d64debca66f37dff39c971d9fc924f2bc68e6c187b48564a6dc82660a98b035f8addb5d +b66235eaaf47456bc1dc4bde454a028e2ce494ece6b713a94cd6bf27cf18c717fd0c57a5681caaa2ad73a473593cdd7a +9103e3bb74304186fa4e3e355a02da77da4aca9b7e702982fc2082af67127ebb23a455098313c88465bc9b7d26820dd5 +b6a42ff407c9dd132670cdb83cbad4b20871716e44133b59a932cd1c3f97c7ac8ff7f61acfaf8628372508d8dc8cad7c +883a9c21c16a167a4171b0f084565c13b6f28ba7c4977a0de69f0a25911f64099e7bbb4da8858f2e93068f4155d04e18 +8dbb3220abc6a43220adf0331e3903d3bfd1d5213aadfbd8dfcdf4b2864ce2e96a71f35ecfb7a07c3bbabf0372b50271 +b4ad08aee48e176bda390b7d9acf2f8d5eb008f30d20994707b757dc6a3974b2902d29cd9b4d85e032810ad25ac49e97 +865bb0f33f7636ec501bb634e5b65751c8a230ae1fa807a961a8289bbf9c7fe8c59e01fbc4c04f8d59b7f539cf79ddd5 +86a54d4c12ad1e3605b9f93d4a37082fd26e888d2329847d89afa7802e815f33f38185c5b7292293d788ad7d7da1df97 +b26c8615c5e47691c9ff3deca3021714662d236c4d8401c5d27b50152ce7e566266b9d512d14eb63e65bc1d38a16f914 +827639d5ce7db43ba40152c8a0eaad443af21dc92636cc8cc2b35f10647da7d475a1e408901cd220552fddad79db74df +a2b79a582191a85dbe22dc384c9ca3de345e69f6aa370aa6d3ff1e1c3de513e30b72df9555b15a46586bd27ea2854d9d +ae0d74644aba9a49521d3e9553813bcb9e18f0b43515e4c74366e503c52f47236be92dfbd99c7285b3248c267b1de5a0 +80fb0c116e0fd6822a04b9c25f456bdca704e2be7bdc5d141dbf5d1c5eeb0a2c4f5d80db583b03ef3e47517e4f9a1b10 +ac3a1fa3b4a2f30ea7e0a114cdc479eb51773573804c2a158d603ad9902ae8e39ffe95df09c0d871725a5d7f9ba71a57 +b56b2b0d601cba7f817fa76102c68c2e518c6f20ff693aad3ff2e07d6c4c76203753f7f91686b1801e8c4659e4d45c48 +89d50c1fc56e656fb9d3915964ebce703cb723fe411ab3c9eaa88ccc5d2b155a9b2e515363d9c600d3c0cee782c43f41 +b24207e61462f6230f3cd8ccf6828357d03e725769f7d1de35099ef9ee4dca57dbce699bb49ed994462bee17059d25ce +b886f17fcbcbfcd08ac07f04bb9543ef58510189decaccea4b4158c9174a067cb67d14b6be3c934e6e2a18c77efa9c9c +b9c050ad9cafd41c6e2e192b70d080076eed59ed38ea19a12bd92fa17b5d8947d58d5546aaf5e8e27e1d3b5481a6ce51 +aaf7a34d3267e3b1ddbc54c641e3922e89303f7c86ebebc7347ebca4cffad5b76117dac0cbae1a133053492799cd936f +a9ee604ada50adef82e29e893070649d2d4b7136cc24fa20e281ce1a07bd736bf0de7c420369676bcbcecff26fb6e900 +9855315a12a4b4cf80ab90b8bd13003223ba25206e52fd4fe6a409232fbed938f30120a3db23eab9c53f308bd8b9db81 +8cd488dd7a24f548a3cf03c54dec7ff61d0685cb0f6e5c46c2d728e3500d8c7bd6bba0156f4bf600466fda53e5b20444 +890ad4942ebac8f5b16c777701ab80c68f56fa542002b0786f8fea0fb073154369920ac3dbfc07ea598b82f4985b8ced +8de0cf9ddc84c9b92c59b9b044387597799246b30b9f4d7626fc12c51f6e423e08ee4cbfe9289984983c1f9521c3e19d +b474dfb5b5f4231d7775b3c3a8744956b3f0c7a871d835d7e4fd9cc895222c7b868d6c6ce250de568a65851151fac860 +86433b6135d9ed9b5ee8cb7a6c40e5c9d30a68774cec04988117302b8a02a11a71a1e03fd8e0264ef6611d219f103007 +80b9ed4adbe9538fb1ef69dd44ec0ec5b57cbfea820054d8d445b4261962624b4c70ac330480594bc5168184378379c3 +8b2e83562ccd23b7ad2d17f55b1ab7ef5fbef64b3a284e6725b800f3222b8bdf49937f4a873917ada9c4ddfb090938c2 +abe78cebc0f5a45d754140d1f685e387489acbfa46d297a8592aaa0d676a470654f417a4f7d666fc0b2508fab37d908e +a9c5f8ff1f8568e252b06d10e1558326db9901840e6b3c26bbd0cd5e850cb5fb3af3f117dbb0f282740276f6fd84126f +975f8dc4fb55032a5df3b42b96c8c0ffecb75456f01d4aef66f973cb7270d4eff32c71520ceefc1adcf38d77b6b80c67 +b043306ed2c3d8a5b9a056565afd8b5e354c8c4569fda66b0d797a50a3ce2c08cffbae9bbe292da69f39e89d5dc7911e +8d2afc36b1e44386ba350c14a6c1bb31ff6ea77128a0c5287584ac3584282d18516901ce402b4644a53db1ed8e7fa581 +8c294058bed53d7290325c363fe243f6ec4f4ea2343692f4bac8f0cb86f115c069ccb8334b53d2e42c067691ad110dba +b92157b926751aaf7ef82c1aa8c654907dccab6376187ee8b3e8c0c82811eae01242832de953faa13ebaff7da8698b3e +a780c4bdd9e4ba57254b09d745075cecab87feda78c88ffee489625c5a3cf96aa6b3c9503a374a37927d9b78de9bd22b +811f548ef3a2e6a654f7dcb28ac9378de9515ed61e5a428515d9594a83e80b35c60f96a5cf743e6fab0d3cb526149f49 +85a4dccf6d90ee8e094731eec53bd00b3887aec6bd81a0740efddf812fd35e3e4fe4f983afb49a8588691c202dabf942 +b152c2da6f2e01c8913079ae2b40a09b1f361a80f5408a0237a8131b429677c3157295e11b365b1b1841924b9efb922e +849b9efee8742502ffd981c4517c88ed33e4dd518a330802caff168abae3cd09956a5ee5eda15900243bc2e829016b74 +955a933f3c18ec0f1c0e38fa931e4427a5372c46a3906ebe95082bcf878c35246523c23f0266644ace1fa590ffa6d119 +911989e9f43e580c886656377c6f856cdd4ff1bd001b6db3bbd86e590a821d34a5c6688a29b8d90f28680e9fdf03ba69 +b73b8b4f1fd6049fb68d47cd96a18fcba3f716e0a1061aa5a2596302795354e0c39dea04d91d232aec86b0bf2ba10522 +90f87456d9156e6a1f029a833bf3c7dbed98ca2f2f147a8564922c25ae197a55f7ea9b2ee1f81bf7383197c4bad2e20c +903cba8b1e088574cb04a05ca1899ab00d8960580c884bd3c8a4c98d680c2ad11410f2b75739d6050f91d7208cac33a5 +9329987d42529c261bd15ecedd360be0ea8966e7838f32896522c965adfc4febf187db392bd441fb43bbd10c38fdf68b +8178ee93acf5353baa349285067b20e9bb41aa32d77b5aeb7384fe5220c1fe64a2461bd7a83142694fe673e8bbf61b7c +a06a8e53abcff271b1394bcc647440f81fb1c1a5f29c27a226e08f961c3353f4891620f2d59b9d1902bf2f5cc07a4553 +aaf5fe493b337810889e777980e6bbea6cac39ac66bc0875c680c4208807ac866e9fda9b5952aa1d04539b9f4a4bec57 +aa058abb1953eceac14ccfa7c0cc482a146e1232905dcecc86dd27f75575285f06bbae16a8c9fe8e35d8713717f5f19f +8f15dd732799c879ca46d2763453b359ff483ca33adb1d0e0a57262352e0476c235987dc3a8a243c74bc768f93d3014c +a61cc8263e9bc03cce985f1663b8a72928a607121005a301b28a278e9654727fd1b22bc8a949af73929c56d9d3d4a273 +98d6dc78502d19eb9f921225475a6ebcc7b44f01a2df6f55ccf6908d65b27af1891be2a37735f0315b6e0f1576c1f8d8 +8bd258b883f3b3793ec5be9472ad1ff3dc4b51bc5a58e9f944acfb927349ead8231a523cc2175c1f98e7e1e2b9f363b8 +aeacc2ecb6e807ad09bedd99654b097a6f39840e932873ace02eabd64ccfbb475abdcb62939a698abf17572d2034c51e +b8ccf78c08ccd8df59fd6eda2e01de328bc6d8a65824d6f1fc0537654e9bc6bf6f89c422dd3a295cce628749da85c864 +8f91fd8cb253ba2e71cc6f13da5e05f62c2c3b485c24f5d68397d04665673167fce1fc1aec6085c69e87e66ec555d3fd +a254baa10cb26d04136886073bb4c159af8a8532e3fd36b1e9c3a2e41b5b2b6a86c4ebc14dbe624ee07b7ccdaf59f9ab +94e3286fe5cd68c4c7b9a7d33ae3d714a7f265cf77cd0e9bc19fc51015b1d1c34ad7e3a5221c459e89f5a043ee84e3a9 +a279da8878af8d449a9539bec4b17cea94f0242911f66fab275b5143ab040825f78c89cb32a793930609415cfa3a1078 +ac846ceb89c9e5d43a2991c8443079dc32298cd63e370e64149cec98cf48a6351c09c856f2632fd2f2b3d685a18bbf8b +a847b27995c8a2e2454aaeb983879fb5d3a23105c33175839f7300b7e1e8ec3efd6450e9fa3f10323609dee7b98c6fd5 +a2f432d147d904d185ff4b2de8c6b82fbea278a2956bc406855b44c18041854c4f0ecccd472d1d0dff1d8aa8e281cb1d +94a48ad40326f95bd63dff4755f863a1b79e1df771a1173b17937f9baba57b39e651e7695be9f66a472f098b339364fc +a12a0ccd8f96e96e1bc6494341f7ebce959899341b3a084aa1aa87d1c0d489ac908552b7770b887bb47e7b8cbc3d8e66 +81a1f1681bda923bd274bfe0fbb9181d6d164fe738e54e25e8d4849193d311e2c4253614ed673c98af2c798f19a93468 +abf71106a05d501e84cc54610d349d7d5eae21a70bd0250f1bebbf412a130414d1c8dbe673ffdb80208fd72f1defa4d4 +96266dc2e0df18d8136d79f5b59e489978eee0e6b04926687fe389d4293c14f36f055c550657a8e27be4118b64254901 +8df5dcbefbfb4810ae3a413ca6b4bf08619ca53cd50eb1dde2a1c035efffc7b7ac7dff18d403253fd80104bd83dc029e +9610b87ff02e391a43324a7122736876d5b3af2a137d749c52f75d07b17f19900b151b7f439d564f4529e77aa057ad12 +a90a5572198b40fe2fcf47c422274ff36c9624df7db7a89c0eb47eb48a73a03c985f4ac5016161c76ca317f64339bce1 +98e5e61a6ab6462ba692124dba7794b6c6bde4249ab4fcc98c9edd631592d5bc2fb5e38466691a0970a38e48d87c2e43 +918cefb8f292f78d4db81462c633daf73b395e772f47b3a7d2cea598025b1d8c3ec0cbff46cdb23597e74929981cde40 +a98918a5dc7cf610fe55f725e4fd24ce581d594cb957bb9b4e888672e9c0137003e1041f83e3f1d7b9caab06462c87d4 +b92b74ac015262ca66c33f2d950221e19d940ba3bf4cf17845f961dc1729ae227aa9e1f2017829f2135b489064565c29 +a053ee339f359665feb178b4e7ee30a85df37debd17cacc5a27d6b3369d170b0114e67ad1712ed26d828f1df641bcd99 +8c3c8bad510b35da5ce5bd84b35c958797fbea024ad1c97091d2ff71d9b962e9222f65a9b776e5b3cc29c36e1063d2ee +af99dc7330fe7c37e850283eb47cc3257888e7c197cb0d102edf94439e1e02267b6a56306d246c326c4c79f9dc8c6986 +afecb2dc34d57a725efbd7eb93d61eb29dbe8409b668ab9ea040791f5b796d9be6d4fc10d7f627bf693452f330cf0435 +93334fedf19a3727a81a6b6f2459db859186227b96fe7a391263f69f1a0884e4235de64d29edebc7b99c44d19e7c7d7a +89579c51ac405ad7e9df13c904061670ce4b38372492764170e4d3d667ed52e5d15c7cd5c5991bbfa3a5e4e3fa16363e +9778f3e8639030f7ef1c344014f124e375acb8045bd13d8e97a92c5265c52de9d1ffebaa5bc3e1ad2719da0083222991 +88f77f34ee92b3d36791bdf3326532524a67d544297dcf1a47ff00b47c1b8219ff11e34034eab7d23b507caa2fd3c6b9 +a699c1e654e7c484431d81d90657892efeb4adcf72c43618e71ca7bd7c7a7ebbb1db7e06e75b75dc4c74efd306b5df3f +81d13153baebb2ef672b5bdb069d3cd669ce0be96b742c94e04038f689ff92a61376341366b286eee6bf3ae85156f694 +81efb17de94400fdacc1deec2550cbe3eecb27c7af99d8207e2f9be397e26be24a40446d2a09536bb5172c28959318d9 +989b21ebe9ceab02488992673dc071d4d5edec24bff0e17a4306c8cb4b3c83df53a2063d1827edd8ed16d6e837f0d222 +8d6005d6536825661b13c5fdce177cb37c04e8b109b7eb2b6d82ea1cb70efecf6a0022b64f84d753d165edc2bba784a3 +a32607360a71d5e34af2271211652d73d7756d393161f4cf0da000c2d66a84c6826e09e759bd787d4fd0305e2439d342 +aaad8d6f6e260db45d51b2da723be6fa832e76f5fbcb77a9a31e7f090dd38446d3b631b96230d78208cae408c288ac4e +abcfe425255fd3c5cffd3a818af7650190c957b6b07b632443f9e33e970a8a4c3bf79ac9b71f4d45f238a04d1c049857 +aeabf026d4c783adc4414b5923dbd0be4b039cc7201219f7260d321f55e9a5b166d7b5875af6129c034d0108fdc5d666 +af49e740c752d7b6f17048014851f437ffd17413c59797e5078eaaa36f73f0017c3e7da020310cfe7d3c85f94a99f203 +8854ca600d842566e3090040cd66bb0b3c46dae6962a13946f0024c4a8aca447e2ccf6f240045f1ceee799a88cb9210c +b6c03b93b1ab1b88ded8edfa1b487a1ed8bdce8535244dddb558ffb78f89b1c74058f80f4db2320ad060d0c2a9c351cc +b5bd7d17372faff4898a7517009b61a7c8f6f0e7ed4192c555db264618e3f6e57fb30a472d169fea01bf2bf0362a19a8 +96eb1d38319dc74afe7e7eb076fcd230d19983f645abd14a71e6103545c01301b31c47ae931e025f3ecc01fb3d2f31fa +b55a8d30d4403067def9b65e16f867299f8f64c9b391d0846d4780bc196569622e7e5b64ce799b5aefac8f965b2a7a7b +8356d199a991e5cbbff608752b6291731b6b6771aed292f8948b1f41c6543e4ab1bedc82dd26d10206c907c03508df06 +97f4137445c2d98b0d1d478049de952610ad698c91c9d0f0e7227d2aae690e9935e914ec4a2ea1fbf3fc1dddfeeacebb +af5621707e0938320b15ddfc87584ab325fbdfd85c30efea36f8f9bd0707d7ec12c344eff3ec21761189518d192df035 +8ac7817e71ea0825b292687928e349da7140285d035e1e1abff0c3704fa8453faaae343a441b7143a74ec56539687cc4 +8a5e0a9e4758449489df10f3386029ada828d1762e4fb0a8ffe6b79e5b6d5d713cb64ed95960e126398b0cdb89002bc9 +81324be4a71208bbb9bca74b77177f8f1abb9d3d5d9db195d1854651f2cf333cd618d35400da0f060f3e1b025124e4b2 +849971d9d095ae067525b3cbc4a7dfae81f739537ade6d6cec1b42fb692d923176197a8770907c58069754b8882822d6 +89f830825416802477cc81fdf11084885865ee6607aa15aa4eb28e351c569c49b8a1b9b5e95ddc04fa0ebafe20071313 +9240aeeaff37a91af55f860b9badd466e8243af9e8c96a7aa8cf348cd270685ab6301bc135b246dca9eda696f8b0e350 +acf74db78cc33138273127599eba35b0fb4e7b9a69fe02dae18fc6692d748ca332bd00b22afa8e654ed587aab11833f3 +b091e6d37b157b50d76bd297ad752220cd5c9390fac16dc838f8557aed6d9833fc920b61519df21265406216315e883f +a6446c429ebf1c7793c622250e23594c836b2fbcaf6c5b3d0995e1595a37f50ea643f3e549b0be8bbdadd69044d72ab9 +93e675353bd60e996bf1c914d5267eeaa8a52fc3077987ccc796710ef9becc6b7a00e3d82671a6bdfb8145ee3c80245a +a2f731e43251d04ed3364aa2f072d05355f299626f2d71a8a38b6f76cf08c544133f7d72dd0ab4162814b674b9fc7fa6 +97a8b791a5a8f6e1d0de192d78615d73d0c38f1e557e4e15d15adc663d649e655bc8da3bcc499ef70112eafe7fb45c7a +98cd624cbbd6c53a94469be4643c13130916b91143425bcb7d7028adbbfede38eff7a21092af43b12d4fab703c116359 +995783ce38fd5f6f9433027f122d4cf1e1ff3caf2d196ce591877f4a544ce9113ead60de2de1827eaff4dd31a20d79a8 +8cf251d6f5229183b7f3fe2f607a90b4e4b6f020fb4ba2459d28eb8872426e7be8761a93d5413640a661d73e34a5b81f +b9232d99620652a3aa7880cad0876f153ff881c4ed4c0c2e7b4ea81d5d42b70daf1a56b869d752c3743c6d4c947e6641 +849716f938f9d37250cccb1bf77f5f9fde53096cdfc6f2a25536a6187029a8f1331cdbed08909184b201f8d9f04b792f +80c7c4de098cbf9c6d17b14eba1805e433b5bc905f6096f8f63d34b94734f2e4ebf4bce8a177efd1186842a61204a062 +b790f410cf06b9b8daadceeb4fd5ff40a2deda820c8df2537e0a7554613ae3948e149504e3e79aa84889df50c8678eeb +813aab8bd000299cd37485b73cd7cba06e205f8efb87f1efc0bae8b70f6db2bc7702eb39510ad734854fb65515fe9d0f +94f0ab7388ac71cdb67f6b85dfd5945748afb2e5abb622f0b5ad104be1d4d0062b651f134ba22385c9e32c2dfdcccce1 +ab6223dca8bd6a4f969e21ccd9f8106fc5251d321f9e90cc42cea2424b3a9c4e5060a47eeef6b23c7976109b548498e8 +859c56b71343fce4d5c5b87814c47bf55d581c50fd1871a17e77b5e1742f5af639d0e94d19d909ec7dfe27919e954e0c +aae0d632b6191b8ad71b027791735f1578e1b89890b6c22e37de0e4a6074886126988fe8319ae228ac9ef3b3bcccb730 +8ca9f32a27a024c3d595ecfaf96b0461de57befa3b331ab71dc110ec3be5824fed783d9516597537683e77a11d334338 +a061df379fb3f4b24816c9f6cd8a94ecb89b4c6dc6cd81e4b8096fa9784b7f97ab3540259d1de9c02eb91d9945af4823 +998603102ac63001d63eb7347a4bb2bf4cf33b28079bb48a169076a65c20d511ccd3ef696d159e54cc8e772fb5d65d50 +94444d96d39450872ac69e44088c252c71f46be8333a608a475147752dbb99db0e36acfc5198f158509401959c12b709 +ac1b51b6c09fe055c1d7c9176eea9adc33f710818c83a1fbfa073c8dc3a7eb3513cbdd3f5960b7845e31e3e83181e6ba +803d530523fc9e1e0f11040d2412d02baef3f07eeb9b177fa9bfa396af42eea898a4276d56e1db998dc96ae47b644cb2 +85a3c9fc7638f5bf2c3e15ba8c2fa1ae87eb1ceb44c6598c67a2948667a9dfa41e61f66d535b4e7fda62f013a5a8b885 +a961cf5654c46a1a22c29baf7a4e77837a26b7f138f410e9d1883480ed5fa42411d522aba32040b577046c11f007388e +ad1154142344f494e3061ef45a34fab1aaacf5fdf7d1b26adbb5fbc3d795655fa743444e39d9a4119b4a4f82a6f30441 +b1d6c30771130c77806e7ab893b73d4deb590b2ff8f2f8b5e54c2040c1f3e060e2bd99afc668cf706a2df666a508bbf6 +a00361fd440f9decabd98d96c575cd251dc94c60611025095d1201ef2dedde51cb4de7c2ece47732e5ed9b3526c2012c +a85c5ab4d17d328bda5e6d839a9a6adcc92ff844ec25f84981e4f44a0e8419247c081530f8d9aa629c7eb4ca21affba6 +a4ddd3eab4527a2672cf9463db38bc29f61460e2a162f426b7852b7a7645fbd62084fd39a8e4d60e1958cce436dd8f57 +811648140080fe55b8618f4cf17f3c5a250adb0cd53d885f2ddba835d2b4433188e41fc0661faac88e4ff910b16278c0 +b85c7f1cfb0ed29addccf7546023a79249e8f15ac2d14a20accbfef4dd9dc11355d599815fa09d2b6b4e966e6ea8cff1 +a10b5d8c260b159043b020d5dd62b3467df2671afea6d480ca9087b7e60ed170c82b121819d088315902842d66c8fb45 +917e191df1bcf3f5715419c1e2191da6b8680543b1ba41fe84ed07ef570376e072c081beb67b375fca3565a2565bcabb +881fd967407390bfd7badc9ab494e8a287559a01eb07861f527207c127eadea626e9bcc5aa9cca2c5112fbac3b3f0e9c +959fd71149af82cc733619e0e5bf71760ca2650448c82984b3db74030d0e10f8ab1ce1609a6de6f470fe8b5bd90df5b3 +a3370898a1c5f33d15adb4238df9a6c945f18b9ada4ce2624fc32a844f9ece4c916a64e9442225b6592afa06d2e015f2 +817efb8a791435e4236f7d7b278181a5fa34587578c629dbc14fbf9a5c26772290611395eecd20222a4c58649fc256d8 +a04c9876acf2cfdc8ef96de4879742709270fa1d03fe4c8511fbef2d59eb0aaf0336fa2c7dfe41a651157377fa217813 +81e15875d7ea7f123e418edf14099f2e109d4f3a6ce0eb65f67fe9fb10d2f809a864a29f60ad3fc949f89e2596b21783 +b49f529975c09e436e6bc202fdc16e3fdcbe056db45178016ad6fdece9faad4446343e83aed096209690b21a6910724f +879e8eda589e1a279f7f49f6dd0580788c040d973748ec4942dbe51ea8fbd05983cc919b78f0c6b92ef3292ae29db875 +81a2b74b2118923f34139a102f3d95e7eee11c4c2929c2576dee200a5abfd364606158535a6c9e4178a6a83dbb65f3c4 +8913f281d8927f2b45fc815d0f7104631cb7f5f7278a316f1327d670d15868daadd2a64e3eb98e1f53fe7e300338cc80 +a6f815fba7ef9af7fbf45f93bc952e8b351f5de6568a27c7c47a00cb39a254c6b31753794f67940fc7d2e9cc581529f4 +b3722a15c66a0014ce4d082de118def8d39190c15678a472b846225585f3a83756ae1b255b2e3f86a26168878e4773b2 +817ae61ab3d0dd5b6e24846b5a5364b1a7dc2e77432d9fed587727520ae2f307264ea0948c91ad29f0aea3a11ff38624 +b3db467464415fcad36dc1de2d6ba7686772a577cc2619242ac040d6734881a45d3b40ed4588db124e4289cfeec4bbf6 +ad66a14f5a54ac69603b16e5f1529851183da77d3cc60867f10aea41339dd5e06a5257982e9e90a352cdd32750f42ee4 +adafa3681ef45d685555601a25a55cf23358319a17f61e2179e704f63df83a73bdd298d12cf6cef86db89bd17119e11d +a379dc44cb6dd3b9d378c07b2ec654fec7ca2f272de6ba895e3d00d20c9e4c5550498a843c8ac67e4221db2115bedc1c +b7bf81c267a78efc6b9e5a904574445a6487678d7ef70054e3e93ea6a23f966c2b68787f9164918e3b16d2175459ed92 +b41d66a13a4afafd5760062b77f79de7e6ab8ccacde9c6c5116a6d886912fb491dc027af435b1b44aacc6af7b3c887f2 +9904d23a7c1c1d2e4bab85d69f283eb0a8e26d46e8b7b30224438015c936729b2f0af7c7c54c03509bb0500acb42d8a4 +ae30d65e9e20c3bfd603994ae2b175ff691d51f3e24b2d058b3b8556d12ca4c75087809062dddd4aaac81c94d15d8a17 +9245162fab42ac01527424f6013310c3eb462982518debef6c127f46ba8a06c705d7dc9f0a41e796ba8d35d60ae6cc64 +87fab853638d7a29a20f3ba2b1a7919d023e9415bfa78ebb27973d8cbc7626f584dc5665d2e7ad71f1d760eba9700d88 +85aac46ecd330608e5272430970e6081ff02a571e8ea444f1e11785ea798769634a22a142d0237f67b75369d3c484a8a +938c85ab14894cc5dfce3d80456f189a2e98eddbc8828f4ff6b1df1dcb7b42b17ca2ff40226a8a1390a95d63dca698dd +a18ce1f846e3e3c4d846822f60271eecf0f5d7d9f986385ac53c5ace9589dc7c0188910448c19b91341a1ef556652fa9 +8611608a9d844f0e9d7584ad6ccf62a5087a64f764caf108db648a776b5390feb51e5120f0ef0e9e11301af3987dd7dc +8106333ba4b4de8d1ae43bc9735d3fea047392e88efd6a2fa6f7b924a18a7a265ca6123c3edc0f36307dd7fb7fe89257 +a91426fa500951ff1b051a248c050b7139ca30dde8768690432d597d2b3c4357b11a577be6b455a1c5d145264dcf81fc +b7f9f90e0e450f37b081297f7f651bad0496a8b9afd2a4cf4120a2671aaaa8536dce1af301258bfbfdb122afa44c5048 +84126da6435699b0c09fa4032dec73d1fca21d2d19f5214e8b0bea43267e9a8dd1fc44f8132d8315e734c8e2e04d7291 +aff064708103884cb4f1a3c1718b3fc40a238d35cf0a7dc24bdf9823693b407c70da50df585bf5bc4e9c07d1c2d203e8 +a8b40fc6533752983a5329c31d376c7a5c13ce6879cc7faee648200075d9cd273537001fb4c86e8576350eaac6ba60c2 +a02db682bdc117a84dcb9312eb28fcbde12d49f4ce915cc92c610bb6965ec3cc38290f8c5b5ec70afe153956692cda95 +86decd22b25d300508472c9ce75d3e465b737e7ce13bc0fcce32835e54646fe12322ba5bc457be18bfd926a1a6ca4a38 +a18666ef65b8c2904fd598791f5627207165315a85ee01d5fb0e6b2e10bdd9b00babc447da5bd63445e3337de33b9b89 +89bb0c06effadefdaf34ffe4b123e1678a90d4451ee856c863df1e752eef41fd984689ded8f0f878bf8916d5dd8e8024 +97cfcba08ebec05d0073992a66b1d7d6fb9d95871f2cdc36db301f78bf8069294d1c259efef5c93d20dc937eedae3a1a +ac2643b14ece79dcb2e289c96776a47e2bebd40dd6dc74fd035df5bb727b5596f40e3dd2d2202141e69b0993717ede09 +a5e6fd88a2f9174d9bd4c6a55d9c30974be414992f22aa852f552c7648f722ed8077acf5aba030abd47939bb451b2c60 +8ad40a612824a7994487731a40b311b7349038c841145865539c6ada75c56de6ac547a1c23df190e0caaafecddd80ccc +953a7cea1d857e09202c438c6108060961f195f88c32f0e012236d7a4b39d840c61b162ec86436e8c38567328bea0246 +80d8b47a46dae1868a7b8ccfe7029445bbe1009dad4a6c31f9ef081be32e8e1ac1178c3c8fb68d3e536c84990cc035b1 +81ecd99f22b3766ce0aca08a0a9191793f68c754fdec78b82a4c3bdc2db122bbb9ebfd02fc2dcc6e1567a7d42d0cc16a +b1dd0446bccc25846fb95d08c1c9cc52fb51c72c4c5d169ffde56ecfe800f108dc1106d65d5c5bd1087c656de3940b63 +b87547f0931e164e96de5c550ca5aa81273648fe34f6e193cd9d69cf729cb432e17aa02e25b1c27a8a0d20a3b795e94e +820a94e69a927e077082aae66f6b292cfbe4589d932edf9e68e268c9bd3d71ef76cf7d169dd445b93967c25db11f58f1 +b0d07ddf2595270c39adfa0c8cf2ab1322979b0546aa4d918f641be53cd97f36c879bb75d205e457c011aca3bbd9f731 +8700b876b35b4b10a8a9372c5230acecd39539c1bb87515640293ad4464a9e02929d7d6a6a11112e8a29564815ac0de4 +a61a601c5bb27dcb97e37c8e2b9ce479c6b192a5e04d9ed5e065833c5a1017ee5f237b77d1a17be5d48f8e7cc0bcacf6 +92fb88fe774c1ba1d4a08cae3c0e05467ad610e7a3f1d2423fd47751759235fe0a3036db4095bd6404716aa03820f484 +b274f140d77a3ce0796f5e09094b516537ccaf27ae1907099bff172e6368ba85e7c3ef8ea2a07457cac48ae334da95b3 +b2292d9181f16581a9a9142490b2bdcdfb218ca6315d1effc8592100d792eb89d5356996c890441f04f2b4a95763503e +8897e73f576d86bc354baa3bd96e553107c48cf5889dcc23c5ba68ab8bcd4e81f27767be2233fdfa13d39f885087e668 +a29eac6f0829791c728d71abc49569df95a4446ecbfc534b39f24f56c88fe70301838dfc1c19751e7f3c5c1b8c6af6a0 +9346dc3720adc5df500a8df27fd9c75ef38dc5c8f4e8ed66983304750e66d502c3c59b8e955be781b670a0afc70a2167 +9566d534e0e30a5c5f1428665590617e95fd05d45f573715f58157854ad596ece3a3cfec61356aee342308d623e029d5 +a464fb8bffe6bd65f71938c1715c6e296cc6d0311a83858e4e7eb5873b7f2cf0c584d2101e3407b85b64ca78b2ac93ce +b54088f7217987c87e9498a747569ac5b2f8afd5348f9c45bf3fd9fbf713a20f495f49c8572d087efe778ac7313ad6d3 +91fa9f5f8000fe050f5b224d90b59fcce13c77e903cbf98ded752e5b3db16adb2bc1f8c94be48b69f65f1f1ad81d6264 +92d04a5b0ac5d8c8e313709b432c9434ecd3e73231f01e9b4e7952b87df60cbfa97b5dedd2200bd033b4b9ea8ba45cc1 +a94b90ad3c3d6c4bbe169f8661a790c40645b40f0a9d1c7220f01cf7fc176e04d80bab0ced9323fcafb93643f12b2760 +94d86149b9c8443b46196f7e5a3738206dd6f3be7762df488bcbb9f9ee285a64c997ed875b7b16b26604fa59020a8199 +82efe4ae2c50a2d7645240c173a047f238536598c04a2c0b69c96e96bd18e075a99110f1206bc213f39edca42ba00cc1 +ab8667685f831bc14d4610f84a5da27b4ea5b133b4d991741a9e64dceb22cb64a3ce8f1b6e101d52af6296df7127c9ad +83ba433661c05dcc5d562f4a9a261c8110dac44b8d833ae1514b1fc60d8b4ee395b18804baea04cb10adb428faf713c3 +b5748f6f660cc5277f1211d2b8649493ed8a11085b871cd33a5aea630abd960a740f08c08be5f9c21574600ac9bf5737 +a5c8dd12af48fb710642ad65ebb97ca489e8206741807f7acfc334f8035d3c80593b1ff2090c9bb7bd138f0c48714ca8 +a2b382fd5744e3babf454b1d806cc8783efeb4761bc42b6914ea48a46a2eae835efbe0a18262b6bc034379e03cf1262b +b3145ffaf603f69f15a64936d32e3219eea5ed49fdfd2f5bf40ea0dfd974b36fb6ff12164d4c2282d892db4cf3ff3ce1 +87a316fb213f4c5e30c5e3face049db66be4f28821bd96034714ec23d3e97849d7b301930f90a4323c7ccf53de23050c +b9de09a919455070fed6220fc179c8b7a4c753062bcd27acf28f5b9947a659c0b364298daf7c85c4ca6fca7f945add1f +806fbd98d411b76979464c40ad88bc07a151628a27fcc1012ba1dfbaf5b5cc9d962fb9b3386008978a12515edce934bc +a15268877fae0d21610ae6a31061ed7c20814723385955fac09fdc9693a94c33dea11db98bb89fdfe68f933490f5c381 +8d633fb0c4da86b2e0b37d8fad5972d62bff2ac663c5ec815d095cd4b7e1fe66ebef2a2590995b57eaf941983c7ad7a4 +8139e5dd9cf405e8ef65f11164f0440827d98389ce1b418b0c9628be983a9ddd6cf4863036ccb1483b40b8a527acd9ed +88b15fa94a08eac291d2b94a2b30eb851ff24addf2cc30b678e72e32cfcb3424cf4b33aa395d741803f3e578ddf524de +b5eaf0c8506e101f1646bcf049ee38d99ea1c60169730da893fd6020fd00a289eb2f415947e44677af49e43454a7b1be +8489822ad0647a7e06aa2aa5595960811858ddd4542acca419dd2308a8c5477648f4dd969a6740bb78aa26db9bfcc555 +b1e9a7b9f3423c220330d45f69e45fa03d7671897cf077f913c252e3e99c7b1b1cf6d30caad65e4228d5d7b80eb86e5e +b28fe9629592b9e6a55a1406903be76250b1c50c65296c10c5e48c64b539fb08fe11f68cf462a6edcbba71b0cee3feb2 +a41acf96a02c96cd8744ff6577c244fc923810d17ade133587e4c223beb7b4d99fa56eae311a500d7151979267d0895c +880798938fe4ba70721be90e666dfb62fcab4f3556fdb7b0dc8ec5bc34f6b4513df965eae78527136eb391889fe2caf9 +98d4d89d358e0fb7e212498c73447d94a83c1b66e98fc81427ab13acddb17a20f52308983f3a5a8e0aaacec432359604 +81430b6d2998fc78ba937a1639c6020199c52da499f68109da227882dc26d005b73d54c5bdcac1a04e8356a8ca0f7017 +a8d906a4786455eb74613aba4ce1c963c60095ffb8658d368df9266fdd01e30269ce10bf984e7465f34b4fd83beba26a +af54167ac1f954d10131d44a8e0045df00d581dd9e93596a28d157543fbe5fb25d213806ed7fb3cba6b8f5b5423562db +8511e373a978a12d81266b9afbd55035d7bc736835cfa921903a92969eeba3624437d1346b55382e61415726ab84a448 +8cf43eea93508ae586fa9a0f1354a1e16af659782479c2040874a46317f9e8d572a23238efa318fdfb87cc63932602b7 +b0bdd3bacff077173d302e3a9678d1d37936188c7ecc34950185af6b462b7c679815176f3cce5db19aac8b282f2d60ad +a355e9b87f2f2672052f5d4d65b8c1c827d24d89b0d8594641fccfb69aef1b94009105f3242058bb31c8bf51caae5a41 +b8baa9e4b950b72ff6b88a6509e8ed1304bc6fd955748b2e59a523a1e0c5e99f52aec3da7fa9ff407a7adf259652466c +840bc3dbb300ea6f27d1d6dd861f15680bd098be5174f45d6b75b094d0635aced539fa03ddbccb453879de77fb5d1fe9 +b4bc7e7e30686303856472bae07e581a0c0bfc815657c479f9f5931cff208d5c12930d2fd1ff413ebd8424bcd7a9b571 +89b5d514155d7999408334a50822508b9d689add55d44a240ff2bdde2eee419d117031f85e924e2a2c1ca77db9b91eea +a8604b6196f87a04e1350302e8aa745bba8dc162115d22657b37a1d1a98cb14876ddf7f65840b5dbd77e80cd22b4256c +83cb7acdb9e03247515bb2ce0227486ccf803426717a14510f0d59d45e998b245797d356f10abca94f7a14e1a2f0d552 +aeb3266a9f16649210ab2df0e1908ac259f34ce1f01162c22b56cf1019096ee4ea5854c36e30bb2feb06c21a71e8a45c +89e72e86edf2aa032a0fc9acf4d876a40865fbb2c8f87cb7e4d88856295c4ac14583e874142fd0c314a49aba68c0aa3c +8c3576eba0583c2a7884976b4ed11fe1fda4f6c32f6385d96c47b0e776afa287503b397fa516a455b4b8c3afeedc76db +a31e5b633bda9ffa174654fee98b5d5930a691c3c42fcf55673d927dbc8d91c58c4e42e615353145431baa646e8bbb30 +89f2f3f7a8da1544f24682f41c68114a8f78c86bd36b066e27da13acb70f18d9f548773a16bd8e24789420e17183f137 +ada27fa4e90a086240c9164544d2528621a415a5497badb79f8019dc3dce4d12eb6b599597e47ec6ac39c81efda43520 +90dc1eb21bf21c0187f359566fc4bf5386abea52799306a0e5a1151c0817c5f5bc60c86e76b1929c092c0f3ff48cedd2 +b702a53ebcc17ae35d2e735a347d2c700e9cbef8eadbece33cac83df483b2054c126593e1f462cfc00a3ce9d737e2af5 +9891b06455ec925a6f8eafffba05af6a38cc5e193acaaf74ffbf199df912c5197106c5e06d72942bbb032ce277b6417f +8c0ee71eb01197b019275bcf96cae94e81d2cdc3115dbf2d8e3080074260318bc9303597e8f72b18f965ad601d31ec43 +8aaf580aaf75c1b7a5f99ccf60503506e62058ef43b28b02f79b8536a96be3f019c9f71caf327b4e6730134730d1bef5 +ae6f9fc21dd7dfa672b25a87eb0a41644f7609fab5026d5cedb6e43a06dbbfd6d6e30322a2598c8dedde88c52eaed626 +8159b953ffece5693edadb2e906ebf76ff080ee1ad22698950d2d3bfc36ac5ea78f58284b2ca180664452d55bd54716c +ab7647c32ca5e9856ac283a2f86768d68de75ceeba9e58b74c5324f8298319e52183739aba4340be901699d66ac9eb3f +a4d85a5701d89bcfaf1572db83258d86a1a0717603d6f24ac2963ffcf80f1265e5ab376a4529ca504f4396498791253c +816080c0cdbfe61b4d726c305747a9eb58ac26d9a35f501dd32ba43c098082d20faf3ccd41aad24600aa73bfa453dfac +84f3afac024f576b0fd9acc6f2349c2fcefc3f77dbe5a2d4964d14b861b88e9b1810334b908cf3427d9b67a8aee74b18 +94b390655557b1a09110018e9b5a14490681ade275bdc83510b6465a1218465260d9a7e2a6e4ec700f58c31dc3659962 +a8c66826b1c04a2dd4c682543242e7a57acae37278bd09888a3d17747c5b5fec43548101e6f46d703638337e2fd3277b +86e6f4608a00007fa533c36a5b054c5768ccafe41ad52521d772dcae4c8a4bcaff8f7609be30d8fab62c5988cbbb6830 +837da4cf09ae8aa0bceb16f8b3bfcc3b3367aecac9eed6b4b56d7b65f55981ef066490764fb4c108792623ecf8cad383 +941ff3011462f9b5bf97d8cbdb0b6f5d37a1b1295b622f5485b7d69f2cb2bcabc83630dae427f0259d0d9539a77d8424 +b99e5d6d82aa9cf7d5970e7f710f4039ac32c2077530e4c2779250c6b9b373bc380adb0a03b892b652f649720672fc8c +a791c78464b2d65a15440b699e1e30ebd08501d6f2720adbc8255d989a82fcded2f79819b5f8f201bed84a255211b141 +84af7ad4a0e31fcbb3276ab1ad6171429cf39adcf78dc03750dc5deaa46536d15591e26d53e953dfb31e1622bc0743ab +a833e62fe97e1086fae1d4917fbaf09c345feb6bf1975b5cb863d8b66e8d621c7989ab3dbecda36bc9eaffc5eaa6fa66 +b4ef79a46a2126f53e2ebe62770feb57fd94600be29459d70a77c5e9cc260fa892be06cd60f886bf48459e48eb50d063 +b43b8f61919ea380bf151c294e54d3a3ff98e20d1ee5efbfe38aa2b66fafbc6a49739793bd5cb1c809f8b30466277c3a +ab37735af2412d2550e62df9d8b3b5e6f467f20de3890bf56faf1abf2bf3bd1d98dc3fa0ad5e7ab3fce0fa20409eb392 +82416b74b1551d484250d85bb151fabb67e29cce93d516125533df585bc80779ab057ea6992801a3d7d5c6dcff87a018 +8145d0787f0e3b5325190ae10c1d6bee713e6765fb6a0e9214132c6f78f4582bb2771aaeae40d3dad4bafb56bf7e36d8 +b6935886349ecbdd5774e12196f4275c97ec8279fdf28ccf940f6a022ebb6de8e97d6d2173c3fe402cbe9643bed3883b +87ef9b4d3dc71ac86369f8ed17e0dd3b91d16d14ae694bc21a35b5ae37211b043d0e36d8ff07dcc513fb9e6481a1f37f +ae1d0ded32f7e6f1dc8fef495879c1d9e01826f449f903c1e5034aeeabc5479a9e323b162b688317d46d35a42d570d86 +a40d16497004db4104c6794e2f4428d75bdf70352685944f3fbe17526df333e46a4ca6de55a4a48c02ecf0bde8ba03c0 +8d45121efba8cc308a498e8ee39ea6fa5cae9fb2e4aab1c2ff9d448aa8494ccbec9a078f978a86fcd97b5d5e7be7522a +a8173865c64634ba4ac2fa432740f5c05056a9deaf6427cb9b4b8da94ca5ddbc8c0c5d3185a89b8b28878194de9cdfcd +b6ec06a74d690f6545f0f0efba236e63d1fdfba54639ca2617408e185177ece28901c457d02b849fd00f1a53ae319d0a +b69a12df293c014a40070e3e760169b6f3c627caf9e50b35a93f11ecf8df98b2bc481b410eecb7ab210bf213bbe944de +97e7dc121795a533d4224803e591eef3e9008bab16f12472210b73aaf77890cf6e3877e0139403a0d3003c12c8f45636 +acdfa6fdd4a5acb7738cc8768f7cba84dbb95c639399b291ae8e4e63df37d2d4096900a84d2f0606bf534a9ccaa4993f +86ee253f3a9446a33e4d1169719b7d513c6b50730988415382faaf751988c10a421020609f7bcdef91be136704b906e2 +aac9438382a856caf84c5a8a234282f71b5fc5f65219103b147e7e6cf565522285fbfd7417b513bdad8277a00f652ca1 +83f3799d8e5772527930f5dc071a2e0a65471618993ec8990a96ccdeee65270e490bda9d26bb877612475268711ffd80 +93f28a81ac8c0ec9450b9d762fae9c7f8feaace87a6ee6bd141ef1d2d0697ef1bbd159fe6e1de640dbdab2b0361fca8a +a0825c95ba69999b90eac3a31a3fd830ea4f4b2b7409bde5f202b61d741d6326852ce790f41de5cb0eccec7af4db30c1 +83924b0e66233edd603c3b813d698daa05751fc34367120e3cf384ea7432e256ccee4d4daf13858950549d75a377107d +956fd9fa58345277e06ba2ec72f49ed230b8d3d4ff658555c52d6cddeb84dd4e36f1a614f5242d5ca0192e8daf0543c2 +944869912476baae0b114cced4ff65c0e4c90136f73ece5656460626599051b78802df67d7201c55d52725a97f5f29fe +865cb25b64b4531fb6fe4814d7c8cd26b017a6c6b72232ff53defc18a80fe3b39511b23f9e4c6c7249d06e03b2282ed2 +81e09ff55214960775e1e7f2758b9a6c4e4cd39edf7ec1adfaad51c52141182b79fe2176b23ddc7df9fd153e5f82d668 +b31006896f02bc90641121083f43c3172b1039334501fbaf1672f7bf5d174ddd185f945adf1a9c6cf77be34c5501483d +88b92f6f42ae45e9f05b16e52852826e933efd0c68b0f2418ac90957fd018df661bc47c8d43c2a7d7bfcf669dab98c3c +92fc68f595853ee8683930751789b799f397135d002eda244fe63ecef2754e15849edde3ba2f0cc8b865c9777230b712 +99ca06a49c5cd0bb097c447793fcdd809869b216a34c66c78c7e41e8c22f05d09168d46b8b1f3390db9452d91bc96dea +b48b9490a5d65296802431852d548d81047bbefc74fa7dc1d4e2a2878faacdfcb365ae59209cb0ade01901a283cbd15d +aff0fdbef7c188b120a02bc9085d7b808e88f73973773fef54707bf2cd772cd066740b1b6f4127b5c349f657bd97e738 +966fd4463b4f43dd8ccba7ad50baa42292f9f8b2e70da23bb6780e14155d9346e275ef03ddaf79e47020dcf43f3738bd +9330c3e1fadd9e08ac85f4839121ae20bbeb0a5103d84fa5aadbd1213805bdcda67bf2fb75fc301349cbc851b5559d20 +993bb99867bd9041a71a55ad5d397755cfa7ab6a4618fc526179bfc10b7dc8b26e4372fe9a9b4a15d64f2b63c1052dda +a29b59bcfab51f9b3c490a3b96f0bf1934265c315349b236012adbd64a56d7f6941b2c8cc272b412044bc7731f71e1dc +a65c9cefe1fc35d089fe8580c2e7671ebefdb43014ac291528ff4deefd4883fd4df274af83711dad610dad0d615f9d65 +944c78c56fb227ae632805d448ca3884cd3d2a89181cead3d2b7835e63297e6d740aa79a112edb1d4727824991636df5 +a73d782da1db7e4e65d7b26717a76e16dd9fab4df65063310b8e917dc0bc24e0d6755df5546c58504d04d9e68c3b474a +af80f0b87811ae3124f68108b4ca1937009403f87928bbc53480e7c5408d072053ace5eeaf5a5aba814dab8a45502085 +88aaf1acfc6e2e19b8387c97da707cb171c69812fefdd4650468e9b2c627bd5ccfb459f4d8e56bdfd84b09ddf87e128f +92c97276ff6f72bab6e9423d02ad6dc127962dbce15a0dd1e4a393b4510c555df6aa27be0f697c0d847033a9ca8b8dfd +a0e07d43d96e2d85b6276b3c60aadb48f0aedf2de8c415756dc597249ea64d2093731d8735231dadc961e5682ac59479 +adc9e6718a8f9298957d1da3842a7751c5399bbdf56f8de6c1c4bc39428f4aee6f1ba6613d37bf46b9403345e9d6fc81 +951da434da4b20d949b509ceeba02e24da7ed2da964c2fcdf426ec787779c696b385822c7dbea4df3e4a35921f1e912c +a04cbce0d2b2e87bbf038c798a12ec828423ca6aca08dc8d481cf6466e3c9c73d4d4a7fa47df9a7e2e15aae9e9f67208 +8f855cca2e440d248121c0469de1f94c2a71b8ee2682bbad3a78243a9e03da31d1925e6760dbc48a1957e040fae9abe8 +b642e5b17c1df4a4e101772d73851180b3a92e9e8b26c918050f51e6dd3592f102d20b0a1e96f0e25752c292f4c903ff +a92454c300781f8ae1766dbbb50a96192da7d48ef4cbdd72dd8cbb44c6eb5913c112cc38e9144615fdc03684deb99420 +8b74f7e6c2304f8e780df4649ef8221795dfe85fdbdaa477a1542d135b75c8be45bf89adbbb6f3ddf54ca40f02e733e9 +85cf66292cbb30cec5fd835ab10c9fcb3aea95e093aebf123e9a83c26f322d76ebc89c4e914524f6c5f6ee7d74fc917d +ae0bfe0cdc97c09542a7431820015f2d16067b30dca56288013876025e81daa8c519e5e347268e19aa1a85fa1dc28793 +921322fc6a47dc091afa0ad6df18ed14cde38e48c6e71550aa513918b056044983aee402de21051235eecf4ce8040fbe +96c030381e97050a45a318d307dcb3c8377b79b4dd5daf6337cded114de26eb725c14171b9b8e1b3c08fe1f5ea6b49e0 +90c23b86b6111818c8baaf53a13eaee1c89203b50e7f9a994bf0edf851919b48edbac7ceef14ac9414cf70c486174a77 +8bf6c301240d2d1c8d84c71d33a6dfc6d9e8f1cfae66d4d0f7a256d98ae12b0bcebfa94a667735ee89f810bcd7170cff +a41a4ffbbea0e36874d65c009ee4c3feffff322f6fc0e30d26ee4dbc1f46040d05e25d9d0ecb378cef0d24a7c2c4b850 +a8d4cdd423986bb392a0a92c12a8bd4da3437eec6ef6af34cf5310944899287452a2eb92eb5386086d5063381189d10e +a81dd26ec057c4032a4ed7ad54d926165273ed51d09a1267b2e477535cf6966835a257c209e4e92d165d74fa75695fa3 +8d7f708c3ee8449515d94fc26b547303b53d8dd55f177bc3b25d3da2768accd9bc8e9f09546090ebb7f15c66e6c9c723 +839ba65cffcd24cfffa7ab3b21faabe3c66d4c06324f07b2729c92f15cad34e474b0f0ddb16cd652870b26a756b731d3 +87f1a3968afec354d92d77e2726b702847c6afcabb8438634f9c6f7766de4c1504317dc4fa9a4a735acdbf985e119564 +91a8a7fd6542f3e0673f07f510d850864b34ac087eb7eef8845a1d14b2b1b651cbdc27fa4049bdbf3fea54221c5c8549 +aef3cf5f5e3a2385ead115728d7059e622146c3457d266c612e778324b6e06fbfb8f98e076624d2f3ce1035d65389a07 +819915d6232e95ccd7693fdd78d00492299b1983bc8f96a08dcb50f9c0a813ed93ae53c0238345d5bea0beda2855a913 +8e9ba68ded0e94935131b392b28218315a185f63bf5e3c1a9a9dd470944509ca0ba8f6122265f8da851b5cc2abce68f1 +b28468e9b04ee9d69003399a3cf4457c9bf9d59f36ab6ceeb8e964672433d06b58beeea198fedc7edbaa1948577e9fa2 +a633005e2c9f2fd94c8bce2dd5bb708fe946b25f1ec561ae65e54e15cdd88dc339f1a083e01f0d39610c8fe24151aaf0 +841d0031e22723f9328dd993805abd13e0c99b0f59435d2426246996b08d00ce73ab906f66c4eab423473b409e972ce0 +85758d1b084263992070ec8943f33073a2d9b86a8606672550c17545507a5b3c88d87382b41916a87ee96ff55a7aa535 +8581b06b0fc41466ef94a76a1d9fb8ae0edca6d018063acf6a8ca5f4b02d76021902feba58972415691b4bdbc33ae3b4 +83539597ff5e327357ee62bc6bf8c0bcaec2f227c55c7c385a4806f0d37fb461f1690bad5066b8a5370950af32fafbef +aee3557290d2dc10827e4791d00e0259006911f3f3fce4179ed3c514b779160613eca70f720bff7804752715a1266ffa +b48d2f0c4e90fc307d5995464e3f611a9b0ef5fe426a289071f4168ed5cc4f8770c9332960c2ca5c8c427f40e6bb389f +847af8973b4e300bb06be69b71b96183fd1a0b9d51b91701bef6fcfde465068f1eb2b1503b07afda380f18d69de5c9e1 +a70a6a80ce407f07804c0051ac21dc24d794b387be94eb24e1db94b58a78e1bcfb48cd0006db8fc1f9bedaece7a44fbe +b40e942b8fa5336910ff0098347df716bff9d1fa236a1950c16eeb966b3bc1a50b8f7b0980469d42e75ae13ced53cead +b208fabaa742d7db3148515330eb7a3577487845abdb7bd9ed169d0e081db0a5816595c33d375e56aeac5b51e60e49d3 +b7c8194b30d3d6ef5ab66ec88ad7ebbc732a3b8a41731b153e6f63759a93f3f4a537eab9ad369705bd730184bdbbdc34 +9280096445fe7394d04aa1bc4620c8f9296e991cc4d6c131bd703cb1cc317510e6e5855ac763f4d958c5edfe7eebeed7 +abc2aa4616a521400af1a12440dc544e3c821313d0ab936c86af28468ef8bbe534837e364598396a81cf8d06274ed5a6 +b18ca8a3325adb0c8c18a666d4859535397a1c3fe08f95eebfac916a7a99bbd40b3c37b919e8a8ae91da38bc00fa56c0 +8a40c33109ecea2a8b3558565877082f79121a432c45ec2c5a5e0ec4d1c203a6788e6b69cb37f1fd5b8c9a661bc5476d +88c47301dd30998e903c84e0b0f2c9af2e1ce6b9f187dab03528d44f834dc991e4c86d0c474a2c63468cf4020a1e24a0 +920c832853e6ab4c851eecfa9c11d3acc7da37c823be7aa1ab15e14dfd8beb5d0b91d62a30cec94763bd8e4594b66600 +98e1addbe2a6b8edc7f12ecb9be81c3250aeeca54a1c6a7225772ca66549827c15f3950d01b8eb44aecb56fe0fff901a +8cfb0fa1068be0ec088402f5950c4679a2eb9218c729da67050b0d1b2d7079f3ddf4bf0f57d95fe2a8db04bc6bcdb20c +b70f381aafe336b024120453813aeab70baac85b9c4c0f86918797b6aee206e6ed93244a49950f3d8ec9f81f4ac15808 +a4c8edf4aa33b709a91e1062939512419711c1757084e46f8f4b7ed64f8e682f4e78b7135920c12f0eb0422fe9f87a6a +b4817e85fd0752d7ebb662d3a51a03367a84bac74ebddfba0e5af5e636a979500f72b148052d333b3dedf9edd2b4031b +a87430169c6195f5d3e314ff2d1c2f050e766fd5d2de88f5207d72dba4a7745bb86d0baca6e9ae156582d0d89e5838c7 +991b00f8b104566b63a12af4826b61ce7aa40f4e5b8fff3085e7a99815bdb4471b6214da1e480214fac83f86a0b93cc5 +b39966e3076482079de0678477df98578377a094054960ee518ef99504d6851f8bcd3203e8da5e1d4f6f96776e1fe6eb +a448846d9dc2ab7a0995fa44b8527e27f6b3b74c6e03e95edb64e6baa4f1b866103f0addb97c84bef1d72487b2e21796 +894bec21a453ae84b592286e696c35bc30e820e9c2fd3e63dd4fbe629e07df16439c891056070faa490155f255bf7187 +a9ec652a491b11f6a692064e955f3f3287e7d2764527e58938571469a1e29b5225b9415bd602a45074dfbfe9c131d6ca +b39d37822e6cbe28244b5f42ce467c65a23765bd16eb6447c5b3e942278069793763483dafd8c4dd864f8917aad357fe +88dba51133f2019cb266641c56101e3e5987d3b77647a2e608b5ff9113dfc5f85e2b7c365118723131fbc0c9ca833c9c +b566579d904b54ecf798018efcb824dccbebfc6753a0fd2128ac3b4bd3b038c2284a7c782b5ca6f310eb7ea4d26a3f0a +a97a55c0a492e53c047e7d6f9d5f3e86fb96f3dddc68389c0561515343b66b4bc02a9c0d5722dff1e3445308240b27f7 +a044028ab4bcb9e1a2b9b4ca4efbf04c5da9e4bf2fff0e8bd57aa1fc12a71e897999c25d9117413faf2f45395dee0f13 +a78dc461decbeaeed8ebd0909369b491a5e764d6a5645a7dac61d3140d7dc0062526f777b0eb866bff27608429ebbdde +b2c2a8991f94c39ca35fea59f01a92cb3393e0eccb2476dfbf57261d406a68bd34a6cff33ed80209991688c183609ef4 +84189eefb521aff730a4fd3fd5b10ddfd29f0d365664caef63bb015d07e689989e54c33c2141dd64427805d37a7e546e +85ac80bd734a52235da288ff042dea9a62e085928954e8eacd2c751013f61904ed110e5b3afe1ab770a7e6485efb7b5e +9183a560393dcb22d0d5063e71182020d0fbabb39e32493eeffeb808df084aa243eb397027f150b55a247d1ed0c8513e +81c940944df7ecc58d3c43c34996852c3c7915ed185d7654627f7af62abae7e0048dd444a6c09961756455000bd96d09 +aa8c34e164019743fd8284b84f06c3b449aae7996e892f419ee55d82ad548cb300fd651de329da0384243954c0ef6a60 +89a7b7bdfc7e300d06a14d463e573d6296d8e66197491900cc9ae49504c4809ff6e61b758579e9091c61085ba1237b83 +878d21809ba540f50bd11f4c4d9590fb6f3ab9de5692606e6e2ef4ed9d18520119e385be5e1f4b3f2e2b09c319f0e8fc +8eb248390193189cf0355365e630b782cd15751e672dc478b39d75dc681234dcd9309df0d11f4610dbb249c1e6be7ef9 +a1d7fb3aecb896df3a52d6bd0943838b13f1bd039c936d76d03de2044c371d48865694b6f532393b27fd10a4cf642061 +a34bca58a24979be442238cbb5ece5bee51ae8c0794dd3efb3983d4db713bc6f28a96e976ac3bd9a551d3ed9ba6b3e22 +817c608fc8cacdd178665320b5a7587ca21df8bdd761833c3018b967575d25e3951cf3d498a63619a3cd2ad4406f5f28 +86c95707db0495689afd0c2e39e97f445f7ca0edffad5c8b4cacd1421f2f3cc55049dfd504f728f91534e20383955582 +99c3b0bb15942c301137765d4e19502f65806f3b126dc01a5b7820c87e8979bce6a37289a8f6a4c1e4637227ad5bf3bf +8aa1518a80ea8b074505a9b3f96829f5d4afa55a30efe7b4de4e5dbf666897fdd2cf31728ca45921e21a78a80f0e0f10 +8d74f46361c79e15128ac399e958a91067ef4cec8983408775a87eca1eed5b7dcbf0ddf30e66f51780457413496c7f07 +a41cde4a786b55387458a1db95171aca4fd146507b81c4da1e6d6e495527c3ec83fc42fad1dfe3d92744084a664fd431 +8c352852c906fae99413a84ad11701f93f292fbf7bd14738814f4c4ceab32db02feb5eb70bc73898b0bc724a39d5d017 +a5993046e8f23b71ba87b7caa7ace2d9023fb48ce4c51838813174880d918e9b4d2b0dc21a2b9c6f612338c31a289df8 +83576d3324bf2d8afbfb6eaecdc5d767c8e22e7d25160414924f0645491df60541948a05e1f4202e612368e78675de8a +b43749b8df4b15bc9a3697e0f1c518e6b04114171739ef1a0c9c65185d8ec18e40e6954d125cbc14ebc652cf41ad3109 +b4eebd5d80a7327a040cafb9ccdb12b2dfe1aa86e6bc6d3ac8a57fadfb95a5b1a7332c66318ff72ba459f525668af056 +9198be7f1d413c5029b0e1c617bcbc082d21abe2c60ec8ce9b54ca1a85d3dba637b72fda39dae0c0ae40d047eab9f55a +8d96a0232832e24d45092653e781e7a9c9520766c3989e67bbe86b3a820c4bf621ea911e7cd5270a4bfea78b618411f6 +8d7160d0ea98161a2d14d46ef01dff72d566c330cd4fabd27654d300e1bc7644c68dc8eabf2a20a59bfe7ba276545f9b +abb60fce29dec7ba37e3056e412e0ec3e05538a1fc0e2c68877378c867605966108bc5742585ab6a405ce0c962b285b6 +8fabffa3ed792f05e414f5839386f6449fd9f7b41a47595c5d71074bd1bb3784cc7a1a7e1ad6b041b455035957e5b2dc +90ff017b4804c2d0533b72461436b10603ab13a55f86fd4ec11b06a70ef8166f958c110519ca1b4cc7beba440729fe2d +b340cfd120f6a4623e3a74cf8c32bfd7cd61a280b59dfd17b15ca8fae4d82f64a6f15fbde4c02f424debc72b7db5fe67 +871311c9c7220c932e738d59f0ecc67a34356d1429fe570ca503d340c9996cb5ee2cd188fad0e3bd16e4c468ec1dbebd +a772470262186e7b94239ba921b29f2412c148d6f97c4412e96d21e55f3be73f992f1ad53c71008f0558ec3f84e2b5a7 +b2a897dcb7ffd6257f3f2947ec966f2077d57d5191a88840b1d4f67effebe8c436641be85524d0a21be734c63ab5965d +a044f6eacc48a4a061fa149500d96b48cbf14853469aa4d045faf3dca973be1bd4b4ce01646d83e2f24f7c486d03205d +981af5dc2daa73f7fa9eae35a93d81eb6edba4a7f673b55d41f6ecd87a37685d31bb40ef4f1c469b3d72f2f18b925a17 +912d2597a07864de9020ac77083eff2f15ceb07600f15755aba61251e8ce3c905a758453b417f04d9c38db040954eb65 +9642b7f6f09394ba5e0805734ef6702c3eddf9eea187ba98c676d5bbaec0e360e3e51dc58433aaa1e2da6060c8659cb7 +8ab3836e0a8ac492d5e707d056310c4c8e0489ca85eb771bff35ba1d658360084e836a6f51bb990f9e3d2d9aeb18fbb5 +879e058e72b73bb1f4642c21ffdb90544b846868139c6511f299aafe59c2d0f0b944dffc7990491b7c4edcd6a9889250 +b9e60b737023f61479a4a8fd253ed0d2a944ea6ba0439bbc0a0d3abf09b0ad1f18d75555e4a50405470ae4990626f390 +b9c2535d362796dcd673640a9fa2ebdaec274e6f8b850b023153b0a7a30fffc87f96e0b72696f647ebe7ab63099a6963 +94aeff145386a087b0e91e68a84a5ede01f978f9dd9fe7bebca78941938469495dc30a96bba9508c0d017873aeea9610 +98b179f8a3d9f0d0a983c30682dd425a2ddc7803be59bd626c623c8951a5179117d1d2a68254c95c9952989877d0ee55 +889ecf5f0ee56938273f74eb3e9ecfb5617f04fb58e83fe4c0e4aef51615cf345bc56f3f61b17f6eed3249d4afd54451 +a0f2b2c39bcea4b50883e2587d16559e246248a66ecb4a4b7d9ab3b51fb39fe98d83765e087eee37a0f86b0ba4144c02 +b2a61e247ed595e8a3830f7973b07079cbda510f28ad8c78c220b26cb6acde4fbb5ee90c14a665f329168ee951b08cf0 +95bd0fcfb42f0d6d8a8e73d7458498a85bcddd2fb132fd7989265648d82ac2707d6d203fac045504977af4f0a2aca4b7 +843e5a537c298666e6cf50fcc044f13506499ef83c802e719ff2c90e85003c132024e04711be7234c04d4b0125512d5d +a46d1797c5959dcd3a5cfc857488f4d96f74277c3d13b98b133620192f79944abcb3a361d939a100187f1b0856eae875 +a1c7786736d6707a48515c38660615fcec67eb8a2598f46657855215f804fd72ab122d17f94fcffad8893f3be658dca7 +b23dc9e610abc7d8bd21d147e22509a0fa49db5be6ea7057b51aae38e31654b3aa044df05b94b718153361371ba2f622 +b00cc8f257d659c22d30e6d641f79166b1e752ea8606f558e4cad6fc01532e8319ea4ee12265ba4140ac45aa4613c004 +ac7019af65221b0cc736287b32d7f1a3561405715ba9a6a122342e04e51637ba911c41573de53e4781f2230fdcb2475f +81a630bc41b3da8b3eb4bf56cba10cd9f93153c3667f009dc332287baeb707d505fb537e6233c8e53d299ec0f013290c +a6b7aea5c545bb76df0f230548539db92bc26642572cb7dd3d5a30edca2b4c386f44fc8466f056b42de2a452b81aff5b +8271624ff736b7b238e43943c81de80a1612207d32036d820c11fc830c737972ccc9c60d3c2359922b06652311e3c994 +8a684106458cb6f4db478170b9ad595d4b54c18bf63b9058f095a2fa1b928c15101472c70c648873d5887880059ed402 +a5cc3c35228122f410184e4326cf61a37637206e589fcd245cb5d0cec91031f8f7586b80503070840fdfd8ce75d3c88b +9443fc631aed8866a7ed220890911057a1f56b0afe0ba15f0a0e295ab97f604b134b1ed9a4245e46ee5f9a93aa74f731 +984b6f7d79835dffde9558c6bb912d992ca1180a2361757bdba4a7b69dc74b056e303adc69fe67414495dd9c2dd91e64 +b15a5c8cba5de080224c274d31c68ed72d2a7126d347796569aef0c4e97ed084afe3da4d4b590b9dda1a07f0c2ff3dfb +991708fe9650a1f9a4e43938b91d45dc68c230e05ee999c95dbff3bf79b1c1b2bb0e7977de454237c355a73b8438b1d9 +b4f7edc7468b176a4a7c0273700c444fa95c726af6697028bed4f77eee887e3400f9c42ee15b782c0ca861c4c3b8c98a +8c60dcc16c51087eb477c13e837031d6c6a3dc2b8bf8cb43c23f48006bc7173151807e866ead2234b460c2de93b31956 +83ad63e9c910d1fc44bc114accfb0d4d333b7ebe032f73f62d25d3e172c029d5e34a1c9d547273bf6c0fead5c8801007 +85de73213cc236f00777560756bdbf2b16841ba4b55902cf2cad9742ecaf5d28209b012ceb41f337456dfeca93010cd7 +a7561f8827ccd75b6686ba5398bb8fc3083351c55a589b18984e186820af7e275af04bcd4c28e1dc11be1e8617a0610b +88c0a4febd4068850557f497ea888035c7fc9f404f6cc7794e7cc8722f048ad2f249e7dc62743e7a339eb7473ad3b0cd +932b22b1d3e6d5a6409c34980d176feb85ada1bf94332ef5c9fc4d42b907dabea608ceef9b5595ef3feee195151f18d8 +a2867bb3f5ab88fbdae3a16c9143ab8a8f4f476a2643c505bb9f37e5b1fd34d216cab2204c9a017a5a67b7ad2dda10e8 +b573d5f38e4e9e8a3a6fd82f0880dc049efa492a946d00283019bf1d5e5516464cf87039e80aef667cb86fdea5075904 +b948f1b5ab755f3f5f36af27d94f503b070696d793b1240c1bdfd2e8e56890d69e6904688b5f8ff5a4bdf5a6abfe195f +917eae95ebc4109a2e99ddd8fec7881d2f7aaa0e25fda44dec7ce37458c2ee832f1829db7d2dcfa4ca0f06381c7fe91d +95751d17ed00a3030bce909333799bb7f4ab641acf585807f355b51d6976dceee410798026a1a004ef4dcdff7ec0f5b8 +b9b7bd266f449a79bbfe075e429613e76c5a42ac61f01c8f0bbbd34669650682efe01ff9dbbc400a1e995616af6aa278 +ac1722d097ce9cd7617161f8ec8c23d68f1fb1c9ca533e2a8b4f78516c2fd8fb38f23f834e2b9a03bb06a9d655693ca9 +a7ad9e96ffd98db2ecdb6340c5d592614f3c159abfd832fe27ee9293519d213a578e6246aae51672ee353e3296858873 +989b8814d5de7937c4acafd000eec2b4cd58ba395d7b25f98cafd021e8efa37029b29ad8303a1f6867923f5852a220eb +a5bfe6282c771bc9e453e964042d44eff4098decacb89aecd3be662ea5b74506e1357ab26f3527110ba377711f3c9f41 +8900a7470b656639721d2abbb7b06af0ac4222ab85a1976386e2a62eb4b88bfb5b72cf7921ddb3cf3a395d7eeb192a2e +95a71b55cd1f35a438cf5e75f8ff11c5ec6a2ebf2e4dba172f50bfad7d6d5dca5de1b1afc541662c81c858f7604c1163 +82b5d62fea8db8d85c5bc3a76d68dedd25794cf14d4a7bc368938ffca9e09f7e598fdad2a5aac614e0e52f8112ae62b9 +997173f07c729202afcde3028fa7f52cefc90fda2d0c8ac2b58154a5073140683e54c49ed1f254481070d119ce0ce02a +aeffb91ccc7a72bbd6ffe0f9b99c9e66e67d59cec2e02440465e9636a613ab3017278cfa72ea8bc4aba9a8dc728cb367 +952743b06e8645894aeb6440fc7a5f62dd3acf96dab70a51e20176762c9751ea5f2ba0b9497ccf0114dc4892dc606031 +874c63baeddc56fbbca2ff6031f8634b745f6e34ea6791d7c439201aee8f08ef5ee75f7778700a647f3b21068513fce6 +85128fec9c750c1071edfb15586435cc2f317e3e9a175bb8a9697bcda1eb9375478cf25d01e7fed113483b28f625122d +85522c9576fd9763e32af8495ae3928ed7116fb70d4378448926bc9790e8a8d08f98cf47648d7da1b6e40d6a210c7924 +97d0f37a13cfb723b848099ca1c14d83e9aaf2f7aeb71829180e664b7968632a08f6a85f557d74b55afe6242f2a36e7c +abaa472d6ad61a5fccd1a57c01aa1bc081253f95abbcba7f73923f1f11c4e79b904263890eeb66926de3e2652f5d1c70 +b3c04945ba727a141e5e8aec2bf9aa3772b64d8fd0e2a2b07f3a91106a95cbcb249adcd074cbe498caf76fffac20d4ef +82c46781a3d730d9931bcabd7434a9171372dde57171b6180e5516d4e68db8b23495c8ac3ab96994c17ddb1cf249b9fb +a202d8b65613c42d01738ccd68ed8c2dbc021631f602d53f751966e04182743ebc8e0747d600b8a8676b1da9ae7f11ab +ae73e7256e9459db04667a899e0d3ea5255211fb486d084e6550b6dd64ca44af6c6b2d59d7aa152de9f96ce9b58d940d +b67d87b176a9722945ec7593777ee461809861c6cfd1b945dde9ee4ff009ca4f19cf88f4bbb5c80c9cbab2fe25b23ac8 +8f0b7a317a076758b0dac79959ee4a06c08b07d0f10538a4b53d3da2eda16e2af26922feb32c090330dc4d969cf69bd3 +90b36bf56adbd8c4b6cb32febc3a8d5f714370c2ac3305c10fa6d168dffb2a026804517215f9a2d4ec8310cdb6bb459b +aa80c19b0682ead69934bf18cf476291a0beddd8ef4ed75975d0a472e2ab5c70f119722a8574ae4973aceb733d312e57 +a3fc9abb12574e5c28dcb51750b4339b794b8e558675eef7d26126edf1de920c35e992333bcbffcbf6a5f5c0d383ce62 +a1573ff23ab972acdcd08818853b111fc757fdd35aa070186d3e11e56b172fb49d840bf297ac0dd222e072fc09f26a81 +98306f2be4caa92c2b4392212d0cbf430b409b19ff7d5b899986613bd0e762c909fc01999aa94be3bd529d67f0113d7f +8c1fc42482a0819074241746d17dc89c0304a2acdae8ed91b5009e9e3e70ff725ba063b4a3e68fdce05b74f5180c545e +a6c6113ebf72d8cf3163b2b8d7f3fa24303b13f55752522c660a98cd834d85d8c79214d900fa649499365e2e7641f77a +ab95eea424f8a2cfd9fb1c78bb724e5b1d71a0d0d1e4217c5d0f98b0d8bbd3f8400a2002abc0a0e4576d1f93f46fefad +823c5a4fd8cf4a75fdc71d5f2dd511b6c0f189b82affeacd2b7cfcad8ad1a5551227dcc9bfdb2e34b2097eaa00efbb51 +b97314dfff36d80c46b53d87a61b0e124dc94018a0bb680c32765b9a2d457f833a7c42bbc90b3b1520c33a182580398d +b17566ee3dcc6bb3b004afe4c0136dfe7dd27df9045ae896dca49fb36987501ae069eb745af81ba3fc19ff037e7b1406 +b0bdc0f55cfd98d331e3a0c4fbb776a131936c3c47c6bffdc3aaf7d8c9fa6803fbc122c2fefbb532e634228687d52174 +aa5d9e60cc9f0598559c28bb9bdd52aa46605ab4ffe3d192ba982398e72cec9a2a44c0d0d938ce69935693cabc0887ea +802b6459d2354fa1d56c592ac1346c428dadea6b6c0a87bf7d309bab55c94e1cf31dd98a7a86bd92a840dd51f218b91b +a526914efdc190381bf1a73dd33f392ecf01350b9d3f4ae96b1b1c3d1d064721c7d6eec5788162c933245a3943f5ee51 +b3b8fcf637d8d6628620a1a99dbe619eabb3e5c7ce930d6efd2197e261bf394b74d4e5c26b96c4b8009c7e523ccfd082 +8f7510c732502a93e095aba744535f3928f893f188adc5b16008385fb9e80f695d0435bfc5b91cdad4537e87e9d2551c +97b90beaa56aa936c3ca45698f79273a68dd3ccd0076eab48d2a4db01782665e63f33c25751c1f2e070f4d1a8525bf96 +b9fb798324b1d1283fdc3e48288e3861a5449b2ab5e884b34ebb8f740225324af86e4711da6b5cc8361c1db15466602f +b6d52b53cea98f1d1d4c9a759c25bf9d8a50b604b144e4912acbdbdc32aab8b9dbb10d64a29aa33a4f502121a6fb481c +9174ffff0f2930fc228f0e539f5cfd82c9368d26b074467f39c07a774367ff6cccb5039ac63f107677d77706cd431680 +a33b6250d4ac9e66ec51c063d1a6a31f253eb29bbaed12a0d67e2eccfffb0f3a52750fbf52a1c2aaba8c7692346426e7 +a97025fd5cbcebe8ef865afc39cd3ea707b89d4e765ec817fd021d6438e02fa51e3544b1fd45470c58007a08efac6edd +b32a78480edd9ff6ba2f1eec4088db5d6ceb2d62d7e59e904ecaef7bb4a2e983a4588e51692b3be76e6ffbc0b5f911a5 +b5ab590ef0bb77191f00495b33d11c53c65a819f7d0c1f9dc4a2caa147a69c77a4fff7366a602d743ee1f395ce934c1e +b3fb0842f9441fb1d0ee0293b6efbc70a8f58d12d6f769b12872db726b19e16f0f65efbc891cf27a28a248b0ef9c7e75 +9372ad12856fefb928ccb0d34e198df99e2f8973b07e9d417a3134d5f69e12e79ff572c4e03ccd65415d70639bc7c73e +aa8d6e83d09ce216bfe2009a6b07d0110d98cf305364d5529c170a23e693aabb768b2016befb5ada8dabdd92b4d012bb +a954a75791eeb0ce41c85200c3763a508ed8214b5945a42c79bfdcfb1ec4f86ad1dd7b2862474a368d4ac31911a2b718 +8e2081cfd1d062fe3ab4dab01f68062bac802795545fede9a188f6c9f802cb5f884e60dbe866710baadbf55dc77c11a4 +a2f06003b9713e7dd5929501ed485436b49d43de80ea5b15170763fd6346badf8da6de8261828913ee0dacd8ff23c0e1 +98eecc34b838e6ffd1931ca65eec27bcdb2fdcb61f33e7e5673a93028c5865e0d1bf6d3bec040c5e96f9bd08089a53a4 +88cc16019741b341060b95498747db4377100d2a5bf0a5f516f7dec71b62bcb6e779de2c269c946d39040e03b3ae12b7 +ad1135ccbc3019d5b2faf59a688eef2500697642be8cfbdf211a1ab59abcc1f24483e50d653b55ff1834675ac7b4978f +a946f05ed9972f71dfde0020bbb086020fa35b482cce8a4cc36dd94355b2d10497d7f2580541bb3e81b71ac8bba3c49f +a83aeed488f9a19d8cfd743aa9aa1982ab3723560b1cd337fc2f91ad82f07afa412b3993afb845f68d47e91ba4869840 +95eebe006bfc316810cb71da919e5d62c2cebb4ac99d8e8ef67be420302320465f8b69873470982de13a7c2e23516be9 +a55f8961295a11e91d1e5deadc0c06c15dacbfc67f04ccba1d069cba89d72aa3b3d64045579c3ea8991b150ac29366ae +b321991d12f6ac07a5de3c492841d1a27b0d3446082fbce93e7e1f9e8d8fe3b45d41253556261c21b70f5e189e1a7a6f +a0b0822f15f652ce7962a4f130104b97bf9529797c13d6bd8e24701c213cc37f18157bd07f3d0f3eae6b7cd1cb40401f +96e2fa4da378aa782cc2d5e6e465fc9e49b5c805ed01d560e9b98abb5c0de8b74a2e7bec3aa5e2887d25cccb12c66f0c +97e4ab610d414f9210ed6f35300285eb3ccff5b0b6a95ed33425100d7725e159708ea78704497624ca0a2dcabce3a2f9 +960a375b17bdb325761e01e88a3ea57026b2393e1d887b34b8fa5d2532928079ce88dc9fd06a728b26d2bb41b12b9032 +8328a1647398e832aadc05bd717487a2b6fcdaa0d4850d2c4da230c6a2ed44c3e78ec4837b6094f3813f1ee99414713f +aa283834ebd18e6c99229ce4b401eda83f01d904f250fedd4e24f1006f8fa0712a6a89a7296a9bf2ce8de30e28d1408e +b29e097f2caadae3e0f0ae3473c072b0cd0206cf6d2e9b22c1a5ad3e07d433e32bd09ed1f4e4276a2da4268633357b7f +9539c5cbba14538b2fe077ecf67694ef240da5249950baaabea0340718b882a966f66d97f08556b08a4320ceb2cc2629 +b4529f25e9b42ae8cf8338d2eface6ba5cd4b4d8da73af502d081388135c654c0b3afb3aa779ffc80b8c4c8f4425dd2b +95be0739c4330619fbe7ee2249c133c91d6c07eab846c18c5d6c85fc21ac5528c5d56dcb0145af68ed0c6a79f68f2ccd +ac0c83ea802227bfc23814a24655c9ff13f729619bcffdb487ccbbf029b8eaee709f8bddb98232ef33cd70e30e45ca47 +b503becb90acc93b1901e939059f93e671900ca52c6f64ae701d11ac891d3a050b505d89324ce267bc43ab8275da6ffe +98e3811b55b1bacb70aa409100abb1b870f67e6d059475d9f278c751b6e1e2e2d6f2e586c81a9fb6597fda06e7923274 +b0b0f61a44053fa6c715dbb0731e35d48dba257d134f851ee1b81fd49a5c51a90ebf5459ec6e489fce25da4f184fbdb1 +b1d2117fe811720bb997c7c93fe9e4260dc50fca8881b245b5e34f724aaf37ed970cdad4e8fcb68e05ac8cf55a274a53 +a10f502051968f14b02895393271776dee7a06db9de14effa0b3471825ba94c3f805302bdddac4d397d08456f620999d +a3dbad2ef060ae0bb7b02eaa4a13594f3f900450faa1854fc09620b01ac94ab896321dfb1157cf2374c27e5718e8026a +b550fdec503195ecb9e079dcdf0cad559d64d3c30818ef369b4907e813e689da316a74ad2422e391b4a8c2a2bef25fc0 +a25ba865e2ac8f28186cea497294c8649a201732ecb4620c4e77b8e887403119910423df061117e5f03fc5ba39042db1 +b3f88174e03fdb443dd6addd01303cf88a4369352520187c739fc5ae6b22fa99629c63c985b4383219dab6acc5f6f532 +97a7503248e31e81b10eb621ba8f5210c537ad11b539c96dfb7cf72b846c7fe81bd7532c5136095652a9618000b7f8d3 +a8bcdc1ce5aa8bfa683a2fc65c1e79de8ff5446695dcb8620f7350c26d2972a23da22889f9e2b1cacb3f688c6a2953dc +8458c111df2a37f5dd91a9bee6c6f4b79f4f161c93fe78075b24a35f9817da8dde71763218d627917a9f1f0c4709c1ed +ac5f061a0541152b876cbc10640f26f1cc923c9d4ae1b6621e4bb3bf2cec59bbf87363a4eb72fb0e5b6d4e1c269b52d5 +a9a25ca87006e8a9203cbb78a93f50a36694aa4aad468b8d80d3feff9194455ca559fcc63838128a0ab75ad78c07c13a +a450b85f5dfffa8b34dfd8bc985f921318efacf8857cf7948f93884ba09fb831482ee90a44224b1a41e859e19b74962f +8ed91e7f92f5c6d7a71708b6132f157ac226ecaf8662af7d7468a4fa25627302efe31e4620ad28719318923e3a59bf82 +ab524165fd4c71b1fd395467a14272bd2b568592deafa039d8492e9ef36c6d3f96927c95c72d410a768dc0b6d1fbbc9b +b662144505aa8432c75ffb8d10318526b6d5777ac7af9ebfad87d9b0866c364f7905a6352743bd8fd79ffd9d5dd4f3e6 +a48f1677550a5cd40663bb3ba8f84caaf8454f332d0ceb1d94dbea52d0412fe69c94997f7749929712fd3995298572f7 +8391cd6e2f6b0c242de1117a612be99776c3dc95cb800b187685ea5bf7e2722275eddb79fd7dfc8be8e389c4524cdf70 +875d3acb9af47833b72900bc0a2448999d638f153c5e97e8a14ec02d0c76f6264353a7e275e1f1a5855daced523d243b +91f1823657d30b59b2f627880a9a9cb530f5aca28a9fd217fe6f2f5133690dfe7ad5a897872e400512db2e788b3f7628 +ad3564332aa56cea84123fc7ca79ea70bb4fef2009fa131cb44e4b15e8613bd11ca1d83b9d9bf456e4b7fee9f2e8b017 +8c530b84001936d5ab366c84c0b105241a26d1fb163669f17c8f2e94776895c2870edf3e1bc8ccd04d5e65531471f695 +932d01fa174fdb0c366f1230cffde2571cc47485f37f23ba5a1825532190cc3b722aeb1f15aed62cf83ccae9403ba713 +88b28c20585aca50d10752e84b901b5c2d58efef5131479fbbe53de7bce2029e1423a494c0298e1497669bd55be97a5d +b914148ca717721144ebb3d3bf3fcea2cd44c30c5f7051b89d8001502f3856fef30ec167174d5b76265b55d70f8716b5 +81d0173821c6ddd2a068d70766d9103d1ee961c475156e0cbd67d54e668a796310474ef698c7ab55abe6f2cf76c14679 +8f28e8d78e2fe7fa66340c53718e0db4b84823c8cfb159c76eac032a62fb53da0a5d7e24ca656cf9d2a890cb2a216542 +8a26360335c73d1ab51cec3166c3cf23b9ea51e44a0ad631b0b0329ef55aaae555420348a544e18d5760969281759b61 +94f326a32ed287545b0515be9e08149eb0a565025074796d72387cc3a237e87979776410d78339e23ef3172ca43b2544 +a785d2961a2fa5e70bffa137858a92c48fe749fee91b02599a252b0cd50d311991a08efd7fa5e96b78d07e6e66ffe746 +94af9030b5ac792dd1ce517eaadcec1482206848bea4e09e55cc7f40fd64d4c2b3e9197027c5636b70d6122c51d2235d +9722869f7d1a3992850fe7be405ec93aa17dc4d35e9e257d2e469f46d2c5a59dbd504056c85ab83d541ad8c13e8bcd54 +b13c4088b61a06e2c03ac9813a75ff1f68ffdfee9df6a8f65095179a475e29cc49119cad2ce05862c3b1ac217f3aace9 +8c64d51774753623666b10ca1b0fe63ae42f82ed6aa26b81dc1d48c86937c5772eb1402624c52a154b86031854e1fb9f +b47e4df18002b7dac3fee945bf9c0503159e1b8aafcce2138818e140753011b6d09ef1b20894e08ba3006b093559061b +93cb5970076522c5a0483693f6a35ffd4ea2aa7aaf3730c4eccd6af6d1bebfc1122fc4c67d53898ae13eb6db647be7e2 +a68873ef80986795ea5ed1a597d1cd99ed978ec25e0abb57fdcc96e89ef0f50aeb779ff46e3dce21dc83ada3157a8498 +8cab67f50949cc8eee6710e27358aea373aae3c92849f8f0b5531c080a6300cdf2c2094fe6fecfef6148de0d28446919 +993e932bcb616dbaa7ad18a4439e0565211d31071ef1b85a0627db74a05d978c60d507695eaeea5c7bd9868a21d06923 +acdadff26e3132d9478a818ef770e9fa0d2b56c6f5f48bd3bd674436ccce9bdfc34db884a73a30c04c5f5e9764cb2218 +a0d3e64c9c71f84c0eef9d7a9cb4fa184224b969db5514d678e93e00f98b41595588ca802643ea225512a4a272f5f534 +91c9140c9e1ba6e330cb08f6b2ce4809cd0d5a0f0516f70032bf30e912b0ed684d07b413b326ab531ee7e5b4668c799b +87bc2ee7a0c21ba8334cd098e35cb703f9af57f35e091b8151b9b63c3a5b0f89bd7701dbd44f644ea475901fa6d9ef08 +9325ccbf64bf5d71b303e31ee85d486298f9802c5e55b2c3d75427097bf8f60fa2ab4fcaffa9b60bf922c3e24fbd4b19 +95d0506e898318f3dc8d28d16dfd9f0038b54798838b3c9be2a2ae3c2bf204eb496166353fc042220b0bd4f6673b9285 +811de529416331fe9c416726d45df9434c29dcd7e949045eb15740f47e97dde8f31489242200e19922cac2a8b7c6fd1f +ade632d04a4c8bbab6ca7df370b2213cb9225023e7973f0e29f4f5e52e8aeaabc65171306bbdd12a67b195dfbb96d48f +88b7f029e079b6ae956042c0ea75d53088c5d0efd750dd018adaeacf46be21bf990897c58578c491f41afd3978d08073 +91f477802de507ffd2be3f4319903119225b277ad24f74eb50f28b66c14d32fae53c7edb8c7590704741af7f7f3e3654 +809838b32bb4f4d0237e98108320d4b079ee16ed80c567e7548bd37e4d7915b1192880f4812ac0e00476d246aec1dbc8 +84183b5fc4a7997a8ae5afedb4d21dce69c480d5966b5cbdafd6dd10d29a9a6377f3b90ce44da0eb8b176ac3af0253bb +8508abbf6d3739a16b9165caf0f95afb3b3ac1b8c38d6d374cf0c91296e2c1809a99772492b539cda184510bce8a0271 +8722054e59bab2062e6419a6e45fc803af77fde912ef2cd23055ad0484963de65a816a2debe1693d93c18218d2b8e81a +8e895f80e485a7c4f56827bf53d34b956281cdc74856c21eb3b51f6288c01cc3d08565a11cc6f3e2604775885490e8c5 +afc92714771b7aa6e60f3aee12efd9c2595e9659797452f0c1e99519f67c8bc3ac567119c1ddfe82a3e961ee9defea9a +818ff0fd9cefd32db87b259e5fa32967201016fc02ef44116cdca3c63ce5e637756f60477a408709928444a8ad69c471 +8251e29af4c61ae806fc5d032347fb332a94d472038149225298389495139ce5678fae739d02dfe53a231598a992e728 +a0ea39574b26643f6f1f48f99f276a8a64b5481989cfb2936f9432a3f8ef5075abfe5c067dc5512143ce8bf933984097 +af67a73911b372bf04e57e21f289fc6c3dfac366c6a01409b6e76fea4769bdb07a6940e52e8d7d3078f235c6d2f632c6 +b5291484ef336024dd2b9b4cf4d3a6b751133a40656d0a0825bcc6d41c21b1c79cb50b0e8f4693f90c29c8f4358641f9 +8bc0d9754d70f2cb9c63f991902165a87c6535a763d5eece43143b5064ae0bcdce7c7a8f398f2c1c29167b2d5a3e6867 +8d7faff53579ec8f6c92f661c399614cc35276971752ce0623270f88be937c414eddcb0997e14724a783905a026c8883 +9310b5f6e675fdf60796f814dbaa5a6e7e9029a61c395761e330d9348a7efab992e4e115c8be3a43d08e90d21290c892 +b5eb4f3eb646038ad2a020f0a42202532d4932e766da82b2c1002bf9c9c2e5336b54c8c0ffcc0e02d19dde2e6a35b6cc +91dabfd30a66710f1f37a891136c9be1e23af4abf8cb751f512a40c022a35f8e0a4fb05b17ec36d4208de02d56f0d53a +b3ded14e82d62ac7a5a036122a62f00ff8308498f3feae57d861babaff5a6628d43f0a0c5fc903f10936bcf4e2758ceb +a88e8348fed2b26acca6784d19ef27c75963450d99651d11a950ea81d4b93acd2c43e0ecce100eaf7e78508263d5baf3 +b1f5bbf7c4756877b87bb42163ac570e08c6667c4528bf68b5976680e19beeff7c5effd17009b0718797077e2955457a +ad2e7b516243f915d4d1415326e98b1a7390ae88897d0b03b66c2d9bd8c3fba283d7e8fe44ed3333296a736454cef6d8 +8f82eae096d5b11f995de6724a9af895f5e1c58d593845ad16ce8fcae8507e0d8e2b2348a0f50a1f66a17fd6fac51a5c +890e4404d0657c6c1ee14e1aac132ecf7a568bb3e04137b85ac0f84f1d333bd94993e8750f88eee033a33fb00f85dcc7 +82ac7d3385e035115f1d39a99fc73e5919de44f5e6424579776d118d711c8120b8e5916372c6f27bed4cc64cac170b6c +85ee16d8901c272cfbbe966e724b7a891c1bd5e68efd5d863043ad8520fc409080af61fd726adc680b3f1186fe0ac8b8 +86dc564c9b545567483b43a38f24c41c6551a49cabeebb58ce86404662a12dbfafd0778d30d26e1c93ce222e547e3898 +a29f5b4522db26d88f5f95f18d459f8feefab02e380c2edb65aa0617a82a3c1a89474727a951cef5f15050bcf7b380fb +a1ce039c8f6cac53352899edb0e3a72c76da143564ad1a44858bd7ee88552e2fe6858d1593bbd74aeee5a6f8034b9b9d +97f10d77983f088286bd7ef3e7fdd8fa275a56bec19919adf33cf939a90c8f2967d2b1b6fc51195cb45ad561202a3ed7 +a25e2772e8c911aaf8712bdac1dd40ee061c84d3d224c466cfaae8e5c99604053f940cde259bd1c3b8b69595781dbfec +b31bb95a0388595149409c48781174c340960d59032ab2b47689911d03c68f77a2273576fbe0c2bf4553e330656058c7 +b8b2e9287ad803fb185a13f0d7456b397d4e3c8ad5078f57f49e8beb2e85f661356a3392dbd7bcf6a900baa5582b86a1 +a3d0893923455eb6e96cc414341cac33d2dbc88fba821ac672708cce131761d85a0e08286663a32828244febfcae6451 +82310cb42f647d99a136014a9f881eb0b9791efd2e01fc1841907ad3fc8a9654d3d1dab6689c3607214b4dc2aca01cee +874022d99c16f60c22de1b094532a0bc6d4de700ad01a31798fac1d5088b9a42ad02bef8a7339af7ed9c0d4f16b186ee +94981369e120265aed40910eebc37eded481e90f4596b8d57c3bec790ab7f929784bd33ddd05b7870aad6c02e869603b +a4f1f50e1e2a73f07095e0dd31cb45154f24968dae967e38962341c1241bcd473102fff1ff668b20c6547e9732d11701 +ae2328f3b0ad79fcda807e69a1b5278145225083f150f67511dafc97e079f860c3392675f1752ae7e864c056e592205b +875d8c971e593ca79552c43d55c8c73b17cd20c81ff2c2fed1eb19b1b91e4a3a83d32df150dbfd5db1092d0aebde1e1f +add2e80aa46aae95da73a11f130f4bda339db028e24c9b11e5316e75ba5e63bc991d2a1da172c7c8e8fee038baae3433 +b46dbe1cb3424002aa7de51e82f600852248e251465c440695d52538d3f36828ff46c90ed77fc1d11534fe3c487df8ef +a5e5045d28b4e83d0055863c30c056628c58d4657e6176fd0536f5933f723d60e851bb726d5bf3c546b8ce4ac4a57ef8 +91fec01e86dd1537e498fff7536ea3ca012058b145f29d9ada49370cd7b7193ac380e116989515df1b94b74a55c45df3 +a7428176d6918cd916a310bdc75483c72de660df48cac4e6e7478eef03205f1827ea55afc0df5d5fa7567d14bbea7fc9 +851d89bef45d9761fe5fdb62972209335193610015e16a675149519f9911373bac0919add226ef118d9f3669cfdf4734 +b74acf5c149d0042021cb2422ea022be4c4f72a77855f42393e71ffd12ebb3eec16bdf16f812159b67b79a9706e7156d +99f35dce64ec99aa595e7894b55ce7b5a435851b396e79036ffb249c28206087db4c85379df666c4d95857db02e21ff9 +b6b9a384f70db9e298415b8ab394ee625dafff04be2886476e59df8d052ca832d11ac68a9b93fba7ab055b7bc36948a4 +898ee4aefa923ffec9e79f2219c7389663eb11eb5b49014e04ed4a336399f6ea1691051d86991f4c46ca65bcd4fdf359 +b0f948217b0d65df7599a0ba4654a5e43c84db477936276e6f11c8981efc6eaf14c90d3650107ed4c09af4cc8ec11137 +aa6286e27ac54f73e63dbf6f41865dd94d24bc0cf732262fcaff67319d162bb43af909f6f8ee27b1971939cfbba08141 +8bca7cdf730cf56c7b2c8a2c4879d61361a6e1dba5a3681a1a16c17a56e168ace0e99cf0d15826a1f5e67e6b8a8a049a +a746d876e8b1ce225fcafca603b099b36504846961526589af977a88c60d31ba2cc56e66a3dec8a77b3f3531bf7524c9 +a11e2e1927e6704cdb8874c75e4f1842cef84d7d43d7a38e339e61dc8ba90e61bbb20dd3c12e0b11d2471d58eed245be +a36395e22bc1d1ba8b0459a235203177737397da5643ce54ded3459d0869ff6d8d89f50c73cb62394bf66a959cde9b90 +8b49f12ba2fdf9aca7e5f81d45c07d47f9302a2655610e7634d1e4bd16048381a45ef2c95a8dd5b0715e4b7cf42273af +91cffa2a17e64eb7f76bccbe4e87280ee1dd244e04a3c9eac12e15d2d04845d876eb24fe2ec6d6d266cce9efb281077f +a6b8afabf65f2dee01788114e33a2f3ce25376fb47a50b74da7c3c25ff1fdc8aa9f41307534abbf48acb6f7466068f69 +8d13db896ccfea403bd6441191995c1a65365cab7d0b97fbe9526da3f45a877bd1f4ef2edef160e8a56838cd1586330e +98c717de9e01bef8842c162a5e757fe8552d53269c84862f4d451e7c656ae6f2ae473767b04290b134773f63be6fdb9d +8c2036ace1920bd13cf018e82848c49eb511fad65fd0ff51f4e4b50cf3bfc294afb63cba682c16f52fb595a98fa84970 +a3520fdff05dbad9e12551b0896922e375f9e5589368bcb2cc303bde252743b74460cb5caf99629325d3620f13adc796 +8d4f83a5bfec05caf5910e0ce538ee9816ee18d0bd44c1d0da2a87715a23cd2733ad4d47552c6dc0eb397687d611dd19 +a7b39a0a6a02823452d376533f39d35029867b3c9a6ad6bca181f18c54132d675613a700f9db2440fb1b4fa13c8bf18a +80bcb114b2544b80f404a200fc36860ed5e1ad31fe551acd4661d09730c452831751baa9b19d7d311600d267086a70bc +90dcce03c6f88fc2b08f2b42771eedde90cc5330fe0336e46c1a7d1b5a6c1641e5fcc4e7b3d5db00bd8afca9ec66ed81 +aec15f40805065c98e2965b1ae12a6c9020cfdb094c2d0549acfc7ea2401a5fb48d3ea7d41133cf37c4e096e7ff53eb9 +80e129b735dba49fa627a615d6c273119acec8e219b2f2c4373a332b5f98d66cbbdd688dfbe72a8f8bfefaccc02c50c1 +a9b596da3bdfe23e6799ece5f7975bf7a1979a75f4f546deeaf8b34dfe3e0d623217cb4cf4ccd504cfa3625b88cd53f1 +abcbbb70b16f6e517c0ab4363ab76b46e4ff58576b5f8340e5c0e8cc0e02621b6e23d742d73b015822a238b17cfd7665 +a046937cc6ea6a2e1adae543353a9fe929c1ae4ad655be1cc051378482cf88b041e28b1e9a577e6ccff2d3570f55e200 +831279437282f315e65a60184ef158f0a3dddc15a648dc552bdc88b3e6fe8288d3cfe9f0031846d81350f5e7874b4b33 +993d7916fa213c6d66e7c4cafafc1eaec9a2a86981f91c31eb8a69c5df076c789cbf498a24c84e0ee77af95b42145026 +823907a3b6719f8d49b3a4b7c181bd9bb29fcf842d7c70660c4f351852a1e197ca46cf5e879b47fa55f616fa2b87ce5e +8d228244e26132b234930ee14c75d88df0943cdb9c276a8faf167d259b7efc1beec2a87c112a6c608ad1600a239e9aae +ab6e55766e5bfb0cf0764ed909a8473ab5047d3388b4f46faeba2d1425c4754c55c6daf6ad4751e634c618b53e549529 +ab0cab6860e55a84c5ad2948a7e0989e2b4b1fd637605634b118361497332df32d9549cb854b2327ca54f2bcb85eed8f +b086b349ae03ef34f4b25a57bcaa5d1b29bd94f9ebf87e22be475adfe475c51a1230c1ebe13506cb72c4186192451658 +8a0b49d8a254ca6d91500f449cbbfbb69bb516c6948ac06808c65595e46773e346f97a5ce0ef7e5a5e0de278af22709c +ac49de11edaaf04302c73c578cc0824bdd165c0d6321be1c421c1950e68e4f3589aa3995448c9699e93c6ebae8803e27 +884f02d841cb5d8f4c60d1402469216b114ab4e93550b5bc1431756e365c4f870a9853449285384a6fa49e12ce6dc654 +b75f3a28fa2cc8d36b49130cb7448a23d73a7311d0185ba803ad55c8219741d451c110f48b786e96c728bc525903a54f +80ae04dbd41f4a35e33f9de413b6ad518af0919e5a30cb0fa1b061b260420780bb674f828d37fd3b52b5a31673cbd803 +b9a8011eb5fcea766907029bf743b45262db3e49d24f84503687e838651ed11cb64c66281e20a0ae9f6aa51acc552263 +90bfdd75e2dc9cf013e22a5d55d2d2b8a754c96103a17524488e01206e67f8b6d52b1be8c4e3d5307d4fe06d0e51f54c +b4af353a19b06203a815ec43e79a88578cc678c46f5a954b85bc5c53b84059dddba731f3d463c23bfd5273885c7c56a4 +aa125e96d4553b64f7140e5453ff5d2330318b69d74d37d283e84c26ad672fa00e3f71e530eb7e28be1e94afb9c4612e +a18e060aee3d49cde2389b10888696436bb7949a79ca7d728be6456a356ea5541b55492b2138da90108bd1ce0e6f5524 +93e55f92bdbccc2de655d14b1526836ea2e52dba65eb3f87823dd458a4cb5079bf22ce6ef625cb6d6bfdd0995ab9a874 +89f5a683526b90c1c3ceebbb8dc824b21cff851ce3531b164f6626e326d98b27d3e1d50982e507d84a99b1e04e86a915 +83d1c38800361633a3f742b1cb2bfc528129496e80232611682ddbe403e92c2ac5373aea0bca93ecb5128b0b2b7a719e +8ecba560ac94905e19ce8d9c7af217bf0a145d8c8bd38e2db82f5e94cc3f2f26f55819176376b51f154b4aab22056059 +a7e2a4a002b60291924850642e703232994acb4cfb90f07c94d1e0ecd2257bb583443283c20fc6017c37e6bfe85b7366 +93ed7316fa50b528f1636fc6507683a672f4f4403e55e94663f91221cc198199595bd02eef43d609f451acc9d9b36a24 +a1220a8ebc5c50ceed76a74bc3b7e0aa77f6884c71b64b67c4310ac29ce5526cb8992d6abc13ef6c8413ce62486a6795 +b2f6eac5c869ad7f4a25161d3347093e2f70e66cd925032747e901189355022fab3038bca4d610d2f68feb7e719c110b +b703fa11a4d511ca01c7462979a94acb40b5d933759199af42670eb48f83df202fa0c943f6ab3b4e1cc54673ea3aab1e +b5422912afbfcb901f84791b04f1ddb3c3fbdc76d961ee2a00c5c320e06d3cc5b5909c3bb805df66c5f10c47a292b13d +ad0934368da823302e1ac08e3ede74b05dfdbfffca203e97ffb0282c226814b65c142e6e15ec1e754518f221f01b30f7 +a1dd302a02e37df15bf2f1147efe0e3c06933a5a767d2d030e1132f5c3ce6b98e216b6145eb39e1e2f74e76a83165b8d +a346aab07564432f802ae44738049a36f7ca4056df2d8f110dbe7fef4a3e047684dea609b2d03dc6bf917c9c2a47608f +b96c5f682a5f5d02123568e50f5d0d186e4b2c4c9b956ec7aabac1b3e4a766d78d19bd111adb5176b898e916e49be2aa +8a96676d56876fc85538db2e806e1cba20fd01aeb9fa3cb43ca6ca94a2c102639f65660db330e5d74a029bb72d6a0b39 +ab0048336bd5c3def1a4064eadd49e66480c1f2abb4df46e03afbd8a3342c2c9d74ee35d79f08f4768c1646681440984 +888427bdf76caec90814c57ee1c3210a97d107dd88f7256f14f883ad0f392334b82be11e36dd8bfec2b37935177c7831 +b622b282becf0094a1916fa658429a5292ba30fb48a4c8066ce1ddcefb71037948262a01c95bab6929ed3a76ba5db9fe +b5b9e005c1f456b6a368a3097634fb455723abe95433a186e8278dceb79d4ca2fbe21f8002e80027b3c531e5bf494629 +a3c6707117a1e48697ed41062897f55d8119403eea6c2ee88f60180f6526f45172664bfee96bf61d6ec0b7fbae6aa058 +b02a9567386a4fbbdb772d8a27057b0be210447348efe6feb935ceec81f361ed2c0c211e54787dc617cdffed6b4a6652 +a9b8364e40ef15c3b5902e5534998997b8493064fa2bea99600def58279bb0f64574c09ba11e9f6f669a8354dd79dc85 +9998a2e553a9aa9a206518fae2bc8b90329ee59ab23005b10972712389f2ec0ee746033c733092ffe43d73d33abbb8ef +843a4b34d9039bf79df96d79f2d15e8d755affb4d83d61872daf540b68c0a3888cf8fc00d5b8b247b38524bcb3b5a856 +84f7128920c1b0bb40eee95701d30e6fc3a83b7bb3709f16d97e72acbb6057004ee7ac8e8f575936ca9dcb7866ab45f7 +918d3e2222e10e05edb34728162a899ad5ada0aaa491aeb7c81572a9c0d506e31d5390e1803a91ff3bd8e2bb15d47f31 +9442d18e2489613a7d47bb1cb803c8d6f3259d088cd079460976d87f7905ee07dea8f371b2537f6e1d792d36d7e42723 +b491976970fe091995b2ed86d629126523ccf3e9daf8145302faca71b5a71a5da92e0e05b62d7139d3efac5c4e367584 +aa628006235dc77c14cef4c04a308d66b07ac92d377df3de1a2e6ecfe3144f2219ad6d7795e671e1cb37a3641910b940 +99d386adaea5d4981d7306feecac9a555b74ffdc218c907c5aa7ac04abaead0ec2a8237300d42a3fbc464673e417ceed +8f78e8b1556f9d739648ea3cab9606f8328b52877fe72f9305545a73b74d49884044ba9c1f1c6db7d9b7c7b7c661caba +8fb357ae49932d0babdf74fc7aa7464a65d3b6a2b3acf4f550b99601d3c0215900cfd67f2b6651ef94cfc323bac79fae +9906f2fa25c0290775aa001fb6198113d53804262454ae8b83ef371b5271bde189c0460a645829cb6c59f9ee3a55ce4d +8f4379b3ebb50e052325b27655ca6a82e6f00b87bf0d2b680d205dd2c7afdc9ff32a9047ae71a1cdf0d0ce6b9474d878 +a85534e88c2bd43c043792eaa75e50914b21741a566635e0e107ae857aed0412035f7576cf04488ade16fd3f35fdbb87 +b4ce93199966d3c23251ca7f28ec5af7efea1763d376b0385352ffb2e0a462ef95c69940950278cf0e3dafd638b7bd36 +b10cb3d0317dd570aa73129f4acf63c256816f007607c19b423fb42f65133ce21f2f517e0afb41a5378cccf893ae14d0 +a9b231c9f739f7f914e5d943ed9bff7eba9e2c333fbd7c34eb1648a362ee01a01af6e2f7c35c9fe962b11152cddf35de +99ff6a899e156732937fb81c0cced80ae13d2d44c40ba99ac183aa246103b31ec084594b1b7feb96da58f4be2dd5c0ed +8748d15d18b75ff2596f50d6a9c4ce82f61ecbcee123a6ceae0e43cab3012a29b6f83cf67b48c22f6f9d757c6caf76b2 +b88ab05e4248b7fb634cf640a4e6a945d13e331237410f7217d3d17e3e384ddd48897e7a91e4516f1b9cbd30f35f238b +8d826deaeeb84a3b2d2c04c2300ca592501f992810582d6ae993e0d52f6283a839dba66c6c72278cff5871802b71173b +b36fed027c2f05a5ef625ca00b0364b930901e9e4420975b111858d0941f60e205546474bb25d6bfa6928d37305ae95f +af2fcfc6b87967567e8b8a13a4ed914478185705724e56ce68fb2df6d1576a0cf34a61e880997a0d35dc2c3276ff7501 +ac351b919cd1fbf106feb8af2c67692bfcddc84762d18cea681cfa7470a5644839caace27efee5f38c87d3df306f4211 +8d6665fb1d4d8d1fa23bd9b8a86e043b8555663519caac214d1e3e3effbc6bee7f2bcf21e645f77de0ced279d69a8a8b +a9fc1c2061756b2a1a169c1b149f212ff7f0d2488acd1c5a0197eba793cffa593fc6d1d1b40718aa75ca3ec77eff10e1 +aff64f0fa009c7a6cf0b8d7a22ddb2c8170c3cb3eec082e60d5aadb00b0040443be8936d728d99581e33c22178c41c87 +82e0b181adc5e3b1c87ff8598447260e839d53debfae941ebea38265575546c3a74a14b4325a030833a62ff6c52d9365 +b7ad43cbb22f6f892c2a1548a41dc120ab1f4e1b8dea0cb6272dd9cb02054c542ecabc582f7e16de709d48f5166cae86 +985e0c61094281532c4afb788ecb2dfcba998e974b5d4257a22040a161883908cdd068fe80f8eb49b8953cfd11acf43a +ae46895c6d67ea6d469b6c9c07b9e5d295d9ae73b22e30da4ba2c973ba83a130d7eef39717ec9d0f36e81d56bf742671 +8600177ea1f7e7ef90514b38b219a37dedfc39cb83297e4c7a5b479817ef56479d48cf6314820960c751183f6edf8b0e +b9208ec1c1d7a1e99b59c62d3e4e61dfb706b0e940d09d3abfc3454c19749083260614d89cfd7e822596c3cdbcc6bb95 +a1e94042c796c2b48bc724352d2e9f3a22291d9a34705993357ddb6adabd76da6fc25dac200a8cb0b5bbd99ecddb7af6 +b29c3adedd0bcad8a930625bc4dfdc3552a9afd5ca6dd9c0d758f978068c7982b50b711aa0eb5b97f2b84ee784637835 +af0632a238bb1f413c7ea8e9b4c3d68f2827bd2e38cd56024391fba6446ac5d19a780d0cfd4a78fe497d537b766a591a +aaf6e7f7d54f8ef5e2e45dd59774ecbeecf8683aa70483b2a75be6a6071b5981bbaf1627512a65d212817acdfab2e428 +8c751496065da2e927cf492aa5ca9013b24f861d5e6c24b30bbf52ec5aaf1905f40f9a28175faef283dd4ed4f2182a09 +8952377d8e80a85cf67d6b45499f3bad5fd452ea7bcd99efc1b066c4720d8e5bff1214cea90fd1f972a7f0baac3d29be +a1946ee543d1a6e21f380453be4d446e4130950c5fc3d075794eb8260f6f52d0a795c1ff91d028a648dc1ce7d9ab6b47 +89f3fefe37af31e0c17533d2ca1ce0884cc1dc97c15cbfab9c331b8debd94781c9396abef4bb2f163d09277a08d6adf0 +a2753f1e6e1a154fb117100a5bd9052137add85961f8158830ac20541ab12227d83887d10acf7fd36dcaf7c2596d8d23 +814955b4198933ee11c3883863b06ff98c7eceb21fc3e09df5f916107827ccf3323141983e74b025f46ae00284c9513b +8cc5c6bb429073bfef47cae7b3bfccb0ffa076514d91a1862c6bda4d581e0df87db53cc6c130bf8a7826304960f5a34e +909f22c1f1cdc87f7be7439c831a73484a49acbf8f23d47087d7cf867c64ef61da3bde85dc57d705682b4c3fc710d36e +8048fee7f276fcd504aed91284f28e73693615e0eb3858fa44bcf79d7285a9001c373b3ef71d9a3054817ba293ebe28c +94400e5cf5d2700ca608c5fe35ce14623f71cc24959f2bc27ca3684092850f76b67fb1f07ca9e5b2ca3062cf8ad17bd4 +81c2ae7d4d1b17f8b6de6a0430acc0d58260993980fe48dc2129c4948269cdc74f9dbfbf9c26b19360823fd913083d48 +8c41fe765128e63f6889d6a979f6a4342300327c8b245a8cfe3ecfbcac1e09c3da30e2a1045b24b78efc6d6d50c8c6ac +a5dd4ae51ae48c8be4b218c312ade226cffce671cf121cb77810f6c0990768d6dd767badecb5c69921d5574d5e8433d3 +b7642e325f4ba97ae2a39c1c9d97b35aafd49d53dba36aed3f3cb0ca816480b3394079f46a48252d46596559c90f4d58 +ae87375b40f35519e7bd4b1b2f73cd0b329b0c2cb9d616629342a71c6c304338445eda069b78ea0fbe44087f3de91e09 +b08918cb6f736855e11d3daca1ddfbdd61c9589b203b5493143227bf48e2c77c2e8c94b0d1aa2fab2226e0eae83f2681 +ac36b84a4ac2ebd4d6591923a449c564e3be8a664c46092c09e875c2998eba16b5d32bfd0882fd3851762868e669f0b1 +a44800a3bb192066fa17a3f29029a23697240467053b5aa49b9839fb9b9b8b12bcdcbfc557f024b61f4f51a9aacdefcb +9064c688fec23441a274cdf2075e5a449caf5c7363cc5e8a5dc9747183d2e00a0c69f2e6b3f6a7057079c46014c93b3b +aa367b021469af9f5b764a79bb3afbe2d87fe1e51862221672d1a66f954b165778b7c27a705e0f93841fab4c8468344d +a1a8bfc593d4ab71f91640bc824de5c1380ab2591cfdafcbc78a14b32de3c0e15f9d1b461d85c504baa3d4232c16bb53 +97df48da1799430f528184d30b6baa90c2a2f88f34cdfb342d715339c5ebd6d019aa693cea7c4993daafc9849063a3aa +abd923831fbb427e06e0dd335253178a9e5791395c84d0ab1433c07c53c1209161097e9582fb8736f8a60bde62d8693e +84cd1a43f1a438b43dc60ffc775f646937c4f6871438163905a3cebf1115f814ccd38a6ccb134130bff226306e412f32 +91426065996b0743c5f689eb3ca68a9f7b9e4d01f6c5a2652b57fa9a03d8dc7cd4bdbdab0ca5a891fee1e97a7f00cf02 +a4bee50249db3df7fd75162b28f04e57c678ba142ce4d3def2bc17bcb29e4670284a45f218dad3969af466c62a903757 +83141ebcc94d4681404e8b67a12a46374fded6df92b506aff3490d875919631408b369823a08b271d006d5b93136f317 +a0ea1c8883d58d5a784da3d8c8a880061adea796d7505c1f903d07c287c5467f71e4563fc0faafbc15b5a5538b0a7559 +89d9d480574f201a87269d26fb114278ed2c446328df431dc3556e3500e80e4cd01fcac196a2459d8646361ebda840df +8bf302978973632dd464bec819bdb91304712a3ec859be071e662040620422c6e75eba6f864f764cffa2799272efec39 +922f666bc0fd58b6d7d815c0ae4f66d193d32fc8382c631037f59eeaeae9a8ca6c72d08e72944cf9e800b8d639094e77 +81ad8714f491cdff7fe4399f2eb20e32650cff2999dd45b9b3d996d54a4aba24cc6c451212e78c9e5550368a1a38fb3f +b58fcf4659d73edb73175bd9139d18254e94c3e32031b5d4b026f2ed37aa19dca17ec2eb54c14340231615277a9d347e +b365ac9c2bfe409b710928c646ea2fb15b28557e0f089d39878e365589b9d1c34baf5566d20bb28b33bb60fa133f6eff +8fcae1d75b53ab470be805f39630d204853ca1629a14158bac2f52632277d77458dec204ff84b7b2d77e641c2045be65 +a03efa6bebe84f4f958a56e2d76b5ba4f95dd9ed7eb479edc7cc5e646c8d4792e5b0dfc66cc86aa4b4afe2f7a4850760 +af1c823930a3638975fb0cc5c59651771b2719119c3cd08404fbd4ce77a74d708cefbe3c56ea08c48f5f10e6907f338f +8260c8299b17898032c761c325ac9cabb4c5b7e735de81eacf244f647a45fb385012f4f8df743128888c29aefcaaad16 +ab2f37a573c82e96a8d46198691cd694dfa860615625f477e41f91b879bc58a745784fccd8ffa13065834ffd150d881d +986c746c9b4249352d8e5c629e8d7d05e716b3c7aab5e529ca969dd1e984a14b5be41528baef4c85d2369a42d7209216 +b25e32da1a8adddf2a6080725818b75bc67240728ad1853d90738485d8924ea1e202df0a3034a60ffae6f965ec55cf63 +a266e627afcebcefea6b6b44cbc50f5c508f7187e87d047b0450871c2a030042c9e376f3ede0afcf9d1952f089582f71 +86c3bbca4c0300606071c0a80dbdec21ce1dd4d8d4309648151c420854032dff1241a1677d1cd5de4e4de4385efda986 +b9a21a1fe2d1f3273a8e4a9185abf2ff86448cc98bfa435e3d68306a2b8b4a6a3ea33a155be3cb62a2170a86f77679a5 +b117b1ea381adce87d8b342cba3a15d492ff2d644afa28f22424cb9cbc820d4f7693dfc1a4d1b3697046c300e1c9b4c8 +9004c425a2e68870d6c69b658c344e3aa3a86a8914ee08d72b2f95c2e2d8a4c7bb0c6e7e271460c0e637cec11117bf8e +86a18aa4783b9ebd9131580c8b17994825f27f4ac427b0929a1e0236907732a1c8139e98112c605488ee95f48bbefbfc +84042243b955286482ab6f0b5df4c2d73571ada00716d2f737ca05a0d2e88c6349e8ee9e67934cfee4a1775dbf7f4800 +92c2153a4733a62e4e1d5b60369f3c26777c7d01cd3c8679212660d572bd3bac9b8a8a64e1f10f7dbf5eaa7579c4e423 +918454b6bb8e44a2afa144695ba8d48ae08d0cdfef4ad078f67709eddf3bb31191e8b006f04e82ea45a54715ef4d5817 +acf0b54f6bf34cf6ed6c2b39cf43194a40d68de6bcf1e4b82c34c15a1343e9ac3737885e1a30b78d01fa3a5125463db8 +a7d60dbe4b6a7b054f7afe9ee5cbbfeca0d05dc619e6041fa2296b549322529faddb8a11e949562309aecefb842ac380 +91ffb53e6d7e5f11159eaf13e783d6dbdfdb1698ed1e6dbf3413c6ea23492bbb9e0932230a9e2caac8fe899a17682795 +b6e8d7be5076ee3565d5765a710c5ecf17921dd3cf555c375d01e958a365ae087d4a88da492a5fb81838b7b92bf01143 +a8c6b763de2d4b2ed42102ef64eccfef31e2fb2a8a2776241c82912fa50fc9f77f175b6d109a97ede331307c016a4b1a +99839f86cb700c297c58bc33e28d46b92931961548deac29ba8df91d3e11721b10ea956c8e16984f9e4acf1298a79b37 +8c2e2c338f25ea5c25756b7131cde0d9a2b35abf5d90781180a00fe4b8e64e62590dc63fe10a57fba3a31c76d784eb01 +9687d7df2f41319ca5469d91978fed0565a5f11f829ebadaa83db92b221755f76c6eacd7700735e75c91e257087512e3 +8795fdfb7ff8439c58b9bf58ed53873d2780d3939b902b9ddaaa4c99447224ced9206c3039a23c2c44bcc461e2bb637f +a803697b744d2d087f4e2307218d48fa88620cf25529db9ce71e2e3bbcc65bac5e8bb9be04777ef7bfb5ed1a5b8e6170 +80f3d3efbbb9346ddd413f0a8e36b269eb5d7ff6809d5525ff9a47c4bcab2c01b70018b117f6fe05253775612ff70c6b +9050e0e45bcc83930d4c505af35e5e4d7ca01cd8681cba92eb55821aececcebe32bb692ebe1a4daac4e7472975671067 +8d206812aac42742dbaf233e0c080b3d1b30943b54b60283515da005de05ea5caa90f91fedcfcba72e922f64d7040189 +a2d44faaeb2eff7915c83f32b13ca6f31a6847b1c1ce114ea240bac3595eded89f09b2313b7915ad882292e2b586d5b4 +961776c8576030c39f214ea6e0a3e8b3d32f023d2600958c098c95c8a4e374deeb2b9dc522adfbd6bda5949bdc09e2a2 +993fa7d8447407af0fbcd9e6d77f815fa5233ab00674efbcf74a1f51c37481445ae291cc7b76db7c178f9cb0e570e0fc +abd5b1c78e05f9d7c8cc99bdaef8b0b6a57f2daf0f02bf492bec48ea4a27a8f1e38b5854da96efff11973326ff980f92 +8f15af4764bc275e6ccb892b3a4362cacb4e175b1526a9a99944e692fe6ccb1b4fc19abf312bb2a089cb1f344d91a779 +a09b27ccd71855512aba1d0c30a79ffbe7f6707a55978f3ced50e674b511a79a446dbc6d7946add421ce111135a460af +94b2f98ce86a9271fbd4153e1fc37de48421fe3490fb3840c00f2d5a4d0ba8810c6a32880b002f6374b59e0a7952518b +8650ac644f93bbcb88a6a0f49fee2663297fd4bc6fd47b6a89b9d8038d32370438ab3a4775ec9b58cb10aea8a95ef7b6 +95e5c2f2e84eed88c6980bbba5a1c0bb375d5a628bff006f7516d45bb7d723da676add4fdd45956f312e7bab0f052644 +b3278a3fa377ac93af7cfc9453f8cb594aae04269bbc99d2e0e45472ff4b6a2f97a26c4c57bf675b9d86f5e77a5d55d1 +b4bcbe6eb666a206e2ea2f877912c1d3b5bdbd08a989fc4490eb06013e1a69ad1ba08bcdac048bf29192312be399077b +a76d70b78c99fffcbf9bb9886eab40f1ea4f99a309710b660b64cbf86057cbcb644d243f6e341711bb7ef0fedf0435a7 +b2093c1ee945dca7ac76ad5aed08eae23af31dd5a77c903fd7b6f051f4ab84425d33a03c3d45bf2907bc93c02d1f3ad8 +904b1f7534e053a265b22d20be859912b9c9ccb303af9a8d6f1d8f6ccdc5c53eb4a45a1762b880d8444d9be0cd55e7f9 +8f664a965d65bc730c9ef1ec7467be984d4b8eb46bd9b0d64e38e48f94e6e55dda19aeac82cbcf4e1473440e64c4ca18 +8bcee65c4cc7a7799353d07b114c718a2aae0cd10a3f22b7eead5185d159dafd64852cb63924bf87627d176228878bce +8c78f2e3675096fef7ebaa898d2615cd50d39ca3d8f02b9bdfb07e67da648ae4be3da64838dffc5935fd72962c4b96c7 +8c40afd3701629421fec1df1aac4e849384ef2e80472c0e28d36cb1327acdf2826f99b357f3d7afdbc58a6347fc40b3c +a197813b1c65a8ea5754ef782522a57d63433ef752215ecda1e7da76b0412ee619f58d904abd2e07e0c097048b6ae1dd +a670542629e4333884ad7410f9ea3bd6f988df4a8f8a424ca74b9add2312586900cf9ae8bd50411f9146e82626b4af56 +a19875cc07ab84e569d98b8b67fb1dbbdfb59093c7b748fae008c8904a6fd931a63ca8d03ab5fea9bc8d263568125a9b +b57e7f68e4eb1bd04aafa917b1db1bdab759a02aa8a9cdb1cba34ba8852b5890f655645c9b4e15d5f19bf37e9f2ffe9f +8abe4e2a4f6462b6c64b3f10e45db2a53c2b0d3c5d5443d3f00a453e193df771eda635b098b6c8604ace3557514027af +8459e4fb378189b22b870a6ef20183deb816cefbf66eca1dc7e86d36a2e011537db893729f500dc154f14ce24633ba47 +930851df4bc7913c0d8c0f7bd3b071a83668987ed7c397d3d042fdc0d9765945a39a3bae83da9c88cb6b686ed8aeeb26 +8078c9e5cd05e1a8c932f8a1d835f61a248b6e7133fcbb3de406bf4ffc0e584f6f9f95062740ba6008d98348886cf76b +addff62bb29430983fe578e3709b0949cdc0d47a13a29bc3f50371a2cb5c822ce53e2448cfaa01bcb6e0aa850d5a380e +9433add687b5a1e12066721789b1db2edf9b6558c3bdc0f452ba33b1da67426abe326e9a34d207bfb1c491c18811bde1 +822beda3389963428cccc4a2918fa9a8a51cf0919640350293af70821967108cded5997adae86b33cb917780b097f1ca +a7a9f52bda45e4148ed56dd176df7bd672e9b5ed18888ccdb405f47920fdb0844355f8565cefb17010b38324edd8315f +b35c3a872e18e607b2555c51f9696a17fa18da1f924d503b163b4ec9fe22ed0c110925275cb6c93ce2d013e88f173d6a +adf34b002b2b26ab84fc1bf94e05bd8616a1d06664799ab149363c56a6e0c807fdc473327d25632416e952ea327fcd95 +ae4a6b9d22a4a3183fac29e2551e1124a8ce4a561a9a2afa9b23032b58d444e6155bb2b48f85c7b6d70393274e230db7 +a2ea3be4fc17e9b7ce3110284038d46a09e88a247b6971167a7878d9dcf36925d613c382b400cfa4f37a3ebea3699897 +8e5863786b641ce3140fbfe37124d7ad3925472e924f814ebfc45959aaf3f61dc554a597610b5defaecc85b59a99b50f +aefde3193d0f700d0f515ab2aaa43e2ef1d7831c4f7859f48e52693d57f97fa9e520090f3ed700e1c966f4b76048e57f +841a50f772956622798e5cd208dc7534d4e39eddee30d8ce133383d66e5f267e389254a0cdae01b770ecd0a9ca421929 +8fbc2bfd28238c7d47d4c03b1b910946c0d94274a199575e5b23242619b1de3497784e646a92aa03e3e24123ae4fcaba +926999579c8eec1cc47d7330112586bdca20b4149c8b2d066f527c8b9f609e61ce27feb69db67eea382649c6905efcf9 +b09f31f305efcc65589adf5d3690a76cf339efd67cd43a4e3ced7b839507466e4be72dd91f04e89e4bbef629d46e68c0 +b917361f6b95f759642638e0b1d2b3a29c3bdef0b94faa30de562e6078c7e2d25976159df3edbacbf43614635c2640b4 +8e7e8a1253bbda0e134d62bfe003a2669d471b47bd2b5cde0ff60d385d8e62279d54022f5ac12053b1e2d3aaa6910b4c +b69671a3c64e0a99d90b0ed108ce1912ff8ed983e4bddd75a370e9babde25ee1f5efb59ec707edddd46793207a8b1fe7 +910b2f4ebd37b7ae94108922b233d0920b4aba0bd94202c70f1314418b548d11d8e9caa91f2cd95aff51b9432d122b7f +82f645c90dfb52d195c1020346287c43a80233d3538954548604d09fbab7421241cde8593dbc4acc4986e0ea39a27dd9 +8fee895f0a140d88104ce442fed3966f58ff9d275e7373483f6b4249d64a25fb5374bbdc6bce6b5ab0270c2847066f83 +84f5bd7aab27b2509397aeb86510dd5ac0a53f2c8f73799bf720f2f87a52277f8d6b0f77f17bc80739c6a7119b7eb062 +9903ceced81099d7e146e661bcf01cbaccab5ba54366b85e2177f07e2d8621e19d9c9c3eee14b9266de6b3f9b6ea75ae +b9c16ea2a07afa32dd6c7c06df0dec39bca2067a9339e45475c98917f47e2320f6f235da353fd5e15b477de97ddc68dd +9820a9bbf8b826bec61ebf886de2c4f404c1ebdc8bab82ee1fea816d9de29127ce1852448ff717a3fe8bbfe9e92012e5 +817224d9359f5da6f2158c2c7bf9165501424f063e67ba9859a07ab72ee2ee62eb00ca6da821cfa19065c3282ca72c74 +94b95c465e6cb00da400558a3c60cfec4b79b27e602ca67cbc91aead08de4b6872d8ea096b0dc06dca4525c8992b8547 +a2b539a5bccd43fa347ba9c15f249b417997c6a38c63517ca38394976baa08e20be384a360969ff54e7e721db536b3e5 +96caf707e34f62811ee8d32ccf28d8d6ec579bc33e424d0473529af5315c456fd026aa910c1fed70c91982d51df7d3ca +8a77b73e890b644c6a142bdbac59b22d6a676f3b63ddafb52d914bb9d395b8bf5aedcbcc90429337df431ebd758a07a6 +8857830a7351025617a08bc44caec28d2fae07ebf5ffc9f01d979ce2a53839a670e61ae2783e138313929129790a51a1 +aa3e420321ed6f0aa326d28d1a10f13facec6f605b6218a6eb9cbc074801f3467bf013a456d1415a5536f12599efa3d3 +824aed0951957b00ea2f3d423e30328a3527bf6714cf9abbae84cf27e58e5c35452ba89ccc011de7c68c75d6e021d8f1 +a2e87cc06bf202e953fb1081933d8b4445527dde20e38ed1a4f440144fd8fa464a2b73e068b140562e9045e0f4bd3144 +ae3b8f06ad97d7ae3a5e5ca839efff3e4824dc238c0c03fc1a8d2fc8aa546cdfd165b784a31bb4dec7c77e9305b99a4b +b30c3e12395b1fb8b776f3ec9f87c70e35763a7b2ddc68f0f60a4982a84017f27c891a98561c830038deb033698ed7fc +874e507757cd1177d0dff0b0c62ce90130324442a33da3b2c8ee09dbca5d543e3ecfe707e9f1361e7c7db641c72794bb +b53012dd10b5e7460b57c092eaa06d6502720df9edbbe3e3f61a9998a272bf5baaac4a5a732ad4efe35d6fac6feca744 +85e6509d711515534d394e6cacbed6c81da710074d16ef3f4950bf2f578d662a494d835674f79c4d6315bced4defc5f0 +b6132b2a34b0905dcadc6119fd215419a7971fe545e52f48b768006944b4a9d7db1a74b149e2951ea48c083b752d0804 +989867da6415036d19b4bacc926ce6f4df7a556f50a1ba5f3c48eea9cefbb1c09da81481c8009331ee83f0859185e164 +960a6c36542876174d3fbc1505413e29f053ed87b8d38fef3af180491c7eff25200b45dd5fe5d4d8e63c7e8c9c00f4c8 +9040b59bd739d9cc2e8f6e894683429e4e876a8106238689ff4c22770ae5fdae1f32d962b30301fa0634ee163b524f35 +af3fcd0a45fe9e8fe256dc7eab242ef7f582dd832d147444483c62787ac820fafc6ca55d639a73f76bfa5e7f5462ab8f +b934c799d0736953a73d91e761767fdb78454355c4b15c680ce08accb57ccf941b13a1236980001f9e6195801cffd692 +8871e8e741157c2c326b22cf09551e78da3c1ec0fc0543136f581f1550f8bab03b0a7b80525c1e99812cdbf3a9698f96 +a8a977f51473a91d178ee8cfa45ffef8d6fd93ab1d6e428f96a3c79816d9c6a93cd70f94d4deda0125fd6816e30f3bea +a7688b3b0a4fc1dd16e8ba6dc758d3cfe1b7cf401c31739484c7fa253cce0967df1b290769bcefc9d23d3e0cb19e6218 +8ae84322662a57c6d729e6ff9d2737698cc2da2daeb1f39e506618750ed23442a6740955f299e4a15dda6db3e534d2c6 +a04a961cdccfa4b7ef83ced17ab221d6a043b2c718a0d6cc8e6f798507a31f10bf70361f70a049bc8058303fa7f96864 +b463e39732a7d9daec8a456fb58e54b30a6e160aa522a18b9a9e836488cce3342bcbb2e1deab0f5e6ec0a8796d77197d +b1434a11c6750f14018a2d3bcf94390e2948f4f187e93bb22070ca3e5393d339dc328cbfc3e48815f51929465ffe7d81 +84ff81d73f3828340623d7e3345553610aa22a5432217ef0ebd193cbf4a24234b190c65ca0873c22d10ea7b63bd1fbed +b6fe2723f0c47757932c2ddde7a4f8434f665612f7b87b4009c2635d56b6e16b200859a8ade49276de0ef27a2b6c970a +9742884ed7cd52b4a4a068a43d3faa02551a424136c85a9313f7cb58ea54c04aa83b0728fd741d1fe39621e931e88f8f +b7d2d65ea4d1ad07a5dee39e40d6c03a61264a56b1585b4d76fc5b2a68d80a93a42a0181d432528582bf08d144c2d6a9 +88c0f66bada89f8a43e5a6ead2915088173d106c76f724f4a97b0f6758aed6ae5c37c373c6b92cdd4aea8f6261f3a374 +81f9c43582cb42db3900747eb49ec94edb2284999a499d1527f03315fd330e5a509afa3bff659853570e9886aab5b28b +821f9d27d6beb416abf9aa5c79afb65a50ed276dbda6060103bc808bcd34426b82da5f23e38e88a55e172f5c294b4d40 +8ba307b9e7cb63a6c4f3851b321aebfdb6af34a5a4c3bd949ff7d96603e59b27ff4dc4970715d35f7758260ff942c9e9 +b142eb6c5f846de33227d0bda61d445a7c33c98f0a8365fe6ab4c1fabdc130849be597ef734305894a424ea715372d08 +a732730ae4512e86a741c8e4c87fee8a05ee840fec0e23b2e037d58dba8dde8d10a9bc5191d34d00598941becbbe467f +adce6f7c30fd221f6b10a0413cc76435c4bb36c2d60bca821e5c67409fe9dbb2f4c36ef85eb3d734695e4be4827e9fd3 +a74f00e0f9b23aff7b2527ce69852f8906dab9d6abe62ecd497498ab21e57542e12af9918d4fd610bb09e10b0929c510 +a593b6b0ef26448ce4eb3ab07e84238fc020b3cb10d542ff4b16d4e2be1bcde3797e45c9cf753b8dc3b0ffdb63984232 +aed3913afccf1aa1ac0eb4980eb8426d0baccebd836d44651fd72af00d09fac488a870223c42aca3ceb39752070405ae +b2c44c66a5ea7fde626548ba4cef8c8710191343d3dadfd3bb653ce715c0e03056a5303a581d47dde66e70ea5a2d2779 +8e5029b2ccf5128a12327b5103f7532db599846e422531869560ceaff392236434d87159f597937dbf4054f810c114f4 +82beed1a2c4477e5eb39fc5b0e773b30cfec77ef2b1bf17eadaf60eb35b6d0dd9d8cf06315c48d3546badb3f21cd0cca +90077bd6cc0e4be5fff08e5d07a5a158d36cebd1d1363125bc4fae0866ffe825b26f933d4ee5427ba5cd0c33c19a7b06 +a7ec0d8f079970e8e34f0ef3a53d3e0e45428ddcef9cc776ead5e542ef06f3c86981644f61c5a637e4faf001fb8c6b3e +ae6d4add6d1a6f90b22792bc9d40723ee6850c27d0b97eefafd5b7fd98e424aa97868b5287cc41b4fbd7023bca6a322c +831aa917533d077da07c01417feaa1408846363ba2b8d22c6116bb858a95801547dd88b7d7fa1d2e3f0a02bdeb2e103d +96511b860b07c8a5ed773f36d4aa9d02fb5e7882753bf56303595bcb57e37ccc60288887eb83bef08c657ec261a021a2 +921d2a3e7e9790f74068623de327443666b634c8443aba80120a45bba450df920b2374d96df1ce3fb1b06dd06f8cf6e3 +aa74451d51fe82b4581ead8e506ec6cd881010f7e7dd51fc388eb9a557db5d3c6721f81c151d08ebd9c2591689fbc13e +a972bfbcf4033d5742d08716c927c442119bdae336bf5dff914523b285ccf31953da2733759aacaa246a9af9f698342c +ad1fcd0cae0e76840194ce4150cb8a56ebed728ec9272035f52a799d480dfc85840a4d52d994a18b6edb31e79be6e8ad +a2c69fe1d36f235215432dad48d75887a44c99dfa0d78149acc74087da215a44bdb5f04e6eef88ff7eff80a5a7decc77 +a94ab2af2b6ee1bc6e0d4e689ca45380d9fbd3c5a65b9bd249d266a4d4c07bf5d5f7ef2ae6000623aee64027892bf8fe +881ec1fc514e926cdc66480ac59e139148ff8a2a7895a49f0dff45910c90cdda97b66441a25f357d6dd2471cddd99bb3 +884e6d3b894a914c8cef946a76d5a0c8351843b2bffa2d1e56c6b5b99c84104381dd1320c451d551c0b966f4086e60f9 +817c6c10ce2677b9fc5223500322e2b880583254d0bb0d247d728f8716f5e05c9ff39f135854342a1afecd9fbdcf7c46 +aaf4a9cb686a14619aa1fc1ac285dd3843ac3dd99f2b2331c711ec87b03491c02f49101046f3c5c538dc9f8dba2a0ac2 +97ecea5ce53ca720b5d845227ae61d70269a2f53540089305c86af35f0898bfd57356e74a8a5e083fa6e1ea70080bd31 +a22d811e1a20a75feac0157c418a4bfe745ccb5d29466ffa854dca03e395b6c3504a734341746b2846d76583a780b32e +940cbaa0d2b2db94ae96b6b9cf2deefbfd059e3e5745de9aec4a25f0991b9721e5cd37ef71c631575d1a0c280b01cd5b +ae33cb4951191258a11044682de861bf8d92d90ce751b354932dd9f3913f542b6a0f8a4dc228b3cd9244ac32c4582832 +a580df5e58c4274fe0f52ac2da1837e32f5c9db92be16c170187db4c358f43e5cfdda7c5911dcc79d77a5764e32325f5 +81798178cb9d8affa424f8d3be67576ba94d108a28ccc01d330c51d5a63ca45bb8ca63a2f569b5c5fe1303cecd2d777f +89975b91b94c25c9c3660e4af4047a8bacf964783010820dbc91ff8281509379cb3b24c25080d5a01174dd9a049118d5 +a7327fcb3710ed3273b048650bde40a32732ef40a7e58cf7f2f400979c177944c8bc54117ba6c80d5d4260801dddab79 +92b475dc8cb5be4b90c482f122a51bcb3b6c70593817e7e2459c28ea54a7845c50272af38119406eaadb9bcb993368d0 +9645173e9ecefc4f2eae8363504f7c0b81d85f8949a9f8a6c01f2d49e0a0764f4eacecf3e94016dd407fc14494fce9f9 +9215fd8983d7de6ae94d35e6698226fc1454977ae58d42d294be9aad13ac821562ad37d5e7ee5cdfe6e87031d45cd197 +810360a1c9b88a9e36f520ab5a1eb8bed93f52deefbe1312a69225c0a08edb10f87cc43b794aced9c74220cefcc57e7d +ad7e810efd61ed4684aeda9ed8bb02fb9ae4b4b63fda8217d37012b94ff1b91c0087043bfa4e376f961fff030c729f3b +8b07c95c6a06db8738d10bb03ec11b89375c08e77f0cab7e672ce70b2685667ca19c7e1c8b092821d31108ea18dfd4c7 +968825d025ded899ff7c57245250535c732836f7565eab1ae23ee7e513201d413c16e1ba3f5166e7ac6cf74de8ceef4f +908243370c5788200703ade8164943ad5f8c458219186432e74dbc9904a701ea307fd9b94976c866e6c58595fd891c4b +959969d16680bc535cdc6339e6186355d0d6c0d53d7bbfb411641b9bf4b770fd5f575beef5deec5c4fa4d192d455c350 +ad177f4f826a961adeac76da40e2d930748effff731756c797eddc4e5aa23c91f070fb69b19221748130b0961e68a6bb +82f8462bcc25448ef7e0739425378e9bb8a05e283ce54aae9dbebaf7a3469f57833c9171672ad43a79778366c72a5e37 +a28fb275b1845706c2814d9638573e9bc32ff552ebaed761fe96fdbce70395891ca41c400ae438369264e31a2713b15f +8a9c613996b5e51dadb587a787253d6081ea446bf5c71096980bf6bd3c4b69905062a8e8a3792de2d2ece3b177a71089 +8d5aefef9f60cb27c1db2c649221204dda48bb9bf8bf48f965741da051340e8e4cab88b9d15c69f3f84f4c854709f48a +93ebf2ca6ad85ab6deace6de1a458706285b31877b1b4d7dcb9d126b63047efaf8c06d580115ec9acee30c8a7212fa55 +b3ee46ce189956ca298057fa8223b7fd1128cf52f39159a58bca03c71dd25161ac13f1472301f72aef3e1993fe1ab269 +a24d7a8d066504fc3f5027ccb13120e2f22896860e02c45b5eba1dbd512d6a17c28f39155ea581619f9d33db43a96f92 +ae9ceacbfe12137db2c1a271e1b34b8f92e4816bad1b3b9b6feecc34df0f8b3b0f7ed0133acdf59c537d43d33fc8d429 +83967e69bf2b361f86361bd705dce0e1ad26df06da6c52b48176fe8dfcbeb03c462c1a4c9e649eff8c654b18c876fdef +9148e6b814a7d779c19c31e33a068e97b597de1f8100513db3c581190513edc4d544801ce3dd2cf6b19e0cd6daedd28a +94ccdafc84920d320ed22de1e754adea072935d3c5f8c2d1378ebe53d140ea29853f056fb3fb1e375846061a038cc9bc +afb43348498c38b0fa5f971b8cdd3a62c844f0eb52bc33daf2f67850af0880fce84ecfb96201b308d9e6168a0d443ae3 +86d5736520a83538d4cd058cc4b4e84213ed00ebd6e7af79ae787adc17a92ba5359e28ba6c91936d967b4b28d24c3070 +b5210c1ff212c5b1e9ef9126e08fe120a41e386bb12c22266f7538c6d69c7fd8774f11c02b81fd4e88f9137b020801fe +b78cfd19f94d24e529d0f52e18ce6185cb238edc6bd43086270fd51dd99f664f43dd4c7d2fe506762fbd859028e13fcf +a6e7220598c554abdcc3fdc587b988617b32c7bb0f82c06205467dbedb58276cc07cae317a190f19d19078773f4c2bbb +b88862809487ee430368dccd85a5d72fa4d163ca4aad15c78800e19c1a95be2192719801e315d86cff7795e0544a77e4 +87ecb13a03921296f8c42ceb252d04716f10e09c93962239fcaa0a7fef93f19ab3f2680bc406170108bc583e9ff2e721 +a810cd473832b6581c36ec4cb403f2849357ba2d0b54df98ef3004b8a530c078032922a81d40158f5fb0043d56477f6e +a247b45dd85ca7fbb718b328f30a03f03c84aef2c583fbdc9fcc9eb8b52b34529e8c8f535505c10598b1b4dac3d7c647 +96ee0b91313c68bac4aa9e065ce9e1d77e51ca4cff31d6a438718c58264dee87674bd97fc5c6b8008be709521e4fd008 +837567ad073e42266951a9a54750919280a2ac835a73c158407c3a2b1904cf0d17b7195a393c71a18ad029cbd9cf79ee +a6a469c44b67ebf02196213e7a63ad0423aab9a6e54acc6fcbdbb915bc043586993454dc3cd9e4be8f27d67c1050879b +8712d380a843b08b7b294f1f06e2f11f4ad6bcc655fdde86a4d8bc739c23916f6fad2b902fe47d6212f03607907e9f0e +920adfb644b534789943cdae1bdd6e42828dda1696a440af2f54e6b97f4f97470a1c6ea9fa6a2705d8f04911d055acd1 +a161c73adf584a0061e963b062f59d90faac65c9b3a936b837a10d817f02fcabfa748824607be45a183dd40f991fe83f +874f4ecd408c76e625ea50bc59c53c2d930ee25baf4b4eca2440bfbffb3b8bc294db579caa7c68629f4d9ec24187c1ba +8bff18087f112be7f4aa654e85c71fef70eee8ae480f61d0383ff6f5ab1a0508f966183bb3fc4d6f29cb7ca234aa50d3 +b03b46a3ca3bc743a173cbc008f92ab1aedd7466b35a6d1ca11e894b9482ea9dc75f8d6db2ddd1add99bfbe7657518b7 +8b4f3691403c3a8ad9e097f02d130769628feddfa8c2b3dfe8cff64e2bed7d6e5d192c1e2ba0ac348b8585e94acd5fa1 +a0d9ca4a212301f97591bf65d5ef2b2664766b427c9dd342e23cb468426e6a56be66b1cb41fea1889ac5d11a8e3c50a5 +8c93ed74188ca23b3df29e5396974b9cc135c91fdefdea6c0df694c8116410e93509559af55533a3776ac11b228d69b1 +82dd331fb3f9e344ebdeeb557769b86a2cc8cc38f6c298d7572a33aea87c261afa9dbd898989139b9fc16bc1e880a099 +a65faedf326bcfd8ef98a51410c78b021d39206704e8291cd1f09e096a66b9b0486be65ff185ca224c45918ac337ddeb +a188b37d363ac072a766fd5d6fa27df07363feff1342217b19e3c37385e42ffde55e4be8355aceaa2f267b6d66b4ac41 +810fa3ba3e96d843e3bafd3f2995727f223d3567c8ba77d684c993ba1773c66551eb5009897c51b3fe9b37196984f5ec +87631537541852da323b4353af45a164f68b304d24c01183bf271782e11687f3fcf528394e1566c2a26cb527b3148e64 +b721cb2b37b3c477a48e3cc0044167d51ff568a5fd2fb606e5aec7a267000f1ddc07d3db919926ae12761a8e017c767c +904dfad4ba2cc1f6e60d1b708438a70b1743b400164cd981f13c064b8328d5973987d4fb9cf894068f29d3deaf624dfb +a70491538893552c20939fae6be2f07bfa84d97e2534a6bbcc0f1729246b831103505e9f60e97a8fa7d2e6c1c2384579 +8726cf1b26b41f443ff7485adcfddc39ace2e62f4d65dd0bb927d933e262b66f1a9b367ded5fbdd6f3b0932553ac1735 +ae8a11cfdf7aa54c08f80cb645e3339187ab3886babe9fae5239ba507bb3dd1c0d161ca474a2df081dcd3d63e8fe445e +92328719e97ce60e56110f30a00ac5d9c7a2baaf5f8d22355d53c1c77941e3a1fec7d1405e6fbf8959665fe2ba7a8cad +8d9d6255b65798d0018a8cccb0b6343efd41dc14ff2058d3eed9451ceaad681e4a0fa6af67b0a04318aa628024e5553d +b70209090055459296006742d946a513f0cba6d83a05249ee8e7a51052b29c0ca9722dc4af5f9816a1b7938a5dac7f79 +aab7b766b9bf91786dfa801fcef6d575dc6f12b77ecc662eb4498f0312e54d0de9ea820e61508fc8aeee5ab5db529349 +a8104b462337748b7f086a135d0c3f87f8e51b7165ca6611264b8fb639d9a2f519926cb311fa2055b5fadf03da70c678 +b0d2460747d5d8b30fc6c6bd0a87cb343ddb05d90a51b465e8f67d499cfc5e3a9e365da05ae233bbee792cdf90ec67d5 +aa55f5bf3815266b4a149f85ed18e451c93de9163575e3ec75dd610381cc0805bb0a4d7c4af5b1f94d10231255436d2c +8d4c6a1944ff94426151909eb5b99cfd92167b967dabe2bf3aa66bb3c26c449c13097de881b2cfc1bf052862c1ef7b03 +8862296162451b9b6b77f03bf32e6df71325e8d7485cf3335d66fd48b74c2a8334c241db8263033724f26269ad95b395 +901aa96deb26cda5d9321190ae6624d357a41729d72ef1abfd71bebf6139af6d690798daba53b7bc5923462115ff748a +96c195ec4992728a1eb38cdde42d89a7bce150db43adbc9e61e279ea839e538deec71326b618dd39c50d589f78fc0614 +b6ff8b8aa0837b99a1a8b46fb37f20ad4aecc6a98381b1308697829a59b8442ffc748637a88cb30c9b1f0f28a926c4f6 +8d807e3dca9e7bef277db1d2cfb372408dd587364e8048b304eff00eacde2c723bfc84be9b98553f83cba5c7b3cba248 +8800c96adb0195c4fc5b24511450dee503c32bf47044f5e2e25bd6651f514d79a2dd9b01cd8c09f3c9d3859338490f57 +89fe366096097e38ec28dd1148887112efa5306cc0c3da09562aafa56f4eb000bf46ff79bf0bdd270cbde6bf0e1c8957 +af409a90c2776e1e7e3760b2042507b8709e943424606e31e791d42f17873a2710797f5baaab4cc4a19998ef648556b0 +8d761863c9b6edbd232d35ab853d944f5c950c2b643f84a1a1327ebb947290800710ff01dcfa26dc8e9828481240e8b1 +90b95e9be1e55c463ed857c4e0617d6dc3674e99b6aa62ed33c8e79d6dfcf7d122f4f4cc2ee3e7c5a49170cb617d2e2e +b3ff381efefabc4db38cc4727432e0301949ae4f16f8d1dea9b4f4de611cf5a36d84290a0bef160dac4e1955e516b3b0 +a8a84564b56a9003adcadb3565dc512239fc79572762cda7b5901a255bc82656bb9c01212ad33d6bef4fbbce18dacc87 +90a081890364b222eef54bf0075417f85e340d2fec8b7375995f598aeb33f26b44143ebf56fca7d8b4ebb36b5747b0eb +ade6ee49e1293224ddf2d8ab7f14bb5be6bc6284f60fd5b3a1e0cf147b73cff57cf19763b8a36c5083badc79c606b103 +b2fa99806dd2fa3de09320b615a2570c416c9bcdb052e592b0aead748bbe407ec9475a3d932ae48b71c2627eb81986a6 +91f3b7b73c8ccc9392542711c45fe6f236057e6efad587d661ad5cb4d6e88265f86b807bb1151736b1009ab74fd7acb4 +8800e2a46af96696dfbdcbf2ca2918b3dcf28ad970170d2d1783b52b8d945a9167d052beeb55f56c126da7ffa7059baa +9862267a1311c385956b977c9aa08548c28d758d7ba82d43dbc3d0a0fd1b7a221d39e8399997fea9014ac509ff510ac4 +b7d24f78886fd3e2d283e18d9ad5a25c1a904e7d9b9104bf47da469d74f34162e27e531380dbbe0a9d051e6ffd51d6e7 +b0f445f9d143e28b9df36b0f2c052da87ee2ca374d9d0fbe2eff66ca6fe5fe0d2c1951b428d58f7314b7e74e45d445ea +b63fc4083eabb8437dafeb6a904120691dcb53ce2938b820bb553da0e1eecd476f72495aacb72600cf9cad18698fd3db +b9ffd8108eaebd582d665f8690fe8bb207fd85185e6dd9f0b355a09bac1bbff26e0fdb172bc0498df025414e88fe2eda +967ed453e1f1a4c5b7b6834cc9f75c13f6889edc0cc91dc445727e9f408487bbf05c337103f61397a10011dfbe25d61d +98ceb673aff36e1987d5521a3984a07079c3c6155974bb8b413e8ae1ce84095fe4f7862fba7aefa14753eb26f2a5805f +85f01d28603a8fdf6ce6a50cb5c44f8a36b95b91302e3f4cd95c108ce8f4d212e73aec1b8d936520d9226802a2bd9136 +88118e9703200ca07910345fbb789e7a8f92bd80bbc79f0a9e040e8767d33df39f6eded403a9b636eabf9101e588482a +90833a51eef1b10ed74e8f9bbd6197e29c5292e469c854eed10b0da663e2bceb92539710b1858bbb21887bd538d28d89 +b513b905ec19191167c6193067b5cfdf5a3d3828375360df1c7e2ced5815437dfd37f0c4c8f009d7fb29ff3c8793f560 +b1b6d405d2d18f9554b8a358cc7e2d78a3b34269737d561992c8de83392ac9a2857be4bf15de5a6c74e0c9d0f31f393c +b828bd3e452b797323b798186607849f85d1fb20c616833c0619360dfd6b3e3aa000fd09dafe4b62d74abc41072ff1a9 +8efde67d0cca56bb2c464731879c9ac46a52e75bac702a63200a5e192b4f81c641f855ca6747752b84fe469cb7113b6c +b2762ba1c89ac3c9a983c242e4d1c2610ff0528585ed5c0dfc8a2c0253551142af9b59f43158e8915a1da7cc26b9df67 +8a3f1157fb820d1497ef6b25cd70b7e16bb8b961b0063ad340d82a79ee76eb2359ca9e15e6d42987ed7f154f5eeaa2da +a75e29f29d38f09c879f971c11beb5368affa084313474a5ecafa2896180b9e47ea1995c2733ec46f421e395a1d9cffe +8e8c3dd3e7196ef0b4996b531ec79e4a1f211db5d5635e48ceb80ff7568b2ff587e845f97ee703bb23a60945ad64314a +8e7f32f4a3e3c584af5e3d406924a0aa34024c42eca74ef6cc2a358fd3c9efaf25f1c03aa1e66bb94b023a2ee2a1cace +ab7dce05d59c10a84feb524fcb62478906b3fa045135b23afbede3bb32e0c678d8ebe59feabccb5c8f3550ea76cae44b +b38bb4b44d827f6fd3bd34e31f9186c59e312dbfadd4a7a88e588da10146a78b1f8716c91ad8b806beb8da65cab80c4c +9490ce9442bbbd05438c7f5c4dea789f74a7e92b1886a730544b55ba377840740a3ae4f2f146ee73f47c9278b0e233bc +83c003fab22a7178eed1a668e0f65d4fe38ef3900044e9ec63070c23f2827d36a1e73e5c2b883ec6a2afe2450171b3b3 +9982f02405978ddc4fca9063ebbdb152f524c84e79398955e66fe51bc7c1660ec1afc3a86ec49f58d7b7dde03505731c +ab337bd83ccdd2322088ffa8d005f450ced6b35790f37ab4534313315ee84312adc25e99cce052863a8bedee991729ed +8312ce4bec94366d88f16127a17419ef64285cd5bf9e5eda010319b48085966ed1252ed2f5a9fd3e0259b91bb65f1827 +a60d5a6327c4041b0c00a1aa2f0af056520f83c9ce9d9ccd03a0bd4d9e6a1511f26a422ea86bd858a1f77438adf07e6c +b84a0a0b030bdad83cf5202aa9afe58c9820e52483ab41f835f8c582c129ee3f34aa096d11c1cd922eda02ea1196a882 +8077d105317f4a8a8f1aadeb05e0722bb55f11abcb490c36c0904401107eb3372875b0ac233144829e734f0c538d8c1d +9202503bd29a6ec198823a1e4e098f9cfe359ed51eb5174d1ca41368821bfeebcbd49debfd02952c41359d1c7c06d2b1 +abc28c155e09365cb77ffead8dc8f602335ef93b2f44e4ef767ce8fc8ef9dd707400f3a722e92776c2e0b40192c06354 +b0f6d1442533ca45c9399e0a63a11f85ff288d242cea6cb3b68c02e77bd7d158047cae2d25b3bcd9606f8f66d9b32855 +b01c3d56a0db84dc94575f4b6ee2de4beca3230e86bed63e2066beb22768b0a8efb08ebaf8ac3dedb5fe46708b084807 +8c8634b0432159f66feaabb165842d1c8ac378f79565b1b90c381aa8450eb4231c3dad11ec9317b9fc2b155c3a771e32 +8e67f623d69ecd430c9ee0888520b6038f13a2b6140525b056dc0951f0cfed2822e62cf11d952a483107c5c5acac4826 +9590bb1cba816dd6acd5ac5fba5142c0a19d53573e422c74005e0bcf34993a8138c83124cad35a3df65879dba6134edd +801cd96cde0749021a253027118d3ea135f3fcdbe895db08a6c145641f95ebd368dd6a1568d995e1d0084146aebe224a +848b5d196427f6fc1f762ee3d36e832b64a76ec1033cfedc8b985dea93932a7892b8ef1035c653fb9dcd9ab2d9a44ac8 +a1017eb83d5c4e2477e7bd2241b2b98c4951a3b391081cae7d75965cadc1acaec755cf350f1f3d29741b0828e36fedea +8d6d2785e30f3c29aad17bd677914a752f831e96d46caf54446d967cb2432be2c849e26f0d193a60bee161ea5c6fe90a +935c0ba4290d4595428e034b5c8001cbd400040d89ab00861108e8f8f4af4258e41f34a7e6b93b04bc253d3b9ffc13bf +aac02257146246998477921cef2e9892228590d323b839f3e64ea893b991b463bc2f47e1e5092ddb47e70b2f5bce7622 +b921fde9412970a5d4c9a908ae8ce65861d06c7679af577cf0ad0d5344c421166986bee471fd6a6cecb7d591f06ec985 +8ef4c37487b139d6756003060600bb6ebac7ea810b9c4364fc978e842f13ac196d1264fbe5af60d76ff6d9203d8e7d3f +94b65e14022b5cf6a9b95f94be5ace2711957c96f4211c3f7bb36206bd39cfbd0ea82186cab5ad0577a23214a5c86e9e +a31c166d2a2ca1d5a75a5920fef7532681f62191a50d8555fdaa63ba4581c3391cc94a536fc09aac89f64eafceec3f90 +919a8cc128de01e9e10f5d83b08b52293fdd41bde2b5ae070f3d95842d4a16e5331cf2f3d61c765570c8022403610fa4 +b23d6f8331eef100152d60483cfa14232a85ee712c8538c9b6417a5a7c5b353c2ac401390c6c215cb101f5cee6b5f43e +ab357160c08a18319510a571eafff154298ce1020de8e1dc6138a09fcb0fcbcdd8359f7e9386bda00b7b9cdea745ffdc +ab55079aea34afa5c0bd1124b9cdfe01f325b402fdfa017301bf87812eaa811ea5798c3aaf818074d420d1c782b10ada +ade616010dc5009e7fc4f8d8b00dc716686a5fa0a7816ad9e503e15839d3b909b69d9dd929b7575376434ffec0d2bea8 +863997b97ed46898a8a014599508fa3079f414b1f4a0c4fdc6d74ae8b444afa350f327f8bfc2a85d27f9e2d049c50135 +8d602ff596334efd4925549ed95f2aa762b0629189f0df6dbb162581657cf3ea6863cd2287b4d9c8ad52813d87fcd235 +b70f68c596dcdeed92ad5c6c348578b26862a51eb5364237b1221e840c47a8702f0fbc56eb520a22c0eed99795d3903e +9628088f8e0853cefadee305a8bf47fa990c50fa96a82511bbe6e5dc81ef4b794e7918a109070f92fc8384d77ace226f +97e26a46e068b605ce96007197ecd943c9a23881862f4797a12a3e96ba2b8d07806ad9e2a0646796b1889c6b7d75188c +b1edf467c068cc163e2d6413cc22b16751e78b3312fe47b7ea82b08a1206d64415b2c8f2a677fa89171e82cc49797150 +a44d15ef18745b251429703e3cab188420e2d974de07251501799b016617f9630643fcd06f895634d8ecdd579e1bf000 +abd126df3917ba48c618ee4dbdf87df506193462f792874439043fa1b844466f6f4e0ff2e42516e63b5b23c0892b2695 +a2a67f57c4aa3c2aa1eeddbfd5009a89c26c2ce8fa3c96a64626aba19514beb125f27df8559506f737de3eae0f1fc18f +a633e0132197e6038197304b296ab171f1d8e0d0f34dcf66fe9146ac385b0239232a8470b9205a4802ab432389f4836d +a914b3a28509a906c3821463b936455d58ff45dcbe158922f9efb2037f2eb0ce8e92532d29b5d5a3fcd0d23fa773f272 +a0e1412ce4505daf1a2e59ce4f0fc0e0023e335b50d2b204422f57cd65744cc7a8ed35d5ef131a42c70b27111d3115b7 +a2339e2f2b6072e88816224fdd612c04d64e7967a492b9f8829db15367f565745325d361fd0607b0def1be384d010d9e +a7309fc41203cb99382e8193a1dcf03ac190a7ce04835304eb7e341d78634e83ea47cb15b885601956736d04cdfcaa01 +81f3ccd6c7f5b39e4e873365f8c37b214e8ab122d04a606fbb7339dc3298c427e922ec7418002561d4106505b5c399ee +92c121cf914ca549130e352eb297872a63200e99b148d88fbc9506ad882bec9d0203d65f280fb5b0ba92e336b7f932e8 +a4b330cf3f064f5b131578626ad7043ce2a433b6f175feb0b52d36134a454ca219373fd30d5e5796410e005b69082e47 +86fe5774112403ad83f9c55d58317eeb17ad8e1176d9f2f69c2afb7ed83bc718ed4e0245ceab4b377f5f062dcd4c00e7 +809d152a7e2654c7fd175b57f7928365a521be92e1ed06c05188a95864ddb25f7cab4c71db7d61bbf4cae46f3a1d96ce +b82d663e55c2a5ada7e169e9b1a87bc1c0177baf1ec1c96559b4cb1c5214ce1ddf2ab8d345014cab6402f3774235cf5a +86580af86df1bd2c385adb8f9a079e925981b7184db66fc5fe5b14cddb82e7d836b06eaeef14924ac529487b23dae111 +b5f5f4c5c94944ecc804df6ab8687d64e27d988cbfeae1ba7394e0f6adbf778c5881ead7cd8082dd7d68542b9bb4ecd5 +a6016916146c2685c46e8fdd24186394e2d5496e77e08c0c6a709d4cd7dfa97f1efcef94922b89196819076a91ad37b5 +b778e7367ded3b6eab53d5fc257f7a87e8faf74a593900f2f517220add2125be3f6142022660d8181df8d164ad9441ce +8581b2d36abe6f553add4d24be761bec1b8efaa2929519114346615380b3c55b59e6ad86990e312f7e234d0203bdf59b +9917e74fd45c3f71a829ff5498a7f6b5599b48c098dda2339bf04352bfc7f368ccf1a407f5835901240e76452ae807d7 +afd196ce6f9335069138fd2e3d133134da253978b4ce373152c0f26affe77a336505787594022e610f8feb722f7cc1fb +a477491a1562e329764645e8f24d8e228e5ef28c9f74c6b5b3abc4b6a562c15ffb0f680d372aed04d9e1bf944dece7be +9767440d58c57d3077319d3a330e5322b9ba16981ec74a5a14d53462eab59ae7fd2b14025bfc63b268862094acb444e6 +80986d921be3513ef69264423f351a61cb48390c1be8673aee0f089076086aaebea7ebe268fd0aa7182695606116f679 +a9554c5c921c07b450ee04e34ec58e054ac1541b26ce2ce5a393367a97348ba0089f53db6660ad76b60278b66fd12e3e +95097e7d2999b3e84bf052c775581cf361325325f4a50192521d8f4693c830bed667d88f482dc1e3f833aa2bd22d2cbf +9014c91d0f85aefd28436b5228c12f6353c055a9326c7efbf5e071e089e2ee7c070fcbc84c5fafc336cbb8fa6fec1ca1 +90f57ba36ee1066b55d37384942d8b57ae00f3cf9a3c1d6a3dfee1d1af42d4b5fa9baeb0cd7e46687d1d6d090ddb931d +8e4b1db12fd760a17214c9e47f1fce6e43c0dbb4589a827a13ac61aaae93759345697bb438a00edab92e0b7b62414683 +8022a959a513cdc0e9c705e0fc04eafd05ff37c867ae0f31f6d01cddd5df86138a426cab2ff0ac8ff03a62e20f7e8f51 +914e9a38829834c7360443b8ed86137e6f936389488eccf05b4b4db7c9425611705076ecb3f27105d24b85c852be7511 +957fb10783e2bd0db1ba66b18e794df710bc3b2b05776be146fa5863c15b1ebdd39747b1a95d9564e1772cdfc4f37b8a +b6307028444daed8ed785ac9d0de76bc3fe23ff2cc7e48102553613bbfb5afe0ebe45e4212a27021c8eb870721e62a1f +8f76143597777d940b15a01b39c5e1b045464d146d9a30a6abe8b5d3907250e6c7f858ff2308f8591e8b0a7b3f3c568a +96163138ac0ce5fd00ae9a289648fd9300a0ca0f63a88481d703ecd281c06a52a3b5178e849e331f9c85ca4ba398f4cc +a63ef47c3e18245b0482596a09f488a716df3cbd0f9e5cfabed0d742843e65db8961c556f45f49762f3a6ac8b627b3ef +8cb595466552e7c4d42909f232d4063e0a663a8ef6f6c9b7ce3a0542b2459cde04e0e54c7623d404acb5b82775ac04f6 +b47fe69960eb45f399368807cff16d941a5a4ebad1f5ec46e3dc8a2e4d598a7e6114d8f0ca791e9720fd786070524e2b +89eb5ff83eea9df490e5beca1a1fbbbbcf7184a37e2c8c91ede7a1e654c81e8cd41eceece4042ea7918a4f4646b67fd6 +a84f5d155ed08b9054eecb15f689ba81e44589e6e7207a99790c598962837ca99ec12344105b16641ca91165672f7153 +a6cc8f25c2d5b2d2f220ec359e6a37a52b95fa6af6e173c65e7cd55299eff4aa9e6d9e6f2769e6459313f1f2aecb0fab +afcde944411f017a9f7979755294981e941cc41f03df5e10522ef7c7505e5f1babdd67b3bf5258e8623150062eb41d9b +8fab39f39c0f40182fcd996ade2012643fe7731808afbc53f9b26900b4d4d1f0f5312d9d40b3df8baa4739970a49c732 +ae193af9726da0ebe7df1f9ee1c4846a5b2a7621403baf8e66c66b60f523e719c30c6b4f897bb14b27d3ff3da8392eeb +8ac5adb82d852eba255764029f42e6da92dcdd0e224d387d1ef94174038db9709ac558d90d7e7c57ad4ce7f89bbfc38c +a2066b3458fdf678ee487a55dd5bfb74fde03b54620cb0e25412a89ee28ad0d685e309a51e3e4694be2fa6f1593a344c +88d031745dd0ae07d61a15b594be5d4b2e2a29e715d081649ad63605e3404b0c3a5353f0fd9fad9c05c18e93ce674fa1 +8283cfb0ef743a043f2b77ecaeba3005e2ca50435585b5dd24777ee6bce12332f85e21b446b536da38508807f0f07563 +b376de22d5f6b0af0b59f7d9764561f4244cf8ffe22890ecd3dcf2ff1832130c9b821e068c9d8773136f4796721e5963 +ae3afc50c764f406353965363840bf28ee85e7064eb9d5f0bb3c31c64ab10f48c853e942ee2c9b51bae59651eaa08c2f +948b204d103917461a01a6c57a88f2d66b476eae5b00be20ec8c747650e864bc8a83aee0aff59cb7584b7a3387e0ee48 +81ab098a082b07f896c5ffd1e4446cb7fb44804cbbf38d125208b233fc82f8ec9a6a8d8dd1c9a1162dc28ffeec0dde50 +a149c6f1312821ced2969268789a3151bdda213451760b397139a028da609c4134ac083169feb0ee423a0acafd10eceb +b0ac9e27a5dadaf523010f730b28f0ebac01f460d3bbbe277dc9d44218abb5686f4fac89ae462682fef9edbba663520a +8d0e0073cca273daaaa61b6fc54bfe5a009bc3e20ae820f6c93ba77b19eca517d457e948a2de5e77678e4241807157cb +ad61d3a2edf7c7533a04964b97499503fd8374ca64286dba80465e68fe932e96749b476f458c6fc57cb1a7ca85764d11 +90eb5e121ae46bc01a30881eaa556f46bd8457a4e80787cf634aab355082de34ac57d7f497446468225f7721e68e2a47 +8cdac557de7c42d1f3780e33dec1b81889f6352279be81c65566cdd4952d4c15d79e656cbd46035ab090b385e90245ef +82b67e61b88b84f4f4d4f65df37b3e3dcf8ec91ea1b5c008fdccd52da643adbe6468a1cfdb999e87d195afe2883a3b46 +8503b467e8f5d6048a4a9b78496c58493a462852cab54a70594ae3fd064cfd0deb4b8f336a262155d9fedcaa67d2f6fd +8db56c5ac763a57b6ce6832930c57117058e3e5a81532b7d19346346205e2ec614eb1a2ee836ef621de50a7bc9b7f040 +ad344699198f3c6e8c0a3470f92aaffc805b76266734414c298e10b5b3797ca53578de7ccb2f458f5e0448203f55282b +80602032c43c9e2a09154cc88b83238343b7a139f566d64cb482d87436b288a98f1ea244fd3bff8da3c398686a900c14 +a6385bd50ecd548cfb37174cdbb89e10025b5cadaf3cff164c95d7aef5a33e3d6a9bf0c681b9e11db9ef54ebeee2a0c1 +abf2d95f4aa34b0581eb9257a0cc8462b2213941a5deb8ba014283293e8b36613951b61261cc67bbd09526a54cbbff76 +a3d5de52f48df72c289ff713e445991f142390798cd42bd9d9dbefaee4af4f5faf09042d126b975cf6b98711c3072553 +8e627302ff3d686cff8872a1b7c2a57b35f45bf2fc9aa42b049d8b4d6996a662b8e7cbac6597f0cb79b0cc4e29fbf133 +8510702e101b39a1efbf4e504e6123540c34b5689645e70d0bac1ecc1baf47d86c05cef6c4317a4e99b4edaeb53f2d00 +aa173f0ecbcc6088f878f8726d317748c81ebf501bba461f163b55d66099b191ec7c55f7702f351a9c8eb42cfa3280e2 +b560a697eafab695bcef1416648a0a664a71e311ecbe5823ae903bd0ed2057b9d7574b9a86d3fe22aa3e6ddce38ea513 +8df6304a3d9cf40100f3f687575419c998cd77e5cc27d579cf4f8e98642de3609af384a0337d145dd7c5635172d26a71 +8105c7f3e4d30a29151849673853b457c1885c186c132d0a98e63096c3774bc9deb956cf957367e633d0913680bda307 +95373fc22c0917c3c2044ac688c4f29a63ed858a45c0d6d2d0fe97afd6f532dcb648670594290c1c89010ecc69259bef +8c2fae9bcadab341f49b55230310df93cac46be42d4caa0d42e45104148a91e527af1b4209c0d972448162aed28fab64 +b05a77baab70683f76209626eaefdda2d36a0b66c780a20142d23c55bd479ddd4ad95b24579384b6cf62c8eb4c92d021 +8e6bc6a7ea2755b4aaa19c1c1dee93811fcde514f03485fdc3252f0ab7f032c315614f6336e57cea25dcfb8fb6084eeb +b656a27d06aade55eadae2ad2a1059198918ea6cc3fd22c0ed881294d34d5ac7b5e4700cc24350e27d76646263b223aa +a296469f24f6f56da92d713afcd4dd606e7da1f79dc4e434593c53695847eefc81c7c446486c4b3b8c8d00c90c166f14 +87a326f57713ac2c9dffeb3af44b9f3c613a8f952676fc46343299122b47ee0f8d792abaa4b5db6451ced5dd153aabd0 +b689e554ba9293b9c1f6344a3c8fcb6951d9f9eac4a2e2df13de021aade7c186be27500e81388e5b8bcab4c80f220a31 +87ae0aa0aa48eac53d1ca5a7b93917de12db9e40ceabf8fdb40884ae771cfdf095411deef7c9f821af0b7070454a2608 +a71ffa7eae8ace94e6c3581d4cb2ad25d48cbd27edc9ec45baa2c8eb932a4773c3272b2ffaf077b40f76942a1f3af7f2 +94c218c91a9b73da6b7a495b3728f3028df8ad9133312fc0c03e8c5253b7ccb83ed14688fd4602e2fd41f29a0bc698bd +ae1e77b90ca33728af07a4c03fb2ef71cd92e2618e7bf8ed4d785ce90097fc4866c29999eb84a6cf1819d75285a03af2 +b7a5945b277dab9993cf761e838b0ac6eaa903d7111fca79f9fde3d4285af7a89bf6634a71909d095d7619d913972c9c +8c43b37be02f39b22029b20aca31bff661abce4471dca88aa3bddefd9c92304a088b2dfc8c4795acc301ca3160656af2 +b32e5d0fba024554bd5fe8a793ebe8003335ddd7f585876df2048dcf759a01285fecb53daae4950ba57f3a282a4d8495 +85ea7fd5e10c7b659df5289b2978b2c89e244f269e061b9a15fcab7983fc1962b63546e82d5731c97ec74b6804be63ef +96b89f39181141a7e32986ac02d7586088c5a9662cec39843f397f3178714d02f929af70630c12cbaba0268f8ba2d4fa +929ab1a2a009b1eb37a2817c89696a06426529ebe3f306c586ab717bd34c35a53eca2d7ddcdef36117872db660024af9 +a696dccf439e9ca41511e16bf3042d7ec0e2f86c099e4fc8879d778a5ea79e33aa7ce96b23dc4332b7ba26859d8e674d +a8fe69a678f9a194b8670a41e941f0460f6e2dbc60470ab4d6ae2679cc9c6ce2c3a39df2303bee486dbfde6844e6b31a +95f58f5c82de2f2a927ca99bf63c9fc02e9030c7e46d0bf6b67fe83a448d0ae1c99541b59caf0e1ccab8326231af09a5 +a57badb2c56ca2c45953bd569caf22968f76ed46b9bac389163d6fe22a715c83d5e94ae8759b0e6e8c2f27bff7748f3f +868726fd49963b24acb5333364dffea147e98f33aa19c7919dc9aca0fd26661cfaded74ede7418a5fadbe7f5ae67b67b +a8d8550dcc64d9f1dd7bcdab236c4122f2b65ea404bb483256d712c7518f08bb028ff8801f1da6aed6cbfc5c7062e33b +97e25a87dae23155809476232178538d4bc05d4ff0882916eb29ae515f2a62bfce73083466cc0010ca956aca200aeacc +b4ea26be3f4bd04aa82d7c4b0913b97bcdf5e88b76c57eb1a336cbd0a3eb29de751e1bc47c0e8258adec3f17426d0c71 +99ee555a4d9b3cf2eb420b2af8e3bc99046880536116d0ce7193464ac40685ef14e0e3c442f604e32f8338cb0ef92558 +8c64efa1da63cd08f319103c5c7a761221080e74227bbc58b8fb35d08aa42078810d7af3e60446cbaff160c319535648 +8d9fd88040076c28420e3395cbdfea402e4077a3808a97b7939d49ecbcf1418fe50a0460e1c1b22ac3f6e7771d65169a +ae3c19882d7a9875d439265a0c7003c8d410367627d21575a864b9cb4918de7dbdb58a364af40c5e045f3df40f95d337 +b4f7bfacab7b2cafe393f1322d6dcc6f21ffe69cd31edc8db18c06f1a2b512c27bd0618091fd207ba8df1808e9d45914 +94f134acd0007c623fb7934bcb65ef853313eb283a889a3ffa79a37a5c8f3665f3d5b4876bc66223610c21dc9b919d37 +aa15f74051171daacdc1f1093d3f8e2d13da2833624b80a934afec86fc02208b8f55d24b7d66076444e7633f46375c6a +a32d6bb47ef9c836d9d2371807bafbbbbb1ae719530c19d6013f1d1f813c49a60e4fa51d83693586cba3a840b23c0404 +b61b3599145ea8680011aa2366dc511a358b7d67672d5b0c5be6db03b0efb8ca5a8294cf220ea7409621f1664e00e631 +859cafc3ee90b7ececa1ed8ef2b2fc17567126ff10ca712d5ffdd16aa411a5a7d8d32c9cab1fbf63e87dce1c6e2f5f53 +a2fef1b0b2874387010e9ae425f3a9676d01a095d017493648bcdf3b31304b087ccddb5cf76abc4e1548b88919663b6b +939e18c73befc1ba2932a65ede34c70e4b91e74cc2129d57ace43ed2b3af2a9cc22a40fbf50d79a63681b6d98852866d +b3b4259d37b1b14aee5b676c9a0dd2d7f679ab95c120cb5f09f9fbf10b0a920cb613655ddb7b9e2ba5af4a221f31303c +997255fe51aaca6e5a9cb3359bcbf25b2bb9e30649bbd53a8a7c556df07e441c4e27328b38934f09c09d9500b5fabf66 +abb91be2a2d860fd662ed4f1c6edeefd4da8dc10e79251cf87f06029906e7f0be9b486462718f0525d5e049472692cb7 +b2398e593bf340a15f7801e1d1fbda69d93f2a32a889ec7c6ae5e8a37567ac3e5227213c1392ee86cfb3b56ec2787839 +8ddf10ccdd72922bed36829a36073a460c2118fc7a56ff9c1ac72581c799b15c762cb56cb78e3d118bb9f6a7e56cb25e +93e6bc0a4708d16387cacd44cf59363b994dc67d7ada7b6d6dbd831c606d975247541b42b2a309f814c1bfe205681fc6 +b93fc35c05998cffda2978e12e75812122831523041f10d52f810d34ff71944979054b04de0117e81ddf5b0b4b3e13c0 +92221631c44d60d68c6bc7b287509f37ee44cbe5fdb6935cee36b58b17c7325098f98f7910d2c3ca5dc885ad1d6dabc7 +a230124424a57fad3b1671f404a94d7c05f4c67b7a8fbacfccea28887b78d7c1ed40b92a58348e4d61328891cd2f6cee +a6a230edb8518a0f49d7231bc3e0bceb5c2ac427f045819f8584ba6f3ae3d63ed107a9a62aad543d7e1fcf1f20605706 +845be1fe94223c7f1f97d74c49d682472585d8f772762baad8a9d341d9c3015534cc83d102113c51a9dea2ab10d8d27b +b44262515e34f2db597c8128c7614d33858740310a49cdbdf9c8677c5343884b42c1292759f55b8b4abc4c86e4728033 +805592e4a3cd07c1844bc23783408310accfdb769cca882ad4d07d608e590a288b7370c2cb327f5336e72b7083a0e30f +95153e8b1140df34ee864f4ca601cb873cdd3efa634af0c4093fbaede36f51b55571ab271e6a133020cd34db8411241f +82878c1285cfa5ea1d32175c9401f3cc99f6bb224d622d3fd98cc7b0a27372f13f7ab463ce3a33ec96f9be38dbe2dfe3 +b7588748f55783077c27fc47d33e20c5c0f5a53fc0ac10194c003aa09b9f055d08ec971effa4b7f760553997a56967b3 +b36b4de6d1883b6951f59cfae381581f9c6352fcfcf1524fccdab1571a20f80441d9152dc6b48bcbbf00371337ca0bd5 +89c5523f2574e1c340a955cbed9c2f7b5fbceb260cb1133160dabb7d41c2f613ec3f6e74bbfab3c4a0a6f0626dbe068f +a52f58cc39f968a9813b1a8ddc4e83f4219e4dd82c7aa1dd083bea7edf967151d635aa9597457f879771759b876774e4 +8300a67c2e2e123f89704abfde095463045dbd97e20d4c1157bab35e9e1d3d18f1f4aaba9cbe6aa2d544e92578eaa1b6 +ac6a7f2918768eb6a43df9d3a8a04f8f72ee52f2e91c064c1c7d75cad1a3e83e5aba9fe55bb94f818099ac91ccf2e961 +8d64a2b0991cf164e29835c8ddef6069993a71ec2a7de8157bbfa2e00f6367be646ed74cbaf524f0e9fe13fb09fa15fd +8b2ffe5a545f9f680b49d0a9797a4a11700a2e2e348c34a7a985fc278f0f12def6e06710f40f9d48e4b7fbb71e072229 +8ab8f71cd337fa19178924e961958653abf7a598e3f022138b55c228440a2bac4176cea3aea393549c03cd38a13eb3fc +8419d28318c19ea4a179b7abb43669fe96347426ef3ac06b158d79c0acf777a09e8e770c2fb10e14b3a0421705990b23 +8bacdac310e1e49660359d0a7a17fe3d334eb820e61ae25e84cb52f863a2f74cbe89c2e9fc3283745d93a99b79132354 +b57ace3fa2b9f6b2db60c0d861ace7d7e657c5d35d992588aeed588c6ce3a80b6f0d49f8a26607f0b17167ab21b675e4 +83e265cde477f2ecc164f49ddc7fb255bb05ff6adc347408353b7336dc3a14fdedc86d5a7fb23f36b8423248a7a67ed1 +a60ada971f9f2d79d436de5d3d045f5ab05308cae3098acaf5521115134b2a40d664828bb89895840db7f7fb499edbc5 +a63eea12efd89b62d3952bf0542a73890b104dd1d7ff360d4755ebfa148fd62de668edac9eeb20507967ea37fb220202 +a0275767a270289adc991cc4571eff205b58ad6d3e93778ddbf95b75146d82517e8921bd0d0564e5b75fa0ccdab8e624 +b9b03fd3bf07201ba3a039176a965d736b4ef7912dd9e9bf69fe1b57c330a6aa170e5521fe8be62505f3af81b41d7806 +a95f640e26fb1106ced1729d6053e41a16e4896acac54992279ff873e5a969aad1dcfa10311e28b8f409ac1dab7f03bb +b144778921742418053cb3c70516c63162c187f00db2062193bb2c14031075dbe055d020cde761b26e8c58d0ea6df2c1 +8432fbb799e0435ef428d4fefc309a05dd589bce74d7a87faf659823e8c9ed51d3e42603d878e80f439a38be4321c2fa +b08ddef14e42d4fd5d8bf39feb7485848f0060d43b51ed5bdda39c05fe154fb111d29719ee61a23c392141358c0cfcff +8ae3c5329a5e025b86b5370e06f5e61177df4bda075856fade20a17bfef79c92f54ed495f310130021ba94fb7c33632b +92b6d3c9444100b4d7391febfc1dddaa224651677c3695c47a289a40d7a96d200b83b64e6d9df51f534564f272a2c6c6 +b432bc2a3f93d28b5e506d68527f1efeb2e2570f6be0794576e2a6ef9138926fdad8dd2eabfa979b79ab7266370e86bc +8bc315eacedbcfc462ece66a29662ca3dcd451f83de5c7626ef8712c196208fb3d8a0faf80b2e80384f0dd9772f61a23 +a72375b797283f0f4266dec188678e2b2c060dfed5880fc6bb0c996b06e91a5343ea2b695adaab0a6fd183b040b46b56 +a43445036fbaa414621918d6a897d3692fdae7b2961d87e2a03741360e45ebb19fcb1703d23f1e15bb1e2babcafc56ac +b9636b2ffe305e63a1a84bd44fb402442b1799bd5272638287aa87ca548649b23ce8ce7f67be077caed6aa2dbc454b78 +99a30bf0921d854c282b83d438a79f615424f28c2f99d26a05201c93d10378ab2cd94a792b571ddae5d4e0c0013f4006 +8648e3c2f93d70b392443be116b48a863e4b75991bab5db656a4ef3c1e7f645e8d536771dfe4e8d1ceda3be8d32978b0 +ab50dc9e6924c1d2e9d2e335b2d679fc7d1a7632e84964d3bac0c9fe57e85aa5906ec2e7b0399d98ddd022e9b19b5904 +ab729328d98d295f8f3272afaf5d8345ff54d58ff9884da14f17ecbdb7371857fdf2f3ef58080054e9874cc919b46224 +83fa5da7592bd451cad3ad7702b4006332b3aae23beab4c4cb887fa6348317d234bf62a359e665b28818e5410c278a09 +8bdbff566ae9d368f114858ef1f009439b3e9f4649f73efa946e678d6c781d52c69af195df0a68170f5f191b2eac286b +91245e59b4425fd4edb2a61d0d47c1ccc83d3ced8180de34887b9655b5dcda033d48cde0bdc3b7de846d246c053a02e8 +a2cb00721e68f1cad8933947456f07144dc69653f96ceed845bd577d599521ba99cdc02421118971d56d7603ed118cbf +af8cd66d303e808b22ec57860dd909ca64c27ec2c60e26ffecfdc1179d8762ffd2739d87b43959496e9fee4108df71df +9954136812dffcd5d3f167a500e7ab339c15cfc9b3398d83f64b0daa3dd5b9a851204f424a3493b4e326d3de81e50a62 +93252254d12511955f1aa464883ad0da793f84d900fea83e1df8bca0f2f4cf5b5f9acbaec06a24160d33f908ab5fea38 +997cb55c26996586ba436a95566bd535e9c22452ca5d2a0ded2bd175376557fa895f9f4def4519241ff386a063f2e526 +a12c78ad451e0ac911260ade2927a768b50cb4125343025d43474e7f465cdc446e9f52a84609c5e7e87ae6c9b3f56cda +a789d4ca55cbba327086563831b34487d63d0980ba8cf55197c016702ed6da9b102b1f0709ce3da3c53ff925793a3d73 +a5d76acbb76741ce85be0e655b99baa04f7f587347947c0a30d27f8a49ae78cce06e1cde770a8b618d3db402be1c0c4b +873c0366668c8faddb0eb7c86f485718d65f8c4734020f1a18efd5fa123d3ea8a990977fe13592cd01d17e60809cb5ff +b659b71fe70f37573ff7c5970cc095a1dc0da3973979778f80a71a347ef25ad5746b2b9608bad4ab9a4a53a4d7df42d7 +a34cbe05888e5e5f024a2db14cb6dcdc401a9cbd13d73d3c37b348f68688f87c24ca790030b8f84fef9e74b4eab5e412 +94ce8010f85875c045b0f014db93ef5ab9f1f6842e9a5743dce9e4cb872c94affd9e77c1f1d1ab8b8660b52345d9acb9 +adefa9b27a62edc0c5b019ddd3ebf45e4de846165256cf6329331def2e088c5232456d3de470fdce3fa758bfdd387512 +a6b83821ba7c1f83cc9e4529cf4903adb93b26108e3d1f20a753070db072ad5a3689643144bdd9c5ea06bb9a7a515cd0 +a3a9ddedc2a1b183eb1d52de26718151744db6050f86f3580790c51d09226bf05f15111691926151ecdbef683baa992c +a64bac89e7686932cdc5670d07f0b50830e69bfb8c93791c87c7ffa4913f8da881a9d8a8ce8c1a9ce5b6079358c54136 +a77b5a63452cb1320b61ab6c7c2ef9cfbcade5fd4727583751fb2bf3ea330b5ca67757ec1f517bf4d503ec924fe32fbd +8746fd8d8eb99639d8cd0ca34c0d9c3230ed5a312aab1d3d925953a17973ee5aeb66e68667e93caf9cb817c868ea8f3d +88a2462a26558fc1fbd6e31aa8abdc706190a17c27fdc4217ffd2297d1b1f3321016e5c4b2384c5454d5717dc732ed03 +b78893a97e93d730c8201af2e0d3b31cb923d38dc594ffa98a714e627c473d42ea82e0c4d2eeb06862ee22a9b2c54588 +920cc8b5f1297cf215a43f6fc843e379146b4229411c44c0231f6749793d40f07b9af7699fd5d21fd69400b97febe027 +a0f0eafce1e098a6b58c7ad8945e297cd93aaf10bc55e32e2e32503f02e59fc1d5776936577d77c0b1162cb93b88518b +98480ba0064e97a2e7a6c4769b4d8c2a322cfc9a3b2ca2e67e9317e2ce04c6e1108169a20bd97692e1cb1f1423b14908 +83dbbb2fda7e287288011764a00b8357753a6a44794cc8245a2275237f11affdc38977214e463ad67aec032f3dfa37e9 +86442fff37598ce2b12015ff19b01bb8a780b40ad353d143a0f30a06f6d23afd5c2b0a1253716c855dbf445cc5dd6865 +b8a4c60c5171189414887847b9ed9501bff4e4c107240f063e2d254820d2906b69ef70406c585918c4d24f1dd052142b +919f33a98e84015b2034b57b5ffe9340220926b2c6e45f86fd79ec879dbe06a148ae68b77b73bf7d01bd638a81165617 +95c13e78d89474a47fbc0664f6f806744b75dede95a479bbf844db4a7f4c3ae410ec721cb6ffcd9fa9c323da5740d5ae +ab7151acc41fffd8ec6e90387700bcd7e1cde291ea669567295bea1b9dd3f1df2e0f31f3588cd1a1c08af8120aca4921 +80e74c5c47414bd6eeef24b6793fb1fa2d8fb397467045fcff887c52476741d5bc4ff8b6d3387cb53ad285485630537f +a296ad23995268276aa351a7764d36df3a5a3cffd7dbeddbcea6b1f77adc112629fdeffa0918b3242b3ccd5e7587e946 +813d2506a28a2b01cb60f49d6bd5e63c9b056aa56946faf2f33bd4f28a8d947569cfead3ae53166fc65285740b210f86 +924b265385e1646287d8c09f6c855b094daaee74b9e64a0dddcf9ad88c6979f8280ba30c8597b911ef58ddb6c67e9fe3 +8d531513c70c2d3566039f7ca47cd2352fd2d55b25675a65250bdb8b06c3843db7b2d29c626eed6391c238fc651cf350 +82b338181b62fdc81ceb558a6843df767b6a6e3ceedc5485664b4ea2f555904b1a45fbb35f6cf5d96f27da10df82a325 +92e62faaedea83a37f314e1d3cb4faaa200178371d917938e59ac35090be1db4b4f4e0edb78b9c991de202efe4f313d8 +99d645e1b642c2dc065bac9aaa0621bc648c9a8351efb6891559c3a41ba737bd155fb32d7731950514e3ecf4d75980e4 +b34a13968b9e414172fb5d5ece9a39cf2eb656128c3f2f6cc7a9f0c69c6bae34f555ecc8f8837dc34b5e470e29055c78 +a2a0bb7f3a0b23a2cbc6585d59f87cd7e56b2bbcb0ae48f828685edd9f7af0f5edb4c8e9718a0aaf6ef04553ba71f3b7 +8e1a94bec053ed378e524b6685152d2b52d428266f2b6eadd4bcb7c4e162ed21ab3e1364879673442ee2162635b7a4d8 +9944adaff14a85eab81c73f38f386701713b52513c4d4b838d58d4ffa1d17260a6d056b02334850ea9a31677c4b078bd +a450067c7eceb0854b3eca3db6cf38669d72cb7143c3a68787833cbca44f02c0be9bfbe082896f8a57debb13deb2afb1 +8be4ad3ac9ef02f7df09254d569939757101ee2eda8586fefcd8c847adc1efe5bdcb963a0cafa17651befaafb376a531 +90f6de91ea50255f148ac435e08cf2ac00c772a466e38155bd7e8acf9197af55662c7b5227f88589b71abe9dcf7ba343 +86e5a24f0748b106dee2d4d54e14a3b0af45a96cbee69cac811a4196403ebbee17fd24946d7e7e1b962ac7f66dbaf610 +afdd96fbcda7aa73bf9eeb2292e036c25753d249caee3b9c013009cc22e10d3ec29e2aa6ddbb21c4e949b0c0bccaa7f4 +b5a4e7436d5473647c002120a2cb436b9b28e27ad4ebdd7c5f122b91597c507d256d0cbd889d65b3a908531936e53053 +b632414c3da704d80ac2f3e5e0e9f18a3637cdc2ebeb613c29300745582427138819c4e7b0bec3099c1b8739dac1807b +a28df1464d3372ce9f37ef1db33cc010f752156afae6f76949d98cd799c0cf225c20228ae86a4da592d65f0cffe3951b +898b93d0a31f7d3f11f253cb7a102db54b669fd150da302d8354d8e02b1739a47cb9bd88015f3baf12b00b879442464e +96fb88d89a12049091070cb0048a381902965e67a8493e3991eaabe5d3b7ff7eecd5c94493a93b174df3d9b2c9511755 +b899cb2176f59a5cfba3e3d346813da7a82b03417cad6342f19cc8f12f28985b03bf031e856a4743fd7ebe16324805b0 +a60e2d31bc48e0c0579db15516718a03b73f5138f15037491f4dae336c904e312eda82d50862f4debd1622bb0e56d866 +979fc8b987b5cef7d4f4b58b53a2c278bd25a5c0ea6f41c715142ea5ff224c707de38451b0ad3aa5e749aa219256650a +b2a75bff18e1a6b9cf2a4079572e41205741979f57e7631654a3c0fcec57c876c6df44733c9da3d863db8dff392b44a3 +b7a0f0e811222c91e3df98ff7f286b750bc3b20d2083966d713a84a2281744199e664879401e77470d44e5a90f3e5181 +82b74ba21c9d147fbc338730e8f1f8a6e7fc847c3110944eb17a48bea5e06eecded84595d485506d15a3e675fd0e5e62 +a7f44eef817d5556f0d1abcf420301217d23c69dd2988f44d91ea1f1a16c322263cbacd0f190b9ba22b0f141b9267b4f +aadb68164ede84fc1cb3334b3194d84ba868d5a88e4c9a27519eef4923bc4abf81aab8114449496c073c2a6a0eb24114 +b5378605fabe9a8c12a5dc55ef2b1de7f51aedb61960735c08767a565793cea1922a603a6983dc25f7cea738d0f7c40d +a97a4a5cd8d51302e5e670aee78fe6b5723f6cc892902bbb4f131e82ca1dfd5de820731e7e3367fb0c4c1922a02196e3 +8bdfeb15c29244d4a28896f2b2cb211243cd6a1984a3f5e3b0ebe5341c419beeab3304b390a009ffb47588018034b0ea +a9af3022727f2aa2fca3b096968e97edad3f08edcbd0dbca107b892ae8f746a9c0485e0d6eb5f267999b23a845923ed0 +8e7594034feef412f055590fbb15b6322dc4c6ab7a4baef4685bd13d71a83f7d682b5781bdfa0d1c659489ce9c2b8000 +84977ca6c865ebee021c58106c1a4ad0c745949ecc5332948002fd09bd9b890524878d0c29da96fd11207621136421fe +8687551a79158e56b2375a271136756313122132a6670fa51f99a1b5c229ed8eea1655a734abae13228b3ebfd2a825dd +a0227d6708979d99edfc10f7d9d3719fd3fc68b0d815a7185b60307e4c9146ad2f9be2b8b4f242e320d4288ceeb9504c +89f75583a16735f9dd8b7782a130437805b34280ccea8dac6ecaee4b83fe96947e7b53598b06fecfffdf57ffc12cc445 +a0056c3353227f6dd9cfc8e3399aa5a8f1d71edf25d3d64c982910f50786b1e395c508d3e3727ac360e3e040c64b5298 +b070e61a6d813626144b312ded1788a6d0c7cec650a762b2f8df6e4743941dd82a2511cd956a3f141fc81e15f4e092da +b4e6db232e028a1f989bb5fc13416711f42d389f63564d60851f009dcffac01acfd54efa307aa6d4c0f932892d4e62b0 +89b5991a67db90024ddd844e5e1a03ef9b943ad54194ae0a97df775dde1addf31561874f4e40fbc37a896630f3bbda58 +ad0e8442cb8c77d891df49cdb9efcf2b0d15ac93ec9be1ad5c3b3cca1f4647b675e79c075335c1f681d56f14dc250d76 +b5d55a6ae65bb34dd8306806cb49b5ccb1c83a282ee47085cf26c4e648e19a52d9c422f65c1cd7e03ca63e926c5e92ea +b749501347e5ec07e13a79f0cb112f1b6534393458b3678a77f02ca89dca973fa7b30e55f0b25d8b92b97f6cb0120056 +94144b4a3ffc5eec6ba35ce9c245c148b39372d19a928e236a60e27d7bc227d18a8cac9983851071935d8ffb64b3a34f +92bb4f9f85bc8c028a3391306603151c6896673135f8a7aefedd27acb322c04ef5dac982fc47b455d6740023e0dd3ea3 +b9633a4a101461a782fc2aa092e9dbe4e2ad00987578f18cd7cf0021a909951d60fe79654eb7897806795f93c8ff4d1c +809f0196753024821b48a016eca5dbb449a7c55750f25981bb7a4b4c0e0846c09b8f6128137905055fc43a3f0deb4a74 +a27dc9cdd1e78737a443570194a03d89285576d3d7f3a3cf15cc55b3013e42635d4723e2e8fe1d0b274428604b630db9 +861f60f0462e04cd84924c36a28163def63e777318d00884ab8cb64c8df1df0bce5900342163edb60449296484a6c5bf +b7bc23fb4e14af4c4704a944253e760adefeca8caee0882b6bbd572c84434042236f39ae07a8f21a560f486b15d82819 +b9a6eb492d6dd448654214bd01d6dc5ff12067a11537ab82023fc16167507ee25eed2c91693912f4155d1c07ed9650b3 +97678af29c68f9a5e213bf0fb85c265303714482cfc4c2c00b4a1e8a76ed08834ee6af52357b143a1ca590fb0265ea5a +8a15b499e9eca5b6cac3070b5409e8296778222018ad8b53a5d1f6b70ad9bb10c68a015d105c941ed657bf3499299e33 +b487fefede2e8091f2c7bfe85770db2edff1db83d4effe7f7d87bff5ab1ace35e9b823a71adfec6737fede8d67b3c467 +8b51b916402aa2c437fce3bcad6dad3be8301a1a7eab9d163085b322ffb6c62abf28637636fe6114573950117fc92898 +b06a2106d031a45a494adec0881cb2f82275dff9dcdd2bc16807e76f3bec28a6734edd3d54f0be8199799a78cd6228ad +af0a185391bbe2315eb97feac98ad6dd2e5d931d012c621abd6e404a31cc188b286fef14871762190acf086482b2b5e2 +8e78ee8206506dd06eb7729e32fceda3bebd8924a64e4d8621c72e36758fda3d0001af42443851d6c0aea58562870b43 +a1ba52a569f0461aaf90b49b92be976c0e73ec4a2c884752ee52ffb62dd137770c985123d405dfb5de70692db454b54a +8d51b692fa1543c51f6b62b9acb8625ed94b746ef96c944ca02859a4133a5629da2e2ce84e111a7af8d9a5b836401c64 +a7a20d45044cf6492e0531d0b8b26ffbae6232fa05a96ed7f06bdb64c2b0f5ca7ec59d5477038096a02579e633c7a3ff +84df867b98c53c1fcd4620fef133ee18849c78d3809d6aca0fb6f50ff993a053a455993f216c42ab6090fa5356b8d564 +a7227c439f14c48e2577d5713c97a5205feb69acb0b449152842e278fa71e8046adfab468089c8b2288af1fc51fa945b +855189b3a105670779997690876dfaa512b4a25a24931a912c2f0f1936971d2882fb4d9f0b3d9daba77eaf660e9d05d5 +b5696bd6706de51c502f40385f87f43040a5abf99df705d6aac74d88c913b8ecf7a99a63d7a37d9bdf3a941b9e432ff5 +ab997beb0d6df9c98d5b49864ef0b41a2a2f407e1687dfd6089959757ba30ed02228940b0e841afe6911990c74d536c4 +b36b65f85546ebfdbe98823d5555144f96b4ab39279facd19c0de3b8919f105ba0315a0784dce4344b1bc62d8bb4a5a3 +b8371f0e4450788720ac5e0f6cd3ecc5413d33895083b2c168d961ec2b5c3de411a4cc0712481cbe8df8c2fa1a7af006 +98325d8026b810a8b7a114171ae59a57e8bbc9848e7c3df992efc523621729fd8c9f52114ce01d7730541a1ada6f1df1 +8d0e76dbd37806259486cd9a31bc8b2306c2b95452dc395546a1042d1d17863ef7a74c636b782e214d3aa0e8d717f94a +a4e15ead76da0214d702c859fb4a8accdcdad75ed08b865842bd203391ec4cba2dcc916455e685f662923b96ee0c023f +8618190972086ebb0c4c1b4a6c94421a13f378bc961cc8267a301de7390c5e73c3333864b3b7696d81148f9d4843fd02 +85369d6cc7342e1aa15b59141517d8db8baaaeb7ab9670f3ba3905353948d575923d283b7e5a05b13a30e7baf1208a86 +87c51ef42233c24a6da901f28c9a075d9ba3c625687c387ad6757b72ca6b5a8885e6902a3082da7281611728b1e45f26 +aa6348a4f71927a3106ad0ea8b02fc8d8c65531e4ab0bd0a17243e66f35afe252e40ab8eef9f13ae55a72566ffdaff5c +96a3bc976e9d03765cc3fee275fa05b4a84c94fed6b767e23ca689394501e96f56f7a97cffddc579a6abff632bf153be +97dbf96c6176379fdb2b888be4e757b2bca54e74124bd068d3fa1dbd82a011bbeb75079da38e0cd22a761fe208ecad9b +b70cf0a1d14089a4129ec4e295313863a59da8c7e26bf74cc0e704ed7f0ee4d7760090d0ddf7728180f1bf2c5ac64955 +882d664714cc0ffe53cbc9bef21f23f3649824f423c4dbad1f893d22c4687ab29583688699efc4d5101aa08b0c3e267a +80ecb7cc963e677ccaddbe3320831dd6ee41209acf4ed41b16dc4817121a3d86a1aac9c4db3d8c08a55d28257088af32 +a25ba667d832b145f9ce18c3f9b1bd00737aa36db020e1b99752c8ef7d27c6c448982bd8d352e1b6df266b8d8358a8d5 +83734841c13dee12759d40bdd209b277e743b0d08cc0dd1e0b7afd2d65bfa640400eefcf6be4a52e463e5b3d885eeac6 +848d16505b04804afc773aebabb51b36fd8aacfbb0e09b36c0d5d57df3c0a3b92f33e7d5ad0a7006ec46ebb91df42b8c +909a8d793f599e33bb9f1dc4792a507a97169c87cd5c087310bc05f30afcd247470b4b56dec59894c0fb1d48d39bb54e +8e558a8559df84a1ba8b244ece667f858095c50bb33a5381e60fcc6ba586b69693566d8819b4246a27287f16846c1dfa +84d6b69729f5aaa000cd710c2352087592cfbdf20d5e1166977e195818e593fa1a50d1e04566be23163a2523dc1612f1 +9536d262b7a42125d89f4f32b407d737ba8d9242acfc99d965913ab3e043dcac9f7072a43708553562cac4cba841df30 +9598548923ca119d6a15fd10861596601dd1dedbcccca97bb208cdc1153cf82991ea8cc17686fbaa867921065265970c +b87f2d4af6d026e4d2836bc3d390a4a18e98a6e386282ce96744603bab74974272e97ac2da281afa21885e2cbb3a8001 +991ece62bf07d1a348dd22191868372904b9f8cf065ae7aa4e44fd24a53faf6d851842e35fb472895963aa1992894918 +a8c53dea4c665b30e51d22ca6bc1bc78aaf172b0a48e64a1d4b93439b053877ec26cb5221c55efd64fa841bbf7d5aff4 +93487ec939ed8e740f15335b58617c3f917f72d07b7a369befd479ae2554d04deb240d4a14394b26192efae4d2f4f35d +a44793ab4035443f8f2968a40e043b4555960193ffa3358d22112093aadfe2c136587e4139ffd46d91ed4107f61ea5e0 +b13fe033da5f0d227c75927d3dacb06dbaf3e1322f9d5c7c009de75cdcba5e308232838785ab69a70f0bedea755e003f +970a29b075faccd0700fe60d1f726bdebf82d2cc8252f4a84543ebd3b16f91be42a75c9719a39c4096139f0f31393d58 +a4c3eb1f7160f8216fc176fb244df53008ff32f2892363d85254002e66e2de21ccfe1f3b1047589abee50f29b9d507e3 +8c552885eab04ba40922a8f0c3c38c96089c95ff1405258d3f1efe8d179e39e1295cbf67677894c607ae986e4e6b1fb0 +b3671746fa7f848c4e2ae6946894defadd815230b906b419143523cc0597bc1d6c0a4c1e09d49b66b4a2c11cde3a4de3 +937a249a95813a5e2ef428e355efd202e15a37d73e56cfb7e57ea9f943f2ce5ca8026f2f1fd25bf164ba89d07077d858 +83646bdf6053a04aa9e2f112499769e5bd5d0d10f2e13db3ca89bd45c0b3b7a2d752b7d137fb3909f9c62b78166c9339 +b4eac4b91e763666696811b7ed45e97fd78310377ebea1674b58a2250973f80492ac35110ed1240cd9bb2d17493d708c +82db43a99bc6573e9d92a3fd6635dbbb249ac66ba53099c3c0c8c8080b121dd8243cd5c6e36ba0a4d2525bae57f5c89c +a64d6a264a681b49d134c655d5fc7756127f1ee7c93d328820f32bca68869f53115c0d27fef35fe71f7bc4fdaed97348 +8739b7a9e2b4bc1831e7f04517771bc7cde683a5e74e052542517f8375a2f64e53e0d5ac925ef722327e7bb195b4d1d9 +8f337cdd29918a2493515ebb5cf702bbe8ecb23b53c6d18920cc22f519e276ca9b991d3313e2d38ae17ae8bdfa4f8b7e +b0edeab9850e193a61f138ef2739fc42ceec98f25e7e8403bfd5fa34a7bc956b9d0898250d18a69fa4625a9b3d6129da +a9920f26fe0a6d51044e623665d998745c9eca5bce12051198b88a77d728c8238f97d4196f26e43b24f8841500b998d0 +86e655d61502b979eeeeb6f9a7e1d0074f936451d0a1b0d2fa4fb3225b439a3770767b649256fe481361f481a8dbc276 +84d3b32fa62096831cc3bf013488a9f3f481dfe293ae209ed19585a03f7db8d961a7a9dd0db82bd7f62d612707575d9c +81c827826ec9346995ffccf62a241e3b2d32f7357acd1b1f8f7a7dbc97022d3eb51b8a1230e23ce0b401d2e535e8cd78 +94a1e40c151191c5b055b21e86f32e69cbc751dcbdf759a48580951834b96a1eed75914c0d19a38aefd21fb6c8d43d0c +ab890222b44bc21b71f7c75e15b6c6e16bb03371acce4f8d4353ff3b8fcd42a14026589c5ed19555a3e15e4d18bfc3a3 +accb0be851e93c6c8cc64724cdb86887eea284194b10e7a43c90528ed97e9ec71ca69c6fac13899530593756dd49eab2 +b630220aa9e1829c233331413ee28c5efe94ea8ea08d0c6bfd781955078b43a4f92915257187d8526873e6c919c6a1de +add389a4d358c585f1274b73f6c3c45b58ef8df11f9d11221f620e241bf3579fba07427b288c0c682885a700cc1fa28d +a9fe6ca8bf2961a3386e8b8dcecc29c0567b5c0b3bcf3b0f9169f88e372b80151af883871fc5229815f94f43a6f5b2b0 +ad839ae003b92b37ea431fa35998b46a0afc3f9c0dd54c3b3bf7a262467b13ff3c323ada1c1ae02ac7716528bdf39e3e +9356d3fd0edcbbb65713c0f2a214394f831b26f792124b08c5f26e7f734b8711a87b7c4623408da6a091c9aef1f6af3c +896b25b083c35ac67f0af3784a6a82435b0e27433d4d74cd6d1eafe11e6827827799490fb1c77c11de25f0d75f14e047 +8bfa019391c9627e8e5f05c213db625f0f1e51ec68816455f876c7e55b8f17a4f13e5aae9e3fb9e1cf920b1402ee2b40 +8ba3a6faa6a860a8f3ce1e884aa8769ceded86380a86520ab177ab83043d380a4f535fe13884346c5e51bee68da6ab41 +a8292d0844084e4e3bb7af92b1989f841a46640288c5b220fecfad063ee94e86e13d3d08038ec2ac82f41c96a3bfe14d +8229bb030b2fc566e11fd33c7eab7a1bb7b49fed872ea1f815004f7398cb03b85ea14e310ec19e1f23e0bdaf60f8f76c +8cfbf869ade3ec551562ff7f63c2745cc3a1f4d4dc853a0cd42dd5f6fe54228f86195ea8fe217643b32e9f513f34a545 +ac52a3c8d3270ddfe1b5630159da9290a5ccf9ccbdef43b58fc0a191a6c03b8a5974cf6e2bbc7bd98d4a40a3581482d7 +ab13decb9e2669e33a7049b8eca3ca327c40dea15ad6e0e7fa63ed506db1d258bc36ac88b35f65cae0984e937eb6575d +b5e748eb1a7a1e274ff0cc56311c198f2c076fe4b7e73e5f80396fe85358549df906584e6bb2c8195b3e2be7736850a5 +b5cb911325d8f963c41f691a60c37831c7d3bbd92736efa33d1f77a22b3fde7f283127256c2f47e197571e6fe0b46149 +8a01dc6ed1b55f26427a014faa347130738b191a06b800e32042a46c13f60b49534520214359d68eb2e170c31e2b8672 +a72fa874866e19b2efb8e069328362bf7921ec375e3bcd6b1619384c3f7ee980f6cf686f3544e9374ff54b4d17a1629c +8db21092f7c5f110fba63650b119e82f4b42a997095d65f08f8237b02dd66fdf959f788df2c35124db1dbd330a235671 +8c65d50433d9954fe28a09fa7ba91a70a590fe7ba6b3060f5e4be0f6cef860b9897fa935fb4ebc42133524eb071dd169 +b4614058e8fa21138fc5e4592623e78b8982ed72aa35ee4391b164f00c68d277fa9f9eba2eeefc890b4e86eba5124591 +ab2ad3a1bce2fbd55ca6b7c23786171fe1440a97d99d6df4d80d07dd56ac2d7203c294b32fc9e10a6c259381a73f24a1 +812ae3315fdc18774a8da3713a4679e8ed10b9405edc548c00cacbe25a587d32040566676f135e4723c5dc25df5a22e9 +a464b75f95d01e5655b54730334f443c8ff27c3cb79ec7af4b2f9da3c2039c609908cd128572e1fd0552eb597e8cef8d +a0db3172e93ca5138fe419e1c49a1925140999f6eff7c593e5681951ee0ec1c7e454c851782cbd2b8c9bc90d466e90e0 +806db23ba7d00b87d544eed926b3443f5f9c60da6b41b1c489fba8f73593b6e3b46ebfcab671ee009396cd77d5e68aa1 +8bfdf2c0044cc80260994e1c0374588b6653947b178e8b312be5c2a05e05767e98ea15077278506aee7df4fee1aaf89e +827f6558c16841b5592ff089c9c31e31eb03097623524394813a2e4093ad2d3f8f845504e2af92195aaa8a1679d8d692 +925c4f8eab2531135cd71a4ec88e7035b5eea34ba9d799c5898856080256b4a15ed1a746e002552e2a86c9c157e22e83 +a9f9a368f0e0b24d00a35b325964c85b69533013f9c2cfad9708be5fb87ff455210f8cb8d2ce3ba58ca3f27495552899 +8ac0d3bebc1cae534024187e7c71f8927ba8fcc6a1926cb61c2b6c8f26bb7831019e635a376146c29872a506784a4aaa +97c577be2cbbfdb37ad754fae9df2ada5fc5889869efc7e18a13f8e502fbf3f4067a509efbd46fd990ab47ce9a70f5a8 +935e7d82bca19f16614aa43b4a3474e4d20d064e4bfdf1cea2909e5c9ab72cfe3e54dc50030e41ee84f3588cebc524e9 +941aafc08f7c0d94cebfbb1f0aad5202c02e6e37f2c12614f57e727efa275f3926348f567107ee6d8914dd71e6060271 +af0fbc1ba05b4b5b63399686df3619968be5d40073de0313cbf5f913d3d4b518d4c249cdd2176468ccaa36040a484f58 +a0c414f23f46ca6d69ce74c6f8a00c036cb0edd098af0c1a7d39c802b52cfb2d5dbdf93fb0295453d4646e2af7954d45 +909cf39e11b3875bb63b39687ae1b5d1f5a15445e39bf164a0b14691b4ddb39a8e4363f584ef42213616abc4785b5d66 +a92bac085d1194fbd1c88299f07a061d0bdd3f980b663e81e6254dbb288bf11478c0ee880e28e01560f12c5ccb3c0103 +841705cd5cd76b943e2b7c5e845b9dd3c8defe8ef67e93078d6d5e67ade33ad4b0fd413bc196f93b0a4073c855cd97d4 +8e7eb8364f384a9161e81d3f1d52ceca9b65536ae49cc35b48c3e2236322ba4ae9973e0840802d9fa4f4d82ea833544f +aed3ab927548bc8bec31467ba80689c71a168e34f50dcb6892f19a33a099f5aa6b3f9cb79f5c0699e837b9a8c7f27efe +b8fbf7696210a36e20edabd77839f4dfdf50d6d015cdf81d587f90284a9bcef7d2a1ff520728d7cc69a4843d6c20dedd +a9d533769ce6830211c884ae50a82a7bf259b44ac71f9fb11f0296fdb3981e6b4c1753fe744647b247ebc433a5a61436 +8b4bdf90d33360b7f428c71cde0a49fb733badba8c726876945f58c620ce7768ae0e98fc8c31fa59d8955a4823336bb1 +808d42238e440e6571c59e52a35ae32547d502dc24fd1759d8ea70a7231a95859baf30b490a4ba55fa2f3aaa11204597 +85594701f1d2fee6dc1956bc44c7b31db93bdeec2f3a7d622c1a08b26994760773e3d57521a44cfd7e407ac3fd430429 +a66de045ce7173043a6825e9dc440ac957e2efb6df0a337f4f8003eb0c719d873a52e6eba3cb0d69d977ca37d9187674 +87a1c6a1fdff993fa51efa5c3ba034c079c0928a7d599b906336af7c2dcab9721ceaf3108c646490af9dff9a754f54b3 +926424223e462ceb75aed7c22ade8a7911a903b7e5dd4bc49746ddce8657f4616325cd12667d4393ac52cdd866396d0e +b5dc96106593b42b30f06f0b0a1e0c1aafc70432e31807252d3674f0b1ea5e58eac8424879d655c9488d85a879a3e572 +997ca0987735cc716507cb0124b1d266d218b40c9d8e0ecbf26a1d65719c82a637ce7e8be4b4815d307df717bde7c72a +92994d3f57a569b7760324bb5ae4e8e14e1633d175dab06aa57b8e391540e05f662fdc08b8830f489a063f59b689a688 +a8087fcc6aa4642cb998bea11facfe87eb33b90a9aa428ab86a4124ad032fc7d2e57795311a54ec9f55cc120ebe42df1 +a9bd7d1de6c0706052ca0b362e2e70e8c8f70f1f026ea189b4f87a08ce810297ebfe781cc8004430776c54c1a05ae90c +856d33282e8a8e33a3d237fb0a0cbabaf77ba9edf2fa35a831fdafcadf620561846aa6cbb6bdc5e681118e1245834165 +9524a7aa8e97a31a6958439c5f3339b19370f03e86b89b1d02d87e4887309dbbe9a3a8d2befd3b7ed5143c8da7e0a8ad +824fdf433e090f8acbd258ac7429b21f36f9f3b337c6d0b71d1416a5c88a767883e255b2888b7c906dd2e9560c4af24c +88c7fee662ca7844f42ed5527996b35723abffd0d22d4ca203b9452c639a5066031207a5ae763dbc0865b3299d19b1ec +919dca5c5595082c221d5ab3a5bc230f45da7f6dec4eb389371e142c1b9c6a2c919074842479c2844b72c0d806170c0c +b939be8175715e55a684578d8be3ceff3087f60fa875fff48e52a6e6e9979c955efef8ff67cfa2b79499ea23778e33b0 +873b6db725e7397d11bc9bed9ac4468e36619135be686790a79bc6ed4249058f1387c9a802ea86499f692cf635851066 +aeae06db3ec47e9e5647323fa02fac44e06e59b885ad8506bf71b184ab3895510c82f78b6b22a5d978e8218e7f761e9f +b99c0a8359c72ab88448bae45d4bf98797a26bca48b0d4460cd6cf65a4e8c3dd823970ac3eb774ae5d0cea4e7fadf33e +8f10c8ec41cdfb986a1647463076a533e6b0eec08520c1562401b36bb063ac972aa6b28a0b6ce717254e35940b900e3c +a106d9be199636d7add43b942290269351578500d8245d4aae4c083954e4f27f64740a3138a66230391f2d0e6043a8de +a469997908244578e8909ff57cffc070f1dbd86f0098df3cfeb46b7a085cfecc93dc69ee7cad90ff1dc5a34d50fe580c +a4ef087bea9c20eb0afc0ee4caba7a9d29dfa872137828c721391273e402fb6714afc80c40e98bbd8276d3836bffa080 +b07a013f73cd5b98dae0d0f9c1c0f35bff8a9f019975c4e1499e9bee736ca6fcd504f9bc32df1655ff333062382cff04 +b0a77188673e87cc83348c4cc5db1eecf6b5184e236220c8eeed7585e4b928db849944a76ec60ef7708ef6dac02d5592 +b1284b37e59b529f0084c0dacf0af6c0b91fc0f387bf649a8c74819debf606f7b07fc3e572500016fb145ec2b24e9f17 +97b20b5b4d6b9129da185adfbf0d3d0b0faeba5b9715f10299e48ea0521709a8296a9264ce77c275a59c012b50b6519a +b9d37e946fae5e4d65c1fbfacc8a62e445a1c9d0f882e60cca649125af303b3b23af53c81d7bac544fb7fcfc7a314665 +8e5acaac379f4bb0127efbef26180f91ff60e4c525bc9b798fc50dfaf4fe8a5aa84f18f3d3cfb8baead7d1e0499af753 +b0c0b8ab1235bf1cda43d4152e71efc1a06c548edb964eb4afceb201c8af24240bf8ab5cae30a08604e77432b0a5faf0 +8cc28d75d5c8d062d649cbc218e31c4d327e067e6dbd737ec0a35c91db44fbbd0d40ec424f5ed79814add16947417572 +95ae6219e9fd47efaa9cb088753df06bc101405ba50a179d7c9f7c85679e182d3033f35b00dbba71fdcd186cd775c52e +b5d28fa09f186ebc5aa37453c9b4d9474a7997b8ae92748ecb940c14868792292ac7d10ade01e2f8069242b308cf97e5 +8c922a0faa14cc6b7221f302df3342f38fc8521ec6c653f2587890192732c6da289777a6cd310747ea7b7d104af95995 +b9ad5f660b65230de54de535d4c0fcae5bc6b59db21dea5500fdc12eea4470fb8ea003690fdd16d052523418d5e01e8c +a39a9dd41a0ff78c82979483731f1cd68d3921c3e9965869662c22e02dde3877802e180ba93f06e7346f96d9fa9261d2 +8b32875977ec372c583b24234c27ed73aef00cdff61eb3c3776e073afbdeade548de9497c32ec6d703ff8ad0a5cb7fe4 +9644cbe755a5642fe9d26cfecf170d3164f1848c2c2e271d5b6574a01755f3980b3fc870b98cf8528fef6ecef4210c16 +81ea9d1fdd9dd66d60f40ce0712764b99da9448ae0b300f8324e1c52f154e472a086dda840cb2e0b9813dc8ce8afd4b5 +906aaa4a7a7cdf01909c5cfbc7ded2abc4b869213cbf7c922d4171a4f2e637e56f17020b852ad339d83b8ac92f111666 +939b5f11acbdeff998f2a080393033c9b9d8d5c70912ea651c53815c572d36ee822a98d6dfffb2e339f29201264f2cf4 +aba4898bf1ccea9b9e2df1ff19001e05891581659c1cbbde7ee76c349c7fc7857261d9785823c9463a8aea3f40e86b38 +83ca1a56b8a0be4820bdb5a9346357c68f9772e43f0b887729a50d2eb2a326bbcede676c8bf2e51d7c89bbd8fdb778a6 +94e86e9fe6addfe2c3ee3a547267ed921f4230d877a85bb4442c2d9350c2fa9a9c54e6fe662de82d1a2407e4ab1691c2 +a0cc3bdef671a59d77c6984338b023fa2b431b32e9ed2abe80484d73edc6540979d6f10812ecc06d4d0c5d4eaca7183c +b5343413c1b5776b55ea3c7cdd1f3af1f6bd802ea95effe3f2b91a523817719d2ecc3f8d5f3cc2623ace7e35f99ca967 +92085d1ed0ed28d8cabe3e7ff1905ed52c7ceb1eac5503760c52fb5ee3a726aba7c90b483c032acc3f166b083d7ec370 +8ec679520455275cd957fca8122724d287db5df7d29f1702a322879b127bff215e5b71d9c191901465d19c86c8d8d404 +b65eb2c63d8a30332eb24ee8a0c70156fc89325ebbb38bacac7cf3f8636ad8a472d81ccca80423772abc00192d886d8a +a9fe1c060b974bee4d590f2873b28635b61bfcf614e61ff88b1be3eee4320f4874e21e8d666d8ac8c9aba672efc6ecae +b3fe2a9a389c006a831dea7e777062df84b5c2803f9574d7fbe10b7e1c125817986af8b6454d6be9d931a5ac94cfe963 +95418ad13b734b6f0d33822d9912c4c49b558f68d08c1b34a0127fcfa666bcae8e6fda8832d2c75bb9170794a20e4d7c +a9a7df761e7f18b79494bf429572140c8c6e9d456c4d4e336184f3f51525a65eb9582bea1e601bdb6ef8150b7ca736a5 +a0de03b1e75edf7998c8c1ac69b4a1544a6fa675a1941950297917366682e5644a4bda9cdeedfaf9473d7fccd9080b0c +a61838af8d95c95edf32663a68f007d95167bf6e41b0c784a30b22d8300cfdd5703bd6d16e86396638f6db6ae7e42a85 +8866d62084d905c145ff2d41025299d8b702ac1814a7dec4e277412c161bc9a62fed735536789cb43c88693c6b423882 +91da22c378c81497fe363e7f695c0268443abee50f8a6625b8a41e865638a643f07b157ee566de09ba09846934b4e2d7 +941d21dd57c9496aa68f0c0c05507405fdd413acb59bc668ce7e92e1936c68ec4b065c3c30123319884149e88228f0b2 +a77af9b094bc26966ddf2bf9e1520c898194a5ccb694915950dadc204facbe3066d3d89f50972642d76b14884cfbaa21 +8e76162932346869f4618bde744647f7ab52ab498ad654bdf2a4feeb986ac6e51370841e5acbb589e38b6e7142bb3049 +b60979ace17d6937ece72e4f015da4657a443dd01cebc7143ef11c09e42d4aa8855999a65a79e2ea0067f31c9fc2ab0f +b3e2ffdd5ee6fd110b982fd4fad4b93d0fca65478f986d086eeccb0804960bfaa1919afa743c2239973ea65091fe57d2 +8ce0ce05e7d7160d44574011da687454dbd3c8b8290aa671731b066e2c82f8cf2d63cb8e932d78c6122ec610e44660e6 +ab005dd8d297045c39e2f72fb1c48edb501ccf3575d3d04b9817b3afee3f0bb0f3f53f64bda37d1d9cde545aae999bae +95bd7edb4c4cd60e3cb8a72558845a3cce6bb7032ccdf33d5a49ebb6ddf203bc3c79e7b7e550735d2d75b04c8b2441e8 +889953ee256206284094e4735dbbb17975bafc7c3cb94c9fbfee4c3e653857bfd49e818f64a47567f721b98411a3b454 +b188423e707640ab0e75a061e0b62830cde8afab8e1ad3dae30db69ffae4e2fc005bababbdcbd7213b918ed4f70e0c14 +a97e0fafe011abd70d4f99a0b36638b3d6e7354284588f17a88970ed48f348f88392779e9a038c6cbc9208d998485072 +87db11014a91cb9b63e8dfaa82cdebca98272d89eb445ee1e3ff9dbaf2b3fad1a03b888cffc128e4fe208ed0dddece0f +aad2e40364edd905d66ea4ac9d51f9640d6fda9a54957d26ba233809851529b32c85660fa401dbee3679ec54fa6dd966 +863e99336ca6edf03a5a259e59a2d0f308206e8a2fb320cfc0be06057366df8e0f94b33a28f574092736b3c5ada84270 +b34bcc56a057589f34939a1adc51de4ff6a9f4fee9c7fa9aa131e28d0cf0759a0c871b640162acdfbf91f3f1b59a3703 +935dd28f2896092995c5eff1618e5b6efe7a40178888d7826da9b0503c2d6e68a28e7fac1a334e166d0205f0695ef614 +b842cd5f8f5de5ca6c68cb4a5c1d7b451984930eb4cc18fd0934d52fdc9c3d2d451b1c395594d73bc3451432bfba653f +9014537885ce2debad736bc1926b25fdab9f69b216bf024f589c49dc7e6478c71d595c3647c9f65ff980b14f4bb2283b +8e827ccca1dd4cd21707140d10703177d722be0bbe5cac578db26f1ef8ad2909103af3c601a53795435b27bf95d0c9ed +8a0b8ad4d466c09d4f1e9167410dbe2edc6e0e6229d4b3036d30f85eb6a333a18b1c968f6ca6d6889bb08fecde017ef4 +9241ee66c0191b06266332dc9161dede384c4bb4e116dbd0890f3c3790ec5566da4568243665c4725b718ac0f6b5c179 +aeb4d5fad81d2b505d47958a08262b6f1b1de9373c2c9ba6362594194dea3e002ab03b8cbb43f867be83065d3d370f19 +8781bc83bb73f7760628629fe19e4714b494dbed444c4e4e4729b7f6a8d12ee347841a199888794c2234f51fa26fc2b9 +b58864f0acd1c2afa29367e637cbde1968d18589245d9936c9a489c6c495f54f0113ecdcbe4680ac085dd3c397c4d0c3 +94a24284afaeead61e70f3e30f87248d76e9726759445ca18cdb9360586c60cc9f0ec1c397f9675083e0b56459784e2e +aed358853f2b54dcbddf865e1816c2e89be12e940e1abfa661e2ee63ffc24a8c8096be2072fa83556482c0d89e975124 +b95374e6b4fc0765708e370bc881e271abf2e35c08b056a03b847e089831ef4fe3124b9c5849d9c276eb2e35b3daf264 +b834cdbcfb24c8f84bfa4c552e7fadc0028a140952fd69ed13a516e1314a4cd35d4b954a77d51a1b93e1f5d657d0315d +8fb6d09d23bfa90e7443753d45a918d91d75d8e12ec7d016c0dfe94e5c592ba6aaf483d2f16108d190822d955ad9cdc3 +aa315cd3c60247a6ad4b04f26c5404c2713b95972843e4b87b5a36a89f201667d70f0adf20757ebe1de1b29ae27dda50 +a116862dca409db8beff5b1ccd6301cdd0c92ca29a3d6d20eb8b87f25965f42699ca66974dd1a355200157476b998f3b +b4c2f5fe173c4dc8311b60d04a65ce1be87f070ac42e13cd19c6559a2931c6ee104859cc2520edebbc66a13dc7d30693 +8d4a02bf99b2260c334e7d81775c5cf582b00b0c982ce7745e5a90624919028278f5e9b098573bad5515ce7fa92a80c8 +8543493bf564ce6d97bd23be9bff1aba08bd5821ca834f311a26c9139c92a48f0c2d9dfe645afa95fec07d675d1fd53b +9344239d13fde08f98cb48f1f87d34cf6abe8faecd0b682955382a975e6eed64e863fa19043290c0736261622e00045c +aa49d0518f343005ca72b9e6c7dcaa97225ce6bb8b908ebbe7b1a22884ff8bfb090890364e325a0d414ad180b8f161d1 +907d7fd3e009355ab326847c4a2431f688627faa698c13c03ffdd476ecf988678407f029b8543a475dcb3dafdf2e7a9c +845f1f10c6c5dad2adc7935f5cd2e2b32f169a99091d4f1b05babe7317b9b1cdce29b5e62f947dc621b9acbfe517a258 +8f3be8e3b380ea6cdf9e9c237f5e88fd5a357e5ded80ea1fc2019810814de82501273b4da38916881125b6fa0cfd4459 +b9c7f487c089bf1d20c822e579628db91ed9c82d6ca652983aa16d98b4270c4da19757f216a71b9c13ddee3e6e43705f +8ba2d8c88ad2b872db104ea8ddbb006ec2f3749fd0e19298a804bb3a5d94de19285cc7fb19fee58a66f7851d1a66c39f +9375ecd3ed16786fe161af5d5c908f56eeb467a144d3bbddfc767e90065b7c94fc53431adebecba2b6c9b5821184d36e +a49e069bfadb1e2e8bff6a4286872e2a9765d62f0eaa4fcb0e5af4bbbed8be3510fb19849125a40a8a81d1e33e81c3eb +9522cc66757b386aa6b88619525c8ce47a5c346d590bb3647d12f991e6c65c3ab3c0cfc28f0726b6756c892eae1672be +a9a0f1f51ff877406fa83a807aeb17b92a283879f447b8a2159653db577848cc451cbadd01f70441e351e9ed433c18bc +8ff7533dcff6be8714df573e33f82cf8e9f2bcaaa43e939c4759d52b754e502717950de4b4252fb904560fc31dce94a4 +959724671e265a28d67c29d95210e97b894b360da55e4cf16e6682e7912491ed8ca14bfaa4dce9c25a25b16af580494f +92566730c3002f4046c737032487d0833c971e775de59fe02d9835c9858e2e3bc37f157424a69764596c625c482a2219 +a84b47ceff13ed9c3e5e9cdf6739a66d3e7c2bd8a6ba318fefb1a9aecf653bb2981da6733ddb33c4b0a4523acc429d23 +b4ddf571317e44f859386d6140828a42cf94994e2f1dcbcc9777f4eebbfc64fc1e160b49379acc27c4672b8e41835c5d +8ab95c94072b853d1603fdd0a43b30db617d13c1d1255b99075198e1947bfa5f59aed2b1147548a1b5e986cd9173d15c +89511f2eab33894fd4b3753d24249f410ff7263052c1fef6166fc63a79816656b0d24c529e45ccce6be28de6e375d916 +a0866160ca63d4f2be1b4ea050dac6b59db554e2ebb4e5b592859d8df339b46fd7cb89aaed0951c3ee540aee982c238a +8fcc5cbba1b94970f5ff2eb1922322f5b0aa7d918d4b380c9e7abfd57afd8b247c346bff7b87af82efbce3052511cd1b +99aeb2a5e846b0a2874cca02c66ed40d5569eb65ab2495bc3f964a092e91e1517941f2688e79f8cca49cd3674c4e06dc +b7a096dc3bad5ca49bee94efd884aa3ff5615cf3825cf95fbe0ce132e35f46581d6482fa82666c7ef5f1643eaee8f1ca +94393b1da6eaac2ffd186b7725eca582f1ddc8cdd916004657f8a564a7c588175cb443fc6943b39029f5bbe0add3fad8 +884b85fe012ccbcd849cb68c3ad832d83b3ef1c40c3954ffdc97f103b1ed582c801e1a41d9950f6bddc1d11f19d5ec76 +b00061c00131eded8305a7ce76362163deb33596569afb46fe499a7c9d7a0734c084d336b38d168024c2bb42b58e7660 +a439153ac8e6ca037381e3240e7ba08d056c83d7090f16ed538df25901835e09e27de2073646e7d7f3c65056af6e4ce7 +830fc9ca099097d1f38b90e6843dc86f702be9d20bdacc3e52cae659dc41df5b8d2c970effa6f83a5229b0244a86fe22 +b81ea2ffaaff2bb00dd59a9ab825ba5eed4db0d8ac9c8ed1a632ce8f086328a1cddd045fbe1ace289083c1325881b7e7 +b51ea03c58daf2db32c99b9c4789b183365168cb5019c72c4cc91ac30b5fb7311d3db76e6fa41b7cd4a8c81e2f6cdc94 +a4170b2c6d09ca5beb08318730419b6f19215ce6c631c854116f904be3bc30dd85a80c946a8ab054d3e307afaa3f8fbc +897cc42ff28971ff54d2a55dd6b35cfb8610ac902f3c06e3a5cea0e0a257e870c471236a8e84709211c742a09c5601a6 +a18f2e98d389dace36641621488664ecbb422088ab03b74e67009b8b8acacaaa24fdcf42093935f355207d934adc52a8 +92adcfb678cc2ba19c866f3f2b988fdcb4610567f3ab436cc0cb9acaf5a88414848d71133ebdbec1983e38e6190f1b5f +a86d43c2ce01b366330d3b36b3ca85f000c3548b8297e48478da1ee7d70d8576d4650cba7852ed125c0d7cb6109aa7f3 +8ed31ceed9445437d7732dce78a762d72ff32a7636bfb3fd7974b7ae15db414d8184a1766915244355deb354fbc5803b +9268f70032584f416e92225d65af9ea18c466ebc7ae30952d56a4e36fd9ea811dde0a126da9220ba3c596ec54d8a335e +9433b99ee94f2d3fbdd63b163a2bdf440379334c52308bd24537f7defd807145a062ff255a50d119a7f29f4b85d250e3 +90ce664f5e4628a02278f5cf5060d1a34f123854634b1870906e5723ac9afd044d48289be283b267d45fcbf3f4656aaf +aaf21c4d59378bb835d42ae5c5e5ab7a3c8c36a59e75997989313197752b79a472d866a23683b329ea69b048b87fa13e +b83c0589b304cec9ede549fde54f8a7c2a468c6657da8c02169a6351605261202610b2055c639b9ed2d5b8c401fb8f56 +9370f326ea0f170c2c05fe2c5a49189f20aec93b6b18a5572a818cd4c2a6adb359e68975557b349fb54f065d572f4c92 +ac3232fa5ce6f03fca238bef1ce902432a90b8afce1c85457a6bee5571c033d4bceefafc863af04d4e85ac72a4d94d51 +80d9ea168ff821b22c30e93e4c7960ce3ad3c1e6deeebedd342a36d01bd942419b187e2f382dbfd8caa34cca08d06a48 +a387a3c61676fb3381eefa2a45d82625635a666e999aba30e3b037ec9e040f414f9e1ad9652abd3bcad63f95d85038db +a1b229fe32121e0b391b0f6e0180670b9dc89d79f7337de4c77ea7ad0073e9593846f06797c20e923092a08263204416 +92164a9d841a2b828cedf2511213268b698520f8d1285852186644e9a0c97512cafa4bfbe29af892c929ebccd102e998 +82ee2fa56308a67c7db4fd7ef539b5a9f26a1c2cc36da8c3206ba4b08258fbb3cec6fe5cdbd111433fb1ba2a1e275927 +8c77bfe9e191f190a49d46f05600603fa42345592539b82923388d72392404e0b29a493a15e75e8b068dddcd444c2928 +80b927f93ccf79dcf5c5b20bcf5a7d91d7a17bc0401bb7cc9b53a6797feac31026eb114257621f5a64a52876e4474cc1 +b6b68b6501c37804d4833d5a063dd108a46310b1400549074e3cac84acc6d88f73948b7ad48d686de89c1ec043ae8c1a +ab3da00f9bdc13e3f77624f58a3a18fc3728956f84b5b549d62f1033ae4b300538e53896e2d943f160618e05af265117 +b6830e87233b8eace65327fdc764159645b75d2fd4024bf8f313b2dd5f45617d7ecfb4a0b53ccafb5429815a9a1adde6 +b9251cfe32a6dc0440615aadcd98b6b1b46e3f4e44324e8f5142912b597ee3526bea2431e2b0282bb58f71be5b63f65e +af8d70711e81cdddfb39e67a1b76643292652584c1ce7ce4feb1641431ad596e75c9120e85f1a341e7a4da920a9cdd94 +98cd4e996594e89495c078bfd52a4586b932c50a449a7c8dfdd16043ca4cda94dafbaa8ad1b44249c99bbcc52152506e +b9fc6d1c24f48404a4a64fbe3e43342738797905db46e4132aee5f086aaa4c704918ad508aaefa455cfe1b36572e6242 +a365e871d30ba9291cedaba1be7b04e968905d003e9e1af7e3b55c5eb048818ae5b913514fb08b24fb4fbdccbb35d0b8 +93bf99510971ea9af9f1e364f1234c898380677c8e8de9b0dd24432760164e46c787bc9ec42a7ad450500706cf247b2d +b872f825a5b6e7b9c7a9ddfeded3516f0b1449acc9b4fd29fc6eba162051c17416a31e5be6d3563f424d28e65bab8b8f +b06b780e5a5e8eb4f4c9dc040f749cf9709c8a4c9ef15e925f442b696e41e5095db0778a6c73bcd329b265f2c6955c8b +848f1a981f5fc6cd9180cdddb8d032ad32cdfa614fc750d690dbae36cc0cd355cbf1574af9b3ffc8b878f1b2fafb9544 +a03f48cbff3e9e8a3a655578051a5ae37567433093ac500ed0021c6250a51b767afac9bdb194ee1e3eac38a08c0eaf45 +b5be78ce638ff8c4aa84352b536628231d3f7558c5be3bf010b28feac3022e64691fa672f358c8b663904aebe24a54ed +a9d4da70ff676fa55d1728ba6ab03b471fa38b08854d99e985d88c2d050102d8ccffbe1c90249a5607fa7520b15fe791 +8fe9f7092ffb0b69862c8e972fb1ecf54308c96d41354ed0569638bb0364f1749838d6d32051fff1599112978c6e229c +ae6083e95f37770ecae0df1e010456f165d96cfe9a7278c85c15cffd61034081ce5723e25e2bede719dc9341ec8ed481 +a260891891103089a7afbd9081ea116cfd596fd1015f5b65e10b0961eb37fab7d09c69b7ce4be8bf35e4131848fb3fe4 +8d729fa32f6eb9fd2f6a140bef34e8299a2f3111bffd0fe463aa8622c9d98bfd31a1df3f3e87cd5abc52a595f96b970e +a30ec6047ae4bc7da4daa7f4c28c93aedb1112cfe240e681d07e1a183782c9ff6783ac077c155af23c69643b712a533f +ac830726544bfe7b5467339e5114c1a75f2a2a8d89453ce86115e6a789387e23551cd64620ead6283dfa4538eb313d86 +8445c135b7a48068d8ed3e011c6d818cfe462b445095e2fbf940301e50ded23f272d799eea47683fc027430ce14613ef +95785411715c9ae9d8293ce16a693a2aa83e3cb1b4aa9f76333d0da2bf00c55f65e21e42e50e6c5772ce213dd7b4f7a0 +b273b024fa18b7568c0d1c4d2f0c4e79ec509dafac8c5951f14192d63ddbcf2d8a7512c1c1b615cc38fa3e336618e0c5 +a78b9d3ea4b6a90572eb27956f411f1d105fdb577ee2ffeec9f221da9b45db84bfe866af1f29597220c75e0c37a628d8 +a4be2bf058c36699c41513c4d667681ce161a437c09d81383244fc55e1c44e8b1363439d0cce90a3e44581fb31d49493 +b6eef13040f17dd4eba22aaf284d2f988a4a0c4605db44b8d2f4bf9567ac794550b543cc513c5f3e2820242dd704152e +87eb00489071fa95d008c5244b88e317a3454652dcb1c441213aa16b28cd3ecaa9b22fec0bdd483c1df71c37119100b1 +92d388acdcb49793afca329cd06e645544d2269234e8b0b27d2818c809c21726bc9cf725651b951e358a63c83dedee24 +ae27e219277a73030da27ab5603c72c8bd81b6224b7e488d7193806a41343dff2456132274991a4722fdb0ef265d04cd +97583e08ecb82bbc27c0c8476d710389fa9ffbead5c43001bd36c1b018f29faa98de778644883e51870b69c5ffb558b5 +90a799a8ce73387599babf6b7da12767c0591cadd36c20a7990e7c05ea1aa2b9645654ec65308ee008816623a2757a6a +a1b47841a0a2b06efd9ab8c111309cc5fc9e1d5896b3e42ed531f6057e5ade8977c29831ce08dbda40348386b1dcc06d +b92b8ef59bbddb50c9457691bc023d63dfcc54e0fd88bd5d27a09e0d98ac290fc90e6a8f6b88492043bf7c87fac8f3e4 +a9d6240b07d62e22ec8ab9b1f6007c975a77b7320f02504fc7c468b4ee9cfcfd945456ff0128bc0ef2174d9e09333f8d +8e96534c94693226dc32bca79a595ca6de503af635f802e86442c67e77564829756961d9b701187fe91318da515bf0e6 +b6ba290623cd8dd5c2f50931c0045d1cfb0c30877bc8fe58cbc3ff61ee8da100045a39153916efa1936f4aee0892b473 +b43baa7717fac02d4294f5b3bb5e58a65b3557747e3188b482410388daac7a9c177f762d943fd5dcf871273921213da8 +b9cf00f8fb5e2ef2b836659fece15e735060b2ea39b8e901d3dcbdcf612be8bf82d013833718c04cd46ffaa70b85f42e +8017d0c57419e414cbba504368723e751ef990cc6f05dad7b3c2de6360adc774ad95512875ab8337d110bf39a42026fa +ae7401048b838c0dcd4b26bb6c56d79d51964a0daba780970b6c97daee4ea45854ea0ac0e4139b3fe60dac189f84df65 +887b237b0cd0f816b749b21db0b40072f9145f7896c36916296973f9e6990ede110f14e5976c906d08987c9836cca57f +a88c3d5770148aee59930561ca1223aceb2c832fb5417e188dca935905301fc4c6c2c9270bc1dff7add490a125eb81c6 +b6cf9b02c0cd91895ad209e38c54039523f137b5848b9d3ad33ae43af6c20c98434952db375fe378de7866f2d0e8b18a +84ef3d322ff580c8ad584b1fe4fe346c60866eb6a56e982ba2cf3b021ecb1fdb75ecc6c29747adda86d9264430b3f816 +a0561c27224baf0927ad144cb71e31e54a064c598373fcf0d66aebf98ab7af1d8e2f343f77baefff69a6da750a219e11 +aa5cc43f5b8162b016f5e1b61214c0c9d15b1078911c650b75e6cdfb49b85ee04c6739f5b1687d15908444f691f732de +ad4ac099b935589c7b8fdfdf3db332b7b82bb948e13a5beb121ebd7db81a87d278024a1434bcf0115c54ca5109585c3d +8a00466abf3f109a1dcd19e643b603d3af23d42794ef8ca2514dd507ecea44a031ac6dbc18bd02f99701168b25c1791e +b00b5900dfad79645f8bee4e5adc7b84eb22e5b1e67df77ccb505b7fc044a6c08a8ea5faca662414eb945f874f884cea +950e204e5f17112250b22ea6bb8423baf522fc0af494366f18fe0f949f51d6e6812074a80875cf1ed9c8e7420058d541 +91e5cbf8bb1a1d50c81608c9727b414d0dd2fb467ebc92f100882a3772e54f94979cfdf8e373fdef7c7fcdd60fec9e00 +a093f6a857b8caaff80599c2e89c962b415ecbaa70d8fd973155fa976a284c6b29a855f5f7a3521134d00d2972755188 +b4d55a3551b00da54cc010f80d99ddd2544bde9219a3173dfaadf3848edc7e4056ab532fb75ac26f5f7141e724267663 +a03ea050fc9b011d1b04041b5765d6f6453a93a1819cd9bd6328637d0b428f08526466912895dcc2e3008ee58822e9a7 +99b12b3665e473d01bc6985844f8994fb65cb15745024fb7af518398c4a37ff215da8f054e8fdf3286984ae36a73ca5e +9972c7e7a7fb12e15f78d55abcaf322c11249cd44a08f62c95288f34f66b51f146302bce750ff4d591707075d9123bd2 +a64b4a6d72354e596d87cda213c4fc2814009461570ccb27d455bbe131f8d948421a71925425b546d8cf63d5458cd64b +91c215c73b195795ede2228b7ed1f6e37892e0c6b0f4a0b5a16c57aa1100c84df9239054a173b6110d6c2b7f4bf1ce52 +88807198910ec1303480f76a3683870246a995e36adaeadc29c22f0bdba8152fe705bd070b75de657b04934f7d0ccf80 +b37c0026c7b32eb02cacac5b55cb5fe784b8e48b2945c64d3037af83ece556a117f0ff053a5968c2f5fa230e291c1238 +94c768384ce212bc2387e91ce8b45e4ff120987e42472888a317abc9dcdf3563b62e7a61c8e98d7cdcbe272167d91fc6 +a10c2564936e967a390cb14ef6e8f8b04ea9ece5214a38837eda09e79e0c7970b1f83adf017c10efd6faa8b7ffa2c567 +a5085eed3a95f9d4b1269182ea1e0d719b7809bf5009096557a0674bde4201b0ddc1f0f16a908fc468846b3721748ce3 +87468eb620b79a0a455a259a6b4dfbc297d0d53336537b771254dd956b145dc816b195b7002647ea218552e345818a3f +ace2b77ffb87366af0a9cb5d27d6fc4a14323dbbf1643f5f3c4559306330d86461bb008894054394cbfaefeaa0bc2745 +b27f56e840a54fbd793f0b7a7631aa4cee64b5947e4382b2dfb5eb1790270288884c2a19afebe5dc0c6ef335d4531c1c +876e438633931f7f895062ee16c4b9d10428875f7bc79a8e156a64d379a77a2c45bf5430c5ab94330f03da352f1e9006 +a2512a252587d200d2092b44c914df54e04ff8bcef36bf631f84bde0cf5a732e3dc7f00f662842cfd74b0b0f7f24180e +827f1bc8f54a35b7a4bd8154f79bcc055e45faed2e74adf7cf21cca95df44d96899e847bd70ead6bb27b9c0ed97bbd8b +a0c92cf5a9ed843714f3aea9fe7b880f622d0b4a3bf66de291d1b745279accf6ba35097849691370f41732ba64b5966b +a63f5c1e222775658421c487b1256b52626c6f79cb55a9b7deb2352622cedffb08502042d622eb3b02c97f9c09f9c957 +8cc093d52651e65fb390e186db6cc4de559176af4624d1c44cb9b0e836832419dacac7b8db0627b96288977b738d785d +aa7b6a17dfcec146134562d32a12f7bd7fe9522e300859202a02939e69dbd345ed7ff164a184296268f9984f9312e8fc +8ac76721f0d2b679f023d06cbd28c85ae5f4b43c614867ccee88651d4101d4fd352dbdb65bf36bfc3ebc0109e4b0c6f9 +8d350f7c05fc0dcd9a1170748846fb1f5d39453e4cb31e6d1457bed287d96fc393b2ecc53793ca729906a33e59c6834a +b9913510dfc5056d7ec5309f0b631d1ec53e3a776412ada9aefdaf033c90da9a49fdde6719e7c76340e86599b1f0eec2 +94955626bf4ce87612c5cfffcf73bf1c46a4c11a736602b9ba066328dc52ad6d51e6d4f53453d4ed55a51e0aad810271 +b0fcab384fd4016b2f1e53f1aafd160ae3b1a8865cd6c155d7073ecc1664e05b1d8bca1def39c158c7086c4e1103345e +827de3f03edfbde08570b72de6662c8bfa499b066a0a27ebad9b481c273097d17a5a0a67f01553da5392ec3f149b2a78 +ab7940384c25e9027c55c40df20bd2a0d479a165ced9b1046958353cd69015eeb1e44ed2fd64e407805ba42df10fc7bf +8ad456f6ff8cd58bd57567d931f923d0c99141978511b17e03cab7390a72b9f62498b2893e1b05c7c22dd274e9a31919 +ac75399e999effe564672db426faa17a839e57c5ef735985c70cd559a377adec23928382767b55ed5a52f7b11b54b756 +b17f975a00b817299ac7af5f2024ea820351805df58b43724393bfb3920a8cd747a3bbd4b8286e795521489db3657168 +a2bed800a6d95501674d9ee866e7314063407231491d794f8cf57d5be020452729c1c7cefd8c50dc1540181f5caab248 +9743f5473171271ffdd3cc59a3ae50545901a7b45cd4bc3570db487865f3b73c0595bebabbfe79268809ee1862e86e4a +b7eab77c2d4687b60d9d7b04e842b3880c7940140012583898d39fcc22d9b9b0a9be2c2e3788b3e6f30319b39c338f09 +8e2b8f797a436a1b661140e9569dcf3e1eea0a77c7ff2bc4ff0f3e49af04ed2de95e255df8765f1d0927fb456a9926b1 +8aefea201d4a1f4ff98ffce94e540bb313f2d4dfe7e9db484a41f13fc316ed02b282e1acc9bc6f56cad2dc2e393a44c9 +b950c17c0e5ca6607d182144aa7556bb0efe24c68f06d79d6413a973b493bfdf04fd147a4f1ab03033a32004cc3ea66f +b7b8dcbb179a07165f2dc6aa829fad09f582a71b05c3e3ea0396bf9e6fe73076f47035c031c2101e8e38e0d597eadd30 +a9d77ed89c77ec1bf8335d08d41c3c94dcca9fd1c54f22837b4e54506b212aa38d7440126c80648ab7723ff18e65ed72 +a819d6dfd4aef70e52b8402fe5d135f8082d40eb7d3bb5c4d7997395b621e2bb10682a1bad2c9caa33dd818550fc3ec6 +8f6ee34128fac8bbf13ce2d68b2bb363eb4fd65b297075f88e1446ddeac242500eeb4ef0735e105882ff5ba8c44c139b +b4440e48255c1644bcecf3a1e9958f1ec4901cb5b1122ee5b56ffd02cad1c29c4266999dbb85aa2605c1b125490074d4 +a43304a067bede5f347775d5811cf65a6380a8d552a652a0063580b5c5ef12a0867a39c7912fa219e184f4538eba1251 +a891ad67a790089ffc9f6d53e6a3d63d3556f5f693e0cd8a7d0131db06fd4520e719cfcc3934f0a8f62a95f90840f1d4 +aea6df8e9bb871081aa0fc5a9bafb00be7d54012c5baf653791907d5042a326aeee966fd9012a582cc16695f5baf7042 +8ffa2660dc52ed1cd4eff67d6a84a8404f358a5f713d04328922269bee1e75e9d49afeec0c8ad751620f22352a438e25 +87ec6108e2d63b06abed350f8b363b7489d642486f879a6c3aa90e5b0f335efc2ff2834eef9353951a42136f8e6a1b32 +865619436076c2760d9e87ddc905023c6de0a8d56eef12c98a98c87837f2ca3f27fd26a2ad752252dbcbe2b9f1d5a032 +980437dce55964293cb315c650c5586ffd97e7a944a83f6618af31c9d92c37b53ca7a21bb5bc557c151b9a9e217e7098 +95d128fc369df4ad8316b72aea0ca363cbc7b0620d6d7bb18f7076a8717a6a46956ff140948b0cc4f6d2ce33b5c10054 +8c7212d4a67b9ec70ebbca04358ad2d36494618d2859609163526d7b3acc2fc935ca98519380f55e6550f70a9bc76862 +893a2968819401bf355e85eee0f0ed0406a6d4a7d7f172d0017420f71e00bb0ba984f6020999a3cdf874d3cd8ebcd371 +9103c1af82dece25d87274e89ea0acd7e68c2921c4af3d8d7c82ab0ed9990a5811231b5b06113e7fa43a6bd492b4564f +99cfd87a94eab7d35466caa4ed7d7bb45e5c932b2ec094258fb14bf205659f83c209b83b2f2c9ccb175974b2a33e7746 +874b6b93e4ee61be3f00c32dd84c897ccd6855c4b6251eb0953b4023634490ed17753cd3223472873cbc6095b2945075 +84a32c0dc4ea60d33aac3e03e70d6d639cc9c4cc435c539eff915017be3b7bdaba33349562a87746291ebe9bc5671f24 +a7057b24208928ad67914e653f5ac1792c417f413d9176ba635502c3f9c688f7e2ee81800d7e3dc0a340c464da2fd9c5 +a03fb9ed8286aacfa69fbd5d953bec591c2ae4153400983d5dbb6cd9ea37fff46ca9e5cceb9d117f73e9992a6c055ad2 +863b2de04e89936c9a4a2b40380f42f20aefbae18d03750fd816c658aee9c4a03df7b12121f795c85d01f415baaeaa59 +8526eb9bd31790fe8292360d7a4c3eed23be23dd6b8b8f01d2309dbfdc0cfd33ad1568ddd7f8a610f3f85a9dfafc6a92 +b46ab8c5091a493d6d4d60490c40aa27950574a338ea5bbc045be3a114af87bdcb160a8c80435a9b7ad815f3cb56a3f3 +aeadc47b41a8d8b4176629557646202f868b1d728b2dda58a347d937e7ffc8303f20d26d6c00b34c851b8aeec547885d +aebb19fc424d72c1f1822aa7adc744cd0ef7e55727186f8df8771c784925058c248406ebeeaf3c1a9ee005a26e9a10c6 +8ff96e81c1a4a2ab1b4476c21018fae0a67e92129ee36120cae8699f2d7e57e891f5c624902cb1b845b944926a605cc3 +8251b8d2c43fadcaa049a9e7aff838dae4fb32884018d58d46403ac5f3beb5c518bfd45f03b8abb710369186075eb71c +a8b2a64f865f51a5e5e86a66455c093407933d9d255d6b61e1fd81ffafc9538d73caaf342338a66ba8ee166372a3d105 +aad915f31c6ba7fdc04e2aaac62e84ef434b7ee76a325f07dc430d12c84081999720181067b87d792efd0117d7ee1eab +a13db3bb60389883fd41d565c54fb5180d9c47ce2fe7a169ae96e01d17495f7f4fa928d7e556e7c74319c4c25d653eb2 +a4491b0198459b3f552855d680a59214eb74e6a4d6c5fa3b309887dc50ebea2ecf6d26c040550f7dc478b452481466fb +8f017f13d4b1e3f0c087843582b52d5f8d13240912254d826dd11f8703a99a2f3166dfbdfdffd9a3492979d77524276b +96c3d5dcd032660d50d7cd9db2914f117240a63439966162b10c8f1f3cf74bc83b0f15451a43b31dbd85e4a7ce0e4bb1 +b479ec4bb79573d32e0ec93b92bdd7ec8c26ddb5a2d3865e7d4209d119fd3499eaac527615ffac78c440e60ef3867ae0 +b2c49c4a33aa94b52b6410b599e81ff15490aafa7e43c8031c865a84e4676354a9c81eb4e7b8be6825fdcefd1e317d44 +906dc51d6a90c089b6704b47592805578a6eed106608eeb276832f127e1b8e858b72e448edcbefb497d152447e0e68ff +b0e81c63b764d7dfbe3f3fddc9905aef50f3633e5d6a4af6b340495124abedcff5700dfd1577bbbed7b6bf97d02719cb +9304c64701e3b4ed6d146e48a881f7d83a17f58357cca0c073b2bb593afd2d94f6e2a7a1ec511d0a67ad6ff4c3be5937 +b6fdbd12ba05aa598d80b83f70a15ef90e5cba7e6e75fa038540ee741b644cd1f408a6cecfd2a891ef8d902de586c6b5 +b80557871a6521b1b3c74a1ba083ae055b575df607f1f7b04c867ba8c8c181ea68f8d90be6031f4d25002cca27c44da2 +aa7285b8e9712e06b091f64163f1266926a36607f9d624af9996856ed2aaf03a580cb22ce407d1ade436c28b44ca173f +8148d72b975238b51e6ea389e5486940d22641b48637d7dfadfa603a605bfc6d74a016480023945d0b85935e396aea5d +8a014933a6aea2684b5762af43dcf4bdbb633cd0428d42d71167a2b6fc563ece5e618bff22f1db2ddb69b845b9a2db19 +990d91740041db770d0e0eb9d9d97d826f09fd354b91c41e0716c29f8420e0e8aac0d575231efba12fe831091ec38d5a +9454d0d32e7e308ddec57cf2522fb1b67a2706e33fb3895e9e1f18284129ab4f4c0b7e51af25681d248d7832c05eb698 +a5bd434e75bac105cb3e329665a35bce6a12f71dd90c15165777d64d4c13a82bceedb9b48e762bd24034e0fc9fbe45f4 +b09e3b95e41800d4dc29c6ffdaab2cd611a0050347f6414f154a47ee20ee59bf8cf7181454169d479ebce1eb5c777c46 +b193e341d6a047d15eea33766d656d807b89393665a783a316e9ba10518e5515c8e0ade3d6e15641d917a8a172a5a635 +ade435ec0671b3621dde69e07ead596014f6e1daa1152707a8c18877a8b067bde2895dd47444ffa69db2bbef1f1d8816 +a7fd3d6d87522dfc56fb47aef9ce781a1597c56a8bbfd796baba907afdc872f753d732bfda1d3402aee6c4e0c189f52d +a298cb4f4218d0464b2fab393e512bbc477c3225aa449743299b2c3572f065bc3a42d07e29546167ed9e1b6b3b3a3af3 +a9ee57540e1fd9c27f4f0430d194b91401d0c642456c18527127d1f95e2dba41c2c86d1990432eb38a692fda058fafde +81d6c1a5f93c04e6d8e5a7e0678c1fc89a1c47a5c920bcd36180125c49fcf7c114866b90e90a165823560b19898a7c16 +a4b7a1ec9e93c899b9fd9aaf264c50e42c36c0788d68296a471f7a3447af4dbc81e4fa96070139941564083ec5b5b5a1 +b3364e327d381f46940c0e11e29f9d994efc6978bf37a32586636c0070b03e4e23d00650c1440f448809e1018ef9f6d8 +8056e0913a60155348300e3a62e28b5e30629a90f7dd4fe11289097076708110a1d70f7855601782a3cdc5bdb1ca9626 +b4980fd3ea17bac0ba9ee1c470b17e575bb52e83ebdd7d40c93f4f87bebeaff1c8a679f9d3d09d635f068d37d5bd28bd +905a9299e7e1853648e398901dfcd437aa575c826551f83520df62984f5679cb5f0ea86aa45ed3e18b67ddc0dfafe809 +ab99553bf31a84f2e0264eb34a08e13d8d15e2484aa9352354becf9a15999c76cc568d68274b70a65e49703fc23540d0 +a43681597bc574d2dae8964c9a8dc1a07613d7a1272bdcb818d98c85d44e16d744250c33f3b5e4d552d97396b55e601f +a54e5a31716fccb50245898c99865644405b8dc920ded7a11f3d19bdc255996054b268e16f2e40273f11480e7145f41e +8134f3ad5ef2ad4ba12a8a4e4d8508d91394d2bcdc38b7c8c8c0b0a820357ac9f79d286c65220f471eb1adca1d98fc68 +94e2f755e60471578ab2c1adb9e9cea28d4eec9b0e92e0140770bca7002c365fcabfe1e5fb4fe6cfe79a0413712aa3ef +ad48f8d0ce7eb3cc6e2a3086ad96f562e5bed98a360721492ae2e74dc158586e77ec8c35d5fd5927376301b7741bad2b +8614f0630bdd7fbad3a31f55afd9789f1c605dc85e7dc67e2edfd77f5105f878bb79beded6e9f0b109e38ea7da67e8d5 +9804c284c4c5e77dabb73f655b12181534ca877c3e1e134aa3f47c23b7ec92277db34d2b0a5d38d2b69e5d1c3008a3e3 +a51b99c3088e473afdaa9e0a9f7e75a373530d3b04e44e1148da0726b95e9f5f0c7e571b2da000310817c36f84b19f7f +ac4ff909933b3b76c726b0a382157cdc74ab851a1ac6cef76953c6444441804cc43abb883363f416592e8f6cfbc4550b +ae7d915eb9fc928b65a29d6edbc75682d08584d0014f7bcf17d59118421ae07d26a02137d1e4de6938bcd1ab8ef48fad +852f7e453b1af89b754df6d11a40d5d41ea057376e8ecacd705aacd2f917457f4a093d6b9a8801837fa0f62986ad7149 +92c6bf5ada5d0c3d4dd8058483de36c215fa98edab9d75242f3eff9db07c734ad67337da6f0eefe23a487bf75a600dee +a2b42c09d0db615853763552a48d2e704542bbd786aae016eb58acbf6c0226c844f5fb31e428cb6450b9db855f8f2a6f +880cc07968266dbfdcfbc21815cd69e0eddfee239167ac693fb0413912d816f2578a74f7716eecd6deefa68c6eccd394 +b885b3ace736cd373e8098bf75ba66fa1c6943ca1bc4408cd98ac7074775c4478594f91154b8a743d9c697e1b29f5840 +a51ce78de512bd87bfa0835de819941dffbf18bec23221b61d8096fc9436af64e0693c335b54e7bfc763f287bdca2db6 +a3c76166a3bdb9b06ef696e57603b58871bc72883ee9d45171a30fe6e1d50e30bc9c51b4a0f5a7270e19a77b89733850 +acefc5c6f8a1e7c24d7b41e0fc7f6f3dc0ede6cf3115ffb9a6e54b1d954cbca9bda8ad7a084be9be245a1b8e9770d141 +b420ed079941842510e31cfad117fa11fb6b4f97dfbc6298cb840f27ebaceba23eeaf3f513bcffbf5e4aae946310182d +95c3bb5ef26c5ed2f035aa5d389c6b3c15a6705b9818a3fefaed28922158b35642b2e8e5a1a620fdad07e75ad4b43af4 +825149f9081ecf07a2a4e3e8b5d21bade86c1a882475d51c55ee909330b70c5a2ac63771c8600c6f38df716af61a3ea1 +873b935aae16d9f08adbc25353cee18af2f1b8d5f26dec6538d6bbddc515f2217ed7d235dcfea59ae61b428798b28637 +9294150843a2bedcedb3bb74c43eb28e759cf9499582c5430bccefb574a8ddd4f11f9929257ff4c153990f9970a2558f +b619563a811cc531da07f4f04e5c4c6423010ff9f8ed7e6ec9449162e3d501b269fb1c564c09c0429431879b0f45df02 +91b509b87eb09f007d839627514658c7341bc76d468920fe8a740a8cb96a7e7e631e0ea584a7e3dc1172266f641d0f5c +8b8aceace9a7b9b4317f1f01308c3904d7663856946afbcea141a1c615e21ccad06b71217413e832166e9dd915fbe098 +87b3b36e725833ea0b0f54753c3728c0dbc87c52d44d705ffc709f2d2394414c652d3283bab28dcce09799504996cee0 +b2670aad5691cbf308e4a6a77a075c4422e6cbe86fdba24e9f84a313e90b0696afb6a067eebb42ba2d10340d6a2f6e51 +876784a9aff3d54faa89b2bacd3ff5862f70195d0b2edc58e8d1068b3c9074c0da1cfa23671fe12f35e33b8a329c0ccd +8b48b9e758e8a8eae182f5cbec96f67d20cca6d3eee80a2d09208eb1d5d872e09ef23d0df8ebbb9b01c7449d0e3e3650 +b79303453100654c04a487bdcadc9e3578bc80930c489a7069a52e8ca1dba36c492c8c899ce025f8364599899baa287d +961b35a6111da54ece6494f24dacd5ea46181f55775b5f03df0e370c34a5046ac2b4082925855325bb42bc2a2c98381d +a31feb1be3f5a0247a1f7d487987eb622e34fca817832904c6ee3ee60277e5847945a6f6ea1ac24542c72e47bdf647df +a12a2aa3e7327e457e1aae30e9612715dd2cfed32892c1cd6dcda4e9a18203af8a44afb46d03b2eed89f6b9c5a2c0c23 +a08265a838e69a2ca2f80fead6ccf16f6366415b920c0b22ee359bcd8d4464ecf156f400a16a7918d52e6d733dd64211 +b723d6344e938d801cca1a00032af200e541d4471fd6cbd38fb9130daa83f6a1dffbbe7e67fc20f9577f884acd7594b2 +a6733d83ec78ba98e72ddd1e7ff79b7adb0e559e256760d0c590a986e742445e8cdf560d44b29439c26d87edd0b07c8c +a61c2c27d3f7b9ff4695a17afedf63818d4bfba390507e1f4d0d806ce8778d9418784430ce3d4199fd3bdbc2504d2af3 +8332f3b63a6dc985376e8b1b25eeae68be6160fbe40053ba7bcf6f073204f682da72321786e422d3482fd60c9e5aa034 +a280f44877583fbb6b860d500b1a3f572e3ee833ec8f06476b3d8002058e25964062feaa1e5bec1536d734a5cfa09145 +a4026a52d277fcea512440d2204f53047718ebfcae7b48ac57ea7f6bfbc5de9d7304db9a9a6cbb273612281049ddaec5 +95cdf69c831ab2fad6c2535ede9c07e663d2ddccc936b64e0843d2df2a7b1c31f1759c3c20f1e7a57b1c8f0dbb21b540 +95c96cec88806469c277ab567863c5209027cecc06c7012358e5f555689c0d9a5ffb219a464f086b45817e8536b86d2f +afe38d4684132a0f03d806a4c8df556bf589b25271fbc6fe2e1ed16de7962b341c5003755da758d0959d2e6499b06c68 +a9b77784fda64987f97c3a23c5e8f61b918be0f7c59ba285084116d60465c4a2aaafc8857eb16823282cc83143eb9126 +a830f05881ad3ce532a55685877f529d32a5dbe56cea57ffad52c4128ee0fad0eeaf0da4362b55075e77eda7babe70e5 +992b3ad190d6578033c13ed5abfee4ef49cbc492babb90061e3c51ee4b5790cdd4c8fc1abff1fa2c00183b6b64f0bbbe +b1015424d9364aeff75de191652dc66484fdbec3e98199a9eb9671ec57bec6a13ff4b38446e28e4d8aedb58dd619cd90 +a745304604075d60c9db36cada4063ac7558e7ec2835d7da8485e58d8422e817457b8da069f56511b02601289fbb8981 +a5ba4330bc5cb3dbe0486ddf995632a7260a46180a08f42ae51a2e47778142132463cc9f10021a9ad36986108fefa1a9 +b419e9fd4babcaf8180d5479db188bb3da232ae77a1c4ed65687c306e6262f8083070a9ac32220cddb3af2ec73114092 +a49e23dc5f3468f3bf3a0bb7e4a114a788b951ff6f23a3396ae9e12cbff0abd1240878a3d1892105413dbc38818e807c +b7ecc7b4831f650202987e85b86bc0053f40d983f252e9832ef503aea81c51221ce93279da4aa7466c026b2d2070e55d +96a8c35cb87f84fa84dcd6399cc2a0fd79cc9158ef4bdde4bae31a129616c8a9f2576cd19baa3f497ca34060979aed7d +8681b2c00aa62c2b519f664a95dcb8faef601a3b961bb4ce5d85a75030f40965e2983871d41ea394aee934e859581548 +85c229a07efa54a713d0790963a392400f55fbb1a43995a535dc6c929f20d6a65cf4efb434e0ad1cb61f689b8011a3bc +90856f7f3444e5ad44651c28e24cc085a5db4d2ffe79aa53228c26718cf53a6e44615f3c5cda5aa752d5f762c4623c66 +978999b7d8aa3f28a04076f74d11c41ef9c89fdfe514936c4238e0f13c38ec97e51a5c078ebc6409e517bfe7ccb42630 +a099914dd7ed934d8e0d363a648e9038eb7c1ec03fa04dbcaa40f7721c618c3ef947afef7a16b4d7ac8c12aa46637f03 +ab2a104fed3c83d16f2cda06878fa5f30c8c9411de71bfb67fd2fc9aa454dcbcf3d299d72f8cc12e919466a50fcf7426 +a4471d111db4418f56915689482f6144efc4664cfb0311727f36c864648d35734351becc48875df96f4abd3cfcf820f9 +83be11727cd30ea94ccc8fa31b09b81c9d6a9a5d3a4686af9da99587332fe78c1f94282f9755854bafd6033549afec91 +88020ff971dc1a01a9e993cd50a5d2131ffdcbb990c1a6aaa54b20d8f23f9546a70918ea57a21530dcc440c1509c24ad +ae24547623465e87905eaffa1fa5d52bb7c453a8dbd89614fa8819a2abcedaf455c2345099b7324ae36eb0ad7c8ef977 +b59b0c60997de1ee00b7c388bc7101d136c9803bf5437b1d589ba57c213f4f835a3e4125b54738e78abbc21b000f2016 +a584c434dfe194546526691b68fa968c831c31da42303a1d735d960901c74011d522246f37f299555416b8cf25c5a548 +80408ce3724f4837d4d52376d255e10f69eb8558399ae5ca6c11b78b98fe67d4b93157d2b9b639f1b5b64198bfe87713 +abb941e8d406c2606e0ddc35c113604fdd9d249eacc51cb64e2991e551b8639ce44d288cc92afa7a1e7fc599cfc84b22 +b223173f560cacb1c21dba0f1713839e348ad02cbfdef0626748604c86f89e0f4c919ed40b583343795bdd519ba952c8 +af1c70512ec3a19d98b8a1fc3ff7f7f5048a27d17d438d43f561974bbdd116fcd5d5c21040f3447af3f0266848d47a15 +8a44809568ebe50405bede19b4d2607199159b26a1b33e03d180e6840c5cf59d991a4fb150d111443235d75ecad085b7 +b06207cdca46b125a27b3221b5b50cf27af4c527dd7c80e2dbcebbb09778a96df3af67e50f07725239ce3583dad60660 +993352d9278814ec89b26a11c4a7c4941bf8f0e6781ae79559d14749ee5def672259792db4587f85f0100c7bb812f933 +9180b8a718b971fd27bc82c8582d19c4b4f012453e8c0ffeeeffe745581fc6c07875ab28be3af3fa3896d19f0c89ac5b +8b8e1263eb48d0fe304032dd5ea1f30e73f0121265f7458ba9054d3626894e8a5fef665340abd2ede9653045c2665938 +99a2beee4a10b7941c24b2092192faf52b819afd033e4a2de050fd6c7f56d364d0cf5f99764c3357cf32399e60fc5d74 +946a4aad7f8647ea60bee2c5fcdeb6f9a58fb2cfca70c4d10e458027a04846e13798c66506151be3df9454b1e417893f +a672a88847652d260b5472d6908d1d57e200f1e492d30dd1cecc441cdfc9b76e016d9bab560efd4d7f3c30801de884a9 +9414e1959c156cde1eb24e628395744db75fc24b9df4595350aaad0bc38e0246c9b4148f6443ef68b8e253a4a6bcf11c +9316e9e4ec5fab4f80d6540df0e3a4774db52f1d759d2e5b5bcd3d7b53597bb007eb1887cb7dc61f62497d51ffc8d996 +902d6d77bb49492c7a00bc4b70277bc28c8bf9888f4307bb017ac75a962decdedf3a4e2cf6c1ea9f9ba551f4610cbbd7 +b07025a18b0e32dd5e12ec6a85781aa3554329ea12c4cd0d3b2c22e43d777ef6f89876dd90a9c8fb097ddf61cf18adc5 +b355a849ad3227caa4476759137e813505ec523cbc2d4105bc7148a4630f9e81918d110479a2d5f5e4cd9ccec9d9d3e3 +b49532cfdf02ee760109881ad030b89c48ee3bb7f219ccafc13c93aead754d29bdafe345be54c482e9d5672bd4505080 +9477802410e263e4f938d57fa8f2a6cac7754c5d38505b73ee35ea3f057aad958cb9722ba6b7b3cfc4524e9ca93f9cdc +9148ea83b4436339580f3dbc9ba51509e9ab13c03063587a57e125432dd0915f5d2a8f456a68f8fff57d5f08c8f34d6e +b00b6b5392b1930b54352c02b1b3b4f6186d20bf21698689bbfc7d13e86538a4397b90e9d5c93fd2054640c4dbe52a4f +926a9702500441243cd446e7cbf15dde16400259726794694b1d9a40263a9fc9e12f7bcbf12a27cb9aaba9e2d5848ddc +a0c6155f42686cbe7684a1dc327100962e13bafcf3db97971fc116d9f5c0c8355377e3d70979cdbd58fd3ea52440901c +a277f899f99edb8791889d0817ea6a96c24a61acfda3ad8c3379e7c62b9d4facc4b965020b588651672fd261a77f1bfc +8f528cebb866b501f91afa50e995234bef5bf20bff13005de99cb51eaac7b4f0bf38580cfd0470de40f577ead5d9ba0f +963fc03a44e9d502cc1d23250efef44d299befd03b898d07ce63ca607bb474b5cf7c965a7b9b0f32198b04a8393821f7 +ab087438d0a51078c378bf4a93bd48ef933ff0f1fa68d02d4460820df564e6642a663b5e50a5fe509527d55cb510ae04 +b0592e1f2c54746bb076be0fa480e1c4bebc4225e1236bcda3b299aa3853e3afb401233bdbcfc4a007b0523a720fbf62 +851613517966de76c1c55a94dc4595f299398a9808f2d2f0a84330ba657ab1f357701d0895f658c18a44cb00547f6f57 +a2fe9a1dd251e72b0fe4db27be508bb55208f8f1616b13d8be288363ec722826b1a1fd729fc561c3369bf13950bf1fd6 +b896cb2bc2d0c77739853bc59b0f89b2e008ba1f701c9cbe3bef035f499e1baee8f0ff1e794854a48c320586a2dfc81a +a1b60f98e5e5106785a9b81a85423452ee9ef980fa7fa8464f4366e73f89c50435a0c37b2906052b8e58e212ebd366cf +a853b0ebd9609656636df2e6acd5d8839c0fda56f7bf9288a943b06f0b67901a32b95e016ca8bc99bd7b5eab31347e72 +b290fa4c1346963bd5225235e6bdf7c542174dab4c908ab483d1745b9b3a6015525e398e1761c90e4b49968d05e30eea +b0f65a33ad18f154f1351f07879a183ad62e5144ad9f3241c2d06533dad09cbb2253949daff1bb02d24d16a3569f7ef0 +a00db59b8d4218faf5aeafcd39231027324408f208ec1f54d55a1c41228b463b88304d909d16b718cfc784213917b71e +b8d695dd33dc2c3bc73d98248c535b2770ad7fa31aa726f0aa4b3299efb0295ba9b4a51c71d314a4a1bd5872307534d1 +b848057cca2ca837ee49c42b88422303e58ea7d2fc76535260eb5bd609255e430514e927cc188324faa8e657396d63ec +92677836061364685c2aaf0313fa32322746074ed5666fd5f142a7e8f87135f45cd10e78a17557a4067a51dfde890371 +a854b22c9056a3a24ab164a53e5c5cf388616c33e67d8ebb4590cb16b2e7d88b54b1393c93760d154208b5ca822dc68f +86fff174920388bfab841118fb076b2b0cdec3fdb6c3d9a476262f82689fb0ed3f1897f7be9dbf0932bb14d346815c63 +99661cf4c94a74e182752bcc4b98a8c2218a8f2765642025048e12e88ba776f14f7be73a2d79bd21a61def757f47f904 +8a8893144d771dca28760cba0f950a5d634195fd401ec8cf1145146286caffb0b1a6ba0c4c1828d0a5480ce49073c64c +938a59ae761359ee2688571e7b7d54692848eb5dde57ffc572b473001ea199786886f8c6346a226209484afb61d2e526 +923f68a6aa6616714cf077cf548aeb845bfdd78f2f6851d8148cba9e33a374017f2f3da186c39b82d14785a093313222 +ac923a93d7da7013e73ce8b4a2b14b8fd0cc93dc29d5de941a70285bdd19be4740fedfe0c56b046689252a3696e9c5bc +b49b32c76d4ec1a2c68d4989285a920a805993bc6fcce6dacd3d2ddae73373050a5c44ba8422a3781050682fa0ef6ba2 +8a367941c07c3bdca5712524a1411bad7945c7c48ffc7103b1d4dff2c25751b0624219d1ccde8c3f70c465f954be5445 +b838f029df455efb6c530d0e370bbbf7d87d61a9aea3d2fe5474c5fe0a39cf235ceecf9693c5c6c5820b1ba8f820bd31 +a8983b7c715eaac7f13a001d2abc462dfc1559dab4a6b554119c271aa8fe00ffcf6b6949a1121f324d6d26cb877bcbae +a2afb24ad95a6f14a6796315fbe0d8d7700d08f0cfaf7a2abe841f5f18d4fecf094406cbd54da7232a159f9c5b6e805e +87e8e95ad2d62f947b2766ff405a23f7a8afba14e7f718a691d95369c79955cdebe24c54662553c60a3f55e6322c0f6f +87c2cbcecb754e0cc96128e707e5c5005c9de07ffd899efa3437cadc23362f5a1d3fcdd30a1f5bdc72af3fb594398c2a +91afd6ee04f0496dc633db88b9370d41c428b04fd991002502da2e9a0ef051bcd7b760e860829a44fbe5539fa65f8525 +8c50e5d1a24515a9dd624fe08b12223a75ca55196f769f24748686315329b337efadca1c63f88bee0ac292dd0a587440 +8a07e8f912a38d94309f317c32068e87f68f51bdfa082d96026f5f5f8a2211621f8a3856dda8069386bf15fb2d28c18f +94ad1dbe341c44eeaf4dc133eed47d8dbfe752575e836c075745770a6679ff1f0e7883b6aa917462993a7f469d74cab5 +8745f8bd86c2bb30efa7efb7725489f2654f3e1ac4ea95bd7ad0f3cfa223055d06c187a16192d9d7bdaea7b050c6a324 +900d149c8d79418cda5955974c450a70845e02e5a4ecbcc584a3ca64d237df73987c303e3eeb79da1af83bf62d9e579f +8f652ab565f677fb1a7ba03b08004e3cda06b86c6f1b0b9ab932e0834acf1370abb2914c15b0d08327b5504e5990681c +9103097d088be1f75ab9d3da879106c2f597e2cc91ec31e73430647bdd5c33bcfd771530d5521e7e14df6acda44f38a6 +b0fec7791cfb0f96e60601e1aeced9a92446b61fedab832539d1d1037558612d78419efa87ff5f6b7aab8fd697d4d9de +b9d2945bdb188b98958854ba287eb0480ef614199c4235ce5f15fc670b8c5ffe8eeb120c09c53ea8a543a022e6a321ac +a9461bb7d5490973ebaa51afc0bb4a5e42acdccb80e2f939e88b77ac28a98870e103e1042899750f8667a8cc9123bae9 +a37fdf11d4bcb2aed74b9f460a30aa34afea93386fa4cdb690f0a71bc58f0b8df60bec56e7a24f225978b862626fa00e +a214420e183e03d531cf91661466ea2187d84b6e814b8b20b3730a9400a7d25cf23181bb85589ebc982cec414f5c2923 +ad09a45a698a6beb3e0915f540ef16e9af7087f53328972532d6b5dfe98ce4020555ece65c6cbad8bd6be8a4dfefe6fd +ab6742800b02728c92d806976764cb027413d6f86edd08ad8bb5922a2969ee9836878cd39db70db0bd9a2646862acc4f +974ca9305bd5ea1dc1755dff3b63e8bfe9f744321046c1395659bcea2a987b528e64d5aa96ac7b015650b2253b37888d +84eee9d6bce039c52c2ebc4fccc0ad70e20c82f47c558098da4be2f386a493cbc76adc795b5488c8d11b6518c2c4fab8 +875d7bda46efcb63944e1ccf760a20144df3b00d53282b781e95f12bfc8f8316dfe6492c2efbf796f1150e36e436e9df +b68a2208e0c587b5c31b5f6cb32d3e6058a9642e2d9855da4f85566e1412db528475892060bb932c55b3a80877ad7b4a +ba006368ecab5febb6ab348644d9b63de202293085ed468df8bc24d992ae8ce468470aa37f36a73630c789fb9c819b30 +90a196035150846cd2b482c7b17027471372a8ce7d914c4d82b6ea7fa705d8ed5817bd42d63886242585baf7d1397a1c +a223b4c85e0daa8434b015fd9170b5561fe676664b67064974a1e9325066ecf88fc81f97ab5011c59fad28cedd04b240 +82e8ec43139cf15c6bbeed484b62e06cded8a39b5ce0389e4cbe9c9e9c02f2f0275d8d8d4e8dfec8f69a191bef220408 +81a3fc07a7b68d92c6ee4b6d28f5653ee9ec85f7e2ee1c51c075c1b130a8c5097dc661cf10c5aff1c7114b1a6a19f11a +8ed2ef8331546d98819a5dd0e6c9f8cb2630d0847671314a28f277faf68da080b53891dd75c82cbcf7788b255490785d +acecabf84a6f9bbed6b2fc2e7e4b48f02ef2f15e597538a73aea8f98addc6badda15e4695a67ecdb505c1554e8f345ec +b8f51019b2aa575f8476e03dcadf86cc8391f007e5f922c2a36b2daa63f5a503646a468990cd5c65148d323942193051 +aaa595a84b403ec65729bc1c8055a94f874bf9adddc6c507b3e1f24f79d3ad359595a672b93aab3394db4e2d4a7d8970 +895144c55fcbd0f64d7dd69e6855cfb956e02b5658eadf0f026a70703f3643037268fdd673b0d21b288578a83c6338dd +a2e92ae6d0d237d1274259a8f99d4ea4912a299816350b876fba5ebc60b714490e198a916e1c38c6e020a792496fa23c +a45795fda3b5bb0ad1d3c628f6add5b2a4473a1414c1a232e80e70d1cfffd7f8a8d9861f8df2946999d7dbb56bf60113 +b6659bf7f6f2fef61c39923e8c23b8c70e9c903028d8f62516d16755cd3fba2fe41c285aa9432dc75ab08f8a1d8a81fc +a735609a6bc5bfd85e58234fc439ff1f58f1ff1dd966c5921d8b649e21f006bf2b8642ad8a75063c159aaf6935789293 +a3c622eb387c9d15e7bda2e3e84d007cb13a6d50d655c3f2f289758e49d3b37b9a35e4535d3cc53d8efd51f407281f19 +8afe147b53ad99220f5ef9d763bfc91f9c20caecbcf823564236fb0e6ede49414c57d71eec4772c8715cc65a81af0047 +b5f0203233cf71913951e9c9c4e10d9243e3e4a1f2cb235bf3f42009120ba96e04aa414c9938ea8873b63148478927e8 +93c52493361b458d196172d7ba982a90a4f79f03aa8008edc322950de3ce6acf4c3977807a2ffa9e924047e02072b229 +b9e72b805c8ac56503f4a86c82720afbd5c73654408a22a2ac0b2e5caccdfb0e20b59807433a6233bc97ae58cf14c70a +af0475779b5cee278cca14c82da2a9f9c8ef222eb885e8c50cca2315fea420de6e04146590ed0dd5a29c0e0812964df5 +b430ccab85690db02c2d0eb610f3197884ca12bc5f23c51e282bf3a6aa7e4a79222c3d8761454caf55d6c01a327595f9 +830032937418b26ee6da9b5206f3e24dc76acd98589e37937e963a8333e5430abd6ce3dd93ef4b8997bd41440eed75d6 +8820a6d73180f3fe255199f3f175c5eb770461ad5cfdde2fb11508041ed19b8c4ce66ad6ecebf7d7e836cc2318df47ca +aef1393e7d97278e77bbf52ef6e1c1d5db721ccf75fe753cf47a881fa034ca61eaa5098ee5a344c156d2b14ff9e284ad +8a4a26c07218948c1196c45d927ef4d2c42ade5e29fe7a91eaebe34a29900072ce5194cf28d51f746f4c4c649daf4396 +84011dc150b7177abdcb715efbd8c201f9cb39c36e6069af5c50a096021768ba40cef45b659c70915af209f904ede3b6 +b1bd90675411389bb66910b21a4bbb50edce5330850c5ab0b682393950124252766fc81f5ecfc72fb7184387238c402e +8dfdcd30583b696d2c7744655f79809f451a60c9ad5bf1226dc078b19f4585d7b3ef7fa9d54e1ac09520d95cbfd20928 +b351b4dc6d98f75b8e5a48eb7c6f6e4b78451991c9ba630e5a1b9874c15ac450cd409c1a024713bf2cf82dc400e025ef +a462b8bc97ac668b97b28b3ae24b9f5de60e098d7b23ecb600d2194cd35827fb79f77c3e50d358f5bd72ee83fef18fa0 +a183753265c5f7890270821880cce5f9b2965b115ba783c6dba9769536f57a04465d7da5049c7cf8b3fcf48146173c18 +a8a771b81ed0d09e0da4d79f990e58eabcd2be3a2680419502dd592783fe52f657fe55125b385c41d0ba3b9b9cf54a83 +a71ec577db46011689d073245e3b1c3222a9b1fe6aa5b83629adec5733dd48617ebea91346f0dd0e6cdaa86e4931b168 +a334b8b244f0d598a02da6ae0f918a7857a54dce928376c4c85df15f3b0f2ba3ac321296b8b7c9dd47d770daf16c8f8c +a29037f8ef925c417c90c4df4f9fb27fb977d04e2b3dd5e8547d33e92ab72e7a00f5461de21e28835319eae5db145eb7 +b91054108ae78b00e3298d667b913ebc44d8f26e531eae78a8fe26fdfb60271c97efb2dee5f47ef5a3c15c8228138927 +926c13efbe90604f6244be9315a34f72a1f8d1aab7572df431998949c378cddbf2fe393502c930fff614ff06ae98a0ce +995c758fd5600e6537089b1baa4fbe0376ab274ff3e82a17768b40df6f91c2e443411de9cafa1e65ea88fb8b87d504f4 +9245ba307a7a90847da75fca8d77ec03fdfc812c871e7a2529c56a0a79a6de16084258e7a9ac4ae8a3756f394336e21c +99e0cfa2bb57a7e624231317044c15e52196ecce020db567c8e8cb960354a0be9862ee0c128c60b44777e65ac315e59f +ad4f6b3d27bbbb744126601053c3dc98c07ff0eb0b38a898bd80dce778372846d67e5ab8fb34fb3ad0ef3f235d77ba7f +a0f12cae3722bbbca2e539eb9cc7614632a2aefe51410430070a12b5bc5314ecec5857b7ff8f41e9980cac23064f7c56 +b487f1bc59485848c98222fd3bc36c8c9bb3d2912e2911f4ceca32c840a7921477f9b1fe00877e05c96c75d3eecae061 +a6033db53925654e18ecb3ce715715c36165d7035db9397087ac3a0585e587998a53973d011ac6d48af439493029cee6 +a6b4d09cd01c70a3311fd131d3710ccf97bde3e7b80efd5a8c0eaeffeb48cca0f951ced905290267b115b06d46f2693b +a9dff1df0a8f4f218a98b6f818a693fb0d611fed0fc3143537cbd6578d479af13a653a8155e535548a2a0628ae24fa58 +a58e469f65d366b519f9a394cacb7edaddac214463b7b6d62c2dbc1316e11c6c5184ce45c16de2d77f990dcdd8b55430 +989e71734f8119103586dc9a3c5f5033ddc815a21018b34c1f876cdfc112efa868d5751bf6419323e4e59fa6a03ece1c +a2da00e05036c884369e04cf55f3de7d659cd5fa3f849092b2519dd263694efe0f051953d9d94b7e121f0aee8b6174d7 +968f3c029f57ee31c4e1adea89a7f92e28483af9a74f30fbdb995dc2d40e8e657dff8f8d340d4a92bf65f54440f2859f +932778df6f60ac1639c1453ef0cbd2bf67592759dcccb3e96dcc743ff01679e4c7dd0ef2b0833dda548d32cb4eba49e2 +a805a31139f8e0d6dae1ac87d454b23a3dc9fc653d4ca18d4f8ebab30fc189c16e73981c2cb7dd6f8c30454a5208109d +a9ba0991296caa2aaa4a1ceacfb205544c2a2ec97088eace1d84ee5e2767656a172f75d2f0c4e16a3640a0e0dec316e0 +b1e49055c968dced47ec95ae934cf45023836d180702e20e2df57e0f62fb85d7ac60d657ba3ae13b8560b67210449459 +a94e1da570a38809c71e37571066acabff7bf5632737c9ab6e4a32856924bf6211139ab3cedbf083850ff2d0e0c0fcfc +88ef1bb322000c5a5515b310c838c9af4c1cdbb32eab1c83ac3b2283191cd40e9573747d663763a28dad0d64adc13840 +a987ce205f923100df0fbd5a85f22c9b99b9b9cbe6ddfa8dfda1b8fe95b4f71ff01d6c5b64ca02eb24edb2b255a14ef0 +84fe8221a9e95d9178359918a108de4763ebfa7a6487facb9c963406882a08a9a93f492f8e77cf9e7ea41ae079c45993 +aa1cf3dc7c5dcfa15bbbc811a4bb6dbac4fba4f97fb1ed344ab60264d7051f6eef19ea9773441d89929ee942ed089319 +8f6a7d610d59d9f54689bbe6a41f92d9f6096cde919c1ab94c3c7fcecf0851423bc191e5612349e10f855121c0570f56 +b5af1fa7894428a53ea520f260f3dc3726da245026b6d5d240625380bfb9c7c186df0204bb604efac5e613a70af5106e +a5bce6055ff812e72ce105f147147c7d48d7a2313884dd1f488b1240ee320f13e8a33f5441953a8e7a3209f65b673ce1 +b9b55b4a1422677d95821e1d042ab81bbf0bf087496504021ec2e17e238c2ca6b44fb3b635a5c9eac0871a724b8d47c3 +941c38e533ce4a673a3830845b56786585e5fe49c427f2e5c279fc6db08530c8f91db3e6c7822ec6bb4f956940052d18 +a38e191d66c625f975313c7007bbe7431b5a06ed2da1290a7d5d0f2ec73770d476efd07b8e632de64597d47df175cbb0 +94ba76b667abf055621db4c4145d18743a368d951565632ed4e743dd50dd3333507c0c34f286a5c5fdbf38191a2255cd +a5ca38c60be5602f2bfa6e00c687ac96ac36d517145018ddbee6f12eb0faa63dd57909b9eeed26085fe5ac44e55d10ab +b00fea3b825e60c1ed1c5deb4b551aa65a340e5af36b17d5262c9cd2c508711e4dc50dc2521a2c16c7c901902266e64a +971b86fc4033485e235ccb0997a236206ba25c6859075edbcdf3c943116a5030b7f75ebca9753d863a522ba21a215a90 +b3b31f52370de246ee215400975b674f6da39b2f32514fe6bd54e747752eedca22bb840493b44a67df42a3639c5f901f +affbbfac9c1ba7cbfa1839d2ae271dd6149869b75790bf103230637da41857fc326ef3552ff31c15bda0694080198143 +a95d42aa7ef1962520845aa3688f2752d291926f7b0d73ea2ee24f0612c03b43f2b0fe3c9a9a99620ffc8d487b981bc2 +914a266065caf64985e8c5b1cb2e3f4e3fe94d7d085a1881b1fefa435afef4e1b39a98551d096a62e4f5cc1a7f0fdc2e +81a0b4a96e2b75bc1bf2dbd165d58d55cfd259000a35504d1ffb18bc346a3e6f07602c683723864ffb980f840836fd8d +91c1556631cddd4c00b65b67962b39e4a33429029d311c8acf73a18600e362304fb68bccb56fde40f49e95b7829e0b87 +8befbacc19e57f7c885d1b7a6028359eb3d80792fe13b92a8400df21ce48deb0bb60f2ddb50e3d74f39f85d7eab23adc +92f9458d674df6e990789690ec9ca73dacb67fc9255b58c417c555a8cc1208ace56e8e538f86ba0f3615573a0fbac00d +b4b1b3062512d6ae7417850c08c13f707d5838e43d48eb98dd4621baf62eee9e82348f80fe9b888a12874bfa538771f8 +a13c4a3ac642ede37d9c883f5319e748d2b938f708c9d779714108a449b343f7b71a6e3ef4080fee125b416762920273 +af44983d5fc8cceee0551ef934e6e653f2d3efa385e5c8a27a272463a6f333e290378cc307c2b664eb923c78994e706e +a389fd6c59fe2b4031cc244e22d3991e541bd203dd5b5e73a6159e72df1ab41d49994961500dcde7989e945213184778 +8d2141e4a17836c548de9598d7b298b03f0e6c73b7364979a411c464e0628e21cff6ac3d6decdba5d1c4909eff479761 +980b22ef53b7bdf188a3f14bc51b0dbfdf9c758826daa3cbc1e3986022406a8aa9a6a79e400567120b88c67faa35ce5f +a28882f0a055f96df3711de5d0aa69473e71245f4f3e9aa944e9d1fb166e02caa50832e46da6d3a03b4801735fd01b29 +8db106a37d7b88f5d995c126abb563934dd8de516af48e85695d02b1aea07f79217e3cdd03c6f5ca57421830186c772b +b5a7e50da0559a675c472f7dfaee456caab6695ab7870541b2be8c2b118c63752427184aad81f0e1afc61aef1f28c46f +9962118780e20fe291d10b64f28d09442a8e1b5cffd0f3dd68d980d0614050a626c616b44e9807fbee7accecae00686a +b38ddf33745e8d2ad6a991aefaf656a33c5f8cbe5d5b6b6fd03bd962153d8fd0e01b5f8f96d80ae53ab28d593ab1d4e7 +857dc12c0544ff2c0c703761d901aba636415dee45618aba2e3454ff9cbc634a85c8b05565e88520ff9be2d097c8b2b1 +a80d465c3f8cc63af6d74a6a5086b626c1cb4a8c0fee425964c3bd203d9d7094e299f81ce96d58afc20c8c9a029d9dae +89e1c8fbde8563763be483123a3ed702efac189c6d8ab4d16c85e74bbaf856048cc42d5d6e138633a38572ba5ec3f594 +893a594cf495535f6d216508f8d03c317dcf03446668cba688da90f52d0111ac83d76ad09bf5ea47056846585ee5c791 +aadbd8be0ae452f7f9450c7d2957598a20cbf10139a4023a78b4438172d62b18b0de39754dd2f8862dbd50a3a0815e53 +ae7d39670ecca3eb6db2095da2517a581b0e8853bdfef619b1fad9aacd443e7e6a40f18209fadd44038a55085c5fe8b2 +866ef241520eacb6331593cfcb206f7409d2f33d04542e6e52cba5447934e02d44c471f6c9a45963f9307e9809ab91d9 +b1a09911ad3864678f7be79a9c3c3eb5c84a0a45f8dcb52c67148f43439aeaaa9fd3ed3471276b7e588b49d6ebe3033a +add07b7f0dbb34049cd8feeb3c18da5944bf706871cfd9f14ff72f6c59ad217ebb1f0258b13b167851929387e4e34cfe +ae048892d5c328eefbdd4fba67d95901e3c14d974bfc0a1fc68155ca9f0d59e61d7ba17c6c9948b120cf35fd26e6fee9 +9185b4f3b7da0ddb4e0d0f09b8a9e0d6943a4611e43f13c3e2a767ed8592d31e0ba3ebe1914026a3627680274291f6e5 +a9c022d4e37b0802284ce3b7ee9258628ab4044f0db4de53d1c3efba9de19d15d65cc5e608dbe149c21c2af47d0b07b5 +b24dbd5852f8f24921a4e27013b6c3fa8885b973266cb839b9c388efad95821d5d746348179dcc07542bd0d0aefad1ce +b5fb4f279300876a539a27a441348764908bc0051ebd66dc51739807305e73db3d2f6f0f294ffb91b508ab150eaf8527 +ace50841e718265b290c3483ed4b0fdd1175338c5f1f7530ae9a0e75d5f80216f4de37536adcbc8d8c95982e88808cd0 +b19cadcde0f63bd1a9c24bd9c2806f53c14c0b9735bf351601498408ba503ddbd2037c891041cbba47f58b8c483f3b21 +b6061e63558d312eb891b97b39aa552fa218568d79ee26fe6dd5b864aea9e3216d8f2e2f3b093503be274766dac41426 +89730fdb2876ab6f0fe780d695f6e12090259027e789b819956d786e977518057e5d1d7f5ab24a3ae3d5d4c97773bd2b +b6fa841e81f9f2cad0163a02a63ae96dc341f7ae803b616efc6e1da2fbea551c1b96b11ad02c4afbdf6d0cc9f23da172 +8fb66187182629c861ddb6896d7ed3caf2ad050c3dba8ab8eb0d7a2c924c3d44c48d1a148f9e33fb1f061b86972f8d21 +86022ac339c1f84a7fa9e05358c1a5b316b4fc0b83dbe9c8c7225dc514f709d66490b539359b084ce776e301024345fa +b50b9c321468da950f01480bb62b6edafd42f83c0001d6e97f2bd523a1c49a0e8574fb66380ea28d23a7c4d54784f9f0 +a31c05f7032f30d1dac06678be64d0250a071fd655e557400e4a7f4c152be4d5c7aa32529baf3e5be7c4bd49820054f6 +b95ac0848cd322684772119f5b682d90a66bbf9dac411d9d86d2c34844bbd944dbaf8e47aa41380455abd51687931a78 +ae4a6a5ce9553b65a05f7935e61e496a4a0f6fd8203367a2c627394c9ce1e280750297b74cdc48fd1d9a31e93f97bef4 +a22daf35f6e9b05e52e0b07f7bd1dbbebd2c263033fb0e1b2c804e2d964e2f11bc0ece6aca6af079dd3a9939c9c80674 +902150e0cb1f16b9b59690db35281e28998ce275acb313900da8b2d8dfd29fa1795f8ca3ff820c31d0697de29df347c1 +b17b5104a5dc665cdd7d47e476153d715eb78c6e5199303e4b5445c21a7fa7cf85fe7cfd08d7570f4e84e579b005428c +a03f49b81c15433f121680aa02d734bb9e363af2156654a62bcb5b2ba2218398ccb0ff61104ea5d7df5b16ea18623b1e +802101abd5d3c88876e75a27ffc2f9ddcce75e6b24f23dba03e5201281a7bd5cc7530b6a003be92d225093ca17d3c3bb +a4d183f63c1b4521a6b52226fc19106158fc8ea402461a5cccdaa35fee93669df6a8661f45c1750cd01308149b7bf08e +8d17c22e0c8403b69736364d460b3014775c591032604413d20a5096a94d4030d7c50b9fe3240e31d0311efcf9816a47 +947225acfcce5992eab96276f668c3cbe5f298b90a59f2bb213be9997d8850919e8f496f182689b5cbd54084a7332482 +8df6f4ed216fc8d1905e06163ba1c90d336ab991a18564b0169623eb39b84e627fa267397da15d3ed754d1f3423bff07 +83480007a88f1a36dea464c32b849a3a999316044f12281e2e1c25f07d495f9b1710b4ba0d88e9560e72433addd50bc2 +b3019d6e591cf5b33eb972e49e06c6d0a82a73a75d78d383dd6f6a4269838289e6e07c245f54fed67f5c9bb0fd5e1c5f +92e8ce05e94927a9fb02debadb99cf30a26172b2705003a2c0c47b3d8002bf1060edb0f6a5750aad827c98a656b19199 +ac2aff801448dbbfc13cca7d603fd9c69e82100d997faf11f465323b97255504f10c0c77401e4d1890339d8b224f5803 +b0453d9903d08f508ee27e577445dc098baed6cde0ac984b42e0f0efed62760bd58d5816cf1e109d204607b7b175e30c +ae68dc4ba5067e825d46d2c7c67f1009ceb49d68e8d3e4c57f4bcd299eb2de3575d42ea45e8722f8f28497a6e14a1cfe +b22486c2f5b51d72335ce819bbafb7fa25eb1c28a378a658f13f9fc79cd20083a7e573248d911231b45a5cf23b561ca7 +89d1201d1dbd6921867341471488b4d2fd0fc773ae1d4d074c78ae2eb779a59b64c00452c2a0255826fca6b3d03be2b1 +a2998977c91c7a53dc6104f5bc0a5b675e5350f835e2f0af69825db8af4aeb68435bdbcc795f3dd1f55e1dd50bc0507f +b0be4937a925b3c05056ed621910d535ccabf5ab99fd3b9335080b0e51d9607d0fd36cb5781ff340018f6acfca4a9736 +aea145a0f6e0ba9df8e52e84bb9c9de2c2dc822f70d2724029b153eb68ee9c17de7d35063dcd6a39c37c59fdd12138f7 +91cb4545d7165ee8ffbc74c874baceca11fdebbc7387908d1a25877ca3c57f2c5def424dab24148826832f1e880bede0 +b3b579cb77573f19c571ad5eeeb21f65548d7dff9d298b8d7418c11f3e8cd3727c5b467f013cb87d6861cfaceee0d2e3 +b98a1eeec2b19fecc8378c876d73645aa52fb99e4819903735b2c7a885b242787a30d1269a04bfb8573d72d9bbc5f0f0 +940c1f01ed362bd588b950c27f8cc1d52276c71bb153d47f07ec85b038c11d9a8424b7904f424423e714454d5e80d1cd +aa343a8ecf09ce11599b8cf22f7279cf80f06dbf9f6d62cb05308dbbb39c46fd0a4a1240b032665fbb488a767379b91b +87c3ac72084aca5974599d3232e11d416348719e08443acaba2b328923af945031f86432e170dcdd103774ec92e988c9 +91d6486eb5e61d2b9a9e742c20ec974a47627c6096b3da56209c2b4e4757f007e793ebb63b2b246857c9839b64dc0233 +aebcd3257d295747dd6fc4ff910d839dd80c51c173ae59b8b2ec937747c2072fa85e3017f9060aa509af88dfc7529481 +b3075ba6668ca04eff19efbfa3356b92f0ab12632dcda99cf8c655f35b7928c304218e0f9799d68ef9f809a1492ff7db +93ba7468bb325639ec2abd4d55179c69fd04eaaf39fc5340709227bbaa4ad0a54ea8b480a1a3c8d44684e3be0f8d1980 +a6aef86c8c0d92839f38544d91b767c582568b391071228ff5a5a6b859c87bf4f81a7d926094a4ada1993ddbd677a920 +91dcd6d14207aa569194aa224d1e5037b999b69ade52843315ca61ba26abe9a76412c9e88259bc5cf5d7b95b97d9c3bc +b3b483d31c88f78d49bd065893bc1e3d2aa637e27dedb46d9a7d60be7660ce7a10aaaa7deead362284a52e6d14021178 +8e5730070acf8371461ef301cc4523e8e672aa0e3d945d438a0e0aa6bdf8cb9c685dcf38df429037b0c8aff3955c6f5b +b8c6d769890a8ee18dc4f9e917993315877c97549549b34785a92543cbeec96a08ae3a28d6e809c4aacd69de356c0012 +95ca86cd384eaceaa7c077c5615736ca31f36824bd6451a16142a1edc129fa42b50724aeed7c738f08d7b157f78b569e +94df609c6d71e8eee7ab74226e371ccc77e01738fe0ef1a6424435b4570fe1e5d15797b66ed0f64eb88d4a3a37631f0e +89057b9783212add6a0690d6bb99097b182738deff2bd9e147d7fd7d6c8eacb4c219923633e6309ad993c24572289901 +83a0f9f5f265c5a0e54defa87128240235e24498f20965009fef664f505a360b6fb4020f2742565dfc7746eb185bcec0 +91170da5306128931349bc3ed50d7df0e48a68b8cc8420975170723ac79d8773e4fa13c5f14dc6e3fafcad78379050b1 +b7178484d1b55f7e56a4cc250b6b2ec6040437d96bdfddfa7b35ed27435860f3855c2eb86c636f2911b012eb83b00db8 +ac0b00c4322d1e4208e09cd977b4e54d221133ff09551f75b32b0b55d0e2be80941dda26257b0e288c162e63c7e9cf68 +9690ed9e7e53ed37ff362930e4096b878b12234c332fd19d5d064824084245952eda9f979e0098110d6963e468cf513e +b6fa547bb0bb83e5c5be0ed462a8783fba119041c136a250045c09d0d2af330c604331e7de960df976ff76d67f8000cd +814603907c21463bcf4e59cfb43066dfe1a50344ae04ef03c87c0f61b30836c3f4dea0851d6fa358c620045b7f9214c8 +9495639e3939fad2a3df00a88603a5a180f3c3a0fe4d424c35060e2043e0921788003689887b1ed5be424d9a89bb18bb +aba4c02d8d57f2c92d5bc765885849e9ff8393d6554f5e5f3e907e5bfac041193a0d8716d7861104a4295d5a03c36b03 +8ead0b56c1ca49723f94a998ba113b9058059321da72d9e395a667e6a63d5a9dac0f5717cec343f021695e8ced1f72af +b43037f7e3852c34ed918c5854cd74e9d5799eeddfe457d4f93bb494801a064735e326a76e1f5e50a339844a2f4a8ec9 +99db8422bb7302199eb0ff3c3d08821f8c32f53a600c5b6fb43e41205d96adae72be5b460773d1280ad1acb806af9be8 +8a9be08eae0086c0f020838925984df345c5512ff32e37120b644512b1d9d4fecf0fd30639ca90fc6cf334a86770d536 +81b43614f1c28aa3713a309a88a782fb2bdfc4261dd52ddc204687791a40cf5fd6a263a8179388596582cccf0162efc2 +a9f3a8b76912deb61d966c75daf5ddb868702ebec91bd4033471c8e533183df548742a81a2671de5be63a502d827437d +902e2415077f063e638207dc7e14109652e42ab47caccd6204e2870115791c9defac5425fd360b37ac0f7bd8fe7011f8 +aa18e4fdc1381b59c18503ae6f6f2d6943445bd00dd7d4a2ad7e5adad7027f2263832690be30d456e6d772ad76f22350 +a348b40ba3ba7d81c5d4631f038186ebd5e5f314f1ea737259151b07c3cc8cf0c6ed4201e71bcc1c22fefda81a20cde6 +aa1306f7ac1acbfc47dc6f7a0cb6d03786cec8c8dc8060388ccda777bca24bdc634d03e53512c23dba79709ff64f8620 +818ccfe46e700567b7f3eb400e5a35f6a5e39b3db3aa8bc07f58ace35d9ae5a242faf8dbccd08d9a9175bbce15612155 +b7e3da2282b65dc8333592bb345a473f03bd6df69170055fec60222de9897184536bf22b9388b08160321144d0940279 +a4d976be0f0568f4e57de1460a1729129252b44c552a69fceec44e5b97c96c711763360d11f9e5bf6d86b4976bf40d69 +85d185f0397c24c2b875b09b6328a23b87982b84ee880f2677a22ff4c9a1ba9f0fea000bb3f7f66375a00d98ebafce17 +b4ccbb8c3a2606bd9b87ce022704663af71d418351575f3b350d294f4efc68c26f9a2ce49ff81e6ff29c3b63d746294e +93ffd3265fddb63724dfde261d1f9e22f15ecf39df28e4d89e9fea03221e8e88b5dd9b77628bacaa783c6f91802d47cc +b1fd0f8d7a01378e693da98d03a2d2fda6b099d03454b6f2b1fa6472ff6bb092751ce6290059826b74ac0361eab00e1e +a89f440c71c561641589796994dd2769616b9088766e983c873fae0716b95c386c8483ab8a4f367b6a68b72b7456dd32 +af4fe92b01d42d03dd5d1e7fa55e96d4bbcb7bf7d4c8c197acd16b3e0f3455807199f683dcd263d74547ef9c244b35cc +a8227f6e0a344dfe76bfbe7a1861be32c4f4bed587ccce09f9ce2cf481b2dda8ae4f566154bc663d15f962f2d41761bd +a7b361663f7495939ed7f518ba45ea9ff576c4e628995b7aea026480c17a71d63fc2c922319f0502eb7ef8f14a406882 +8ddcf382a9f39f75777160967c07012cfa89e67b19714a7191f0c68eaf263935e5504e1104aaabd0899348c972a8d3c6 +98c95b9f6f5c91f805fb185eedd06c6fc4457d37dd248d0be45a6a168a70031715165ea20606245cbdf8815dc0ac697f +805b44f96e001e5909834f70c09be3efcd3b43632bcac5b6b66b6d227a03a758e4b1768ce2a723045681a1d34562aaeb +b0e81b07cdc45b3dca60882676d9badb99f25c461b7efe56e3043b80100bb62d29e1873ae25eb83087273160ece72a55 +b0c53f0abe78ee86c7b78c82ae1f7c070bb0b9c45c563a8b3baa2c515d482d7507bb80771e60b38ac13f78b8af92b4a9 +a7838ef6696a9e4d2e5dfd581f6c8d6a700467e8fd4e85adabb5f7a56f514785dd4ab64f6f1b48366f7d94728359441b +88c76f7700a1d23c30366a1d8612a796da57b2500f97f88fdf2d76b045a9d24e7426a8ffa2f4e86d3046937a841dad58 +ad8964baf98c1f02e088d1d9fcb3af6b1dfa44cdfe0ed2eae684e7187c33d3a3c28c38e8f4e015f9c04d451ed6f85ff6 +90e9d00a098317ececaa9574da91fc149eda5b772dedb3e5a39636da6603aa007804fa86358550cfeff9be5a2cb7845e +a56ff4ddd73d9a6f5ab23bb77efa25977917df63571b269f6a999e1ad6681a88387fcc4ca3b26d57badf91b236503a29 +97ad839a6302c410a47e245df84c01fb9c4dfef86751af3f9340e86ff8fc3cd52fa5ff0b9a0bd1d9f453e02ca80658a6 +a4c8c44cbffa804129e123474854645107d1f0f463c45c30fd168848ebea94880f7c0c5a45183e9eb837f346270bdb35 +a72e53d0a1586d736e86427a93569f52edd2f42b01e78aee7e1961c2b63522423877ae3ac1227a2cf1e69f8e1ff15bc3 +8559f88a7ef13b4f09ac82ae458bbae6ab25671cfbf52dae7eac7280d6565dd3f0c3286aec1a56a8a16dc3b61d78ce47 +8221503f4cdbed550876c5dc118a3f2f17800c04e8be000266633c83777b039a432d576f3a36c8a01e8fd18289ebc10b +99bfbe5f3e46d4d898a578ba86ed26de7ed23914bd3bcdf3c791c0bcd49398a52419077354a5ab75cea63b6c871c6e96 +aa134416d8ff46f2acd866c1074af67566cfcf4e8be8d97329dfa0f603e1ff208488831ce5948ac8d75bfcba058ddcaa +b02609d65ebfe1fe8e52f21224a022ea4b5ea8c1bd6e7b9792eed8975fc387cdf9e3b419b8dd5bcce80703ab3a12a45f +a4f14798508698fa3852e5cac42a9db9797ecee7672a54988aa74037d334819aa7b2ac7b14efea6b81c509134a6b7ad2 +884f01afecbcb987cb3e7c489c43155c416ed41340f61ecb651d8cba884fb9274f6d9e7e4a46dd220253ae561614e44c +a05523c9e71dce1fe5307cc71bd721feb3e1a0f57a7d17c7d1c9fb080d44527b7dbaa1f817b1af1c0b4322e37bc4bb1e +8560aec176a4242b39f39433dd5a02d554248c9e49d3179530815f5031fee78ba9c71a35ceeb2b9d1f04c3617c13d8f0 +996aefd402748d8472477cae76d5a2b92e3f092fc834d5222ae50194dd884c9fb8b6ed8e5ccf8f6ed483ddbb4e80c747 +8fd09900320000cbabc40e16893e2fcf08815d288ec19345ad7b6bb22f7d78a52b6575a3ca1ca2f8bc252d2eafc928ec +939e51f73022bc5dc6862a0adf8fb8a3246b7bfb9943cbb4b27c73743926cc20f615a036c7e5b90c80840e7f1bfee0e7 +a0a6258700cadbb9e241f50766573bf9bdb7ad380b1079dc3afb4054363d838e177b869cad000314186936e40359b1f2 +972699a4131c8ed27a2d0e2104d54a65a7ff1c450ad9da3a325c662ab26869c21b0a84d0700b98c8b5f6ce3b746873d7 +a454c7fe870cb8aa6491eafbfb5f7872d6e696033f92e4991d057b59d70671f2acdabef533e229878b60c7fff8f748b1 +a167969477214201f09c79027b10221e4707662e0c0fde81a0f628249f2f8a859ce3d30a7dcc03b8ecca8f7828ad85c7 +8ff6b7265175beb8a63e1dbf18c9153fb2578c207c781282374f51b40d57a84fd2ef2ea2b9c6df4a54646788a62fd17f +a3d7ebeccde69d73d8b3e76af0da1a30884bb59729503ff0fb0c3bccf9221651b974a6e72ea33b7956fc3ae758226495 +b71ef144c9a98ce5935620cb86c1590bd4f48e5a2815d25c0cdb008fde628cf628c31450d3d4f67abbfeb16178a74cfd +b5e0a16d115134f4e2503990e3f2035ed66b9ccf767063fe6747870d97d73b10bc76ed668550cb82eedc9a2ca6f75524 +b30ffaaf94ee8cbc42aa2c413175b68afdb207dbf351fb20be3852cb7961b635c22838da97eaf43b103aff37e9e725cc +98aa7d52284f6c1f22e272fbddd8c8698cf8f5fbb702d5de96452141fafb559622815981e50b87a72c2b1190f59a7deb +81fbacda3905cfaf7780bb4850730c44166ed26a7c8d07197a5d4dcd969c09e94a0461638431476c16397dd7bdc449f9 +95e47021c1726eac2e5853f570d6225332c6e48e04c9738690d53e07c6b979283ebae31e2af1fc9c9b3e59f87e5195b1 +ac024a661ba568426bb8fce21780406537f518075c066276197300841e811860696f7588188bc01d90bace7bc73d56e3 +a4ebcaf668a888dd404988ab978594dee193dad2d0aec5cdc0ccaf4ec9a7a8228aa663db1da8ddc52ec8472178e40c32 +a20421b8eaf2199d93b083f2aff37fb662670bd18689d046ae976d1db1fedd2c2ff897985ecc6277b396db7da68bcb27 +8bc33d4b40197fd4d49d1de47489d10b90d9b346828f53a82256f3e9212b0cbc6930b895e879da9cec9fedf026aadb3e +aaafdd1bec8b757f55a0433eddc0a39f818591954fd4e982003437fcceb317423ad7ee74dbf17a2960380e7067a6b4e2 +aad34277ebaed81a6ec154d16736866f95832803af28aa5625bf0461a71d02b1faba02d9d9e002be51c8356425a56867 +976e9c8b150d08706079945bd0e84ab09a648ecc6f64ded9eb5329e57213149ae409ae93e8fbd8eda5b5c69f5212b883 +8097fae1653247d2aed4111533bc378171d6b2c6d09cbc7baa9b52f188d150d645941f46d19f7f5e27b7f073c1ebd079 +83905f93b250d3184eaba8ea7d727c4464b6bdb027e5cbe4f597d8b9dc741dcbea709630bd4fd59ce24023bec32fc0f3 +8095030b7045cff28f34271386e4752f9a9a0312f8df75de4f424366d78534be2b8e1720a19cb1f9a2d21105d790a225 +a7b7b73a6ae2ed1009c49960374b0790f93c74ee03b917642f33420498c188a169724945a975e5adec0a1e83e07fb1b2 +856a41c54df393b6660b7f6354572a4e71c8bfca9cabaffb3d4ef2632c015e7ee2bc10056f3eccb3dbed1ad17d939178 +a8f7a55cf04b38cd4e330394ee6589da3a07dc9673f74804fdf67b364e0b233f14aec42e783200a2e4666f7c5ff62490 +82c529f4e543c6bca60016dc93232c115b359eaee2798a9cf669a654b800aafe6ab4ba58ea8b9cdda2b371c8d62fa845 +8caab020c1baddce77a6794113ef1dfeafc5f5000f48e97f4351b588bf02f1f208101745463c480d37f588d5887e6d8c +8fa91b3cc400f48b77b6fd77f3b3fbfb3f10cdff408e1fd22d38f77e087b7683adad258804409ba099f1235b4b4d6fea +8aa02787663d6be9a35677d9d8188b725d5fcd770e61b11b64e3def8808ea5c71c0a9afd7f6630c48634546088fcd8e2 +b5635b7b972e195cab878b97dea62237c7f77eb57298538582a330b1082f6207a359f2923864630136d8b1f27c41b9aa +8257bb14583551a65975946980c714ecd6e5b629672bb950b9caacd886fbd22704bc9e3ba7d30778adab65dc74f0203a +ab5fe1cd12634bfa4e5c60d946e2005cbd38f1063ec9a5668994a2463c02449a0a185ef331bd86b68b6e23a8780cb3ba +a7d3487da56cda93570cc70215d438204f6a2709bfb5fda6c5df1e77e2efc80f4235c787e57fbf2c74aaff8cbb510a14 +b61cff7b4c49d010e133319fb828eb900f8a7e55114fc86b39c261a339c74f630e1a7d7e1350244ada566a0ff3d46c4b +8d4d1d55d321d278db7a85522ccceca09510374ca81d4d73e3bb5249ace7674b73900c35a531ec4fa6448fabf7ad00dc +966492248aee24f0f56c8cfca3c8ec6ba3b19abb69ae642041d4c3be8523d22c65c4dafcab4c58989ccc4e0bd2f77919 +b20c320a90cb220b86e1af651cdc1e21315cd215da69f6787e28157172f93fc8285dcd59b039c626ed8ca4633cba1a47 +aae9e6b22f018ceb5c0950210bb8182cb8cb61014b7e14581a09d36ebd1bbfebdb2b82afb7fdb0cf75e58a293d9c456d +875547fb67951ad37b02466b79f0c9b985ccbc500cfb431b17823457dc79fb9597ec42cd9f198e15523fcd88652e63a4 +92afce49773cb2e20fb21e4f86f18e0959ebb9c33361547ddb30454ee8e36b1e234019cbdca0e964cb292f7f77df6b90 +8af85343dfe1821464c76ba11c216cbef697b5afc69c4d821342e55afdac047081ec2e3f7b09fc14b518d9a23b78c003 +b7de4a1648fd63f3a918096ea669502af5357438e69dac77cb8102b6e6c15c76e033cfaa80dafc806e535ede5c1a20aa +ac80e9b545e8bd762951d96c9ce87f629d01ffcde07efc2ef7879ca011f1d0d8a745abf26c9d452541008871304fac00 +a4cf0f7ed724e481368016c38ea5816698a5f68eb21af4d3c422d2ba55f96a33e427c2aa40de1b56a7cfac7f7cf43ab0 +899b0a678bb2db2cae1b44e75a661284844ebcdd87abf308fedeb2e4dbe5c5920c07db4db7284a7af806a2382e8b111a +af0588a2a4afce2b1b13c1230816f59e8264177e774e4a341b289a101dcf6af813638fed14fb4d09cb45f35d5d032609 +a4b8df79e2be76e9f5fc5845f06fe745a724cf37c82fcdb72719b77bdebea3c0e763f37909373e3a94480cc5e875cba0 +83e42c46d88930c8f386b19fd999288f142d325e2ebc86a74907d6d77112cb0d449bc511c95422cc810574031a8cbba9 +b5e39534070de1e5f6e27efbdd3dc917d966c2a9b8cf2d893f964256e95e954330f2442027dc148c776d63a95bcde955 +958607569dc28c075e658cd4ae3927055c6bc456eef6212a6fea8205e48ed8777a8064f584cda38fe5639c371e2e7fba +812adf409fa63575113662966f5078a903212ffb65c9b0bbe62da0f13a133443a7062cb8fd70f5e5dd5559a32c26d2c8 +a679f673e5ce6a3cce7fa31f22ee3785e96bcb55e5a776e2dd3467bef7440e3555d1a9b87cb215e86ee9ed13a090344b +afedbb34508b159eb25eb2248d7fe328f86ef8c7d84c62d5b5607d74aae27cc2cc45ee148eb22153b09898a835c58df4 +b75505d4f6b67d31e665cfaf5e4acdb5838ae069166b7fbcd48937c0608a59e40a25302fcc1873d2e81c1782808c70f0 +b62515d539ec21a155d94fc00ea3c6b7e5f6636937bce18ed5b618c12257fb82571886287fd5d1da495296c663ebc512 +ab8e1a9446bbdd588d1690243b1549d230e6149c28f59662b66a8391a138d37ab594df38e7720fae53217e5c3573b5be +b31e8abf4212e03c3287bb2c0a153065a7290a16764a0bac8f112a72e632185a654bb4e88fdd6053e6c7515d9719fadb +b55165477fe15b6abd2d0f4fddaa9c411710dcc4dd712daba3d30e303c9a3ee5415c256f9dc917ecf18c725b4dbab059 +a0939d4f57cacaae549b78e87cc234de4ff6a35dc0d9cd5d7410abc30ebcd34c135e008651c756e5a9d2ca79c40ef42b +8cf10e50769f3443340844aad4d56ec790850fed5a41fcbd739abac4c3015f0a085a038fbe7fae9f5ad899cce5069f6b +924055e804d82a99ea4bb160041ea4dc14b568abf379010bc1922fde5d664718c31d103b8b807e3a1ae809390e708c73 +8ec0f9d26f71b0f2e60a179e4fd1778452e2ffb129d50815e5d7c7cb9415fa69ae5890578086e8ef6bfde35ad2a74661 +98c7f12b15ec4426b59f737f73bf5faea4572340f4550b7590dfb7f7ffedb2372e3e555977c63946d579544c53210ad0 +8a935f7a955c78f69d66f18eee0092e5e833fa621781c9581058e219af4d7ceee48b84e472e159dda6199715fb2f9acf +b78d4219f95a2dbfaa7d0c8a610c57c358754f4f43c2af312ab0fe8f10a5f0177e475332fb8fd23604e474fc2abeb051 +8d086a14803392b7318c28f1039a17e3cfdcece8abcaca3657ec3d0ac330842098a85c0212f889fabb296dfb133ce9aa +a53249f417aac82f2c2a50c244ce21d3e08a5e5a8bd33bec2a5ab0d6cd17793e34a17edfa3690899244ce201e2fb9986 +8619b0264f9182867a1425be514dc4f1ababc1093138a728a28bd7e4ecc99b9faaff68c23792264bc6e4dce5f52a5c52 +8c171edbbbde551ec19e31b2091eb6956107dd9b1f853e1df23bff3c10a3469ac77a58335eee2b79112502e8e163f3de +a9d19ec40f0ca07c238e9337c6d6a319190bdba2db76fb63902f3fb459aeeb50a1ac30db5b25ee1b4201f3ca7164a7f4 +b9c6ec14b1581a03520b8d2c1fbbc31fb8ceaef2c0f1a0d0080b6b96e18442f1734bea7ef7b635d787c691de4765d469 +8cb437beb4cfa013096f40ccc169a713dc17afee6daa229a398e45fd5c0645a9ad2795c3f0cd439531a7151945d7064d +a6e8740cc509126e146775157c2eb278003e5bb6c48465c160ed27888ca803fa12eee1f6a8dd7f444f571664ed87fdc1 +b75c1fecc85b2732e96b3f23aefb491dbd0206a21d682aee0225838dc057d7ed3b576176353e8e90ae55663f79e986e4 +ad8d249b0aea9597b08358bce6c77c1fd552ef3fbc197d6a1cfe44e5e6f89b628b12a6fb04d5dcfcbacc51f46e4ae7bb +b998b2269932cbd58d04b8e898d373ac4bb1a62e8567484f4f83e224061bc0f212459f1daae95abdbc63816ae6486a55 +827988ef6c1101cddc96b98f4a30365ff08eea2471dd949d2c0a9b35c3bbfa8c07054ad1f4c88c8fbf829b20bb5a9a4f +8692e638dd60babf7d9f2f2d2ce58e0ac689e1326d88311416357298c6a2bffbfebf55d5253563e7b3fbbf5072264146 +a685d75b91aea04dbc14ab3c1b1588e6de96dae414c8e37b8388766029631b28dd860688079b12d09cd27f2c5af11adf +b57eced93eec3371c56679c259b34ac0992286be4f4ff9489d81cf9712403509932e47404ddd86f89d7c1c3b6391b28c +a1c8b4e42ebcbd8927669a97f1b72e236fb19249325659e72be7ddaaa1d9e81ca2abb643295d41a8c04a2c01f9c0efd7 +877c33de20d4ed31674a671ba3e8f01a316581e32503136a70c9c15bf0b7cb7b1cba6cd4eb641fad165fb3c3c6c235fd +a2a469d84ec478da40838f775d11ad38f6596eb41caa139cc190d6a10b5108c09febae34ffdafac92271d2e73c143693 +972f817caedb254055d52e963ed28c206848b6c4cfdb69dbc961c891f8458eaf582a6d4403ce1177d87bc2ea410ef60a +accbd739e138007422f28536381decc54bb6bd71d93edf3890e54f9ef339f83d2821697d1a4ac1f5a98175f9a9ecb9b5 +8940f8772e05389f823b62b3adc3ed541f91647f0318d7a0d3f293aeeb421013de0d0a3664ea53dd24e5fbe02d7efef6 +8ecce20f3ef6212edef07ec4d6183fda8e0e8cad2c6ccd0b325e75c425ee1faba00b5c26b4d95204238931598d78f49d +97cc72c36335bd008afbed34a3b0c7225933faba87f7916d0a6d2161e6f82e0cdcda7959573a366f638ca75d30e9dab1 +9105f5de8699b5bdb6bd3bb6cc1992d1eac23929c29837985f83b22efdda92af64d9c574aa9640475087201bbbe5fd73 +8ffb33c4f6d05c413b9647eb6933526a350ed2e4278ca2ecc06b0e8026d8dbe829c476a40e45a6df63a633090a3f82ef +8bfc6421fdc9c2d2aaa68d2a69b1a2728c25b84944cc3e6a57ff0c94bfd210d1cbf4ff3f06702d2a8257024d8be7de63 +a80e1dc1dddfb41a70220939b96dc6935e00b32fb8be5dff4eed1f1c650002ff95e4af481c43292e3827363b7ec4768a +96f714ebd54617198bd636ba7f7a7f8995a61db20962f2165078d9ed8ee764d5946ef3cbdc7ebf8435bb8d5dd4c1deac +8cdb0890e33144d66391d2ae73f5c71f5a861f72bc93bff6cc399fc25dd1f9e17d8772592b44593429718784802ac377 +8ccf9a7f80800ee770b92add734ed45a73ecc31e2af0e04364eefc6056a8223834c7c0dc9dfc52495bdec6e74ce69994 +aa0875f423bd68b5f10ba978ddb79d3b96ec093bfbac9ff366323193e339ed7c4578760fb60f60e93598bdf1e5cc4995 +a9214f523957b59c7a4cb61a40251ad72aba0b57573163b0dc0f33e41d2df483fb9a1b85a5e7c080e9376c866790f8cb +b6224b605028c6673a536cc8ff9aeb94e7a22e686fda82cf16068d326469172f511219b68b2b3affb7933af0c1f80d07 +b6d58968d8a017c6a34e24c2c09852f736515a2c50f37232ac6b43a38f8faa7572cc31dade543b594b61b5761c4781d0 +8a97cefe5120020c38deeb861d394404e6c993c6cbd5989b6c9ebffe24f46ad11b4ba6348e2991cbf3949c28cfc3c99d +95bf046f8c3a9c0ce2634be4de3713024daec3fc4083e808903b25ce3ac971145af90686b451efcc72f6b22df0216667 +a6a4e2f71b8fa28801f553231eff2794c0f10d12e7e414276995e21195abc9c2983a8997e41af41e78d19ff6fbb2680b +8e5e62a7ca9c2f58ebaab63db2ff1fb1ff0877ae94b7f5e2897f273f684ae639dff44cc65718f78a9c894787602ab26a +8542784383eec4f565fcb8b9fc2ad8d7a644267d8d7612a0f476fc8df3aff458897a38003d506d24142ad18f93554f2b +b7db68ba4616ea072b37925ec4fb39096358c2832cc6d35169e032326b2d6614479f765ae98913c267105b84afcb9bf2 +8b31dbb9457d23d416c47542c786e07a489af35c4a87dadb8ee91bea5ac4a5315e65625d78dad2cf8f9561af31b45390 +a8545a1d91ac17257732033d89e6b7111db8242e9c6ebb0213a88906d5ef407a2c6fdb444e29504b06368b6efb4f4839 +b1bd85d29ebb28ccfb05779aad8674906b267c2bf8cdb1f9a0591dd621b53a4ee9f2942687ee3476740c0b4a7621a3ae +a2b54534e152e46c50d91fff03ae9cd019ff7cd9f4168b2fe7ac08ef8c3bbc134cadd3f9d6bd33d20ae476c2a8596c8a +b19b571ff4ae3e9f5d95acda133c455e72c9ea9973cae360732859836c0341c4c29ab039224dc5bc3deb824e031675d8 +940b5f80478648bac025a30f3efeb47023ce20ee98be833948a248bca6979f206bb28fc0f17b90acf3bb4abd3d14d731 +8f106b40588586ac11629b96d57808ad2808915d89539409c97414aded90b4ff23286a692608230a52bff696055ba5d6 +ae6bda03aa10da3d2abbc66d764ca6c8d0993e7304a1bdd413eb9622f3ca1913baa6da1e9f4f9e6cf847f14f44d6924d +a18e7796054a340ef826c4d6b5a117b80927afaf2ebd547794c400204ae2caf277692e2eabb55bc2f620763c9e9da66d +8d2d25180dc2c65a4844d3e66819ccfcf48858f0cc89e1c77553b463ec0f7feb9a4002ce26bc618d1142549b9850f232 +863f413a394de42cc8166c1c75d513b91d545fff1de6b359037a742c70b008d34bf8e587afa2d62c844d0c6f0ea753e7 +83cd0cf62d63475e7fcad18a2e74108499cdbf28af2113cfe005e3b5887794422da450b1944d0a986eb7e1f4c3b18f25 +b4f8b350a6d88fea5ab2e44715a292efb12eb52df738c9b2393da3f1ddee68d0a75b476733ccf93642154bceb208f2b8 +b3f52aaa4cd4221cb9fc45936cc67fd3864bf6d26bf3dd86aa85aa55ecfc05f5e392ecce5e7cf9406b4b1c4fce0398c8 +b33137084422fb643123f40a6df2b498065e65230fc65dc31791c330e898c51c3a65ff738930f32c63d78f3c9315f85b +91452bfa75019363976bb7337fe3a73f1c10f01637428c135536b0cdc7da5ce558dae3dfc792aa55022292600814a8ef +ad6ba94c787cd4361ca642c20793ea44f1f127d4de0bb4a77c7fbfebae0fcadbf28e2cb6f0c12c12a07324ec8c19761d +890aa6248b17f1501b0f869c556be7bf2b1d31a176f9978bb97ab7a6bd4138eed32467951c5ef1871944b7f620542f43 +82111db2052194ee7dd22ff1eafffac0443cf969d3762cceae046c9a11561c0fdce9c0711f88ac01d1bed165f8a7cee3 +b1527b71df2b42b55832f72e772a466e0fa05743aacc7814f4414e4bcc8d42a4010c9e0fd940e6f254cafedff3cd6543 +922370fa49903679fc565f09c16a5917f8125e72acfeb060fcdbadbd1644eb9f4016229756019c93c6d609cda5d5d174 +aa4c7d98a96cab138d2a53d4aee8ebff6ef903e3b629a92519608d88b3bbd94de5522291a1097e6acf830270e64c8ee1 +b3dc21608a389a72d3a752883a382baaafc61ecc44083b832610a237f6a2363f24195acce529eb4aed4ef0e27a12b66e +94619f5de05e07b32291e1d7ab1d8b7337a2235e49d4fb5f3055f090a65e932e829efa95db886b32b153bdd05a53ec8c +ade1e92722c2ffa85865d2426fb3d1654a16477d3abf580cfc45ea4b92d5668afc9d09275d3b79283e13e6b39e47424d +b7201589de7bed094911dd62fcd25c459a8e327ac447b69f541cdba30233063e5ddffad0b67e9c3e34adcffedfd0e13d +809d325310f862d6549e7cb40f7e5fc9b7544bd751dd28c4f363c724a0378c0e2adcb5e42ec8f912f5f49f18f3365c07 +a79c20aa533de7a5d671c99eb9eb454803ba54dd4f2efa3c8fec1a38f8308e9905c71e9282955225f686146388506ff6 +a85eeacb5e8fc9f3ed06a3fe2dc3108ab9f8c5877b148c73cf26e4e979bf5795edbe2e63a8d452565fd1176ed40402b2 +97ef55662f8a1ec0842b22ee21391227540adf7708f491436044f3a2eb18c471525e78e1e14fa292507c99d74d7437c6 +93110d64ed5886f3d16ce83b11425576a3a7a9bb831cd0de3f9a0b0f2270a730d68136b4ef7ff035ede004358f419b5c +ac9ed0a071517f0ae4f61ce95916a90ba9a77a3f84b0ec50ef7298acdcd44d1b94525d191c39d6bd1bb68f4471428760 +98abd6a02c7690f5a339adf292b8c9368dfc12e0f8069cf26a5e0ce54b4441638f5c66ea735142f3c28e00a0024267e6 +b51efb73ba6d44146f047d69b19c0722227a7748b0e8f644d0fc9551324cf034c041a2378c56ce8b58d06038fb8a78de +8f115af274ef75c1662b588b0896b97d71f8d67986ae846792702c4742ab855952865ce236b27e2321967ce36ff93357 +b3c4548f14d58b3ab03c222da09e4381a0afe47a72d18d50a94e0008797f78e39e99990e5b4757be62310d400746e35a +a9b1883bd5f31f909b8b1b6dcb48c1c60ed20aa7374b3ffa7f5b2ed036599b5bef33289d23c80a5e6420d191723b92f7 +85d38dffd99487ae5bb41ab4a44d80a46157bbbe8ef9497e68f061721f74e4da513ccc3422936b059575975f6787c936 +adf870fcb96e972c033ab7a35d28ae79ee795f82bc49c3bd69138f0e338103118d5529c53f2d72a9c0d947bf7d312af2 +ab4c7a44e2d9446c6ff303eb49aef0e367a58b22cc3bb27b4e69b55d1d9ee639c9234148d2ee95f9ca8079b1457d5a75 +a386420b738aba2d7145eb4cba6d643d96bda3f2ca55bb11980b318d43b289d55a108f4bc23a9606fb0bccdeb3b3bb30 +847020e0a440d9c4109773ecca5d8268b44d523389993b1f5e60e541187f7c597d79ebd6e318871815e26c96b4a4dbb1 +a530aa7e5ca86fcd1bec4b072b55cc793781f38a666c2033b510a69e110eeabb54c7d8cbcb9c61fee531a6f635ffa972 +87364a5ea1d270632a44269d686b2402da737948dac27f51b7a97af80b66728b0256547a5103d2227005541ca4b7ed04 +8816fc6e16ea277de93a6d793d0eb5c15e9e93eb958c5ef30adaf8241805adeb4da8ce19c3c2167f971f61e0b361077d +8836a72d301c42510367181bb091e4be377777aed57b73c29ef2ce1d475feedd7e0f31676284d9a94f6db01cc4de81a2 +b0d9d8b7116156d9dde138d28aa05a33e61f8a85839c1e9071ccd517b46a5b4b53acb32c2edd7150c15bc1b4bd8db9e3 +ae931b6eaeda790ba7f1cd674e53dc87f6306ff44951fa0df88d506316a5da240df9794ccbd7215a6470e6b31c5ea193 +8c6d5bdf87bd7f645419d7c6444e244fe054d437ed1ba0c122fde7800603a5fadc061e5b836cb22a6cfb2b466f20f013 +90d530c6d0cb654999fa771b8d11d723f54b8a8233d1052dc1e839ea6e314fbed3697084601f3e9bbb71d2b4eaa596df +b0d341a1422588c983f767b1ed36c18b141774f67ef6a43cff8e18b73a009da10fc12120938b8bba27f225bdfd3138f9 +a131b56f9537f460d304e9a1dd75702ace8abd68cb45419695cb8dee76998139058336c87b7afd6239dc20d7f8f940cc +aa6c51fa28975f709329adee1bbd35d49c6b878041841a94465e8218338e4371f5cb6c17f44a63ac93644bf28f15d20f +88440fb584a99ebd7f9ea04aaf622f6e44e2b43bbb49fb5de548d24a238dc8f26c8da2ccf03dd43102bda9f16623f609 +9777b8695b790e702159a4a750d5e7ff865425b95fa0a3c15495af385b91c90c00a6bd01d1b77bffe8c47d01baae846f +8b9d764ece7799079e63c7f01690c8eff00896a26a0d095773dea7a35967a8c40db7a6a74692f0118bf0460c26739af4 +85808c65c485520609c9e61fa1bb67b28f4611d3608a9f7a5030ee61c3aa3c7e7dc17fff48af76b4aecee2cb0dbd22ac +ad2783a76f5b3db008ef5f7e67391fda4e7e36abde6b3b089fc4835b5c339370287935af6bd53998bed4e399eda1136d +96f18ec03ae47c205cc4242ca58e2eff185c9dca86d5158817e2e5dc2207ab84aadda78725f8dc080a231efdc093b940 +97de1ab6c6cc646ae60cf7b86df73b9cf56cc0cd1f31b966951ebf79fc153531af55ca643b20b773daa7cab784b832f7 +870ba266a9bfa86ef644b1ef025a0f1b7609a60de170fe9508de8fd53170c0b48adb37f19397ee8019b041ce29a16576 +ad990e888d279ac4e8db90619d663d5ae027f994a3992c2fbc7d262b5990ae8a243e19157f3565671d1cb0de17fe6e55 +8d9d5adcdd94c5ba3be4d9a7428133b42e485f040a28d16ee2384758e87d35528f7f9868de9bd23d1a42a594ce50a567 +85a33ed75d514ece6ad78440e42f7fcdb59b6f4cff821188236d20edae9050b3a042ce9bc7d2054296e133d033e45022 +92afd2f49a124aaba90de59be85ff269457f982b54c91b06650c1b8055f9b4b0640fd378df02a00e4fc91f7d226ab980 +8c0ee09ec64bd831e544785e3d65418fe83ed9c920d9bb4d0bf6dd162c1264eb9d6652d2def0722e223915615931581c +8369bedfa17b24e9ad48ebd9c5afea4b66b3296d5770e09b00446c5b0a8a373d39d300780c01dcc1c6752792bccf5fd0 +8b9e960782576a59b2eb2250d346030daa50bbbec114e95cdb9e4b1ba18c3d34525ae388f859708131984976ca439d94 +b682bface862008fea2b5a07812ca6a28a58fd151a1d54c708fc2f8572916e0d678a9cb8dc1c10c0470025c8a605249e +a38d5e189bea540a824b36815fc41e3750760a52be0862c4cac68214febdc1a754fb194a7415a8fb7f96f6836196d82a +b9e7fbda650f18c7eb8b40e42cc42273a7298e65e8be524292369581861075c55299ce69309710e5b843cb884de171bd +b6657e5e31b3193874a1bace08f42faccbd3c502fb73ad87d15d18a1b6c2a146f1baa929e6f517db390a5a47b66c0acf +ae15487312f84ed6265e4c28327d24a8a0f4d2d17d4a5b7c29b974139cf93223435aaebe3af918f5b4bb20911799715f +8bb4608beb06bc394e1a70739b872ce5a2a3ffc98c7547bf2698c893ca399d6c13686f6663f483894bccaabc3b9c56ad +b58ac36bc6847077584308d952c5f3663e3001af5ecf2e19cb162e1c58bd6c49510205d453cffc876ca1dc6b8e04a578 +924f65ced61266a79a671ffb49b300f0ea44c50a0b4e3b02064faa99fcc3e4f6061ea8f38168ab118c5d47bd7804590e +8d67d43b8a06b0ff4fafd7f0483fa9ed1a9e3e658a03fb49d9d9b74e2e24858dc1bed065c12392037b467f255d4e5643 +b4d4f87813125a6b355e4519a81657fa97c43a6115817b819a6caf4823f1d6a1169683fd68f8d025cdfa40ebf3069acb +a7fd4d2c8e7b59b8eed3d4332ae94b77a89a2616347402f880bc81bde072220131e6dbec8a605be3a1c760b775375879 +8d4a7d8fa6f55a30df37bcf74952e2fa4fd6676a2e4606185cf154bdd84643fd01619f8fb8813a564f72e3f574f8ce30 +8086fb88e6260e9a9c42e9560fde76315ff5e5680ec7140f2a18438f15bc2cc7d7d43bfb5880b180b738c20a834e6134 +916c4c54721de03934fee6f43de50bb04c81f6f8dd4f6781e159e71c40c60408aa54251d457369d133d4ba3ed7c12cb4 +902e5bf468f11ed9954e2a4a595c27e34abe512f1d6dc08bbca1c2441063f9af3dc5a8075ab910a10ff6c05c1c644a35 +a1302953015e164bf4c15f7d4d35e3633425a78294406b861675667eec77765ff88472306531e5d3a4ec0a2ff0dd6a9e +87874461df3c9aa6c0fa91325576c0590f367075f2f0ecfeb34afe162c04c14f8ce9d608c37ac1adc8b9985bc036e366 +84b50a8a61d3cc609bfb0417348133e698fe09a6d37357ce3358de189efcf35773d78c57635c2d26c3542b13cc371752 +acaed2cff8633d12c1d12bb7270c54d65b0b0733ab084fd47f81d0a6e1e9b6f300e615e79538239e6160c566d8bb8d29 +889e6a0e136372ca4bac90d1ab220d4e1cad425a710e8cdd48b400b73bb8137291ceb36a39440fa84305783b1d42c72f +90952e5becec45b2b73719c228429a2c364991cf1d5a9d6845ae5b38018c2626f4308daa322cab1c72e0f6c621bb2b35 +8f5a97a801b6e9dcd66ccb80d337562c96f7914e7169e8ff0fda71534054c64bf2a9493bb830623d612cfe998789be65 +84f3df8b9847dcf1d63ca470dc623154898f83c25a6983e9b78c6d2d90a97bf5e622445be835f32c1e55e6a0a562ea78 +91d12095cd7a88e7f57f254f02fdb1a1ab18984871dead2f107404bcf8069fe68258c4e6f6ebd2477bddf738135400bb +b771a28bc04baef68604d4723791d3712f82b5e4fe316d7adc2fc01b935d8e644c06d59b83bcb542afc40ebafbee0683 +872f6341476e387604a7e93ae6d6117e72d164e38ebc2b825bc6df4fcce815004d7516423c190c1575946b5de438c08d +90d6b4aa7d40a020cdcd04e8b016d041795961a8e532a0e1f4041252131089114a251791bf57794cadb7d636342f5d1c +899023ba6096a181448d927fed7a0fe858be4eac4082a42e30b3050ee065278d72fa9b9d5ce3bc1372d4cbd30a2f2976 +a28f176571e1a9124f95973f414d5bdbf5794d41c3839d8b917100902ac4e2171eb940431236cec93928a60a77ede793 +838dbe5bcd29c4e465d02350270fa0036cd46f8730b13d91e77afb7f5ed16525d0021d3b2ae173a76c378516a903e0cb +8e105d012dd3f5d20f0f1c4a7e7f09f0fdd74ce554c3032e48da8cce0a77260d7d47a454851387770f5c256fa29bcb88 +8f4df0f9feeb7a487e1d138d13ea961459a6402fd8f8cabb226a92249a0d04ded5971f3242b9f90d08da5ff66da28af6 +ad1cfda4f2122a20935aa32fb17c536a3653a18617a65c6836700b5537122af5a8206befe9eaea781c1244c43778e7f1 +832c6f01d6571964ea383292efc8c8fa11e61c0634a25fa180737cc7ab57bc77f25e614aac9a2a03d98f27b3c1c29de2 +903f89cc13ec6685ac7728521898781fecb300e9094ef913d530bf875c18bcc3ceed7ed51e7b482d45619ab4b025c2e9 +a03c474bb915aad94f171e8d96f46abb2a19c9470601f4c915512ec8b9e743c3938450a2a5b077b4618b9df8809e1dc1 +83536c8456f306045a5f38ae4be2e350878fa7e164ea408d467f8c3bc4c2ee396bd5868008c089183868e4dfad7aa50b +88f26b4ea1b236cb326cd7ad7e2517ec8c4919598691474fe15d09cabcfc37a8d8b1b818f4d112432ee3a716b0f37871 +a44324e3fe96e9c12b40ded4f0f3397c8c7ee8ff5e96441118d8a6bfad712d3ac990b2a6a23231a8f691491ac1fd480f +b0de4693b4b9f932191a21ee88629964878680152a82996c0019ffc39f8d9369bbe2fe5844b68d6d9589ace54af947e4 +8e5d8ba948aea5fd26035351a960e87f0d23efddd8e13236cc8e4545a3dda2e9a85e6521efb8577e03772d3637d213d9 +93efc82d2017e9c57834a1246463e64774e56183bb247c8fc9dd98c56817e878d97b05f5c8d900acf1fbbbca6f146556 +8731176363ad7658a2862426ee47a5dce9434216cef60e6045fa57c40bb3ce1e78dac4510ae40f1f31db5967022ced32 +b10c9a96745722c85bdb1a693100104d560433d45b9ac4add54c7646a7310d8e9b3ca9abd1039d473ae768a18e489845 +a2ac374dfbb464bf850b4a2caf15b112634a6428e8395f9c9243baefd2452b4b4c61b0cb2836d8eae2d57d4900bf407e +b69fe3ded0c4f5d44a09a0e0f398221b6d1bf5dbb8bc4e338b93c64f1a3cac1e4b5f73c2b8117158030ec03787f4b452 +8852cdbaf7d0447a8c6f211b4830711b3b5c105c0f316e3a6a18dcfbb9be08bd6f4e5c8ae0c3692da08a2dfa532f9d5c +93bbf6d7432a7d98ade3f94b57bf9f4da9bc221a180a370b113066dd42601bb9e09edd79e2e6e04e00423399339eebda +a80941c391f1eeafc1451c59e4775d6a383946ff22997aeaadf806542ba451d3b0f0c6864eeba954174a296efe2c1550 +a045fe2bb011c2a2f71a0181a8f457a3078470fb74c628eab8b59aef69ffd0d649723bf74d6885af3f028bc5a104fb39 +b9d8c35911009c4c8cad64692139bf3fc16b78f5a19980790cb6a7aea650a25df4231a4437ae0c351676a7e42c16134f +94c79501ded0cfcbab99e1841abe4a00a0252b3870e20774c3da16c982d74c501916ec28304e71194845be6e3113c7ab +900a66418b082a24c6348d8644ddb1817df5b25cb33044a519ef47cc8e1f7f1e38d2465b7b96d32ed472d2d17f8414c6 +b26f45d393b8b2fcb29bdbb16323dc7f4b81c09618519ab3a39f8ee5bd148d0d9f3c0b5dfab55b5ce14a1cb9206d777b +aa1a87735fc493a80a96a9a57ca40a6d9c32702bfcaa9869ce1a116ae65d69cefe2f3e79a12454b4590353e96f8912b4 +a922b188d3d0b69b4e4ea2a2aa076566962844637da12c0832105d7b31dea4a309eee15d12b7a336be3ea36fcbd3e3b7 +8f3841fcf4105131d8c4d9885e6e11a46c448226401cf99356c291fadb864da9fa9d30f3a73c327f23f9fd99a11d633e +9791d1183fae270e226379af6c497e7da803ea854bb20afa74b253239b744c15f670ee808f708ede873e78d79a626c9a +a4cad52e3369491ada61bf28ada9e85de4516d21c882e5f1cd845bea9c06e0b2887b0c5527fcff6fc28acd3c04f0a796 +b9ac86a900899603452bd11a7892a9bfed8054970bfcbeaa8c9d1930db891169e38d6977f5258c25734f96c8462eee3b +a3a154c28e5580656a859f4efc2f5ebfa7eaa84ca40e3f134fa7865e8581586db74992dbfa4036aa252fba103773ddde +95cc2a0c1885a029e094f5d737e3ecf4d26b99036453a8773c77e360101f9f98676ee246f6f732a377a996702d55691f +842651bbe99720438d8d4b0218feb60481280c05beb17750e9ca0d8c0599a60f873b7fbdcc7d8835ba9a6d57b16eec03 +81ee54699da98f5620307893dcea8f64670609fa20e5622265d66283adeac122d458b3308c5898e6c57c298db2c8b24f +b97868b0b2bc98032d68352a535a1b341b9ff3c7af4e3a7f3ebc82d3419daa1b5859d6aedc39994939623c7cd878bd9b +b60325cd5d36461d07ef253d826f37f9ee6474a760f2fff80f9873d01fd2b57711543cdc8d7afa1c350aa753c2e33dea +8c205326c11d25a46717b780c639d89714c7736c974ae71287e3f4b02e6605ac2d9b4928967b1684f12be040b7bf2dd3 +95a392d82db51e26ade6c2ccd3396d7e40aff68fa570b5951466580d6e56dda51775dce5cf3a74a7f28c3cb2eb551c4d +8f2cc8071eb56dffb70bda6dd433b556221dc8bba21c53353c865f00e7d4d86c9e39f119ea9a8a12ef583e9a55d9a6b6 +9449a71af9672aaf8856896d7e3d788b22991a7103f75b08c0abbcc2bfe60fda4ed8ce502cea4511ff0ea52a93e81222 +857090ab9fdb7d59632d068f3cc8cf27e61f0d8322d30e6b38e780a1f05227199b4cd746aac1311c36c659ef20931f28 +98a891f4973e7d9aaf9ac70854608d4f7493dffc7e0987d7be9dd6029f6ea5636d24ef3a83205615ca1ff403750058e1 +a486e1365bbc278dd66a2a25d258dc82f46b911103cb16aab3945b9c95ae87b386313a12b566df5b22322ede0afe25ad +a9a1eb399ed95d396dccd8d1ac718043446f8b979ec62bdce51c617c97a312f01376ab7fb87d27034e5f5570797b3c33 +b7abc3858d7a74bb446218d2f5a037e0fae11871ed9caf44b29b69c500c1fa1dcfad64c9cdccc9d80d5e584f06213deb +8cfb09fe2e202faa4cebad932b1d35f5ca204e1c2a0c740a57812ac9a6792130d1312aabd9e9d4c58ca168bfebd4c177 +a90a305c2cd0f184787c6be596fa67f436afd1f9b93f30e875f817ac2aae8bdd2e6e656f6be809467e6b3ad84adb86b1 +80a9ef993c2b009ae172cc8f7ec036f5734cf4f4dfa06a7db4d54725e7fbfae5e3bc6f22687bdbb6961939d6f0c87537 +848ade1901931e72b955d7db1893f07003e1708ff5d93174bac5930b9a732640f0578839203e9b77eb27965c700032d3 +93fdf4697609c5ae9c33b9ca2f5f1af44abeb2b98dc4fdf732cf7388de086f410730dc384d9b7a7f447bb009653c8381 +89ce3fb805aea618b5715c0d22a9f46da696b6fa86794f56fdf1d44155a33d42daf1920bcbe36cbacf3cf4c92df9cbc7 +829ce2c342cf82aa469c65f724f308f7a750bd1494adc264609cd790c8718b8b25b5cab5858cf4ee2f8f651d569eea67 +af2f0cee7bf413204be8b9df59b9e4991bc9009e0d6dbe6815181df0ec2ca93ab8f4f3135b1c14d8f53d74bff0bd6f27 +b87998cecf7b88cde93d1779f10a521edd5574a2fbd240102978639ec57433ba08cdb53849038a329cebbe74657268d2 +a64542a1261a6ed3d720c2c3a802303aad8c4c110c95d0f12e05c1065e66f42da494792b6bfc5b9272363f3b1d457f58 +86a6fd042e4f282fadf07a4bfee03fc96a3aea49f7a00f52bf249a20f1ec892326855410e61f37fbb27d9305eb2fc713 +967ea5bc403b6db269682f7fd0df90659350d7e1aa66bc4fab4c9dfcd75ed0bba4b52f1cebc5f34dc8ba810793727629 +a52990f9f3b8616ce3cdc2c74cd195029e6a969753dcf2d1630438700e7d6ebde36538532b3525ac516f5f2ce9dd27a3 +a64f7ff870bab4a8bf0d4ef6f5c744e9bf1021ed08b4c80903c7ad318e80ba1817c3180cc45cb5a1cae1170f0241655f +b00f706fa4de1f663f021e8ad3d155e84ce6084a409374b6e6cd0f924a0a0b51bebaaaf1d228c77233a73b0a5a0df0e9 +8b882cc3bff3e42babdb96df95fb780faded84887a0a9bab896bef371cdcf169d909f5658649e93006aa3c6e1146d62e +9332663ef1d1dcf805c3d0e4ce7a07d9863fb1731172e766b3cde030bf81682cc011e26b773fb9c68e0477b4ae2cfb79 +a8aa8151348dbd4ef40aaeb699b71b4c4bfd3218560c120d85036d14f678f6736f0ec68e80ce1459d3d35feccc575164 +a16cd8b729768f51881c213434aa28301fa78fcb554ddd5f9012ee1e4eae7b5cb3dd88d269d53146dea92d10790faf0b +86844f0ef9d37142faf3b1e196e44fbe280a3ba4189aa05c356778cb9e3b388a2bff95eed305ada8769935c9974e4c57 +ae2eec6b328fccf3b47bcdac32901ac2744a51beb410b04c81dea34dee4912b619466a4f5e2780d87ecefaebbe77b46d +915df4c38d301c8a4eb2dc5b1ba0ffaad67cbb177e0a80095614e9c711f4ef24a4cef133f9d982a63d2a943ba6c8669d +ae6a2a4dedfc2d1811711a8946991fede972fdf2a389b282471280737536ffc0ac3a6d885b1f8bda0366eb0b229b9979 +a9b628c63d08b8aba6b1317f6e91c34b2382a6c85376e8ef2410a463c6796740ae936fc4e9e0737cb9455d1daa287bd8 +848e30bf7edf2546670b390d5cf9ab71f98fcb6add3c0b582cb34996c26a446dee5d1bde4fdcde4fc80c10936e117b29 +907d6096c7c8c087d1808dd995d5d2b9169b3768c3f433475b50c2e2bd4b082f4d543afd8b0b0ddffa9c66222a72d51d +a59970a2493b07339124d763ac9d793c60a03354539ecbcf6035bc43d1ea6e35718202ae6d7060b7d388f483d971573c +b9cfef2af9681b2318f119d8611ff6d9485a68d8044581b1959ab1840cbca576dbb53eec17863d2149966e9feb21122f +ad47271806161f61d3afa45cdfe2babceef5e90031a21779f83dc8562e6076680525b4970b2f11fe9b2b23c382768323 +8e425a99b71677b04fe044625d338811fbb8ee32368a424f6ab2381c52e86ee7a6cecedf777dc97181519d41c351bc22 +86b55b54d7adefc12954a9252ee23ae83efe8b5b4b9a7dc307904413e5d69868c7087a818b2833f9b004213d629be8ad +a14fda6b93923dd11e564ae4457a66f397741527166e0b16a8eb91c6701c244fd1c4b63f9dd3515193ec88fa6c266b35 +a9b17c36ae6cd85a0ed7f6cabc5b47dc8f80ced605db327c47826476dc1fb8f8669aa7a7dc679fbd4ee3d8e8b4bd6a6f +82a0829469c1458d959c821148f15dacae9ea94bf56c59a6ab2d4dd8b3d16d73e313b5a3912a6c1f131d73a8f06730c4 +b22d56d549a53eaef549595924bdb621ff807aa4513feedf3fdcbf7ba8b6b9cfa4481c2f67fc642db397a6b794a8b63a +974c59c24392e2cb9294006cbe3c52163e255f3bd0c2b457bdc68a6338e6d5b6f87f716854492f8d880a6b896ccf757c +b70d247ba7cad97c50b57f526c2ba915786e926a94e8f8c3eebc2e1be6f4255411b9670e382060049c8f4184302c40b2 +ad80201fe75ef21c3ddbd98cf23591e0d7a3ba1036dfe77785c32f44755a212c31f0ceb0a0b6f5ee9b6dc81f358d30c3 +8c656e841f9bb90b9a42d425251f3fdbc022a604d75f5845f479ed4be23e02aaf9e6e56cde351dd7449c50574818a199 +8b88dd3fa209d3063b7c5b058f7249ee9900fbc2287d16da61a0704a0a1d71e45d9c96e1cda7fdf9654534ec44558b22 +961da00cc8750bd84d253c08f011970ae1b1158ad6778e8ed943d547bceaf52d6d5a212a7de3bf2706688c4389b827d2 +a5dd379922549a956033e3d51a986a4b1508e575042b8eaa1df007aa77cf0b8c2ab23212f9c075702788fa9c53696133 +ac8fcfde3a349d1e93fc8cf450814e842005c545c4844c0401bc80e6b96cdb77f29285a14455e167c191d4f312e866cd +ac63d79c799783a8466617030c59dd5a8f92ee6c5204676fd8d881ce5f7f8663bdbeb0379e480ea9b6340ab0dc88e574 +805874fde19ce359041ae2bd52a39e2841acabfd31f965792f2737d7137f36d4e4722ede8340d8c95afa6af278af8acb +8d2f323a228aa8ba7b7dc1399138f9e6b41df1a16a7069003ab8104b8b68506a45141bc5fe66acf430e23e13a545190b +a1610c721a2d9af882bb6b39bea97cff1527a3aea041d25934de080214ae77c959e79957164440686d15ab301e897d4d +aba16d29a47fc36f12b654fde513896723e2c700c4190f11b26aa4011da57737ad717daa02794aa3246e4ae5f0b0cc3a +a406db2f15fdd135f346cc4846623c47edd195e80ba8c7cb447332095314d565e4040694ca924696bb5ee7f8996ea0ba +8b30e2cd9b47d75ba57b83630e40f832249af6c058d4f490416562af451993eec46f3e1f90bc4d389e4c06abd1b32a46 +aacf9eb7036e248e209adbfc3dd7ce386569ea9b312caa4b240726549db3c68c4f1c8cbf8ed5ea9ea60c7e57c9df3b8e +b20fcac63bf6f5ee638a42d7f89be847f348c085ddcbec3fa318f4323592d136c230495f188ef2022aa355cc2b0da6f9 +811eff750456a79ec1b1249d76d7c1547065b839d8d4aaad860f6d4528eb5b669473dcceeeea676cddbc3980b68461b7 +b52d14ae33f4ab422f953392ae76a19c618cc31afc96290bd3fe2fb44c954b5c92c4789f3f16e8793f2c0c1691ade444 +a7826dafeeba0db5b66c4dfcf2b17fd7b40507a5a53ac2e42942633a2cb30b95ba1739a6e9f3b7a0e0f1ec729bf274e2 +8acfd83ddf7c60dd7c8b20c706a3b972c65d336b8f9b3d907bdd8926ced271430479448100050b1ef17578a49c8fa616 +af0c69f65184bb06868029ad46f8465d75c36814c621ac20a5c0b06a900d59305584f5a6709683d9c0e4b6cd08d650a6 +b6cc8588191e00680ee6c3339bd0f0a17ad8fd7f4be57d5d7075bede0ea593a19e67f3d7c1a20114894ee5bfcab71063 +a82fd4f58635129dbb6cc3eb9391cf2d28400018b105fc41500fbbd12bd890b918f97d3d359c29dd3b4c4e34391dfab0 +92fc544ed65b4a3625cf03c41ddff7c039bc22d22c0d59dcc00efd5438401f2606adb125a1d5de294cca216ec8ac35a3 +906f67e4a32582b71f15940523c0c7ce370336935e2646bdaea16a06995256d25e99df57297e39d6c39535e180456407 +97510337ea5bbd5977287339197db55c60533b2ec35c94d0a460a416ae9f60e85cee39be82abeeacd5813cf54df05862 +87e6894643815c0ea48cb96c607266c5ee4f1f82ba5fe352fb77f9b6ed14bfc2b8e09e80a99ac9047dfcf62b2ae26795 +b6fd55dd156622ad7d5d51b7dde75e47bd052d4e542dd6449e72411f68275775c846dde301e84613312be8c7bce58b07 +b98461ac71f554b2f03a94e429b255af89eec917e208a8e60edf5fc43b65f1d17a20de3f31d2ce9f0cb573c25f2f4d98 +96f0dea40ca61cefbee41c4e1fe9a7d81fbe1f49bb153d083ab70f5d0488a1f717fd28cedcf6aa18d07cce2c62801898 +8d7c3ab310184f7dc34b6ce4684e4d29a31e77b09940448ea4daac730b7eb308063125d4dd229046cf11bfd521b771e0 +96f0564898fe96687918bbf0a6adead99cf72e3a35ea3347e124af9d006221f8e82e5a9d2fe80094d5e8d48e610f415e +ad50fcb92c2675a398cf07d4c40a579e44bf8d35f27cc330b57e54d5ea59f7d898af0f75dccfe3726e5471133d70f92b +828beed62020361689ae7481dd8f116902b522fb0c6c122678e7f949fdef70ead011e0e6bffd25678e388744e17cdb69 +8349decac1ca16599eee2efc95bcaabf67631107da1d34a2f917884bd70dfec9b4b08ab7bc4379d6c73b19c0b6e54fb8 +b2a6a2e50230c05613ace9e58bb2e98d94127f196f02d9dddc53c43fc68c184549ca12d713cb1b025d8260a41e947155 +94ff52181aadae832aed52fc3b7794536e2a31a21fc8be3ea312ca5c695750d37f08002f286b33f4023dba1e3253ecfa +a21d56153c7e5972ee9a319501be4faff199fdf09bb821ea9ce64aa815289676c00f105e6f00311b3a5b627091b0d0fc +a27a60d219f1f0c971db73a7f563b371b5c9fc3ed1f72883b2eac8a0df6698400c9954f4ca17d7e94e44bd4f95532afb +a2fc56fae99b1f18ba5e4fe838402164ce82f8a7f3193d0bbd360c2bac07c46f9330c4c7681ffb47074c6f81ee6e7ac6 +b748e530cd3afb96d879b83e89c9f1a444f54e55372ab1dcd46a0872f95ce8f49cf2363fc61be82259e04f555937ed16 +8bf8993e81080c7cbba1e14a798504af1e4950b2f186ab3335b771d6acaee4ffe92131ae9c53d74379d957cb6344d9cd +96774d0ef730d22d7ab6d9fb7f90b9ead44285219d076584a901960542756700a2a1603cdf72be4708b267200f6c36a9 +b47703c2ab17be1e823cc7bf3460db1d6760c0e33862c90ca058845b2ff234b0f9834ddba2efb2ee1770eb261e7d8ffd +84319e67c37a9581f8b09b5e4d4ae88d0a7fb4cbb6908971ab5be28070c3830f040b1de83ee663c573e0f2f6198640e4 +96811875fa83133e0b3c0e0290f9e0e28bca6178b77fdf5350eb19344d453dbd0d71e55a0ef749025a5a2ca0ad251e81 +81a423423e9438343879f2bfd7ee9f1c74ebebe7ce3cfffc8a11da6f040cc4145c3b527bd3cf63f9137e714dbcb474ef +b8c3535701ddbeec2db08e17a4fa99ba6752d32ece5331a0b8743676f421fcb14798afc7c783815484f14693d2f70db8 +81aee980c876949bf40782835eec8817d535f6f3f7e00bf402ddd61101fdcd60173961ae90a1cf7c5d060339a18c959d +87e67b928d97b62c49dac321ce6cb680233f3a394d4c9a899ac2e8db8ccd8e00418e66cdfd68691aa3cb8559723b580c +8eac204208d99a2b738648df96353bbb1b1065e33ee4f6bba174b540bbbd37d205855e1f1e69a6b7ff043ca377651126 +848e6e7a54ad64d18009300b93ea6f459ce855971dddb419b101f5ac4c159215626fadc20cc3b9ab1701d8f6dfaddd8b +88aa123d9e0cf309d46dddb6acf634b1ade3b090a2826d6e5e78669fa1220d6df9a6697d7778cd9b627db17eea846126 +9200c2a629b9144d88a61151b661b6c4256cc5dadfd1e59a8ce17a013c2d8f7e754aabe61663c3b30f1bc47784c1f8cf +b6e1a2827c3bdda91715b0e1b1f10dd363cef337e7c80cac1f34165fc0dea7c8b69747e310563db5818390146ce3e231 +92c333e694f89f0d306d54105b2a5dcc912dbe7654d9e733edab12e8537350815be472b063e56cfde5286df8922fdecb +a6fac04b6d86091158ebb286586ccfec2a95c9786e14d91a9c743f5f05546073e5e3cc717635a0c602cad8334e922346 +a581b4af77feebc1fb897d49b5b507c6ad513d8f09b273328efbb24ef0d91eb740d01b4d398f2738125dacfe550330cd +81c4860cccf76a34f8a2bc3f464b7bfd3e909e975cce0d28979f457738a56e60a4af8e68a3992cf273b5946e8d7f76e2 +8d1eaa09a3180d8af1cbaee673db5223363cc7229a69565f592fa38ba0f9d582cedf91e15dabd06ebbf2862fc0feba54 +9832f49b0147f4552402e54593cfa51f99540bffada12759b71fcb86734be8e500eea2d8b3d036710bdf04c901432de9 +8bdb0e8ec93b11e5718e8c13cb4f5de545d24829fd76161216340108098dfe5148ed25e3b57a89a516f09fa79043734d +ab96f06c4b9b0b2c0571740b24fca758e6976315053a7ecb20119150a9fa416db2d3a2e0f8168b390bb063f0c1caf785 +ab777f5c52acd62ecf4d1f168b9cc8e1a9b45d4ec6a8ff52c583e867c2239aba98d7d3af977289b367edce03d9c2dfb1 +a09d3ce5e748da84802436951acc3d3ea5d8ec1d6933505ed724d6b4b0d69973ab0930daec9c6606960f6e541e4a3ce2 +8ef94f7be4d85d5ad3d779a5cf4d7b2fc3e65c52fb8e1c3c112509a4af77a0b5be994f251e5e40fabeeb1f7d5615c22b +a7406a5bf5708d9e10922d3c5c45c03ef891b8d0d74ec9f28328a72be4cdc05b4f2703fa99366426659dfca25d007535 +b7f52709669bf92a2e070bfe740f422f0b7127392c5589c7f0af71bb5a8428697c762d3c0d74532899da24ea7d8695c2 +b9dfb0c8df84104dbf9239ccefa4672ef95ddabb8801b74997935d1b81a78a6a5669a3c553767ec19a1281f6e570f4ff +ae4d5c872156061ce9195ac640190d8d71dd406055ee43ffa6f9893eb24b870075b74c94d65bc1d5a07a6573282b5520 +afe6bd3eb72266d333f1807164900dcfa02a7eb5b1744bb3c86b34b3ee91e3f05e38fa52a50dc64eeb4bdb1dd62874b8 +948043cf1bc2ef3c01105f6a78dc06487f57548a3e6ef30e6ebc51c94b71e4bf3ff6d0058c72b6f3ecc37efd7c7fa8c0 +a22fd17c2f7ffe552bb0f23fa135584e8d2d8d75e3f742d94d04aded2a79e22a00dfe7acbb57d44e1cdb962fb22ae170 +8cd0f4e9e4fb4a37c02c1bde0f69359c43ab012eb662d346487be0c3758293f1ca560122b059b091fddce626383c3a8f +90499e45f5b9c81426f3d735a52a564cafbed72711d9279fdd88de8038e953bc48c57b58cba85c3b2e4ce56f1ddb0e11 +8c30e4c034c02958384564cac4f85022ef36ab5697a3d2feaf6bf105049675bbf23d01b4b6814711d3d9271abff04cac +81f7999e7eeea30f3e1075e6780bbf054f2fb6f27628a2afa4d41872a385b4216dd5f549da7ce6cf39049b2251f27fb7 +b36a7191f82fc39c283ffe53fc1f5a9a00b4c64eee7792a8443475da9a4d226cf257f226ea9d66e329af15d8f04984ec +aad4da528fdbb4db504f3041c747455baff5fcd459a2efd78f15bdf3aea0bdb808343e49df88fe7a7c8620009b7964a3 +99ebd8c6dd5dd299517fb6381cfc2a7f443e6e04a351440260dd7c2aee3f1d8ef06eb6c18820b394366ecdfd2a3ce264 +8873725b81871db72e4ec3643084b1cdce3cbf80b40b834b092767728605825c19b6847ad3dcf328438607e8f88b4410 +b008ee2f895daa6abd35bd39b6f7901ae4611a11a3271194e19da1cdcc7f1e1ea008fe5c5440e50d2c273784541ad9c5 +9036feafb4218d1f576ef89d0e99124e45dacaa6d816988e34d80f454d10e96809791d5b78f7fd65f569e90d4d7238c5 +92073c1d11b168e4fa50988b0288638b4868e48bbc668c5a6dddf5499875d53be23a285acb5e4bad60114f6cf6c556e9 +88c87dfcb8ba6cbfe7e1be081ccfadbd589301db2cb7c99f9ee5d7db90aa297ed1538d5a867678a763f2deede5fd219a +b42a562805c661a50f5dea63108002c0f27c0da113da6a9864c9feb5552225417c0356c4209e8e012d9bcc9d182c7611 +8e6317d00a504e3b79cd47feb4c60f9df186467fe9ca0f35b55c0364db30528f5ff071109dabb2fc80bb9cd4949f0c24 +b7b1ea6a88694f8d2f539e52a47466695e39e43a5eb9c6f23bca15305fe52939d8755cc3ac9d6725e60f82f994a3772f +a3cd55161befe795af93a38d33290fb642b8d80da8b786c6e6fb02d393ea308fbe87f486994039cbd7c7b390414594b6 +b416d2d45b44ead3b1424e92c73c2cf510801897b05d1724ff31cbd741920cd858282fb5d6040fe1f0aa97a65bc49424 +950ee01291754feace97c2e933e4681e7ddfbc4fcd079eb6ff830b0e481d929c93d0c7fb479c9939c28ca1945c40da09 +869bd916aee8d86efe362a49010382674825d49195b413b4b4018e88ce43fe091b475d0b863ff0ba2259400f280c2b23 +9782f38cd9c9d3385ec286ebbc7cba5b718d2e65a5890b0a5906b10a89dc8ed80d417d71d7c213bf52f2af1a1f513ea7 +91cd33bc2628d096269b23faf47ee15e14cb7fdc6a8e3a98b55e1031ea0b68d10ba30d97e660f7e967d24436d40fad73 +8becc978129cc96737034c577ae7225372dd855da8811ae4e46328e020c803833b5bdbc4a20a93270e2b8bd1a2feae52 +a36b1d8076783a9522476ce17f799d78008967728ce920531fdaf88303321bcaf97ecaa08e0c01f77bc32e53c5f09525 +b4720e744943f70467983aa34499e76de6d59aa6fadf86f6b787fdce32a2f5b535b55db38fe2da95825c51002cfe142d +91ad21fc502eda3945f6de874d1b6bf9a9a7711f4d61354f9e5634fc73f9c06ada848de15ab0a75811d3250be862827d +84f78e2ebf5fc077d78635f981712daf17e2475e14c2a96d187913006ad69e234746184a51a06ef510c9455b38acb0d7 +960aa7906e9a2f11db64a26b5892ac45f20d2ccb5480f4888d89973beb6fa0dfdc06d68d241ff5ffc7f1b82b1aac242d +a99365dcd1a00c66c9db6924b97c920f5c723380e823b250db85c07631b320ec4e92e586f7319e67a522a0578f7b6d6c +a25d92d7f70cf6a88ff317cfec071e13774516da664f5fac0d4ecaa65b8bf4eb87a64a4d5ef2bd97dfae98d388dbf5cc +a7af47cd0041295798f9779020a44653007444e8b4ef0712982b06d0dcdd434ec4e1f7c5f7a049326602cb605c9105b7 +aefe172eac5568369a05980931cc476bebd9dea573ba276d59b9d8c4420784299df5a910033b7e324a6c2dfc62e3ef05 +b69bc9d22ffa645baa55e3e02522e9892bb2daa7fff7c15846f13517d0799766883ee09ae0869df4139150c5b843ca8a +95a10856140e493354fdd12722c7fdded21b6a2ffbc78aa2697104af8ad0c8e2206f44b0bfee077ef3949d46bbf7c16b +891f2fcd2c47cbea36b7fa715968540c233313f05333f09d29aba23c193f462ed490dd4d00969656e89c53155fdfe710 +a6c33e18115e64e385c843dde34e8a228222795c7ca90bc2cc085705d609025f3351d9be61822c69035a49fb3e48f2d5 +b87fb12f12c0533b005adad0487f03393ff682e13575e3cb57280c3873b2c38ba96a63c49eef7a442753d26b7005230b +b905c02ba451bfd411c135036d92c27af3b0b1c9c2f1309d6948544a264b125f39dd41afeff4666b12146c545adc168a +8b29c513f43a78951cf742231cf5457a6d9d55edf45df5481a0f299a418d94effef561b15d2c1a01d1b8067e7153fda9 +b9941cccd51dc645920d2781c81a317e5a33cb7cf76427b60396735912cb6d2ca9292bb4d36b6392467d390d2c58d9f3 +a8546b627c76b6ef5c93c6a98538d8593dbe21cb7673fd383d5401b0c935eea0bdeeefeb1af6ad41bad8464fb87bbc48 +aa286b27de2812de63108a1aec29d171775b69538dc6198640ac1e96767c2b83a50391f49259195957d457b493b667c9 +a932fb229f641e9abbd8eb2bd874015d97b6658ab6d29769fc23b7db9e41dd4f850382d4c1f08af8f156c5937d524473 +a1412840fcc86e2aeec175526f2fb36e8b3b8d21a78412b7266daf81e51b3f68584ed8bd42a66a43afdd8c297b320520 +89c78be9efb624c97ebca4fe04c7704fa52311d183ffd87737f76b7dadc187c12c982bd8e9ed7cd8beb48cdaafd2fd01 +a3f5ddec412a5bec0ce15e3bcb41c6214c2b05d4e9135a0d33c8e50a78eaba71e0a5a6ea8b45854dec5c2ed300971fc2 +9721f9cec7a68b7758e3887548790de49fa6a442d0396739efa20c2f50352a7f91d300867556d11a703866def2d5f7b5 +a23764e140a87e5991573521af039630dd28128bf56eed2edbed130fd4278e090b60cf5a1dca9de2910603d44b9f6d45 +a1a6494a994215e48ab55c70efa8ffdddce6e92403c38ae7e8dd2f8288cad460c6c7db526bbdf578e96ca04d9fe12797 +b1705ea4cb7e074efe0405fc7b8ee2ec789af0426142f3ec81241cacd4f7edcd88e39435e4e4d8e7b1df64f3880d6613 +85595d061d677116089a6064418b93eb44ff79e68d12bd9625078d3bbc440a60d0b02944eff6054433ee34710ae6fbb4 +9978d5e30bedb7526734f9a1febd973a70bfa20890490e7cc6f2f9328feab1e24f991285dbc3711d892514e2d7d005ad +af30243c66ea43b9f87a061f947f7bce745f09194f6e95f379c7582b9fead920e5d6957eaf05c12ae1282ada4670652f +a1930efb473f88001e47aa0b2b2a7566848cccf295792e4544096ecd14ee5d7927c173a8576b405bfa2eec551cd67eb5 +b0446d1c590ee5a45f7e22d269c044f3848c97aec1d226b44bfd0e94d9729c28a38bccddc3a1006cc5fe4e3c24f001f2 +b8a8380172df3d84b06176df916cf557966d4f2f716d3e9437e415d75b646810f79f2b2b71d857181b7fc944018883a3 +a563afec25b7817bfa26e19dc9908bc00aa8fc3d19be7d6de23648701659009d10e3e4486c28e9c6b13d48231ae29ac5 +a5a8e80579de886fb7d6408f542791876885947b27ad6fa99a8a26e381f052598d7b4e647b0115d4b5c64297e00ce28e +8f87afcc7ad33c51ac719bade3cd92da671a37a82c14446b0a2073f4a0a23085e2c8d31913ed2d0be928f053297de8f6 +a43c455ce377e0bc434386c53c752880687e017b2f5ae7f8a15c044895b242dffde4c92fb8f8bb50b18470b17351b156 +8368f8b12a5bceb1dba25adb3a2e9c7dc9b1a77a1f328e5a693f5aec195cd1e06b0fe9476b554c1c25dac6c4a5b640a3 +919878b27f3671fc78396f11531c032f3e2bd132d04cc234fa4858676b15fb1db3051c0b1db9b4fc49038216f11321ce +b48cd67fb7f1242696c1f877da4bdf188eac676cd0e561fbac1a537f7b8229aff5a043922441d603a26aae56a15faee4 +a3e0fdfd4d29ea996517a16f0370b54787fefe543c2fe73bfc6f9e560c1fd30dad8409859e2d7fa2d44316f24746c712 +8bb156ade8faf149df7bea02c140c7e392a4742ae6d0394d880a849127943e6f26312033336d3b9fdc0092d71b5efe87 +8845e5d5cc555ca3e0523244300f2c8d7e4d02aaebcb5bd749d791208856c209a6f84dd99fd55968c9f0ab5f82916707 +a3e90bb5c97b07789c2f32dff1aec61d0a2220928202f5ad5355ae71f8249237799d6c8a22602e32e572cb12eabe0c17 +b150bcc391884c996149dc3779ce71f15dda63a759ee9cc05871f5a8379dcb62b047098922c0f26c7bd04deb394c33f9 +95cd4ad88d51f0f2efcfd0c2df802fe252bb9704d1afbf9c26a248df22d55da87bdfaf41d7bc6e5df38bd848f0b13f42 +a05a49a31e91dff6a52ac8b9c2cfdd646a43f0d488253f9e3cfbce52f26667166bbb9b608fc358763a65cbf066cd6d05 +a59c3c1227fdd7c2e81f5e11ef5c406da44662987bac33caed72314081e2eed66055d38137e01b2268e58ec85dd986c0 +b7020ec3bd73a99861f0f1d88cf5a19abab1cbe14b7de77c9868398c84bb8e18dbbe9831838a96b6d6ca06e82451c67b +98d1ff2525e9718ee59a21d8900621636fcd873d9a564b8dceb4be80a194a0148daf1232742730b3341514b2e5a5436c +886d97b635975fc638c1b6afc493e5998ca139edba131b75b65cfe5a8e814f11bb678e0eeee5e6e5cd913ad3f2fefdfc +8fb9fd928d38d5d813b671c924edd56601dd7163b686c13f158645c2f869d9250f3859aa5463a39258c90fef0f41190a +aac35e1cd655c94dec3580bb3800bd9c2946c4a9856f7d725af15fbea6a2d8ca51c8ad2772abed60ee0e3fb9cb24046b +b8d71fa0fa05ac9e443c9b4929df9e7f09a919be679692682e614d24227e04894bfc14a5c73a62fb927fedff4a0e4aa7 +a45a19f11fbbb531a704badbb813ed8088ab827c884ee4e4ebf363fa1132ff7cfa9d28be9c85b143e4f7cdbc94e7cf1a +82b54703a4f295f5471b255ab59dce00f0fe90c9fb6e06b9ee48b15c91d43f4e2ef4a96c3118aeb03b08767be58181bb +8283264c8e6d2a36558f0d145c18576b6600ff45ff99cc93eca54b6c6422993cf392668633e5df396b9331e873d457e5 +8c549c03131ead601bc30eb6b9537b5d3beb7472f5bb1bcbbfd1e9f3704477f7840ab3ab7f7dc13bbbbcdff886a462d4 +afbb0c520ac1b5486513587700ad53e314cb74bfbc12e0b5fbdcfdaac36d342e8b59856196a0d84a25cff6e6e1d17e76 +89e4c22ffb51f2829061b3c7c1983c5c750cad158e3a825d46f7cf875677da5d63f653d8a297022b5db5845c9271b32b +afb27a86c4c2373088c96b9adf4433f2ebfc78ac5c526e9f0510670b6e4e5e0057c0a4f75b185e1a30331b9e805c1c15 +a18e16b57445f88730fc5d3567bf5a176861dc14c7a08ed2996fe80eed27a0e7628501bcb78a1727c5e9ac55f29c12c4 +93d61bf88b192d6825cf4e1120af1c17aa0f994d158b405e25437eaeefae049f7b721a206e7cc8a04fdc29d3c42580a1 +a99f2995a2e3ed2fd1228d64166112038de2f516410aa439f4c507044e2017ea388604e2d0f7121256fadf7fbe7023d1 +914fd91cffc23c32f1c6d0e98bf660925090d873367d543034654389916f65f552e445b0300b71b61b721a72e9a5983c +b42a578a7787b71f924e7def425d849c1c777156b1d4170a8ee7709a4a914e816935131afd9a0412c4cb952957b20828 +82fb30590e84b9e45db1ec475a39971cf554dc01bcc7050bc89265740725c02e2be5a972168c5170c86ae83e5b0ad2c0 +b14f8d8e1e93a84976289e0cf0dfa6f3a1809e98da16ee5c4932d0e1ed6bf8a07697fdd4dd86a3df84fb0003353cdcc0 +85d7a2f4bda31aa2cb208b771fe03291a4ebdaf6f1dc944c27775af5caec412584c1f45bc741fca2a6a85acb3f26ad7d +af02e56ce886ff2253bc0a68faad76f25ead84b2144e5364f3fb9b648f03a50ee9dc0b2c33ebacf7c61e9e43201ef9ef +87e025558c8a0b0abd06dfc350016847ea5ced7af2d135a5c9eec9324a4858c4b21510fb0992ec52a73447f24945058e +80fff0bafcd058118f5e7a4d4f1ae0912efeb281d2cbe4d34ba8945cc3dbe5d8baf47fb077343b90b8d895c90b297aca +b6edcf3a40e7b1c3c0148f47a263cd819e585a51ef31c2e35a29ce6f04c53e413f743034c0d998d9c00a08ba00166f31 +abb87ed86098c0c70a76e557262a494ff51a30fb193f1c1a32f8e35eafa34a43fcc07aa93a3b7a077d9e35afa07b1a3d +a280214cd3bb0fb7ecd2d8bcf518cbd9078417f2b91d2533ec2717563f090fb84f2a5fcfdbbeb2a2a1f8a71cc5aa5941 +a63083ca7238ea2b57d15a475963cf1d4f550d8cd76db290014a0461b90351f1f26a67d674c837b0b773b330c7c3d534 +a8fa39064cb585ece5263e2f42f430206476bf261bd50f18d2b694889bd79d04d56410664cecad62690e5c5a20b3f6ff +85ba52ce9d700a5dcf6c5b00559acbe599d671ce5512467ff4b6179d7fad550567ce2a9c126a50964e3096458ea87920 +b913501e1008f076e5eac6d883105174f88b248e1c9801e568fefaffa1558e4909364fc6d9512aa4d125cbd7cc895f05 +8eb33b5266c8f2ed4725a6ad147a322e44c9264cf261c933cbbe230a43d47fca0f29ec39756b20561dabafadd5796494 +850ebc8b661a04318c9db5a0515066e6454fa73865aa4908767a837857ecd717387f614acb614a88e075d4edc53a2f5a +a08d6b92d866270f29f4ce23a3f5d99b36b1e241a01271ede02817c8ec3f552a5c562db400766c07b104a331835c0c64 +8131804c89bb3e74e9718bfc4afa547c1005ff676bd4db9604335032b203390cfa54478d45c6c78d1fe31a436ed4be9f +9106d94f23cc1eacec8316f16d6f0a1cc160967c886f51981fdb9f3f12ee1182407d2bb24e5b873de58cb1a3ee915a6b +a13806bfc3eae7a7000c9d9f1bd25e10218d4e67f59ae798b145b098bca3edad2b1040e3fc1e6310e612fb8818f459ac +8c69fbca502046cb5f6db99900a47b34117aef3f4b241690cdb3b84ca2a2fc7833e149361995dc41fa78892525bce746 +852c473150c91912d58ecb05769222fa18312800c3f56605ad29eec9e2d8667b0b81c379048d3d29100ed2773bb1f3c5 +b1767f6074426a00e01095dbb1795beb4e4050c6411792cbad6537bc444c3165d1058bafd1487451f9c5ddd209e0ae7e +80c600a5fe99354ce59ff0f84c760923dc8ff66a30bf47dc0a086181785ceb01f9b951c4e66df800ea6d705e8bc47055 +b5cf19002fbc88a0764865b82afcb4d64a50196ea361e5c71dff7de084f4dcbbc34ec94a45cc9e0247bd51da565981aa +93e67a254ea8ce25e112d93cc927fadaa814152a2c4ec7d9a56eaa1ed47aec99b7e9916b02e64452cc724a6641729bbb +ace70b32491bda18eee4a4d041c3bc9effae9340fe7e6c2f5ad975ee0874c17f1a7da7c96bd85fccff9312c518fac6e9 +ab4cfa02065017dd7f1aadc66f2c92f78f0f11b8597c03a5d69d82cb2eaf95a4476a836ac102908f137662472c8d914b +a40b8cd8deb8ae503d20364d64cab7c2801b7728a9646ed19c65edea6a842756a2f636283494299584ad57f4bb12cd0b +8594e11d5fc2396bcd9dbf5509ce4816dbb2b7305168021c426171fb444d111da5a152d6835ad8034542277011c26c0e +8024de98c26b4c994a66628dc304bb737f4b6859c86ded552c5abb81fd4c6c2e19d5a30beed398a694b9b2fdea1dd06a +8843f5872f33f54df8d0e06166c1857d733995f67bc54abb8dfa94ad92407cf0179bc91b0a50bbb56cdc2b350d950329 +b8bab44c7dd53ef9edf497dcb228e2a41282c90f00ba052fc52d57e87b5c8ab132d227af1fcdff9a12713d1f980bcaae +982b4d7b29aff22d527fd82d2a52601d95549bfb000429bb20789ed45e5abf1f4b7416c7b7c4b79431eb3574b29be658 +8eb1f571b6a1878e11e8c1c757e0bc084bab5e82e897ca9be9b7f4b47b91679a8190bf0fc8f799d9b487da5442415857 +a6e74b588e5af935c8b243e888582ef7718f8714569dd4992920740227518305eb35fab674d21a5551cca44b3e511ef2 +a30fc2f3a4cb4f50566e82307de73cd7bd8fe2c1184e9293c136a9b9e926a018d57c6e4f308c95b9eb8299e94d90a2a1 +a50c5869ca5d2b40722c056a32f918d47e0b65ca9d7863ca7d2fb4a7b64fe523fe9365cf0573733ceaadebf20b48fff8 +83bbdd32c04d17581418cf360749c7a169b55d54f2427390defd9f751f100897b2d800ce6636c5bbc046c47508d60c8c +a82904bdf614de5d8deaff688c8a5e7ac5b3431687acbcda8fa53960b7c417a39c8b2e462d7af91ce6d79260f412db8e +a4362e31ff4b05d278b033cf5eebea20de01714ae16d4115d04c1da4754269873afc8171a6f56c5104bfd7b0db93c3e7 +b5b8daa63a3735581e74a021b684a1038cea77168fdb7fdf83c670c2cfabcfc3ab2fc7359069b5f9048188351aef26b5 +b48d723894b7782d96ac8433c48faca1bdfa5238019c451a7f47d958097cce3ae599b876cf274269236b9d6ff8b6d7ca +98ffff6a61a3a6205c7820a91ca2e7176fab5dba02bc194c4d14942ac421cb254183c705506ab279e4f8db066f941c6c +ae7db24731da2eaa6efc4f7fcba2ecc26940ddd68038dce43acf2cee15b72dc4ef42a7bfdd32946d1ed78786dd7696b3 +a656db14f1de9a7eb84f6301b4acb2fbf78bfe867f48a270e416c974ab92821eb4df1cb881b2d600cfed0034ac784641 +aa315f8ecba85a5535e9a49e558b15f39520fce5d4bf43131bfbf2e2c9dfccc829074f9083e8d49f405fb221d0bc4c3c +90bffba5d9ff40a62f6c8e9fc402d5b95f6077ed58d030c93e321b8081b77d6b8dac3f63a92a7ddc01585cf2c127d66c +abdd733a36e0e0f05a570d0504e73801bf9b5a25ff2c78786f8b805704997acb2e6069af342538c581144d53149fa6d3 +b4a723bb19e8c18a01bd449b1bb3440ddb2017f10bb153da27deb7a6a60e9bb37619d6d5435fbb1ba617687838e01dd0 +870016b4678bab3375516db0187a2108b2e840bae4d264b9f4f27dbbc7cc9cac1d7dc582d7a04d6fd1ed588238e5e513 +80d33d2e20e8fc170aa3cb4f69fffb72aeafb3b5bb4ea0bc79ab55da14142ca19b2d8b617a6b24d537366e3b49cb67c3 +a7ee76aec273aaae03b3b87015789289551969fb175c11557da3ab77e39ab49d24634726f92affae9f4d24003050d974 +8415ea4ab69d779ebd42d0fe0c6aef531d6a465a5739e429b1fcf433ec45aa8296c527e965a20f0ec9f340c9273ea3cf +8c7662520794e8b4405d0b33b5cac839784bc86a5868766c06cbc1fa306dbe334978177417b31baf90ce7b0052a29c56 +902b2abecc053a3dbdea9897ee21e74821f3a1b98b2d560a514a35799f4680322550fd3a728d4f6d64e1de98033c32b8 +a05e84ed9ecab8d508d670c39f2db61ad6e08d2795ec32a3c9d0d3737ef3801618f4fc2a95f90ec2f068606131e076c5 +8b9208ff4d5af0c2e3f53c9375da666773ac57197dfabb0d25b1c8d0588ba7f3c15ee9661bb001297f322ea2fbf6928b +a3c827741b34a03254d4451b5ab74a96f2b9f7fb069e2f5adaf54fd97cc7a4d516d378db5ca07da87d8566d6eef13726 +8509d8a3f4a0ed378e0a1e28ea02f6bf1d7f6c819c6c2f5297c7df54c895b848f841653e32ba2a2c22c2ff739571acb8 +a0ce988b7d3c40b4e496aa83a09e4b5472a2d98679622f32bea23e6d607bc7de1a5374fb162bce0549a67dad948519be +aa8a3dd12bd60e3d2e05f9c683cdcb8eab17fc59134815f8d197681b1bcf65108cba63ac5c58ee632b1e5ed6bba5d474 +8b955f1d894b3aefd883fb4b65f14cd37fc2b9db77db79273f1700bef9973bf3fd123897ea2b7989f50003733f8f7f21 +ac79c00ddac47f5daf8d9418d798d8af89fc6f1682e7e451f71ea3a405b0d36af35388dd2a332af790bc83ca7b819328 +a0d44dd2a4438b809522b130d0938c3fe7c5c46379365dbd1810a170a9aa5818e1c783470dd5d0b6d4ac7edbb7330910 +a30b69e39ad43dd540a43c521f05b51b5f1b9c4eed54b8162374ae11eac25da4f5756e7b70ce9f3c92c2eeceee7431ed +ac43220b762c299c7951222ea19761ab938bf38e4972deef58ed84f4f9c68c230647cf7506d7cbfc08562fcca55f0485 +b28233b46a8fb424cfa386a845a3b5399d8489ceb83c8f3e05c22c934798d639c93718b7b68ab3ce24c5358339e41cbb +ac30d50ee8ce59a10d4b37a3a35e62cdb2273e5e52232e202ca7d7b8d09d28958ee667fae41a7bb6cdc6fe8f6e6c9c85 +b199842d9141ad169f35cc7ff782b274cbaa645fdb727761e0a89edbf0d781a15f8218b4bf4eead326f2903dd88a9cc1 +85e018c7ddcad34bb8285a737c578bf741ccd547e68c734bdb3808380e12c5d4ef60fc896b497a87d443ff9abd063b38 +8c856e6ba4a815bdb891e1276f93545b7072f6cb1a9aa6aa5cf240976f29f4dee01878638500a6bf1daf677b96b54343 +b8a47555fa8710534150e1a3f13eab33666017be6b41005397afa647ea49708565f2b86b77ad4964d140d9ced6b4d585 +8cd1f1db1b2f4c85a3f46211599caf512d5439e2d8e184663d7d50166fd3008f0e9253272f898d81007988435f715881 +b1f34b14612c973a3eceb716dc102b82ab18afef9de7630172c2780776679a7706a4874e1df3eaadf541fb009731807f +b25464af9cff883b55be2ff8daf610052c02df9a5e147a2cf4df6ce63edcdee6dc535c533590084cc177da85c5dc0baa +91c3c4b658b42d8d3448ae1415d4541d02379a40dc51e36a59bd6e7b9ba3ea51533f480c7c6e8405250ee9b96a466c29 +86dc027b95deb74c36a58a1333a03e63cb5ae22d3b29d114cfd2271badb05268c9d0c819a977f5e0c6014b00c1512e3a +ae0e6ff58eb5fa35da5107ebeacf222ab8f52a22bb1e13504247c1dfa65320f40d97b0e6b201cb6613476687cb2f0681 +8f13415d960b9d7a1d93ef28afc2223e926639b63bdefce0f85e945dfc81670a55df288893a0d8b3abe13c5708f82f91 +956f67ca49ad27c1e3a68c1faad5e7baf0160c459094bf6b7baf36b112de935fdfd79fa4a9ea87ea8de0ac07272969f4 +835e45e4a67df9fb51b645d37840b3a15c171d571a10b03a406dd69d3c2f22df3aa9c5cbe1e73f8d767ce01c4914ea9a +919b938e56d4b32e2667469d0bdccb95d9dda3341aa907683ee70a14bbbe623035014511c261f4f59b318b610ac90aa3 +96b48182121ccd9d689bf1dfdc228175564cd68dc904a99c808a7f0053a6f636c9d953e12198bdf2ea49ea92772f2e18 +ac5e5a941d567fa38fdbcfa8cf7f85bb304e3401c52d88752bcd516d1fa9bac4572534ea2205e38423c1df065990790f +ac0bd594fb85a8d4fc26d6df0fa81f11919401f1ecf9168b891ec7f061a2d9368af99f7fd8d9b43b2ce361e7b8482159 +83d92c69ca540d298fe80d8162a1c7af3fa9b49dfb69e85c1d136a3ec39fe419c9fa78e0bb6d96878771fbd37fe92e40 +b35443ae8aa66c763c2db9273f908552fe458e96696b90e41dd509c17a5c04ee178e3490d9c6ba2dc0b8f793c433c134 +923b2d25aa45b2e580ffd94cbb37dc8110f340f0f011217ee1bd81afb0714c0b1d5fb4db86006cdd2457563276f59c59 +96c9125d38fca1a61ac21257b696f8ac3dae78def50285e44d90ea293d591d1c58f703540a7e4e99e070afe4646bbe15 +b57946b2332077fbcdcb406b811779aefd54473b5559a163cd65cb8310679b7e2028aa55c12a1401fdcfcac0e6fae29a +845daedc5cf972883835d7e13c937b63753c2200324a3b8082a6c4abb4be06c5f7c629d4abe4bfaf1d80a1f073eb6ce6 +91a55dfd0efefcd03dc6dacc64ec93b8d296cb83c0ee72400a36f27246e7f2a60e73b7b70ba65819e9cfb73edb7bd297 +8874606b93266455fe8fdd25df9f8d2994e927460af06f2e97dd4d2d90db1e6b06d441b72c2e76504d753badca87fb37 +8ee99e6d231274ff9252c0f4e84549da173041299ad1230929c3e3d32399731c4f20a502b4a307642cac9306ccd49d3c +8836497714a525118e20849d6933bb8535fb6f72b96337d49e3133d936999c90a398a740f42e772353b5f1c63581df6d +a6916945e10628f7497a6cdc5e2de113d25f7ade3e41e74d3de48ccd4fce9f2fa9ab69645275002e6f49399b798c40af +9597706983107eb23883e0812e1a2c58af7f3499d50c6e29b455946cb9812fde1aa323d9ed30d1c0ffd455abe32303cd +a24ee89f7f515cc33bdbdb822e7d5c1877d337f3b2162303cfc2dae028011c3a267c5cb4194afa63a4856a6e1c213448 +8cd25315e4318801c2776824ae6e7d543cb85ed3bc2498ba5752df2e8142b37653cf9e60104d674be3aeb0a66912e97a +b5085ecbe793180b40dbeb879f4c976eaaccaca3a5246807dced5890e0ed24d35f3f86955e2460e14fb44ff5081c07ba +960188cc0b4f908633a6840963a6fa2205fc42c511c6c309685234911c5304ef4c304e3ae9c9c69daa2fb6a73560c256 +a32d0a70bf15d569b4cda5aebe3e41e03c28bf99cdd34ffa6c5d58a097f322772acca904b3a47addb6c7492a7126ebac +977f72d06ad72d4aa4765e0f1f9f4a3231d9f030501f320fe7714cc5d329d08112789fa918c60dd7fdb5837d56bb7fc6 +99fa038bb0470d45852bb871620d8d88520adb701712fcb1f278fed2882722b9e729e6cdce44c82caafad95e37d0e6f7 +b855e8f4fc7634ada07e83b6c719a1e37acb06394bc8c7dcab7747a8c54e5df3943915f021364bd019fdea103864e55f +88bc2cd7458532e98c596ef59ea2cf640d7cc31b4c33cef9ed065c078d1d4eb49677a67de8e6229cc17ea48bace8ee5a +aaa78a3feaa836d944d987d813f9b9741afb076e6aca1ffa42682ab06d46d66e0c07b8f40b9dbd63e75e81efa1ef7b08 +b7b080420cc4d808723b98b2a5b7b59c81e624ab568ecdfdeb8bf3aa151a581b6f56e983ef1b6f909661e25db40b0c69 +abee85c462ac9a2c58e54f06c91b3e5cd8c5f9ab5b5deb602b53763c54826ed6deb0d6db315a8d7ad88733407e8d35e2 +994d075c1527407547590df53e9d72dd31f037c763848d1662eebd4cefec93a24328c986802efa80e038cb760a5300f5 +ab8777640116dfb6678e8c7d5b36d01265dfb16321abbfc277da71556a34bb3be04bc4ae90124ed9c55386d2bfb3bda0 +967e3a828bc59409144463bcf883a3a276b5f24bf3cbfdd7a42343348cba91e00b46ac285835a9b91eef171202974204 +875a9f0c4ffe5bb1d8da5e3c8e41d0397aa6248422a628bd60bfae536a651417d4e8a7d2fb98e13f2dad3680f7bd86d3 +acaa330c3e8f95d46b1880126572b238dbb6d04484d2cd4f257ab9642d8c9fc7b212188b9c7ac9e0fd135c520d46b1bf +aceb762edbb0f0c43dfcdb01ea7a1ac5918ca3882b1e7ebc4373521742f1ed5250d8966b498c00b2b0f4d13212e6dd0b +81d072b4ad258b3646f52f399bced97c613b22e7ad76373453d80b1650c0ca87edb291a041f8253b649b6e5429bb4cff +980a47d27416ac39c7c3a0ebe50c492f8c776ea1de44d5159ac7d889b6d554357f0a77f0e5d9d0ff41aae4369eba1fc2 +8b4dfd5ef5573db1476d5e43aacfb5941e45d6297794508f29c454fe50ea622e6f068b28b3debe8635cf6036007de2e3 +a60831559d6305839515b68f8c3bc7abbd8212cc4083502e19dd682d56ca37c9780fc3ce4ec2eae81ab23b221452dc57 +951f6b2c1848ced9e8a2339c65918e00d3d22d3e59a0a660b1eca667d18f8430d737884e9805865ef3ed0fe1638a22d9 +b02e38fe790b492aa5e89257c4986c9033a8b67010fa2add9787de857d53759170fdd67715ca658220b4e14b0ca48124 +a51007e4346060746e6b0e4797fc08ef17f04a34fe24f307f6b6817edbb8ce2b176f40771d4ae8a60d6152cbebe62653 +a510005b05c0b305075b27b243c9d64bcdce85146b6ed0e75a3178b5ff9608213f08c8c9246f2ca6035a0c3e31619860 +aaff4ef27a7a23be3419d22197e13676d6e3810ceb06a9e920d38125745dc68a930f1741c9c2d9d5c875968e30f34ab5 +864522a9af9857de9814e61383bebad1ba9a881696925a0ea6bfc6eff520d42c506bbe5685a9946ed710e889765be4a0 +b63258c080d13f3b7d5b9f3ca9929f8982a6960bdb1b0f8676f4dca823971601672f15e653917bf5d3746bb220504913 +b51ce0cb10869121ae310c7159ee1f3e3a9f8ad498827f72c3d56864808c1f21fa2881788f19ece884d3f705cd7bd0c5 +95d9cecfc018c6ed510e441cf84c712d9909c778c16734706c93222257f64dcd2a9f1bd0b400ca271e22c9c487014274 +8beff4d7d0140b86380ff4842a9bda94c2d2be638e20ac68a4912cb47dbe01a261857536375208040c0554929ced1ddc +891ff49258749e2b57c1e9b8e04b12c77d79c3308b1fb615a081f2aacdfb4b39e32d53e069ed136fdbd43c53b87418fa +9625cad224e163d387738825982d1e40eeff35fe816d10d7541d15fdc4d3eee48009090f3faef4024b249205b0b28f72 +8f3947433d9bd01aa335895484b540a9025a19481a1c40b4f72dd676bfcf332713714fd4010bde936eaf9470fd239ed0 +a00ec2d67789a7054b53f0e858a8a232706ccc29a9f3e389df7455f1a51a2e75801fd78469a13dbc25d28399ae4c6182 +a3f65884506d4a62b8775a0ea0e3d78f5f46bc07910a93cd604022154eabdf1d73591e304d61edc869e91462951975e1 +a14eef4fd5dfac311713f0faa9a60415e3d30b95a4590cbf95f2033dffb4d16c02e7ceff3dcd42148a4e3bc49cce2dd4 +8afa11c0eef3c540e1e3460bc759bb2b6ea90743623f88e62950c94e370fe4fd01c22b6729beba4dcd4d581198d9358f +afb05548a69f0845ffcc5f5dc63e3cdb93cd270f5655173b9a950394b0583663f2b7164ba6df8d60c2e775c1d9f120af +97f179e01a947a906e1cbeafa083960bc9f1bade45742a3afee488dfb6011c1c6e2db09a355d77f5228a42ccaa7bdf8e +8447fca4d35f74b3efcbd96774f41874ca376bf85b79b6e66c92fa3f14bdd6e743a051f12a7fbfd87f319d1c6a5ce217 +a57ca39c23617cd2cf32ff93b02161bd7baf52c4effb4679d9d5166406e103bc8f3c6b5209e17c37dbb02deb8bc72ddd +9667c7300ff80f0140be002b0e36caab07aaee7cce72679197c64d355e20d96196acaf54e06e1382167d081fe6f739c1 +828126bb0559ce748809b622677267ca896fa2ee76360fd2c02990e6477e06a667241379ca7e65d61a5b64b96d7867de +8b8835dea6ba8cf61c91f01a4b3d2f8150b687a4ee09b45f2e5fc8f80f208ae5d142d8e3a18153f0722b90214e60c5a7 +a98e8ff02049b4da386e3ee93db23bbb13dfeb72f1cfde72587c7e6d962780b7671c63e8ac3fbaeb1a6605e8d79e2f29 +87a4892a0026d7e39ef3af632172b88337cb03669dea564bcdb70653b52d744730ebb5d642e20cb627acc9dbb547a26b +877352a22fc8052878a57effc159dac4d75fe08c84d3d5324c0bab6d564cdf868f33ceee515eee747e5856b62cfa0cc7 +8b801ba8e2ff019ee62f64b8cb8a5f601fc35423eb0f9494b401050103e1307dc584e4e4b21249cd2c686e32475e96c3 +a9e7338d6d4d9bfec91b2af28a8ed13b09415f57a3a00e5e777c93d768fdb3f8e4456ae48a2c6626b264226e911a0e28 +99c05fedf40ac4726ed585d7c1544c6e79619a0d3fb6bda75a08c7f3c0008e8d5e19ed4da48de3216135f34a15eba17c +a61cce8a1a8b13a4a650fdbec0eeea8297c352a8238fb7cac95a0df18ed16ee02a3daa2de108fa122aca733bd8ad7855 +b97f37da9005b440b4cb05870dd881bf8491fe735844f2d5c8281818583b38e02286e653d9f2e7fa5e74c3c3eb616540 +a72164a8554da8e103f692ac5ebb4aece55d5194302b9f74b6f2a05335b6e39beede0bf7bf8c5bfd4d324a784c5fb08c +b87e8221c5341cd9cc8bb99c10fe730bc105550f25ed4b96c0d45e6142193a1b2e72f1b3857373a659b8c09be17b3d91 +a41fb1f327ef91dcb7ac0787918376584890dd9a9675c297c45796e32d6e5985b12f9b80be47fc3a8596c245f419d395 +90dafa3592bdbb3465c92e2a54c2531822ba0459d45d3e7a7092fa6b823f55af28357cb51896d4ec2d66029c82f08e26 +a0a9adc872ebc396557f484f1dd21954d4f4a21c4aa5eec543f5fa386fe590839735c01f236574f7ff95407cd12de103 +b8c5c940d58be7538acf8672852b5da3af34f82405ef2ce8e4c923f1362f97fc50921568d0fd2fe846edfb0823e62979 +85aaf06a8b2d0dac89dafd00c28533f35dbd074978c2aaa5bef75db44a7b12aeb222e724f395513b9a535809a275e30b +81f3cbe82fbc7028c26a6c1808c604c63ba023a30c9f78a4c581340008dbda5ec07497ee849a2183fcd9124f7936af32 +a11ac738de75fd60f15a34209d3825d5e23385796a4c7fc5931822f3f380af977dd0f7b59fbd58eed7777a071e21b680 +85a279c493de03db6fa6c3e3c1b1b29adc9a8c4effc12400ae1128da8421954fa8b75ad19e5388fe4543b76fb0812813 +83a217b395d59ab20db6c4adb1e9713fc9267f5f31a6c936042fe051ce8b541f579442f3dcf0fa16b9e6de9fd3518191 +83a0b86e7d4ed8f9ccdc6dfc8ff1484509a6378fa6f09ed908e6ab9d1073f03011dc497e14304e4e3d181b57de06a5ab +a63ad69c9d25704ce1cc8e74f67818e5ed985f8f851afa8412248b2df5f833f83b95b27180e9e7273833ed0d07113d3b +99b1bc2021e63b561fe44ddd0af81fcc8627a91bfeecbbc989b642bc859abc0c8d636399701aad7bbaf6a385d5f27d61 +b53434adb66f4a807a6ad917c6e856321753e559b1add70824e5c1e88191bf6993fccb9b8b911fc0f473fb11743acacd +97ed3b9e6fb99bf5f945d4a41f198161294866aa23f2327818cdd55cb5dc4c1a8eff29dd8b8d04902d6cd43a71835c82 +b1e808260e368a18d9d10bdea5d60223ba1713b948c782285a27a99ae50cc5fc2c53d407de07155ecc16fb8a36d744a0 +a3eb4665f18f71833fec43802730e56b3ee5a357ea30a888ad482725b169d6f1f6ade6e208ee081b2e2633079b82ba7d +ab8beb2c8353fc9f571c18fdd02bdb977fc883313469e1277b0372fbbb33b80dcff354ca41de436d98d2ed710faa467e +aa9071cfa971e4a335a91ad634c98f2be51544cb21f040f2471d01bb97e1df2277ae1646e1ea8f55b7ba9f5c8c599b39 +80b7dbfdcaf40f0678012acc634eba44ea51181475180d9deb2050dc4f2de395289edd0223018c81057ec79b04b04c49 +89623d7f6cb17aa877af14de842c2d4ab7fd576d61ddd7518b5878620a01ded40b6010de0da3cdf31d837eecf30e9847 +a773bb024ae74dd24761f266d4fb27d6fd366a8634febe8235376b1ae9065c2fe12c769f1d0407867dfbe9f5272c352f +8455a561c3aaa6ba64c881a5e13921c592b3a02e968f4fb24a2243c36202795d0366d9cc1a24e916f84d6e158b7aeac7 +81d8bfc4b283cf702a40b87a2b96b275bdbf0def17e67d04842598610b67ea08c804d400c3e69fa09ea001eaf345b276 +b8f8f82cb11fea1c99467013d7e167ff03deb0c65a677fab76ded58826d1ba29aa7cf9fcd7763615735ea3ad38e28719 +89a6a04baf9cccc1db55179e1650b1a195dd91fb0aebc197a25143f0f393524d2589975e3fbfc2547126f0bced7fd6f2 +b81b2162df045390f04df07cbd0962e6b6ca94275a63edded58001a2f28b2ae2af2c7a6cba4ecd753869684e77e7e799 +a3757f722776e50de45c62d9c4a2ee0f5655a512344c4cbec542d8045332806568dd626a719ef21a4eb06792ca70f204 +8c5590df96ec22179a4e8786de41beb44f987a1dcc508eb341eecbc0b39236fdfad47f108f852e87179ccf4e10091e59 +87502f026ed4e10167419130b88c3737635c5b9074c364e1dd247cef5ef0fc064b4ae99b187e33301e438bbd2fe7d032 +af925a2165e980ced620ff12289129fe17670a90ae0f4db9d4b39bd887ccb1f5d2514ac9ecf910f6390a8fc66bd5be17 +857fca899828cf5c65d26e3e8a6e658542782fc72762b3b9c73514919f83259e0f849a9d4838b40dc905fe43024d0d23 +87ffebdbfb69a9e1007ebac4ffcb4090ff13705967b73937063719aa97908986effcb7262fdadc1ae0f95c3690e3245d +a9ff6c347ac6f4c6ab993b748802e96982eaf489dc69032269568412fc9a79e7c2850dfc991b28211b3522ee4454344b +a65b3159df4ec48bebb67cb3663cd744027ad98d970d620e05bf6c48f230fa45bf17527fe726fdf705419bb7a1bb913e +84b97b1e6408b6791831997b03cd91f027e7660fd492a93d95daafe61f02427371c0e237c75706412f442991dfdff989 +ab761c26527439b209af0ae6afccd9340bbed5fbe098734c3145b76c5d2cd7115d9227b2eb523882b7317fbb09180498 +a0479a8da06d7a69c0b0fee60df4e691c19c551f5e7da286dab430bfbcabf31726508e20d26ea48c53365a7f00a3ad34 +a732dfc9baa0f4f40b5756d2e8d8937742999623477458e0bc81431a7b633eefc6f53b3b7939fe0a020018549c954054 +901502436a1169ba51dc479a5abe7c8d84e0943b16bc3c6a627b49b92cd46263c0005bc324c67509edd693f28e612af1 +b627aee83474e7f84d1bab9b7f6b605e33b26297ac6bbf52d110d38ba10749032bd551641e73a383a303882367af429b +95108866745760baef4a46ef56f82da6de7e81c58b10126ebd2ba2cd13d339f91303bf2fb4dd104a6956aa3b13739503 +899ed2ade37236cec90056f3569bc50f984f2247792defafcceb49ad0ca5f6f8a2f06573705300e07f0de0c759289ff5 +a9f5eee196d608efe4bcef9bf71c646d27feb615e21252cf839a44a49fd89da8d26a758419e0085a05b1d59600e2dc42 +b36c6f68fed6e6c85f1f4a162485f24817f2843ec5cbee45a1ebfa367d44892e464949c6669f7972dc7167af08d55d25 +aaaede243a9a1b6162afbc8f571a52671a5a4519b4062e3f26777664e245ba873ed13b0492c5dbf0258c788c397a0e9e +972b4fb39c31cbe127bf9a32a5cc10d621ebdd9411df5e5da3d457f03b2ab2cd1f6372d8284a4a9400f0b06ecdbfd38e +8f6ca1e110e959a4b1d9a5ce5f212893cec21db40d64d5ac4d524f352d72198f923416a850bf845bc5a22a79c0ea2619 +a0f3c93b22134f66f04b2553a53b738644d1665ceb196b8494b315a4c28236fb492017e4a0de4224827c78e42f9908b7 +807fb5ee74f6c8735b0b5ca07e28506214fe4047dbeb00045d7c24f7849e98706aea79771241224939cb749cf1366c7d +915eb1ff034224c0b645442cdb7d669303fdc00ca464f91aaf0b6fde0b220a3a74ff0cb043c26c9f3a5667b3fdaa9420 +8fda6cef56ed33fefffa9e6ac8e6f76b1af379f89761945c63dd448801f7bb8ca970504a7105fac2f74f652ccff32327 +87380cffdcffb1d0820fa36b63cc081e72187f86d487315177d4d04da4533eb19a0e2ff6115ceab528887819c44a5164 +8cd89e03411a18e7f16f968b89fb500c36d47d229f6487b99e62403a980058db5925ce249206743333538adfad168330 +974451b1df33522ce7056de9f03e10c70bf302c44b0741a59df3d6877d53d61a7394dcee1dd46e013d7cb9d73419c092 +98c35ddf645940260c490f384a49496a7352bb8e3f686feed815b1d38f59ded17b1ad6e84a209e773ed08f7b8ff1e4c2 +963f386cf944bb9b2ddebb97171b64253ea0a2894ac40049bdd86cda392292315f3a3d490ca5d9628c890cfb669f0acb +8d507712152babd6d142ee682638da8495a6f3838136088df9424ef50d5ec28d815a198c9a4963610b22e49b4cdf95e9 +83d4bc6b0be87c8a4f1e9c53f257719de0c73d85b490a41f7420e777311640937320557ff2f1d9bafd1daaa54f932356 +82f5381c965b7a0718441131c4d13999f4cdce637698989a17ed97c8ea2e5bdb5d07719c5f7be8688edb081b23ede0f4 +a6ebecab0b72a49dfd01d69fa37a7f74d34fb1d4fef0aa10e3d6fceb9eccd671225c230af89f6eb514250e41a5f91f52 +846d185bdad6e11e604df7f753b7a08a28b643674221f0e750ebdb6b86ec584a29c869e131bca868972a507e61403f6a +85a98332292acb744bd1c0fd6fdcf1f889a78a2c9624d79413ffa194cc8dfa7821a4b60cde8081d4b5f71f51168dd67f +8f7d97c3b4597880d73200d074eb813d95432306e82dafc70b580b8e08cb8098b70f2d07b4b3ac6a4d77e92d57035031 +8185439c8751e595825d7053518cbe121f191846a38d4dbcb558c3f9d7a3104f3153401adaaaf27843bbe2edb504bfe3 +b3c00d8ece1518fca6b1215a139b0a0e26d9cba1b3a424f7ee59f30ce800a5db967279ed60958dd1f3ee69cf4dd1b204 +a2e6cb6978e883f9719c3c0d44cfe8de0cc6f644b98f98858433bea8bbe7b612c8aca5952fccce4f195f9d54f9722dc2 +99663087e3d5000abbec0fbda4e7342ec38846cc6a1505191fb3f1a337cb369455b7f8531a6eb8b0f7b2c4baf83cbe2b +ab0836c6377a4dbc7ca6a4d6cf021d4cd60013877314dd05f351706b128d4af6337711ed3443cb6ca976f40d74070a9a +87abfd5126152fd3bac3c56230579b489436755ea89e0566aa349490b36a5d7b85028e9fb0710907042bcde6a6f5d7e3 +974ba1033f75f60e0cf7c718a57ae1da3721cf9d0fb925714c46f027632bdd84cd9e6de4cf4d00bc55465b1c5ebb7384 +a607b49d73689ac64f25cec71221d30d53e781e1100d19a2114a21da6507a60166166369d860bd314acb226596525670 +a7c2b0b915d7beba94954f2aa7dd08ec075813661e2a3ecca5d28a0733e59583247fed9528eb28aba55b972cdbaf06eb +b8b3123e44128cc8efbe3270f2f94e50ca214a4294c71c3b851f8cbb70cb67fe9536cf07d04bf7fe380e5e3a29dd3c15 +a59a07e343b62ad6445a0859a32b58c21a593f9ddbfe52049650f59628c93715aa1f4e1f45b109321756d0eeec8a5429 +94f51f8a4ed18a6030d0aaa8899056744bd0e9dc9ac68f62b00355cddab11da5da16798db75f0bfbce0e5bdfe750c0b6 +97460a97ca1e1fa5ce243b81425edc0ec19b7448e93f0b55bc9785eedeeafe194a3c8b33a61a5c72990edf375f122777 +8fa859a089bc17d698a7ee381f37ce9beadf4e5b44fce5f6f29762bc04f96faff5d58c48c73631290325f05e9a1ecf49 +abdf38f3b20fc95eff31de5aa9ef1031abfa48f1305ee57e4d507594570401503476d3bcc493838fc24d6967a3082c7f +b8914bfb82815abb86da35c64d39ab838581bc0bf08967192697d9663877825f2b9d6fbdcf9b410463482b3731361aef +a8187f9d22b193a5f578999954d6ec9aa9b32338ccadb8a3e1ce5bad5ea361d69016e1cdfac44e9d6c54e49dd88561b9 +aac262cb7cba7fd62c14daa7b39677cabc1ef0947dd06dd89cac8570006a200f90d5f0353e84f5ff03179e3bebe14231 +a630ef5ece9733b8c46c0a2df14a0f37647a85e69c63148e79ffdcc145707053f9f9d305c3f1cf3c7915cb46d33abd07 +b102c237cb2e254588b6d53350dfda6901bd99493a3fbddb4121d45e0b475cf2663a40d7b9a75325eda83e4ba1e68cb3 +86a930dd1ddcc16d1dfa00aa292cb6c2607d42c367e470aa920964b7c17ab6232a7108d1c2c11fc40fb7496547d0bbf8 +a832fdc4500683e72a96cce61e62ac9ee812c37fe03527ad4cf893915ca1962cee80e72d4f82b20c8fc0b764376635a1 +88ad985f448dabb04f8808efd90f273f11f5e6d0468b5489a1a6a3d77de342992a73eb842d419034968d733f101ff683 +98a8538145f0d86f7fbf9a81c9140f6095c5bdd8960b1c6f3a1716428cd9cca1bf8322e6d0af24e6169abcf7df2b0ff6 +9048c6eba5e062519011e177e955a200b2c00b3a0b8615bdecdebc217559d41058d3315f6d05617be531ef0f6aef0e51 +833bf225ab6fc68cdcacf1ec1b50f9d05f5410e6cdcd8d56a3081dc2be8a8d07b81534d1ec93a25c2e270313dfb99e3b +a84bcd24c3da5e537e64a811b93c91bfc84d7729b9ead7f79078989a6eb76717d620c1fad17466a0519208651e92f5ff +b7cdd0a3fbd79aed93e1b5a44ca44a94e7af5ed911e4492f332e3a5ed146c7286bde01b52276a2fcc02780d2109874dd +8a19a09854e627cb95750d83c20c67442b66b35896a476358f993ba9ac114d32c59c1b3d0b8787ee3224cf3888b56c64 +a9abd5afb8659ee52ada8fa5d57e7dd355f0a7350276f6160bec5fbf70d5f99234dd179eb221c913e22a49ec6d267846 +8c13c4274c0d30d184e73eaf812200094bbbd57293780bdadbceb262e34dee5b453991e7f37c7333a654fc71c69d6445 +a4320d73296ff8176ce0127ca1921c450e2a9c06eff936681ebaffb5a0b05b17fded24e548454de89aca2dcf6d7a9de4 +b2b8b3e15c1f645f07783e5628aba614e60157889db41d8161d977606788842b67f83f361eae91815dc0abd84e09abd5 +ad26c3aa35ddfddc15719b8bb6c264aaec7065e88ac29ba820eb61f220fef451609a7bb037f3722d022e6c86e4f1dc88 +b8615bf43e13ae5d7b8dd903ce37190800cd490f441c09b22aa29d7a29ed2c0417b7a08ead417868f1de2589deaadd80 +8d3425e1482cd1e76750a76239d33c06b3554c3c3c87c15cb7ab58b1cee86a4c5c4178b44e23f36928365a1b484bde02 +806893a62e38c941a7dd6f249c83af16596f69877cc737d8f73f6b8cd93cbc01177a7a276b2b8c6b0e5f2ad864db5994 +86618f17fa4b0d65496b661bbb5ba3bc3a87129d30a4b7d4f515b904f4206ca5253a41f49fd52095861e5e065ec54f21 +9551915da1304051e55717f4c31db761dcdcf3a1366c89a4af800a9e99aca93a357bf928307f098e62b44a02cb689a46 +8f79c4ec0ec1146cb2a523b52fe33def90d7b5652a0cb9c2d1c8808a32293e00aec6969f5b1538e3a94cd1efa3937f86 +a0c03e329a707300081780f1e310671315b4c6a4cedcb29697aedfabb07a9d5df83f27b20e9c44cf6b16e39d9ded5b98 +86a7cfa7c8e7ce2c01dd0baec2139e97e8e090ad4e7b5f51518f83d564765003c65968f85481bbb97cb18f005ccc7d9f +a33811770c6dfda3f7f74e6ad0107a187fe622d61b444bbd84fd7ef6e03302e693b093df76f6ab39bb4e02afd84a575a +85480f5c10d4162a8e6702b5e04f801874d572a62a130be94b0c02b58c3c59bdcd48cd05f0a1c2839f88f06b6e3cd337 +8e181011564b17f7d787fe0e7f3c87f6b62da9083c54c74fd6c357a1f464c123c1d3d8ade3cf72475000b464b14e2be3 +8ee178937294b8c991337e0621ab37e9ffa4ca2bdb3284065c5e9c08aad6785d50cf156270ff9daf9a9127289710f55b +8bd1e8e2d37379d4b172f1aec96f2e41a6e1393158d7a3dbd9a95c8dd4f8e0b05336a42efc11a732e5f22b47fc5c271d +8f3da353cd487c13136a85677de8cedf306faae0edec733cf4f0046f82fa4639db4745b0095ff33a9766aba50de0cbcf +8d187c1e97638df0e4792b78e8c23967dac43d98ea268ca4aabea4e0fa06cb93183fd92d4c9df74118d7cc27bf54415e +a4c992f08c2f8bac0b74b3702fb0c75c9838d2ce90b28812019553d47613c14d8ce514d15443159d700b218c5a312c49 +a6fd1874034a34c3ea962a316c018d9493d2b3719bb0ec4edbc7c56b240802b2228ab49bee6f04c8a3e9f6f24a48c1c2 +b2efed8e799f8a15999020900dc2c58ece5a3641c90811b86a5198e593d7318b9d53b167818ccdfbe7df2414c9c34011 +995ff7de6181ddf95e3ead746089c6148da3508e4e7a2323c81785718b754d356789b902e7e78e2edc6b0cbd4ff22c78 +944073d24750a9068cbd020b834afc72d2dde87efac04482b3287b40678ad07588519a4176b10f2172a2c463d063a5cd +99db4b1bb76475a6fd75289986ef40367960279524378cc917525fb6ba02a145a218c1e9caeb99332332ab486a125ac0 +89fce4ecd420f8e477af4353b16faabb39e063f3f3c98fde2858b1f2d1ef6eed46f0975a7c08f233b97899bf60ccd60a +8c09a4f07a02b80654798bc63aada39fd638d3e3c4236ccd8a5ca280350c31e4a89e5f4c9aafb34116e71da18c1226b8 +85325cfa7ded346cc51a2894257eab56e7488dbff504f10f99f4cd2b630d913003761a50f175ed167e8073f1b6b63fb0 +b678b4fbec09a8cc794dcbca185f133578f29e354e99c05f6d07ac323be20aecb11f781d12898168e86f2e0f09aca15e +a249cfcbca4d9ba0a13b5f6aac72bf9b899adf582f9746bb2ad043742b28915607467eb794fca3704278f9136f7642be +9438e036c836a990c5e17af3d78367a75b23c37f807228362b4d13e3ddcb9e431348a7b552d09d11a2e9680704a4514f +925ab70450af28c21a488bfb5d38ac994f784cf249d7fd9ad251bb7fd897a23e23d2528308c03415074d43330dc37ef4 +a290563904d5a8c0058fc8330120365bdd2ba1fdbaef7a14bc65d4961bb4217acfaed11ab82669e359531f8bf589b8db +a7e07a7801b871fc9b981a71e195a3b4ba6b6313bc132b04796a125157e78fe5c11a3a46cf731a255ac2d78a4ae78cd0 +b26cd2501ee72718b0eebab6fb24d955a71f363f36e0f6dff0ab1d2d7836dab88474c0cef43a2cc32701fca7e82f7df3 +a1dc3b6c968f3de00f11275092290afab65b2200afbcfa8ddc70e751fa19dbbc300445d6d479a81bda3880729007e496 +a9bc213e28b630889476a095947d323b9ac6461dea726f2dc9084473ae8e196d66fb792a21905ad4ec52a6d757863e7d +b25d178df8c2df8051e7c888e9fa677fde5922e602a95e966db9e4a3d6b23ce043d7dc48a5b375c6b7c78e966893e8c3 +a1c8d88d72303692eaa7adf68ea41de4febec40cc14ae551bb4012afd786d7b6444a3196b5d9d5040655a3366d96b7cd +b22bd44f9235a47118a9bbe2ba5a2ba9ec62476061be2e8e57806c1a17a02f9a51403e849e2e589520b759abd0117683 +b8add766050c0d69fe81d8d9ea73e1ed05f0135d093ff01debd7247e42dbb86ad950aceb3b50b9af6cdc14ab443b238f +af2cf95f30ef478f018cf81d70d47d742120b09193d8bb77f0d41a5d2e1a80bfb467793d9e2471b4e0ad0cb2c3b42271 +8af5ef2107ad284e246bb56e20fef2a255954f72de791cbdfd3be09f825298d8466064f3c98a50496c7277af32b5c0bc +85dc19558572844c2849e729395a0c125096476388bd1b14fa7f54a7c38008fc93e578da3aac6a52ff1504d6ca82db05 +ae8c9b43c49572e2e166d704caf5b4b621a3b47827bb2a3bcd71cdc599bba90396fd9a405261b13e831bb5d44c0827d7 +a7ba7efede25f02e88f6f4cbf70643e76784a03d97e0fbd5d9437c2485283ad7ca3abb638a5f826cd9f6193e5dec0b6c +94a9d122f2f06ef709fd8016fd4b712d88052245a65a301f5f177ce22992f74ad05552b1f1af4e70d1eac62cef309752 +82d999b3e7cf563833b8bc028ff63a6b26eb357dfdb3fd5f10e33a1f80a9b2cfa7814d871b32a7ebfbaa09e753e37c02 +aec6edcde234df502a3268dd2c26f4a36a2e0db730afa83173f9c78fcb2b2f75510a02b80194327b792811caefda2725 +94c0bfa66c9f91d462e9194144fdd12d96f9bbe745737e73bab8130607ee6ea9d740e2cfcbbd00a195746edb6369ee61 +ab7573dab8c9d46d339e3f491cb2826cabe8b49f85f1ede78d845fc3995537d1b4ab85140b7d0238d9c24daf0e5e2a7e +87e8b16832843251fe952dadfd01d41890ed4bb4b8fa0254550d92c8cced44368225eca83a6c3ad47a7f81ff8a80c984 +9189d2d9a7c64791b19c0773ad4f0564ce6bea94aa275a917f78ad987f150fdb3e5e26e7fef9982ac184897ecc04683f +b3661bf19e2da41415396ae4dd051a9272e8a2580b06f1a1118f57b901fa237616a9f8075af1129af4eabfefedbe2f1c +af43c86661fb15daf5d910a4e06837225e100fb5680bd3e4b10f79a2144c6ec48b1f8d6e6b98e067d36609a5d038889a +82ac0c7acaa83ddc86c5b4249aae12f28155989c7c6b91e5137a4ce05113c6cbc16f6c44948b0efd8665362d3162f16a +8f268d1195ab465beeeb112cd7ffd5d5548559a8bc01261106d3555533fc1971081b25558d884d552df0db1cddda89d8 +8ef7caa5521f3e037586ce8ac872a4182ee20c7921c0065ed9986c047e3dda08294da1165f385d008b40d500f07d895f +8c2f98f6880550573fad46075d3eba26634b5b025ce25a0b4d6e0193352c8a1f0661064027a70fe8190b522405f9f4e3 +b7653f353564feb164f0f89ec7949da475b8dad4a4d396d252fc2a884f6932d027b7eb2dc4d280702c74569319ed701a +a026904f4066333befd9b87a8fad791d014096af60cdd668ef919c24dbe295ff31f7a790e1e721ba40cf5105abca67f4 +988f982004ada07a22dd345f2412a228d7a96b9cae2c487de42e392afe1e35c2655f829ce07a14629148ce7079a1f142 +9616add009067ed135295fb74d5b223b006b312bf14663e547a0d306694ff3a8a7bb9cfc466986707192a26c0bce599f +ad4c425de9855f6968a17ee9ae5b15e0a5b596411388cf976df62ecc6c847a6e2ddb2cea792a5f6e9113c2445dba3e5c +b698ac9d86afa3dc69ff8375061f88e3b0cff92ff6dfe747cebaf142e813c011851e7a2830c10993b715e7fd594604a9 +a386fa189847bb3b798efca917461e38ead61a08b101948def0f82cd258b945ed4d45b53774b400af500670149e601b7 +905c95abda2c68a6559d8a39b6db081c68cef1e1b4be63498004e1b2f408409be9350b5b5d86a30fd443e2b3e445640a +9116dade969e7ce8954afcdd43e5cab64dc15f6c1b8da9d2d69de3f02ba79e6c4f6c7f54d6bf586d30256ae405cd1e41 +a3084d173eacd08c9b5084a196719b57e47a0179826fda73466758235d7ecdb87cbcf097bd6b510517d163a85a7c7edd +85bb00415ad3c9be99ff9ba83672cc59fdd24356b661ab93713a3c8eab34e125d8867f628a3c3891b8dc056e69cd0e83 +8d58541f9f39ed2ee4478acce5d58d124031338ec11b0d55551f00a5a9a6351faa903a5d7c132dc5e4bb026e9cbd18e4 +a622adf72dc250e54f672e14e128c700166168dbe0474cecb340da175346e89917c400677b1bc1c11fcc4cc26591d9db +b3f865014754b688ca8372e8448114fff87bf3ca99856ab9168894d0c4679782c1ced703f5b74e851b370630f5e6ee86 +a7e490b2c40c2446fcd91861c020da9742c326a81180e38110558bb5d9f2341f1c1885e79b364e6419023d1cbdc47380 +b3748d472b1062e54572badbb8e87ac36534407f74932e7fc5b8392d008e8e89758f1671d1e4d30ab0fa40551b13bb5e +89898a5c5ec4313aabc607b0049fd1ebad0e0c074920cf503c9275b564d91916c2c446d3096491c950b7af3ac5e4b0ed +8eb8c83fef2c9dd30ea44e286e9599ec5c20aba983f702e5438afe2e5b921884327ad8d1566c72395587efac79ca7d56 +b92479599e806516ce21fb0bd422a1d1d925335ebe2b4a0a7e044dd275f30985a72b97292477053ac5f00e081430da80 +a34ae450a324fe8a3c25a4d653a654f9580ed56bbea213b8096987bbad0f5701d809a17076435e18017fea4d69f414bc +81381afe6433d62faf62ea488f39675e0091835892ecc238e02acf1662669c6d3962a71a3db652f6fe3bc5f42a0e5dc5 +a430d475bf8580c59111103316fe1aa79c523ea12f1d47a976bbfae76894717c20220e31cf259f08e84a693da6688d70 +b842814c359754ece614deb7d184d679d05d16f18a14b288a401cef5dad2cf0d5ee90bad487b80923fc5573779d4e4e8 +971d9a2627ff2a6d0dcf2af3d895dfbafca28b1c09610c466e4e2bff2746f8369de7f40d65b70aed135fe1d72564aa88 +8f4ce1c59e22b1ce7a0664caaa7e53735b154cfba8d2c5cc4159f2385843de82ab58ed901be876c6f7fce69cb4130950 +86cc9dc321b6264297987000d344fa297ef45bcc2a4df04e458fe2d907ad304c0ea2318e32c3179af639a9a56f3263cf +8229e0876dfe8f665c3fb19b250bd89d40f039bbf1b331468b403655be7be2e104c2fd07b9983580c742d5462ca39a43 +99299d73066e8eb128f698e56a9f8506dfe4bd014931e86b6b487d6195d2198c6c5bf15cccb40ccf1f8ddb57e9da44a2 +a3a3be37ac554c574b393b2f33d0a32a116c1a7cfeaf88c54299a4da2267149a5ecca71f94e6c0ef6e2f472b802f5189 +a91700d1a00387502cdba98c90f75fbc4066fefe7cc221c8f0e660994c936badd7d2695893fde2260c8c11d5bdcdd951 +8e03cae725b7f9562c5c5ab6361644b976a68bada3d7ca508abca8dfc80a469975689af1fba1abcf21bc2a190dab397d +b01461ad23b2a8fa8a6d241e1675855d23bc977dbf4714add8c4b4b7469ccf2375cec20e80cedfe49361d1a30414ac5b +a2673bf9bc621e3892c3d7dd4f1a9497f369add8cbaa3472409f4f86bd21ac67cfac357604828adfee6ada1835365029 +a042dff4bf0dfc33c178ba1b335e798e6308915128de91b12e5dbbab7c4ac8d60a01f6aea028c3a6d87b9b01e4e74c01 +86339e8a75293e4b3ae66b5630d375736b6e6b6b05c5cda5e73fbf7b2f2bd34c18a1d6cefede08625ce3046e77905cb8 +af2ebe1b7d073d03e3d98bc61af83bf26f7a8c130fd607aa92b75db22d14d016481b8aa231e2c9757695f55b7224a27f +a00ee882c9685e978041fd74a2c465f06e2a42ffd3db659053519925be5b454d6f401e3c12c746e49d910e4c5c9c5e8c +978a781c0e4e264e0dad57e438f1097d447d891a1e2aa0d5928f79a9d5c3faae6f258bc94fdc530b7b2fa6a9932bb193 +aa4b7ce2e0c2c9e9655bf21e3e5651c8503bce27483017b0bf476be743ba06db10228b3a4c721219c0779747f11ca282 +b003d1c459dacbcf1a715551311e45d7dbca83a185a65748ac74d1800bbeaba37765d9f5a1a221805c571910b34ebca8 +95b6e531b38648049f0d19de09b881baa1f7ea3b2130816b006ad5703901a05da57467d1a3d9d2e7c73fb3f2e409363c +a6cf9c06593432d8eba23a4f131bb7f72b9bd51ab6b4b772a749fe03ed72b5ced835a349c6d9920dba2a39669cb7c684 +aa3d59f6e2e96fbb66195bc58c8704e139fa76cd15e4d61035470bd6e305db9f98bcbf61ac1b95e95b69ba330454c1b3 +b57f97959c208361de6d7e86dff2b873068adb0f158066e646f42ae90e650079798f165b5cd713141cd3a2a90a961d9a +a76ee8ed9052f6a7a8c69774bb2597be182942f08115baba03bf8faaeaee526feba86120039fe8ca7b9354c3b6e0a8e6 +95689d78c867724823f564627d22d25010f278674c6d2d0cdb10329169a47580818995d1d727ce46c38a1e47943ebb89 +ab676d2256c6288a88e044b3d9ffd43eb9d5aaee00e8fc60ac921395fb835044c71a26ca948e557fed770f52d711e057 +96351c72785c32e5d004b6f4a1259fb8153d631f0c93fed172f18e8ba438fbc5585c1618deeabd0d6d0b82173c2e6170 +93dd8d3db576418e22536eba45ab7f56967c6c97c64260d6cddf38fb19c88f2ec5cd0e0156f50e70855eee8a2b879ffd +ad6ff16f40f6de3d7a737f8e6cebd8416920c4ff89dbdcd75eabab414af9a6087f83ceb9aff7680aa86bff98bd09c8cc +84de53b11671abc9c38710e19540c5c403817562aeb22a88404cdaff792c1180f717dbdfe8f54940c062c4d032897429 +872231b9efa1cdd447b312099a5c164c560440a9441d904e70f5abfc3b2a0d16be9a01aca5e0a2599a61e19407587e3d +88f44ac27094a2aa14e9dc40b099ee6d68f97385950f303969d889ee93d4635e34dff9239103bdf66a4b7cbba3e7eb7a +a59afebadf0260e832f6f44468443562f53fbaf7bcb5e46e1462d3f328ac437ce56edbca617659ac9883f9e13261fad7 +b1990e42743a88de4deeacfd55fafeab3bc380cb95de43ed623d021a4f2353530bcab9594389c1844b1c5ea6634c4555 +85051e841149a10e83f56764e042182208591396d0ce78c762c4a413e6836906df67f38c69793e158d64fef111407ba3 +9778172bbd9b1f2ec6bbdd61829d7b39a7df494a818e31c654bf7f6a30139899c4822c1bf418dd4f923243067759ce63 +9355005b4878c87804fc966e7d24f3e4b02bed35b4a77369d01f25a3dcbff7621b08306b1ac85b76fe7b4a3eb5f839b1 +8f9dc6a54fac052e236f8f0e1f571ac4b5308a43acbe4cc8183bce26262ddaf7994e41cf3034a4cbeca2c505a151e3b1 +8cc59c17307111723fe313046a09e0e32ea0cce62c13814ab7c6408c142d6a0311d801be4af53fc9240523f12045f9ef +8e6057975ed40a1932e47dd3ac778f72ee2a868d8540271301b1aa6858de1a5450f596466494a3e0488be4fbeb41c840 +812145efbd6559ae13325d56a15940ca4253b17e72a9728986b563bb5acc13ec86453796506ac1a8f12bd6f9e4a288c3 +911da0a6d6489eb3dab2ec4a16e36127e8a291ae68a6c2c9de33e97f3a9b1f00da57a94e270a0de79ecc5ecb45d19e83 +b72ea85973f4b2a7e6e71962b0502024e979a73c18a9111130e158541fa47bbaaf53940c8f846913a517dc69982ba9e1 +a7a56ad1dbdc55f177a7ad1d0af78447dc2673291e34e8ab74b26e2e2e7d8c5fe5dc89e7ef60f04a9508847b5b3a8188 +b52503f6e5411db5d1e70f5fb72ccd6463fa0f197b3e51ca79c7b5a8ab2e894f0030476ada72534fa4eb4e06c3880f90 +b51c7957a3d18c4e38f6358f2237b3904618d58b1de5dec53387d25a63772e675a5b714ad35a38185409931157d4b529 +b86b4266e719d29c043d7ec091547aa6f65bbf2d8d831d1515957c5c06513b72aa82113e9645ad38a7bc3f5383504fa6 +b95b547357e6601667b0f5f61f261800a44c2879cf94e879def6a105b1ad2bbf1795c3b98a90d588388e81789bd02681 +a58fd4c5ae4673fa350da6777e13313d5d37ed1dafeeb8f4f171549765b84c895875d9d3ae6a9741f3d51006ef81d962 +9398dc348d078a604aadc154e6eef2c0be1a93bb93ba7fe8976edc2840a3a318941338cc4d5f743310e539d9b46613d2 +902c9f0095014c4a2f0dccaaab543debba6f4cc82c345a10aaf4e72511725dbed7a34cd393a5f4e48a3e5142b7be84ed +a7c0447849bb44d04a0393a680f6cd390093484a79a147dd238f5d878030d1c26646d88211108e59fe08b58ad20c6fbd +80db045535d6e67a422519f5c89699e37098449d249698a7cc173a26ccd06f60238ae6cc7242eb780a340705c906790c +8e52b451a299f30124505de2e74d5341e1b5597bdd13301cc39b05536c96e4380e7f1b5c7ef076f5b3005a868657f17c +824499e89701036037571761e977654d2760b8ce21f184f2879fda55d3cda1e7a95306b8abacf1caa79d3cc075b9d27f +9049b956b77f8453d2070607610b79db795588c0cec12943a0f5fe76f358dea81e4f57a4692112afda0e2c05c142b26f +81911647d818a4b5f4990bfd4bc13bf7be7b0059afcf1b6839333e8569cdb0172fd2945410d88879349f677abaed5eb3 +ad4048f19b8194ed45b6317d9492b71a89a66928353072659f5ce6c816d8f21e69b9d1817d793effe49ca1874daa1096 +8d22f7b2ddb31458661abd34b65819a374a1f68c01fc6c9887edeba8b80c65bceadb8f57a3eb686374004b836261ef67 +92637280c259bc6842884db3d6e32602a62252811ae9b019b3c1df664e8809ffe86db88cfdeb8af9f46435c9ee790267 +a2f416379e52e3f5edc21641ea73dc76c99f7e29ea75b487e18bd233856f4c0183429f378d2bfc6cd736d29d6cadfa49 +882cb6b76dbdc188615dcf1a8439eba05ffca637dd25197508156e03c930b17b9fed2938506fdd7b77567cb488f96222 +b68b621bb198a763fb0634eddb93ed4b5156e59b96c88ca2246fd1aea3e6b77ed651e112ac41b30cd361fadc011d385e +a3cb22f6b675a29b2d1f827cacd30df14d463c93c3502ef965166f20d046af7f9ab7b2586a9c64f4eae4fad2d808a164 +8302d9ce4403f48ca217079762ce42cee8bc30168686bb8d3a945fbd5acd53b39f028dce757b825eb63af2d5ae41169d +b2eef1fbd1a176f1f4cd10f2988c7329abe4eb16c7405099fb92baa724ab397bc98734ef7d4b24c0f53dd90f57520d04 +a1bbef0bd684a3f0364a66bde9b29326bac7aa3dde4caed67f14fb84fed3de45c55e406702f1495a3e2864d4ee975030 +976acdb0efb73e3a3b65633197692dedc2adaed674291ae3df76b827fc866d214e9cac9ca46baefc4405ff13f953d936 +b9fbf71cc7b6690f601f0b1c74a19b7d14254183a2daaafec7dc3830cba5ae173d854bbfebeca985d1d908abe5ef0cda +90591d7b483598c94e38969c4dbb92710a1a894bcf147807f1bcbd8aa3ac210b9f2be65519aa829f8e1ccdc83ad9b8cf +a30568577c91866b9c40f0719d46b7b3b2e0b4a95e56196ac80898a2d89cc67880e1229933f2cd28ee3286f8d03414d7 +97589a88c3850556b359ec5e891f0937f922a751ac7c95949d3bbc7058c172c387611c0f4cb06351ef02e5178b3dd9e4 +98e7bbe27a1711f4545df742f17e3233fbcc63659d7419e1ca633f104cb02a32c84f2fac23ca2b84145c2672f68077ab +a7ddb91636e4506d8b7e92aa9f4720491bb71a72dadc47c7f4410e15f93e43d07d2b371951a0e6a18d1bd087aa96a5c4 +a7c006692227a06db40bceac3d5b1daae60b5692dd9b54772bedb5fea0bcc91cbcdb530cac31900ffc70c5b3ffadc969 +8d3ec6032778420dfa8be52066ba0e623467df33e4e1901dbadd586c5d750f4ccde499b5197e26b9ea43931214060f69 +8d9a8410518ea64f89df319bfd1fc97a0971cdb9ad9b11d1f8fe834042ea7f8dce4db56eeaf179ff8dda93b6db93e5ce +a3c533e9b3aa04df20b9ff635cb1154ce303e045278fcf3f10f609064a5445552a1f93989c52ce852fd0bbd6e2b6c22e +81934f3a7f8c1ae60ec6e4f212986bcc316118c760a74155d06ce0a8c00a9b9669ec4e143ca214e1b995e41271774fd9 +ab8e2d01a71192093ef8fafa7485e795567cc9db95a93fb7cc4cf63a391ef89af5e2bfad4b827fffe02b89271300407f +83064a1eaa937a84e392226f1a60b7cfad4efaa802f66de5df7498962f7b2649924f63cd9962d47906380b97b9fe80e1 +b4f5e64a15c6672e4b55417ee5dc292dcf93d7ea99965a888b1cc4f5474a11e5b6520eacbcf066840b343f4ceeb6bf33 +a63d278b842456ef15c278b37a6ea0f27c7b3ffffefca77c7a66d2ea06c33c4631eb242bbb064d730e70a8262a7b848a +83a41a83dbcdf0d22dc049de082296204e848c453c5ab1ba75aa4067984e053acf6f8b6909a2e1f0009ed051a828a73b +819485b036b7958508f15f3c19436da069cbe635b0318ebe8c014cf1ef9ab2df038c81161b7027475bcfa6fff8dd9faf +aa40e38172806e1e045e167f3d1677ef12d5dcdc89b43639a170f68054bd196c4fae34c675c1644d198907a03f76ba57 +969bae484883a9ed1fbed53b26b3d4ee4b0e39a6c93ece5b3a49daa01444a1c25727dabe62518546f36b047b311b177c +80a9e73a65da99664988b238096a090d313a0ee8e4235bc102fa79bb337b51bb08c4507814eb5baec22103ec512eaab0 +86604379aec5bddda6cbe3ef99c0ac3a3c285b0b1a15b50451c7242cd42ae6b6c8acb717dcca7917838432df93a28502 +a23407ee02a495bed06aa7e15f94cfb05c83e6d6fba64456a9bbabfa76b2b68c5c47de00ba169e710681f6a29bb41a22 +98cff5ecc73b366c6a01b34ac9066cb34f7eeaf4f38a5429bad2d07e84a237047e2a065c7e8a0a6581017dadb4695deb +8de9f68a938f441f3b7ab84bb1f473c5f9e5c9e139e42b7ccee1d254bd57d0e99c2ccda0f3198f1fc5737f6023dd204e +b0ce48d815c2768fb472a315cad86aa033d0e9ca506f146656e2941829e0acb735590b4fbc713c2d18d3676db0a954ac +82f485cdefd5642a6af58ac6817991c49fac9c10ace60f90b27f1788cc026c2fe8afc83cf499b3444118f9f0103598a8 +82c24550ed512a0d53fc56f64cc36b553823ae8766d75d772dacf038c460f16f108f87a39ceef7c66389790f799dbab3 +859ffcf1fe9166388316149b9acc35694c0ea534d43f09dae9b86f4aa00a23b27144dda6a352e74b9516e8c8d6fc809c +b8f7f353eec45da77fb27742405e5ad08d95ec0f5b6842025be9def3d9892f85eb5dd0921b41e6eff373618dba215bca +8ccca4436f9017e426229290f5cd05eac3f16571a4713141a7461acfe8ae99cd5a95bf5b6df129148693c533966145da +a2c67ecc19c0178b2994846fea4c34c327a5d786ac4b09d1d13549d5be5996d8a89021d63d65cb814923388f47cc3a03 +aa0ff87d676b418ec08f5cbf577ac7e744d1d0e9ebd14615b550eb86931eafd2a36d4732cc5d6fab1713fd7ab2f6f7c0 +8aef4730bb65e44efd6bb9441c0ae897363a2f3054867590a2c2ecf4f0224e578c7a67f10b40f8453d9f492ac15a9b2d +86a187e13d8fba5addcfdd5b0410cedd352016c930f913addd769ee09faa6be5ca3e4b1bdb417a965c643a99bd92be42 +a0a4e9632a7a094b14b29b78cd9c894218cdf6783e61671e0203865dc2a835350f465fbaf86168f28af7c478ca17bc89 +a8c7b02d8deff2cd657d8447689a9c5e2cd74ef57c1314ac4d69084ac24a7471954d9ff43fe0907d875dcb65fd0d3ce5 +97ded38760aa7be6b6960b5b50e83b618fe413cbf2bcc1da64c05140bcc32f5e0e709cd05bf8007949953fac5716bad9 +b0d293835a24d64c2ae48ce26e550b71a8c94a0883103757fb6b07e30747f1a871707d23389ba2b2065fa6bafe220095 +8f9e291bf849feaa575592e28e3c8d4b7283f733d41827262367ea1c40f298c7bcc16505255a906b62bf15d9f1ba85fb +998f4e2d12708b4fd85a61597ca2eddd750f73c9e0c9b3cf0825d8f8e01f1628fd19797dcaed3b16dc50331fc6b8b821 +b30d1f8c115d0e63bf48f595dd10908416774c78b3bbb3194192995154d80ea042d2e94d858de5f8aa0261b093c401fd +b5d9c75bb41f964cbff3f00e96d9f1480c91df8913f139f0d385d27a19f57a820f838eb728e46823cbff00e21c660996 +a6edec90b5d25350e2f5f0518777634f9e661ec9d30674cf5b156c4801746d62517751d90074830ac0f4b09911c262f1 +82f98da1264b6b75b8fbeb6a4d96d6a05b25c24db0d57ba3a38efe3a82d0d4e331b9fc4237d6494ccfe4727206457519 +b89511843453cf4ecd24669572d6371b1e529c8e284300c43e0d5bb6b3aaf35aeb634b3cb5c0a2868f0d5e959c1d0772 +a82bf065676583e5c1d3b81987aaae5542f522ba39538263a944bb33ea5b514c649344a96c0205a3b197a3f930fcda6c +a37b47ea527b7e06c460776aa662d9a49ff4149d3993f1a974b0dd165f7171770d189b0e2ea54fd5fccb6a14b116e68a +a1017677f97dda818274d47556d09d0e4ccacb23a252f82a6cfe78c630ad46fb9806307445a59fb61262182de3a2b29c +b01e9fcac239ba270e6877b79273ddd768bf8a51d2ed8a051b1c11e18eff3de5920e2fcbfbd26f06d381eddd3b1f1e1b +82fcd53d803b1c8e4ed76adc339b7f3a5962d37042b9683aabac7513ac68775d4a566a9460183926a6a95dbe7d551a1f +a763e78995d55cd21cdb7ef75d9642d6e1c72453945e346ab6690c20a4e1eeec61bb848ef830ae4b56182535e3c71d8f +b769f4db602251d4b0a1186782799bdcef66de33c110999a5775c50b349666ffd83d4c89714c4e376f2efe021a5cfdb2 +a59cbd1b785efcfa6e83fc3b1d8cf638820bc0c119726b5368f3fba9dce8e3414204fb1f1a88f6c1ff52e87961252f97 +95c8c458fd01aa23ecf120481a9c6332ebec2e8bb70a308d0576926a858457021c277958cf79017ddd86a56cacc2d7db +82eb41390800287ae56e77f2e87709de5b871c8bdb67c10a80fc65f3acb9f7c29e8fa43047436e8933f27449ea61d94d +b3ec25e3545eb83aed2a1f3558d1a31c7edde4be145ecc13b33802654b77dc049b4f0065069dd9047b051e52ab11dcdd +b78a0c715738f56f0dc459ab99e252e3b579b208142836b3c416b704ca1de640ca082f29ebbcee648c8c127df06f6b1e +a4083149432eaaf9520188ebf4607d09cf664acd1f471d4fb654476e77a9eaae2251424ffda78d09b6cb880df35c1219 +8c52857d68d6e9672df3db2df2dbf46b516a21a0e8a18eec09a6ae13c1ef8f369d03233320dd1c2c0bbe00abfc1ea18b +8c856089488803066bff3f8d8e09afb9baf20cecc33c8823c1c0836c3d45498c3de37e87c016b705207f60d2b00f8609 +831a3df39be959047b2aead06b4dcd3012d7b29417f642b83c9e8ce8de24a3dbbd29c6fdf55e2db3f7ea04636c94e403 +aed84d009f66544addabe404bf6d65af7779ce140dc561ff0c86a4078557b96b2053b7b8a43432ffb18cd814f143b9da +93282e4d72b0aa85212a77b336007d8ba071eea17492da19860f1ad16c1ea8867ccc27ef5c37c74b052465cc11ea4f52 +a7b78b8c8d057194e8d68767f1488363f77c77bddd56c3da2bc70b6354c7aa76247c86d51f7371aa38a4aa7f7e3c0bb7 +b1c77283d01dcd1bde649b5b044eac26befc98ff57cbee379fb5b8e420134a88f2fc7f0bf04d15e1fbd45d29e7590fe6 +a4aa8de70330a73b2c6458f20a1067eed4b3474829b36970a8df125d53bbdda4f4a2c60063b7cccb0c80fc155527652f +948a6c79ba1b8ad7e0bed2fae2f0481c4e41b4d9bbdd9b58164e28e9065700e83f210c8d5351d0212e0b0b68b345b3a5 +86a48c31dcbbf7b082c92d28e1f613a2378a910677d7db3a349dc089e4a1e24b12eee8e8206777a3a8c64748840b7387 +976adb1af21e0fc34148917cf43d933d7bfd3fd12ed6c37039dcd5a4520e3c6cf5868539ba5bf082326430deb8a4458d +b93e1a4476f2c51864bb4037e7145f0635eb2827ab91732b98d49b6c07f6ac443111aa1f1da76d1888665cb897c3834e +8afd46fb23bf869999fa19784b18a432a1f252d09506b8dbb756af900518d3f5f244989b3d7c823d9029218c655d3dc6 +83f1e59e3abeed18cdc632921672673f1cb6e330326e11c4e600e13e0d5bc11bdc970ae12952e15103a706fe720bf4d6 +90ce4cc660714b0b673d48010641c09c00fc92a2c596208f65c46073d7f349dd8e6e077ba7dcef9403084971c3295b76 +8b09b0f431a7c796561ecf1549b85048564de428dac0474522e9558b6065fede231886bc108539c104ce88ebd9b5d1b0 +85d6e742e2fb16a7b0ba0df64bc2c0dbff9549be691f46a6669bca05e89c884af16822b85faefefb604ec48c8705a309 +a87989ee231e468a712c66513746fcf03c14f103aadca0eac28e9732487deb56d7532e407953ab87a4bf8961588ef7b0 +b00da10efe1c29ee03c9d37d5918e391ae30e48304e294696b81b434f65cf8c8b95b9d1758c64c25e534d045ba28696f +91c0e1fb49afe46c7056400baa06dbb5f6e479db78ee37e2d76c1f4e88994357e257b83b78624c4ef6091a6c0eb8254d +883fb797c498297ccbf9411a3e727c3614af4eccde41619b773dc7f3259950835ee79453debf178e11dec4d3ada687a0 +a14703347e44eb5059070b2759297fcfcfc60e6893c0373eea069388eba3950aa06f1c57cd2c30984a2d6f9e9c92c79e +afebc7585b304ceba9a769634adff35940e89cd32682c78002822aab25eec3edc29342b7f5a42a56a1fec67821172ad5 +aea3ff3822d09dba1425084ca95fd359718d856f6c133c5fabe2b2eed8303b6e0ba0d8698b48b93136a673baac174fd9 +af2456a09aa777d9e67aa6c7c49a1845ea5cdda2e39f4c935c34a5f8280d69d4eec570446998cbbe31ede69a91e90b06 +82cada19fed16b891ef3442bafd49e1f07c00c2f57b2492dd4ee36af2bd6fd877d6cb41188a4d6ce9ec8d48e8133d697 +82a21034c832287f616619a37c122cee265cc34ae75e881fcaea4ea7f689f3c2bc8150bbf7dbcfd123522bfb7f7b1d68 +86877217105f5d0ec3eeff0289fc2a70d505c9fdf7862e8159553ef60908fb1a27bdaf899381356a4ef4649072a9796c +82b196e49c6e861089a427c0b4671d464e9d15555ffb90954cd0d630d7ae02eb3d98ceb529d00719c2526cd96481355a +a29b41d0d43d26ce76d4358e0db2b77df11f56e389f3b084d8af70a636218bd3ac86b36a9fe46ec9058c26a490f887f7 +a4311c4c20c4d7dd943765099c50f2fd423e203ccfe98ff00087d205467a7873762510cac5fdce7a308913ed07991ed7 +b1f040fc5cc51550cb2c25cf1fd418ecdd961635a11f365515f0cb4ffb31da71f48128c233e9cc7c0cf3978d757ec84e +a9ebae46f86d3bd543c5f207ed0d1aed94b8375dc991161d7a271f01592912072e083e2daf30c146430894e37325a1b9 +826418c8e17ad902b5fe88736323a47e0ca7a44bce4cbe27846ec8fe81de1e8942455dda6d30e192cdcc73e11df31256 +85199db563427c5edcbac21f3d39fec2357be91fb571982ddcdc4646b446ad5ced84410de008cb47b3477ee0d532daf8 +b7eed9cd400b2ca12bf1d9ae008214b8561fb09c8ad9ff959e626ffde00fee5ff2f5b6612e231f2a1a9b1646fcc575e3 +8b40bf12501dcbac78f5a314941326bfcddf7907c83d8d887d0bb149207f85d80cd4dfbd7935439ea7b14ea39a3fded7 +83e3041af302485399ba6cd5120e17af61043977083887e8d26b15feec4a6b11171ac5c06e6ad0971d4b58a81ff12af3 +8f5b9a0eecc589dbf8c35a65d5e996a659277ef6ea509739c0cb7b3e2da9895e8c8012de662e5b23c5fa85d4a8f48904 +835d71ed5e919d89d8e6455f234f3ff215462c4e3720c371ac8c75e83b19dfe3ae15a81547e4dc1138e5f5997f413cc9 +8b7d2e4614716b1db18e9370176ea483e6abe8acdcc3dcdf5fb1f4d22ca55d652feebdccc171c6de38398d9f7bfdec7a +93eace72036fe57d019676a02acf3d224cf376f166658c1bf705db4f24295881d477d6fdd7916efcfceff8c7a063deda +b1ac460b3d516879a84bc886c54f020a9d799e7c49af3e4d7de5bf0d2793c852254c5d8fe5616147e6659512e5ccb012 +acd0947a35cb167a48bcd9667620464b54ac0e78f9316b4aa92dcaab5422d7a732087e52e1c827faa847c6b2fe6e7766 +94ac33d21c3d12ff762d32557860e911cd94d666609ddcc42161b9c16f28d24a526e8b10bb03137257a92cec25ae637d +832e02058b6b994eadd8702921486241f9a19e68ed1406dad545e000a491ae510f525ccf9d10a4bba91c68f2c53a0f58 +9471035d14f78ff8f463b9901dd476b587bb07225c351161915c2e9c6114c3c78a501379ab6fb4eb03194c457cbd22bf +ab64593e034c6241d357fcbc32d8ea5593445a5e7c24cac81ad12bd2ef01843d477a36dc1ba21dbe63b440750d72096a +9850f3b30045e927ad3ec4123a32ed2eb4c911f572b6abb79121873f91016f0d80268de8b12e2093a4904f6e6cab7642 +987212c36b4722fe2e54fa30c52b1e54474439f9f35ca6ad33c5130cd305b8b54b532dd80ffd2c274105f20ce6d79f6e +8b4d0c6abcb239b5ed47bef63bc17efe558a27462c8208fa652b056e9eae9665787cd1aee34fbb55beb045c8bfdb882b +a9f3483c6fee2fe41312d89dd4355d5b2193ac413258993805c5cbbf0a59221f879386d3e7a28e73014f10e65dd503d9 +a2225da3119b9b7c83d514b9f3aeb9a6d9e32d9cbf9309cbb971fd53c4b2c001d10d880a8ad8a7c281b21d85ceca0b7c +a050be52e54e676c151f7a54453bbb707232f849beab4f3bf504b4d620f59ed214409d7c2bd3000f3ff13184ccda1c35 +adbccf681e15b3edb6455a68d292b0a1d0f5a4cb135613f5e6db9943f02181341d5755875db6ee474e19ace1c0634a28 +8b6eff675632a6fad0111ec72aacc61c7387380eb87933fd1d098856387d418bd38e77d897e65d6fe35951d0627c550b +aabe2328ddf90989b15e409b91ef055cb02757d34987849ae6d60bef2c902bf8251ed21ab30acf39e500d1d511e90845 +92ba4eb1f796bc3d8b03515f65c045b66e2734c2da3fc507fdd9d6b5d1e19ab3893726816a32141db7a31099ca817d96 +8a98b3cf353138a1810beb60e946183803ef1d39ac4ea92f5a1e03060d35a4774a6e52b14ead54f6794d5f4022b8685c +909f8a5c13ec4a59b649ed3bee9f5d13b21d7f3e2636fd2bb3413c0646573fdf9243d63083356f12f5147545339fcd55 +9359d914d1267633141328ed0790d81c695fea3ddd2d406c0df3d81d0c64931cf316fe4d92f4353c99ff63e2aefc4e34 +b88302031681b54415fe8fbfa161c032ea345c6af63d2fb8ad97615103fd4d4281c5a9cae5b0794c4657b97571a81d3b +992c80192a519038082446b1fb947323005b275e25f2c14c33cc7269e0ec038581cc43705894f94bad62ae33a8b7f965 +a78253e3e3eece124bef84a0a8807ce76573509f6861d0b6f70d0aa35a30a123a9da5e01e84969708c40b0669eb70aa6 +8d5724de45270ca91c94792e8584e676547d7ac1ac816a6bb9982ee854eb5df071d20545cdfd3771cd40f90e5ba04c8e +825a6f586726c68d45f00ad0f5a4436523317939a47713f78fd4fe81cd74236fdac1b04ecd97c2d0267d6f4981d7beb1 +93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8 +b5bfd7dd8cdeb128843bc287230af38926187075cbfbefa81009a2ce615ac53d2914e5870cb452d2afaaab24f3499f72185cbfee53492714734429b7b38608e23926c911cceceac9a36851477ba4c60b087041de621000edc98edada20c1def2 +b5337ba0ce5d37224290916e268e2060e5c14f3f9fc9e1ec3af5a958e7a0303122500ce18f1a4640bf66525bd10e763501fe986d86649d8d45143c08c3209db3411802c226e9fe9a55716ac4a0c14f9dcef9e70b2bb309553880dc5025eab3cc +b3c1dcdc1f62046c786f0b82242ef283e7ed8f5626f72542aa2c7a40f14d9094dd1ebdbd7457ffdcdac45fd7da7e16c51200b06d791e5e43e257e45efdf0bd5b06cd2333beca2a3a84354eb48662d83aef5ecf4e67658c851c10b13d8d87c874 +954d91c7688983382609fca9e211e461f488a5971fd4e40d7e2892037268eacdfd495cfa0a7ed6eb0eb11ac3ae6f651716757e7526abe1e06c64649d80996fd3105c20c4c94bc2b22d97045356fe9d791f21ea6428ac48db6f9e68e30d875280 +88a6b6bb26c51cf9812260795523973bb90ce80f6820b6c9048ab366f0fb96e48437a7f7cb62aedf64b11eb4dfefebb0147608793133d32003cb1f2dc47b13b5ff45f1bb1b2408ea45770a08dbfaec60961acb8119c47b139a13b8641e2c9487 +85cd7be9728bd925d12f47fb04b32d9fad7cab88788b559f053e69ca18e463113ecc8bbb6dbfb024835f901b3a957d3108d6770fb26d4c8be0a9a619f6e3a4bf15cbfd48e61593490885f6cee30e4300c5f9cf5e1c08e60a2d5b023ee94fcad0 +80477dba360f04399821a48ca388c0fa81102dd15687fea792ee8c1114e00d1bc4839ad37ac58900a118d863723acfbe08126ea883be87f50e4eabe3b5e72f5d9e041db8d9b186409fd4df4a7dde38c0e0a3b1ae29b098e5697e7f110b6b27e4 +b7a6aec08715a9f8672a2b8c367e407be37e59514ac19dd4f0942a68007bba3923df22da48702c63c0d6b3efd3c2d04e0fe042d8b5a54d562f9f33afc4865dcbcc16e99029e25925580e87920c399e710d438ac1ce3a6dc9b0d76c064a01f6f7 +ac1b001edcea02c8258aeffbf9203114c1c874ad88dae1184fadd7d94cd09053649efd0ca413400e6e9b5fa4eac33261000af88b6bd0d2abf877a4f0355d2fb4d6007adb181695201c5432e50b850b51b3969f893bddf82126c5a71b042b7686 +90043fda4de53fb364fab2c04be5296c215599105ecff0c12e4917c549257125775c29f2507124d15f56e30447f367db0596c33237242c02d83dfd058735f1e3c1ff99069af55773b6d51d32a68bf75763f59ec4ee7267932ae426522b8aaab6 +a8660ce853e9dc08271bf882e29cd53397d63b739584dda5263da4c7cc1878d0cf6f3e403557885f557e184700575fee016ee8542dec22c97befe1d10f414d22e84560741cdb3e74c30dda9b42eeaaf53e27822de2ee06e24e912bf764a9a533 +8fe3921a96d0d065e8aa8fce9aa42c8e1461ca0470688c137be89396dd05103606dab6cdd2a4591efd6addf72026c12e065da7be276dee27a7e30afa2bd81c18f1516e7f068f324d0bad9570b95f6bd02c727cd2343e26db0887c3e4e26dceda +8ae1ad97dcb9c192c9a3933541b40447d1dc4eebf380151440bbaae1e120cc5cdf1bcea55180b128d8e180e3af623815191d063cc0d7a47d55fb7687b9d87040bf7bc1a7546b07c61db5ccf1841372d7c2fe4a5431ffff829f3c2eb590b0b710 +8c2fa96870a88150f7876c931e2d3cc2adeaaaf5c73ef5fa1cf9dfa0991ae4819f9321af7e916e5057d87338e630a2f21242c29d76963cf26035b548d2a63d8ad7bd6efefa01c1df502cbdfdfe0334fb21ceb9f686887440f713bf17a89b8081 +b9aa98e2f02bb616e22ee5dd74c7d1049321ac9214d093a738159850a1dbcc7138cb8d26ce09d8296368fd5b291d74fa17ac7cc1b80840fdd4ee35e111501e3fa8485b508baecda7c1ab7bd703872b7d64a2a40b3210b6a70e8a6ffe0e5127e3 +9292db67f8771cdc86854a3f614a73805bf3012b48f1541e704ea4015d2b6b9c9aaed36419769c87c49f9e3165f03edb159c23b3a49c4390951f78e1d9b0ad997129b17cdb57ea1a6638794c0cca7d239f229e589c5ae4f9fe6979f7f8cba1d7 +91cd9e86550f230d128664f7312591fee6a84c34f5fc7aed557bcf986a409a6de722c4330453a305f06911d2728626e611acfdf81284f77f60a3a1595053a9479964fd713117e27c0222cc679674b03bc8001501aaf9b506196c56de29429b46 +a9516b73f605cc31b89c68b7675dc451e6364595243d235339437f556cf22d745d4250c1376182273be2d99e02c10eee047410a43eff634d051aeb784e76cb3605d8e079b9eb6ad1957dfdf77e1cd32ce4a573c9dfcc207ca65af6eb187f6c3d +a9667271f7d191935cc8ad59ef3ec50229945faea85bfdfb0d582090f524436b348aaa0183b16a6231c00332fdac2826125b8c857a2ed9ec66821cfe02b3a2279be2412441bc2e369b255eb98614e4be8490799c4df22f18d47d24ec70bba5f7 +a4371144d2aa44d70d3cb9789096d3aa411149a6f800cb46f506461ee8363c8724667974252f28aea61b6030c05930ac039c1ee64bb4bd56532a685cae182bf2ab935eee34718cffcb46cae214c77aaca11dbb1320faf23c47247db1da04d8dc +89a7eb441892260b7e81168c386899cd84ffc4a2c5cad2eae0d1ab9e8b5524662e6f660fe3f8bfe4c92f60b060811bc605b14c5631d16709266886d7885a5eb5930097127ec6fb2ebbaf2df65909cf48f253b3d5e22ae48d3e9a2fd2b01f447e +9648c42ca97665b5eccb49580d8532df05eb5a68db07f391a2340769b55119eaf4c52fe4f650c09250fa78a76c3a1e271799b8333cc2628e3d4b4a6a3e03da1f771ecf6516dd63236574a7864ff07e319a6f11f153406280d63af9e2b5713283 +9663bf6dd446ea7a90658ee458578d4196dc0b175ef7fcfa75f44d41670850774c2e46c5a6be132a2c072a3c0180a24f0305d1acac49d2d79878e5cda80c57feda3d01a6af12e78b5874e2a4b3717f11c97503b41a4474e2e95b179113726199 +b212aeb4814e0915b432711b317923ed2b09e076aaf558c3ae8ef83f9e15a83f9ea3f47805b2750ab9e8106cb4dc6ad003522c84b03dc02829978a097899c773f6fb31f7fe6b8f2d836d96580f216fec20158f1590c3e0d7850622e15194db05 +925f005059bf07e9ceccbe66c711b048e236ade775720d0fe479aebe6e23e8af281225ad18e62458dc1b03b42ad4ca290d4aa176260604a7aad0d9791337006fbdebe23746f8060d42876f45e4c83c3643931392fde1cd13ff8bddf8111ef974 +9553edb22b4330c568e156a59ef03b26f5c326424f830fe3e8c0b602f08c124730ffc40bc745bec1a22417adb22a1a960243a10565c2be3066bfdb841d1cd14c624cd06e0008f4beb83f972ce6182a303bee3fcbcabc6cfe48ec5ae4b7941bfc +935f5a404f0a78bdcce709899eda0631169b366a669e9b58eacbbd86d7b5016d044b8dfc59ce7ed8de743ae16c2343b50e2f925e88ba6319e33c3fc76b314043abad7813677b4615c8a97eb83cc79de4fedf6ccbcfa4d4cbf759a5a84e4d9742 +a5b014ab936eb4be113204490e8b61cd38d71da0dec7215125bcd131bf3ab22d0a32ce645bca93e7b3637cf0c2db3d6601a0ddd330dc46f9fae82abe864ffc12d656c88eb50c20782e5bb6f75d18760666f43943abb644b881639083e122f557 +935b7298ae52862fa22bf03bfc1795b34c70b181679ae27de08a9f5b4b884f824ef1b276b7600efa0d2f1d79e4a470d51692fd565c5cf8343dd80e5d3336968fc21c09ba9348590f6206d4424eb229e767547daefa98bc3aa9f421158dee3f2a +9830f92446e708a8f6b091cc3c38b653505414f8b6507504010a96ffda3bcf763d5331eb749301e2a1437f00e2415efb01b799ad4c03f4b02de077569626255ac1165f96ea408915d4cf7955047620da573e5c439671d1fa5c833fb11de7afe6 +840dcc44f673fff3e387af2bb41e89640f2a70bcd2b92544876daa92143f67c7512faf5f90a04b7191de01f3e2b1bde00622a20dc62ca23bbbfaa6ad220613deff43908382642d4d6a86999f662efd64b1df448b68c847cfa87630a3ffd2ec76 +92950c895ed54f7f876b2fda17ecc9c41b7accfbdd42c210cc5b475e0737a7279f558148531b5c916e310604a1de25a80940c94fe5389ae5d6a5e9c371be67bceea1877f5401725a6595bcf77ece60905151b6dfcb68b75ed2e708c73632f4fd +8010246bf8e94c25fd029b346b5fbadb404ef6f44a58fd9dd75acf62433d8cc6db66974f139a76e0c26dddc1f329a88214dbb63276516cf325c7869e855d07e0852d622c332ac55609ba1ec9258c45746a2aeb1af0800141ee011da80af175d4 +b0f1bad257ebd187bdc3f37b23f33c6a5d6a8e1f2de586080d6ada19087b0e2bf23b79c1b6da1ee82271323f5bdf3e1b018586b54a5b92ab6a1a16bb3315190a3584a05e6c37d5ca1e05d702b9869e27f513472bcdd00f4d0502a107773097da +9636d24f1ede773ce919f309448dd7ce023f424afd6b4b69cb98c2a988d849a283646dc3e469879daa1b1edae91ae41f009887518e7eb5578f88469321117303cd3ac2d7aee4d9cb5f82ab9ae3458e796dfe7c24284b05815acfcaa270ff22e2 +b373feb5d7012fd60578d7d00834c5c81df2a23d42794fed91aa9535a4771fde0341c4da882261785e0caca40bf83405143085e7f17e55b64f6c5c809680c20b050409bf3702c574769127c854d27388b144b05624a0e24a1cbcc4d08467005b +b15680648949ce69f82526e9b67d9b55ce5c537dc6ab7f3089091a9a19a6b90df7656794f6edc87fb387d21573ffc847062623685931c2790a508cbc8c6b231dd2c34f4d37d4706237b1407673605a604bcf6a50cc0b1a2db20485e22b02c17e +8817e46672d40c8f748081567b038a3165f87994788ec77ee8daea8587f5540df3422f9e120e94339be67f186f50952504cb44f61e30a5241f1827e501b2de53c4c64473bcc79ab887dd277f282fbfe47997a930dd140ac08b03efac88d81075 +a6e4ef6c1d1098f95aae119905f87eb49b909d17f9c41bcfe51127aa25fee20782ea884a7fdf7d5e9c245b5a5b32230b07e0dbf7c6743bf52ee20e2acc0b269422bd6cf3c07115df4aa85b11b2c16630a07c974492d9cdd0ec325a3fabd95044 +8634aa7c3d00e7f17150009698ce440d8e1b0f13042b624a722ace68ead870c3d2212fbee549a2c190e384d7d6ac37ce14ab962c299ea1218ef1b1489c98906c91323b94c587f1d205a6edd5e9d05b42d591c26494a6f6a029a2aadb5f8b6f67 +821a58092900bdb73decf48e13e7a5012a3f88b06288a97b855ef51306406e7d867d613d9ec738ebacfa6db344b677d21509d93f3b55c2ebf3a2f2a6356f875150554c6fff52e62e3e46f7859be971bf7dd9d5b3e1d799749c8a97c2e04325df +8dba356577a3a388f782e90edb1a7f3619759f4de314ad5d95c7cc6e197211446819c4955f99c5fc67f79450d2934e3c09adefc91b724887e005c5190362245eec48ce117d0a94d6fa6db12eda4ba8dde608fbbd0051f54dcf3bb057adfb2493 +a32a690dc95c23ed9fb46443d9b7d4c2e27053a7fcc216d2b0020a8cf279729c46114d2cda5772fd60a97016a07d6c5a0a7eb085a18307d34194596f5b541cdf01b2ceb31d62d6b55515acfd2b9eec92b27d082fbc4dc59fc63b551eccdb8468 +a040f7f4be67eaf0a1d658a3175d65df21a7dbde99bfa893469b9b43b9d150fc2e333148b1cb88cfd0447d88fa1a501d126987e9fdccb2852ecf1ba907c2ca3d6f97b055e354a9789854a64ecc8c2e928382cf09dda9abde42bbdf92280cdd96 +864baff97fa60164f91f334e0c9be00a152a416556b462f96d7c43b59fe1ebaff42f0471d0bf264976f8aa6431176eb905bd875024cf4f76c13a70bede51dc3e47e10b9d5652d30d2663b3af3f08d5d11b9709a0321aba371d2ef13174dcfcaf +95a46f32c994133ecc22db49bad2c36a281d6b574c83cfee6680b8c8100466ca034b815cfaedfbf54f4e75188e661df901abd089524e1e0eb0bf48d48caa9dd97482d2e8c1253e7e8ac250a32fd066d5b5cb08a8641bdd64ecfa48289dca83a3 +a2cce2be4d12144138cb91066e0cd0542c80b478bf467867ebef9ddaf3bd64e918294043500bf5a9f45ee089a8d6ace917108d9ce9e4f41e7e860cbce19ac52e791db3b6dde1c4b0367377b581f999f340e1d6814d724edc94cb07f9c4730774 +b145f203eee1ac0a1a1731113ffa7a8b0b694ef2312dabc4d431660f5e0645ef5838e3e624cfe1228cfa248d48b5760501f93e6ab13d3159fc241427116c4b90359599a4cb0a86d0bb9190aa7fabff482c812db966fd2ce0a1b48cb8ac8b3bca +adabe5d215c608696e03861cbd5f7401869c756b3a5aadc55f41745ad9478145d44393fec8bb6dfc4ad9236dc62b9ada0f7ca57fe2bae1b71565dbf9536d33a68b8e2090b233422313cc96afc7f1f7e0907dc7787806671541d6de8ce47c4cd0 +ae7845fa6b06db53201c1080e01e629781817f421f28956589c6df3091ec33754f8a4bd4647a6bb1c141ac22731e3c1014865d13f3ed538dcb0f7b7576435133d9d03be655f8fbb4c9f7d83e06d1210aedd45128c2b0c9bab45a9ddde1c862a5 +9159eaa826a24adfa7adf6e8d2832120ebb6eccbeb3d0459ffdc338548813a2d239d22b26451fda98cc0c204d8e1ac69150b5498e0be3045300e789bcb4e210d5cd431da4bdd915a21f407ea296c20c96608ded0b70d07188e96e6c1a7b9b86b +a9fc6281e2d54b46458ef564ffaed6944bff71e389d0acc11fa35d3fcd8e10c1066e0dde5b9b6516f691bb478e81c6b20865281104dcb640e29dc116daae2e884f1fe6730d639dbe0e19a532be4fb337bf52ae8408446deb393d224eee7cfa50 +84291a42f991bfb36358eedead3699d9176a38f6f63757742fdbb7f631f2c70178b1aedef4912fed7b6cf27e88ddc7eb0e2a6aa4b999f3eb4b662b93f386c8d78e9ac9929e21f4c5e63b12991fcde93aa64a735b75b535e730ff8dd2abb16e04 +a1b7fcacae181495d91765dfddf26581e8e39421579c9cbd0dd27a40ea4c54af3444a36bf85a11dda2114246eaddbdd619397424bb1eb41b5a15004b902a590ede5742cd850cf312555be24d2df8becf48f5afba5a8cd087cb7be0a521728386 +92feaaf540dbd84719a4889a87cdd125b7e995a6782911931fef26da9afcfbe6f86aaf5328fe1f77631491ce6239c5470f44c7791506c6ef1626803a5794e76d2be0af92f7052c29ac6264b7b9b51f267ad820afc6f881460521428496c6a5f1 +a525c925bfae1b89320a5054acc1fa11820f73d0cf28d273092b305467b2831fab53b6daf75fb926f332782d50e2522a19edcd85be5eb72f1497193c952d8cd0bcc5d43b39363b206eae4cb1e61668bde28a3fb2fc1e0d3d113f6dfadb799717 +98752bb6f5a44213f40eda6aa4ff124057c1b13b6529ab42fe575b9afa66e59b9c0ed563fb20dff62130c436c3e905ee17dd8433ba02c445b1d67182ab6504a90bbe12c26a754bbf734665c622f76c62fe2e11dd43ce04fd2b91a8463679058b +a9aa9a84729f7c44219ff9e00e651e50ddea3735ef2a73fdf8ed8cd271961d8ed7af5cd724b713a89a097a3fe65a3c0202f69458a8b4c157c62a85668b12fc0d3957774bc9b35f86c184dd03bfefd5c325da717d74192cc9751c2073fe9d170e +b221c1fd335a4362eff504cd95145f122bf93ea02ae162a3fb39c75583fc13a932d26050e164da97cff3e91f9a7f6ff80302c19dd1916f24acf6b93b62f36e9665a8785413b0c7d930c7f1668549910f849bca319b00e59dd01e5dec8d2edacc +a71e2b1e0b16d754b848f05eda90f67bedab37709550171551050c94efba0bfc282f72aeaaa1f0330041461f5e6aa4d11537237e955e1609a469d38ed17f5c2a35a1752f546db89bfeff9eab78ec944266f1cb94c1db3334ab48df716ce408ef +b990ae72768779ba0b2e66df4dd29b3dbd00f901c23b2b4a53419226ef9232acedeb498b0d0687c463e3f1eead58b20b09efcefa566fbfdfe1c6e48d32367936142d0a734143e5e63cdf86be7457723535b787a9cfcfa32fe1d61ad5a2617220 +8d27e7fbff77d5b9b9bbc864d5231fecf817238a6433db668d5a62a2c1ee1e5694fdd90c3293c06cc0cb15f7cbeab44d0d42be632cb9ff41fc3f6628b4b62897797d7b56126d65b694dcf3e298e3561ac8813fbd7296593ced33850426df42db +a92039a08b5502d5b211a7744099c9f93fa8c90cedcb1d05e92f01886219dd464eb5fb0337496ad96ed09c987da4e5f019035c5b01cc09b2a18b8a8dd419bc5895388a07e26958f6bd26751929c25f89b8eb4a299d822e2d26fec9ef350e0d3c +92dcc5a1c8c3e1b28b1524e3dd6dbecd63017c9201da9dbe077f1b82adc08c50169f56fc7b5a3b28ec6b89254de3e2fd12838a761053437883c3e01ba616670cea843754548ef84bcc397de2369adcca2ab54cd73c55dc68d87aec3fc2fe4f10 +97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb +ad3eb50121139aa34db1d545093ac9374ab7bca2c0f3bf28e27c8dcd8fc7cb42d25926fc0c97b336e9f0fb35e5a04c81 +8029c8ce0d2dce761a7f29c2df2290850c85bdfaec2955626d7acc8864aeb01fe16c9e156863dc63b6c22553910e27c1 +b1386c995d3101d10639e49b9e5d39b9a280dcf0f135c2e6c6928bb3ab8309a9da7178f33925768c324f11c3762cfdd5 +9596d929610e6d2ed3502b1bb0f1ea010f6b6605c95d4859f5e53e09fa68dc71dfd5874905447b5ec6cd156a76d6b6e8 +851e3c3d4b5b7cdbba25d72abf9812cf3d7c5a9dbdec42b6635e2add706cbeea18f985afe5247459f6c908620322f434 +b10f4cf8ec6e02491bbe6d9084d88c16306fdaf399fef3cd1453f58a4f7633f80dc60b100f9236c3103eaf727468374f +ade11ec630127e04d17e70db0237d55f2ff2a2094881a483797e8cddb98b622245e1f608e5dcd1172b9870e733b4a32f +af58c8a2f58f904ce20db81005331bf2d251e227e7d1bef575d691bdca842e6233eb2e26c2e116a61a78594772b38d25 +b3c1313c31ec82da5a7a09e9cf6656ca598c243345fe8d4828e520ade91787ffb8b9867db789b34ad67cef47b26ff86d +a8ed8a235355948e0b04be080b7b3e145293accefb4704d1da9050796b2f6870516c1ebf77ae6a65359edcfd016c0f36 +80e792d5ba24b8058f6d7291a2ec5cb68aab1e16e96d793128e86815631baf42c56b6205c19e25ce9727bd1fd6f9defb +816288c5d726b094e3fdf95cb8882f442c4d9d1101b92c7938a7dfd49bc50636d73ea1b05f75eb731c908c8fd8dee717 +ae009128d128ba2e1519bfa7a0c01ed494a7d461c3aba60f8a301701fed61fe4e31d6c79ce189542ae51df91e73ce1b3 +96a866d60a9007d05825c332476a83e869e15b11d7257172a67690ea9bd3efea44bf9c8d42191454eb04fcf110b16396 +8b250a2a06419adb9b611e89f7f8f2990aa301949b533ad3bf17c4a61ab5f5be0b1d5e2b571864d13f1bb75805c7795d +8450f49facf2e620fa45ee90e1801178842d927a2a25fc6ed7ba99a4eec7ae40eebfee41028eaa84f107f4a777694976 +91049080cf659c0985a22d1366e59191bb89663f922e8168b9b7d85c8a73d74a6d9dceefd855d3d858b493670c750581 +a1e167aeb2008087f3195926f1985c0a459d6ec57237255b1473a96de4e2c1cf766127c862c7dc853a6909e67cb06cf7 +b667c0d4e26e20698b07567358625d5f003839c92de8088e12dbd74a6f6a3156b4ea8d252c9ad62af5f6c4fec1cf6cc7 +8e4b5e304c0b1b161ae3e4b68b5e3ac66c42acd7c1ee2458044f6527c508a93995e50894d72d57c1350f91afe72775ff +8c642640aa7915421cdc21fd639f88a42052b1cfa358ff7702e60793a92b7b5926dae15a0c8f8f59cd3013f01c159ba3 +a356f35e713cfc283056bf539de54a21731e61efb4c47319f20de4a4b723d76a33b65f4a67d298b9ec5c2a1579418657 +93ce204146ce95f484dc79c27919a16c9e3fc14a9111c6c63d44491158d5838117d20851cc3227a5e8ba6ccf79e77f39 +b585664cbb9a84b52f89114e1cf0cf1171bea78a136dc1404ac88a11210b2debc3b7a55e702da93ff629095c134a295e +b6dfd444ec7fdceb14c6328f26ca12c3f9fc4327d8d8c68948e92e7e61262b82d833a65a9e3af6353ffa832b6da25705 +b4d4b8eb9ecfffe3f0d48fb4149c7b31aec1da7041ec03bd0750c52a2a7cbc3a7cfbf09d5bfdc56e3860826a62d0bb91 +a4e248e3d61db52da9683fef188579c470d65e2df9064726847b1599fc774049ffdc6ef2ae578d5ed7874f1298ecdf69 +a68a0fffc2e37d3183feb01b42234c0f4e510f9dc29d09c571e6da00fecad9da224cd0f31550070148667e226c4ca413 +86adda2ffecb77236c18005051f31f9657a0d50fef2a1175dfda32e74d5d53df825c10f289eb0ad39df0c64fc9bc7729 +998266d5c9c3764ed97d66fa9ed176af043999652bae19f0657c8328629d30af453230e3681c5a38e2f01e389ed8d825 +a05261554d3c620af0c914cf27ab98f5d3593c33ab313c198e0c40d6c72022eb5943778cd4f73e9fe8383392a7004976 +ad243fb3631bf90fedb9d679fd71fc0cf06bda028591ded2bd4c634ea7b3c2bd22eca2ab318fcdaa6c2cda1e63e1c57b +89b9859a04f903c95e97fb2951f01cc6418a2505eee0b5bc7266b4d33e01b69b9fe7dc56fa9ebb5856095be0925a422d +a68d118343a5bbfbbab95ff9bfe53aeb7fdbaf16db983e6f4456366df2aa01fbdb6ee9901cb102fc7d2bd099be2f1f3e +b49301f25d5a9dd2ec60ddb0b4b477291958487efea9e54dc0e4ef388f03b8bbadd13259d191f7a0b7513876767d8282 +8b93df7fb4513f67749905fd43db78f7026589b704ebb9ea3255d0ad6415437799f40f02e07efccda1e6fd5e8cd0a721 +ad88769ace96455da37c3c9019a9f523c694643be3f6b37b1e9dcc5053d1fe8e463abebdb1b3ef2f2fb801528a01c47c +80f0eb5dcbfaaf421bf59a8b9bd5245c4823c94510093e23e0b0534647fb5525a25ea3aeea0a927a1ee20c057f2c9234 +b10ad82ea6a5aeabe345d00eb17910d6942b6862f7f3773c7d321194e67c9cced0b3310425662606634dcd7f8b976c04 +82f6fd91f87822f6cc977808eeac77889f4a32fb0d618e784b2331263d0ffa820b3f70b069d32e0319c9e033ab75d3b4 +9436d3dc6b5e25b1f695f8c6c1c553dab312ccace4dac3afddc141d3506467cd50cb04a49ea96ea7f5a8a7b0fc65ef37 +8e0a9491651d52be8ebf4315fbbb410272f9a74b965d33b79ff1b9e1be3be59e43d9566773560e43280549c348e48f01 +8809137e5d3a22400d6e645a9bd84e21c492371736c7e62c51cef50fee3aa7f2405724367a83fd051ff702d971167f67 +b536a24f31a346de7f9863fc351fa602158404d2f94747eebe43abf1f21bf8f95a64146c02a4bec27b503f546789a388 +b5cdf5a04fc12a0e0ef7545830061dff7fd8abea46e48fbe6235109e6c36ee6bffcb9529e2f3d0d701cf58bbfb6a4197 +ab15377525753467d042b7931f66f862cbbb77464212c9aa72d4e5c04375ef55f619b3a446091c1ba1a3b5d9f05e538f +905a75b943ad017ff78ea6ddd1d28a45c7273ee1c2e5e3353685813793ead3370c09cabd903fcab9d8b1c6961372d486 +8147df4324faddc02fb0896367a7647b719b6499a361aecfdd3a34296fa6768ad31c34f9e873fd1e683386c44651883e +ac91d08570dd91f89d2e01dca67cdc83b640e20f073ea9f0734759c92182bb66c5d645f15ebd91ed705b66486ed2088d +ac6295ef2513bbea7ef4cdcf37d280300c34e63c4b9704663d55891a61bf5c91b04cc1d202a3a0a7c4520c30edc277c7 +b604be776a012095c0d4ebc77797dd8dec62a54c0559fb2185d7bac6b50d4e5fd471ac2d7f4523206d5d8178eabd9a87 +80ead68def272ce3f57951145e71ed6dc26da98e5825ef439af577c0c5de766d4e39207f205d5d21db903d89f37bbb02 +9950b4a830388c897158c7fe3921e2fe24beedc7c84e2024e8b92b9775f8f99593b54a86b8870ec5087734295ba06032 +b89ba714adabf94e658a7d14ac8fc197376a416841c2a80e1a6dde4f438d5f747d1fb90b39e8ea435c59d6ecda13dea1 +b0c78e7cc60bd05be46d48fbb0421a678c7f14b8d93730deb66fbe1647613b2c62b5075126d917047820c57fc3509cb9 +a860c4acc5444e9ae987e8c93cb9a5f17d954d63c060cc616f724e26bc73d2c54cd36e0492d1fde173847278e55942ba +8fb8269c9d5c15428e8d45da1251e4c4a4b600d47da0caea29fef246854d8fb6acae86a8e6440d0c429d8dd9c2dfee0c +96c5d8eb6fd5c525b348ee4335d200139e437e4be83690af0f35b7f336a7cda8c6d2958647988b84da9f2dd7bbb7710b +a7f62141c4346cc14e9823dc38ac7d587b0427022afc1498d12ee2c43f6ac3a82167057e670dd524b74137f8c3ceb56d +956aac50d06b46a3e94397f163f593f5010d366aa2d816c2205c7d0f47f90cf0f36c169e964f9bcf698d49182d47d91f +b812899bcdc0e70d79ca729cb01104bf60e1357b9085a10f64f3ba9865d57e9abd0a505a502d4de07afb46f4d266be2f +abce02c7e1372e25d40944dc9ece2904a8f59c8854c5f2875fe63ace8ce37d97881f4f9ab4f7bad070ec8e0daee58d3f +8fb13c515b2d6abb4e14ed753fad5cc36c3631dfe21a23d0f603aad719423dd5423157eefcbd9a9c6074e155b79eb38d +a9ef67304dc297ab5af778cf8afa849eeac27db4b6978963e97b95ef7a8d3264d0d07775f728c298a2b6daed2ecf5053 +a9b975520adb066e2ff2a4cde53284c23bc84261a22dc43b1634d99eff8e7892e46bb6e6da7319c9e72788aa9ea7a1ea +a6eaea4ab4206294474d9b956d9d3188d558a5633de2bd05df0d3bac03dbcbe4ed85406349c1d2e660b77c6da1f5bf8c +af4a19f77290dddee762e1e0d4bc9945aacea3f75756ae46cd3e58a8f74d1b5db73e4834687946b0f39191e32f2fed0c +aafa6523f58f1a4cabc924c86d842816d606afeea21fa4b2b8b9573425810fdcc41c98888318e868f9c05e2be12178a3 +8ef38fba0a3fa4ebe985239c8b759c22aaef0c57e6f39050a651c869487803b0d1e389c3d958fb5a7f37740f050ac69e +b07dfc9f85913c608ca7596a2e361f05e4853fad00e796fd492d247de6414892ce160f627669b1ba933b6ad726415d4e +94da679ad1d78b2bff5283c938f17b2a7d6e9cbcdf59d340e6dfb652951c7a9e852ac0590f99cfee9631b9410f6f00ea +98a907c9c021a5b034d3720197c160a82c4b7146cb73d48efeed99b9d0c6b831812cf80ac7e19e85a676a8cd3ead72de +adb746595466a12929019d0048cea33236b05c1229d2eba73b259a18a786f2bc3f05fc0598d8ce253cecb80bdf679aaf +a2fbac016996d68f9027a157b0a3f6a336144a798d6113adfcda3a5d05b62c31f108f112aa915906aef22b7f83b9228b +81841dea1904406d1b6fa49b4b3f7f6cb40b7646cf44d36c9fa07e3dee29f8e47324b40d8356ddf653109673c3374e9b +a3edbb8aac5e60c775775cbdb19067341b2e2530de48738e84c2c07151241ee31f0d8333bf20c2bc9dcb7b2e638a6b5e +b8aa6890e22964828787ce86460d3a32f12a655bb5c28de500f2fcf6b61e3334640ec6ba96029a4912af0d18df4b4139 +8ca43169f04243ad0fdb0152de17c60d9e31ee0ab520970fccd98590e05508821a183b4b367967e60d53c2c826ec5dbd +b179fffd9df8c00486c5a8b9327d599f5a11745ef564f06e126849b06fe2f99273c81f65bc941efb0debaadfecbfec1c +acf068f1c2b1926279cc82750ce21b0d6b0bfd0406f0d8bbfa959bd83935932957c7f6b8de318315bf0b75f6ee41a0f2 +b97831da260919c856e9f71a41687f5979bc16f8a53b1037285b4a2f9ce93af5cfe70bf0ad484744827fb55c847b58eb +aff50b0bd907383b0c241727af364fe084d021221bfb1b09fb6c1a7752eeba45d662493d590f1f182764b90b25f17906 +aeeef044c14e3ad41e1235c9e816e1eb49087fd3abe877b89b3bade74459186126e160bb569bcd77779e701b19b5f71a +8483deb2b7001ca7c438fcdca8ca6aba96c9cbc4becfd9b16a6062705eae270011bcaedcae69bb54630d8c78129e57c7 +aeee8d24be4ac0d9784c029e239fb5e64316ce29b88f47394cfaaa8bb966a72061bff72f99d02dc51c9705854686e77f +90ae09525a16bb2422169e15d6831c87968a14ebc0d1d27e11a759839c73c655b9d33ee5b12f275d6f440688146fbd2f +a3a41fc7fefef101422465e506bea7f3ff23c26fe35f5732b86f5f2471fb93b37ebc339f84c6be1e8d22abc812c2e212 +86f4b5293e8aea4af1f1fb05dcf99714cb3aff1cfc849b1bb73524061c921c9da9ad92579a852e1889da29d952f02fe5 +8932ef39d4050a1e9dc0fd8afeaf159472d71c5c27f458c69d2730836606ea56e19c8c4febf2535f930d3260e9bc7637 +86307b9f3696bb21c20e4558e30310389e7367803c353d437e9b696039a0ff054d9a4953b75237ab1d1dd6f71118c189 +96e57730e683ef5b550c91de18b19ac73879f3e26234297db68d28747ed0953beb0f3913cfb720c602720bf9330685d8 +b04a19ee70123782e47b238abde55baf60ac0c66292a998af0d14afc8bbeb1134e557b94cd17a020084631c09a0d3c02 +829abc8718be8139569fcb2c398962f38f4201114d30e2b2fb23566f8a27a5c380f5605cec543415202a12ed859e33f6 +a0744fa488c8fa92a722c5fc4ef5a47dfe824eccd87d26c8bab9c174cbb151d44b1b29082c48652f03d3177e5ec86001 +81d4035ae9fd28bdcd78b135cb54955d3b685a527319df6ee7e904b8e6d796f5f5a5f5035ee1de750c4cb6050e452b9e +b205e8c2ec24d7104fa0106c09ad34b5a912c1adef553fb718838dd627355993c2ec01055c11d00b2c75b68e9516d44b +b12d09da7968fa7394e449624fc7174d1d76c069ccb03e140d4d87a2d3f6d1f7b9cfc930f0c80becc673406ebe63f08e +b23752c158695da85048fdf38b395681cc0e8998630af8a9ed41efbda08c9964c2dc8ae6e53377264be4467d702c0de4 +b0d84582fd73628d96b8c1ec96197697c41a963542451a2ade0890af0d33c7161d0f18e1a1ce2c168ca2dc1e9119d55e +8b877e618b469aa187632e410b125d2999d5738fd66d482000706b51fd904a0c7e7daa8c9b729fa33817bbc4154cba2a +b1cfc8a7551b601723b937d497d01dec3ee7614c2bf13d430b1058d5ebc1406045009ff02c2ac15bf8cf16f860193d1e +b6d9da84f97b21e13175bbb0b5cc8e79e88b470c87a3e115726c1bd98e0288526c58f3faaa8aa170ace0cd6a60852525 +ad2e773c2d527671ca5fab7085dde4da31cd35f45d4315dd95d8893ff5fb900494dca08eccfc1a2fc7bf7c7fd2fcab97 +8d5a79b34aeb761d4a0c73f09f02e9548e6d382c33ee6887a759ab05762b490b8a549ef2933c7e3a46415c154c0221c0 +b6f2cbe81bd0a7298403be392f8456bed30aed7ef30216959357698f789affd2942ae5fbaf3f48ecebeb7c273b20cb57 +b5b6c45d99cea7ce6a1dc134aff4a8f630f299b42bd59592a7592345f8cd35bcbee944e61b0723de732fcad6e4425b63 +8077d64dfcb2418974e956ea6dbf8a4c05b25d2a025333ad7e2a379f1976dc036771403383a51bfa3476c9c619ef8bef +ad2e0a9d479c77a5fb73b3613a177fdaad50dcb50fed50e756ba18164c153af30b07fb2565e80ff7469f1b0338b7b5de +81017d1d80a6b6df4e99d0d7f85a8180b5523e8fa2ea2672fddff604933f8a113cab27fce098dcb454d7d1f7ed266e04 +852355479d68e76c7febf6dfe2ef8e80d575c0d3bd52c983803592021cfa898c571c0b884412c21e66f0dbfe03167b53 +98e1bf8ad48421467c93b9f72b47dded7c41b4fcd36ea55ca43ab24b0d0b876f5a731f422579b7167c7138fad2121266 +803369314abd5422019ed4b0ef652b4dbe97ef5a87b0ea373eec9628b64a12120b2c3d4eb53db405131ff786d14c7ac6 +adf2613fc34f73e1160975c140e925ed84d254e03cc3bc7fc1d19957b499c9ba9d9e4c1639981b594a7095c0a52c6757 +a2f6a68efdff6e4173c00692abcfdfcdaf6f8b62369afad3dafaae4f2f38c4860780b4624d185e20e4f4498b75b5fe94 +8b1658aa0e119fb8401d486ed08d60240d26a8623ef9788e3b45ad09ae31259395b021bd16be395139cbb7149714e764 +a7dd8bf21121285e00672ee8bb84e0cb39b2496fb53a26e35dfbca7f2b04e9a9ff9db15f53fe63fcbeafeb2deeaf2ca4 +b6d8d709e44bc18f3b41d69608edce60c02bcba48d3b7e2fd420842657f0665a7343246dea149a25e8f3416284abae66 +aaf744ca5e9bcb63e3e2939b7a1e96e4a93c88c76bec0cf4294dd7db95cdd3f6a7d92196e352d08680e2328bc4592899 +84434b015a7c398d35f1ec71fce455d62ba4ed4f62da042ec31bb2b4db47073314354cd50bc322297a1cfe35138bf490 +8d70b3a3cd9d5dfefdacfa418c0b775a112a47ce538d33a560a519660009c3f141fd6221c18539129e9c0acdaceeeb80 +b8c6903412a800ec78a4c15f31c24385a267b0c0ece32fd31bbbb557fd70c3b2d60d8fc0f90fbd70f43baa1928ea30ba +8e391dd445ea06cabb433f057853f8159511b2f9bef41aed9ccd14e0a6fcd912bbaebd38fd5fb736cfde0fa34b7a4874 +a40cd988f70613df32babbd1bbc2f1b29ff1ab0147b01161555a81d56c9621657999bcdb1df38485f687afc51d5d0f23 +b6a008b4426b3d7b28ae04eee4698fc8ef6a35d89008ef5394da39ce582ce1a45dcfae9a33b90f6fa4237f3667803873 +8987280debfb175c3b44a2f152ea82548e4f680966f1fcbee9bf7d714e31bf8080c33f52705ef3aeee70544b22516aba +a78a51a2c11eea7680a5a0ae417a2981f8c69c396e06da621eadd7510a3664ade49d065617bec67b3de779548a4f4509 +a4d9163f0a1bc048385e94d5e0bcafeee1b18f28eb23505623b9e8ef16f3df76408254dfbe790e45f2884198060d388d +83dcae2568a0c518793c0f6e38b42f9ceb50673d100b556a17ec8bd9faeec84afe50b8d72422c6b2356959667bb8e2de +874731941be4474b4576226e5906b5dee89fc9b56a9870dcc7289c1a7d494d345ba6aba31f7546a16f9963283c05f744 +82c1cfab1f501189ac20147fc4631075dbf1abf9125b7d42fcb4f31cf73f3d6461b1bd08fdf6e45cc54bc08a7d5d51d1 +b978228286f5d4a10ce027b6bea3021affcaa805340ca4b5192c69e8c56db59f48e4a14a284ec015f53baf97389f62b2 +af125f4fdccd1c1b64fdffecb5ec7cf8c7392bbe476e1b89a5b5329c5ba4a526e58c11e72ab9de8a38d60af648d75adc +8411a41ec14295acab0d36389013535a80dfff6e024bffeb32fb3070762f61256419e8c51b2ad6de9dbe4f1e8e286912 +8ea67a91112a41f9c65515cd496f4b0cdefa1400fc06568eef000c9eae6dc250fb7622eb3f2deca10b37287cd96fa463 +8da99b6c55c31dee6a49aabb54da249d348a31d4416201a10c45a3b04b11e99d4ae9813632f0ee36c523b5cca62f6f49 +8b44656341e039e2bd83a19c3bb9a88f6209482e274f8cd4f8557b728e5948dd80b5745f621b96f4562928689314e8c2 +a02d424a615ba0dce8ed91f477e79852215a3a39d025059826fa278e7eebef19824b2a2844f5b3865a0f471b609a23f5 +a1f115cebc3fff3bcf233da27cef19eae791660f155d088003460f75567a550bef0722885010ddc384acdeac635939dc +b61a55ce9d143c17876776e064b58a10baf0ba13553c785c1e47f57b5f94c0cda8bc89d43d73386e57816c15b61a8ec8 +b4073f47041e20a8e548c7fb00e07ba3b9056c34eb4ab63bb0e7b48f8e338e8b56a17611a1b5f4c03b352450b86f1d69 +a7b1a07b213205b682fc5b6acb7e76fdf97b280c26621d8f3b76b7c1deb3511957da33a4e358c8e8f3d98b2a8855d67e +b797e67c2670fbd9844e8a68c585f404b035dc14bd4ec75c3f95f932c777f9db5d5f5df7629164af488fc1213035cc5f +99618200797b945f595794d6468e5c618649554ad9ba896330f1cc844090eb956ae9fc23132912f9047085c5f0c3bf7b +81194aa1319abf534cb3927af9adfb178a99d0e3e8c99ab1105f1d3b4fed40ec2971caf1d6647acb0c8d681eca53097b +80673f18e4978dbc226a6cd4b128a1259d9a7f833879c6e2fbe24d69fef2c3c23a51a4f3e8d88fa4533434bbb0723661 +8125bf6c7dbb2fb63aaa3f53283559f172c788223674adbeb6d5bd17cfe888e6b87a79aec774917f20ce911c1f85f8e7 +884bcdb1878b14fc38adc9fb8b4dd0b3afde404fbeb664f26ddfebc81736018551f23e75ce4cfe4865f610bcd454fbd7 +aec65c8d4be8316e98aa54888af01bc6703a0c5d04b69756ff39a0a947b66817ec59d76afe9f61a25749b5e890f03e02 +aa457aaa1b014a4c5a8992847a187a23321bb43452c98745987d038e3b04046102ae859b7a8e980eea978a39d76a88ef +a9832ee63b08e19123f719bfe2fe742125f32463efa966c7709a98ebfc65277670e9ea1fa2d2d78b96bdc7523b0c4c3e +a87b6b1b7858f96d55064274f29fbde56067064962cf3c3e2ba3110b22ea633bc037a74d23543ce3307a46208855d74f +897cbe4ab68a753020fec732dfcc052c7ed9905342b5a6fe0aa25c631f9ad9b659e0ee75d46f0df6507b6720675ee28c +97c3b5f0d54c1fc45e79445c3ff30458959e406a069f5bbf7979d684195b4fa0406b87c1c008f4075bc9e602ed863152 +921e65d582ea9322ddfad1c855331c3cac81f53c700b96db5305a643c084eb6793094e07944bfd41dc02c3b3cf671530 +8f23ef1aca02a260a3b65d25b110f28d3bafca44727448c8f2d03c5e77eda620c1721b06681bd816ee6027664d76352a +946a89b132ec0795aea9ff9dde7b77e7feafffe6e4a2f093042a7e6c71cd6ab87ce0ca914a1b5fabad4e1f96a795f163 +a01e2de9db33df6511172123ad6f7c64074237471df646b32dd9aff8c15278e2723108e4facaedca97e9f49503f8c792 +99dcdcde45b2ea3f15279936feede5f7d3b63ca4972f335b0559c2fa6f9faabd8127aa892a36deb114357ca906553ed8 +a3f8af37bfcf66b04d1896a4bd5d343f4733d4c3305369ac7e75a08f20f2004c10c642d2c7577f4e5c4d1f2cd851ac3b +b7294d15a3d674a56099f97a1adc9e82c15e90832eaf1722df110fc2abc8634c51515e5ad8522015498a3753b1fa8c49 +b4f27f5062ba7a04ea0048b3025b5e3d5b5d319a9e80310c808a5fb4e8e77b38c10a0f3172cb805cadbcc8bc66d36ec7 +aefe5decee0ae2dc372cc6cf4217daf97c4c908d145f100f0daf1ccdfdf641c78432c2e473e7e4b77dcdf2d4c2bb05f0 +acc84af7648a535ffd218c0cc95c8f7b092418c548815f1bafc286b1fe14f6ccb51b2044db3bff864d0bb70e88604084 +84d8e3dac0df6a22beb03742e1d4af684f139f07e2ea0f7fb27fc2d7d4f1e89b5e89f71af32ff115ed5e6092133535f0 +8ada001e1a03a823c4c056f636e77adc0f9dc08689d28de0d99e0feecab5db13abf37b41ec268dbdb42c75419a046c68 +87dac6c798d1744dff81d8bc3e0e04f3c9bf260e811685ddb9a9a8d6eda73927439b344f9a818d2103fad633de5a4a17 +ad9929a7d8a7d5d5954e48281a87e5c84f67e19110d73296b9989a09c76767a57a8115629239ffb4d99dfdf9c52ef6d9 +81ac7cbeef8ec35a5c3b61cc887080c29e6cd3e08af37e45830d17400dbacfb374dd07bf370b979828c3875b2027d5c6 +97f92c9182953b7e10f7a1bbb6b5b5c40b8275eb5a6eec1e29874c4712814749aa8c409651380216e1ff01d7b8511041 +a09794d0bbe7db013045d3fd857c1544fe6231d21afa3495fa300371f6301a3a0f4b8ea175b281503dd06078ff371ae4 +839bb58d320aa08116dd387a57a2b9bd9efc89c4cdfd82d0e47a00cabe644631d09be5436bd485df3b61b75ddf81a3ef +b1cdaa344f783757e8b9c1f84421da3c5be4c69f019a8fd4c1aa5bf1a63e8970c99e35c22cf3b48a0e6738bc6ba7ce8d +92af68e3216c78998208fb24b5ba0e645d0d3f5e28222b805668d7e9cdd6c033d3b22fd6df4c2d745d7f910d133cd226 +87640a4ea4e605e2204e5232b29a6c1c31152d83547eef14122cb76a0da52b8653801af48455a3ed713b9dcfee7b1ef1 +8147e5bf0c8f4731155ca0517ef3fae5a32b4d5d2d98ed0007b23893d8dbb7f8a1199c50c1750c2fa7c9cebe594b1bb0 +a76b4473c63c3ab6103c729afd2482822e4150f3155af39983b0ff0766c71cb622455ce6304e23853661eaa322219d18 +b3e2f05ca551bc3adec0067e4034aaffd72e0b64ac18ae25452c996927976c6727966e26d213b032521889be2170800d +a8414cd14cb3be658e9e0004ce511ef7063439b1cbc3166a11de030613fde4b59caad4e91d426927863c55382afbf476 +b2f0f8ab99f4d0ea785ac84fdbc00b20217b1df59b30b51d9d209d489d53b69dd5d82cdacc16fd1dd15c3a4001595f50 +8b2025d5fd658c9bbed619f3e3f6ac8efe7aeff8aa9401bd66a7ceb0062c44b353608ca073f95be99204f0a913bb77eb +94a46bc5a87291b42024b2137e623c70115b9c6b196604106bfbfa20f3f56ac7779763f56b580190d3cb2f1c648cada1 +aca9355545118d0769cacf69c4b23d6d68d229cd8f68f1bc0c847c05569c5af6bbbd8c4dceb637b4a6b3b5c83841bf5e +b0731992cab87c7116406b283a84707a34838bfa3284b0f6082dfabeaf41c5ac2b0ddc1b420547a1b0955aee92de2dc0 +b671f77588c0f69f6830a5b28e7d07ed161b81fa9791bb3a24aae6638e3aa5e186df74978a82549c370c18ebee04d4f0 +b5621ed841780f3e6681d880a76cf519cdd20d35197b112eeaa686764d57b5dfa78ffe1a294b6bc76b6e3949cd2a2369 +afeba2524659d00caecf089645611553187a6ed7102050f6dd20f5a19bed08ac7065912d88371ee06242897d58d652a4 +b78bfb83d44ced14a20135804aba3f00128c3ce1f302e95567ce4097b0d973414153fb305b9f156882a5a0554bf25973 +98510aede95d26b1adf214053eae051ffaf24894e2fa37961a91d0ff5392dd09388196648d95b73e90bd88f2587cc4bf +b35c682d49c295946b9f120fbc47b95abd9ee86d294abb003a92139fb825b509209562575015856a270eb3eea86397a7 +b9641bf685571dd9c478dd2033a1f1b11cd3a662b26502c78595863b8e536a189674a9a85f7a253453ebfd1b99fbd841 +b2ad37036a59b1c9b8457972665720a6868422ed8157b6810a9c0783006103be34ab732d7aeb8629653edd18fd0f1717 +af0920cff05179a3896ea6ea322c39adf91ada5bc40fe3f6fb1b1b4e121e907c904bbaa8ca00468b3749f3da144d71f3 +8e269672818ef1e2f9e0c8aa65c84442fcd9151d74bb8e870cee8c0e3fe24526e1a5388b430cef47b67f79b4e4056bcc +aa29a16fe00ea3d143b1032b1dd26b8ce638f37f95c085c7e777e8e2784bd724bd5c38b1583c61a6ec7c451dd78fd3fb +87452b7435911cc5f513b0c81b15aa04972ecbe3d7bbd0a5d676c96a8a311301c0e07fac925c53a350b46fbd3d4d0fc1 +869a81c351096f47748e41566ae7b77a454b1cdfaa41d34a5742f80df38fbf5cbb08924b6fdff58e3b18f05c62bbbbb1 +8b7bc1b0486300981147a40a449ada9a41afc06d735cce8bf0fab3ee94ba2e2ea57b1397e3cd31bc295352beb8334ef7 +93e93fc41adb2df279d95654921b4c2edf0d293dab58d0afefb221f777349ef88d0985b3447e3b935954a81f1580a92c +970fa7cdca8324faf3e62348bb50d78f580b4f43f2e1c11bd8382d48d0074a3c55c6407203a0c9cb1c5f2163ba421ef4 +924983929e608d27e4a36d4ed919297869e3c64de51aca794d32d6e90aea546bf898d98ceca28a0b2187734821b78504 +8d395332529c703d943d68415d443332b5c1342ca9d9a59bfa8bd4ab63e93358c4b0dde6ce1f2e8ea9dc8f52ad7ebd95 +80200dda853e588256599e7f905add5d5ee7c74272780317694fbae39318ae9be05d5bcd7b20cf460069743f3d4ef240 +a287d51d6359c9ef7c7ac1b20e479ce7d0146dba5606397bd04b7a622cec642508d5b45d51b31de71f9763595b6ac88e +a320396c075175d6599225cf2e1de8c7cab549f6316c07feb0f6eaa21f06b2dd29ab14fbdf2af4543b4890ec0fd08a4d +b1e9fe230418d20368691058adcbbe30011bab3000422f0371015ff8bd09c60fb5fa85d18550d35b1c900977ca48f58b +9718fc26a51783b971744933f20490e9b5cd9162f86b84788c4c5217f5409e37b5a39d628b18e5b35a757acf67596321 +a0cf81fdb161f4f1b419c5e4caa36d4bdca2325f0cd25b119a30178016f171bd6fb88403e4e3aec026c4089f180d540e +8ab1e36bd04625ee794ef04c4dcb8e004d61aceb2b62438377f49ad95dcf025ba25eb799280004941e555bf7172af6fe +9257b9e3d14d37fc7efae49b0c68d36eaac546035f4a2654d566b3ce1b2c4564cbb03dc8ec66efceb768559a8a507a18 +945d1123b839637ab5154a1972c3c83a0ff34a3b1a3465de6ef0416b1950f649869a3ef88d7f1036648ee385265ce2df +81449639d708860fc0229c94f754f7262e8a3c7f67960ff12dfd15df95f57a9ffcee2013e81978b7703dd42bd5d0816f +a865481deaae5a690fd53892791e5fa729db283b75a525a11cdfee1ce17e8e7f0b449d25f20b3c1b43da128dbdf98a8b +98766812a65fcd25b853546e3bba618a3edc9fd61510e4f8ab60c038a7fa50d197abeec8776109df0f2119be9445ad00 +b1b8dd5379d903dc41d74e999b1ab693607a0d2905692f4fb96adf08f738e5d31f9d00df28ccb8b5856145ca552c3e3c +99d20be7b511bec78a8ed03c207aa4aa9097ba39d85e18f1b8d52f65431ab7e9a773c7b9ac3e8d8b25458bc91bd00703 +b1b7c3563fe8cb33c7d3e0b89d00bdd13e86452ff507c2e69db7b3af06f247f139155396e9b0278753310dc63940a10b +b3dc9c08451b1de7c9969b1e47574bffff50490f4a16c51e12390195d9e9c72f794790caf7b0a835d64e01fec995d3ac +aaaa4761a00022ede0809d7063d3532b7bfae90ff16f45e17a340ad4ebaa2fbac40728ccc5fbe36a67ab0e707566c5dc +8319a1903314eab01f5442d2aee6ae9c3f6edfda0d9a88b416d0f874d7d1d05d08bb482102f8ca70a4fa34836d0840c1 +932949a6e9edfec344932a74d4f81eec3667ece1e8b8ca840ce07ffd4b5d6d8f01657c764d64ac1b9190f876b136490e +904db1568128487e312fe629dd8bb920cecafd3bb9cad8b63e269ae0129f2f5c80cd82f0d81e7feca9835c3945a72d28 +a17280693d30dcd43c85de8f6b02d5f30cb9097274ad680cede1ef105c903615b4c40f3c6aaca478642de324972514e0 +8d5f76e093aee71d0cdeb017fdfcb13bd068039746de90690ce150a0bfdbe7ddc4d539df0f82c2d2890a40b191900594 +96fa1f2196a3883cdd73c66d28403cbbb58f6a939a3697ee0d308d8a076393cbb4be86255af986869230ee410c01bcfa +a8b74438dc5cabd70a91bf25601af915c4418d074327a9b01e0190c27d3922c89bb9b41e0b366e82e313edda8f21983d +ac9fdc1a9b2e3ff379eb2370979372e13c4177bf4574f1490fadf05a7073e6d61e703e2d8eed9ce984aba317d411e219 +a45a6c9b958169f2f8df70143e6ac3e2f6f969a4eed6fd9f1c620711bc2454739bb69f0094079464790c5429c0d8aedd +8901cbdd1009864386577842c1e3d37835fddf834064d9613b4559ea9aef3084204e1f863c4306f874141f4374f449ff +b6c582161691e3635536686825be9c4d7399d668a7675738417e0363e064dfd28acdbd8dbc9e34c1dab8a1990f1f0eba +89e89ddaf3cacc78428f3168549c161283ca8337345750667c98212717b21e7d994eae4e45bbddacc832a18df1d79276 +84be275627eed8e1a73c7af8a20cee1ef5cc568cfeea7ec323d7f91b44e9653e9aeed47c1896a8240b99dde545f0e1fa +a779a54ab4f40228f6e2539595fb8d509b70aab7c19e1928c1be69ec1dc19285c3898cf15e5f8b8bc725e13af177fe17 +92e2a49d2b9b36349d442283b17d46f8f9bf5932c34223015ce62d2f285e7363b2c12232be4a838b5b6cf08e694c094c +8b4e28c6f3f36caa2cfb82ba88066c830f8017bd35608b077143dff236f3181230166f5a5c02fa0e5272297331726aed +85fd77d46162ffac4b8adb25baff0eb0512a53a3d01638b3a376ea34702279ce21c8e7d8884308c03e00c9bcc1a9fd29 +aad5e46916ff1be29009b595d1d8fa160cc7aa01c7fbf3a68f445c87615790dcab1fcdbdceda533d182b6541f09f2f73 +948df7654726250dae393325addd3c0a20431c81f00470962190335ea4b6d9f7463d6f308cda46b92084c1f24390b1da +8f577474dea132676504376c5542b730b6604fe3d965eaa194659fd11c52233bd0b11ab62e198c0f442327ff1c00e501 +ae2f1001546db3e0c19700adad997cd9f765fe7a51a502cbcd9a2a07a3a5db79c8f603e05cf96d80b688cb6c9b6cd3ae +953b68e5d9561088dd20406ea7fb6894cba33868a38ace38fc30b5813140cb15dd6dd2171befae5b4df2e4a9658889d8 +86c52901655ff11419b084a04da8fc3596eae59d81d3461601c0baff59ba59e3d1dd0b7ce719e741a3e97c013e898579 +b9a72dd5eff73f9912a28b55de073568efb3eb0241a10b77a2bfd4f30c2aa4fbfe0c89eb345c9f07fb725660873cb515 +8e7353f5f2932e4ffd95811caf46c9bd1a53643c27eb41a4ebd211f230955cd71a8b27e17cfe8aa708d8514c0de67a66 +a096b8e66312a92fb10839ebe60189a8d1bd34dff55f7dfae85e4d2f53a1a4a88211c19fc84494f066358ddce82be131 +931c5cd82719d76596832b007969b5f75d65cffabb41b9dac7910300db677c1309abe77eeb9837a68c760bb72013b73a +8ba10f5118d778085122065b55dd1918fddb650cce7854d15a8f0da747da44d7b12d44fc29ad7dc38f174be803db74c6 +8c971deec679372a328587d91fd24ab91043e936ca709c333453d7afd43ee256d08c71cb89f0ab0e89ae119831df6d86 +a2ac28a58034fbd8fd518f409221bad0efec52670880f202e09c0530e2aabc2171ed95e99891790596ffad163d86c110 +b3354e3dfa8068aba4f3741152b9204baa4e342c1cc77e6dd1419cbaf8da1d118be605846b8609e997d6a62a11f3423a +a12ab65a213c9d95c24865fddc2dffe0cf9fc527dd6bcdacc1bd7271e79929a4ab3427a231f4f49d0530474e6cbc88f9 +90afd65b7e6973f8aafbe74da0f42441840d3c93bd69bc1bec8fa56824e7ca97ad1b427c8a85da7d588469bd4ccc50c3 +a09175940c59489bac3d3da3a4091270d9118948cbbdd57f2bcc63fbf45b8010651c801d3e58dccf42733ce1d6b446a3 +a843bbf286e3cecc1fe370ff1bcf5f1001bc2e95b34246625ff50d48ee62343e82fba2d25b8a4bd5f7b5ffe90920efa2 +a3c4d1003219157fdbee2707ce07afa6c2a64ae8e450182c307ed7f070024071f30b12c4b0032960ff913c74e73a9976 +b24af3f68d66f825d06fc3ff94fcccebe28b1a0d4ba29c48d3a3c953b9bf7ae6707f193fef25e2dcbd2b74e483c774f0 +b0f657f7723184ef7d7e4381143f1ac8020d8c6c6f2dcbebb0eaf9870d61a81f2d452596503311e46d1b38f625d4756b +b90091004fc8f6205c51bec68547ac82dba0f5525631e7632cf6efe54eecd9020729fbee6105d1b8012402d3b79c54aa +8e3fa187713c60eb0a416d6900a894cdf81e6b6b69dae0bb64f6287f3c3f030cfa85c665f7aace1eab4937f380b8f728 +879bf0784ccf6725c9cd1ea8c49fde31c91c605de1ea664a33c2ce24c277ee45d20b66309f98d989acb2ff3b77e13101 +af3f3a3ddc4e11abd627d5aef8adffa91c25df5f0c68b4d2b5d51e7d9af3395ba4f6f7ae2325a6672847e1ecc6cad628 +973e667289e796d3a40f072e6fea575a9b371a9997cf8961677f8dd934619ddc47c1a3efe91bae9ef95acb11a8fe6d09 +afa81c5606de82f46b93f4bb6db3fc0670f4e0d1091388b138a66b3827322d95a56168c951c30831d59eeadc227500bd +b83eff77db5b4c18574662942eb36f6261c59f655f8a9c3d3731412d0f257c8e80aacc995c4b2303058a1ba32522a434 +912e5ac9234b9445be8260393ff08e4859a7a385e800b74d1534eeb971f58f74cfb518dfdb89f8705d89fbf721439129 +ab27c8ece4a51d23e22c2e22efa43487c941139b37ea1182e96efb54ca4809d8245eae0ebe8ba94f0ed4457896fe11b1 +a6630585d104a745bc79dba266d9292bbdad346449c8ee8140a5e6e8a6194411df9cdbf3d3ef83468a536d4f052e9335 +8b8c128244da48e7fec641a882d0005a2d05c7138d86a293e6a0a97c76bf632b44767d0ce44663c975e7f9f9679e25e3 +87dbcaca67351a4e7d2297d7cdba4796d12f58857e7ee4abd0645563577ff33544a44cd84e50b3a3b420d6998de9b57c +b859ba43df259d7f8e7fac70bfd7aae546d57a5dc90e107b174a95bf7fd3cf00f740c4434848e69b2a7e6061f66c1ef1 +99d6e20978fefc40c6d310187eb2ad3a39296f189ee122ed64d74f81033c3069d44f7a9d3988a1df635b609603a17272 +99a5ddf3420cc0c92b21f71a805245608d4995ead447d8f73a670d26d33e26920d5f07bfe1f6230bd5f15978055b4253 +b936ac0944d3c5e4b494f48f158000abb37b80b5c763f77fe856398c664b0f1ddbcc0a9a2a672db9278f08b4bafbe2ec +b4af85fbf4040e35a686dd016adec037c99b47cc2e4dfccaf7870ee9e8c97bff30f3035992def2a9d4af323c0b3af8ae +a5ee32b8bd5f8fa9000da4da0bf00565659a43285393d37080b555d0166bde64d87317b2eab2d48a0e7b287caa989be2 +894d4ad58ecb1c9ebc4f5a97407082e56cb7358d7a881ba7da72321c5027498454f2c7fa2bd5f67a4b11d38c7f14344a +965be9eeaa0d450dacc1b1cc2fbf0d5d4b0dd188f2c89aaa9260e7307a2a1eb22db6092fccb662269e9a1abfc547cabb +805893c424aec206260c1c2d2509d2cb9e67ee528bd5179a8417a667aa216a3f318ed118b50d28da18e36c01f0805e3f +972d7040d4963b35260ef0cc37cd01746f1a2a87cedc0dc7b0ee7e838c9e4573784ea743f563b5267eb3905d4fa961ba +8c7156991d4c2e561888feaecf501f721b4174e7d14109e9deeac5a9d748301c07e11fb2b04b09799f0d34ff42cb77d1 +894722ac35af3d507e81d737d21e16c5ba04686f8f004aa75934aae5e17acd3e065b96e229eb011c2f34096f4c62048b +81237937c247c88e8e31e2c72412189fe59c1daf65c5513489d86cf29ee922c0bb08e5f7890f09f4ada7e5262083d266 +8cf62cda2fe0d9a6b42aa2a1c483f4ad26378c7cc2c2d1510a76df7560b07dba8528b33aaacb15f7f20b9d4c7c9f61f6 +aaf0921fb3e1920eee5d0acb59dcc268b42f4b435d60d25d30357edd7dd758d035919691bd15311d85489dfa2e5ee696 +92cec07be2247ef42002ebcaf65ec855611b8e893a5675796f2225f55412201b0bf9f4761924d0c8377b9f131e09e39f +8e514a62ac1e91773d99588415426c97ad63e917c10d762fe06ace5277a5c3bf3730e4b9e5d116f8493b9ab8687b70e3 +83932df2d923a5052468a3ea87f7b55c6a80ede3594046ee4fe233046570921822bc16555b92ba6aeabaef9b1dc0805a +a2b5bfb249de3472113fd3f35bfabf3c21d5609da62a27ea6aab5f309c9068d94bc58ba03efb4ec11be06306d59e60e8 +8106cf3ebe6f0507be8c6e8d137987315fe3689ecb75bb27980f36ba5efac504baccea0e7603549b6d126beccc278804 +a73ee70b6fe8c082443972102c453fc0e386852476cf22224fc0bfe554735c12f96037fbf10922795f4502c4f052b5f4 +932b27e175440169958504f3ed6400e7d6dcd5e716c19dcd0f15c56c04503ed133d5a993e111c016f141e32d68b29886 +96f7ce4595318e0b4a6b368f788ff82226aac676aed4ace343867f751de414453a9aaaabef6e6224ce5aedc3d5cf77c4 +a950c1e3bc9a14484997013d44d876374b939af437ae7c821c131fb886063ee9fe7214a25a0c7084f0b07b99412eff75 +a9dba3886ed6855303106a1bdd26010f294218684e1c178afcfea3f37a2f04fd01724a31d82de3449046617e3507a115 +87a2f776b32a6b550cf3ceeaf78db02819be74968d228b1d14e0d74a1cdf994bb500b7abef6619455e98d728701fac5c +8cd887b07e335edc0b27e6a660cebb64d210741395be431d79d570139687b056557159407459799a8197b6079644f666 +b81a61fce00588909c13a90c1caa150f15788786af443ff60ce654b57147601f7e70b95659e01f470334a220b547611b +8aebc51141544c5f3d3b99422250424b9800031a8fdfbf22c430907a3a446fecaa2392105d66d64b1c8e847240da4a6a +90db7dc12baa02f3f86d3edadf9434e2b9318d4f6f0eca08276b765dbb38d8eb0d08be2fe70adf2bf16ceda5db08d3ca +aa1839894152d548cc6ad963de20fb6fcc843bc9af2a2bf967c63626b8ad19e900894d6106265f38f3afccca317c22f0 +848e27b741496988a582515c0c8847b2bfc6a001259396cdeea1e1b1d2828ca3a626693a1bf4adf3a3d7f8b1fa3d75fe +a0aa11754d4ee136ac3ca609b17bcae77758763b2016544ca7921dddedd8aafcc7ad5f2b337c8bf53084eb8e43ea41fb +b8713b7aa1c112178195fdcc9b7024f46e6bc04c4e76c41abe620aa265287809200d98eaed6c9703fa97e81d6964f0ec +8605b5b33309e9ea6823542b85383c496794b8481c577497aaf99ba90496e794dce405be615bf92c7b6361460e6b82e3 +826fa34faa7f83e063a7bf172addfc07badabada59cfc6604fdf481d29085251c0a67a1355b2cbd374e2975934b84cb6 +b45d131082dc16fa53af010d43eefb79200dc23d2f3ee26af95ac6a5cebc49c84a9ed293e534ed16ff3ef9a4a25456ec +91bd6ce3c5396a7a0de489e49f0cdf6dce1cd2d0be7a410326423c3185bd1125ce1e610768be7f15f4e44b62f8834fc3 +903ffbe3d33fbf106c01c727dc3a385201a67ded70d4df623934882f69a3a96c909b027a124f3d70cb072b0046a149e8 +b405359db9d9ef4821a181b440ef2918c240595141d861d19a85867a5afa74d2972d22c988775eab441e734700bae4a3 +8abb756d027233c83751910a832b0ef4d28d100077f1c5d656720c94906f91d85dd0ea94b1cc0ed95b692efee14c786e +a78ee77ab476a41a3454160ba7ca4085d8b1f7057c63e76db8b07cf20afdeddd2250cd00771a6329133bb4ad48ccc20a +a41810271d8c37197aa9b3dfcefe3498e42f5978d3f3d59defff4676d6402d8575b40683834f184f143b6cfbfc859b3a +90c24a0750242660bcc6d487358a3cc015730538a0a8beb00ad5ac2ef33cb8ca8a62121e50bec8f3d2f43900f8e3134a +8b96c39695d864ef5796941754978a1fd612b369f6b77fe5ae6587beac936ee28190af8f0a3822b63060af35e49a5c8b +acde2548883d0e63c0fc257bb9dadd919aba60a985b69ebcfa1bca78acca42fc1322ec30bcc8e7c188818f858d04ad33 +895c86ae9ff8d95f2707d4838a3bc8ddb05b2611f0476f014b9c150d0e8332bc73285037a747426f09ac8179ba4e19fc +821761fe406e18bd86fa9ca9db99d382cd3b5c70c456f471fa3706d57763d147706304c75d54f51ce8f3115aa26e59d9 +a803a80e3e8f47dc3c59ea23eafdec017458eac648b360cd42cbd075e0dde6f6f450b48c7646fb1e178c04f82ae51a12 +91f40e1b6f588bd592829ce937996452c40be0fd6c43793c607866701ac6a8c7227e0891d45c6e7b1599382b0a3fbdbb +9408246d996a634a58689337f2526dfb3ba9ffef1d3ff91c32aa8cbbed900861ef25d6477308b67d76491edfcc70d65e +a492325a427f3df1c9c690c5b553daa8ac41f62f5ae55f425539222bacf959e2f67afabbba1732e120d3e7a6dcdf7049 +8fd0c3e15477cae228613a171b6e9ec29ddc63ef74854d99b638adeffe39f89f34346a42851e8445e855a9f2bbef0f57 +b735ed01fafa051004dbaad5e8c9e2faca8f6049ef9b590f256ea4d75b04594af12764ad4e6031735eae36f83179db93 +a7d35f43fca06c86b3425dcb68a87186834ba9740664fd657915771beca4cdc0fa2fc9b4c2e9d9bdad8ec33543ddfa59 +a1156e71e2db1b17df5da28747c88e091bd687bfee59d89096437ab4dc9a543fe5c5272d5023d72adbaab397a6fc94d1 +ab06a58bd81b33a411bade8d8c5232d38fadc2e38507159edea6e2e104b8ebd65ca02b05335118f691d44197b847a4dd +848b67a10f1e6ff8f5c228f226ef2ffeb67fb8f50925fc94cbb588d61896d9dc79726959e649898fd3354fe3ff7b7ee3 +aa933397361f32b388edcf832f0db172a38e756b34d5f7a4a050fa7325058006c22cede26ee27917e8f1b0f301792bd7 +89e49e7f02cfaae4a4b9c4180c9f6559d76e3a45774955859d4147970b1470dac37bdc9aedca1c32a20b045049161590 +adc1825d5ab94fc719f25d8c9773f4d518134ed88eb13ac33cb910b2be3523ef9ef88d9e4aea2418b806e20108317bf6 +96c4b444c8a023da644f3a343ebeeed19a8392d2ce175992461451c318a54273b76c3574d8f2dceda2947ddd34d1a674 +8aa7e97e87c8c5b29bbd51a6d30396a6be1fb82b716ef83800f2c36d5b85467ade7e0f59d2db82c310fa92a9265f0b03 +9146c32d99f02c3a6f764dcd9b4807f1585f528ac69dc4f84e4380f6fda4f9d5057c375671d51e7aca2b2b4140e83da0 +a10760a533d9bc57536bcaf65f080302086aa50225437efd64e176841544711828c23a15c49c0dd1f357d3f10722ab72 +acb0811777e17f7ae7aaba5f6fce81b759c067a4908730916195a2505c7450d0e6e2194c2ef0f241090597d58e70de47 +b24f161e9bcdbad56665e2490b5e4c7768390d4668cd69a04ed74739062dbe832636dd33cda89e9b0afa8c77e93fc641 +96b4d01106b831868a88ef016500ef2fa42d0ce87a37ca8ca4194a92a22c113edfe04eb2ca037329f3c1acc635148f55 +aebbb95fb4f7adcc8e7a217aeb73f9e037cbb873d08c1cd9d68c6c6834511adf1af8b44567fee84327599bdcb734dedb +a9bd8b17300532fb94d028659bcafbe7bbdf32f8945baf5db4cfaa1bac09e57c94cad0ba046b4514044b8fe81ea8596d +a5557cbda599857c512533e7cadcf27bf8444daa0602aa7499cafc1cf1cf21f9d16429915db7485f0e9a1b5046cf01c5 +8810307c40bc661c478a9747ebf2a30e5a5ead942d1ac0418db36ba5db0709c476f7d19685cabe6959e33ec1f3bff914 +8829b741f41f2c32e10b252d9338deb486dba2f23996a44cf1dd888ad967a589d51329be34d764139f372a1043f6c2e5 +a6b4728d18857c5fa082fa67bfb3b1d801e76b251b1e211a19c87cea5fe7ce757f943c85071f7a03a718388cd5690e95 +86da7f397e2533cd487f962ae58e87bea2cd50af70ef2df9ea0f29f70b5843cde664d30ec207ab84fc817f3851277e02 +8085776ef4ac6d42ab85b9d9135ecc6380720efd274f966544eeedf4684028197de76ecab919fa5414302597e1962bca +b05a065c733033d223ba13d16baa7a97bd8c8b8b1f0e59a9bdd36ee17e9922d48eb39bd180c168b122088a77f0bf321a +a89343fe44a93023dcc7ef71bd3bcb6786f68e1885ad260edc56a52445d34757f476395ba7ad35437f89bc573c7618dc +a114a9cd6105b524f3969c69faa2e09afe21753a93361a296f9e0e3b4e3e63726ddf2e6bfd3ddc046043e50bd44e539e +8a5611fec539cf681c05636bb580f29acc06f628bb012649ffa41ea6c1521194a5643d5dd843f09b6eb2c3bdb4d41acd +ade247c4011ec73ec90b72f35afa59a999e64ba5a7e664a4b30874fea53ba6a14a76a41b58a5f891a20d019e5f091bdb +905b5d96df388160ade1ffe210d0c6d1979081bc3de3b8d93ac0d677cc2fc2dc1ef6dcd49d3947055514292a3fa2932e +a9520796ca9fccd11b7524d866507f731f0f88976f0de04286e68d7cf6dbd192d0d269f0cd60fd3d34011a9fe9e144c2 +989a1edf4d7dae811eb57a865c8e64297837ffeeaae6ee6ac3af0f1044f023f1ca552bf00f1642491f0f0f20e820632e +879c8e63713f4935ed6e020559e140ea3073ced79d3096c152c430141272117b4fd9a9fc3eef012e81262df02ea14bd7 +95074738ac1540c0312274333acd1ecad9c5509fee883c4d9295fa8d8200f6e637c363de395f9fa612f05c0dc58fae88 +a770e4fc595269eb806b113ab3187ea75c8f96b57bf9fcfaf535f3eedc1d4d7e6285a20990575de0ff09f62d06ed0692 +81283e5dfb6423439ff513eca1cc316941d196df8da2d1069d2d0b63f5289e630af2fd4119bc0144c002d33313372dab +abd1b108e743887b78f698f2aba9d5492f87a22868d1351d705d93a1084fd45be67170c68a6e18b07f400d9a01cda8c2 +8509c3f67b92908cea8144f4e2a71631a66a61ac3547601c788907e52e380e5fe8ae4110aed95d13c67d3bcdd5b55a61 +8fa5a790ec5cce6d4114128c295390120869aac5490a82feebd3c37a167120df2e7fdfaf2a4050a7dfebf48fb093212f +944753e1ea7d8bc727d46a7702077dc01dc0c6574e8263a16579b57ee155ca5901f71bb347a01a9a922b329d3ff75135 +b46bc1fd4590b7a6275e20036d247c5909fc549c78e95b64ae7ed96e3b05bb044840f19f7650ebfe7008ba09fa83c3c9 +b1e47e4d88e59a06c465348c6cc4181d40f45b91e5e883966d370c26622c328415c6144aa2f61ddb88ec752482c550ca +8bd4f8e293e3f1815c7e67167618fb3b0ea76424bc0985908957cfcede36109378e41b4d89555b8c2541b4c447e00461 +a70589a867b2bfb63d0106083d58475d506637148549ed35c83f14e5c8de996e1b1f3447ecc80cf5cd134ef4db9d2fb6 +8048b80ba6131d07370162724127b0f7cb17fa7f71855e55e5a75bd0a9e4fd71b0d0ea2d16ec98858e458528df8d06b5 +97326cb94bae7530f4ec3235770c5a7ba042759e789d91c31fedbd979e3c0e6a2c69e2af3c1979c6fe0094274dbd53ce +a18e9c1d3eabd62af4e31a4b8e08494f4167fd4598c95d0123f39c46c53f9e93f76615900246e81a286c782ac37c569f +80309c59d4522b15aba617cd3c6238663e8b1c7ad84456346082c8f281140fc0edf9caa19de411c7e7fb809ca4fa3f4d +8e450c0990e2f65923f252311623038899eeff7b5c2da85b3a224e0ef7132588b291b782d53c477ecb70f34501466178 +87843f96f41484e254e754c681a65681b9ae5c96c292140368743df9e60f7e2ada58ca2bb95fa39abe064b2ebf21eeba +858e8d5bf2a1cf26d8af5036b28b831d450a446026f58a1734b696c18f1f41482796b91cab0e5b443dd2f0b9cffa52b4 +99627dd6bad8c05c5904cd23aa667d664da846496dbbb8452705c4ec01e1480e9c7295504a5a8529e4a0c842306b038d +b64b33256c18b2c886a837a0c0730fdfe73befb0e2796207c4dc592c5a33cd51f8c2ef47c584dd5773abf9ce9c1b0082 +944f6da2a1546f0bfc4d98c3e73c79e935e33d208b6be26b0b5f8df6d0e3b74a5bda649853b99281bd3a3ec799a7dd04 +a266d165435784d4e884640155e35b2a911b3f89e1e715986de419b166a36a341ba724877d80583fa3da566f6a828971 +adff2698409d0756e78c534032ee926560c13d578cb178d5073172d049ebbce32a92692f7e2033ec781b9b0d894ddce0 +a91933f110756c699c28bf9e24fd405bf432002a28c4349e0ca995528e56a5a2d101b8d78afa90a178ff1a9bf2ba515c +8e77839c0eb4da2d01e4053912cd823eddffbdc6b9c42199fba707ca6ab49fc324288b57be959fbfb11d59085d49324a +aa124517c76692036c737e987f27c2660514e12a953e63ff4bcb269dd18fc44dae95e282de8444bed09639ef6577af88 +b285deae99688f1bd80f338772472fa2b35e68887c7eb52c4ef30fc733812444c5cd110050275ad999d5a9b57f782911 +8877b0fa85b44ef31f50bdb70b879fa6df5eb1940e2b304fd0c8f08abb65f3118fa3d97ff93919038c1e452fb1160334 +8a89f3b50dcbca655024542ca7d93df17deff5c7d01c7da2bdb69e76b3e0b4490d85c800fb3debb4b0b4d20c9527f7ad +b7e5dbe36e985354ac2f4ab7730fea01b850af00767a6c4d8ee72e884d0fe539bb81f2e34638fcf5d07b7c8d605f4c06 +a85a1d78f6d4f9d5d83ec0f2a426708342d4e4a5d15625554e8452f6a843d9aa4db0c7e68caebdaf767c5b3a6a6b2124 +a518078a9dac63c5bf511b21ed8e50d1ccede27ebfe9d240937be813f5ee56aef93dc3bf7c08606be1e6172f13f352ce +91144eedebda4d1ad801654ef4ecd46683489b177ba1de7259f7dd8242c8c1700e15938e06c5d29aa69f4660564209a0 +a16c4657bc29d1d3271f507847b5a4f6401cee4ad35583ad6b7a68e6c2b9b462d77b5dd359fd88ea91ce93bb99130173 +85b855778f4b506880a2833b8468871c700440a87112fa6a83fd3ddb7e294b3a232d045dc37dfc7100b36f910d93c2ae +8d86bb149d31bfbf1fabcae1b8183d19087fd601c3826a72a95d2f9cedb8bb0203d1136a754aa2dd61f84b7f515acfa9 +acfe7264eee24e14e9f95251cbcfdd7e7f7112955a1972058444df3c2d2a1070627baefada3574ebd39600f7f2ea7595 +906bd14ecca20ac4ae44bff77cc94eb5a4ecc61eba130de9838e066e8766ed3b58705f32c650e1e222b3100691b3806b +8f2cbc7b8593c4be941dd01b80dc406fe9dfdf813ef87df911763f644f6309d659ea9e3830ff9155e21b195fc3c01c57 +a68eb15ed78fae0060c6d20852db78f31bebb59d4ddc3c5bdd9a38dbe4efa99141b311473033ff8f8ea23af219bc8125 +a95cb76c9d23fc478c7e8a73161f2ff409c1e28a2624c7d5e026e3cee9e488f22225a0c5907264545a73e83260e3a4ec +b76f90e55fa37c9e2732fd6eba890dd9f1958c1a3e990bd0ce26055e22fe422d6f0bcc57a8a9890585717f0479180905 +b80cc95f365fabd9602ec370ca67aa4fb1219a46e44adf039d63c432e786835bb6b80756b38f80d0864ecb80e4acb453 +b753c86c82d98a5b04e89de8d005f513f5ea5ea5cf281a561d881ed9ad9d9a4be5febb6438e0dba3d377a7509d839df0 +a664733f3b902fac4d1a65ea0d479bb2b54a4f0e2140ed258570da2e5907746e2ac173ace9120d8de4a5e29657ae6e05 +9479722da1a53446e2559bb0e70c4e5bf3f86c0ce478eede6f686db23be97fcd496f00a9e174ceb89ab27f80621f9b80 +b707fd21b75a8d244d8d578f3302d1b32bb2d09f2bd5247dff638d8b8b678c87d4feab83fe275c5553720a059d403836 +93214c16831c6e1d6e5a1266f09f435bbed5030c3c4c96794b38d4a70871782002e558d960778e4465b1ff296ffedad8 +8648f84e18eb63dad624e5fa0e7a28af2ee6d47c28f191be0918c412bf24b5460c04bf2b7a127c472914a0741843f78b +b67f61e75d6b773a6b58b847d87084b94f3cdac3daa7bef75c2238903a84250355a986b158ff96ba276ca13a6035fdd6 +ae9b094b7b5359ee4239d0858d3755a51aba19fce8ad82b0936cca48017523319c3309409ea6e9883a41bece2077e4d8 +8d1d8e1fba8cebd7a0e1effea785a35e16b1a10842f43e2b161d75add11eccf8f942d2ae91c20eef6c1a0c813731ea9a +b82bd387458e3603782d5e2dec32ae03890a3fc156d7138d953f98eff4200de27c224f626e3648e80cd3dfc684c4790f +a6dd02a89ad1c84e25e91176c26355e21a01b126c1df4d22546159dab9d502dbc69bc0d793a017c1456516e4aa5fa53f +a9ab74a5c5459b8500beb0ad13e9cfe2656e966dc9b4f3f98bec7588023b4ddebf74e4fc722d30423f639f4ee1b2587f +b03e5f33ab7ecec12cbc547038d3fa4f7ea0437e571891c39660c38d148212d191be29e04eb2dc001b674219b7a15a9c +925df4fc6e898ca55090ad1a8f756cc5014167a042affda5b24896eeb6aac408545134920586a8e1a2b997de9758b78a +98c8580fb56ed329fad9665bdf5b1676934ddfb701a339cc52c2c051e006f8202e1b2b0f5de01127c2cacf3b84deb384 +afc3765d374c60fac209abd976fe2c6f03ce5cc5c392f664bb8fac01be6d5a6e6251ac5fb54cfcd73e3b2db6af587cbb +8e7e98fb5a0b5b50d1a64a411f216c6738baaca97e06d1eba1c561e5c52809b9dab1da9f378b5f7d56a01af077e4f8cf +b724bf90309651afb2c5babaa62dc6eac2b8a565701520fe0508cee937f4f7b6f483fc164b15d4be4e29414ce5d3c7d4 +9665160e7bf73c94f956ecb8ba8c46fe43ae55c354ce36da40ccc7594beae21d48d9c34d1af15228c42d062a84353a0c +8600ab3aa86b408ee6e477c55572573ed8cfb23689bbdadf9fccb00161b921ec66427d9988763a7009b823fa79f8a187 +b0d8d19fd1022e7bc628d456b9bd1a2584dce504eb0bf0802bdb1abd7a069abbeeccdb97ce688f3f84a229342dbc1c33 +8f447d5e5a65bb4b717d6939cbd06485b1d9870fe43d12f2da93ca3bb636133a96e49f46d2658b6c59f0436d4eede857 +b94e327d408d8553a54e263f6daa5f150f9067364ded7406dcb5c32db3c2dffd81d466ee65378db78d1c90bc20b08ab3 +b58c02781b74ef6f57f9d0714a96161d6bfa04aa758473fb4d67cc02094cd0c0f29d0527c37679a62b98771420cf638b +8cfa0a687ea51561713e928271c43324b938aa11bb90f7ffaa0e4a779b3e98899f2af59364ce67b73a46a88748c76efa +95d6d39c814c5362df69116558d81ce6f1c65fb400fc62de037f670d85f23f392c1451d43341c59bc342bc31842c8582 +af888b384c52d9e04e4db6c4e507c2037eb5857e9bcc33acf84fc3a02d93cbde8cce32141fce9f5fec715b5f24d56356 +a7822bbc3c236fd58bd978f0fc15fe0b60933a0c953db6436a233441219418090ae0c07c490a6548e319029771cdaba7 +8c53729f750922e5eb461774be8851a3f40fe42eed170881cc8024d590bf0a161d861f5c967144d15cdcdc3dc6b5cf88 +a052a25a4aeab0d5bb79bc92a6ae14b5ad07d1baca73f4f6684ccecfc7ea69bc21eadeb9510452fdba116c0502dd698f +923946b83d37f60555dbac99f141f5a232728c6eb819a37e568c8c6e4d9e97a4229fb75d1de7e9d81f3356f69e6d36f1 +8cab82cf7e415b64a63bd272fe514d8b1fa03ba29852ec8ef04e9c73d02a2b0d12092a8937756fdec02d27c8080fb125 +b1123314852495e8d2789260e7b3c6f3e38cb068a47bdf54ed05f963258d8bcabaa36ccbea095ba008e07a2678ec85a7 +a685b779514961e2652155af805996ceb15fb45c7af89c5896f161cac18e07b78c9776047c95b196362c9ad5430bcb22 +b734dd88f6cc6329c1cb0316c08ade03369a11dc33191086c6a177cf24540c7ceee8199b7afa86c344d78d513f828e81 +b0bf492fb136ecdb602c37636ed4deef44560ab752c0af5080a79c9f76a1f954eba60a0bf6ba8bd7b8cac21848c29741 +a5c74682323e85ac20f912ab9c1d6e1b9246c4c829dca40c8a7d58ec07ea0ad3524be30623f351269552f49b65a1245c +837403b9cf830fb33ecc11a7c8433e07745973c36acdeb3fc9ea8f7d8d690d462e1250b7410f79f2f4180fe8f3962a4f +b03d64b944d49c83608f2c5b9c14070c025f7568c4c33d4eeb1da31d07f0bc5897e498b35b50d557ee129f0c3c68e254 +827272aab8bf757e2483156e00fbebe1093a58070dd3af9855bbf946c7abfb9c8a850a6a8acda8c620902f391f968b8f +84c4eb863a865282d321302d06b362f8bd11c2bb0090f90ebffedd3eb3e7af704cff00d39a6d48cbea4262942e95200b +b044eb91653dc55dce75c8d636308a5a0dae1298de4382d318e934140a21ca90e8a210e06fdf93aadbbeab1c2ef3904a +a8c08955a4378522e09a351ecb21b54025a90f2936b974068e80862803e7da2b5380c4b83b4b4aad0409df8d6c8cc0cb +a763a5fb32bd6cb7d7c6199041f429782deacac22b6a8467077fab68824dd69343ebca63a11004c637b9cb3129dbf493 +8c44c8afa9a623f05c2e2aba12e381abdb6753bb494da81f238452f24c758c0a0d517982f3999d2537b7279d381625ed +8613f47fda577cd3bda7c99b80cf4b2dd40699edfd3df78acb5e456dd41fd0773bc8da6c5e8cbf726a519b9fb7646ccc +b21a30d49d7e1c52068482b837a4475568d0923d38e813cea429c1000b5f79b8905b08f6db237e2eccf7ef3e29848162 +b9bdf4915f3fbb8d84cdfd0deedf2c9dc5b14f52bf299ef5dca2f816988e66322df078da2c54b934b69728fd3bef40b5 +993b45f389f55eba8e5ba1042d9a87242c383a066cbf19bc871b090abe04de9ff6c1438cb091875d21b8c10fac51db58 +a85a95d14633d52d499727f3939979a498c154fd7ebb444b08f637b32c1caf5cca5e933a2f5d94f26851ae162707b77d +b9874c7c4be1c88a9646e0c2f467cd76bc21765b5ab85d551305f5ec0b4419e39d90703d4ac1bb01feb3b160517e97b7 +ad6771177fc78812904c90594712956357de1533a07fec3082ba707f19c5866596d624efc3e11773b3100547d8f6c202 +a79f31921134f7197f79c43a4b5d5b86736a8d3ad5af1bdf4ad8789c2bfe1c905199c5e9f21e9f446247224f82b334f8 +a7f1b6c45321222a350a86543162c6e4e3d2a7c2dce41aeb94c42c02418f0892dbd70c31700245d78c4d125163b2cd5e +92abafe3ec9dbe55c193fb69042500067eb8f776e9bf0f1cb5ab8eb12e3d34986d1204136856fb115c12784c3b8dea6e +89bc761238a4d989006ca5af5303c910c584fe7e6f22aa9f65f0718a1bc171e452c43695e9f5a591725e870770c0eceb +aa0e44c2b006a27d35e8087779411ba2f9f1966a0f5646ff6871bcf63a8b1a4a7638751b94c9b9798ccd491c940bc53f +8736fe82862b8106e7fdab7b5a964d87ec291a74b8eb1cb5a6c046a648c1b686064ef3d52297043b8940bfe870c712f8 +956a3def1942f05144d8e9c3a82fd2d3610064b53b9eefde3d5594a8f705bf8f6849eb2c22181796beffeba43cc74ee4 +af27416d00cf97d5a1f4a1b6b51c010884cceca294f1151c3b684a3f83c3c8a3c30771df1166d833cbddf6c873c400c3 +aac3b8dca2336fc4ffc63c362df461289e4bbd3418c621bde6c581d3ecedf66e2b3e523d4db39e3d8ba014577bf85efd +94c3a8167f62074e5b28c2bffe4b6ce645439a9a0c5da3ca1b3ee956590a465d6f84a8a4dbbe9070ffbd6bbc734e4d62 +95e23ba6986d25ed4451215da05bd72c5491528271726d79a94c8cb16aef1c85b190d6c5b8a3a1191c7cafbab1dccf0c +953e3dadb5ad68f7de31ac09692948655d174fe16d88b96930ef35b331da7f1dbc4c17863cd07b4ec3135b5205891a27 +915d018f18b5d63cb3301c2bb5c6e85e75a88ba80663c964d06575b6bacbbe59139d030b218ce0998271d5b28c00b26d +8c871ba3dd138a908b2f7effeea0e71df096b23e0dd47cab10b9762b250abfd1221da94a8ee884e05bdf02271fb85a04 +96bad5c6ebc3080ecbe337409ae398bbeada651221c42a43ea3b7c08c21841ddbcfde544c9b8d4772de6f2ce92c0b963 +b5dbcd0b1c44c62108841558ec0a48df4b327a741e208c38b1c052321eda6e6ad01af71d49dfcdd445ab6fa6f0c34e6d +97dba59219b69e8aef2659d1f10bbea98d74aefff1f6451de3f41be39acbac0122b8ff58b02e90554469e88911ec3547 +b7e5682ec306478be4858296f5d03364a61f3260636a4242f984d351a02e8723378496beb30c4ca22def9c9ca193ea70 +9656a7a3df4d11df3d8bc35930dff70a5e78a488ca57bba20bb06814fc390fc6c7cb3f39b22134992aad196cced577de +8b269695aa63eb56d0324ba984279dc4c88e565321f1d61d553622bd4f1910d5eff68393d3a830eb924472bd478c2aa3 +9177bcd04b28c87bc0440268b4c8995c6790cad6039594971b2c177f0e197055231e776927d3fa30d98fb897a2ba401f +ae0e943973482001c4f214b9da82e1c27e38aa254d0555e016095c537c835d3702bc2de5c67b234ab151e02b3b7a43a6 +82fc719a7d38bf4787fe1888019ad89fbf29beb951d2fece8686d2beb9119d0c8c6d13bc598748c72c70d73d488140ca +b716dc66f87eb16b95df8066877353962d91bf98cf7346a7f27056c2a4956fb65e55cb512af278783887ab269e91cd76 +81d58cd8bc6657362d724b966321cd29a1b5cdc4601a49fa06e07e1ad13b05e9f387ca4f053ed42396c508cd065c5219 +b32ad0280df6651c27bb6ddbdc61d5eb8246722140a2e29c02b8b52127de57a970e1ded5c2a67f9491ae9667349f4c46 +b68a2eb64cc43f423be8985b1a068e3814b0d6217837fb8fbfd9c786db9cca91885c86899c50a1242040b53bf304ced9 +85887515d4e371eabb81194cbc070e0c422179e01dbda050b359bd5870449c7950e6b3947b7a4a0eb68199341cc89fc3 +ac5fff3c27dfbab78eb8aad37ac31cc747a82401ebf3644a4f4f5aa98d37b8bf3b3f4bd8a3428b32a127c25c9e19d239 +86fceaa6fbf8913553a9e1e907fcb1f1986d5e401a7eafd353beefd1899d571454fea96ff5b2a21254d9fb693ec94951 +b6778bb296d3f0de2531b67d36fdbfa21475be0ca48b9dfcc38f396c41b557823735ed0b583e525a2bae1fe06e04058c +898088babeb5b9866537d6489f7514524c118704abd66b54210dc40a1c1ddb0a1edf7fe0b6e0db53b836f1828ecf939e +b27854364b97274765f0fb8d1f80d3660d469785d1b68da05e2bd1e4b8cbbe04304804d4c8aabb44cf030eba6c496510 +8c55bbf3603dc11cb78b6395ccbc01e08afcef13611f7c52956b7a65ccf9c70551bff3ae274367200be9fc2d5cb26506 +947726f73cd6281cd448d94f21d3b91b96de7ad3ff039f9153befbb5f172db9f53cacb4f88c80a3db26e6a0f7a846eb0 +a7b733a05e97528812d71cecb4f638a90d51acf6b8fcbc054787d6deb7e2595b7b8d1cbe1aa09d78375b5e684a2019bc +8d5ca6d161341461544c533314fe0a6655cde032c2d96f0e4ea7e41098b8b39fa075d38e2d8c74e2d0308f250d6cf353 +b960e9f081393e2260b41f988935285586a26657a3d00b0692ea85420373b9f279b2f1bb2da2caae72dd2e314045f1bd +852a49c7388c10821b387c6d51617add97ba72485f52be95d347bac44c638c92e9c6a44ba0d32afc4d59178a497d944a +8412162a65147e1334ad5af512982b2b48eef565682b3f3e0bbe93fbc5e1103db9375a0c486bdb1b2c57e4cb3a8e7851 +8f52c3eb5d4f1e1e82cfd2b291d4910195427603b796f6c311deb35ef14a01a57a9e6cad39619ad108f3e86f384f9e1c +88d221088f2bf0103c53e44d0d96cd7881ec2b0a965db9121a47481771a8b796edd5ac23c4f9c208a171dab301f7d3bb +b49c3235e8b3617ed08a1891b9e2bcb33dbdacceb94ca96330555b7e00904fe6a749ced9312b8634f88bcb4e76f91cb1 +a85834215e32f284d6dfb0cbfd97f6cffc7b9d354e8f8126d54598bb42d7f858a2b914cf84fa664069632db2ff89a332 +aa3d48eb483c6120c27d9b3e3d0178c1c942632ff54b69f5b3cfbc6ad4ff5b2b9ce6eb771fd1eea8edf4a74c97027265 +a446cfded353cdd9487783b45846402b973cdeddf87e2bf10cf4661610fff35743cc25e8d3b5771dcedfb46b018a5d18 +80998377b3b393ef3073f1a655ad9d1e34980750e9a5cfb95f53a221b053ddb4d6985747217e9c920735b0c851d7551f +a35ac469790fac6b8b07b486f36d0c02421a5f74ea2f0a20ffc5da8b622ac45dfccabfb737efa6e1689b4bd908234536 +8fb1f6d8e9c463b16ac1d0f36e04544320d5a482dd6ffaec90ea0f02b4611aaca984828bf67f84dcc3506b69af0a00a1 +b6e818d61aea62c5ed39c0a22ccbb327178feebdabda0c9927aa1549d2c5bb0637785c4aed2a6d9a7b4989fa8634c64a +b4e7208d16018bf67caafe996d436113eac619732e3f529a6efb7e6f094d8ebea55b7be0e122be075770f5957b6ea6f0 +b691d38b552befac61f6d367287c38d01fec73b7f2efdb6713ca30314a37fb7c177eb111fe6bee657f2681014e07630a +9817587e418e6e7e8e97ae27067f17b55d25dfb14e98f63f530620c855d9a348c9fa571c8508e2741f902f8b9fdc0c5c +b6a6e5ca779ba140bf1d84cd5394ede8262f7479637ec0087a4b152243a1774ba916d8115ce759a3bebd1b409de5f2fc +b53d1c84ad766ff794bf497db3228efd2cc8ed5fc1958d89c1126efdff361610ecb45ea8e329b39035ab00a66c1259c7 +adc31333c507c8e0f4aa2934fcdca57fd9c786722a50dbd5404e129541f7ac182cc7373bf14e1e4e06e6cf94b31b90eb +a82b7fde4642d982d95cec669efee140ad797a2442c7f6620580527d163accbf021b893446cbb8038ea82fe25b15d029 +91f7acf8a8903979afa281646fdecb54aa4d2ed905748e156e92f0910de268fa29d67107d40863935d677d1de8039be2 +86fea71c6d43a7d93216a92fc24dfce8521fd4534a9558b33762d002081247867a6eff54cad7116023277fb4049403ad +8ae5369a7f9f4c91f3be44b98089efd9c97c08f5bb4cd8b3150c115ecd86288fa0865a046a489c782973a111eb93966e +b6fb9e829aa2c81c2d9eac72bb2fd7f3a08e0cd763532c2ce3287444d33cf48b3621f205e9603ec58525934b61a795a9 +83e35ca808d84e41fc92115e9f6e283e928c3a614e6dfc48fe78c33b6411262e7bfa731eadb1e1937bc03cff60032e1d +832fca5196c95098ad47b7d24ba2f9d042e1c73ad2273edd1c2ce36386796ccc26e8567847697f3fcc2a0536a2a2087a +8fdb7038bc8f462ab2b76bf7053362f9c030019f1b6105cf42219a4e620ecc961e3eacb16a8e581a562a97f1418b0128 +8d3a5a404b51b1ad8ce3b23970e0d5cc57b573922341008e3a952a1dd24a135e19e55b79d86a70cfd82e1c0e9630f874 +ba00c025c1c21c57c03cdfc0bfd094b35422281ff0a64b68b240617aa58c6b18800af5f2047d3ff9068bbe987d6c7980 +b468f0dd51964b3806b0aa04f3fe28a035e8f5567fc7d27555be33d02701a838b8dbfe1348b6422c4eac46d2c75c40c7 +8a73a18c97da9958903c38584b08d0e7e26993a5d9b068a5e0e1ee0d8a873942745cf795f94f7a3d3ba88790a9fbb2f6 +953a0a40c2c8102723736854d13b228698c14a02d85c8d2e61db1a768019ac305faf0d5db62ac976430ce087a5b20f1e +8998219da6b34f657cb8a621c890a52cb98c2bc0f26f26e2af666eebeadadc5e8bdf4f830a91d04aca8ce186190152c8 +8941e08c3155ad432236ed05460420a05dd0aaab30477493ffb364b14c00ea5b9183d30d3442b6321d2d20c36e4f5c7e +93f293ff7fb56cf5b03aee6f3ad2ad78444398ed5b3be56d7bf5b56b5aa5a2b980d13895dd57a5726d1b067c20cc55e2 +84a16f313e3f75e31824f58d19ab24c6611fb4c75140a7cadc3c166f68819547c1d0ff7f7d13f5d8ae30dff1d80e2aa4 +b6e3e830b15039d3e28b08f5465bb089eade11ee3bd80afe39e010df7db1fcf0c56d698717677a41ddbc91eeaf6544d3 +95e928e6dfff51351281568ae72da7d1edeb6e9fe01f30af0499e7505ba35a22b5bb919d41bb809a432dce83f3977663 +aabeeb60ca46f9b0232ff82ea7766dcab8cc5aaf9d23539f30174f9486640bc9312868ca493b59b314519fc399973e47 +b393a11e957d0bbb3ecf617b075b5906a3450b348e62916c04791b366f0a7397cccd6648440ac544bc30526e1f95aad8 +abb5bfc3964a6d246da60bd809d0ea6daf4f8222efdc12ceb6730194e85f413ee7eb03bae300abf7ea900dbbc3d08971 +96c1bd1d1d216a4bfbcf000c123f296c0d31e1684e9e3884c14df23bf528c8d599f82bb98fcea491716b617216a8e0be +92d1e570a56f1741fd9f3d9f488cc336421c6256c14a08d340a63720be49b0029e3780e3e193a2e22bf66cc652fa22a3 +8769c08551e3a730e46f8e5d0db9cf38e565a001dfb50db3c30fa7fa0e98b19438edc23c6e03c8c144581b720d7b33a4 +b850bd67fdf5d77d9288680b2f6b3bc0f210580447fb6c404eb01139a43fccb7ed20051999ae2323ea5a58de9676bfb4 +80285da7a0aaf72c4528a137182d89a4db22a446e6c4a488cf3411937f4e83f7b00ec7549b0b4417682e283f91225dfe +80520368a80b97d80feb09dbc6908096c40ff7120f415702c1614d7112b0b57f6729581c71f4a3ce794ac959a46494ff +9817b4c27a490b1cd5a6337e7bc7e8005fa075dd980c6bf075ddfa46cd51cc307ad1d9f24e613b762a20fc6c877eab41 +ad66bda1a3034ec5e420b78107896ecf36126ce3ef9705163db259072dfa438c6107717a33572272062b9f60cb89557c +876114ef078c2915288e29c9abe6b0ad6a756b5ee2930ba1b8a17257f3f0557602d1225e8aa41ce8606af71ada2a971b +aa3d6cde4c3b9d3d5d0c77a33e67f182a3e1cf89b0921423b2024236171955b34afc52b1f25b1dad9da9b001371771d7 +984d3e3a72412d290e3459339757af7520d1739c7af0cbcf659c71999328db44f407d92e8a69fea11625612c49eac927 +ae890d0faf5bd3280dcad20a5f90e23a206661be8842375fea2ab22aadc500849ffbc52fe743b376d46bb926cedae6a6 +b1f231f3f4d710c3fe80099faeb56dac67c1baf53b8fe67a9920fe4f90e52cb9a4bf19211249a6456613b28efe337f18 +8caa54b418ba609d16520af3dff2e96d5f2eeb162c065a1763beb926547b2cfb3ae41d738db2c5681a9bc8bc9e6b9a1a +932157ff56c5ac29cf6cf44f450c882b3acfbb9f43d12d118da3d6256bde4e6eb3183aea304ab6967f37baa718ffec99 +9360bed8fc5b6aac36aa69473040689bfc30411d20ffb7275ef39b9ff5789f9055d149383ce9f0f7709a1f9d683adbfe +98b5b33209068335da72782179d0c7aeeabe94b5560a19d72088fe8323e56db7ce65debe37a97536b6b8a0ca3b840b61 +89a385c11be40064160b030a1bb28c3921fc8078522618a238c7ea0f86f34717ed9af9b4e2e20f5128e5f7fc66ad841e +b615703cbc64b4192990cc7e4903b74aed6a0076ce113b59ef7719197ffa46fb29eb78ca56b49873487432d0625c0faa +90f0d77abae9d3ad73a218e5ccec505ad108ea098451461567ae8ef9661606ca8e78df53b5d628b20b7037bd24622330 +92e0e7cc4dfadc5fa0ee6da0c8de0493030db6e54ba0317f52f232a6708b732068b6077bd13a17eb7eb40b88368085b5 +a24dad20094985bfccc6df1343506ed3bf9dcbdf4b2085a87627a5d71f7568db067304e465f8f380c5c88e8a27291a01 +8629a45a10619354c84bdc2f6c42f540eab5a46f53f2ae11970433d7a2aef007897590bf31dfba1c921614c6d6fe1687 +84ac64040d4206f82b08c771f375da4b7d752e41d2aa0da20ce845f6bc1b880a855d3ee966bca19b8ec327b4b43e7f0e +9608e6050c25996c052509f43f24a85cdf184135f46eaac520a9a6e78e0d44a6cee50ebc054048c708aefde8cd6651c2 +a32032b0e0d7cc35e480c328f315327f9385adb102a708c9ba637878deb74582ae26bb6d6e5f8c9e3a839b0e0154b82a +b7e3c78d63acc6564a49e9f00b0a820b56d4f37a2374af1f7f1d016268011df9e7af0670ed2b0eee961f15aa948328dd +8b88bfdd353acc91ad0d308a43e5fb40da22c228f2fe093c6d6904d70f69c6203f56636ed898b05df51d33f1095ef609 +b1d7a430c51fc857af55047683fc18c453b013527196c5e1bf776819a3dffca802217e9249ae03f084e2ea03ad67fcc2 +80558e28a819ddb5e72e97c54be0f57c173ccf78038d360d190b7f1350a19577b8e3f43fa2f7bf113a228cd3b965b2e4 +b4b2ec44e746c00dfc5661ba2514930934fc805cdc29adc531c02d28ce3cc754414b0485d4ee593232cd1175f357ad66 +b57cee5d32835f76572330f61ccd25a203f0e4a7e5053d32965db283aad92f287645533e8e615137208383ec51b1fd99 +930256086b419a8a6581c52590d0dbd9f8a3564c79424198fca3866b786df2f6098a18c50dc4abd20853a7184b1ce15d +8e75fd01181cffcd618a983492390f486e8c889972a46c1f34a4e1b38f384e8e4efc7e3c18533aa2057da9f9623e2238 +b375d927dd988429f9e2764e5943916131092c394fce13b311baa10f34b023dd3571da02553176091a0738cc23771b9a +b9e28e4c0d0477518034d000e32464852e6951c8db6f64ccdb1d2566f5094716213fbf2fc0e29ac88d0e79f725e3c926 +963981e99392afbd2b8318d5a6b2b0cc69c7f2f2f13f4b38dddbfedb2b0eaf0584aecfcbda20a4c60789c15d77970a58 +a7804e1977aa77c263c7c001afa6cf568032dea940e350d6a58ce4614f1a91c13ae1c78bfea740c229dce2444556976a +8787204177da3cde6d35cd3497fa8774d244f9faa9f4bd91b636a613a32ce2ea0326378cf9c4cf475e73ef751b355c4b +895aeef46a07152a04ec812f1aa1fd431389fa0ef6c6e96a5b833e70ea14073bc9984757a8ee456dbec9788e74e6f0ca +8d17f0e5826783440d1f0ec868003510a4d9952bfe4a638e44a36d94482ac18ba70ef7ff773bdf7a3b62d714dcf0fcba +810d5e36b31310b2e054a666d3b3f7ed16dfcb1765532d87ca2a3920316f0187303c27dd113db145d47e8961062a6c03 +b4e2fb48ae04cf8580bb6a28095076c9b95e5f13122b917328f334d4ac8a8648ce442919e28319a40148987350ab5303 +b85549a313544fa1eb3ceb78473b7d3d717fc85b808de7b79db7dbd0af838ebb020622a7503f1cbacab688dddb648f84 +80665adee057088eae827a5fe904ec3ad77d8843cdce0322d535e0659b4abc74a4d7ddd8a94c27f2def5c34ac2c038ee +ad72fc19c2ce99b5b717e35528fe7d3ac8add340b02ebeb4889d9a94c32f312a0b45ea84d21c54f84cc40ee4958b72e1 +99d530c843dff89a47a5ee8c87303ab18f8a82b0d5b808fca050354b35da5c5a5594d55921c6362d6cc917d75bdc18dc +99c7286c293e1be21c5b2a669dfdfcd5aa587105d2886fc5a8eaf8984da4e907f7d7b8c2362d64a4f1621b077a2a08a0 +b4a39e1a9ed5d80c9563c3ca3fadf76f5478c63a98f4346a61b930c9c733e002f3ff02bc16abfdb53d776184cc3f87ba +9378ea71b941979404c92d01fb70b33fa68d085bf15d60eb1c9fc2b5fcdee6379f5583389a3660a756a50019a2f19a69 +b68e17344a2bc45b8e2e19466b86dc139afefbf9bad2e2e28276a725099ebac7f5763f3cb52002261e3abe45ef51eb1a +819e64dc412b2d194d693b9b3157c1070a226af35c629837df145ea12ad52fa8eabd65b025a63c1fb0726207a58cdde8 +a5e8ff8748419466ff6df5d389125f3d46aedacf44eaf12cbfe2f68d218c7d5ab6de4a8279d13aecc25f3b1d98230894 +91560d54a9715cfda9cf7133ae51c432d0bf7fcbaeb468004994e6838bfc5ddcfa30e4e780667d0c4c0376780b083017 +ae8adb3309cc89d79a55ff74f129bb311fe4f5351a8b87600a87e0c3ba60825f71fccf67eadcf7e4b243c619417540fd +8d92cc1a6baa7bfa96fbce9940e7187b3d142f1888bdcb09bb5c8abf63355e9fb942ac4b4819d9be0e0e822d3e8e2e08 +a6e8b79fdd90c34735bb8fbef02165ccbe55ea726dc203b15e7a015bf311c9cac56efd84d221cc55eaa710ee749dbdfe +a409b151de37bddf39ce5f8aa3def60ee91d6f03ddd533fce9bf7bdbeac618cc982c4f1ffbf6e302b8353d8f28f8c479 +b9693975ef82171b3b9fc318ca296e4fe6110b26cbdfd653418f7754563fa7b6e22d64f8025ee4243483fa321572bfe4 +a039ebe0d9ee4a03ade08e2104ffd7169975b224061924cca2aae71464d250851e9f5f6f6cb288b5bf15df9e252712a6 +b27834db422395bd330e53736a001341ce02c9b148c277dabac67dc422741bfa983c28d47c27e8214cd861f2bad8c6f6 +a2bafaf4e2daf629fd27d7d5ac09fb5efc930ff2ae610f37519808683aa583fe1c6f37207daf73de1d8a164f79a0c981 +b856cee1cfcf5e50db9af4ab0aed3db2f43c936eaea369b5bba65582f61f383c285efbda97b1c068c5d230cbe94f7722 +a61ab205554c0550fa267e46a3d454cd1b0a631646b3df140623ff1bfffaa118e9abe6b62814968cc2a506e9c03ea9a0 +8c78edcd106377b9cbdfa2abd5278724aed0d9e4ae5869b5d2b568fdabb7804c953bae96294fcc70ef3cd52ba2cbe4ed +8570869a9bbf6cc84966545a36586a60be4d694839f367b73dfc40b5f623fc4e246b39b9a3090694aa2e17e652d07fd1 +a905b82c4da8d866a894da72315a95dc98faa3c7b3d809aef18f3b2be4801e736a1b79a406179e8cac8f74d27e71ac52 +a8eb8679ff1a64908515f6720ff69434cb33d63aeb22d565fde506618908b1d37585e3bd4d044fd0838b55787af06b42 +af4d86b2fbd1684a657dffe4210321a71e6ae560c144d44668d1f324dc9630e98348c3d444622a689327c1a59cc169dd +80359c6eab16954559ab0e6a1fee9a0526c45d3cae1a371159a2e3aa9b893afdc3a785c9559a5fd9cd8cd774234bf819 +8d4e5ff81eb5d17bbe8ae6416538ca51a9427ce142b311f5cbb14febbbbb9c1ffc6489fd625b9266264c366c12a9d997 +92e181c66489c5fa063ba2a1a354b6fd3439b8b4365a8c90e42e169bfaa1fb5766bf3e0fe804399d18bc8fbcafb5c3b1 +a9ddf229360a095393885083716cb69c819b2d7cfb100e459c2e6beb999ff04446d1e4a0534832ae3b178cbe29f4f1d3 +8e085ef7d919302a1cc797857b75cff194bdbc1c5216434fa808c3dea0cf666f39d9b00f6d12b409693d7a9bd50a912c +916dc4dc89e5e6acf69e4485a09fc66968f9b292eac61a146df1b750aa3da2425a0743d492179f90a543a0d4cd72c980 +b9cbf17e32c43d7863150d4811b974882da338cf0ed1313765b431b89457021dd1e421eeaa52840ef00551bb630962dc +a6fb875786daec1a91484481787093d8d691dd07e15c9c0c6ae0404bf9dc26083ed15d03c6d3fe03e29f28e20da21269 +a870fcb54b9a029e8086de9b08da8782c64ad2cc2e7fdf955b913d294038bb8136193256b85267e75a4ca205808a76b4 +99883f057e09b88bf0e316f9814c091837fd5c26eeb16fec108c9fed4b7a2bd1c783dac0e4242b5a906621ab606c1e50 +85d89069ca3190577dab39bbec43c16bf6dbca439ad3eebd8f5e9f507d84c3c43e77fd6323224582566a3aa2c8018951 +9363ba219e0003f6e8a9d8937b9e1449e4b2c5cd57194563b758bea39deab88778e8f8e4f7816970a617fb077e1e1d42 +820622f25553c035326145c1d2d537dc9cfd064c2f5bdf6d4ec97814de5fe9a0fbd443345fa2ea0a9d40d81d3936aa56 +87e31110aaf447e70c3316459250e4f7f8c24420c97828f9eb33b22107542c5535bdb48b0e58682dd842edea2886ff08 +95bf80cac6f42029d843d1246588acb40a74802f9e94b2bf69b1833936767e701ef7b0e099e22ab9f20f8c0c4a794b6c +a46ecf612b2763d099b27fb814bd8fdbaee51d6b9ac277ad6f28350b843ce91d701371adfaaf4509400dc11628089b58 +8604decf299fb17e073969708be5befeb1090ab688ad9f3f97a0847a40ea9a11bbcfc7a91e8dc27bc67a155123f3bd02 +8eb765c8dc509061825f3688cb2d78b6fef90cf44db33783d256f09be284bc7282205279725b78882688a514247c4976 +b5c30b2244fa109d66b3a5270b178960fdec47d31e63db0b374b80d2b626409eb76d2e8d1ebf47ef96c166743032fc5e +aab01e76290a7e936989530221646160bf8f64e61e79282e980c8c5dcaaa805ff096efd01d075a2c75917a3f4bf15041 +b9d79671debd0b83d0c7c7c3e64c0fb1274300564b262771f839b49218501e7f38ef80cae1f7e5a3c34acdc74c89dab6 +92c0eaceadf036b3b9dfd2712013aba3dd7c30b7760f501f52141618265baa31840fe77850a7014dc528f71f8cf39ce6 +b3cdd098059980455dd5b1c04182df1bd12fa844a866f02a9f8a86aab95b59945baa9af99f687410bffc5b07153cb23c +b361b73a62f71256b7f6ea8e0f6615e14fc5a06ee98b928ab3c9dd3eef9d9d30070e9855c82b7facb639cacb3401e01f +b9c85fc0f25a3271cf28b1ca900078eaaa66cbab0a3e677606e898ac32781a2dfce4d9cbd07404599e2c3c02fa161c9d +ac5b4fdac2a0b2e6430d9fc72bde4249d72183b197fc7347bb1546ae6f544426686bbe0caec3ee973b6836da5e831c44 +b675aebf24b92e398e166f171a6df442b3f5919b6bee192f31675a5e8eeb77d34c6590a6f0c0857417e0f78cfb085db8 +a9bef942044d8d62e6a40169f7dc7b49e40cd0d77f8678dd7c7bae6f46c46786f9b1e319a3fa408f22a54fd2a4d70804 +a20d19cd917d5102ae9ca0cf532127d2b953aa3303310e8a8c4b3da025dded993a47e3a28e6b02acfadb6d65dc2d41a3 +a47fdb04059b83b2afb86a47b2368bbd7247c337a36d3333b6e5ef2cc9476a92c4907e4c58a845c9ef9b497621e0b714 +94a9e9ffc14b411e11a4ffa59878d59460263589003dc7b6915247c549f67feede279bf3645fdd92379022fb21e3caeb +b92e1177dd9ecdaf1370c71b14954219cf0851f309bc216d5907a4e2e84e0df3457018224150c142cc6bf86644bb4b73 +8bc57fadd68a265b7df9b42227a9c0968db7b1bb50dc12f7d755505779f1ff2c408672b3091e903366acc9ce15d19fb6 +b6b5efbe1ac4e1bd2e8447c45000d09397b772ca5496acc447b881022608a41c4f60388814607a01890190105bee7be3 +95f7c85fd614df968f8ccf8d086579c9e1cec4644ecf06da26e3511cb39635a7326b3cec47bd51cf5646f1c660425e9c +b81765fb319bcdc74b4d608383ccb4af7dd84413b23af637be12e2827a75f7e4bcd14441cf979ed9038ae366fbb6f022 +a120ea76cda8c6c50c97035078f6648afe6537809bdba26e7c9e61de8f3070d2347160f9d34010effbf2ec7e94f5749f +92c1b8631953b40d3cc77eee2c72a064b999c09a9b92c11d8fa7b4072966273901c9dba25f9f79f384d9f11a56f3fc7a +a4b00dc0ab67b2300abc9c516e34daf444d6497b066a90cfe3381ed2812304ed37b14f3b948990443dc6c1cf1bed460c +a9e9f7e13c9f031bc7b9e6f1417c7abcc38894fe7d3f54869ee277afd2efa3e6fb50757dd36c8c94d591e0abdea322cc +84f3e98f831792b5ad14bcfe62a4c9f296476c6087c4c1ec7767fc642fbca141ff6a3deeb8b4d4106a9cda5a9937eea0 +8eb1a7931bbea9a714226fd74b0100ab88355287d9b0a349c095e9b5809b98f237ffd706bce7d67a770da355fb9cec7b +9738ef8739e1742c1f26b51a1621be0b89d37406a370c531e236f635c7064c661818817bb3858908986aa687b28b21be +a9cf3ce8501b003ccaf57552a4c4ec31081e44526d3aa3791d3dc4a7e438a357c0956f93c500356186d8fd4588ffac5e +a7af6a219cca59225839a9de5b19263cb23d75557d448bc7d677b62591a2e068c45e5f4457cceb3e9efa01d0601fc18a +972a24ece5eda7692cbb6fb727f92740451bc1281835e2a02931b2b05824a16b01dbe5edd03a0ed5b441ff25a5cc0188 +b21d1ec7597ce95a42f759c9a8d79c8275d7e29047a22e08150f0f65014702f10b7edce8c03f6e7ab578ce8c3b0ec665 +a13a1c7df341bd689e1f8116b7afc149c1ef39161e778aa7903e3df2569356ad31834fa58ceb191485585ce5ef6835c3 +a57bdb08119dc3bc089b5b2b5383455c4de0c2fcdac2dcfa21c7ac5071a61635ff83eceb7412f53fab42d1a01991de32 +b2968748fa4a6921ee752d97aa225d289f599a7db7a222450e69706533573ded450380c87f8cdd4a8b8c8db1b42b5c97 +8718ec04e0d5f38e3034ecd2f13dfde840add500f43a5e13457a1c73db0d18138f938690c8c315b5bcbeb51e8b9a2781 +82094789e26c4a04f2f30bdb97b9aecca9b756cbd28d22ab3c8bed8afc5b2963340ddfc5a5f505e679bf058cbc5dcbb8 +a35b8a566dd6ab67eddc2467906bffc76c345d508e52e9e4bb407b4f2b2c5f39b31d5a4bf5022f87bf7181dc6be2fe41 +a8c93b1e893d4777c0e3a1b4bef3be90c215781501407c4011457fc3240e13524b4d2bea64a6d0a3efe3f3b0dae9b8ab +877095ad18b1e5870818f7a606127ba1736a0b55b0dbcd281ec307c84b08afc0c9117e3a880fe48bfc225fbf37671a97 +84405ee0421ed2db1add3593df8426a9c1fcc8063e875f5311a917febc193748678dd63171d0c21665fb68b6d786c378 +a52cdc8209c3c310bed15a5db260c4f4d4857f19c10e4c4a4cfe9dfc324dfac851421bb801509cf8147f65068d21603c +8f8a028a70dda7285b664722387666274db92230b09b0672f1ead0d778cee79aae60688c3dfd3a8ed1efdeda5784c9d4 +a0be42fecc86f245a45a8ed132d6efc4a0c4e404e1880d14601f5dce3f1c087d8480bad850d18b61629cf0d7b98e0ae0 +83d157445fc45cb963b063f11085746e93ab40ece64648d3d05e33e686770c035022c14fdf3024b32b321abf498689ad +8a72bbf5a732e2d4f02e05f311027c509f228aef3561fc5edac3ef4f93313845d3a9f43c69f42e36f508efcc64a20be0 +b9ca29b0ec8e41c6a02f54d8c16aebf377982488cbe2ed1753090f2db4f804f6269af03e015d647a82ef06ffaa8cba6c +b4df3858d61bbb5ded1cf0be22a79df65ae956e961fbb56c883e1881c4c21fe642e3f5a0c108a882e553ac59595e3241 +86457d8890ac8858d7bab180ef66851247c2bf5e52bf69a4051d1d015252c389684fcc30bb4b664d42fbf670574ab3a3 +86d5576ea6dfa06d9ebce4cd885450f270c88a283e1e0d29cab27851c14ed2f00355e167b52e1539f1218ad11d8f13dd +883ad1364dc2a92388bfafaa9bc943c55b2f813525831e817a6208c666829a40455dde494eba054b2495a95f7ce69e8a +8942371e6925231c2c603b5f5a882d8404d39f0c7c4232557c2610b21c2c07f145466da798ea78b7932da2b774aa3128 +a799eb71496783cc7faf12c9d9804bf6180699a004b2f07fc5cc36840f63ce7eee7dde9275819a9aa3f8d92dc0d47557 +8eb3fb5c769548ee38c7882f51b959c5d5a42b5935269ccf987d6ddbb25a206e80c6000bcc328af149e0727c0b7c02c0 +8f3910d64e421a8f2d8db4c7b352ba5b3fc519d5663973fea5962efe4364fb74448770df944ef37ffe0382648fb56946 +b41413e0c26ff124cf334dab0dc8e538293d8d519d11cc2d10895a96b2064ac60c7da39f08589b38726cffa4c3f0bfef +b46ef2eb10abae0f35fa4c9c7ee2665e8044b8d9f91988a241da40fd5bbc63166925582151941b400006e28bbc5ba22a +b8baa8b4c420bb572a3b6b85479b67d994c49a7ebfe1274687d946a0d0b36dfed7630cfb897350fa166f5e2eff8f9809 +964b46d359c687e0dcfbdab0c2797fc2bd1042af79b7418795b43d32ffca4de89358cee97b9b30401392ff54c7834f9f +8410d0203d382ebf07f200fd02c89b80676957b31d561b76563e4412bebce42ca7cafe795039f46baf5e701171360a85 +b1a8d5d473c1a912ed88ea5cfa37c2aea5c459967546d8f2f5177e04e0813b8d875b525a79c29cb3009c20e7e7292626 +afaab9a1637429251d075e0ba883380043eaf668e001f16d36737028fded6faa6eeed6b5bb340f710961cee1f8801c41 +aef17650003b5185d28d1e2306b2f304279da50925f2704a6a3a68312f29fe5c2f2939f14e08b0ba9dee06ea950ad001 +97bcc442f370804aa4c48c2f8318d6f3452da8389af9335e187482d2e2b83b9382e5c297dce1a0f02935e227b74e09a3 +8a67a27b199f0bcd02d52a3e32f9b76a486b830ec481a49a4e11807e98408b7052b48581b5dd9f0b3e93052ec45dfb68 +b113bf15f430923c9805a5df2709082ab92dcdf686431bbad8c5888ca71cc749290fa4d4388a955c6d6ee3a3b9bc3c53 +8629ca24440740ce86c212afed406026f4ea077e7aa369c4151b6fa57bca7f33f9d026900e5e6e681ae669fd2bd6c186 +933a528371dcecc1ec6ded66b1c7b516bd691b3b8f127c13f948bfbcda3f2c774c7e4a8fbee72139c152064232103bdf +8568ddd01f81a4df34e5fa69c7f4bb8c3c04274147498156aec2e3bd98ea3e57c8a23503925de8fa3de4184563a2b79e +8160874ec030f30fda8f55bcf62613994ff7ed831e4901c7560eac647182b4a9b43bfaff74b916602b9d6ae3bfcaf929 +ae71c48d48cf9459800cdf9f8e96bc22e2d4e37259e5c92a2b24fbe2c6ca42675e312288603c81762f6ceb15400bc4c9 +b05f39bb83fda73e0559db1fd4a71423938a87ad9f060d616d4f4a6c64bf99472a2cbfb95f88b9257c9630fc21a0b81f +80c8479a640ed7a39e67f2db5ad8dfd28979f5443e8e6c23da8087fc24134d4b9e7c94320ffa4154163270f621188c27 +9969ba20ee29c64cb3285a3433a7e56a0fe4ddc6f3d93e147f49fe021bed4a9315266ebb2fb0eb3036bb02001ae015e6 +a198c89fef2ab88e498703b9021becc940a80e32eb897563d65db57cc714eaa0e79092b09dd3a84cfab199250186edcc +8df14a3db8fe558a54d6120bad87405ba9415a92b08c498812c20416c291b09fed33d1e2fcf698eb14471f451e396089 +81e245ef2649b8a5c8d4b27188dd7e985ef6639090bdc03462c081396cf7fc86ed7d01bfe7e649d2b399255e842bdc21 +8659f622c7ab7b40061bcf7a10144b51ad3ab5348567195924f2944e8c4ce137a37f1ba328e4716c10806f3fb7271689 +a575d610fc8fe09334ca619ecdadf02d468ca71dd158a5a913252ca55ea8d8f9ce4548937c239b9cb8ab752a4d5af24a +94744549cd9f29d99f4c8c663997bdfa90e975b31f1086214245de9c87b0c32209f515a0de64d72d5ef49c09b0a031fa +80a8677862b056df59e350c967a27436c671b65d58854e100115bac9824ba177e94c2a1bfcaa191a071b9cefdbee3989 +91be9a5504ec99922440f92a43fe97ddce2f21b9d94cd3a94c085a89b70c903696cec203bbab6d0a70693ba4e558fb01 +8c5a0087bcd370734d12d9b3ab7bc19e9a336d4b49fc42825b2bfedcd73bb85eb47bf8bb8552b9097cc0790e8134d08c +933aa9e6bd86df5d043e0577a48e17eea3352e23befdbb7d7dcac33b5703d5ace230443ac0a40e23bf95da4cc2313478 +984b7ee4bd081ee06c484db6114c2ce0ba356988efb90f4c46ff85ed2865fb37f56a730166c29ef0ae3345a39cdeae7a +ae830f908ea60276c6c949fb8813e2386cf8d1df26dcf8206aa8c849e4467243e074471380ed433465dc8925c138ea4c +874c1df98d45b510b4f22feff46a7e8ed22cfc3fad2ac4094b53b9e6477c8dfc604976ca3cee16c07906dece471aa6c6 +a603eb60d4c0fb90fa000d2913689126849c0261e6a8649218270e22a994902965a4e7f8c9462447259495fe17296093 +a7c73d759a8ad5e3a64c6d050740d444e8d6b6c9ade6fb31cb660fa93dc4a79091230baccb51c888da05c28cb26f6f3f +a4411b79b6a85c79ea173bd9c23d49d19e736475f3d7d53213c5349ebb94a266d510d12ba52b2ac7a62deaaaec7339b8 +943b84f8bbcee53b06266b5c4cd24d649d972593837fe82b0bf5d5e1bbc1a2bf148e1426c366d7c39ab566b10224cadc +8300012096a8b4cefecc080054bf3ceb0918162ba263c6848860423407796b5eb517170c0bad8e4905ac69a383055a21 +8244a1e3ad41908c6f037e2f8db052e81f281646141334829f36c707f307448b9ab79a7f382a1e8d86f877c90b59271c +8eca1b74687802ecc36a5d39e4516a9dee3de61a2047252d9ed737b49e0090c386e9d792ac004c96337681c7f29a16ad +b70fa47535f0524835039a20036c61e77f66146ad79d3d339214d8744742db41ceeb577c829d000011aeafbb12e09579 +84b3abbce48689f3adbb99889c7fd1f3e15ab455d477e34f5151c5c1c358ed77a5b6a581879f7e0f1f34106e0792e547 +ab45ecb58c0ef0dbce3d16afc6ac281e0d90ec48741ea96a141152647e98fcc87f3a3ff07ba81f3179118453ce123156 +90d231a145ba36a59087e259bbfc019fa369201fcfeaa4347d5fd0a22cd8a716e5a797f3cc357f2779edb08f3b666169 +a4f6074d23c6c97e00130bc05f25213ca4fa76c69ca1ace9dece904a2bdd9d987661f5d55023b50028c444af47ff7a08 +933af884939ad0241f3f1f8e8be65f91d77ac0fb234e1134d92713b7cfb927f1933f164aec39177daa13b39c1370fac8 +80d1db6933ce72091332ae47dc691acb2a9038f1239327b26d08ea9d40aa8f2e44410bbda64f2842a398cbe8f74f770f +a7a08605be2241ccc00151b00b3196d9c0717c4150909a2e9cd05538781231762b6cc6994bebbd4cddae7164d048e7b2 +96db0d839765a8fdbbac03430fa800519e11e06c9b402039e9ae8b6503840c7ecac44123df37e3d220ac03e77612f4e4 +96d70f8e9acd5a3151a8a9100ad94f16c289a31d61df681c23b17f21749c9062622d0a90f6d12c52397b609c6e997f76 +8cf8e22273f7459396ff674749ab7e24c94fe8ab36d45d8235e83be98d556f2b8668ba3a4ec1cb98fac3c0925335c295 +97b7e796a822262abc1a1f5a54cb72a1ea12c6c5824ac34cd1310be02d858a3c3aa56a80f340439b60d100e59c25097d +a48208328b08769737aa1a30482563a4a052aea736539eceab148fa6653a80cb6a80542e8b453f1f92a33d0480c20961 +b612184941413fd6c85ff6aa517b58303b9938958aa85a85911e53ed308778624d77eadb27ccf970573e25d3dfd83df7 +b3717068011648c7d03bbd1e2fc9521a86d2c3ae69113d732c2468880a3b932ebec93596957026477b02842ed71a331b +a0ad363e1352dcf035b03830fef4e27d5fd6481d29d5e8c9d51e851e3862d63cdcbaf8e330d61c1b90886921dac2c6fd +8db409fdacfa4bfdaf01cc87c8e97b53ca3a6e3a526d794eaad1c2023f3df4b888f1bf19fee9a990fe6d5c7c3063f30c +b34d6975310ab15938b75ef15020a165fc849949065d32d912554b51ffa1d3f428a6d1a396cb9329367670391de33842 +9117285e9e6762853fc074b8a92b3923864de2c88c13cea7bab574aaf8cdd324843455d2c3f83c00f91f27c7ecc5592a +b4b2e8f190ea0b60819894710c866bf8578dd1b231ae701d430797cc7ede6e216e8ca6a304f3af9484061563645bf2ab +8c493c6853ab135d96a464815dd06cad8b3e8b163849cdefc23d1f20211685753b3d3e147be43e61e92e35d35a0a0697 +9864d7880f778c42d33cf102c425e380d999d55a975a29c2774cad920dfddb80087a446c4f32ed9a6ab5f22ec6f82af0 +90f67fe26f11ca13e0c72b2c2798c0d0569ed6bc4ce5bbaf517c096e7296d5dd5685a25012f6c6d579af5b4f5d400b37 +a228872348966f26e28a962af32e8fa7388d04bc07cfc0224a12be10757ac7ab16a3387c0b8318fcb0c67384b0e8c1a4 +a9d9d64bba3c03b51acf70aeb746a2712ddafe3b3667ae3c25622df377c2b5504e7ab598263bec835ab972283c9a168b +932128971c9d333f32939a1b46c4f7cf7e9d8417bd08dc5bd4573ccbd6ec5b460ac8880fb7f142f7ef8a40eef76d0c6d +964115e7838f2f197d6f09c06fbb2301d6e27c0ecdf208350cf3b36c748436dac50f47f9f9ac651c09ab7ad7221c7e43 +a5941f619e5f55a9cf6e7f1499b1f1bcddcc7cf5e274efedaaad73a75bc71b1fc5c29cd903f6c69dc9a366a6933ca9d1 +a154bf5eaec096029e5fe7c8bf6c695ae51ace356bb1ad234747776c7e1b406dee2d58864c3f4af84ed69f310974125e +b504e6209d48b0338ab1e4bdab663bac343bb6e0433466b70e49dc4464c1ec05f4a98111fd4450393607510ae467c915 +813411918ea79bdde295393284dc378b9bdc6cfcb34678b9733ea8c041ac9a32c1e7906e814887469f2c1e39287e80f8 +8be0369f94e4d72c561e6edb891755368660208853988647c55a8eed60275f2dd6ee27db976de6ecf54ac5c66aaf0ae6 +a7e2701e55b1e7ea9294994c8ad1c080db06a6fc8710cd0c9f804195dce2a97661c566089c80652f27b39018f774f85e +956b537703133b6ddf620d873eac67af058805a8cc4beb70f9c16c6787bf3cc9765e430d57a84a4c3c9fbdd11a007257 +835ae5b3bb3ee5e52e048626e3ddaa49e28a65cb94b7ecdc2e272ff603b7058f1f90b4c75b4b9558f23851f1a5547a35 +85d67c371d1bf6dc72cca7887fa7c886ce988b5d77dc176d767be3205e80f6af2204d6530f7060b1f65d360a0eaeff30 +a84a6647a10fcef8353769ef5f55a701c53870054691a6e9d7e748cbe417b3b41dbb881bae67adc12cb6596c0d8be376 +87ffe271fc0964cb225551c7a61008d8bcb8b3d3942970dbcc2b9f4f9045a767971880368ea254e2038a3a0b94ecf236 +964bb721c51d43ee7dd67c1a2b7dd2cc672ce8fad78c22dcddb43e6aab48d9a4a7dc595d702aa54a6fb0ffabf01f2780 +a89b3f84bb7dcbe3741749776f5b78a269f6b1bebb8e95d3cc80b834fd2177c6be058d16cacfd0d5e1e35e85cde8b811 +b4314538e003a1587b5592ff07355ea03239f17e75c49d51f32babe8e048b90b046a73357bcb9ce382d3e8fbe2f8e68b +86daf4bf201ae5537b5d4f4d734ed2934b9cf74de30513e3280402078f1787871b6973aa60f75858bdf696f19935a0e2 +b1adf5d4f83f089dc4f5dae9dbd215322fa98c964e2eaa409bf8ca3fa5c627880a014ed209492c3894b3df1c117236c4 +b508d52382c5bac5749bc8c89f70c650bb2ed3ef9dc99619468c387c1b6c9ff530a906dfa393f78f34c4f2f31478508a +a8349a5865cb1f191bebb845dfbc25c747681d769dbffd40d8cedf9c9a62fa2cbc14b64bb6121120dab4e24bef8e6b37 +af0500d4af99c83db8890a25f0be1de267a382ec5e9835e2f3503e1bac9412acf9ff83a7b9385708ef8187a38a37bc77 +b76d57a1c1f85b8a8e1722a47057b4c572800957a6b48882d1fc21309c2e45f648a8db0fcff760d1dbc7732cf37c009b +b93c996cec0d3714667b5a5a5f7c05a7dc00bbc9f95ac8e310626b9e41ae4cc5707fac3e5bd86e1e1f2f6d9627b0da94 +93216fdb864217b4c761090a0921cf8d42649ab7c4da1e009ec5450432564cb5a06cb6e8678579202d3985bd9e941cef +8b8be41105186a339987ae3a5f075fbc91f34b9984d222dfed0f0f85d2f684b56a56ab5dc812a411570491743d6c8b18 +959b72782a6b2469e77fe4d492674cc51db148119b0671bd5d1765715f49fa8a87e907646671161586e84979ef16d631 +86b7fc72fb7e7904ea71d5e66ba0d5d898ace7850985c8cc4a1c4902c5bf94351d23ce62eed45e24321fb02adfa49fc8 +a2f244e7c9aa272cb0d067d81d25e5a3045b80b5a520b49fd5996ece267a7f1bea42e53147bbf153d9af215ea605fc9e +81aa2efa5520eebc894ce909ba5ce3250f2d96baa5f4f186a0637a1eea0080dd3a96c2f9fadf92262c1c5566ddb79bab +b607dd110cfe510d087bcff9a18480ba2912662256d0ab7b1d8120b22db4ad036b2266f46152754664c4e08d0fc583f6 +8f588d5f4837e41312744caac5eee9ddc3ad7085871041694f0b5813edf83dc13af7970f7c9b6d234a886e07fa676a04 +924921b903207783b31016cbec4e6c99e70f5244e775755c90d03a8b769738be3ba61577aca70f706a9c2b80040c9485 +ae0a42a222f1a71cd0d3c69ffb2f04c13e1940cce8efabe032629f650be3ceed6abb79651dbb81cb39a33286eb517639 +a07d7d76460f31f5f0e32e40a5ea908d9d2aebf111ac4fadee67ef6540b916733c35a777dcdc05f6417726ca1f2d57dd +88d7f8a31f8c99794291847d28745e5d0b5d3b9684ca4170b686ffbb5bb521a3ef6746c3c8db22e4250a0cdff7939d96 +849573071fd98c020dc9a8622a9eff221cb9f889bde259e7127a8886b73bef7ad430b87750915658918dcfb6b7b4d8d3 +b12d59f732fa47fad175d6263734da8db89230fd340a46ad1cdee51e577041a5c80bf24cd195593e637daf1a66ef5a98 +abbcfb8a4a6d5e269ee1ac5e277df84416c73ca55ec88317f73608201af25af0cb65b943c54684a5651df3a26e3daca2 +ab157f589bdbaf067a6a7ba7513df0492933855d39f3a081196cf2352e0ddc0162d476c433320366e3df601e0556278d +a86c0619b92e5ae4f7daa876a2abc5ba189156afc2fa05eef464dfa342ba37fc670d0dc308ad3822fcb461ab001bac30 +a3f292946476cfe8d5e544a5325439a00e0165a5f9bf3bb6a53f477baeac7697cc0377745536681aa116f326ce911390 +8aecbbfd442a6a0f01c1c09db5d9d50213eb6f1ff6fab674cde3da06a4edff3ed317e804f78300c22ef70c336123e05d +834ed4b58211fcd647d7bf7c0a3ba9085184c5c856b085e8a0fcd5215c661ef43d36f3f0f6329a9f1370501b4e73b6e4 +a114ea5ad2b402a0de6105e5730907f2f1e458d28ae35144cf49836e0ad21325fe3e755cfb67984ae0a32e65402aad1e +a005f12bed97d71cee288b59afe9affb4d256888727343944a99913980df2c963fe02f218e6ea992f88db693a4498066 +a010f286ab06b966e3b91ff8f1bdbe2fe9ab41a27bc392d5787aa02a46e5080e58c62c7d907818caae9f6a8b8123e381 +857bd6df2ddef04dbc7c4f923e0b1696d3016c8bfed07fdfa28a3a3bd62d89b0f9df49aae81cbb6883d5e7b4fadae280 +b3927030da445bc4756ac7230a5d87412a4f7510581fb422212ce2e8cf49689aca7ba71678743af06d4de4914c5aa4a0 +b86403182c98fcce558d995f86752af316b3b2d53ba32075f71c7da2596747b7284c34a1a87de604fcc71e7e117a8add +98dd19b5527733041689b2a4568edaf6aa0fe1a3dd800c290cda157b171e053648a5772c5d3d4c80e5a795bc49adf12e +88a3c227bb7c9bff383f9ad3f7762245939a718ab85ae6e5e13180b12bf724d42054d3852b421c1cd1b3670baddecb63 +b3cfd9ad66b52bbe57b5fff0fad723434d23761409b92c4893124a574acc1e6b1e14b4ec507661551cbbe05e16db362e +923e1bb482cf421dd77801f9780f49c3672b88508a389b94015fd907888dc647ee9ea8ec8d97131d235d066daf1f42b7 +8d5e16240f04f92aa948181d421006bdbc7b215648fb6554193224d00cf337ebbb958f7548cf01b4d828acffb9fbc452 +8b2b8f18ad0559746f6cda3acca294a1467fb1a3bc6b6371bc3a61a3bfe59418934fa8706f78b56005d85d9cb7f90454 +a9316e2a94d6e31426d2ae7312878ba6baaac40f43e2b8a2fa3ab5a774c6918551554b2dbb23dc82f70ba3e0f60b5b0d +9593116d92cf06b8cd6905a2ce569ee6e69a506c897911f43ae80fc66c4914da209fc9347962034eebbc6e3e0fe59517 +887d89d2b2d3c82b30e8f0acf15f0335532bd598b1861755498610cb2dd41ff5376b2a0bb757cb477add0ce8cfe7a9fc +b514cfe17875ecb790ad055271cc240ea4bda39b6cfa6a212908849c0875cb10c3a07826550b24c4b94ea68c6bb9e614 +a563d5187966d1257d2ed71d53c945308f709bcc98e3b13a2a07a1933dc17bcb34b30796bd68c156d91811fbd49da2cb +a7195ccc53b58e65d1088868aeeb9ee208103e8197ad4c317235bb2d0ad3dc56cb7d9a7186416e0b23c226078095d44c +a838e7a368e75b73b5c50fbfedde3481d82c977c3d5a95892ac1b1a3ea6234b3344ad9d9544b5a532ccdef166e861011 +9468ed6942e6b117d76d12d3a36138f5e5fb46e3b87cf6bb830c9b67d73e8176a1511780f55570f52d8cdb51dcf38e8c +8d2fc1899bc3483a77298de0e033085b195caf0e91c8be209fd4f27b60029cbe1f9a801fbd0458b4a686609762108560 +8f4e44f8ca752a56aa96f3602e9234ad905ad9582111daf96a8c4d6f203bf3948f7ce467c555360ad58376ee8effd2ba +8fb88640b656e8f1c7c966c729eb2ba5ccf780c49873f8b873c6971840db7d986bdf1332ba80f8a0bb4b4ee7401468fa +b72aa3235868186913fb5f1d324e748cd3ce1a17d3d6e6ea7639a5076430fe0b08841c95feb19bb94181fe59c483a9eb +b8b102690ebb94fc4148742e7e3fd00f807b745b02cbe92cd92992c9143b6db7bb23a70da64a8b2233e4a6e572fc2054 +8c9ae291f6cd744e2c6afe0719a7fc3e18d79307f781921fb848a0bf222e233879c1eca8236b4b1be217f9440859b6ce +a658ede47e14b3aad789e07f5374402f60e9cacb56b1b57a7c6044ca2418b82c98874e5c8c461898ebd69e38fecd5770 +89c0cb423580e333923eb66bda690f5aca6ec6cba2f92850e54afd882ba608465a7dbb5aa077cd0ca65d9d00909348ab +aed8e28d98d5508bd3818804cf20d296fe050b023db2ed32306f19a7a3f51c7aaafed9d0847a3d2cd5ba5b4dabbc5401 +96a0fcd6235f87568d24fb57269a94402c23d4aa5602572ad361f3f915a5f01be4e6945d576d51be0d37c24b8b0f3d72 +935d0c69edd5dfa8ed07c49661b3e725b50588f814eb38ea31bcc1d36b262fae40d038a90feff42329930f8310348a50 +900518288aa8ea824c7042f76710f2ea358c8bb7657f518a6e13de9123be891fa847c61569035df64605a459dad2ecc8 +947d743a570e84831b4fb5e786024bd752630429d0673bf12028eb4642beb452e133214aff1cfa578a8856c5ebcb1758 +a787266f34d48c13a01b44e02f34a0369c36f7ec0aae3ec92d27a5f4a15b3f7be9b30b8d9dd1217d4eeedff5fd71b2e5 +a24b797214707ccc9e7a7153e94521900c01a1acd7359d4c74b343bfa11ea2cdf96f149802f4669312cd58d5ab159c93 +97f5ee9c743b6845f15c7f0951221468b40e1edaef06328653a0882793f91e8146c26ac76dd613038c5fdcf5448e2948 +80abd843693aed1949b4ea93e0188e281334163a1de150c080e56ca1f655c53eb4e5d65a67bc3fc546ed4445a3c71d00 +908e499eb3d44836808dacff2f6815f883aeced9460913cf8f2fbbb8fe8f5428c6fc9875f60b9996445a032fd514c70f +ae1828ef674730066dc83da8d4dd5fa76fc6eb6fa2f9d91e3a6d03a9e61d7c3a74619f4483fe14cddf31941e5f65420a +a9f4dbe658cd213d77642e4d11385a8f432245b098fccd23587d7b168dbeebe1cca4f37ee8d1725adb0d60af85f8c12f +93e20ee8a314b7772b2439be9d15d0bf30cd612719b64aa2b4c3db48e6df46cea0a22db08ca65a36299a48d547e826a7 +a8746a3e24b08dffa57ae78e53825a9ddbbe12af6e675269d48bff4720babdc24f907fde5f1880a6b31c5d5a51fbb00e +b5e94dfab3c2f5d3aea74a098546aa6a465aa1e3f5989377d0759d1899babf543ad688bb84811d3e891c8713c45886c5 +a3929bada828bd0a72cda8417b0d057ecb2ddd8454086de235540a756e8032f2f47f52001eb1d7b1355339a128f0a53b +b684231711a1612866af1f0b7a9a185a3f8a9dac8bde75c101f3a1022947ceddc472beb95db9d9d42d9f6ccef315edbc +af7809309edbb8eb61ef9e4b62f02a474c04c7c1ffa89543d8c6bf2e4c3d3e5ecbd39ec2fc1a4943a3949b8a09d315a6 +b6f6e224247d9528ef0da4ad9700bee6e040bbf63e4d4c4b5989d0b29a0c17f7b003c60f74332fefa3c8ddbd83cd95c1 +adbcec190a6ac2ddd7c59c6933e5b4e8507ce5fd4e230effc0bd0892fc00e6ac1369a2115f3398dfc074987b3b005c77 +8a735b1bd7f2246d3fa1b729aecf2b1df8e8c3f86220a3a265c23444bdf540d9d6fe9b18ed8e6211fad2e1f25d23dd57 +96b1bf31f46766738c0c687af3893d098d4b798237524cb2c867ed3671775651d5852da6803d0ea7356a6546aa9b33f2 +8036e4c2b4576c9dcf98b810b5739051de4b5dde1e3e734a8e84ab52bc043e2e246a7f6046b07a9a95d8523ec5f7b851 +8a4f4c32ee2203618af3bb603bf10245be0f57f1cfec71037d327fa11c1283b833819cb83b6b522252c39de3ce599fa5 +ad06ed0742c9838e3abaaffdb0ac0a64bad85b058b5be150e4d97d0346ed64fd6e761018d51d4498599669e25a6e3148 +8d91cb427db262b6f912c693db3d0939b5df16bf7d2ab6a7e1bc47f5384371747db89c161b78ff9587259fdb3a49ad91 +ae0a3f84b5acb54729bcd7ef0fbfdcf9ed52da595636777897268d66db3de3f16a9cf237c9f8f6028412d37f73f2dfad +8f774109272dc387de0ca26f434e26bc5584754e71413e35fa4d517ee0f6e845b83d4f503f777fe31c9ec05796b3b4bc +a8670e0db2c537ad387cf8d75c6e42724fae0f16eca8b34018a59a6d539d3c0581e1066053a2ec8a5280ffabad2ca51f +ac4929ed4ecad8124f2a2a482ec72e0ef86d6a4c64ac330dab25d61d1a71e1ee1009d196586ce46293355146086cabba +845d222cb018207976cc2975a9aa3543e46c861486136d57952494eb18029a1ebb0d08b6d7c67c0f37ee82a5c754f26f +b99fa4a29090eac44299f0e4b5a1582eb89b26ed2d4988b36338b9f073851d024b4201cd39a2b176d324f12903c38bee +9138823bc45640b8f77a6464c171af2fe1700bdc2b7b88f4d66b1370b3eafe12f5fbb7b528a7e1d55d9a70ca2f9fc8e6 +8ac387dc4cf52bc48a240f2965ab2531ae3b518d4d1f99c0f520a3d6eb3d5123a35ef96bed8fa71ee2f46793fa5b33b3 +864adec6339d4c2ba2525621fceabd4c455902f6f690f31a26e55413e0722e5711c509dc47ce0bcc27bbdc7651768d2d +a0a52edb72268a15201a968dabc26a22909620bda824bd548fb8c26cc848f704166ed730d958f0173bd3b0a672f367bd +949e445b0459983abd399571a1a7150aab3dd79f4b52a1cd5d733e436c71c1d4b74287c6b0ce6cc90c6711ba4c541586 +858966355dac11369e3b6552f2b381665181693d5a32e596984da3314021710b25a37d8c548b08700eea13d86cb22f21 +974bcbb8d38c5e6518745cc03ad436e585b61f31d705e7e2e5085da9655d768ac4d800904f892c3dab65d6223e3f1fd6 +8092b6506b01308bf6187fde5ebd4fa7448c9a640961ba231be22ac5fa2c7635ef01e8b357722c7695d09b723101ea2a +a5b8ef360bf28533ee17d8cd131fff661d265f609db49599085c0c7d83b0af409a1b5c28e3a5e5d7f8459a368aa121e8 +b031b6d5e3ceab0f0c93314b3b675f55cf18cbc86f70444af266fe39cb22fd7dad75d8c84e07f1c1bfa2cb8283e1361a +93ad489e4f74658320c1cceed0137c023d3001a2c930ed87e6a21dbf02f2eb6ad1c1d8bcb3739c85dcfbecb040928707 +b15e4ec2cdab0d34aec8d6c50338812eb6ecd588cf123a3e9d22a7ca23b5a98662af18289f09e6cdd85a39a2863c945c +b304f71a9717cf40c22073f942618b44bf27cd5e2ed4a386ad45d75b0fcb5a8dafd35158211eaf639495c6f1a651cedb +b82d78d3eaaa7c5101b7a5aae02bd4f002cd5802d18c3abcda0dd53b036661c6d3c8b79e0abe591eab90b6fdc5fef5e3 +abbd1884243a35578b80914a5084449c237ee4e4660c279d1073a4d4217d1b55c6b7e9c087dfd08d94ac1416273d8d07 +92f4b61c62502745e3e198ec29bca2e18696c69dcb914d1f3a73f4998d012b90caf99df46e9bb59942e43cce377fe8fd +906e79df98185820c8208844e1ba6bd86cb96965814b01310bd62f22cbec9b5d379b2ef16772d6fc45a421b60cfd68fe +a0eae2784ef596e2eb270dd40c48d6c508e4394c7d6d08d4cc1b56fde42b604d10ba752b3a80f2c4a737e080ef51b44f +94c084985e276dc249b09029e49a4ef8a369cd1737b51c1772fbb458d61e3fe120d0f517976eba8ffa5711ba93e46976 +83619a0157eff3f480ab91d1d6225fead74c96a6fd685333f1e8e4d746f6273e226bad14232f1d1168a274e889f202f1 +a724fe6a83d05dbbf9bb3f626e96db2c10d6d5c650c0a909415fbda9b5711c8b26e377201fb9ce82e94fa2ab0bf99351 +a8a10c1b91a3a1fa2d7fd1f78a141191987270b13004600601d0f1f357042891010717319489f681aa8a1da79f7f00d5 +a398a2e95b944940b1f8a8e5d697c50e7aa03994a8a640dfad4ea65cfb199a4d97861a3ec62d1c7b2b8d6e26488ca909 +a2eedfe5452513b2a938fffd560798ef81379c5a5032d5b0da7b3bb812addbaad51f564c15d9acbbfc59bb7eddd0b798 +ab31c572f6f145a53e13b962f11320a1f4d411739c86c88989f8f21ab629639905b3eedb0628067942b0dc1814b678ca +ad032736dd0e25652d3566f6763b48b34ea1507922ed162890cd050b1125ec03b6d41d34fccba36ec90336f7cdf788ed +83028a558a5847293147c483b74173eca28578186137df220df747fccd7d769528d7277336ea03c5d9cdd0bc5ae3d666 +ab5d182cd1181de8e14d3ef615580217c165e470b7a094a276b78a3003089123db75c6e1650bf57d23e587c587cd7472 +a4793e089fbdb1597654f43b4f7e02d843d4ab99ee54099c3d9f0bd5c0c5657c90bb076379a055b00c01b12843415251 +98bdc52ee062035356fb2b5c3b41673198ddc60b2d1e546cb44e3bb36094ef3c9cf2e12bbc890feb7d9b15925439d1ea +a4f90cca6f48024a0341bd231797b03693b34e23d3e5b712eb24aba37a27827319b2c16188f97c0636a0c115381dc659 +8888e6c2e4a574d04ba5f4264e77abc24ccc195f1a7e3194169b8a2ceded493740c52db4f9833b3dbf4d67a3c5b252cb +83dc4e302b8b0a76dc0292366520b7d246d73c6aebe1bdd16a02f645c082197bcff24a4369deda60336172cefbcf09af +a4eb2741699febfeb793914da3054337cc05c6fa00d740e5f97cb749ae16802c6256c9d4f0f7297dcdbb8b9f22fc0afa +8b65557d5be273d1cb992a25cfce40d460c3f288d5cb0a54bdef25cbd17cdea5c32ec966e493addf5a74fd8e95b23e63 +97c6577e76c73837bcb398b947cb4d3323d511141e0ddd0b456f59fbb1e8f920a5c20d7827a24309145efddee786140f +abcc0849ffe2a6a72157de907907b0a52deece04cf8317bee6fe1d999444b96e461eac95b6afde3d4fe530344086a625 +9385c0115cb826a49df1917556efa47b5b5e4022b6a0d2082053d498ec9681da904ecf375368bb4e385833116ea61414 +8b868c1841f0cdc175c90a81e610b0652c181db06731f5c8e72f8fafa0191620742e61a00db8215a991d60567b6a81ca +a8df15406f31b8fcf81f8ff98c01f3df73bf9ec84544ddec396bdf7fafa6fe084b3237bf7ef08ad43b26517de8c3cd26 +a9943d21e35464ce54d4cc8b135731265a5d82f9ccf66133effa460ffdb443cdb694a25320506923eede88d972241bf2 +a1378ee107dd7a3abcf269fd828887c288363e9b9ca2711377f2e96d2ed5e7c5ec8d3f1da995a3dcbedf1752d9c088fc +8a230856f9227b834c75bdebc1a57c7298a8351874bf39805c3e0255d6fd0e846f7ad49709b65ec1fd1a309331a83935 +877bcf42549d42610e1780e721f5800972b51ba3b45c95c12b34cb35eeaf7eac8fa752edd7b342411820cf9093fea003 +84c7a0b63842e50905624f1d2662506b16d1f3ea201877dfc76c79181c338b498eceb7cad24c2142c08919120e62f915 +8e18b1bd04b1d65f6ed349b5d33a26fe349219043ead0e350b50ae7a65d6ff5f985dd9d318d3b807d29faa1a7de4fe42 +8ea7b5a7503e1f0b3c3cd01f8e50207044b0a9c50ed1697794048bbe8efd6659e65134d172fb22f95439e1644f662e23 +b1954a2818cad1dad6d343a7b23afa9aa8ad4463edc4eb51e26e087c2010927535020d045d97d44086d76acdb5818cbf +a5271ea85d0d21fa1ff59b027cf88847c0f999bbf578599083ff789a9b5228bc161e1c81deb97e74db1a82a0afd61c50 +aa2fa4c05af3387e2c799315781d1910f69977ec1cfea57a25f1a37c63c4daaa3f0ecd400884a1673e17dd5300853bcf +b1cd2a74ca0b8e6090da29787aef9b037b03b96607983a308b790133bd21297b21ca4e2edec890874096dbf54e9d04c3 +801931607ec66a81272feaa984f0b949ad12d75ecf324ba96627bd4dc5ddead8ebf088f78e836b6587c2b6c0b3366b6c +95d79504710bdf0ad9b9c3da79068c30665818c2f0cdbba02cc0a5e46e29d596032ac984441b429bd62e34535c8d55b0 +9857d41e25e67876510ff8dadf0162019590f902da1897da0ef6fc8556e3c98961edb1eb3a3a5c000f6c494413ded15e +8740c9ffe6bd179c19a400137c3bd3a593b85bd4c264e26b4dfb9e2e17ac73e5b52dfacc1dcb4033cfc0cd04785f4363 +977f98f29d948b4097a4abdf9345f4c1fb0aa94ba0c6bf6faa13b76f3a3efc8f688e1fe96099b71b3e1c05041118c8d1 +a364422b1239126e3e8d7b84953ce2181f9856319b0a29fcab81e17ac27d35798088859c1cfc9fc12b2dbbf54d4f70b3 +a0f6ba637f0db7a48e07439bb92ddb20d590ce9e2ed5bab08d73aa22d82c32a9a370fe934cbe9c08aeb84b11adcf2e0e +a2c548641bd5b677c7748327cca598a98a03a031945276be6d5c4357b6d04f8f40dd1c942ee6ec8499d56a1290ac134d +9863e9cc5fbcdbd105a41d9778d7c402686bfd2d81d9ed107b4fda15e728871c38647529693306855bee33a00d257a7e +a54173bf47b976290c88fd41f99300135de222f1f76293757a438450880e6f13dbde3d5fe7afc687bdfbcfc4fbc1fc47 +b8db413917c60907b73a997b5ab42939abd05552c56a13525e3253eb72b83f0d5cc52b695968a10005c2e2fe13290e61 +a1f8388ef21697c94ba90b1a1c157f0dc138e502379e6fc5dc47890d284563e5db7716266e1b91927e5adf3cde4c0a72 +9949013a59d890eb358eab12e623b2b5edb1acbee238dfad8b7253102abc6173922e188d5b89ec405aa377be8be5f16d +a00fdb7710db992041f6ddb3c00099e1ce311dea43c252c58f560c0d499983a89de67803a8e57baa01ee9d0ee6fa1e44 +a8b1bcbed1951c9cdb974b61078412881b830b48cd6b384db0c00fa68bcc3f4312f8e56c892ea99d3511857ef79d3db9 +8f3ee78404edc08af23b1a28c2012cee0bdf3599a6cb4ea689fc47df4a765ef519191819a72562b91a0fbcdb896a937e +8155bbb7fa8d386848b0a87caae4da3dec1f3dade95c750a64a8e3555166ccc8799f638bd80ed116c74e3a995541587a +abfe30adbc0a6f1fd95c630ed5dac891b85384fa9331e86b83217f29dff0bd7cad19d328485715a7e3df9a19069d4d2f +89d0783e496ee8dbb695764b87fb04cee14d4e96c4ba613a19736971c577d312079048142c12ce5b32b21e4d491d281b +856b8dbc9c5d8f56b6bb7d909f339ca6da9a8787bba91f09130a025ab6d29b64dbf728ba6ed26e160a23c1cdb9bc037b +8a30dd2ea24491141047a7dfe1a4af217661c693edf70b534d52ca547625c7397a0d721e568d5b8398595856e80e9730 +ae7e1412feb68c5721922ed9279fb05549b7ef6812a4fd33dbbbd7effab756ab74634f195d0c072143c9f1fd0e1ee483 +b7ce970e06fa9832b82eef572f2902c263fda29fdce9676f575860aae20863046243558ede2c92343616be5184944844 +85ed0531f0e5c1a5d0bfe819d1aa29d6d5ff7f64ad8a0555560f84b72dee78e66931a594c72e1c01b36a877d48e017ca +b8595be631dc5b7ea55b7eb8f2982c74544b1e5befc4984803b1c69727eac0079558182f109e755df3fd64bee00fcaa5 +99e15a66e5b32468ef8813e106271df4f8ba43a57629162832835b8b89402eb32169f3d2c8de1eb40201ce10e346a025 +844c6f5070a8c73fdfb3ed78d1eddca1be31192797ad53d47f98b10b74cc47a325d2bc07f6ee46f05e26cf46a6433efb +974059da7f13da3694ad33f95829eb1e95f3f3bfc35ef5ef0247547d3d8ee919926c3bd473ab8b877ff4faa07fcc8580 +b6f025aecc5698f6243cc531782b760f946efebe0c79b9a09fe99de1da9986d94fa0057003d0f3631c39783e6d84c7d5 +b0c5358bc9c6dfe181c5fdf853b16149536fbb70f82c3b00db8d854aefe4db26f87332c6117f017386af8b40288d08f9 +a3106be5e52b63119040b167ff9874e2670bd059b924b9817c78199317deb5905ae7bff24a8ff170de54a02c34ff40a4 +ad846eb8953a41c37bcd80ad543955942a47953cbc8fb4d766eac5307892d34e17e5549dc14467724205255bc14e9b39 +b16607e7f0f9d3636e659e907af4a086ad4731488f5703f0917c4ce71a696072a14a067db71a3d103530920e1ec50c16 +8ed820e27116e60c412c608582e9bb262eaaf197197c9b7df6d62b21a28b26d49ea6c8bb77dfde821869d9b58025f939 +97bc25201d98cde389dd5c0c223a6f844393b08f75d3b63326343073e467ac23aacef630ddc68545ea874299ba4a3b4f +b73c9695ad2eefd6cc989a251c433fab7d431f5e19f11d415a901762717d1004bb61e0cc4497af5a8abf2d567e59fef4 +adaabe331eea932533a7cc0cf642e2a5e9d60bbc92dd2924d9b429571cbf0d62d32c207b346607a40643c6909b8727e2 +a7b1bbfe2a5e9e8950c7cb4daab44a40c3ffab01dc012ed7fe445f4af47fa56d774a618fafe332ab99cac4dfb5cf4794 +b4a3c454dcd5af850212e8b9ba5fe5c0d958d6b1cabbf6c6cfe3ccbc4d4c943309c18b047256867daf359006a23f3667 +a5c0b32f6cef993834c1381ec57ad1b6f26ae7a8190dd26af0116e73dadc53bb0eeb1911419d609b79ce98b51fdc33bc +ac2f52de3ecf4c437c06c91f35f7ac7d171121d0b16d294a317897918679f3b9db1cef3dd0f43adb6b89fe3030728415 +94722ae6d328b1f8feaf6f0f78804e9b0219de85d6f14e8626c2845681841b2261d3e6a2c5b124086b7931bf89e26b46 +a841a0602385d17afabca3a1bb6039167d75e5ec870fea60cfcaec4863039b4d745f1a008b40ec07bca4e42cb73f0d21 +8c355f0a1886ffced584b4a002607e58ff3f130e9de827e36d38e57cb618c0cb0b2d2dea2966c461cb3a3887ede9aef1 +a6a9817b0fc2fd1786f5ba1a7b3d8595310987fb8d62f50a752c6bb0b2a95b67d03a4adfd13e10aa6190a280b7ee9a67 +a1d2e552581ecbafeaef08e389eaa0b600a139d446e7d0648ac5db8bbbf3c438d59497e3a2874fc692b4924b87ff2f83 +a1b271c55389f25639fe043e831e2c33a8ba045e07683d1468c6edd81fedb91684e4869becfb164330451cfe699c31a8 +8c263426e7f7e52f299d57d047a09b5eeb893644b86f4d149535a5046afd655a36d9e3fdb35f3201c2ccac2323a9582e +b41c242a7f7880c714241a97d56cce658ee6bcb795aec057a7b7c358d65f809eb901e0d51256826727dc0dc1d1887045 +93001b9445813c82f692f94c0dc1e55298f609936b743cf7aae5ebfa86204f38833d3a73f7b67314be67c06a1de5682d +82087536dc5e78422ad631af6c64c8d44f981c195ddea07d5af9bb0e014cdc949c6fa6e42fce823e0087fdb329d50a34 +8e071861ceba2737792741c031f57e0294c4892684506b7c4a0fc8b2f9a0a6b0a5635de3d1e8716c34df0194d789ae86 +b471c997e1e11774bd053f15609d58838a74073a6c089a7a32c37dd3f933badf98c7e5833263f3e77bc0d156a62dd750 +8d2d8686fb065b61714414bb6878fff3f9e1e303c8e02350fd79e2a7f0555ded05557628152c00166ce71c62c4d2feaa +ae4c75274d21c02380730e91de2056c0262ffcecf0cbdb519f0bdb0b5a10ae2d4996b3dc4b3e16dbaea7f0c63d497fef +97140d819e8ca6330e589c6debdee77041c5a9cedb9b8cbd9c541a49207eeb7f6e6b1c7e736ec8ba6b3ab10f7fcd443a +af6659f31f820291a160be452e64d1293aa68b5074b4c066dac169b8d01d0179139504df867dc56e2a6120354fc1f5be +a5e5d8088a368024617bfde6b731bf9eee35fc362bed3f5dfdd399e23a2495f97f17728fec99ca945b3282d1858aa338 +a59cfc79d15dbdde51ab8e5129c97d3baba5a0a09272e6d2f3862370fdbaf90994e522e8bd99d6b14b3bb2e9e5545c6f +a30499b068083b28d6c7ddcc22f6b39b5ec84c8ee31c5630822c50ea736bb9dca41c265cffc6239f1c9ef2fd21476286 +88ffe103eca84bbe7d1e39a1aa599a5c7c9d5533204d5c4e085402a51441bb8efb8971efe936efbbfa05e5cb0d4b8017 +b202356fbf95a4d699154639e8cb03d02112c3e0128aab54d604645d8510a9ba98936028349b661672c3a4b36b9cb45d +8b89bb6574bf3524473cff1ff743abcf1406bd11fb0a72070ccd7d8fce9493b0069fb0c6655252a5164aee9e446ea772 +93247b1038fa7e26667ee6446561d4882dc808d1015daafb705935ddc3598bb1433182c756465960480f7b2de391649e +b027f94d3358cbb8b6c8c227300293a0dee57bf2fee190a456ad82ecfb6c32f8090afa783e2ab16f8139805e1fb69534 +a18bb1849b2f06c1d2214371031d41c76ffa803ee3aa60920d29dbf3db5fbfac2b7383d5d0080ba29ce25c7baa7c306b +827bf9fd647e238d5ac961c661e5bbf694b4c80b3af8079f94a2484cb8fba2c8cf60e472ebcd0b0024d98ae80ad2ff5a +838e891218c626a7f39b8fd546b013587408e8e366ecc636b54f97fa76f0a758bc1effa1d0f9b6b3bc1a7fcc505970a0 +836523b5e8902d6e430c6a12cff01e417d2bd7b402e03904034e3b39755dee540d382778c1abe851d840d318ebedce7f +850a77dda9ac6c217e2ef00bf386a1adec18b7f462f52801c4f541215690502a77ef7519b690e22fdf54dc2109e0ca38 +a8265c6ae7b29fc2bda6a2f99ced0c1945dd514b1c6ca19da84b5269514f48a4f7b2ccbab65c9107cfd5b30b26e5462f +ab3d02ee1f1267e8d9d8f27cc388e218f3af728f1de811242b10e01de83471a1c8f623e282da5a284d77884d9b8cde0e +831edaf4397e22871ea5ddee1e7036bab9cc72f8d955c7d8a97f5e783f40532edbbb444d0520fefcffeab75677864644 +80484487977e4877738744d67b9a35b6c96be579a9faa4a263e692295bb6e01f6e5a059181f3dd0278e2c3c24d10a451 +aae65a18f28c8812617c11ecf30ad525421f31fb389b8b52d7892415e805a133f46d1feca89923f8f5b8234bd233486a +b3a36fd78979e94288b4cefed82f043a7e24a4a8025479cc7eb39591e34603048a41ee606ee03c0b5781ebe26a424399 +b748b3fc0d1e12e876d626a1ba8ad6ad0c1f41ea89c3948e9f7d2666e90173eb9438027fadcd741d3ae0696bd13840f1 +acdd252d7c216c470683a140a808e011c4d5f1b4e91aeb947f099c717b6a3bad6651142cde988330827eb7d19d5fb25c +b9a25556a6ca35db1ed59a1ec6f23343eab207a3146e4fc3324136e411c8dba77efd567938c63a39c2f1c676b07d8cdb +a8db6aef8f5680d2bdb415d7bcaae11de1458678dcb8c90c441d5986c44f83a9e5855662d0c1aace999172d8628d8fe1 +af58147108e9909c3a9710cc186eab598682dca4bfd22481e040b8c000593ecb22c4ede4253ac9504e964dfa95a9b150 +8dd8bb70f1c9aec0fcc9478f24dfc9c3c36c0bf5ff7a67c017fa4dab2ec633fbd7bc9d8aa41ea63e2696971ed7e375f5 +aa98d600b22aff993a4d7a3ccabd314e1825b200cb598f6b797d7e4d6a76d89e34a4d156c06bddfc62f2ef9b4c809d1d +8a8fc960d6c51294b8205d1dabe430bef59bda69824fa5c3c3105bef22ac77c36d2d0f38ffc95ce63731de5544ccbeff +b6d1020efe01dc8032bd1b35e622325d7b9af9dcd5c9c87c48d7d6ebc58644454294c59b7f4b209204b5b1f899f473bf +8a750dc9fe4891f2dfe5759fb985939810e4cdc0b4e243ff324b6143f87676d8cb4bcb9dfb01b550801cedcaaa5349e2 +98c13142d3a9c5f8d452245c40c6dae4327dd958e0fda85255ea0f87e0bcbaa42a3a0bd50407ed2b23f9f6317a8a4bc5 +99f2b83d9ec4fc46085a6d2a70fd0345df10f4a724c1ba4dee082a1fde9e642e3091992ebf5f90a731abcb6ec11f6d9b +b218546ab2db565b2489ea4205b79daa19ef2acbf772ccaaa5e40150e67ea466090d07198444b48e7109939aa2319148 +84f9d1d868e4b55e535f1016558f1789df0daa0ead2d13153e02f715fe8049b1ce79f5bc1b0bbbb0b7e4dd3c04783f3f +80d870d212fbddfdda943e90d35a5a8aa0509a7a1e7f8909f2fcb09c51c3026be47cc7a22620a3063406872105b4f81a +b5b15138ff6551fac535d4bbce2ea6adc516b6b7734b4601c66ec029da2615e3119dc9ad6a937344acfd7b50e4a1a2ae +95d2f97652086e7ceb54e1d32692b1c867ffba23c4325740c7f10d369283d1b389e8afa0df967831ade55696931e7934 +8a5b580403e1a99cd208f707e8ce0d3f658c8280417683f69008d09cc74d835a85f7380f391b36ead9ac66d9eedd1cbe +a8b0c90bff34c86720637b5a2081f0f144cfe2205c1176cacd87d348609bc67af68aed72414dc9aa6f44a82c92c2a890 +865abbdd96c496892c165a8de0f9e73348bf24fce361d7a9048710178a3625881afb0006e9f5ee39124866b87904c904 +ace67bb994adef4b6f841cdf349195608030044562780a7e9b00b58a4ff117268a03ff01e5a3a9d9d7eff1dd01f5f4bf +b9371d59185b3d2d320d3fefeadb06ba2aa7d164352fb8dc37571509509fa214d736d244ac625a09a033a10d51611e2e +a8ef992771422dcf2d6d84386fde9fe5dba88bfded3dfcd14074ca04331b4fd53a7f316615cdfaf10ed932cbb424a153 +868cbc75f8f789ea45eded2768a1dac0763347e0d8e8028d316a21005f17be179d26d5965903e51b037f2f57fe41765d +b607111bcdfd05fa144aa0281b13ee736079ebbbf384d938a60e5e3579639ed8ef8eb9ca184868cdb220a8e130d4a952 +aca55702af5cae4cae65576769effd98858307a71b011841c563b97c2aa5aeb5c4f8645d254f631ed1582df3dbbf17da +b9b5cbace76246e80c20dfcc6f1e2c757a22ab53f7fd9ff8a1d309538b55174e55e557a13bf68f095ff6a4fa637ef21a +8571b0a96871f254e2397c9be495c76379faf347801cb946b94e63212d6a0da61c80e5d7bebbabcd6eaa7f1029172fe5 +902540326281e6dc9c20d9c4deaaf6fbbbcc3d1869bd0cf7f081c0525bea33df5cfa24ead61430fda47fb964fcc7994b +841af09279d3536a666fa072278950fabf27c59fc15f79bd52acb078675f8087f657929c97b4bc761cbade0ecb955541 +a1f958b147ddf80ab2c0746ba11685c4bae37eb25bfa0442e7e1078a00d5311d25499da30f6d168cb9302ea1f2e35091 +863d939381db37d5a5866964be3392a70be460f0353af799d6b3ed6307176972686bd378f8ad457435a4094d27e8dfb7 +835cd4d7f36eff553d17483eb6c041b14280beb82c7c69bca115929658455a1931212976c619bafb8179aed9940a8cc6 +8d0770e3cb8225e39c454a1fc76954118491b59d97193c72c174ecc7613051e5aed48a534016a8cf0795c524f771a010 +91aa4edb82f6f40db2b7bd4789cc08786f6996ebed3cb6f06248e4884bc949793f04a4c5ea6eefe77984b1cc2a45d699 +8fb494ca2449f659ff4838833507a55500a016be9293e76598bbae0a7cb5687e4693757c2b6d76e62bd6c7f19ed080bb +b59b104449a880a282c1dd6a3d8debb1d8814ef35aab5673c1e500ee4cb0e840fb23e05fa5a0af92509c26b97f098f90 +aca908e3bad65e854ae6be6c5db441a06bcd47f5abafdfa8f5a83c8cd3c6e08c33cab139c45887887a478338e19ceb9f +806f5d802040313a31964fc3eb0ee18ac91b348685bed93c13440984ee46f3d2da7194af18c63dea4196549129660a4e +ae4b2dca75c28d8f23b3ab760b19d839f39ff5a3112e33cb44cff22492604a63c382b88ec67be4b0266924dd438c3183 +99d1c29c6bd8bf384e79cd46e30b8f79f9cbc7d3bf980e9d6ffba048f0fc487cac45c364a8a44bb6027ad90721475482 +a16e861c1af76d35528c25bf804bfc41c4e1e91b2927d07d8e96bffe3a781b4934e9d131ecf173be9399800b8269efac +a253303234fb74f5829060cdcef1d98652441ab6db7344b1e470d195a95722675988048d840201c3b98e794b1e8b037c +905ac8a0ea9ce0eb373fb0f83dd4cbe20afb45b9d21ae307846fd4757d4d891b26a6711924e081e2b8151e14a496da18 +b485315791e775b9856cc5a820b10f1fa5028d5b92c2f0e003ba55134e1eddb3eb25f985f2611a2257acf3e7cfdfab5e +b6189c0458b9a043ebc500abc4d88083a3487b7ac47ed5e13ab2a41e0a1bee50d54a406063f92bc96959f19e822a89a7 +a30e15f995fd099a223fc6dc30dad4b8d40bee00caa2bc3223ba6d53cd717c4968a3e90c4618c711ed37cc4cd4c56cf3 +a1b1ed07fcc350bb12a09cd343768d208fc51a6b3486f0ece8f5a52f8a5810b4bc7ab75582ec0bc2770aed52f68eace5 +88aa739fbae4bece147ba51a863e45d5f7203dbc3138975dc5aef1c32656feb35f014d626e0d5b3d8b1a2bda6f547509 +ab570f3c8eabfca325b3a2ea775ef6b0c6e6138c39d53c2310329e8fb162869fde22b0e55688de9eb63d65c37598fca3 +89d274762c02158e27cb37052e296a78f2b643eb7f9ae409f8dac5c587d8b4d82be4ef7c79344a08ebec16ac4a895714 +99c411d2ad531e64f06e604d44c71c7c384424498ecd0a567d31ec380727fb605af76643d0d5513dd0a8d018076dd087 +80d0777fa9f79f4a0f0f937d6de277eec22b3507e2e398f44b16e11e40edf5feff55b3b07a69e95e7e3a1621add5ed58 +b2430a460783f44feb6e4e342106571ef81ad36e3ddd908ec719febeb7acaf4b833de34998f83a1dab8f0137a3744c11 +b8f38ccfc7279e1e30ad7cefc3ea146b0e2dff62430c50a5c72649a4f38f2bac2996124b03af2079d942b47b078cc4f8 +a178a450a62f30ec2832ac13bbc48789549c64fc9d607b766f6d7998558a0e2fad007ae0148fc5747189b713f654e6ba +98c5ede296f3016f6597f7ccc5f82c88fd38ed6dc3d6da3e4a916bfd7c4c95928722a1d02534fe89387c201d70aa6fd2 +a8cc5e98573705d396576e022b2ba2c3e7c7ece45cd8605cb534b511763682582299e91b4bb4100c967019d9f15bbfaf +848480ea7b7d9536e469da721236d932870b7bbee31ccf7ae31b4d98d91413f59b94a1e0d1786ee7342295aa3734969c +b88ea38f9ee432f49e09e4e013b19dff5a50b65453e17caf612155fff6622198f3cba43b2ea493a87e160935aaaf20a9 +949376934a61e0ef8894339c8913b5f3b228fa0ae5c532ad99b8d783b9e4451e4588541f223d87273c0e96c0020d5372 +96f90bb65ca6b476527d32c415814b9e09061648d34993f72f28fae7dc9c197e04ef979f804076d107bb218dfd9cb299 +a4402da95d9942c8f26617e02a7cef0ebc4b757fac72f222a7958e554c82cc216444de93f659e4a1d643b3e55a95d526 +81179cbc26a33f6d339b05ea3e1d6b9e1190bd44e94161ae36357b9cdf1e37d745d45c61735feed64371fe5384102366 +ad4dc22bdbd60e147fdac57d98166de37c727f090059cfc33e5ee6cf85e23c2643996b75cf1b37c63f3dc9d3c57ffa18 +8a9b1b93dc56e078ce3bb61c2b0088fd6c3e303ba6b943231cc79d4a8e8572f4109bbde5f5aa7333aae3287909cb0fe2 +8876ef583bc1513322457a4807d03381ba1f4d13e179260eaa3bddfede8df677b02b176c6c9f74c8e6eab0e5edee6de6 +b6c67e228bf190fbaeb2b7ec34d4717ce710829c3e4964f56ebb7e64dc85058c30be08030fa87cc94f1734c5206aef5f +a00cb53b804ee9e85ce12c0103f12450d977bc54a41195819973c8a06dcb3f46f2bf83c3102db62c92c57ab4dd1e9218 +a7675a64772eefddf8e94636fb7d1d28f277074327c02eea8fae88989de0c5f2dc1efed010f4992d57b5f59a0ab40d69 +8d42bb915e0bf6a62bcdf2d9330eca9b64f9ec36c21ae14bf1d9b0805e5e0228b8a5872be61be8133ad06f11cb77c363 +a5b134de0d76df71af3001f70e65c6d78bed571bc06bfddf40d0baad7ea2767608b1777b7ef4c836a8445949877eeb34 +aeadbc771eaa5de3a353229d33ed8c66e85efbd498e5be467709cb7ff70d3f1a7640002568b0940e3abd7b2da81d2821 +8c28da8e57a388007bd2620106f6226b011ee716a795c5d9f041c810edf9cf7345b2e2e7d06d8a6b6afa1ee01a5badc1 +8ed070626a4d39ffd952ddb177bc68fd35b325312e7c11694c99b691f92a8ea7734aeb96cf9cc73e05b3c1b1dcad6978 +ada83e18e4842f3d8871881d5dbc81aed88a1328298bfdc9e28275094bd88d71b02e7b8501c380fa8d93096cbc62f4fb +8befc3bec82dcf000a94603b4a35c1950ba5d00d4bed12661e4237afa75062aa5dcef8eac0b9803136c76d2dd424a689 +97c6f36c91ca5ca9230bfcbf109d813728b965a29b62e5f54c8e602d14a52ac38fa1270de8bfe1ab365426f3fc3654c7 +b01d192af3d8dbce2fe2fece231449e70eb9ac194ec98e758da11ca53294a0fa8c29b1d23a5d9064b938b259ea3b4fb5 +819a2c20646178f2f02865340db1c3c6ebc18f4e6559dd93aa604388796a34bd9fed28ad3ccc8afc57a5b60bb5c4e4ec +a9ffc877470afc169fecf9ec2dc33253b677371938b0c4ffa10f77bb80089afa2b4488437be90bb1bcf7586a6f4286e3 +b533051c7ce7107176bcb34ad49fdb41fac32d145854d2fe0a561c200dcf242da484156177e2c8f411c3fdf1559ecf83 +8fe2caff2e4241d353110a3618832f1443f7afe171fd14607009a4a0aa18509a4f1367b67913e1235ac19de15e732eb1 +84705c6370619403b9f498059f9869fdf5f188d9d9231a0cb67b1da2e8c906ead51b934286497293698bba269c48aa59 +899dddf312a37e3b10bdaaacc1789d71d710994b6ee2928ac982ad3fd8a4f6167672bc8bf3419412711c591afe801c28 +b2f7916d946b903ded57b9d57025386143410a41a139b183b70aeca09cf43f5089ead1450fce4e6eb4fba2c8f5c5bbe5 +8d5f742fe27a41623b5820914c5ca59f82246010fa974304204839880e5d0db8bc45ebab2ad19287f0de4ac6af25c09e +b93d4a1f6f73ac34da5ffbd2a4199cf1d51888bc930dc3e481b78806f454fcb700b4021af7525b108d49ebbbaa936309 +8606f8d9121512e0217a70249937e5c7f35fbfe019f02248b035fa3a87d607bc23ae66d0443e26a4324f1f8e57fd6a25 +b21312cdec9c2c30dd7e06e9d3151f3c1aceeb0c2f47cf9800cce41521b9d835cb501f98b410dc1d49a310fdda9bc250 +a56420b64286bdddda1e212bba268e9d1ba6bdb7132484bf7f0b9e38099b94a540884079b07c501c519b0813c184f6b4 +80b2cf0e010118cb2260f9c793cef136f8fa7b5e2711703735524e71d43bce2d296c093be41f2f59118cac71f1c5a2ff +adcb12d65163804d2f66b53f313f97152841c3625dbbda765e889b9937195c6fcd55d45cc48ebffabb56a5e5fe041611 +8b8a42e50dc6b08ab2f69fc0f6d45e1ea3f11ba0c1008ee48448d79d1897356599e84f7f9d8a100329ed384d6787cfc4 +aaa9c74afa2dec7eccfbd8bb0fc6f24ed04e74c9e2566c0755a00afdfdf3c4c7c59e2a037ec89c2f20af3fae1dd83b46 +aa9f6e8fd59187171c6083ae433627d702eb78084f59010ff07aff8f821f7022ef5fbbe23d76814d811b720a8bfa6cc3 +a56a3ded501659ad006d679af3287080b7ee8449e579406c2cae9706ef8bf19c1fc2eb2a6f9eaf2d3c7582cded73e477 +81971e077c1da25845840222b4191e65f6d242b264af4e86800f80072d97d2a27a6adc87c3a1cb1b0dd63d233fbafa81 +a6fa5453c4aaad2947969ee856616bf6448224f7c5bf578f440bcfc85a55beb40bef79df8096c4db59d1bd8ef33293ea +87c545adbfaaf71e0ab4bac9ae4e1419718f52b0060e8bb16b33db6d71b7248ae259d8dd4795b36a4bbb17f8fae9fd86 +b4c7a9bc0910e905713291d549cec5309e2d6c9b5ea96954489b1dff2e490a6c8b1fa1e392232575f0a424ba94202f61 +802350b761bcaba21b7afe82c8c6d36ee892b4524ab67e2161a91bbfa1d8e92e7e771efb1f22c14126218dd2cb583957 +b4e7ddb9143d4d78ea8ea54f1c908879877d3c96ee8b5e1cb738949dcfceb3012a464506d8ae97aa99ea1de2abf34e3d +a49a214065c512ad5b7cc45154657a206ef3979aa753b352f8b334411f096d28fd42bca17e57d4baaafb014ac798fc10 +8a80c70a06792678a97fe307520c0bf8ed3669f2617308752a2ab3c76fdf3726b014335a9b4c9cbcfc1df3b9e983c56f +a34721d9e2a0e4d08995a9d986dc9c266c766296d8d85e7b954651ad2ca07e55abb1b215898ee300da9b67114b036e0d +8cfce4564a526d7dca31e013e0531a9510b63845bbbd868d5783875ed45f92c1c369ce4a01d9d541f55f83c2c0a94f03 +ab3f5f03a5afc727778eb3edf70e4249061810eba06dc3b96b718e194c89429c5bfbec4b06f8bce8a2118a2fdce67b59 +aa80c2529fc19d428342c894d4a30cb876169b1a2df81a723ab313a071cba28321de3511a4de7846207e916b395abcc9 +82b7828249bf535ef24547d6618164b3f72691c17ca1268a5ee9052dba0db2fdd9987c8e083307a54399eab11b0f76b1 +8fbcb56b687adad8655a6cf43364a18a434bf635e60512fad2c435cf046f914228fb314f7d8d24d7e5e774fb5ffb1735 +a3010a61a2642f5ebbce7b4bc5d6ecb3df98722a49eb1655fe43c1d4b08f11dfad4bcec3e3f162d4cc7af6a504f4d47c +b3dcc0fdf531478e7c9ef53190aa5607fd053a7d2af6c24a15d74c279dbb47e3c803a1c6517d7e45d6534bb59e3527f5 +8648f6316c898baaca534dff577c38e046b8dfa8f5a14ee7c7bc95d93ae42aa7794ba0f95688a13b554eeb58aeedf9ba +89fca6fc50407695e9315483b24f8b4e75936edf1475bcf609eed1c4370819abac0e6a7c3c44f669560367d805d9ba63 +a367a17db374f34cd50f66fb31ba5b7de9dbe040f23db2dcc1d6811c0e863606f6c51850af203956f3399000f284d05f +91030f9ca0fff3e2dbd5947dcf2eba95eb3dbca92ee2df0ed83a1f73dbf274611af7daf1bb0c5c2ee46893ab87013771 +84d56181f304ce94015ea575afeef1f84ea0c5dbb5d29fb41f25c7f26077b1a495aff74bd713b83bce48c62d7c36e42d +8fe2f84f178739c3e2a2f7dcac5351c52cbed5fa30255c29b9ae603ffd0c1a181da7fb5da40a4a39eec6ce971c328fcf +a6f9b77b2fdf0b9ee98cb6ff61073260b134eb7a428e14154b3aa34f57628e8980c03664c20f65becfe50d2bdd2751d4 +8c6760865445b9327c34d2a1247583694fbeb876055a6a0a9e5cb460e35d0b2c419e7b14768f1cc388a6468c94fd0a0f +af0350672488a96fe0089d633311ac308978a2b891b6dbb40a73882f1bda7381a1a24a03e115ead2937bf9dcd80572ad +a8e528ec2ee78389dd31d8280e07c3fdd84d49556a0969d9d5c134d9a55cd79e1d65463367b9512389f125ed956bc36a +942c66589b24f93e81fe3a3be3db0cd4d15a93fb75260b1f7419f58d66afaa57c8d2d8e6571536790e2b415eec348fd9 +83fe4184b4b277d8bf65fb747b3c944170824b5832751057e43465526560f60da6e5bbee2f183cb20b896a20197168c7 +88a71aada494e22c48db673d9e203eef7a4e551d25063b126017066c7c241ee82bedaa35741de4bd78a3dd8e21a8af44 +8c642a3186ca264aac16ee5e27bd8da7e40e9c67ae159b5d32daa87b7de394bf2d7e80e7efb1a5506c53bfd6edd8c2c3 +81855d6de9a59cef51bef12c72f07f1e0e8fe324fcc7ec3f850a532e96dcd434c247130610aaee413956f56b31cbb0dc +a01e61390dcd56a58ad2fcdb3275704ddfbedef3ba8b7c5fce4814a6cdd03d19d985dba6fd3383d4db089444ea9b9b4d +96494e89cbf3f9b69488a875434302000c2c49b5d07e5ff048a5b4a8147c98291ae222529b61bb66f1903b2e988e5425 +b9689b3e8dddc6ec9d5c42ba9877f02c1779b2c912bba5183778dc2f022b49aed21c61c8ec7e3c02d74fe3f020a15986 +a2a85e213b80b0511395da318cbb9935c87b82c305f717a264155a28a2ea204e9e726bae04ce6f012e331bd6730cbb9d +91b70f44c7d8c5980ce77e9033a34b05781cbe773854d3f49d2905cc711a3d87c20d5d496801ad6fd82438874ce732b8 +884596417ff741bb4d11925d73852ffeea7161c7f232be3bdce9e6bbe7884c3a784f8f1807356ae49d336b7b53a2b495 +ae2aed8ab6951d8d768789f5bc5d638838d290d33ccc152edfb123e88ba04c6272b44294b0c460880451ad7b3868cc6a +89d8ebfb9beebc77189d27de31c55f823da87798a50bca21622cbf871e5d9f1d3182cf32ee9b90f157e6ce298e9efccf +afd00a4db4c2ed93cf047378c9402914b6b3255779f3bb47ded4ab206acb7eaebba0fd7762928e681b1aebcfee994adc +a2e49b6cd32e95d141ebc29f8c0b398bb5e1a04945f09e7e30a4062142111cd7aa712ac0e3e6394cfb73dd854f41ad77 +ae8e714ab6e01812a4de5828d84060f626358bb2b955f6fb99ae887b0d5ce4f67ebc079ab9e27d189bf1d3f24f7c2014 +a3100c1eebf46d604e75ebf78569c25acf938d112b29ccbe1a91582f6bd8ef5548ae3961c808d3fb73936ac244e28dbc +a9a02dcff0e93d47ead9cdddc4759971c2d848580bf50e117eb100cafca6afeaa7b87208513d5f96b1e1440ffc1b0212 +894ab01462137e1b0db7b84920a3b677fbb46c52b6f4c15320ef64f985e0fc05cec84cd48f389ce039779d5376966ea3 +b1e40e8399ee793e5f501c9c43bde23538e3ce473c20a9f914f4a64f5b565748d13ab2406efe40a048965ee4476113e4 +a5a7d97a19e636238968670a916d007bf2ce6ae8e352345d274101d0bbe3ac9b898f5b85814a7e4c433dd22ac2e000ff +b6394c43b82923231d93fd0aa8124b757163ba62df369898b9481f0118cb85375d0caac979a198ece432dbb4eb7cc357 +82d522ae3ff4fe2c607b34b42af6f39c0cf96fcfe1f5b1812fca21c8d20cece78376da86dcbd6cdb140e23c93ae0bcb2 +b6e0d986383bc4955508d35af92f2993e7e89db745f4525948c5274cfd500880cb5a9d58a5b13d96f6368bb266a4433e +b0b4325772ec156571d740c404e1add233fb693579f653b0fae0042b03157d3b904838f05c321d2d30f2dbd27c4d08ad +ac41367250263a2099006ef80c30bac1d2f25731d4874be623b6e315c45b0dc9a65f530fce82fb3dc25bd0610008c760 +b6c0b1ed7df53da04a6f3e796d3bfa186f9551c523bc67898bc0ecfc6b4a4a22f8c4d3bfc740ebf7b9fa5b0ea9431808 +8e78fca17346601219d01e5cd6a4837161a7c8f86fe2a8d93574d8006da5f06ae7c48eea7d2b70992c2a69184619663c +a21f91f47e04fafbfafacf3185b6863766a2d0c324ccac2c3853a4748af5897dbbe31d91473b480f646121339c9bae2d +a464d68786ab1fc64bd8734fce0be6fbe8dc021d3e771ff492ada76eedff466577c25e282b7c8ab4c1fd95ef5ff3631e +829a24badc7714081e03509ccfb00818ce40430682c1c0e4a399cd10b690bda1f921aabcbf1edfb1d8a2e98e6c0cedd6 +87ccf7e4bbcb818ef525435e7a7f039ecbb9c6670b0af163173da38cbdb07f18bc0b40b7e0c771a74e5a4bc8f12dfe2c +94087bd2af9dbeb449eb7f014cfbf3ee4348c0f47cde7dc0ad401a3c18481a8a33b89322227dee0822244965ae5a2abb +896b83ed78724dac8a3d5a75a99de8e056a083690152c303326aa833618b93ef9ec19ab8c6ef0efe9da2dbcccac54431 +821e6a0d7ccf3c7bd6a6cc67cde6c5b92fb96542cb6b4e65a44bbc90bbc40c51ff9e04702cb69dd2452f39a2ff562898 +b35b2096cda729090663a49cb09656c019fef1fc69a88496028d3a258ad2b3fd6d91ab832163eaa0077989f647e85e7e +b7857ef62c56d8bce62476cdb2ab965eddff24d932e20fc992bd820598686defe6cc0a7232d2be342696c2990d80721a +b343d974dfda3f6589043acd25d53aecf7c34b1e980ae135a55cda554ff55e531bc7c2dfe89b0d2c30e523c7b065dad1 +8d139e16a73cd892b75f3f4e445a10d55d1118f8eeafc75b259d098338419e72e950df6ca49cb45677a3c4e16fb19cdc +817b8535bd759da392b2c5760c51b3952ecf663662a137c997f595c533cd561ed7e655673c11144242160e41d1f2dd71 +817ee0f0819b0ccb794df17982d5b4332abff5fec5e23b69579db2767855642156d9b9acccf6ceab43332ccc8d2744dc +9835d2b652aec9b0eba0c8e3b6169567e257a6a3f274ec705dbc250ee63f0f8e4b342e47b9e0c280c778208483d47af8 +b78c40177f54f0e6d03083a4f50d8e56b5aafdb90f1b047bb504777d6e27be5a58170330aee12fbaa5f1e9d4f944acfc +ab8eebacf3806fac7ab951f6a9f3695545e2e3b839ca399a4ef360a73e77f089bb53d3d31dbd84ddfde55e5f013626e0 +96c411fc6aecca39d07d2aff44d94b40814d8cfc4ee5a192fd23b54589b2801694d820a0dd217e44863ccff31dda891b +8249c424a0caf87d4f7ff255950bbc64064d4d1b093324bfe99583e8457c1f50e6996e3517bf281aa9b252c2a7c5a83a +acf6ed86121821a3dd63f3875b185c5ebe024bdb37878c8a8d558943d36db0616545a60db90789c0925295f45d021225 +a37f155621a789f774dd13e57016b8e91b3a2512b5c75377ec8871b22a66db99655d101f57acaecd93115297caabfc21 +92e60ee245bd4d349f1c656e034b1a7f0c6415a39ac4c54d383112734305488b3b90b0145024255735e0a32f38dba656 +acec614e562ccfc93366309cfdc78c7d7ee0a23e3a7782a4fc4807b8803e6ebfb894a489d03e9a3c817ff2ec14813eba +b912f9dd26ed552cb14b007b893e6ed2494d12517e5761dbeb88521270144f8c3eb9571a0ad444b30a8a65e80bd95996 +8375408dae79c547a29e9a9e5d4ec8241b36b82e45e4ca3b0c36d2227c02d17bb171528d3778eac3bbdc75d6c4e8a367 +8c2d0e6e4406836da112edbbb63996408bb3cda4a2712fd245e4bb29a0100fdc89a2746d859b84a94565bc1cfa681813 +a7431bf59e111c072d28c97626cd54fcdf018421d053a787d2aef454b91251ee8ff9d3702d06b088f92b9ad2bbebff15 +8f3659b0fbeb90b7f30b7a49233325e806551a32911a654dca86e290b314483bbb33fe6482387bc48c35d85c1dd0441c +8dca5ba23f0bb76f7dacabf12886053552ba829a72827b472a2f01e19a893155cdce65f1fb670000f43e8c75ba015a31 +8c1514c083c77624eeb5d995d60994a2866192e15c4474d0be4189fae0e9dbd62494ebb4c02fbc176b53be548abbc5a1 +80498d2ed153381baf3b0f81da839ed0eea6af5796c422b8e59be805dba48c4395bb97824ac308170bb4f14f319c5ddf +84f5ebc3bf96362457993e9fa31493c31c4283075e2403f63d581b6b0db8a3df294b2085643f2007f4de38cb5d627776 +958e6e38774da518193a98397978dbc73d1c3827b4996ec00b4183da2c305a187a0ada9aa306242814b229a395be83c9 +ab8b8fbf73845615e7fab3e09e96cc181159eab09f36b4c1239b3c03313c9aeb4bbb51e16316fe338b2319ed2571b810 +977e4e33b33bd53394e591eba4f9a183e13704c61e467d74b28f4ad0b69aa51501a5221cb1e0e42bcb548ca518caa619 +a9bb7ecb9846cc30d04aad56d253c3df7004cebb272f6adf7b40a84adef9f57291e0d08d64c961b9fc406cdb198aab9b +8d2b72dc36406a545a9da44e1fddfb953d4894710ca026d6421a4ac91e02d0373a599f2acfe41d8258bc9679cf6f43d3 +904192fc8fe250f61ecb8a36abbbccae85f592bbf00c10039c30b5a1c733d752a04e4fd8a1000c6578616f8a16aa83a3 +87f5fdfe20bbbf931b529ec9be77bbfcc398cad9d932d29f62c846e08a91d2f47ae56ad5345122d62a56f629f9a76c4d +84cc3a53b2e7b7e03015f796b6cb7c32d6ded95c5b49c233ac27fafa792994b43c93cda6e618b66fce381f3db69838ba +aab58da10d7bbe091788988d43d66a335644f3d0897bbc98df27dcc0c0fcee0ac72e24f1abdd77e25196a1d0d0728e98 +a10ea8677c2b7da563d84aa91a314a54cab27bb417c257826ebdd3b045d2a0f12729fe630bbbf785d04874f99f26bee8 +acc4970ef2a4435937a9b8a5a5a311226ca188d8f26af1adfcd6efb2376a59155b9a9ff1cff591bde4b684887d5da6e5 +8dc7cf6fcca483c44eb55e7fb924bf3f76cf79b411ae4b01c6c968910877ac9c166b71350f4d935f19bdffb056477961 +ac2dd1182ded2054c2f4dbf27b71a0b517fb57193733a4e4e56aca8a069cff5078ffd3fd033683d076c1c639a4de63c7 +932ec87c450cd0dc678daf8c63cd1bf46124fa472934e517fbbfb78199f288ff7f354b36e0cc6c8739d3f496cfe0913b +b0d631ced213e8492be60ea334dbe3b7799b86d85d5e8e70d02beef3ae87b1d76e1df3bdb5f7ba8a41904c96f6a64455 +929d7239ead7575867e26b536b8badf2e11ca37840034d0e5c77039f8cce122eff5a1bf6e0bcadde6b3858e9f483d475 +aaae5d372d02ee25b14de585af6fbc48f2c7cd2a6af4f08352951b45aa469599eff41e820df642ca1a0f881120e89dbe +b23c411741a6b059f04fa4f5fd9dd10e2a64915f2de6ea31e39c32f2f347a776a953320e5f7613fcb1167efe502f5c5c +a4581b0ae633fe29c6f09928e5efb16db019eeac57f79fef2fa1d3c9bee42ce0e852bc60b9d0133265373747e52a67a4 +81b33afffd7b2575d4a9a1c5dd6eee675c084f82e06b9b3a52a3c9f76e087f12dca6e0ffddc42fb81ce1adb559d47a38 +89cc890f06b424591556aabdfdbb36d7a23700425e90c9cfed7d3da226b4debe414ac5bdf175273828ce6c5355712514 +a4399438be75cfae2bf825496704da5ed9001bed8538d8ac346c8cf0d4407808e9ee67573eb95fe1c6872ac21f639aaa +ad537f7ce74a1ca9a46fc06f15c1c8a6c32363bd6ac78a3c579ed8f84252e38a914cac16709fe65360e822ef47896de4 +8e53b69f5e3e86b86299452e20ea8068b49565d0d0ab5d50ce00158a18403ae44e1b078a3cfd3f919aa81eb049a30c6e +a59f2542c67a430fd3526215c60c02353ee18af2ff87cb6231a2564fe59b8efec421f18d8b8cc7f084675ecf57b3fd05 +b8d9bac93ef56cb4026dd1c731d92260a608fd55b8321e39166678e1dab834d0efddb717685da87786caeb1aaf258089 +aa2df56f4c6fe9e0f899116c37302675f796a1608338700f05a13e779eb7cf278e01947864a8c2c74cc9d9a763804446 +b0108ff2e327dcb6982961232bf7a9a0356d4297902f4b38d380ff1b954bfbcae0093df0f133dd9e84d5966c7b1aada7 +b06b813b01fe7f8cf05b79dc95006f0c01d73101583d456278d71cd78638df2b1115897072b20947943fa263ddab0cd6 +aa41e6c4d50da8abf0ea3c3901412fe9c9dff885383e2c0c0c50ed2f770ada888a27ea08bbb5342b5ff402e7b1230f12 +a48635dbb7debac10cb93d422c2910e5358ba0c584b73f9845028af4a763fd20da8f928b54b27782b27ca47e631ebf38 +80a574c208e994799e4fa9ef895163f33153bc6487491d817c4049e376054c641c4717bda8efbeb09152fa421a7268a7 +b592bfd78ae228afc219c186589b9b0b5c571e314976d1ed5c1642db9159d577679a73c049cfc3dcfefcd5a4f174eeea +aa1f08af3918c61eadf567a5b1a3cdcdfb1b925f23f1f9e3c47889762f4d979d64686ce1ce990055ef8c1030d98daa3b +857df4cfd56d41c6d0c7fcc1c657e83c888253bae58d33b86e0803a37461be5a57140a77fb4b61108d1d8565091ada1c +8fae66a72361df509d253012a94160d84d0b2260822c788927d32fd3c89500500908c8f850ef70df68ddaeb077fd0820 +aa1dbefc9aef1e7b896ff7303837053c63cfb5c8a3d8204680d3228ac16c23636748fe59286468c99699ae668e769a0c +b64b1cb2ba28665ed10bad1dddc42f3f97383c39bad463c6615b527302e2aaf93eb6062946d2150bd41c329697d101be +b6d35e3b524186e9065cee73ea17c082feff1811b5ab5519dd7991cdff2f397e3a79655969755309bd08c7d5a66f5d78 +a4dae7f584270743bbba8bb633bdb8bc4dcc43580e53d3e9e509ff6c327e384f14104b5bdfe5c662dc6568806950da37 +aae84d3d9ad4e237b07c199813a42ed2af3bf641339c342d9abf7ebec29b5bd06249c4488ce5c9277d87f7b71b3ddd37 +b82a463cf643821618a058bddf9f2acb34ac86a8de42a0fa18c9626e51c20351d27a9575398a31227e21e291b0da183e +8b6c921e8707aded3ea693f490322971b1a7f64786ef071bc9826c73a06bd8ae6bf21bc980425769627b529d30b253ce +80724937b27fc50f033c11c50835c632369f0905f413b1713a2b0a2274bec5d7a30438e94193d479ba6679dbe09a65ef +a1d9b259a2ca9cff8af6678b3af0a290c2f51e9cf26d5fe3c6a4fa3d28cbf33cb709b7f78b4f61cb9419427983c61925 +96a3e69a5ed7a98ce59c4481f2ffb75be9542122ad0eb4952c84d4536760df217854d4ec561ce2f4a79d3793c22fa4f4 +990c4d9a4a22d63a8976d34833cafc35936b165f04aed3504e9b435f0de1be4c83b097bbaa062483cf3dee3833b4f5b6 +b9bf5e4b270aec4a0dc219457b5fed984b548892c4b700482525ba1a7df19284464f841dab94abfabcaa9a7b7a757484 +acaecf49cb4786d17cf867d7a93bd4ffee0781766e11b5c1b29089ae0024c859d11b45828fbff5330b888543264d74a9 +b0e1a0865b1e6f9e4a0e31d0c885526ac06678acc526fda5124742a2c303bd0e8871a0cb7951ec8ed9540fc247c8d844 +82b3d327b3d1a631758451e12870816956cd0cef91fcf313a90dd533d5291193a0ff3cc447054564ce68c9b027a7ffd7 +a2843602abb98f0f83e000f3415039788da1e9a096bfe8fed6b99bab96df948c814560424ffebe755cb72f40436fb590 +ab1c7b43cf838798d1e314bc26e04fc021e99a7bfbfc8ffde62fa8d7f92139de86a377289d5177021154229de01ede15 +95e5cf5dd87ae3aed41b03c6c55f9dfad38dc126b17e7e587c156f7745c8da0bd1d60acb718fc1a03b61344f01e3de4d +86f021a3762bb47167f80d4ef1b1c873a91fe83409f9704f192efeebbc3ece0729cd2f92f63419907ea38ae47bc907d2 +aaa1445dafbbcd645d4332d9806225e9346ee5ac6b22ad45e8922134fe12f3d433f567a6a4c19efdd9d5775a7de1e92f +8fd7e15688eef75df7b8bca3d61bc9fca4f56e047cdb6d0b864e7d1c4966eac27d6094b0c8482b49739f83ec51050198 +80aab8b4d394eb011d4ec6a4c2815617308c9b847c6fa6a3d7e6af1c79420ef6ff2a13934a398581c40ee4cf1cac02ac +8970b97ac076a1d8a321ce00eada0edf974a46bf3cc26f6854e4218cdfc8d2b0c32199d9658f254b4fbae5a2c5535f41 +a1aa2ec5b03df0a630e73dd048680ed6d3032c324941423f45cd1f16038789e5e75b876a13948732e9079a422f66a9fc +b5fe5f5e2f2ae2beeb8e95859a02fc45f01f9fb0ebb2bd8ec9ec976b3e806228821a9775096d341d662bc536c4d89452 +a2bc1f170b62d0d5788b02391337b2ab157c38e725694e80aeead7383e05599be0e2f0fa27ef05db007061809356e147 +a8a69701d4a8d0d972390e9f831fd8e9f424b2c2ef069e56bd763e9e835b3ce5f7cf5de5e5c297c06ace4aa74df1067c +b43d551af4ff3873557efe3f3fb98e5ede9008492f181f4796dd1a6bcda8b9445c155e8146966baa812afae1abe06b48 +b4b1dae44fd596813f30602ab20e9b1fb20cb1bd650daacc97b7e054e5c0178b8131d439a9e5b142ca483cc012a362b3 +b95b8a94c30a831eaaebea98c65cc5d0228c78afd6603d4aa426d8186aecc951f1a11c33951f51df04c7e6fa43ffb5ae +b100059624cf9db371bec80013a57a8f296d006c139a8766308f1ea821c7eccc26cad65bc640ab3f6cef9062653bf17d +8e5a2cb76716e0000d13bce5ef87acac307362a6096f090f5f64e5c5c71a10fddfdee8435e7166ba8c3ad8c3f540f3e4 +93d2c43e21588c1e83c4255c52604b4ac3f40e656352d1827e95dd5222a45aebff9674e34fbbe7ed21eca77bd9b8dcbc +8aeaed611546bb9073b07512a9a1f38a7f436ab45e11775a0f9754baaf63e9bcc7bb59b47546a5ded5e4ba2f698e3b5f +af9e6792e74a1163fe27612f999a2f3cfa9048914c5bef69e3b2a75162bb0ce6ece81af699ad7f0c5278a8df0ba000d2 +850bf2d5d34791c371a36404036ad6fdcd8fb62d1bb17a57e88bda7a78ea322397ce24d1abf4d0c89b9cf0b4cc42feb3 +87f7e2a1625e2b7861b11d593aaac933ed08a7c768aebd00a45d893ed295bbb6ed865037b152bb574d70be006ddc1791 +8dcce8f4ad163b29a2348ea15431c2c6ea1189ece88d2790e9f46b9125bd790b22503ec391bc2dee8f35419863b2c50c +b4bf5266c37f12421dd684b29517982d5e4b65dfdfba5fc7bd7479fd854aabf250627498f1e1188a51c0a88d848ec951 +8651623c690247f747af8fdffdc3e5f73d0662bc3279fa2423a3c654af9b6433b9e5e0155f1ce53857e67388e7e3401d +b155120f196d52760129dde2e2b1990039b99484cdc948fa98095cd23da87679850f522e5955eae34ac267d2144160d3 +aec8115e8d7b6601fbceeccf92e35845a06706d46acd188452c9f7d49abef14c6b3a9a9369a8bab2fd4eb9288e2aaca5 +998a8ca4dc0f145f67a8c456f1d6a7323c4836fe036dcbb0f27eb1c596d121eb97369638a9908cfaf218c7706f266245 +b235fbafac62802742ee3d26b1f4e887f7d2da4d711ba7f9bb6ca024de7beec1de66bb830ce96d69538f7dcb93c51b26 +9258d2ddc21ab4e3edcde7eb7f6a382a29f1b626003cc6fdd8858be90f4ad13240072d8a8d44ef8de51ca4f477fa6c45 +99d038487821c948142c678acd8c792960993dd8cb5e02cb229153a1ee9f88249f4ad9007f08e5d82e2a71fb96bb5f32 +a88ee9dbc73d3d8e0f447b76fdb3a27936bde479a58d5799176885583dc93830ac58bca9087075950ea75100cf51af23 +88b9b15816e5a0387153c1f4b90f613beb3ea4596037da01a81fdd2bcbd0baf5598db99f77e7694e5a0d35e822758108 +907ae4b637d06b15846ee27d08c9c9af42df261c5bdd10cf5bc71f8e5ca34b33ac2405307023c50bdb8dc7b98a2cd5fe +9393d6900e1d2d1a1e42412fefd99578d9ac1d855c90a3e7930a739085496448609d674ca9b34016ad91f22d1cac538e +a28ac56b216730b7dcdb5ab3fc22d424c21a677db99a9897a89ed253ea83acfd9d83125133f5be6d9cd92298df110af8 +b027590ee8766f1e352f831fda732adbaf77152485223ad5489ef3b0ce2d2e9f98d547c111fe133847ebb738987fb928 +a9cc08fbd5c3fee8f77cf6eb996a5cafa195df5134dab000e4d0312f970a5577942ee89794e618074f49841f1f933a42 +a8b3535c3df0b1a409d3fc740527ee7dd5ac21756115cde6f87f98cc7623f50cfcf16790689cab113ee7c35a5bd4879f +b61420227b97e5603ae8a716c6759b619f02b8fdc48acbf854352aa6519dad74b97bacc1723ca564cbf3ca48539ed773 +853762498de80eebf955a6c8ddd259af463e4e25f0b6ba7b6a27b19bdbf4c585de55760a16e2d9345cdba6b2a02610f3 +a711c1b13fc6c30745203c5d06390e6c82bd7c50f61734aa8d99c626faba30119bc910be63ec916c91ba53f8483c05a8 +b488c0a793f4481f46b5875d96eecd73e46209a91677769f0890c5e002ecd7d4b1c9f4ba68c47fbed40e3857b1d8717a +a651c5e812ae65b1c66d92c607e80be330737ea49c1dcfe019c0ecea0f41a320406935bb09206a4abff0d1c24599b9ad +85e34e7d96e4b97db98a43247b6c244383b11ca10bf4777364acf509a6faa618bc973e2136a4693fbc8ab597e308fd5a +99837214102b394fffa7f3883759554c6bb7a070f5c809303595a44195e02b9a169460dc6bbffb62bdc0e7ced5f0a5c1 +a952f89c0afb4bdae8c62b89cc3cfb60d0576ba4fe01a5d99534792f38d8848d919b3fc7577435d8443a044d2ee0bcfa +a1ac1f81acb29798acdfc493854663519e2d1b0e9d23d286ce33882c34b4c1c0bb43dd9638166d8026315a44d9ec92a8 +ac9c58aa38219ae659d23007cc7b97fd25b7b610b2d81a8f9f94ddb089efc49c049a8ea4c56e6eaf7b6498f422a97b3c +87e61d501c242b484fb9a937ef21d485f6678d75257fc8fe831b528979068cadbe7e12b49c34058ec96d70a9d179ab14 +aa45f6852f35cc8b65a4a8b5380641d2602a4fa4e3a035db9664df3ac2e170b1280c4a8b7b55161430063e54de4158a6 +a46975614ddde6d134753c8d82c381966f87203d6e5a5fb99a93b0d43aa461466b37f07b8d0973a1abd6ee2b40f24348 +8d35f97297773422351f4d99564c1359ef1a10cfb60aa0e6c8985a78f39b4268486312c8ebf9dd2ef50a771aa03158eb +8497c6242102d21e8b3ade9a9896c96308ab39171ab74cbd94e304c47598e2c2a7b0a0822492ac5c076ba91d4176481d +973f8fcb5f26915b3a3ef6fe58cc44bc7f4e115cd0ad9727d8d1b8113e126ae2e253a19922c5433be4ab2311a839c214 +ae3ee9f1d765a9baf54b4617a289c3b24930aa8d57658a6b0b113bbf9b000c4a78499296c6f428bbb64755dfd4f795d2 +a5be7a8e522ef3dcf9d2951220faf22bb865d050f4af2880b8483222ff7aad7c0866219fcc573df9d829c6efbb517f98 +a5f3c7fabd7853a57695c5ad6d5b99167d08b5414e35ed1068ae386e0cb1ee2afbbe4d2b9024379b6fc3b10c39024d36 +978d5592d4798c9e6baceff095413589461267d6a5b56cd558ec85011342da16f4365d879b905168256f61d36d891b1f +b7b6eaffa095ecbd76d6e1e88ceebabaf674d9ef7e331e875c6d9b9faa1762c800ff1ea597c214c28080f67a50a96c1e +8a1ab53ae5ceaa42e06e58dd8faf6c215fc09ba111ca9eeb800612334d30d5971448be90fec62ed194328aadd8c8eecc +a9ca532cac8ace9a9e845382f8a7840bf40cb426f2fcad8a2f40aadbb400b3a74021627cc9351b0966b841b30284962e +8dddeda8854c8e7ddc52676dd1d0fed1da610ed5415ddd7d25b835bd8420a6f83d7b67ec682270c9648b2e2186343591 +888906aac64fd41d5c518a832d4e044fdc430cfe142fd431caf4676cafc58853ce576f098910d729011be0a9d50d67b5 +96a3f886a2824e750b1e2ea5c587132f52a0c5e3ff192260d8783c666206bd8ebd539933816d7cdd97e4bc374e0b1edf +a150a29ffb2632cc7ec560983d9804cd6da3596c0c25956d27eb04776508eae809659fc883834269437871735de5f9ed +81f7ad4d2959d9d4009d1dfbc6fee38f930f163eb5eac11e98dc38bd2f7f224e3f5c767583f8e52d58d34f3417a6cf90 +97ccac905ea7d9c6349132dd0397b6a2de9e57fd2d70f55e50860e019de15c20171a50b28a5c00ef90d43b838253b3d1 +95694f00c21e8a205d6cbda09956b5b6ec9242ec8c799a91f515b07dcc7de3b6f573e2c0ba149f5a83700cda2d1df0f5 +82bbc3c4a3b3997584903db30fffd182a266c7d1df3e913f908d5a53122fa12cf5acd11d915d85d5bd110fcc43cee736 +8d3f24b4949aa1b4162c28dfbb9f813dd1d8b330f71325448dc45ea34d59b69ca95059402aae011e1b5aba6e536bc6ec +92c734c19752d24782331e74c9af97a8399ddfdd32954e91cda7363dba876aca4f730b451c50a8913950420682da8121 +8653d2c79f77b8c7dcdf7e8dee42433998aeedf1b583abfca686d47a854de1b75e9a4351580c96d1a2a9532659203361 +886f0e414cb558c1a534a1916d3531320a9b6024639712ffe18164ce6313993a553e2b9aafe9c0716318f81a5d0bb1da +b31b5efaba5a5020c3bcea0f54860e0688c2c3f27b9b0e44b45d745158f484e474d5d3b1a0044dd6753c7fb4bf8ace34 +b2d615bbdfdc042d6f67a6170127392d99f0e77ae17b0e1be6786ff2f281795f1bf11f83f2e0f8723b5cdd1db1856e09 +a6e014cca531e6ac2922239b5bee39d69d9ba6d0fa96a4b812217dd342657d35606f0b9c5a317efd423cdb1047815e3d +a8921736b69c9fbb29f443715174bac753e908251804620c542fad6cfbfda7bdfe287f2902f30b043a8a4b4818cfdeef +8d73a9949a042ec2dcefa476e454cd9877eee543b1a6b3b96a78ffcff87421e8b26dd54d5b3192ac32073cb36497acc3 +b936a71ee8df0e48867f3790adf55dc8efc6585024128de2495f8873bd00fd9fa0984472125e801ed9c3cdce6698f160 +82f69c06209c28f64874e850601dda56af44ffc864f42efa8f9c6a0758207bf0a00f583840982dec0a517ab899a98e5b +b7a0a14411101473406f30e82f14b13e6efc9699e7193c0be04bb43d1b49e8c54812ce0f9b39131a20379c4c39d3bbe3 +81159c969f38107af3b858d7582b22925a7ccced02fae3698482d7e9cdc6c568e959651991c6cf16c53a997442054b61 +8bf1116a206e0ce9199fcab6ed2b44a9e46e8143bff3ed3f1431f8d55508fe2728b8902670cfd8d9b316f575f288ed9d +a279b2149824b64144eb92f5a36b22036d34a52bd5a66e5da4b61fbc95af6eda8e485c7914f448abd8674fc14d268d9d +8b98279b5f3588d1a2f8589d2756458690a502728800f8d94b28e00df842a101c96ab9c5aee87c5bbe65552c0c383b80 +b4a27a351ec54420f94e0a0a79d7c7a7337940399646631baca93eeab5fd429d7fb39428be77dcbce64a13eaa3c8ca1d +90c08baa29ec8338ffce381eae3d23ce3f6ba54e5242dec21dc3caaed69cac13f2ab5e8d9d719bc95720fa182eee399c +85156d65bb4fef69ffd539ab918b3286105ca6f1c36a74351ab3310b339727483433e8f8784791f47b4ba35ca933c379 +923005013c27209d07c06a6b92b0cbb248a69c5e15c600bbcc643e8dcd2402adebd94dd4cafb44ec422a127e9780aaec +863b23eb5463a6ef5a12039edc2f8e18e3c97b244841bc50af02459b1bcc558367edf2f6e4fe69f45f37887469dd536d +87a4a7708a112724ff9b69ebb25d623b5cae362ae0946daed2ec80e917800dbfcd69f999c253542533242e7b9a5cc959 +8bf4347ceea7f94b53564f26b1a4749a16f13bf71a9e03a546f906f7c423089820ff217066159b0637d9d6824e9c101c +ab07eef925d264145971628a39e4dd93ff849767f68ed06065802cf22756fc6bf384cf6d9ab174bfc1a87bcc37b037aa +8e3f10a42fad43887d522dc76b1480063267991c2457c39f1e790e0c16c03e38a4c8e79a0b7622892464957bf517ebd8 +a8722fc7b1acf0be18f6ddf3ee97a5a9b02a98da5bc1126a8b7bf10d18ee415be9a85668eb604ef5a1f48659bc447eb5 +878d6b2a9c0aca8e2bc2a5eb7dd8d842aa839bbd7754860c396a641d5794eab88a55f8448de7dbddf9e201cbc54fe481 +ada881c167d39d368c1e9b283cf50491c6bfc66072815608ba23ab468cfbd31ca1bd7f140e158e0d9e4d7ebfa670bc2d +a2b48578fa899d77a7ee1b9cb1e228b40c20b303b3d403fd6612649c81e7db5a7313ba9702adc89627b5fd7439f8b754 +8e051280e10551558dcb5522120ac9216281c29071c0371aaa9bde52961fe26b21d78de3f98cb8cd63e65cff86d1b25c +a7c5022047930c958e499e8051056c5244ae03beb60d4ba9fe666ab77a913a067324dfb6debcb4da4694645145716c9d +95cff6ec03e38c5ab0f6f8dccde252d91856093d8429b7494efc7772996e7985d2d6965307c7fdfa484559c129cca9f9 +993eb550d5e8661791f63e2fa259ab1f78a0e3edad467eb419b076a70923fede2e00ddc48a961d20001aaae89fad11e8 +abb2826e4d4b381d64787a09934b9c4fe1d5f5742f90858228e484f3c546e16ee8a2a0b0a952d834a93154a8b18f3d16 +a922ca9f2061996e65ef38a7c5c7755e59d8d5ce27d577abcdd8165b23b4877398d735f9cb470a771335fc7d99ecb7fc +90f22862216f6bc1bbf5437740a47605d1ff5147b1f06f7b13fec446e4c5a4a4a84792cb244a1905f3478a36f8d7065b +87f3d9a86afef5b79ea1ca690ee1ee4bb9754b66f7c50a42ad6b99af7c222c853ca161f440a0a2a60b3b5a54e3493240 +80a9ca9a2d33b9cf61976b3860d79f5d00de89a06ef043d2a52931809018aeb4ce70423cbef375b29c2c750c2c8704c2 +b4e798ef1d615896108dae37ac50c1e859216ab6dbac11653e44d06ce5209057b4b0dd6d31dcfcda87664a23c8ef1cbd +aaed6d1e7c5b1db06f80dae6c24857daadfb0268f20e48a98fba4b76de1ebf65fb84c3be95fd6a418b498f8285ec63bd +aeceaa316c6369492c939f94809bc80e0857abac86c0d85be8066bbf61afbaaec67e28c572437a8d35c49dd596b3134f +b791c3d53ed34a7d1c8aa89b7953e3684c3cd529230824dc529739a5fbe74b58b87f01e56e7a169f61c508237ef67160 +9351f8c80634386c45c0050d2f813193f9d839173be941e2092d729be5403632a2f18dffdc323d69eb0dc31fa31c5866 +97693184d5c0056ae244dfb6709cafa23a795dc22d497a307a7f9cf442d7452024023c54a8d6bda5d90a355ba2c84f3a +85362daa003d23511ca174a8caafe83d52b6436dc4e43c4c049e5388d9211b5cbef3885896914d86d39be0dd1f910511 +a2511b5fa34b24eeb0e1bcbcf872a569d1ff5570fe7b0fb48f5542f7fe57bad808d34b50afa87580866a6cb0eba02f27 +b382e3327eb1401f2d378dbb56ac7250adde0961bd718575a64d264ffd44772c20752d4035c3ba60eb435e160b375e20 +afad8a5d40b536c0720556845a6b257ed42165c14fb4b4a874717d107752f49ed9380c5b048df3aca67287bb8fc411a8 +8fad0c98434ca5373c2d767868f679b76b4a8d04bca8240ea3f388558262c2d61b73b16fc1160932652b5688c25fffcf +83898008b5cbb6f08f8ef3ec179427869682bb4e8d38f6e6a687a214d4a307436afc64ee67d70a5a8ba9730bf839aecc +b85232e79913785fd82b06890706972b4ad7a309489930ae23390d51aa5189731f8a2df24800409a8c36b3dd6fc91275 +a24ff26ec792f3701da4c5638c1fca4fa4dae95b01827d6200d583c4caf17ea3171393ba2a8c23d1ee8b88402916f176 +adc5c7a7ff6b41d6cc386b7fc69d7bb04179bdf267864f9aa577f0f6a88438191fa81ebaf13055c2f2d7290be6421ace +a05e835abd502d31454d40a019010ff90b6b0b1f993075a35c9907aeab7a342ac0ba6144dc9379aada6119157970e9b2 +85ff07ba58463e7f153fc83f11302e9061e648a5cbd272bb0545030b20e11facd8b3ff90c9ac8c280a704fbda5c9d1b0 +a6c735ada8f4587da8cdad7ea3ada01650b5a3ecab8d81daa7a5f5de51ef4a6592b524692584306f06be3f6701f2870c +b138deee4e53ae8d677fae104f713ef1b8babfecec16b6a85785a66a72784eb09d44c3b63567222ade714e98f7d1604e +ae79c1a49dafcdd972acd95d8ad0a35c02adc7fd736d4c44c3cd13df5789d339b5ea16bddbbd43e486a061ab31baa5c0 +ab3cf2371a1d7dcd0ffe3869a0178230964b06694bf258b2073ea66a2afccd845b38485da83d02e1d607d4c5c36b78a8 +ab9609f28a325fd01cb39540e3a714506c44e52ef28ee640f361deb5760aadbb23e804663b0fa20a66e239c33f8d8bb8 +8ed95ea8e76e1b42823d7915a6aae77d93746f846bf602841dfce0e47543a36efb9ee7e5b42c73c3209d911225cc471b +a80b6162036d43811482323f0ce59eb18740e33a63d7c7bbbf3be206985919e5342d53a69df537d43e8b7d7f51e8892f +93c03d0a5083408ba00c125a8a9385213d4c860072f0297857b1235045819b904e07f2425c13a661d0a01d2e53347f4b +a6581200f00f96c461621e1d26b14a23687dd97eb9f7df4ba641a84340ee7306dc1796248fba4804f185947ad13b4385 +8be174018fa40f7e0cedc5ae68f38969eb7695f2205e9c573641e533d56f68c20abf38a23d2f0dcac371e60b21b18615 +857ad4ee3218c647c58f09b8ab22bcc8976f00a768ab1f708618e868e6143474be846422ce2710a0ed39b5155b6f13a1 +a490bec40f322d599f26bcefcdddd8f2ef6576aa737d5ce7e8d5d422741abe749e3e6a48489aed8c560633f72857e3c2 +a9c0ee339621f1c4a2410f9b4d2f03f1b558dae2973807b8bccd920e8feb7f65dfde3e79986b72ad21fcc4567240381d +8592251568e750a430f7d2c6ddbb3ec82a4dd9fd83efe389e69aa177fd97ac2c96c59a6e86db20d8e6f125d65b46c4d3 +a4e2f4aa6a682913b423b097c4069c4e46a1f3af9556b1bfd0580d0fc01e3991488458049e0735b2a629684a79271c8f +8c4f6a3e738cf74112b08b1680be08158013ef8a515a81215d8a36c9b756786d1b4cb4563923463f3329292f4b48bf6d +8bace547353c02ea00dd547eeda7259aa354d4772dd5e0c486c723cf88627b7112e196b879c3c92a9561b674d9fc486d +8d372f4901e25e8db64fa098148d4a4e709b0e9dcb756d0f90dad99dea393054193ae1a33d292a3dd772ff7ba05e4b71 +a8c7ea6a6a031ed23d65639f01f5423190775558f479700597df7ae7e338a6ae5e9b32f470aff20787ac8b7eec84df6c +b6e9dcba240fdbbf66033410a79a2dd3e9e1ffdf2eae949b3a9ed720e939d92339991dc3e70a5ac7d5253f317daf0b7d +974dec4cd61af75721071752c664d9c2a5121f06ff1515c56139a177a3ca825f763b69d431d4607e393fa74dcc91cc58 +958863e6ad583a9d370a6db3639066982e44766904e7afa849b132f6666b7d08ab931131b3bec7a506d6583e93d56767 +8b93a33b5da9b3300c20a96d80b894e3789c77041183c2cb21751579c8c96857f60cfc2f075201b64e95a78985c5b321 +b726cb9f7ef34ddbc2fad82b3b0af0b30cc913e26c5a614ae5c19cc9c55c8e6dae069db5315a8dcb6d987415bb550ca8 +a730f515398a71bddd66cab2ff996659d4e47dfbb08ce7958a41021f76d269b91c7498b708cd14b183a8ef469c772803 +a4eb3b18132eb0f5337f14e01d63ca0bec0db6a43870f800e5491db756c2f5fce519d8dba5528b4bcef550d06b33699c +b1ab6621eec1ee6784e632e214693f39a14f3715991996b883d66200963e065c86fa0667f7bc36b93b40b5d90ff708c2 +80486a26c3532ad6e19f76d8c9344e2626c07363fd495264927cb5935fa9565ece670dc98767afb04af6a9a5c9231075 +8ee20e0df3c84a1c6b0e21bcc325cf99235b747ffe47f17fdfba548a358ca75cbcc331dd50db2311b400ae882256a608 +aef4268959e5541e7ec69c921a1e81a8374d7e44bf1bb2debf4101cf3cd6b7d6ca7f441758b388de96b3e0edb5b97be9 +8793629bd29d689ec94b016de8886cac6e2ca6638911babb22db4a787661422da0639a4e4089ebeb689d173abfe75950 +b487b3551c20a29e9a5abbda8c50ff594826283e443c09e3ae09b914e46060b3f9abf70434444ce1487e2a74e562616b +8f11531cfc5997dd04b997cb87ba1831aa7041d5434fe72de66304e3f165d882fac891391fbb1eb955c65319e65293b6 +b195136875fd02a75676c33cb3e60504d5964f7a9e81f4c8c8fd38af62e2145c55f765b3158664566191188ac678f381 +b374174b0b3eb04fa49eb4ece45173f0db5d829eac370a20a62309566e0f98b18f72f3633626893c053b7be6bfbd2366 +b2a2f6b0cf652775679b2d677048f2ed8c31a3269e6cddcc7a10e3e6fee89e486b50d9d55fbe452b79c4157c0270fb77 +892177c364dc59032594e7a6fd032286ffdf4fa0b9e3baeb37ec839faebfd2fd46c57b2c9bfe9977b59c93a9cc0ead1d +8ab7c0038a7dbb2ef200dbbe9acbc875829ecad4883792d5c6ce283de67ccd9aa935a9cc7b30b2bd9de7fca7bf2a9a05 +83745cfc78ca709835aa6c6a233c2b86fb31e3f9f6a8becf63e501f2841c4366fb7d131b746c9d3291afda714ff05579 +a723dcb67925ef007e8339dc578d2622d9bb77cfda87cca0088854a59414c02338752c56116a6c1281917842e8467c38 +8a098142da0af2254c425fdbbd0d1b1a17b2bd781391ab37f181775524b8563c64ab8a1602aee2ac6c0a82ba11a8b1d1 +b13bd7529a9b351c5d395c794c28bcb0a3167f1c992e8c062eef47be9be27895945231d249c73a0b6949daa295e14944 +a20dcd2fc2222eaae467d9f5db861040f58bcb991a26e5663ac3aa5e1ff13d0010657c5af586cc4621757add2b905073 +b818f660c3cc4e9f273c25ceeabe562c8afa8ff88529c26f2cf45ae6b2813cca5f350e3cbd56f6257c4df41722dabd25 +b225d5987108b24411bc389276f12509a45e86d5ad6b6d929af5274df0be11109c0fed329669a0acafdf3b0beaa8f2ec +91fcb6d04576d3c6bae947bb7843b430e5fb0592ae49b0a65dfa5791f4eaa4bf2c7f436c8de7360f217001c2b4e5c67a +8821f7a1424ca3fdc5d4a5606ad10dfaba6094cf36669fa9f84cf7617e50425405d14980780e1e18a1ecea7913cda896 +990dcb7f38f56521a70cb71bf4522649fcd46ac052c7feabb0748dfcac9f9c0f95d29e070d32af3cd0adbf869535e17b +b0fac1029fe2c1100f24e2f4bf10c7672199fce53513c7dde2e8d9b00702edf0143e0e1dc7ceae7dcc6994edc2422b6f +a514ebb1a33451b4915c05114db0b10168393613744df848b24e43e09f0bda23baefd9d731075198aace586615ac7911 +8b77f7953c2e67049fdca3653b8d8cf3f799677f79b954da02bdad8cc4d6c855c1c7c16b4f6f9ba35f46426ec28b2d84 +875520cfbda16ec5b1d1d00f578a910d0fc052f17870ba093e22e310bb07648d34817cc2b8811b6f52de535f7046a0d0 +b8c77b4be0b430851c4ff69e91cb770db1935d848198601393810ef395efab52deb9d5c6525472bab720273d5e0e7a79 +b6d4d437146671bdea62fb6545395ea3df39f1cdef21b8476b68e7a25aa7354f847740576d6c9f187bbae9941f0ae450 +95c642f1bccdb62cd6a2212dcdd6ff8d49aee426ca08b7cf3a9d15249d24a9eed5533f92a70c84498c0797f8a57efa27 +b617978047ed0f748c305aa7f30c2dacd0db00baa67fe0c5ce346ef0e6991dc7e05f18dcb2702467421f8390f27aa815 +86411c7a00b3e8b43bf22fb061b1f54ad9bbf632cd74395a478218389c0f544668acf3dd7726532d080ca7da9a5f8608 +97bf684a8849626c4710a6992f6c11f6b5406fd4dfe9e6aa502425aaafe9827e2c435aaf9a5d3d2ba3a4c0e8aec79ba4 +8b178e2a125b461d3180906ffba0af3dce614c64058501fdd35243ababf892d6fcdea4834ce42c25d5569452b782a709 +8ebed2c8a25c61da6a6a8cb0d8f5ea179e28869753eacc728f2c076f7aed8598cd3aa0981f120f9e7ea55b3a689ae882 +a6f235b8e655ca3d634740b53d8c0a757ecc75d2b8838b7948997c1985473d01943d935f687b86cee56cd47c8e773443 +a7959c465a9646908b9d8032a589e41a7dd999f2ffc54bb42f22e5f8a4d8c493a31bcc7ea2cac6c8dbcc59acace7181b +96d0532df2e12da20a57cadb6cf5f6c4ee1aa4775629358c25f1d51677a3e96d1fe3b232532324b4f02f941952d4cc68 +90f493473d686b639a30d1ddc9c72eae6e983f1236e162e58e967a477c0654973ea2e1bdf4ba1a44d7247bc1befc2cab +8b2d87876d9c4085102a07ebb41c565ba69acab99ffc03efc18f20e48d3f3bbe4fc6ddab9c78fe479d9ada80504d85ba +829a0fb3200a28e09cacd6c5346000e7786116ddfd898f37dfd17bef454a8abc0fe939ed8735c00769f7f2f33cd4f906 +86194ec9e88ddb7150e8b03e7a535b6e99863fc6762835601efd03615aa97aaeb413cb210e86035086ed852b39c9d019 +b02efd116a7189cb317ceae392bc301ae55470f0489fa89934e182aeb8c67e280299b975786fe9a470bff46827defb9b +87d7c3903bd22b12d815506f150373f518d47dfc6e5fd74347d88b518124c9923d1e4c98defeb3a45d53d50b423e2175 +a1a430406b28254a7d6348bc98e697e9bab43839aa05d53faee97546f84541ea0b559162619b2045182938f69bf61cae +99d243c226c61c6697fb3d2594f3533fa5dfd7cfc87107908cacde337d7a077fa5a9dc702d26081b065edb1227498e65 +800ee5006ab6217161f42db0cfc552a81728bb4fbd7af6e4620ea099a65ef6664184af3f65a07fcec7e965529c5b49bf +91bfd307579cadc8f81009558605be3edbcb8dbba271475803484017f40130b2b216aef4f620d960193be681877d3a53 +96a060459dec458d19a6f8af6e49dc6c7c58c55dd18915c5fce5e0f4b4a422fce3b9632f6059388fe760289abf70f173 +9921a37f3e657222c7fda3588418a9071409711d9f1fccede7494429f02a45fbc52d79fbb64e9ccd518f60d06d0520d3 +81052b0d15773cb75975ca9230ebb2579700e489c7e3f07cd9cde206fef38b8139bd4976d2b4a7840495fc645f96df03 +88ac37ba66d1de5e23878c992e4d54023729e97e77351f50dc5918d738b5a73faf1dc6feec7e85784761836ba1c6f778 +ae1e6072c13060775f6086d1ae1f88b627ffcb810fc0e0e97deea1f3a15ef0aaa52a6dce2563e4beedadc131af2a8281 +8b60a340f5e4f90badf83001b495ac9f13974c3d2054ddcb3e6b8ca99dec5cd63a263e05c282454191ab2e087d5a2911 +832e2d56ba69dbf817b2b9dbd25c1538d5b8dbf5d9bc05e6be85054a423ebb66a71b157e166e0b9444ac171b34b7ccc9 +8586036fc7dde1e7e3ecb61663130c4529866ae9f5f5095b9fccd24a4c70eea899aae5f10ea1ba66d1665b2d83be35b0 +a77969453b5c083a207913272b5b69d4ccbd8718bdf54be8fbe11b4bd0a2168aae3ba8f9362afa69c0ffa28d7e5a2340 +b7fe9568c214baad0ac5f83745611b481f744ec1c4fa78a549b180dcf79633e5ba75dc20055012a13d849eb7a9be57d3 +b01cad1d2a6c51c0ce88243d1f52f95fb5ee315a905079688027511f0c4ecd0563a3a81846709d272fa5ccb9665e8043 +8eae0a21adfc569aa57237654021c2bdb2c6f0f52ccc90a126682c21a1f9413c63d285f92b2b2f8649150a9284bf70b7 +942acc947192b5f3cf60e92383e5d35f79e7a5904e8e9fd1c8a351676c83ad29b0afb6578d555457cf909f8f4d27adfd +a74e092f8628fba9abcabc27e2e9f3d5a9a941dfe50a2dfde2ad179aabc73afd196676925c2d98643ab8b3d02bdb66ad +896159daa2afd757cf3f9d34af248ad68bb3c62e4c9ac49919422727479cf669098f270b9e645607a7d11adad4c889b2 +a428d8370813d78e7a2a24eebd36e9da2f8bb3605e5a39b5fcda939b531c35a8ebaaa642ba556250a37bddeec90326fb +a5fa04eb60a1d5ee9820e78f42f7be15e1c02757b539aead995768c6209684d6c183c71d282e0c12a4c15c03f9a89d4d +93c77d5d220e40affa7269a6915c076c9aef4db552c643ae5d560a79c955b491c6346ca4cf11cbb7fe1894e28d47b065 +802e605d2de745eef6981d88e7a57ef4046a2062725e8080995374cea2b3273c27f35b7774d0dcba014710d8d6c501f2 +82f7169e6ec9b3e2bd450f35ea2e66d06bcf900acf5b73139677b48e078ce2e16599103027b2326770c99c0a690f2015 +b0c8581879439f9b997551233fe2de71aa03604f9cec37a7b18c5854342d9b67be468f3cac4bf6f64fe8a0066248c498 +a3f626848a4db6e9fb01cac90d3362ec521e969ebd5228af694ea3671061476149f13d652942ac1e39f65591fed740f9 +88a8e759b9cbe16a7c16e43f4afa2de6100d2eafa4dee75ccd653ec38c919013d0a6b35c1ee1eaee7c1985b58bcc9e92 +a3d5fc7aaea072798490616552d947e95f49cf02a420314307aafb555287ec607d75589ba24b009cd68299dc6f7942fa +a809cceeb84f9bcf3c3ddafde3041e7bc3b1d14df8830ab849002176a0725e6f16f70774d8962cb0b8ac0dc43c4ac66f +b8f2e46c031cc8fa160a08c2ebdfa85345ed14771b06daa9636b0e7792b7fddbc501dfc85cc626a01104a43a7d3230c3 +b5367e2a521c318b802ce16ceac80c4b8139f73ddb10ddf38433397cda70a86ea1f051cc55626a4e99d27f30f3975ff5 +96d963660121c1441cd13141279cd371a6a0aa18b6a20761b18df60aa9c14e13489afd83695a0921d5232efe72045f07 +80818d492fd85d666bd91aaf6257b86527fdd796773c793407df1d4a0f91d74649a6bab4d15155c36ed4c6e0a32c5636 +931e22918905fd6c230d3d867ea42861f3074d320d14e1929031924c8ac209a5c552b679b24563bb12f9749b4ee983bd +a4de2c333e74ed9bfa3c0bf6a0beb90427abd9aa4221294cda74331646b58ef46ed57cccc8798ba2b9309894b17cfd69 +883881554c1d88c0ed8d3b6dec3d200f6fea69a77ace3e4d6f86b41506a23724b4394ec8384075f9c75c3868ba8a8e8e +aa0539ecf6ec9bf06f24443027f8f24b6b3d8c5b2084248eecd4bcad3c9a69716e1a0d01057f09a65bff1006ac5e157a +856d74d44c943c9e809b42dc493dff20eca03cb0cf5ed45108c69b1f90d8592a53ae8100e99380a274fafad23e74cdfc +9188257446661c88da093b7c5ce998135913f63842d7c1586065377b169ee35b062d925367fb9b909ca971f1188667b1 +8d3aa57cdafbe998938787479f5d590c1484c6dbe94e6c487e57a746ef5252be0eaa5976d6270de7db64b6b92e57a0f7 +b8f4d6997240f9eda5aca0c43323a828d1563c491b3db2087f60ac4120a3fcd06075fb42bb19d0339ab5ee3fb7db25d2 +ad247ea94b8ae1e81eae4c9fd7b39e6601b53cff47b2547ff90a3cca87192eae28408082774a1fd14bf9ab459b7a4f1f +9598598070f8bdbcc49056c40971e673726cd8c1bc4baa0b5124dfb5fb750e7baa7a7df18eae2bd91955ddcb1ec67955 +b874131ab1608667fa60ea29092d090859eed1812e90c609afff96d79e82c5ba546f617f4c96fc32c9bba97431c1e9af +b00750a9cdc75c2a54f0d3cc99b0fe02300754f25166f7ac85ff41ab5e9cfcca33a29be76a480f12a2d410c7cd5032e5 +84b5bd1c90bb6c66755b28ba4af493ca1b0c3a4df9f436aac67d2e07289053f925cf6a149a84e74e1027dc8758150179 +99caf64bd9d193ff306e8ab5da3f1bb2a190a60c3a82099b8d03d17fa810dc53d176c21379f479e828f60d25beb3ffd0 +a8fd9de502f1c261d5733430e5a18d8b7892a98c9529a016fc2ee53892ae965dcd9c75850bcda4c7edb980b8d88e60ea +848c02cac636e047028a3fe8c1bf4066fb7591b96b0340f8fbd476ff01b35fa3e37d309333771a134f24800e5f3f9289 +a1eab1a06dcca3439f0166441e7e7f2f5b56f5f8aa9f45e411c561f556e0fb71c514c06c26ac53b49a576caca5faac3d +aa603f970dcbe953e700e61c151182c8d32cbbb53ceef572ac93383db33a4b098b5c7b267e42d514ca66b740c0925efe +b55fd5301bd700ddb0b4f72fabe9a91ad49759506101fa802ed1677e9553595aa4d2c66f7574e78d21ce882ce0120ae7 +829137bc4da7b4886d3d04d2c39cbf4b1dc40c813ac1adb425c7b9abf9142b516314cab79c68454df5d71994ce416144 +b83a3a22735001f783dd48a01c4fb3598a51ff3987e842b8045c71c035b9e43645a55254ca5911a5676ef4a8af12d056 +8ca8d463deb13f9eef5e533bc39efaeb0c15631282c5c0deee1673b0053a7cccd514af09801dd6c158caa159fe9351ac +a9ffb1427828f3c456b9c8cc50782de1ab0029b9233a0fd998bad0fd014d27e15c4a32d1e16ad41bff748378b5abdf49 +9627e29f725ddd86456aff813976bbc4a836f4deabf5ad9f73d1a260ceb30948824df9c8841e6b3c529652202be181b3 +b52c988647fe3d9276eed3c262e1044f57fbb116c64cf4f207235c205b3fda0f3d789bf90f5217401b468d85fdfda404 +833bbd6e2924f5c4446cb76b881d1434a5badce9eb9b003f85d076e297ad7ef45b822069fe54d17427a348c3263fb838 +a067a36352db6f82a116cb87d3db5f60b18576852409e2076cbbfc7843af78866313a4969385a40271051dd195d51116 +902b99545971f9a103f99d7399acc347ac46fe156166e51deefc0e92aebf5893460c69aeeae11f5af9f49418e289ce6c +9206a0e9ce9b9880f29ef0417c96931985f5d83bb17cebdbba4ff2af81a3d37155b04649426f698aed372e4f669599e6 +b54a5d7c976e45c0b1d44433595eae9d1ae9aeabfd58cd5ecb0c5804756a7b01c9a517754423b4714a3695533a3114c8 +91b612131e84580ece228b81ace83da0269b53f94d3c02a1a0879ebbd81bdc252064b3d03a7e140b43a90f237d9a45a0 +a6cead3b8607eaeafe37135bd6de8fbd16f806c131eb71c8d36bfbe295d45b070255e50dabf076e2c3f6b8699be71d6a +931da21e67b11ba6ce438546a24d063bcd51aebe39b4220a78d9c0aab88b2d37969b5ef3502d835507f9c8d6d006714c +8fda408caa9daf01122a2308b7b9d328f52e1e2f138a8bec30492488f4d710e5e52524a6455a3a2ae2818ec8a610b650 +ad8ad5c189644352d90c462731c46145410e5adf38682bb80f95495dd64d9d13782537d68690847bbb06c6be7175dbc7 +87bb5cc466ade60feb0961421c3fabdc8a7e20f11df8437bfff63d3f8bd25305002a396c9d0fa4fb9a9986d4717f12c4 +827cff72870ba00c29064a7d2b4973f322d6b6de7924c93d8bf8825e7a0e8478c7748f90f5c716bf83c55b2795d315d8 +a225895a8e94229776ceb51b05356291f2dce748be17a60d5aeb33ef8507c368bafe5d1d6eea927f28b9d1422b661b9a +8e011323ce670ff51c964241a6b72e0e0ffbb3ff9bb2762492323fc3a4abf4718091be0945287c7329850e4f74462cde +a2c03c2e5f4e9d3ef361f68b188451994ad1b24de9f323370559c8abfcdc7bffd289d92e78a5f6b104b0a12c84dab2ef +a22b4771116ce22276fab1fec6826610707ce8a342f9f60b079c4e0259dac3cc41c96c560dfd0ada6edd2828f7c0e8d6 +97c17441d0af9be83b42097aa8b7cec84a253b9a2b957214b8fa93c26d2add46144faffa7b8a55312059b10690f711f1 +94bdf348849f31a2737cbae5e5848aee711067bac85c11c2e68b44c398cfafbf3493a3226cd1ddf7a916e7613fc7b6f6 +838f59c6e8469a8ec6fd40b978a3607439aaebe1e50ff707eec72c0b8278af05b477bf12a384b56d03e3d4eb91e56f67 +a1940f0db58185e2b3aedd2b0bc2b73b4a65c68e09b046f38e9dcd4e13c94f5406bea92635190bf315e48ec64eceef2f +b2f4e0ae44e1f1210a91d8f280f17091fa994034ba8c991583f8182a323e9b3001a712e3584fc2d64ecbf2d319d076b2 +9342b89c721338d02c7854cd7466fb24d93d7313b6114ea591e6607439c8ddb911d1cf35f01898e9c557982bdff8f9b6 +8583fcab15be1dd14d5a415f4b14d706c8c62f058500f1344b37730c8be6741779691f87ded3cbcf6516468b373cafb0 +8fa9587c7989646571ad9032f34cedd353caee14f5be5cde1e9e0a1710f90c08faf6fa96a60e1f150f761c9c8ae7417d +8d9ff904cc08141f5a9879f5f77dc600e6edbe859082231a4d819953890199bcc5f940b730ea688332f07e5279d49e1c +b5f82b46e5ef9a2df8d144202d6e2e4f3bdae8e2048d2af5ea7deb3f722fbe6d370401954e74ff0d8cb1010ffb1f38d5 +a3b5b57d435b06ed70530e060002a8fea71746ad07d969ca23f22b5e52624527595b6a6d54b4e953fb7b7596bac378f0 +b90f89390df6d4b7879b915aa3c29b8d779d035033f8873bb7ac54a14ec98f0d08c0e3bf696e2ffa7b5730d736f571f8 +8e81e371b92887e43d95c0dbdcc9575282b26ccebdc8cbf46587e4f2a83b61e9bc0c6d7d1f114b9d21e04fd6c180b12a +8d682947c51dffc6e0fe0a486293c9ed121f441805168236393087cf62f2a429cca60bf0e472564844347d32c6bea27e +a8341ec7dd189fa7168759240224192c58209b53fc961c18082deba217928c399bde08ceae42bffd37c1135b4d14a845 +a94bb076dcc5ee5ec82fac57c5b384c690df12631882bd1b960e1eb8c04f787bc22b7bac315b9dc5a8a098f17f051a0b +ab64e1c6f01b87706c88a3bd974454a438722768de7340b834ccf93ea9880c14ee7c2181432acf51f980d56de73832ee +b7b0058bb724d879e5ad7aed6230297c54cb599ef659e86bf2cc84c38225899fb388391df9b2e6fdf063171937fd8c72 +ae856f4fb74c27cc98b67429186e7df4feb01278cd57bfd3170af6e52e0a23b9e926bf9565a890cfb4ae8f2d590b2cd5 +804b9c6702f0596d328f92fc1ed5a30a7ba17b9204524135001b569233fc4937035031d079f52fd04968f37c24013898 +84274ed1af6bd6a968583995622b4d18c6a2bc703ce0d0edce45bb736529b4836343dcd11911a94a134dca7877e6cab8 +88808098463f7505034c3b6328c8a08186b33f7a981c08376e429dd64b79b97753170531ed078dd265ded4ec0a1ed8d5 +92823bfb23a4eb84d3759e7d717f0c8641ece0927cd2ba8c728c26bb35df2629a838002f353c8d3d75eb19520aab5f25 +8db36bae4d960cdb9c51f419d7ddc81f372e56be605bc96a9d4072b829f05527c37c8f255cc6115300a2a0d2e6568d89 +a8fcdbd7f3b4d7ff04149a209feb75e97149e7efceaa42d66a6b8e432590fe7bd01f1a77fa8b47108f670b612e33fee9 +a9f4c53c62db7e5dbdea6918862d3c6d24b5bd8732a218edf0ba61e9d1861182323d8ecd7bef8f895b42970b492f6e40 +8b95bc7f07818f4d7b409aff8da0b2c2ae136cde386f53a71565cae9fd14c73c13cc1cfd79c0f97cd77839fb738c5b9a +adbd1d11adc756b51a571ddbcbf4392415231ddad93da09acfafee03a9e4f9e1ce3826110619e5271feadfaffce3e793 +95d327c8bb195cdf25fd79c98f9406a6b0316214b1630ebcce95bdaeffafa36fc1accc6882e0e5d13a8db5c0f3c0e61c +8cb2f1e2fb25558869afdacc7bb866544cfdd566cefcd048b48d458a886130bd086ecb7600a960a7f2563c61cb326510 +b3aa8c4bf5b933d89cd74ca7f7176d6624d562d7d58b041328b49d7562a30b489cb606abb3c49e85baf04c28e9cd1f44 +97f9053a85250c420599827297453c2cfde087065b823d9e43139e6a9cac3a2ec40a1b6e2f0726bdc870fff215462f0b +878d5dbe6b881389c2ca126ff66d87127c9aaa3f62f0d2c1ec0ea2b279ac95f8a06710dce166415db227655e2345a04d +b2c33a6b4203e3ca5247f0890e475518317ffc44cfbb1da9a1ba02114e8b752bea618050b876de5cf3b1906140a64471 +a56170c8313d2b5541a795bea9934d4425b185b5c409f0484df6f44f0e4bcbf50b860ff46b7245cd99c1cfa8fc1965b7 +96e2b658e2876a14147385fc423d2702a3cb76962b6b437222cf9cea39ebf4bdc03bbf434b747866d4bf72b4ceefa639 +89c4a74fa2f067e7ae49c84ef782c331bcc9245db7e941804e2e99d12e987b4d25cb827778ad4c3566c4fc68018650b6 +a01d30cea7d01c80ff26650020fab02e78fc3842e2398a81b44b21d58d4e9816166ff4ed2418831fa995a28ff35cb6f1 +b960c80b55a8845bbf24bc3f23b0110ca701f9544ab6a5bb7929330213cb471321e55c390ceca3e24bff69bdb0d331c0 +802c5b13f22be7be0e5db11eb3be0f0ea7f9182c932265060ba05fba20ea093dd2810d3b969ee3e387e60fe6ee834e8d +92478f88ef7435d15e39a97916c736abb28ea318394b88678fddbbaab3eaf31776110936abad116a8ff6ca632dd12043 +a6d3da0370c303001d5ed99d1db8bce1f26b0e442f0f042e36db9674e92dcd6e80465e772f1e669f99221caee3392fe9 +938f04f70a8f947d6df2f0c0e9af3cce0c06edbb3c131970dd60884fc0b0a0959c504a2a36c3ff76dfe919905671626a +a7117e55224230822e9983df2132347eb7208cb6798f291df926ab51e04b1a1f78d5568c9a8924ee6f57426134360f20 +b91074c77ad93fe48dc2b10c0c5a62ca3ab7d98345b919c52d84a9dc419b59fc1b267e1c2d4b2e120016ef84bbdb0cbe +aa175c6b6edf02fe8778762c9575581c0ee6efc9dbf99c291a41444a23a056b893be6c45333d907d0bbe9fb0eef84d08 +ad36dcb4e2ab425aa339ae464b038d550cb11186741dcf257f1b8b80ed4f32ffabbece45e2dc1525d4c3eeed819ea04f +91cb35c1ffa9cd5aebef523edb8325078da3eb5cf9e95c675a76446fc7692aaee6f949de064ca2f3e0f082cc3fa93e20 +82622f9410c143a86bc4d756b3c7b324dc295231ce865de020d61cc0868f2c150a473cea3a5b756b36771ce1032415a5 +a5c29996ad3a53468ece9356a5b4ccb68971ea1c89cf39644f1da2d4a477c2ea99bf791ef902b87c225d8c53d67c4c92 +92893eceed1af34fa92b23dcbab175b6a0188a27dbac9ad3317c4e39955a763cb383ab13fb1c519cde311d8a4d12e8b3 +8a093cb191b94b0200e38d31955f9d240e2be1edcd6810a2396a061f17c3ddc9c4f4d56766ddff4e121be7110e03b869 +93981473df0cb1f4b47c7d9b64e3123dcf1593845b401e619f5d7c70b5dbea375d1ca43fca65845fcf0a6b2e0af43791 +a6beb6b0697070f9562910add88d9ba91992f8da127b27be81868b1596d1012f09ea7ed601b4a6474c921a1a1a6d866c +92026b1ee30f2ed61c9f30337c3356844217926aabdff383c19ca3c21e0bc49811ca5b308012bee4ef250cfae1615800 +ac0ebaea6d35f84dac4ce648af096305ba68a7a0aea0a11ab2fbe3162075444a158433c98141bc92ef3b3400d6deb46a +83046f482dee24ac3ca83373f0d1b82ac1c4beda0f229a9011a81ec659ff5fc1fb105e219975b5c744308c77a24f71e4 +aa5a312c47ff7248dcb9c6ffbe5a0628ccd565c07365c4413734d415cd4fb35772622ed833862dddff520a67c509c6a5 +a02fb88805c34018ac33582e19ed0a7e4616acc3dd0867e5f21914c2031c05c6dca30b8b35b57c2b137750f3878a6f8c +a60528f1f14bf0c496491d46a0fbbd6c343e4eb3f1631e92f96a3c5e5c684091aabe5801df7a67f7c6dfd1b0d35269d4 +a1fd8e7fad8ca05a340c05a051bb0eb4197eed345f4104629a9e38e234b09d789cc5537024615feb4a6177d32d39e39e +8e70e36c1aa070815440e19443f1f04aae23b1b59fdbcba43b47b94a026c82c8f66c5dfe54f826f4d95ee1930cdb8008 +8234c1969fa7e9079661e4ca309b71b1aaa10f4372be0b963205c23a81f5a3d52ec08ba9ff65b37f832b52d631580d61 +a18cb4134127fb37c4abca328cd0047378a2e1423490af2bd3eba9ffcc99ca81a3c22404c0886f21f65c7b93c41d7981 +b46fa45fe538816de776eec086e040005706cb3eca097e290abfb6864e745c879868aac8361894f3c3564373ef9ad55c +b96ca43b96c59e95439f75d1e726a35a9362f0dbd34963b156e103e080a8126a8dc3501f9fd541ff3bcf4677f5c4a86b +a8e8c87c7301613818d57387009e601a7ab5cbdc2890f63d985c30c74f9cea2d0584c116baf0d9cd5594386ee93fc661 +b47e4f1b9153ef0981f813948150f283b47a7346fd9921d51fe8e4daedaef78ddeb4fd467c2ccb7cebd9816243da1c6e +a370c202a99c8441ffe96fad0f801086d4d7cc7b960f6e98cca29ceedf492afddfd0f351c9c4d29ac008bc255ec1a2a8 +8f5e6ce1655d1c059b006174e3f5a55c88e1821c97f9702ad8e8455d46c2a83ae4482f2d43edda74a835686ec45a8a15 +a30421e694930a3b65d397b2720d5f8e1eec2b6e2bb5a28d3f9b0a84db9aabd83850268bae64c2b10e313cccf120151b +8abe87163046f7a9b18e2a3c0b66e258facc1b31431420e0b70354b7a60ebd250a784634a76692e7d6f4330b62114945 +894f033cf077d4eb312e3258d9dca414356271abce1d6094ecce6d018c5fadb1c15d8d69451574ad0701a2876db191c5 +b0923d64f88ffc872654e1a294bb1af8681689c21cf08f39afe51448a68e60a9a0a74ccce9969276a932a52c07d095a3 +b9ca23b5be8725fae7fa710eefd45522889c50c29c26384e00b78a962384f0aeff9d15cb5910e9565da12a577eb7e5ba +b242ccf292757197a9f470f2d80ccddc48c7f1235ba026bc68a93be2738bc968e8a200aff3e2f4807216442eb3fc50dc +adc2c3b375b308524b79a024ff87d122055440643fea6fc0a651bdb312c7cbe6a456afa9d342bc76446d77d8daf08bc2 +ab645955356c2ebf2f3df9da275e01daf0b44a52afc309277d6d9ad1b05484e5ae0d9d41ad485fe481e5e362826a86ae +8de96ac587a4449fcc8b7fd0a51b4b5185d9c2eb3434f94cbadd092de1e26b0f6b3f7b15a37e8424b1429121ddca0ecd +94c70ad4e9b871566f3da98170b665a09788d421818299857cde0853789fb943cbcf7d4b2c95246ea7b72edc56a8e36c +b2574be63497843340700b701d5cc8be6d23125bd62058802ee67cce1f3b5f5602b27c93fea5611f27dc695ac563f042 +869ec89da7850cedd88bcb3a50a15cece233119b31b64a61bf6b2310892ce42d8b473b584b11e61db29ed24ce8033f83 +8fbaa269da8e28e9adf4c1b08f109da786dbe9cba871c32eecbfb10619b7a5d65a26f9bb33e201a8ed20b3de94003fbb +8bf7a059c37242caf7f821a6314e4e4adf799e0dd86b37892a7172598892c07272acebd05b534755c57b51556b2d610f +b4e72645fca459898cdd9214892ed08b5c99f82049c0a30d72bac0b9717caa9c6cc16c3dc7aa6ea4d42dcd2a6c175df6 +a39170da87a3495da55bbb9701c5461f3403447174ed6a4af75712f7ba4ac35f51a4234bc4b94da888a0959ee109c0c7 +b45675b2774ea7696089dbf7a0afe6c22e85fd0e4ef3db508fbaf96c9d07f700c991789206da9309fd291be696357c5f +b52899e3e3f6341eefcbe1291db6664bf3b6e8021d32fb9c3e37b6258a35c1da927747b2ce990937d6f4c6c3e7d020d2 +84e5bdb3dfe19700d79dd3fabb0159ccfa084f7288db836c855b827613ce8071067c8d7ac5cc2b4e88ed7f84b690f6e1 +801477d200b6d12fc6e0a9bab1c8211193ab06e44551e037a9b4c36fc2d4f67760b9ff4eba9a3bc7b6e177e891f64ff6 +b6b71a5116d3c22af26a7530f535e9b7851f25a84e562a8f17a125d55b9b3fc1bd8cfe65bdcbeeb328409521e802051c +8687e21c34d7804c12489d30680d131ce2133e2981bfa993afd8a8eeda958ebd5e6881d342d725338659882d9f21cf98 +a024e97a7c4de32b6383c34431994abc533ecdbd6be9bff836ec1af022f5a86773bf345c6f33273797a61fb70a8fd5d6 +83f784f095da20ce5b31f54d6cb14b32a8a12675f0029289c9cd036b7c87a8077be2d04a62618685720e6ee69c875e97 +b4e9dfe7cb9d9efd3fe00d99ae5e48769d4af4bf43d4e05c0b54c9cfd8bc854de96b8d3ebf4dcc06b9dac66b7471a0de +a08b79f9d4673afcf7f38b57f484f88feb7c908f597663a2417f92c348150c2be6b5603f914eba0d9d5bdd4e5c5572c1 +b0eaf919589988798cb01ba0610cd1b7fa3c08715675ece8ecd5f9ef6d5d7b2c4c8ae1ea7dfd202237171aa3e6f9de74 +abff99a98baae4dd0954052503ce81827781694a5ea8c1149f96a3adde75dc2d630e138598cd2ae7fdc7a654aa17df8f +83e369b8680d8b9d995222b033b4f4f3e3b20e782113c941325c7fa9c742feef8747e4a212d9aa23285a259cc4faef8d +b16d5855dd2716613697eba36e2fae0872aaea6999e91cf6552f93f9a0b85ed4f6ff922a91b50816bd6cf8e7a4513fc9 +848373db600e32e741aa1d37726bbb28956783f89ce2d781e95fb1ee1adf4359968a141678af268077eae4c25503204e +93a0dd0fdac18a31875564505b4e28f9e8bb2915faae666538597731ac56cd77f23f2456461e2f672983fb24ad91f6e0 +ab1ebbe49fa56524b564bc2e43784147073e6ea5d27a9540fbf2e04d0f87c645ed2fd28b3e4982cc4c0af1734ee47a6f +b3ee30b733839edab6f61f0738e3f4afaeccf700d8dc7415684f193b36d70d07acd5780cf539f12e0fbf8d4683be773a +88388f2cbdec47a6b3ae460b69eb0d2130ac14de950c22fd86de03e40d02292bb93cebe62432da39d509c1289f785fef +9370c41a54b68ff486b4cc6329c3a851716ebf1d088d77a6c56dec93a18b8a77b596cde74cc17d2adb2b2f411a2e4bbb +b9083b60dc16531f77b05a955b51a237a8f8c0173d72c352c5ca441b55abbc890b14937e457aaec4be5cbbf80cae0099 +aafff8f6c6ebaad952c65054dfc7c829453ec735331bf8135e06406b7a9f740c9a200dc48bb2175516b41f77dc160121 +b43d31fbbaf10526809e9e5bd8bb47a76e0fabd7852ee7744404559ab89f0f215ff518f3271a6aa972a459cab82ac558 +b581ede48c6ef34e678f91dc4b89507413e00e70712e3e8c32a80eed770ec8d8b98caee9702d068aeaca6f704be57bd8 +8cb0a137e68b001a5ccac61de27cac9fb78d4af7b2f5a00b8d95d33ac19cc50c69e760c5e0330a85c0ded1edce0fe6f9 +b947fca07c7aa6c2bf13048275402b00b77b28f1d0ba4b589fbcede13f93b5b931c588560ab8ceba23bb8e748031b55d +81753cced5ff819901740a9a584334e355b497cb699f0be5a52cd555a4c9f149535c7bb355b54407f7f0ec27de6c2e19 +b3d59273951ce97838c4853ec329782a255b5fc7c848e7992ded1be28a5ada7fa3254123afe32607b9991ec6e0659b08 +86b253de246f82be1cb0cef01e87c3d022ca1829d2cc7e6a160a5afbd3ca6b94d75739b122e3bb16f8bde28a8f3223ba +b728b659fa2d8487e061a37f7d14a4c2d70cc37497a8715695d8d332cb274deee2ce23b9b5f6a7408516c02c3d526a49 +81277b46d98848a45abfbe39842495659dcbb80dee985a4fc91d77d52b815487aa8bb455f411fcce4c3879c7a075a93f +b05b6f1fb4a6e654f0ee6b83e08b58b57059bb0b7c490405bc8d963c4a2d6be39c558917977e554e1e9e3169961cbf3e +88f75fa7d016fb6442551ec071cc1e2beeb3ccd213d16d744f573a82f5d70f41dd1b18af71d5f9e73d87f2f6b7dbe889 +81a46434f1bbd65a661a0ff45a0295b8fd8a42a7969c5953721bc98698b64bddee3f806876d1e9983063fdd0c11f99df +8b4f6d33c510a4c9c7d623d9ae0c9aa631fcb987704726b2a4d8519372123bce3c439202f25b5b47045ec14ce39a21a8 +8d5112b330fb63cf6ef3d2164b404c14ff9907d685015701399a260951912b19b8f270f869df317e9050a127763d7980 +aadab394e84dfb82db15ecd2427f39b62352c3e1647c3bcd14fb24ae830ad0116f0fed87ddb63963b424a4741961386e +81ca4e5600d00a3bda24cbdea7a532a4cbbd893c10e7ff10667c15ffa8138b91667abe5466b31a3dcdd60155c48538c1 +ad943af1b8a5fcfcf309ed8f2f916339f254cd555c71a407a47365a139306286a05a8314e1c70e20a65fccd75d36fa12 +b16597a0b437060a390467bbfab94c0bdd695ae898894f4689f939e30cc2119cc08ecb594546304adf876f4e275ebcd9 +a44a4e0a6693be356065891c27eefa040a1a79475be53d54d5fdcea7e0668ff9b35f850974000ed119f6865aa6faa721 +adef27d1b6e6921f4eaf69c79e2e01f5174f7033eaafdd33edcfa5119af23f3a834ffe1bdf19576581b797abd1865b34 +90c1e9202f3ffe28f8e1f58e9650dc4ff4dbc158005b6f2296ec36147e524b4f2f87f8aafc39db5b006fe0c491c92f45 +ac817cd54288b6f7fe6338415344fc9e7b669414051631ab2f27851c052c044be06bf7235d668e194bef695923256368 +ab14944ef653a14456d4ebc12e3196df3f1b4707c4e50b317b5ccc8ca3a0720f0330609f0e7e71793f6ca01583f38c70 +ad5353f2f380837e5ffdf079350b3d42935a0517861d03af98db5ed3ea8501abd68885c8c65f5a66e944b1874826a450 +8b5583863f84af8443ce8970b02e26cc5d959e47efbf8a66a54106ab165f1f76b36423aee74c7b5402fd1c4d7c1adfe6 +b3b46037eed9fc30e4f8f0da8bdbdcc40a38e22e876ce9fde981883017854aba82c18eb00887d92ad847d30082fe7271 +98a2b6fc90b7ad172e4368c1e54675b75c8bf2096d91c9f2b60b3397d3be3b705aed5389845dbd68f0f84438cd0f7687 +b155e800852a5f90a2eac69cc4483428da1dc2c31588a13c924e60a7616ce9baeb7d4b829c772b260277cadd8ed84719 +b8b92c520a1302b0cf7d993a52e1dacd7f27bda9868d59c55687d995ae676b7070af4c0792a9bc1c2635d44a4fee01bb +96dfe9bde526b8fc829eda825f55168b88e8f4e43d4d708cc3060df03437b46e12a8ac70d7788aa75760f6294d3e84d8 +a3fa66c54e2fa084ced3bd838614c6c33042f492a5745d167a723c60d5e7d6020ffd1747981a23f8b68df21ad8f0fa77 +b573ca10cc41fc04a642f6f62c355a4fda69b94b8e95dbb02fd1ccce4bce1191356e1fd66d372159944eb36a7071f005 +acd0a1c9abddfd0ea223eda1722aaada362d34234455bd1c6be115d41e535b16f12ca428da7820a757fa4c98884a385d +96f242eee99c4db383b8754fa7987c0c159652e1866faec905a8d3f010e0a1ad05bd77b9ea8dfd653738959180f58430 +9215a9b672a5d6e435e0e0a45156e0e20f75cbbdf1d14940fed3ddb63d433bef643796c7a4fff881829ebb2b2eba9460 +b8ad9bfceaf08dc5a874387219ddd1170bc3a5e25ed72d321d59ae713be5ddf9fdfbd3aa7ab163be28dfa0dd14614e19 +a19a1050590bc500b32c502f393e407abc3d8e683d6f6b978873aff3e3299b18b1f6b59e2b0fe237d819dbdfcfdc98ca +a6870fb11d4429686e52e1f44c8dcfc7ea24a020df9570c021578dbc1f9bdc8cf797cb3a72d7fc52805dba35d59f2cd0 +a7be733b64d5c06c127bd1c87250e42bfe30ca91ed8ce51e0b6e377f454e8f6fef7f99bff650695df2fd10c375da349b +a1b97145dab30330eea2cdc8739b2446a3704b64505fcea3dd8a9b4a72edf222e98d967d6fd7f76794acfd97aa091065 +b2127049907d2a3b654d1c940b740bfba3dbaf660f86ea79c2f909af7c9fe2a07a1caeb1be12370aeffaf8faa50f1582 +8a207701214bb28e99b0784e9228b1c34afa701966267fe7110f6f29f5bb41eaae6cdb98844d0400787978fabd224de8 +9925147a383b6f5f814520220ffdbf20b214225882c3ef49b1a1ca677709176ec82466fb9c4be2dfbe5640afb63b014a +8416ad93871623fb555b5390b80de99edaaf317350cc0c1ae9d54d59517074d40061f315cce8ba2026d9c1e6f6a1009f +a315f943deebbf0a2cdbcf3f8323e215a406e9cbfbcc3f6288714cb3a6befb1bf71b2a21ff7a2ec4731c65044c45b6b5 +8213e0c2539c24efd186ffa8b6dd401ad2233bc19166a0623b26dd1e93614bbf792823f5599ac116231e2efde9885709 +8e5cafd2f34a127a4a896f05e4d929eef06972a1826b3566446942198df26d62f7679b987db2b3765d9d8058b1cd85c2 +b5302b399c9cdf912fd59007ad4737255552663b1e56dbe64a7b2ddd88d2093c73ea319b45db2dd49d1e03f5bef1a0ae +a0c2bcfbed4b008e1a56e5d2f2419aa59d7dd0ebd990f1c18588de702ad0fa79f445d69965fa9381e700eda13b309378 +80a44eea1ffe24c26b16b8e2e70ee519258b9ad4b3e83cc4e5cca88ebc48d0160066f8b91d0581095b0de2428390c8b3 +84a90cb9c7d2f799f1c4ed060387a4b793ab41c5c3eaffd3b60face9b9c3bae93cd2017283bf3de1e3dac63d0d84dd42 +81d22febca276a05ba9bbc5591ee087b0491beb35b4d9f8fc0d041d642a574667ddc57660b20f5c568f7d61fdcb41bda +a3ac965ac27a28e102a439b74fbfc157e75fd57620e4c0750a466165f8aeecb2191dcf8e656f7525aa50d9c7c69b0b5c +913c17434ff0d9fc52e2ece4fec71b37d4474a18f3ea26925c1be2b250434d49759f58033ba0fce1c6862c6197930dc4 +ac430559c151a5e461f67b49c7786c97e1653fa8698e9759ddbdd99f5daf17fc5a012ae6330739440880728f24eba7c9 +b10d8e9f8aed9361b042d1398ec74364f7c7c1cc5c7f917060572761138bdbe89bf409389ee3879f93bc8032dd67b308 +937271005a4cc6a6ec134870c1b56471aa84ed4f4af1b3d5f334bc0c42762fae0c9a6a2828d3de6151a76dad7b72781c +a10e4dcf51889f69e6bd4c052f8d4036b9571ced98a3d7d779cbcb9fa5c3a82228566ea7cc1d012bf56dea0a40c5a64c +a0ed026528d9a8bb3201bc9dcd20598933e8c72fd315deea8da63d06e97392aa729d98a55a8a60fa4d5573513ba5c9fe +b723fcd04cddbd4c36feae827a03746ffef251c4f4c55a88beedaeeee194430a99f566f483668a0d88b13e7a4a37f1de +84a2cdceed44828c7c05a6a762edec0165e434e7029df617d6646aba48776e6c3b823f40689cee136536f8c93e08a629 +b786264e3a237ac3a1d56c9f4e87438dfed620c867100fd38b01287f5b755c7820937403bfb86644e082094d3e410a00 +92cc35b2065fca157c7bba54410f8bd85907a01c9f760aa0ddb7a82cb55811d24cb4dc6b725367a6a1c293b809a48ead +a12bbf22b117f00164a42515bc57cc9e6c43cc77fb737ee3d0c0cad94cb50cd3847d61cab469cf8ca76f7958bdcfc771 +85985b00de533bde2a757eddf53be79ea39091d16af3fc92327bcd1cd59bf2bf4411a334da29ad775e8ffaf3cea7d7b8 +af9eb24185b0d330d0ea1d0b0fa78af0dcf42ced81cb0128f16cafdea687a9c5582bb6d7c5744117b271cd0b3303f0b5 +8c8aaa1d85ed6327f85d579767c7a9158d209171b3efcb3e8a9d9e534c078e821b6aade255101d2c9ef6d67ba66f10be +a450518a03ffb40e1df89e0f88fd55b5b06f4872cdfb7ec55f40dc40d9424b3b289866336c195bdd54597d95569e0096 +81e61cc69f93c435bd77f155e80626a9c764dd92b6c76af15c41346527948d8a6ca87d6351a0fe7987e2ee3aa66a9625 +b615e0cebf4fdff4cb23a20c8389c370915ba26aa703b28efe4ab070b1603d1c5b6541684acf46b52a915f6aee447539 +a7f51885c7a71885cc84ef734ecd107e8bf5f7a25131415f671d143cc1de92859e65001125323c7985799993af6c410d +abfbf7a46f32066989c32f774edcc68163f085ca81e94fe8c9fb32f8d451bbb2c20ac45cd8d97f9e618ab40186933b1a +8cf35a522b5cac1934004aa9dd236bc77198d43272888afa860cfc79b4b28dabf7a3c74098f84510897566fdd609aa45 +86aa927df78f7a06a4985eb0a4f0b93529cef14f9fd2812d46abffbf25e618ead14d99c70e3c3bb2e17f3f7fabc9c264 +860f1b4f4a398e9a8bb4739587cf96979cfbbe1687b7e91e5bd1198db726391b09b1a261bf12e96698818f60b5bd3537 +8e7c4ee19ff115881051e8637dce1f5d6c65e865d0c757e8ce41b6d7bcd86c7070cce60649692bbf28c868c7e2e1e2f4 +acf7ba01b0220419f09169ac8d16e5cc13dce08e88c90b8fdfaa33aab417f011a20b79a178d8a9f7211589d2e0affd7d +b404bde8e715aefbb9f20a353b911b79173ef3e2cf0aba98b5ae6190b90597d65043b0b4e014ad9ea6c77da2d213ea12 +97e3615d1c77a402253bb55da2d1cdf82de316cefffe42b1022c94b4818d6dc4a313731db85321c537914bdf716a875c +940e950b96a4096a578c6874d747515936652b9b113a5f27f5a834a610867b05f9881e2679b0b289b8527baa0009b6dd +8de15a13ca236a3a285ce6e6826c502ae7365bbe468b6e8ac67b15b0bb49be0e996f1eec81ef69e4b7f54f8e4779a054 +a12244777eacb08ecd42b5676b3a51153022ab97e9353ace0f47c6054c22de9ba60d2a60f59a36841c2a791cb1b7c288 +94f7580203e39a2642ee2e7c969b9911f011d7f3a90c398e1302d26edb3df03df1d0c43baa1c6cf90dde95296d49e742 +82ead33144aaecab965faf63af384565992f38fc1066e71e33d53f43ac93892e27fe78c4eaca1cccbc53364e26ff31e9 +a0c129e9706d354249a7f8aa664ccd7ede89aa1445c5547410814b56d10dc086720953363ab1da8ff5f1ed5d8e575104 +93b3057bf3f74edc95237781ae012cc4b1d3fd0455565ceaac7110290aa518ac32478ba4eb9851555fa87270fcc84f1f +949c2fd0b94f31f7cbf00c679bd3f6ec1a2f4056654708d39edf1a450b4e19a6e251d0bb24eb765087e698f61d3fca2c +99fd2e50e211ccb66b895eb2fc42f260f3ad5767f04c2fe238b81dae98aa6e3977443a51f4fe7b43f499caabe45699a5 +84fe19626503218f327b5325bfd7c0c3d2614b47d34964aa0259d564e769c6c81502132cc1765b0b31fbe39852706927 +b43287ec29d9010bec4284de58fed48dd1e129bac79f09d45153c9949131782f77b11b0c9f8ee06a39e5e9bbaa8e2c6d +908902f3ed45482df2f94415fc8e5a308057a40c8905d7cbbd58ec4848e19276577b7f7e69e5e684a8b981738e10f7ef +85cc7d9c1eae372b4f88758cd6e21604b4bc9f0794e1e74b6d9de96347f81944d01331385fae7a38e5f6096c1dc23465 +af60288c702082fc258b3dbd6952c6b75c1641a623905f491b1e72f49b9d39b33d150a336450abd3911a4c128166acdf +a7d8ac7e589558c4014369ab6f4c1f2196205b03e4278152ec0dbbd7ba54e803c3369a71d364a773aac8dbbd117e4a13 +9833aed34e48c206e9328073597aee1123f5bec085339b4e6839a389a429bf3042798a31fac1464ce963204adface76b +84631a4f012bbb62133030224b57deb32dcf464cacc8ffde7775adbe68707263ab5527a1c75e597e03aa703ba658b889 +a686a61f6467858a2a4c13e70ad81b1901290d3e51bbc0c6e366f9e652f575e91b11c75f640ccef8b0c6c1b05a43c9a0 +b585f0ffd5144907703b41539bfad7f9f058f5985f63db911064ba6b07af8da2796b84b16db42b8d11135c3f846cd9e2 +b525539516c7bb25f1d7e165f269dc8c9eedbba74df44887e178ab8fd798e2a31f39812ca922d6b64d91564f14012a64 +91e480d7568fd2fae39c35b0a8d623e66a3160fee1dd4e9097255004938b11ac1cd3918dc6a1e5fbcb700c95a547e5e8 +936ef55c69b842b6177de71fa48dc5442bf5132116b214302f8f242ca36a273a6bbfbfaf373777104dadbe8e7da5e970 +8e950c0f6688abdff8a3b8bd77be6da6f2565c7b55711f5860ea62a3ab1d51aac31821c602bc11a45e33c69e7dde3ea4 +90eed4595104a0527f8db1e028ff622ff70db4eae99cf47f6c2a0246ec7b103570a6a9a877e32e9647cc74969006743d +b756344f6c4ea05b792e416d9bd9ce9dd4bd904e7622761f28a85628506bfc9d88a25e5f04db62fad30a92fb1d8d8556 +ad79ba76534c1a02ac3e9b7308d390792984cd75b7e1d0e5e4ff123642d99d4ea1825643091aa8117336333c40d5bd94 +832b08144887de0c0341d84f6945450af8d7a4eb32367d7703118186c1be525df9382ce61fed5f3b65a0bb3449185f7f +a322fb944e46d8e47994820890c94af423674716da810ea1da71e0a7733ad72c22114ca39a4b59c98ce4291a5684c154 +b982851a65140dbea79bd3b5487e236feccee051deddcc17c2853032efca289ddb6eaf64be3dd85a73012fdbe9d2d4f3 +8eed5e230e201830b44b9fadca4e156fe1a16bf840cf29da0f381ea0587b20c226de2465c67e6268973e776809af68e1 +81c8f1c04490f36e41a53ee1b5185cb8adbb37c258fd6c3be8c56835bf574c37183a94d55b6554fca35d6e6dd9af0133 +8c4928724107cc16d36f2976677eac0b852fc4c3c0bb2f9cd4d59cd24a113faf33b2faf405c3fcce25be51d41e42c2c4 +8e4ba842636fdfc4d71f0983538ea5037d420acd26abd12efca48c252eea85544b2fa9fccdfec4e7c2a6359baffa112d +b4315b84700e26dec26f3488d308430fdff4809c10d4c24309627911cbb769ffaad0d1ecccd622dd02194eaf5ba59f91 +ab888308f757faef32648c1db01650dbc9aea248b09d06e6efcc996d395f48ec96f2d54a02de441d753fe8737862d991 +805094cfd77e207d5c75f3cad99f41f763ec15443052cfd758c6a82ba422d831a1103a7f9b100da49c28198279c3d3dc +ad857f33243e4a2cd2a773700def21fc7f94939d1a6d2c2125ecd58fc206ccafb07a2c02a1cfce19857d3654aca2c70c +a4d12d40149953daa70b89a329e918e9d93efb4e8004a9357fe76682dab9662c8507e16db83e849340f05cdb4933a373 +a0dbac2ed4b5d03606524245e8a31080eb5bd3e9a0c51dad88c3b18e3e6bc5d64953a81c8e60425b80107ee6b62b1fb4 +86da05355900f327164a78901f6e3db857531b33b1e855df1a67a9ba222c6b05fdb6b0ffbacaeb1ba5b45ff8979b6b68 +932c9873aa3e226dd922b5a616c75153bd0390ce8f332a414b9c8cb6606c2501a37a2aa88097bc7d8e2c4261706eb38c +accd9cdf07ccdd42033ce3b105e00bfd39e2304b1e3d66f8b1128645634452c20f759ec45adcef2fdf04408f62c4cc04 +b75cfdfc1cb48918752eab17eb579820ee6e71e6667abdb64df834ffc8c1362fbbc23ca2c80dee248fe1fbb72d87dfc8 +88b998c73b00638fde7d3dd650a08c5ab996dac6ac34251337fbff3fb5ae4a25dd20c1a16c987ad7ded19eca23cea891 +8afef0956c942571a27f504553fb312cca9e50ce41b44e0466d0516c5abe4d8acf4594cdb03b1ccdbe3f2e6a9093b713 +9042cd83c5ff261e9ebda26398caa16cac2cb840d19062fa8ae50e044c27104972948318f4c866dc4d578798272d3e49 +ad536719a64570a2cd1d72b6590ea1d02c8c49f259a7867be26c8191445165954bcfad50ea12688ace3fdfb0e98143bd +97c86328d63d297b6bc9718dc1ad5a05b908a750d1c455c700d84315589128ce4eea958aef2bcf0fcf4adbd8e3ce58d1 +8e592cf0802e6a9541eeb654dc55055e11f3d757847285197132935ca35bbb1a9156829a39384dfa6f645ff89eb36738 +ac16c614998944f77590bf3913a010e13f2d3bbf6a172293baf5983506c1a2d89989fb72e598f5bba1ea10a691377c93 +ab8e6f5b46baa6632de3621497bcbdd584decb999fe7d8a3364843a1e0b76497600630b6a24dd30119d8bcbfca29f335 +abe1d3af5279e60122d9cea8cc6581c819d7a0e20e3715da0f6da7e02d13a7653db643bd946e2fa9ba338eca81fbe140 +8c33bd831ecfb18d1d0713e16beba768e9c42df62170c1f8a16764912be77f2ac5915623d1d25e8c462aa9c2f6669ca4 +903692becae4a6409f7bdb127d9b11de57a5739fe24218dcbaa0092648d5332dfeef29a908ee9e43e5e0a51a4c3639bc +92591e90347ae286acd365eba32cd9ad8f20f4c9cad2dc579b195147ff290adf0d776bcb3d4b04a25d68a941fc0c781b +b64bbccf860299aec16e1f95c768a1f337c740bde612e6ba260e393edb8b04540127194761c42597abb9bcb771c576c3 +9194f056ccfdfeb78a11c5347e2255d7a7ebd1251f9aebc0b58feb68d3e03a7dbbb74e3ef7309455853adfb4694bd01a +aa4f15f6d6a53ae65b7f6f91e8981d07a5919d2138679a561f7bb608dc4596e45ca06c9441d51fb678b2ad89ae7a17ae +90e3d18507beb30bde08c5001faf489a19ab545c177efb3f73fbf5605f9a0abcdc8bfbc44f832d6028e3e0a834bea98f +8f31dc0118c8c88a6e79e502d10e57652b7aba8409a5bf572ca63fed6b7cbad7f28bbc92ac2264f649792fc1d0715085 +a307d1067ea4c56437b6f8913aa8fcbf4a24580fc1e3336e7f6518f0f3adb9c4733090e459a3f737414ec0048179c30a +b7cc41fdf89595cd81a821669be712cd75f3a6c7a18f95da7d7a73de4f51bb0b44771c1f7cd3cd949e6f711313308716 +a9dc74e197fe60e8c0db06b18f8fe536381946edecdf31e9bd90e1ebfcad7f361544884e2fe83c23b5632912ec284faf +8b3e1e81326d611567e26ed29108f33ddb838c45bbd1355b3ae7e5d463612af64b63fff9fa8e6f2c14c8806021a5a080 +92f6537bca12778866335acc1eb4c3dfc2c8e7e5cf03399743dcea46aa66cac92ac2963b0892784263ad0ebe26ffdbf6 +b5cc0061f7a3e41513199c7dd91ac60d727366482a4c7328527f7bd4fc3509412f711bb722b4413b3736a219b843d15d +b3e9711d68d2c6f6e2cc27e385d5f603d9a1c9a96edeefa1ffdf390439954d19504d6aadc566b47e229ad4940ef020d2 +a09d0d3f0e5dc73a4a0827b72710b514bbfce4a7fcd5141d498a5aad6c38071077f50d3f91af897d9ab677b7041dedda +b177fe260f3b86e9ac21f1bfbe2682ae5dd8c9aecebb84f37054bdab6e39094e611ce582210ceeddde66adf759dadb6d +b0ac6595eba9f5dc4b2fd21856267cfbcfb5b12aa34ec69ca32b80071c5b652e85c25a224d80443d503bf25fbbfe07e9 +81f3c0e11b196bd4a2e8f07f8c037002566dc9037da81f3988add458a520c24dd1be3d43d851e28c0c6a85de4b57a542 +a44308c95615f7fedb2d2127012924468c015df9f48359cc2e36ab4223870b0bfc1e9040baabefdf5266f93afaad896b +8493ec4c32d5a13b81039f1b436eb83f259945dc950e3c6c2ccf5087ec56dd2f60890ed4edf01728b6a54950e19b35c6 +a1a439ec2a6a95bdac9aaa925ff337ba956c0d236ab5318354270e73ed6b73b4ae2d27b4c1686cf97b6526d04e65be81 +b4659b7b53c55a4b2bbe210b53520b392f893500e18990d843b72d7379d45fb44dd1dd2184348d6fd853d6b9ecc6b7c6 +afb2c68d75d00130b0e1b4f250001920213121791698ec04262db714cf7b1408d39f6cc10421f954845aad5b8250b77e +b22b843b40a97210f94043b552f348f66743055a3f274856a738e7d90a625b80e9bbb80cbbb450e1666eb56b8bd5c60f +800895ced82fe13d5fff65a93b0051c3df698bf1221b682accfdb63e3970f669ca37025750697f4e8ff2a3322ad57be4 +b21f598c50d7b9f4a584d548f85e42055ef8e24991906d973749090261584c7f4f5e984b528926f7e75375dd84d51af8 +849b1c68192d18274598dd6d0bf48fb5ee3b1ba25b331cff2d06f345bef3bed49760ca5690848cf33388f6a9a32cd646 +aeb6fd9478b10ef456f6bbb1e6dd19b14475e65497772d12cfc097948383d3fbd191bf95f046b8bf1989954118e483d0 +b1b5e0ea2835f7fc8b66e7731e392b43d16cbce04b52906b6751ab1b91978899db5fecbdabc23a19dabb253005468136 +91b6b1284770cf6f7ef35bc0b872b76c7763ffcfa68f9c8cfabcb2f264a66d47598bb9293f6a40f4c3dd33c265f45176 +b9ffed029846487c2cfb8a4bb61782bd8a878f3afdb73c377a0ebe63139fa070e3fcdc583eec3a53fdc5a421ff1fa877 +998007249d041b0b40ff546131cfc86d0b3598dcedf9a8778a223f7ed68ba4833b97324cbb1de91292b8ff51beab44b3 +8eb77ce9e0e406bf6f002870fb2fd1447646dd240df9bd485f8e0869298a1fc799d8a41b130c04370e9a9cc5c7540ca5 +853db8157462c46f2af7e8f94f2ed1c9b9a7ba2896b4973296898ff3d523d6e29e0b63a5d26cecd5e490b33c87a4cecf +b1436b6f3278768f0979ee852944258f2599977d255bea6fc912ba17c5dff5bdc850cf3e1fc52be9d6d188e868670f4f +a76acbc5832019b3b35667ab027feff49f01199a80016620f5c463dfcbfb51bf276ed17b7b683158ba450660cc7973eb +94540cdb051faf3ae8b8c52662868c2dab66bd02505c4f5f8eb4d6b2e2e5fd9a610890c5dcf8fd887eee796d2b5753a8 +aa35099666bceccf4eb3b65b13bba88e30a8be93693ab6761d8e5523343e8d6dd42d977e66499352fe4e9e9784a1dd0d +894471aad17be54319083c4b5e40adcfacf7c36c4aab0b671030b7ef321c53590a25eccd836efd20f32a93185fd315bb +8f52a9f705bb0dea958fcfbd52e2b6c08ad0f89a07a6b2942c1b4c37eead0d97a38a9e9aeb08d5d59b7fa2a9347f738b +9031c16b4f936c9cab55585dc5064739f696c3347ee2c0792320c9f749e760d120e396e8485ffc79d81c9f3337ad3d1c +82090a0d0d9b05459ec1c328ecd4707c333b784e3aaa0ef0072cee1eac83f9a653a75d83b9f63512a8c41200494826b4 +92c3a9553001f9ea4d67236b8ad1a33275378202cc1babc03f313895458f4b2549bfbbbdd37bfb8fbff0decb6b9f820a +88651868f4da37338a22bc553388df5dd1dd0cb78c4d7d07c637d8f6faef4bed72476fdcd4304d5bedf3514011135f08 +83fa0141bfebd88063f1d787719721b4c6b19ecf565b866de9d7d5d1a890e0e3d859b364bb65f8f8e688654456a40263 +90a7fab753e5d56dfc0e53a6b4e6ab14508220f3a62b3f3f30570c4c9ad225e74122635826c92e8e3227ec45e551432a +8fa375b0345bf6e5e062d108f9feaec91029345ecac67ccf1264eac77b8654cbfdda1f10579f481889c0e210254eadde +b83f06116da9daebdb013b26724523f077debaf6bc618b48a7a68858a98d275f7899c4ec73a0a827219b9248dd81c8c9 +8be1cada55e0c5ebb4fd460b2d209ae5326285a20c8bdd54ed9d1a87302f4063c8730bfda52d9d40e0d6fe43a0628465 +a68ad6f813743ec13a811f2ef3982c82d9d9ac1f7733936aa1e122f8dc7f4a305cc221579ab8fc170c3f123a1576f9ab +8878f1128214fdbbb8a0edd85223741e021508ab6d36c50d38680f2951ee713ea056ed03f62b9461897963d50ceefe0b +acc0d43d1b0260528b7425b260a5dea445b232b37240759fc65fe26f7c9d8e51569c5722bc33e94de6492f4ba1783504 +ad80b1dd717b076910ee5ceabcb762e75e4d094dc83b93b65c16de1f75bc712cef223c05d5579c1561829406c07a97d9 +a6fc9803f9c09d95fc326cc284f42ea5566255eb215dba8a9afb0be155ea11bcc55938b2d16f01cd2f2eda218c715efb +83ad733dbdfbaae8095a403dbf09130513f4ed4f08dcf8dd76ce83d1ea72999b7eea3a7b731da0d2bc80a83c6ee0e3e0 +8748912fbd08cb34a85416b0937d9c4327e9eed20d6e30aeb024a7253f14f1e0d774f3326e54738d71aae080e28da0fe +8997e78d8acf23051428af67183ae9b2c4aa42b503745ffe33df35a35103c589987e1473ab14dcd28ee78ebcb10d8e95 +a2f340502a7eb3c4a36412e6f028321372c4fa18a4743945607424e932af1271fa3e6598a162c872072529576eba6283 +868ccf19b5044ab93b45c9ed3ae34fcb504fe1453d6c4a1d12c325032cf01eb90356de82080ed897e97dba13cae33a02 +ac8867005fe4354d67aa37b866a7e581d2f94f7bd0b9f4efb5c2d1370ec13147a60692051b02fd00ae60b512bce9b1ff +8fd01886b046819c83c12bb779e432b25ba13713f9227be702074ec3abb2bba6be37220a0a26a4bd4171b99b14e32bc4 +a128981ed199f92b5959975c150a93a62fec50b61c80a3fa0634d90fc8058f76f5cbee77aae6889af12d296b30e613cd +81fe618552ff7a36c9235c6d4066cf2f930b5b38de4089e18166e4a06ca5723eadd1976d25e34b74b3ce942300b23e5b +ab1223ea049e6e0fbf9b611de7fd7c15e5e9637cbd73aa0e36aea08a7503ba6804f2aa807186fdc9aa7f4f9195f72e24 +b97285286981b2665f898abc13f3243b63005bef8db4cab3f658bf6167036b61af400f08db0fc3c640a9c623b760690d +ae3ddff7c1f0fbb6a13dbbc667a61e863c2c7c51c2051e33cd61620142e7e30a7e0c4c1f8fbb512aa3a8640267c6ac26 +99c2a89d5bef236060e51c4f952664094c20fbfca647e5d24a55c1fb8df2f3df58244fbbf3635db07b1c29ee3234fa6f +a5010764d4b9cd3b410638334d1f70c5f4843f45b4f4a9316aaea5fbb2c510a97449dd7a07b49f47334a69d37d9955d3 +86706d011dcdc9e9d165d01fea1df68dd74bedaf15a39f92893c030cafe96f4498c4c1fec2d2136354341b3f440a1462 +88fd57eb62bd7dc35722f3a0576c2138403a2f663a2603482e8974a895cf56ddbb02657dc6b89eb2cf5c1f9d1aff6426 +b0dfd4c68e3acb6bb8a776adaa421fc5e268ed4d5964bb90a727091e5113b55b3f9c6d33cedb3ee47ff7acc5df8b1749 +93b92bc942e1a636fc5c2dc1840de5faf158a113d640d5a475b48e2c56ccccaf9db0e37e90ce74c4b3f5c9ac3b2eb523 +b29a16fa1ea95cbfc1873c435ad40dc8495ba6341801b72bd95d908147dcffb1b4bb426dd635f3af4c88984f56594dd8 +b8f367105e1a2d554ac30200c66aeb579d3d30a8953d20fb6ebba2d876ec39c52ea5d654f1bb89b8ddf3d9d651f31cdf +b5fbc228c983d08adf8612eba5b3db3acff604439226f86aa133b02cce4ffde2f977c8dbb8b446b4375673f71634c89d +a399bea37d3056e0559f6644faa0af93063b4b545d504d7e228d3dbbc294af83d3c4cf37fe026b63899b4e7d50fd08f5 +928ef411a36414b24aea26fdbed4bdb1bb6bdc2d967e2553ce54c7c4e077e76869cea590257645c9129dd55ce025295c +9684a4adeed416a9ce82ad79b55c4a3adcfbd43950bc442ed8a340381caedb70f4baaaf821e3a152f483f965d8f56162 +92558a37f214d6f4cb6d72cd2f4ad24dff9d17611b9e4a41ee5c741a5d1ca9e4053b0584533ef4da206110b5dc3e2a35 +973bf0724d1785cc5e85d2a8ee8c354ad4cf557217ced0b7940f6f064024c20b2bfc5b144c820b5083da4bf70690de4d +adaf1389dfa528210ca9c2657c5ff10d51f7e3b18e93a59c37211be0506c3576cb2c04ec80cd0f82605e53c5a3556620 +85b58b223b09fda6f3ab674d75e780c49eb2167837243df049281e8f4fed653811138b398db9cdfe7405fdb8485602fe +849504d3db408d80745a07e850b0a804607b91a59922a5d3bc40da2748c029c029419cda38d2a4485cc0824c6b2504f0 +a3f4afcb353bc2582a02be758ebf0cd18752410ca2e64231176bfa23828423e0a450a65f241a9ed8eab36cae8d9c567b +ae362786cdf121206537af9590d330abbc6dc328b53cdd145dbed0e5df1364c816aae757c4c81f9d619e3698dd32bcdf +9024cfa5b0101eb02ab97866d5a3832944e5aa6888484cfba3d856576b920787b364fba5956bd7c68a305afedc958201 +8a116df09fed923acefb2aecf38a4fbc4b973ee964d67f03791d70bee6356af43ffca117d4e9463ffaf0e0d5d5e5a69f +9163016175c73f1bbc912ddfe03bd4e1db19c64951c8909ee6befe71a1249d838e0db49f03670bb4c5c9b2ab0fb4fef3 +8f6357318d8d16e7240a02b05ce5a4976b6079d49daa258789c6dbf4a47950ebe9de6411780fab06c7c1f35651433380 +8e63cbae8be7341892dbedee3111adf0307c4ee9e375181aa53478f5ba9cdce164d6ae890e5f480119a3a51c6e989165 +a9782f30674a4874d91bfba7eda63aeb5dbe66b040c768d6a925d8ee135f0655ea56276b105239cc0668fc91ddb68cd1 +8d9d94b61ab84ec08665cbe0244ea41756785df019e453ef078c19380bd44c39d2958e8465c72eacf41eed5696037805 +b1470e6f5d2e314474937cb5a3bc30c8bf5fc3f79014945f6ee895fe20028ffc272f9d3a7320aac93e36c96d8a5454e3 +a444911bbafc71179766594f3606b6eaff041826607fd3192f62dec05cd0f01b78598609a530f6930e8440db66f76713 +a9823d44e2638fca7bcc8796cc91c3eb17f46ad6db9f7f6510e093727614aa3a4f9b2c4011ef91dc1c2d224d08d8d05b +ab86020972c359ab98294212558b4b14862040139876c67fc494184b5c9bcea1dbe32fe0c8dd9e60be9daa304acd599a +b7e5cb685bbdcfdb1e48259a5d68d047846c8a35c5b3f90172fb183d1df40d22eaf0edaca2761a07c29c577000ccfed0 +8c88319dae4b28989817e79e6667fd891181e8d2ed91b9c6b614985bca14b12982462ec58b17be0463c24bbb79dd62a1 +8c1c6867e7107fb2178157c991b9c8b0f90c8d57a51220bf3650438ccabccf62da4db8a9916491e730ff3d0c106496e3 +a00a79bd58da6528b9af033087260f9f3d00519eafb4746b355204ee994e89481591b508eaa5402821083e250d38467b +8785abd7c37690f6aa870ee5c799eef72e398a7898b6767f698515be277b9c2fc1af12ea89b0620a848221343a3b5ec3 +8aadae68543db65cef71d0e230a09508d72061398ef2fabec0f856aacff2125b79c70e620744aaf331faf3dfc8afb9bc +8ff0cd437fcad9630b8a2333176a55e178db4142ec841581590594d74d5b53baeac5fb903fdf7bcf83e245b95b58285e +af274e8fad6b190be4e5dc92d2705ba6ac0d7e1ea29e958a5cdd4cb764de46a56d9eef62c999a16e7c50a50b2d9fe3a8 +865e6ec7d1aa848786d6a7a4e87a24d442311f0810b01ef5a74928ab59fdfd651e48880b49680047e5b0df6b3c7c2ecc +800706baaeb35bf3bc33bdea9a8b5cb00d82df407b3b7e1b781a9359cf44fb410ed311591080181b768aae223d9246aa +a9496389d0780b309c6998374ae159f58a8d0fe9a1c24c36cebcb45b27d818e653b51a8ee1f01e30a9b2c46a548126ef +b5fccf4fc3186661939fbee2e89c2aa0e3a6ad4907bcc98c7750520540c4c183b1bbfcdf47f2f1c5e75c3a30cdf30c75 +a90028e39081b736e628c2230cc1338f9210ed01309a40fdf08d39c10cced2cdf71271013bea6dba3a0444fe47963106 +a0815cbb325a8fecf2e1bcc5046644be32d43a8001bd5d8cf0022e4572cd0d481b3e717002f7ab21e16da5f5d16886d6 +b2024787fcda52abc4138150f15e81f4a5be442929b1651ddccbfd558029912be4d61c3c9b467605fff640edf7392494 +ab5aa60032304a584cc9245a33f528eae7157808dedd1ad83ebae00aadc25dbe1cd5917eb8b6b2c800df15e67bdd4c4d +866643847ef512c5119f2f6e4e3b8d3f4abb885f530bb16fcef0edb698a5b0768905e51536283925b6795a5e68b60ddc +806aa99c9a46ee11cc3ebf0db2344b7515db8c45b09a46a85f8b2082940a6f7263f3c9b12214116c88310e706f8e973a +a6eada8b9ff3cd010f3174f3d894eb8bb19efdbff4c6d88976514a5b9968b0f1827d8ac4fe510fb0ba92b64583734a1e +98480db817c3abbc8b7baedf9bf5674ec4afcfd0cd0fd670363510a426dad1bcf1b1cb3bf0f1860e54530deb99460291 +81ab480187af4a3dfbc87be29eca39b342a7e8e1d1df3fc61985e0e43d8d116b8eac2f1021bde4ae4e5e3606c1b67a21 +8a37df12dc997bf9b800f8fd581a614a1d5e32b843f067d63d1ca7fde2e229d24413d3a8308ec1e8389bf88154adb517 +b045a55ca0bb505bd5e8fcc4cfdd5e9af1a7d5fe7a797c7ede3f0b09712b37f493d3fcf6ef0e759d7e0157db1f583c95 +ad502e53a50691238323642e1d8b519b3c2c2f0fd6a0dd29de231f453be730cf1adc672887d97df42af0a300f7631087 +80597648f10c6d8fcd7421caf4e7f126179633078a1724817d2adc41b783723f302eabc947a7ba7767166dacf4ce8fa1 +aefb56427966c81081999dffbe89f8a0c402041929cd4e83d6612866cfbb97744f4ab802578349fbecc641fa9955e81b +a340e493fb3fb604eab864d4b18a6e40ba657003f1f88787e88e48b995da3d0ab4926ce438bdc8d100a41912a47dace0 +a6d777bfc0895eac541a092e14499ff8bf7156689d916a678b50a1460583b38e68158984bea113a0a8e970d8a6799a85 +90ce469410f0e8cfff40472817eb445770833cdcf2895a69bc32bcf959854d41712599ceb2b0422008d7300b05e62e02 +815c51be91d8516d5adc2fd61b6600957ed07cf5fdc809aa652b059bea8ed179638a19077a3f040334032f0e7900ac8b +b3ec6c0c3c007c49c6b7f7fc2ffd3d3a41cdff5ad3ac40831f53bfc0c799ffeed5f440a27acc5f64432e847cc17dd82e +823637abeab5fb19e4810b045254558d98828126e9a2d5895a34b9e4b4f49ab0a5b3ee2422f1f378995ea05df5516057 +ac05412bcf46c254f6548d8107a63928bba19ab6889de5d331eb68cf4d8ce206055b83af4cb7c6c23b50188391e93f84 +88514163c587068178302bc56e9a8b3ad2fa62afd405db92f2478bb730101358c99c0fe40020eeed818c4e251007de9c +b1e657d0f7772795b3f5a84317b889e8ded7a08ea5beb2ab437bebf56bcb508ae7215742819ed1e4ae3969995fe3b35d +a727d4f03027fe858656ca5c51240a65924915bd8bd7ffa3cfc8314a03594738234df717e78bb55a7add61a0a4501836 +b601682830fc4d48ece2bdc9f1a1d5b9a2879c40c46135f00c2c3ae1187c821412f0f0cfbc83d4e144ddd7b702ca8e78 +b5cfea436aa1f29c4446979272a8637cb277f282825674ddb3acac2c280662fb119e6b2bdd52c4b8dbf2c39b1d2070d6 +85c211645ff746669f60aa314093703b9045966604c6aa75aae28422621b256c0c2be835b87e87a00d3f144e8ab7b5f0 +867628d25bab4cb85d448fd50fdd117be1decdd57292e194a8baa0655978fae551912851660a1d5b9de7a2afbb88ef5c +a4e79c55d1b13c959ff93ddcf1747722c6312a7941a3b49f79006b3165334bab369e5469f1bddebadb12bfaff53806d5 +ac61f0973e84546487c5da7991209526c380e3731925b93228d93a93bce1283a3e0807152354f5fe7f3ea44fc447f8fe +a1aa676735a73a671a4e10de2078fd2725660052aa344ca2eb4d56ee0fd04552fe9873ee14a85b09c55708443182183a +8e2f13269f0a264ef2b772d24425bef5b9aa7ea5bbfbefbcc5fd2a5efd4927641c3d2374d0548439a9f6302d7e4ba149 +b0aacdaf27548d4f9de6e1ec3ad80e196761e3fb07c440909524a83880d78c93465aea13040e99de0e60340e5a5503cd +a41b25ae64f66de4726013538411d0ac10fdb974420352f2adb6ce2dcad7b762fd7982c8062a9bac85cdfcc4b577fd18 +b32d87d5d551f93a16ec983fd4ef9c0efcdae4f5e242ce558e77bcde8e472a0df666875af0aeec1a7c10daebebab76ea +b8515795775856e25899e487bf4e5c2b49e04b7fbe40cb3b5c25378bcccde11971da280e8b7ba44d72b8436e2066e20f +91769a608c9a32f39ca9d14d5451e10071de2fd6b0baec9a541c8fad22da75ed4946e7f8b081f79cc2a67bd2452066a9 +87b1e6dbca2b9dbc8ce67fd2f54ffe96dfcce9609210a674a4cb47dd71a8d95a5a24191d87ba4effa4a84d7db51f9ba0 +a95accf3dbcbf3798bab280cabe46e3e3688c5db29944dbe8f9bd8559d70352b0cfac023852adc67c73ce203cbb00a81 +a835f8ce7a8aa772c3d7cfe35971c33fc36aa3333b8fae5225787533a1e4839a36c84c0949410bb6aace6d4085588b1e +8ef7faa2cf93889e7a291713ab39b3a20875576a34a8072a133fed01046f8093ace6b858463e1e8a7f923d57e4e1bc38 +969ecd85643a16d937f148e15fb56c9550aefd68a638425de5058333e8c0f94b1df338eaab1bd683190bfde68460622b +8982f4c76b782b9b47a9c5aeb135278e5c991b1558e47b79328c4fae4b30b2b20c01204ff1afb62b7797879d9dee48e2 +b5098b7ba813178ced68f873c8c223e23a3283d9f1a061c95b68f37310bca4b2934a3a725fff1de1341c79bb3ba6007e +97b160787009f7b9649ed63db9387d48a669e17b2aba8656792eb4f5685bb8e6386f275476b4dfbb1b4cb0c2a69bc752 +88b69369c71daad6b84fa51a0f64a6962d8c77e555b13c035ad6fa1038e7190af455b1bd61ae328b65d6a14cf3d5f0d5 +af88b87801361f0de26bd2533554ee6f4d8067e3122b54161c313c52cc9eafea00661c5c43e2d533485d1f26da4e5510 +98ab18e3bbcb23ac1e34439849e56009bb765ab2f2558ebfd0a57cbe742169f114bceb930533fb911b22cb5a8fe172bc +9027507f1725d81e5ac0f0854c89ab627df3020fe928cb8745f887bf3310086c58fca1119fd5cd18a7d3561c042d58de +a676583f8a26e6f8991a0791916ce785b596ce372812f5eb7b4243ba9367ea95c797170fdac5b0c5e6b7f6519cc2b026 +b91b0ab32638aef3365035a41c6068e36d2303bfee8640565e16c9a56c21703270fd45946ce663238a72c053eb3f2230 +aaf4cd1ac0a30906dcd2b66b37848c6cc443da511e0b0367fd792887fdaf1500551590440e61d837dbee9d24c9801108 +a06f20a02d3cd76029baad5a12592f181738378a83a95e90470fa7cc82a5ae9d2ed824a20eeb1e96e6edc0619f298688 +a465d379c3481b294efc3f2f940b651c45579607cf72d143b99705eae42103a0279eb3595966453130e18935265e35d6 +892a8af7816a806295278027a956663ea1297118ede0f2a7e670483b81fb14dccacc7a652e12f160e531d806ca5f2861 +b480917c0e8b6e00de11b4416a20af6c48a343450a32ee43224559d30e1fecdece52cc699493e1754c0571b84f6c02c2 +b3182da84c81e5a52e22cebed985b0efc3056350ec59e8646e7fd984cdb32e6ac14e76609d0ffaca204a7a3c20e9f95d +a04ea6392f3b5a176fa797ddec3214946962b84a8f729ffbd01ca65767ff6237da8147fc9dc7dd88662ad0faefdb538c +95c0d10a9ba2b0eb1fd7aa60c743b6cf333bb7f3d7adedce055d6cd35b755d326bf9102afabb1634f209d8dacfd47f1a +a1a583d28b07601541fa666767f4f45c954431f8f3cc3f96380364c5044ff9f64114160e5002fb2bbc20812b8cbd36cb +a1a0708af5034545e8fcc771f41e14dff421eed08b4606f6d051f2d7799efd00d3a59a1b9a811fa4eddf5682e63102ea +ab27c7f54096483dd85c866cfb347166abe179dc5ffaca0c29cf3bfe5166864c7fa5f954c919b3ba00bdbab38e03407d +ac8c82271c8ca71125b380ed6c61b326c1cfe5664ccd7f52820e11f2bea334b6f60b1cf1d31599ed94d8218aa6fbf546 +a015ea84237d6aa2adb677ce1ff8a137ef48b460afaca20ae826a53d7e731320ebdd9ee836de7d812178bec010dd6799 +925418cda78a56c5b15d0f2dc66f720bda2885f15ffafb02ce9c9eed7167e68c04ad6ae5aa09c8c1c2f387aa39ad6d1b +87c00bba80a965b3742deacafb269ca94ead4eb57fdb3ed28e776b1d0989e1b1dba289019cfb1a0f849e58668a4f1552 +948d492db131ca194f4e6f9ae1ea6ebc46ebbed5d11f1f305d3d90d6b4995b1218b9606d114f48282a15661a8a8051ca +8179617d64306417d6865add8b7be8452f1759721f97d737ef8a3c90da6551034049af781b6686b2ea99f87d376bce64 +918e3da425b7c41e195ed7b726fa26b15a64299fe12a3c22f51a2a257e847611ac6cfcc99294317523fc491e1cbe60c4 +a339682a37844d15ca37f753599d0a71eedfbbf7b241f231dd93e5d349c6f7130e0d0b97e6abd2d894f8b701da37cb11 +8fc284f37bee79067f473bc8b6de4258930a21c28ac54aaf00b36f5ac28230474250f3aa6a703b6057f7fb79a203c2c1 +a2c474e3a52a48cd1928e755f610fefa52d557eb67974d02287dbb935c4b9aab7227a325424fed65f8f6d556d8a46812 +99b88390fa856aa1b8e615a53f19c83e083f9b50705d8a15922e7c3e8216f808a4cc80744ca12506b1661d31d8d962e4 +a1cbd03e4d4f58fc4d48fa165d824b77838c224765f35d976d3107d44a6cf41e13f661f0e86f87589292721f4de703fb +b3a5dde8a40e55d8d5532beaa5f734ee8e91eafad3696df92399ae10793a8a10319b6dc53495edcc9b5cfd50a389a086 +996e25e1df5c2203647b9a1744bd1b1811857f742aee0801508457a3575666fcc8fc0c047c2b4341d4b507008cd674c2 +93e0a66039e74e324ee6c38809b3608507c492ef752202fff0b2c0e1261ca28f1790b3af4fdb236f0ed7e963e05c1ec0 +b6084e5818d2d860ac1606d3858329fbad4708f79d51a6f072dc370a21fdb1e1b207b74bc265a8547658bfb6a9569bb3 +a5336126a99c0ecfc890584b2a167922a26cae652dfc96a96ab2faf0bf9842f166b39ceaf396cd3d300d0ebb2e6e0ebf +b8b6f13ce9201decaba76d4eca9b9fa2e7445f9bc7dc9f82c262f49b15a40d45d5335819b71ff2ee40465da47d015c47 +b45df257b40c68b7916b768092e91c72b37d3ed2a44b09bf23102a4f33348849026cb3f9fbb484adfea149e2d2a180ff +a50d38ee017e28021229c4bb7d83dd9cdad27ab3aa38980b2423b96aa3f7dc618e3b23895b0e1379ca20299ff1919bbf +97542cf600d34e4fdc07d074e8054e950708284ed99c96c7f15496937242365c66e323b0e09c49c9c38113096640a1b6 +822d198629697dcd663be9c95ff1b39419eae2463fa7e6d996b2c009d746bedc8333be241850153d16c5276749c10b20 +9217bc14974766ebdfbf6b434dd84b32b04658c8d8d3c31b5ff04199795d1cfad583782fd0c7438df865b81b2f116f9c +93477879fa28a89471a2c65ef6e253f30911da44260833dd51030b7a2130a923770ebd60b9120f551ab373f7d9ed80aa +87d89ff7373f795a3a798f03e58a0f0f0e7deab8db2802863fab84a7be64ae4dcf82ece18c4ddbefccd356262c2e8176 +a3ba26bd31d3cc53ceeced422eb9a63c0383cde9476b5f1902b7fe2b19e0bbf420a2172ac5c8c24f1f5c466eecc615d4 +a0fe061c76c90d84bd4353e52e1ef4b0561919769dbabe1679b08ef6c98dcfb6258f122bb440993d976c0ab38854386b +b3070aa470185cb574b3af6c94b4069068b89bb9f7ea7db0a668df0b5e6aabdfe784581f13f0cf35cd4c67726f139a8c +9365e4cdf25e116cbc4a55de89d609bba0eaf0df2a078e624765509f8f5a862e5da41b81883df086a0e5005ce1576223 +a9036081945e3072fa3b5f022df698a8f78e62ab1e9559c88f9c54e00bc091a547467d5e2c7cbf6bc7396acb96dd2c46 +8309890959fcc2a4b3d7232f9062ee51ece20c7e631a00ec151d6b4d5dfccf14c805ce5f9aa569d74fb13ae25f9a6bbe +b1dc43f07303634157f78e213c2fae99435661cc56a24be536ccbd345ef666798b3ac53c438209b47eb62b91d6fea90a +84eb451e0a74ef14a2c2266ff01bd33d9a91163c71f89d0a9c0b8edfcfe918fc549565509cd96eed5720a438ff55f7f2 +9863b85a10db32c4317b19cc9245492b9389b318cf128d9bbc7ec80a694fcbbd3c0d3189a8cad00cc9290e67e5b361ee +8a150ee474ebe48bdfcac1b29e46ac90dcded8abbe4807a165214e66f780f424be367df5ef1e94b09acf4a00cd2e614d +a6677a373130b83e30849af12475e192f817ba4f3226529a9cca8baaefb8811db376e4a044b42bf1481268c249b1a66e +b969cbf444c1297aa50d1dfa0894de4565161cb1fc59ba03af9655c5bf94775006fe8659d3445b546538a22a43be6b93 +8383167e5275e0707e391645dc9dea9e8a19640ecfa23387f7f6fcaddff5cde0b4090dfad7af3c36f8d5c7705568e8d8 +a353ddbc6b6837773e49bb1e33a3e00ca2fb5f7e1dba3a004b0de75f94a4e90860d082a455968851ef050ae5904452e0 +adeccf320d7d2831b495479b4db4aa0e25c5f3574f65a978c112e9981b2663f59de4c2fa88974fdcabb2eedb7adab452 +afa0eacc9fdbe27fb5e640ecad7ecc785df0daf00fc1325af716af61786719dd7f2d9e085a71d8dc059e54fd68a41f24 +a5b803a5bbe0ca77c8b95e1e7bacfd22feae9f053270a191b4fd9bca850ef21a2d4bd9bcd50ecfb971bb458ff2354840 +b023c9c95613d9692a301ef33176b655ba11769a364b787f02b42ceb72338642655ea7a3a55a3eec6e1e3b652c3a179e +8fa616aa7196fc2402f23a19e54620d4cf4cf48e1adfb7ea1f3711c69705481ddcc4c97236d47a92e974984d124589e5 +a49e11e30cb81cb7617935e8a30110b8d241b67df2d603e5acc66af53702cf1e9c3ef4a9b777be49a9f0f576c65dcc30 +8df70b0f19381752fe327c81cce15192389e695586050f26344f56e451df2be0b1cdf7ec0cba7ce5b911dcff2b9325ae +8fbbc21a59d5f5a14ff455ca78a9a393cab91deb61cf1c25117db2714d752e0054ed3e7e13dd36ad423815344140f443 +a9a03285488668ab97836a713c6e608986c571d6a6c21e1adbd99ae4009b3dde43721a705d751f1bd4ebf1ea7511dfed +b2f32b8e19e296e8402251df67bae6066aeefd89047586d887ffa2eacdf38e83d4f9dc32e553799024c7a41818945755 +942cf596b2278ad478be5c0ab6a2ad0ceafe110263cc93d15b9a3f420932104e462cf37586c374f10b1040cb83b862e0 +aaa077a55f501c875ceae0a27ef2b180be9de660ef3d6b2132eb17256771ce609d9bc8aaf687f2b56ae46af34ad12b30 +90ac74885be1448101cf3b957d4486e379673328a006ea42715c39916e9334ea77117ff4a60d858e2ccce9694547a14f +9256cdfc2339e89db56fd04bd9b0611be0eefc5ee30711bcece4aadf2efcc5a6dcc0cfd5f733e0e307e3a58055dff612 +a4c7384e208a0863f4c056248f595473dcde70f019ddaede45b8caf0752575c241bac6e436439f380ac88eee23a858e9 +a3aa67391781e0736dddc389f86b430b2fc293b7bd56bfd5a8ec01d1dd52ed940593c3ad4ce25905061936da062b0af6 +80299275ec322fbb66cc7dce4482ddd846534e92121186b6906c9a5d5834346b7de75909b22b98d73120caec964e7012 +aa3a6cd88e5f98a12738b6688f54478815e26778357bcc2bc9f2648db408d6076ef73cced92a0a6b8b486453c9379f18 +b07c444681dc87b08a7d7c86708b82e82f8f2dbd4001986027b82cfbed17b9043e1104ade612e8e7993a00a4f8128c93 +af40e01b68d908ac2a55dca9b07bb46378c969839c6c822d298a01bc91540ea7a0c07720a098be9a3cfe9c27918e80e8 +abd8947c3bbc3883c80d8c873f8e2dc9b878cbbb4fc4a753a68f5027de6d8c26aa8fbbafeb85519ac94e2db660f31f26 +a234f9d1a8f0cb5d017ccca30b591c95ec416c1cb906bd3e71b13627f27960f61f41ed603ffbcf043fd79974ec3169a8 +835aaf52a6af2bc7da4cf1586c1a27c72ad9de03c88922ad172dce7550d70f6f3efcc3820d38cd56ae3f7fc2f901f7a0 +ae75db982a45ad01f4aa7bc50d642ff188219652bb8d521d13a9877049425d57852f3c9e4d340ffec12a4d0c639e7062 +b88884aa9187c33dc784a96832c86a44d24e9ffe6315544d47fc25428f11337b9ffd56eb0a03ad709d1bf86175059096 +8492ca5afcc6c0187b06453f01ed45fd57eb56facbeea30c93686b9e1dab8eaabd89e0ccb24b5f35d3d19cd7a58b5338 +9350623b6e1592b7ea31b1349724114512c3cce1e5459cd5bddd3d0a9b2accc64ab2bf67a71382d81190c3ab7466ba08 +98e8bf9bed6ae33b7c7e0e49fc43de135bffdba12b5dcb9ff38cb2d2a5368bb570fe7ee8e7fbe68220084d1d3505d5be +ab56144393f55f4c6f80c67e0ab68f445568d68b5aa0118c0c666664a43ba6307ee6508ba0bb5eb17664817bc9749af0 +827d5717a41b8592cfd1b796a30d6b2c3ca2cdc92455f9f4294b051c4c97b7ad6373f692ddafda67884102e6c2a16113 +8445ce2bb81598067edaa2a9e356eda42fb6dc5dd936ccf3d1ff847139e6020310d43d0fec1fe70296e8f9e41a40eb20 +9405178d965ee51e8d76d29101933837a85710961bb61f743d563ef17263f3c2e161d57e133afac209cdb5c46b105e31 +b209f9ed324c0daa68f79800c0a1338bbaf6d37b539871cb7570f2c235caca238a2c4407961fcb7471a103545495ef2c +92ae6437af6bbd97e729b82f5b0d8fb081ca822f340e20fae1875bdc65694cd9b8c037a5a1d49aa9cae3d33f5bad414e +9445bdb666eae03449a38e00851629e29a7415c8274e93343dc0020f439a5df0009cd3c4f5b9ce5c0f79aefa53ceac99 +93fdab5f9f792eada28f75e9ac6042a2c7f3142ba416bfdb1f90aa8461dbe4af524eee6db4f421cb70c7bc204684d043 +a7f4dc949af4c3163953320898104a2b17161f7be5a5615da684f881633174fb0b712d0b7584b76302e811f3fac3c12f +a8ac84da817b3066ba9789bf2a566ccf84ab0a374210b8a215a9dcf493656a3fa0ecf07c4178920245fee0e46de7c3ec +8e6a0ae1273acda3aa50d07d293d580414110a63bc3fb6330bb2ee6f824aff0d8f42b7375a1a5ba85c05bfbe9da88cb5 +a5dea98852bd6f51a84fa06e331ea73a08d9d220cda437f694ad9ad02cf10657882242e20bdf21acbbaa545047da4ce5 +b13f410bf4cfce0827a5dfd1d6b5d8eabc60203b26f4c88238b8000f5b3aaf03242cdeadc2973b33109751da367069e1 +a334315a9d61b692ad919b616df0aa75a9f73e4ea6fc27d216f48964e7daebd84b796418580cf97d4f08d4a4b51037cd +8901ba9e963fcd2f7e08179b6d19c7a3b8193b78ca0e5cf0175916de873ca0d000cd7ac678c0473be371e0ac132f35a2 +b11a445433745f6cb14c9a65314bbf78b852f7b00786501b05d66092b871111cd7bee25f702d9e550d7dd91601620abb +8c2f7b8e7b906c71f2f154cc9f053e8394509c37c07b9d4f21b4495e80484fc5fc8ab4bdc525bd6cfa9518680ba0d1a2 +b9733cebe92b43b899d3d1bfbf4b71d12f40d1853b2c98e36e635fdd8a0603ab03119890a67127e6bc79afae35b0bef2 +a560f6692e88510d9ba940371e1ada344caf0c36440f492a3067ba38e9b7011caac37ba096a8a4accb1c8656d3c019b3 +ac18624339c1487b2626eef00d66b302bdb1526b6340d6847befe2fdfb2b410be5555f82939f8707f756db0e021ed398 +afd9a3b8866a7fe4f7bc13470c0169b9705fcd3073685f5a6dcff3bdbbc2be50ac6d9908f9a10c5104b0bffc2bc14dad +97f15c92fe1f10949ed9def5dd238bc1429706e5037a0e0afb71c2d0e5845e2fed95a171c393e372077a7c7059f8c0e0 +9453a1d4d09c309b70968ea527007d34df9c4cfd3048e5391aac5f9b64ca0c05dde5b8c949c481cfc83ef2e57b687595 +b80e4b7c379ad435c91b20b3706253b763cbc980db78f782f955d2516af44c07bbfa5888cbf3a8439dc3907320feb25a +8939f458d28fefe45320b95d75b006e98330254056d063e4a2f20f04bcb25936024efe8d436d491ed34b482f9b9ae49c +a9ead2e833f71f7e574c766440c4b3c9c3363698c7ade14499a56003a272832ee6d99440887fa43ccdf80265b9d56b97 +b6547a36934f05ce7b779e68049d61351cf229ae72dc211cc96a2a471b2724782f9355fdb415ea6f0ea1eb84fe00e785 +828bfb3099b7b650b29b0f21279f829391f64520a6ab916d1056f647088f1e50fac9253ef7464eceab5380035c5a59c4 +8d714b9ea650be4342ff06c0256189e85c5c125adf6c7aeca3dba9b21d5e01a28b688fc2116ce285a0714a8f1425c0b8 +8a82eda041b2e72a3d73d70d85a568e035fbd6dc32559b6c6cfdf6f4edcb59a6ba85b6294a721aa0a71b07714e0b99ae +af5665ebc83d027173b14ffb0e05af0a192b719177889fadc9ac8c082fda721e9a75d9ce3f5602dbfd516600ee3b6405 +a68fdddf03d77bebdb676e40d93e59bd854408793df2935d0a5600601f7691b879981a398d02658c2da39dbbf61ef96c +8c001ebc84fcf0470b837a08a7b6125126b73a2762db47bbdc38c0e7992b1c66bac7a64faa1bf1020d1c63b40adc3082 +8553889b49f9491109792db0a69347880a9cf2911b4f16f59f7f424e5e6b553687d51282e8f95be6a543635247e2e2c2 +a2c269d6370b541daf1f23cc6b5d2b03a5fa0c7538d53ae500ef875952fe215e74a5010329ff41461f4c58b32ad97b3d +a5dae097285392b4eba83a9fd24baa03d42d0a157a37fae4b6efc3f45be86024b1182e4a6b6eadcf5efe37704c0a1ae5 +89871a77d2032387d19369933cd50a26bda643e40cfd0ce73febe717a51b39fae981406fd41e50f4a837c02a99524ef9 +8a76d495e90093ec2ac22f53759dc1cf36fbb8370fb586acbd3895c56a90bbf3796bcc4fc422ca4058adf337ead1402e +ad4eb7576c4954d20623c1336c63662c2a6fb46ec6ef99b7f8e946aa47488dcb136eab60b35600f98c78c16c10c99013 +894c2b120cec539feb1d281baaadde1e44beafedeeec29b804473fe024e25c1db652f151c956e88d9081fb39d27e0b19 +9196bd5c100878792444c573d02b380a69e1b4b30cb59a48114852085058a5fd952df4afee3ecceb5c4ede21e1ed4a1a +a996fffc910764ea87a1eedc3a3d600e6e0ff70e6a999cb435c9b713a89600fc130d1850174efe9fc18244bb7c6c5936 +8591bb8826befa8bee9663230d9a864a5068589f059e37b450e8c85e15ce9a1992f0ce1ead1d9829b452997727edcf9d +9465e20bb22c41bf1fa728be8e069e25cda3f7c243381ca9973cbedad0c7b07d3dd3e85719d77cf80b1058ce60e16d68 +926b5ce39b6e60b94878ffeae9ff20178656c375fb9cfe160b82318ca500eb3e2e3144608b6c3f8d6c856b8fe1e2fbcf +a1ef29cbc83c45eb28ad468d0ce5d0fdd6b9d8191ba5ffa1a781c2b232ed23db6b7b04de06ef31763a6bfe377fa2f408 +9328e63a3c8acf457c9f1f28b32d90d0eeadb0f650b5d43486a61d7374757a7ada5fc1def2a1e600fa255d8b3f48036f +a9c64880fcb7654f4dd08f4c90baac95712dd6dd407e17ea60606e9a97dc8e54dd25cb72a9bf3fc61f8d0ad569fe369d +a908eb7b940c1963f73046d6b35d40e09013bfbfbeb2ccd64df441867e202b0f3b625fa32dd04987c3d7851360abdffc +b3947b5ed6d59e59e4472cdb1c3261de1b5278fb7cb9b5fca553f328b3b3e094596861ea526eca02395f7b7358155b7b +99da7f190d37bc58945f981cf484d40fcf0855cf8178e2ce8d057c7f0a9d9f77425fdbce9ef8366f44f671b20fd27d0b +913976d77d80e3657977df39571577fdf0be68ba846883705b454f8493578baa741cfaede53783e2c97cc08964395d83 +8d754a61e5164a80b5090c13f3e936056812d4ae8dc5cc649e6c7f37464777249bc4ae760a9806939131f39d92cca5bf +82ffd098480828a90cb221a8c28584e15904bad477c13b2e2d6ef0b96a861ce4a309a328fe44342365349456ad7c654f +89ae3ce4b0357044579ca17be85d8361bb1ce3941f87e82077dd67e43ec0f95edd4bd3426225c90994a81a99e79490b7 +a170892074016d57c9d8e5a529379d7e08d2c1158b9ac4487ac9b95266c4fd51cb18ae768a2f74840137eec05000dd5a +aafd8acd1071103c7af8828a7a08076324d41ea530df90f7d98fafb19735fc27ead91b50c2ca45851545b41d589d0f77 +8623c849e61d8f1696dc9752116a26c8503fd36e2cbbc9650feffdd3a083d8cdbb3b2a4e9743a84b9b2ad91ac33083f2 +ac7166ddd253bb22cdbd8f15b0933c001d1e8bc295e7c38dc1d2be30220e88e2155ecd2274e79848087c05e137e64d01 +a5276b216d3df3273bbfa46210b63b84cfe1e599e9e5d87c4e2e9d58666ecf1af66cb7ae65caebbe74b6806677215bd0 +88792f4aa3597bb0aebadb70f52ee8e9db0f7a9d74f398908024ddda4431221a7783e060e0a93bf1f6338af3d9b18f68 +8f5fafff3ecb3aad94787d1b358ab7d232ded49b15b3636b585aa54212f97dc1d6d567c180682cca895d9876cacb7833 +ab7cb1337290842b33e936162c781aa1093565e1a5b618d1c4d87dd866daea5cebbcc486aaa93d8b8542a27d2f8694c7 +88480a6827699da98642152ebc89941d54b4791fbc66110b7632fb57a5b7d7e79943c19a4b579177c6cf901769563f2f +a725ee6d201b3a610ede3459660658ee391803f770acc639cfc402d1667721089fb24e7598f00e49e81e50d9fd8c2423 +98924372da8aca0f67c8c5cad30fa5324519b014fae7849001dcd51b6286118f12b6c49061219c37714e11142b4d46de +a62c27360221b1a7c99697010dfe1fb31ceb17d3291cf2172624ebeff090cbaa3c3b01ec89fe106dace61d934711d42d +825173c3080be62cfdc50256c3f06fe190bc5f190d0eb827d0af5b99d80936e284a4155b46c0d462ee574fe31d60983d +a28980b97023f9595fadf404ed4aa36898d404fe611c32fd66b70252f01618896f5f3fda71aea5595591176aabf0c619 +a50f5f9def2114f6424ff298f3b128068438f40860c2b44e9a6666f43c438f1780be73cf3de884846f1ba67f9bef0802 +b1eee2d730da715543aeb87f104aff6122cb2bf11de15d2519ff082671330a746445777924521ec98568635f26988d0c +862f6994a1ff4adfd9fb021925cccf542fca4d4b0b80fb794f97e1eb2964ef355608a98eec6e07aadd4b45ee625b2a21 +8ce69a18df2f9b9f6e94a456a7d94842c61dea9b00892da7cf5c08144de9be39b8c304aeca8b2e4222f87ba367e61006 +b5f325b1cecd435f5346b6bc562d92f264f1a6d91be41d612df012684fdd69e86063db077bc11ea4e22c5f2a13ae7bee +85526870a911127835446cb83db8986b12d5637d59e0f139ad6501ac949a397a6c73bd2e7fba731b1bb357efe068242c +8552247d3f7778697f77389717def5a149fc20f677914048e1ed41553b039b5427badc930491c0bae663e67668038fd1 +a545640ee5e51f3fe5de7050e914cfe216202056cd9d642c90e89a166566f909ee575353cb43a331fde17f1c9021414e +8b51229b53cff887d4cab573ba32ec52668d197c084414a9ee5589b285481cea0c3604a50ec133105f661321c3ca50f5 +8cdc0b960522bed284d5c88b1532142863d97bbb7dc344a846dc120397570f7bd507ceb15ed97964d6a80eccfef0f28e +a40683961b0812d9d53906e795e6470addc1f30d09affebf5d4fbbd21ddfa88ce441ca5ea99c33fd121405be3f7a3757 +a527875eb2b99b4185998b5d4cf97dd0d4a937724b6ad170411fc8e2ec80f6cee2050f0dd2e6fee9a2b77252d98b9e64 +84f3a75f477c4bc4574f16ebc21aaa32924c41ced435703c4bf07c9119dd2b6e066e0c276ff902069887793378f779e0 +a3544bc22d1d0cab2d22d44ced8f7484bfe391b36991b87010394bfd5012f75d580596ffd4f42b00886749457bb6334b +b81f6eb26934b920285acc20ceef0220dd23081ba1b26e22b365d3165ce2fbae733bbc896bd0932f63dcc84f56428c68 +95e94d40a4f41090185a77bf760915a90b6a3e3ace5e53f0cb08386d438d3aa3479f0cd81081b47a9b718698817265cd +b69bd1625b3d6c17fd1f87ac6e86efa0d0d8abb69f8355a08739109831baeec03fd3cd4c765b5ff8b1e449d33d050504 +8448f4e4c043519d98552c2573b76eebf2483b82d32abb3e2bfc64a538e79e4f59c6ca92adff1e78b2f9d0a91f19e619 +8f11c42d6a221d1fda50887fb68b15acdb46979ab21d909ed529bcad6ae10a66228ff521a54a42aca0dad6547a528233 +a3adb18d7e4a882b13a067784cf80ea96a1d90f5edc61227d1f6e4da560c627688bdf6555d33fe54cab1bca242986871 +a24d333d807a48dc851932ed21cbdd7e255bad2699909234f1706ba55dea4bb6b6f8812ffc0be206755868ba8a4af3f9 +a322de66c22a606e189f7734dbb7fda5d75766d5e69ec04b4e1671d4477f5bcb9ff139ccc18879980ebc3b64ab4a2c49 +88f54b6b410a1edbf125db738d46ee1a507e69bc5a8f2f443eb787b9aa7dbd6e55014ec1e946aabeb3e27a788914fb04 +b32ee6da1dcd8d0a7fd7c1821bb1f1fe919c8922b4c1eeed56e5b068a5a6e68457c42b192cbaef5dc6d49b17fa45bc0f +8a44402da0b3a15c97b0f15db63e460506cb8bef56c457166aea5e8881087d8202724c539ef0feb97131919a73aefca8 +b967e3fead6171fa1d19fd976535d428b501baff59e118050f9901a54b12cc8e4606348454c8f0fc25bd6644e0a5532e +b7a0c9e9371c3efbbb2c6783ce2cc5f149135175f25b6d79b09c808bce74139020e77f0c616fa6dcb3d87a378532529d +a54207782ffc909cd1bb685a3aafabbc4407cda362d7b3c1b14608b6427e1696817aeb4f3f85304ac36e86d3d8caa65b +98c1da056813a7bfebc81d8db7206e3ef9b51f147d9948c088976755826cc5123c239ca5e3fe59bed18b5d0a982f3c3f +ae1c86174dfafa9c9546b17b8201719aecd359f5bbeb1900475041f2d5b8a9600d54d0000c43dd061cfda390585726ff +a8ee5a8be0bd1372a35675c87bfd64221c6696dc16e2d5e0996e481fec5cdbcb222df466c24740331d60f0521285f7d3 +8ddadbe3cf13af50d556ce8fc0dd77971ac83fad9985c3d089b1b02d1e3afc330628635a31707b32595626798ea22d45 +a5c80254baf8a1628dc77c2445ebe21fbda0de09dd458f603e6a9851071b2b7438fe74214df293dfa242c715d4375c95 +b9d83227ed2600a55cb74a7052003a317a85ca4bea50aa3e0570f4982b6fe678e464cc5156be1bd5e7bba722f95e92c5 +b56085f9f3a72bea9aa3a8dc143a96dd78513fa327b4b9ba26d475c088116cab13843c2bff80996bf3b43d3e2bddb1d6 +8fa9b39558c69a9757f1e7bc3f07295e4a433da3e6dd8c0282397d26f64c1ecd8eb3ba9824a7cacfb87496ebbb45d962 +879c6d0cb675812ed9dee68c3479a499f088068501e2677caeae035e6f538da91a49e245f5fcce135066169649872bee +91aa9fd3fed0c2a23d1edda8a6542188aeb8abee8772818769bdee4b512d431e4625a343af5d59767c468779222cf234 +a6be0bb2348c35c4143482c7ef6da9a93a5356f8545e8e9d791d6c08ed55f14d790d21ee61d3a56a2ae7f888a8fd46ca +808ee396a94e1b8755f2b13a6ffbedef9e0369e6c2e53627c9f60130c137299d0e4924d8ef367e0a7fad7f68a8c9193c +ad1086028fcdac94d5f1e7629071e7e47e30ad0190ae59aaebfb7a7ef6202ab91323a503c527e3226a23d7937af41a52 +9102bdaf79b907d1b25b2ec6b497e2d301c8eac305e848c6276b392f0ad734131a39cc02ed42989a53ca8da3d6839172 +8c976c48a45b6bc7cd7a7acea3c2d7c5f43042863b0661d5cd8763e8b50730552187a8eecf6b3d17be89110208808e77 +a2624c7e917e8297faa3af89b701953006bf02b7c95dfba00c9f3de77748bc0b13d6e15bb8d01377f4d98fb189538142 +a405f1e66783cdcfe20081bce34623ec3660950222d50b7255f8b3cc5d4369aeb366e265e5224c0204911539f0fa165e +8d69bdcaa5d883b5636ac8f8842026fcc58c5e2b71b7349844a3f5d6fbecf44443ef4f768eac376f57fb763606e92c9f +82fce0643017d16ec1c3543db95fb57bfa4855cc325f186d109539fcacf8ea15539be7c4855594d4f6dc628f5ad8a7b0 +8860e6ff58b3e8f9ae294ff2487f0d3ffae4cf54fd3e69931662dabc8efd5b237b26b3def3bcd4042869d5087d22afcf +88c80c442251e11c558771f0484f56dc0ed1b7340757893a49acbf96006aa73dfc3668208abea6f65375611278afb02a +8be3d18c6b4aa8e56fcd74a2aacb76f80b518a360814f71edb9ccf3d144bfd247c03f77500f728a62fca7a2e45e504c5 +8b8ebf0df95c3f9b1c9b80469dc0d323784fd4a53f5c5357bb3f250a135f4619498af5700fe54ad08744576588b3dfff +a8d88abdaadd9c2a66bc8db3072032f63ed8f928d64fdb5f810a65074efc7e830d56e0e738175579f6660738b92d0c65 +a0a10b5d1a525eb846b36357983c6b816b8c387d3890af62efb20f50b1cb6dd69549bbef14dab939f1213118a1ae8ec2 +8aadf9b895aeb8fdc9987daa937e25d6964cbd5ec5d176f5cdf2f0c73f6f145f0f9759e7560ab740bf623a3279736c37 +99aeda8a495031cc5bdf9b842a4d7647c55004576a0edc0bd9b985d60182608361ed5459a9d4b21aa8e2bd353d10a086 +832c8b3bfcd6e68eee4b100d58014522de9d4cefa99498bc06c6dca83741e4572e20778e0d846884b33439f160932bca +841f56ebefc0823ab484fc445d62f914e13957e47904419e42771aa605e33ab16c44f781f6f9aa42e3a1baf377f54b42 +a6e40271d419e295a182725d3a9b541ffd343f23e37549c51ecaa20d13cf0c8d282d6d15b24def5702bfee8ba10b12ac +8ac00925ac6187a4c5cde48ea2a4eaf99a607e58b2c617ee6f01df30d03fafada2f0469178dd960d9d64cbd33a0087d8 +b6b80916b540f8a0fe4f23b1a06e2b830008ad138271d5ba3cd16d6619e521fe2a7623c16c41cba48950793386eea942 +8412c0857b96a650e73af9d93087d4109dd092ddf82188e514f18fcac644f44d4d62550bfa63947f2d574a2e9d995bbb +b871395baa28b857e992a28ac7f6d95ec461934b120a688a387e78498eb26a15913b0228488c3e2360391c6b7260b504 +926e2d25c58c679be77d0e27ec3b580645956ba6f13adcbc2ea548ee1b7925c61fcf74c582337a3b999e5427b3f752f2 +a165fa43fecae9b913d5dcfc232568e3e7b8b320ce96b13800035d52844c38fd5dbf7c4d564241d860c023049de4bcbc +b4976d7572fd9cc0ee3f24888634433f725230a7a2159405946a79315bc19e2fc371448c1c9d52bf91539fd1fe39574b +a6b461eb72e07a9e859b9e16dfa5907f4ac92a5a7ca4368b518e4a508dc43f9b4be59db6849739f3ef4c44967b63b103 +b976606d3089345d0bc501a43525d9dca59cf0b25b50dfc8a61c5bd30fac2467331f0638fab2dc68838aa6ee8d2b6bc9 +b16ea61c855da96e180abf7647fa4d9dd6fd90adebadb4c5ed4d7cd24737e500212628fca69615d89cb40e9826e5a214 +95a3e3162eb5ea27a613f8c188f2e0dcc5cbd5b68c239858b989b004d87113e6aa3209fa9fad0ee6ecef42814ba9db1a +b6a026ab56d3224220e5bce8275d023c8d39d1bdf7eec3b0923429b7d5ef18cf613a3591d364be8727bb1fa0ba11eabb +949f117e2e141e25972ee9ccdd0b7a21150de7bbf92bbd89624a0c5f5a88da7b2b172ba2e9e94e1768081f260c2a2f8d +b7c5e9e6630287d2a20a2dfb783ffe6a6ff104ff627c6e4e4342acc2f3eb6e60e9c22f465f8a8dc58c42f49840eca435 +872be5a75c3b85de21447bb06ac9eb610f3a80759f516a2f99304930ddf921f34cbffc7727989cdd7181d5fc62483954 +a50976ea5297d797d220932856afdd214d1248230c9dcd840469ecc28ea9f305b6d7b38339fedb0c00b5251d77af8c95 +80b360f8b44914ff6f0ffbd8b5360e3cabe08639f6fe06d0c1526b1fe9fe9f18c497f1752580b30e950abd3e538ad416 +a2f98f9bf7fac78c9da6bb41de267742a9d31cf5a04b2fb74f551084ec329b376f651a59e1ae919b2928286fb566e495 +8b9d218a8a6c150631548e7f24bbd43f132431ae275c2b72676abbea752f554789c5ff4aac5c0eeee5529af7f2b509ef +aa21a243b07e9c7b169598bf0b102c3c280861780f83121b2ef543b780d47aaa4b1850430ee7927f33ece9847c4e0e1a +8a6f90f4ce58c8aa5d3656fe4e05acccf07a6ec188a5f3cde7bf59a8ae468e66f055ac6dfc50b6e8e98f2490d8deedc5 +8e39f77ca4b5149ffe9945ceac35d068760ba338d469d57c14f626dd8c96dbe993dd7011beff727c32117298c95ee854 +83bd641c76504222880183edd42267e0582642c4993fe2c7a20ce7168e4c3cbf7586e1d2d4b08c84d9b0bf2f6b8800b8 +a9d332993cf0c1c55130e5cf3a478eb5e0bfb49c25c07538accc692ef03d82b458750a7b991cc0b41b813d361a5d31e3 +a0fc60e6a6015df9bee04cea8f20f01d02b14b6f7aa03123ab8d65da071b2d0df5012c2a69e7290baae6ed6dd29ebe07 +a2949dde2e48788ceaac7ec7243f287ffe7c3e788cdba97a4ab0772202aeef2d50382bed8bf7eff5478243f7eabe0bda +a7879373ea18572dba6cf29868ca955ffa55b8af627f29862f6487ee398b81fe3771d8721ca8e06716c5d91b9ac587cb +b3c7081e2c5306303524fbe9fe5645111a57dffd4ec25b7384da12e56376a0150ab52f9d9cc6ca7bdd950695e39b766d +a634a6a19d52dcb9f823352b36c345d2de54b75197bcd90528d27830bd6606d1a9971170de0849ed5010afa9f031d5be +88f2062f405fa181cfdb8475eaf52906587382c666ca09a9522537cfebbc7de8337be12a7fd0db6d6f2f7ab5aefab892 +b1f0058c1f273191247b98783b2a6f5aa716cf799a8370627fc3456683f03a624d0523b63a154fe9243c0dfd5b37c460 +ae39a227cc05852437d87be6a446782c3d7fbe6282e25cf57b6b6e12b189bdc0d4a6e2c3a60b3979256b6b5baf8f1c5f +802a1af228ab0c053b940e695e7ef3338f5be7acf4e5ed01ac8498e55b492d3a9f07996b1700a84e22f0b589638909cd +a36490832f20e4b2f9e79ee358b66d413f034d6a387534b264cdeac2bca96e8b5bcbdd28d1e98c44498032a8e63d94d2 +8728c9a87db2d006855cb304bba54c3c704bf8f1228ae53a8da66ca93b2dac7e980a2a74f402f22b9bc40cd726e9c438 +a08f08ab0c0a1340e53b3592635e256d0025c4700559939aeb9010ed63f7047c8021b4210088f3605f5c14fb51d1c613 +9670fd7e2d90f241e8e05f9f0b475aa260a5fb99aa1c9e61cd023cbad8ed1270ae912f168e1170e62a0f6d319cf45f49 +a35e60f2dd04f098bf274d2999c3447730fe3e54a8aff703bc5a3c274d22f97db4104d61a37417d93d52276b27ef8f31 +859df7a21bc35daec5695201bd69333dc4f0f9e4328f2b75a223e6615b22b29d63b44d338413ca97eb74f15563628cb7 +b2b44ad3e93bc076548acdf2477803203108b89ecc1d0a19c3fb9814d6b342afc420c20f75e9c2188ad75fdb0d34bb2d +941173ee2c87765d10758746d103b667b1227301e1bcfecef2f38f9ab612496a9abd3050cef5537bf28cfecd2aacc449 +92b0bea30ebed20ac30648efb37bac2b865daaa514316e6f5470e1de6cb84651ff77c127aa7beed4521bda5e8fc81122 +af17bf813bb238cf8bb437433f816786612209180a6c0a1d5141292dc2d2c37164ef13bfc50c718bfcc6ce26369298a2 +8461fd951bdfda099318e05cc6f75698784b033f15a71bce26165f0ce421fd632d50df9eeced474838c0050b596e672c +83281aa18ae4b01e8201e1f64248cc6444c92ee846ae72adb178cef356531558597d84ff93a05abf76bfe313eb7dbe86 +b62b150f73999c341daa4d2f7328d2f6ca1ef3b549e01df58182e42927537fc7971c360fe8264af724f4c0247850ef12 +a7022a201f79c012f982b574c714d813064838a04f56964d1186691413757befeeaada063e7884297606e0eea1b1ed43 +a42ac9e8be88e143853fd8e6a9ff21a0461801f0ac76b69cca669597f9af17ecb62cccdcdcbe7f19b62ab93d7f838406 +80f1ca73b6ba3a2fbae6b79b39c0be8c39df81862d46c4990c87cbf45b87996db7859d833abc20af2fcb4faf059c436a +b355943e04132d5521d7bbe49aea26f6aa1c32f5d0853e77cc2400595325e923a82e0ff7601d1aee79f45fd8a254f6ae +87142c891d93e539b31d0b5ead9ea600b9c84db9be9369ff150a8312fe3d10513f4c5b4d483a82b42bc65c45dd9dd3bd +823c3d7f6dda98a9d8c42b3fee28d3154a95451402accadb6cf75fc45d2653c46a569be75a433094fa9e09c0d5cf1c90 +b3c3497fe7356525c1336435976e79ec59c5624c2fb6185ee09ca0510d58b1e392965e25df8a74d90d464c4e8bb1422b +88c48d83e8ddc0d7eea051f3d0e21bc0d3a0bb2b6a39ece76750c1c90c382a538c9a35dc9478b8ceb8157dcccbbf187a +93da81a8939f5f58b668fefdc6f5f7eca6dc1133054de4910b651f8b4a3267af1e44d5a1c9e5964dc7ab741eb146894b +8b396e64985451ac337f16be61105106e262e381ea04660add0b032409b986e1ac64da3bc2feae788e24e9cb431d8668 +9472068b6e331ea67e9b5fbf8057672da93c209d7ded51e2914dbb98dccd8c72b7079b51fd97a7190f8fc8712c431538 +ac47e1446cb92b0a7406f45c708567f520900dfa0070d5e91783139d1bfc946d6e242e2c7b3bf4020500b9f867139709 +896053706869fb26bb6f7933b3d9c7dd6db5c6bd1269c7a0e222b73039e2327d44bda7d7ae82bf5988808b9831d78bcd +a55e397fa7a02321a9fe686654c86083ecedb5757586d7c0250ec813ca6d37151a12061d5feca4691a0fd59d2f0fdd81 +ae23f08ac2b370d845036518f1bddb7fea8dc59371c288a6af310486effeb61963f2eef031ca90f9bdbcf0e475b67068 +b5462921597a79f66c0fec8d4c7cfd89f427692a7ce30d787e6fd6acd2377f238ec74689a0fdbe8ef3c9c9bd24b908dc +ae67e8ea7c46e29e6aae6005131c29472768326819aa294aaf5a280d877de377b44959adb1348fa3e929dcbc3ae1f2c0 +84962b4c66500a20c4424191bdfb619a46cda35bdb34c2d61edcb0b0494f7f61dd5bf8f743302842026b7b7d49edd4b5 +846f76286dc3cc59cb15e5dabb72a54a27c78190631df832d3649b2952fa0408ecde7d4dfdae7046c728efa29879fb51 +8f76c854eaee8b699547e07ad286f7dadfa6974c1328d12502bd7630ae619f6129272fdd15e2137ffef0143c42730977 +8007b163d4ea4ec6d79e7a2aa19d06f388da0b3a56f3ee121441584e22a246c0e792431655632bf6e5e02cb86914eebf +ac4d2cecc1f33e6fb73892980b61e62095ddff5fd6167f53ca93d507328b3c05440729a277dc3649302045b734398af1 +92d2a88f2e9c9875abaff0d42624ccb6d65401de7127b5d42c25e6adccd7a664504c5861618f9031ced8aeb08b779f06 +a832c1821c1b220eb003fc532af02c81196e98df058cdcc9c9748832558362915ea77526937f30a2f74f25073cb89afb +b6f947ab4cc2baec100ed8ec7739a2fd2f9504c982b39ab84a4516015ca56aea8eef5545cfc057dd44c69b42125fb718 +b24afacf2e90da067e5c050d2a63878ee17aaf8fd446536f2462da4f162de87b7544e92c410d35bf2172465940c19349 +b7a0aa92deac71eaab07be8fa43086e071e5580f5dbf9b624427bdd7764605d27303ae86e5165bed30229c0c11958c38 +b0d1d5bfa1823392c5cf6ed927c1b9e84a09a24b284c2cd8fcb5fda8e392c7c59412d8f74eb7c48c6851dff23ae66f58 +a24125ef03a92d2279fb384186ca0274373509cfec90b34a575490486098438932ee1be0334262d22d5f7d3db91efe67 +83e08e5fba9e8e11c164373794f4067b9b472d54f57f4dbe3c241cf7b5b7374102de9d458018a8c51ab3aed1dddf146f +9453101b77bb915ed40990e1e1d2c08ea8ec5deb5b571b0c50d45d1c55c2e2512ec0ceca616ff0376a65678a961d344d +92a0516e9eb6ad233d6b165a8d64a062ce189b25f95d1b3264d6b58da9c8d17da2cd1f534800c43efcf2be73556cd2ff +958d0b5d7d8faf25d2816aa6a2c5770592ad448db778dd9b374085baa66c755b129822632eaabcb65ee35f0bf4b73634 +90a749de8728b301ad2a6b044e8c5fd646ccd8d20220e125cba97667e0bb1d0a62f6e3143b28f3d93f69cdc6aa04122a +84bd34c8d8f74dec07595812058db24d62133c11afed5eb2a8320d3bfc28e442c7f0cfd51011b7b0bb3e5409cb7b6290 +aecc250b556115d97b553ad7b2153f1d69e543e087890000eaa60f4368b736921d0342ce5563124f129096f5d5e2ca9d +977f17ac82ed1fbf422f9b95feb3047a182a27b00960296d804fd74d54bb39ad2c055e665c1240d2ad2e06a3d7501b00 +af5be9846bd4879ebe0af5e7ad253a632f05aedfe306d31fe6debe701ba5aa4e33b65efc05043bc73aadb199f94baed4 +9199e12ec5f2aaaeed6db5561d2dcc1a8fe9c0854f1a069cba090d2dff5e5ba52b10c841ccbd49006a91d881f206150d +8f4a96a96ed8ceaf3beba026c89848c9ca4e6452ce23b7cf34d12f9cc532984a498e051de77745bdc17c7c44c31b7c30 +af3f2a3dbe8652c4bfca0d37fb723f0e66aab4f91b91a625114af1377ad923da8d36da83f75deb7a3219cd63135a3118 +a6d46963195df8962f7aa791d104c709c38caa438ddd192f7647a884282e81f748c94cdf0bb25d38a7b0dc1b1d7bbcf7 +86f3de4b22c42d3e4b24b16e6e8033e60120af341781ab70ae390cb7b5c5216f6e7945313c2e04261a51814a8cb5db92 +b9f86792e3922896cfd847d8ff123ff8d69ecf34968fb3de3f54532f6cd1112b5d34eeabdca46ae64ad9f6e7e5b55edc +83edfbcbc4968381d1e91ab813b3c74ab940eaf6358c226f79182f8b21148ec130685fd91b0ea65916b0a50bccf524ea +93b61daca7a8880b7926398760f50016f2558b0bab74c21181280a1baf3414fc539911bb0b79c4288d29d3c4ad0f4417 +ad541aeb83a47526d38f2e47a5ce7e23a9adabe5efeae03541026881e6d5ef07da3ac1a6ed466ca924fa8e7a91fcff88 +ac4bba31723875025640ed6426003ed8529215a44c9ffd44f37e928feef9fc4dfa889088131c9be3da87e8f3fdf55975 +88fa4d49096586bc9d29592909c38ea3def24629feacd378cc5335b70d13814d6dac415f8c699ee1bf4fe8b85eb89b38 +b67d0b76cbd0d79b71f4673b96e77b6cda516b8faa1510cfe58ff38cc19000bb5d73ff8418b3dab8c1c7960cb9c81e36 +98b4f8766810f0cfecf67bd59f8c58989eb66c07d3dfeee4f4bbce8fd1fce7cc4f69468372eaec7d690748543bd9691d +8445891af3c298b588dec443beacdf41536adb84c812c413a2b843fd398e484eb379075c64066b460839b5fe8f80177c +b603635c3ed6fdc013e2a091fc5164e09acf5f6a00347d87c6ebadb1f44e52ff1a5f0466b91f3f7ffc47d25753e44b75 +87ec2fc928174599a9dafe7538fec7dcf72e6873b17d953ed50708afff0da37653758b52b7cafa0bf50dfcf1eafbb46c +b9dbd0e704d047a457d60efe6822dc679e79846e4cbcb11fa6c02079d65673ee19bbf0d14e8b7b200b9205f4738df7c7 +9591ec7080f3f5ba11197a41f476f9ba17880f414d74f821a072ec5061eab040a2acba3d9856ff8555dfe5eaeb14ca19 +b34c9d1805b5f1ce38a42b800dec4e7f3eb8c38e7d2b0a525378e048426fed150dbfe9cc61f5db82b406d1b9ff2d10bf +a36fdc649dc08f059dfa361e3969d96b4cc4a1ebf10b0cd01a7dd708430979e8d870961fef85878f8779b8e23caafb18 +88dfc739a80c16c95d9d6f73c3357a92d82fa8c3c670c72bee0f1e4bac9ec338e1751eb786eda3e10f747dd7a686900f +84a535ad04f0961756c61c70001903a9adf13126983c11709430a18133c4b4040d17a33765b4a06968f5d536f4bfb5c5 +8c86d695052a2d2571c5ace744f2239840ef21bb88e742f050c7fa737cd925418ecef0971333eb89daa6b3ddfede268c +8e9a700157069dc91e08ddcbdde3a9ad570272ad225844238f1015004239c542fceb0acce6d116c292a55f0d55b6175e +84d659e7f94e4c1d15526f47bc5877a4ef761c2a5f76ec8b09c3a9a30992d41b0e2e38ed0c0106a6b6c86d670c4235f3 +a99253d45d7863db1d27c0ab561fb85da8c025ba578b4b165528d0f20c511a9ca9aff722f4ff7004843f618eb8fced95 +89a3cacb15b84b20e95cd6135550146bbe6c47632cc6d6e14d825a0c79b1e02b66f05d57d1260cb947dc4ae5b0283882 +8385b1555e794801226c44bd5e878cbe68aeac0a19315625a8e5ea0c3526b58cdd4f53f9a14a167a5e8a293b530d615a +b68c729e9df66c5cd22af4909fb3b0057b6a231c4a31cd6bf0fa0e53c5809419d15feb483de6e9408b052458e819b097 +924f56eda269ec7ec2fc20c5731bf7f521546ddf573ccbe145592f1c9fee5134747eb648d9335119a8066ca50a1f7e50 +b2100a26b9c3bec7ec5a53f0febbf56303f199be2f26b2d564cfee2adc65483b84192354f2865c2f4c035fa16252ae55 +8f64dbed62e638563967ec1605a83216aed17eb99aa618c0543d74771ea8f60bbb850c88608d4f8584f922e30a8a0a72 +b31b9e1ffe8d7260479c9413f8e680f3fe391ae8fcf44fcca3000d9b2473a40c1d32299f8f63865a57579a2d6c7e9f08 +a5b1d136142eb23e322c6c07cb838a3f58ab6925472352ebd0bb47041a0d8729e1074ca223922f3a7a672ced7a1e562d +8d9470a5a15d833a447b5f108333d50f30aa7659e331c3f8080b1e928a99922edc650466a2f54f3d48afdb34bff42142 +866368f5891564e5b2de37ad21ff0345c01129a14ea5667f9b64aad12d13ec034622872e414743af0bf20adb2041b497 +88ef9c2ebf25fd0c04b7cfa35fbac2e4156d2f1043fa9f98998b2aa402c8f9a4f1039e782451a46840f3e0e4b3fa47d3 +94ba04a4859273697e264a2d238dc5c9ff573ebc91e4796ea58eebe4080c1bf991255ab2ad8fb1e0301ce7b79cc6e69b +86b6bd0953309a086e526211bf1a99327269304aa74d8cdc994cee63c3a2d4b883e832b0635888dff2a13f1b02eb8df4 +843ea6ea5f2c7a1fd50be56a5765dcce3ea61c99b77c1a729ee0cd8ec706385ac7062e603479d4c8d3527f030762d049 +8d3675195a3b06f2d935d45becc59f9fa8fa440c8df80c029775e47fe9c90e20f7c8e4cc9a2542dd6bfe87536c428f0d +8978580b0c9b0aa3ab2d47e3cfd92fa891d3ddee57829ee4f9780e8e651900457d8e759d1a9b3e8f6ae366e4b57f2865 +890112ec81d0f24b0dfbb4d228e418eff02ae63dc691caf59c1d103e1d194e6e2550e1bec41c0bfdb74fed454f621d0c +97da00bd4b19d1e88caff7f95b8b9a7d29bc0afe85d0c6a163b4b9ef336f0e90e2c49ce6777024bb08df908cc04ea1ca +b458268d275a5211106ccaa8333ce796ef2939b1c4517e502b6462e1f904b41184a89c3954e7c4f933d68b87427a7bfd +aac9c043ba8ba9283e8428044e6459f982413380ee7005a996dc3cc468f6a21001ecaa3b845ce2e73644c2e721940033 +82145013c2155a1200246a1e8720adf8a1d1436b10d0854369d5b1b6208353e484dd16ce59280c6be84a223f2d45e5e2 +b301bafa041f9b203a46beab5f16160d463aa92117c77a3dc6a9261a35645991b9bafcc186c8891ca95021bd35f7f971 +a531b8d2ac3de09b92080a8d8857efa48fb6a048595279110e5104fee7db1dd7f3cfb8a9c45c0ed981cbad101082e335 +a22ac1d627d08a32a8abd41504b5222047c87d558ffae4232cefdeb6a3dc2a8671a4d8ddfba2ff9068a9a3ffb0fe99b1 +b8d9f0e383c35afb6d69be7ff04f31e25c74dd5751f0e51290c18814fbb49ee1486649e64355c80e93a3d9278bd21229 +8165babccd13033a3614c878be749dfa1087ecbeee8e95abcfffe3aa06695711122cb94477a4d55cffd2febf0c1173de +a4c1bc84ecb9d995d1d21c2804adf25621676d60334bd359dac3a2ec5dc8de567aa2831c10147034025fb3e3afb33c4b +b77307cab8e7cb21e4038493058fb6db9e2ec91dda9d7f96f25acbc90309daf7b6d8a205682143ee35d675e9800c3b08 +aaf7466083cd1f325ba860efe3faf4cebe6a5eecf52c3e8375d72043a5cfc8e6cb4b40f8e48f97266e84f0d488e8badf +9264a05a3abc2a5b4958f957f3a486a5eb3ddd10ff57aa6943c9430d0cfa01d63b72695b1ade50ac1b302d312175e702 +b3f9e4c589ad28b1eceed99dc9980fac832524cfcbe4a486dfeedb4b97c080e24bdb3967e9ca63d2240e77f9addfaefd +b2c1e253a78e7179e5d67204422e0debfa09c231970b1bfb70f31a8d77c7f5059a095ca79d2e9830f12c4a8f88881516 +81865a8a25913d1072cb5fd9505c73e0fde45e4c781ddd20fb0a7560d8b1cd5e1f63881c6efc05360e9204dfa6c3ce16 +ab71c2ea7fa7853469a2236dedb344a19a6130dc96d5fd6d87d42d3fffda172557d203b7688ce0f86acd913ce362e6cd +8aa2051bc3926c7bd63565f3782e6f77da824cb3b22bb056aa1c5bccfa274c0d9e49a91df62d0e88876e2bd7776e44b9 +b94e7074167745323d1d353efe7cfb71f40a390e0232354d5dfd041ef523ac8f118fb6dcc42bf16c796e3f61258f36f8 +8210fcf01267300cb1ccf650679cf6e1ee46df24ae4be5364c5ff715332746c113d680c9a8be3f17cacaeb3a7ba226ce +905ac223568eedc5acd8b54e892be05a21abbb4083c5dbec919129f9d9ffa2c4661d78d43bf5656d8d7aafa06f89d647 +a6e93da7e0c998e6ce2592d1aa87d12bf44e71bec12b825139d56682cdce8f0ba6dbfe9441a9989e10578479351a3d9d +acde928a5e2df0d65de595288f2b81838155d5673013100a49b0cb0eb3d633237af1378148539e33ccd1b9a897f0fec3 +a6e1a47e77f0114be6ae7acd2a51e6a9e38415cce7726373988153cdd5d4f86ef58f3309adc5681af4a159300ed4e5b5 +ad2b6a0d72f454054cb0c2ebc42cd59ff2da7990526bd4c9886003ba63b1302a8343628b8fe3295d3a15aa85150e0969 +b0bc3aea89428d7918c2ee0cc57f159fba134dad224d0e72d21a359ca75b08fbb4373542f57a6408352033e1769f72c6 +aad0497525163b572f135fad23fdd8763631f11deeaf61dea5c423f784fe1449c866040f303555920dc25e39cdb2e9b4 +8ce5d8310d2e17342bf881d517c9afc484d12e1f4b4b08ad026b023d98cba410cd9a7cc8e2c3c63456652a19278b6960 +8d9d57dbb24d68b6152337872bd5d422198da773174ade94b633f7c7f27670ff91969579583532ae7d8fe662c6d8a3b0 +855a1c2d83becb3f02a8f9a83519d1cb112102b61d4cdd396844b5206e606b3fefdbcc5aa8751da2b256d987d74d9506 +90eb7e6f938651f733cf81fcd2e7e8f611b627f8d94d4ac17ac00de6c2b841e4f80cada07f4063a13ae87b4a7736ca28 +8161459a21d55e7f5f1cecfc1595c7f468406a82080bfa46d7fb1af4b5ec0cd2064c2c851949483db2aa376e9df418e6 +8344ccd322b2072479f8db2ab3e46df89f536408cba0596f1e4ec6c1957ff0c73f3840990f9028ae0f21c1e9a729d7df +929be2190ddd54a5afe98c3b77591d1eae0ab2c9816dc6fe47508d9863d58f1ea029d503938c8d9e387c5e80047d6f1e +856e3d1f701688c650c258fecd78139ce68e19de5198cf1cd7bb11eba9d0f1c5af958884f58df10e3f9a08d8843f3406 +8490ae5221e27a45a37ca97d99a19a8867bcc026a94f08bdccfbb4b6fa09b83c96b37ec7e0fd6ee05f4ae6141b6b64a8 +b02dbd4d647a05ac248fda13708bba0d6a9cd00cae5634c1938b4c0abbb3a1e4f00f47aa416dcd00ffcdf166330bff9a +9076164bb99ca7b1a98d1e11cb2f965f5c22866658e8259445589b80e3cb3119c8710ede18f396ba902696785619079c +aacf016920936dae63778ad171386f996f65fe98e83cfcdd75e23774f189303e65cc8ad334a7a62f9230ed2c6b7f6fa4 +a8031d46c7f2474789123469ef42e81c9c35eb245d38d8f4796bba406c02b57053f5ec554d45373ab437869a0b1af3f0 +a4b76cd82dc1f305a0ee053e9a4212b67f5acc5e69962a8640d190a176b73fbc2b0644f896ff3927cd708d524668ed09 +b00b029c74e6fdf7fb94df95ef1ccad025c452c19cddb5dccfb91efdcb8a9a1c17847cfa4486eae4f510e8a6c1f0791a +9455e5235f29a73e9f1a707a97ddb104c55b9d6a92cc9952600d49f0447d38ea073ee5cf0d13f7f55f12b4a5132f4b10 +ae118847542ed1084d269e8f3b503d0b6571a2c077def116ad685dcca2fca3dcb3f86e3f244284bdcd5ae7ac968d08a5 +8dcb4965cd57e8b89cd71d6fc700d66caa805bfd29ab71357961527a7894e082d49145c2614b670dcb231ab9050d0663 +add6ed14f3183f4acc73feea19b22c9a330e431c674e5034924da31b69e8c02d79b570d12ef771a04215c4809e0f8a80 +96ae7e110412ee87d0478fdbdbaab290eb0b6edd741bb864961845e87fd44bcbe630371060b8104d8bf17c41f2e3fca0 +a20db17f384e9573ca0928af61affab6ff9dd244296b69b026d737f0c6cd28568846eca8dadf903ee0eecbb47368351d +937bfdf5feb0797863bc7c1be4dcc4f2423787952a3c77dfa3bfe7356f5dbcc4daebde976b84fc6bd97d5124fb8f85c9 +a7050cc780445c124e46bba1acc0347ddcfa09a85b35a52cc5808bf412c859c0c680c0a82218f15a6daeefe73f0d0309 +a9d9b93450e7630f1c018ea4e6a5ca4c19baa4b662eadfbe5c798fe798d8a3775ed1eb12bd96a458806b37ab82bdc10a +a52a4d5639e718380915daaefad7de60764d2d795443a3db7aeab5e16a1b8faa9441a4ccc6e809d8f78b0ac13eef3409 +8e6f72b6664a8433b032849b03af68f9376b3c16c0bc86842c43fc7bf31e40bc9fc105952d5c5780c4afa19d7b802caa +a107ae72f037000c6ee14093de8e9f2c92aa5f89a0a20007f4126419e5cb982469c32187e51a820f94805c9fccd51365 +9708218f9a984fe03abc4e699a4f3378a06530414a2e95e12ca657f031ef2e839c23fd83f96a4ba72f8203d54a1a1e82 +b9129770f4c5fcac999e98c171d67e148abd145e0bf2a36848eb18783bb98dff2c5cef8b7407f2af188de1fae9571b1c +88cc9db8ff27eb583871eeeb517db83039b85404d735517c0c850bdfa99ae1b57fd24cf661ab60b4726878c17e047f37 +a358c9aadc705a11722df49f90b17a2a6ba057b2e652246dc6131aaf23af66c1ca4ac0d5f11073a304f1a1b006bc0aa5 +ac79f25af6364a013ba9b82175ccee143309832df8f9c3f62c193660253679284624e38196733fb2af733488ab1a556e +82338e3ed162274d41a1783f44ae53329610134e6c62565353fbcc81131e88ce9f8a729d01e59e6d73695a378315111b +aa5ddcabf580fd43b6b0c3c8be45ffd26c9de8fa8d4546bb92d34f05469642b92a237d0806a1ad354f3046a4fcf14a92 +b308d2c292052a8e17862c52710140ffafa0b3dbedd6a1b6334934b059fe03e49883529d6baf8b361c6e67b3fbf70100 +96d870a15c833dddd8545b695139733d4a4c07d6206771a1524500c12607048731c49ec4ac26f5acc92dd9b974b2172c +8e99ee9ed51956d05faaf5038bffd48a2957917a76d9974a78df6c1ff3c5423c5d346778f55de07098b578ad623a390e +a19052d0b4b89b26172c292bbf6fd73e7486e7fd3a63c7a501bbd5cf7244e8e8ce3c1113624086b7cdf1a7693fdad8b5 +958957caf99dc4bb6d3c0bc4821be10e3a816bd0ba18094603b56d9d2d1383ccc3ee8bc36d2d0aea90c8a119d4457eb4 +8482589af6c3fc4aa0a07db201d8c0d750dd21ae5446ff7a2f44decf5bff50965fd6338745d179c67ea54095ecd3add4 +8a088cc12cf618761eaa93da12c9158b050c86f10cd9f865b451c69e076c7e5b5a023e2f91c2e1eed2b40746ca06a643 +85e81101590597d7671f606bd1d7d6220c80d3c62e9f20423e734482c94547714a6ac0307e86847cce91de46503c6a8a +b1bd39b481fc452d9abf0fcb73b48c501aaae1414c1c073499e079f719c4e034da1118da4ff5e0ce1c5a71d8af3f4279 +942ae5f64ac7a5353e1deb2213f68aa39daa16bff63eb5c69fc8d9260e59178c0452227b982005f720a3c858542246c8 +99fea18230e39df925f98e26ff03ab959cae7044d773de84647d105dfa75fd602b4f519c8e9d9f226ec0e0de0140e168 +97b9841af4efd2bfd56b9e7cd2275bc1b4ff5606728f1f2b6e24630dbe44bc96f4f2132f7103bca6c37057fc792aeaab +94cdad044a6ab29e646ed30022c6f9a30d259f38043afcea0feceef0edc5f45297770a30718cbfec5ae7d6137f55fe08 +a533a5efa74e67e429b736bb60f2ccab74d3919214351fe01f40a191e3ec321c61f54dd236f2d606c623ad556d9a8b63 +b7bd0bb72cd537660e081f420545f50a6751bb4dd25fde25e8218cab2885dd81ffe3b888d608a396dfcb78d75ba03f3f +b1479e7aa34594ec8a45a97611d377206597149ece991a8cef1399738e99c3fa124a40396a356ab2ea135550a9f6a89f +b75570fc94b491aef11f70ef82aeb00b351c17d216770f9f3bd87f3b5ac90893d70f319b8e0d2450dc8e21b57e26df94 +a5e3f3ab112530fe5c3b41167f7db5708e65479b765b941ce137d647adb4f03781f7821bb4de80c5dc282c6d2680a13d +b9b9c81b4cac7aca7e7c7baac2369d763dd9846c9821536d7467b1a7ec2e2a87b22637ab8bbeddb61879a64d111aa345 +b1e3ee2c4dd03a60b2991d116c372de18f18fe279f712829b61c904103a2bd66202083925bc816d07884982e52a03212 +a13f0593791dbbd360b4f34af42d5cc275816a8db4b82503fe7c2ff6acc22ae4bd9581a1c8c236f682d5c4c02cc274cc +86ba8238d3ed490abcc3f9ecc541305876315fb71bca8aaf87538012daab019992753bf1e10f8670e33bff0d36db0bf0 +b65fbb89fafb0e2a66fe547a60246d00b98fe2cb65db4922d9cef6668de7b2f4bb6c25970f1e112df06b4d1d953d3f34 +abb2d413e6f9e3c5f582e6020f879104473a829380b96a28123eb2bdd41a7a195f769b6ac70b35ba52a9fee9d6a289c3 +88ec764573e501c9d69098a11ea1ad20cdc171362f76eb215129cfcca43460140741ea06cee65a1f21b708afb6f9d5b0 +a7aaec27246a3337911b0201f4c5b746e45780598004dac15d9d15e5682b4c688158adffdef7179abb654f686e4c6adc +a1128589258f1fbfa33341604c3cb07f2a30c651086f90dce63ae48b4f01782e27c3829de5102f847cde140374567c58 +aaf2b149c1ca9352c94cc201125452b1ed7ca7c361ed022d626899426cb2d4cc915d76c58fa58b3ad4a6284a9ae1bc45 +aaf5c71b18b27cd8fe1a9028027f2293f0753d400481655c0d88b081f150d0292fb9bd3e6acabb343a6afb4afdb103b5 +947c0257d1fb29ecc26c4dc5eab977ebb47d698b48f9357ce8ff2d2ed461c5725228cc354a285d2331a60d20de09ff67 +b73e996fa30f581699052ed06054c474ebdf3ae662c4dc6f889e827b8b6263df67aeff7f2c7f2919df319a99bdfdceb1 +b696355d3f742dd1bf5f6fbb8eee234e74653131278861bf5a76db85768f0988a73084e1ae03c2100644a1fa86a49688 +b0abca296a8898ac5897f61c50402bd96b59a7932de61b6e3c073d880d39fc8e109998c9dba666b774415edddcff1997 +b7abe07643a82a7cb409ee4177616e4f91ec1cf733699bf24dec90da0617fe3b52622edec6e12f54897c4b288278e4f3 +8a3fae76993edbc81d7b47f049279f4dd5c408133436605d934dee0eadde187d03e6483409713db122a2a412cd631647 +82eb8e48becfdf06b2d1b93bf072c35df210cf64ed6086267033ad219bf130c55ee60718f28a0e1cad7bc0a39d940260 +a88f783e32944a82ea1ea4206e52c4bcf9962b4232e3c3b45bd72932ee1082527bf80864ce82497e5a8e40f2a60962d0 +830cf6b1e99430ae93a3f26fbfb92c741c895b017924dcd9e418c3dc4a5b21105850a8dd2536fa052667e508b90738f2 +990dce4c2c6f44bb6870328fba6aa2a26b0b8b2d57bfb24acf398b1edc0f3790665275f650884bd438d5403973469fa2 +a2e5b6232d81c94bcb7fed782e2d00ff70fc86a3abddbe4332cb0544b4e109ae9639a180ae4c1f416752ed668d918420 +b4cdf7c2b3753c8d96d92eb3d5fa984fef5d346a76dc5016552069e3f110356b82e9585b9c2f5313c76ffaecef3d6fd8 +83b23b87f91d8d602bff3a4aa1ead39fcc04b26cf113a9da6d2bd08ba7ea827f10b69a699c16911605b0126a9132140f +8aae7a2d9daa8a2b14f9168fe82933b35587a3e9ebf0f9c37bf1f8aa015f18fb116b7fba85a25c0b5e9f4b91ba1d350b +80d1163675145cc1fab9203d5581e4cd2bed26ad49f077a7927dec88814e0bed7912e6bbe6507613b8e393d5ee3be9be +93ddeb77b6a4c62f69b11cf36646ed089dcaa491590450456a525faf5659d810323b3effa0b908000887c20ac6b12c80 +9406360a2b105c44c45ba440055e40da5c41f64057e6b35a3786526869b853472e615e6beb957b62698a2e8a93608e13 +93bfc435ab9183d11e9ad17dac977a5b7e518db720e79a99072ce7e1b8fcb13a738806f414df5a3caa3e0b8a6ce38625 +8a12402c2509053500e8456d8b77470f1bbb9785dd7995ebbbe32fd7171406c7ce7bd89a96d0f41dbc6194e8f7442f42 +aab901e35bf17e6422722c52a9da8b7062d065169bf446ef0cbf8d68167a8b92dab57320c1470fee1f4fc6100269c6e2 +8cad277d9e2ba086378190d33f1116ba40071d2cb78d41012ec605c23f13009e187d094d785012b9c55038ec96324001 +85511c72e2894e75075436a163418279f660c417e1d7792edce5f95f2a52024d1b5677e2e150bf4339ad064f70420c60 +85549ca8dcbe49d16d4b3e2b8a30495f16c0de35711978ada1e2d88ad28e80872fca3fb02deb951b8bcb01b6555492e4 +8d379ab35194fe5edf98045a088db240a643509ddc2794c9900aa6b50535476daa92fd2b0a3d3d638c2069e535cd783b +b45cfebe529556b110392cb64059f4eb4d88aaf10f1000fdd986f7f140fdd878ce529c3c69dfd2c9d06f7b1e426e38f3 +ac009efd11f0c4cdd07dd4283a8181420a2ba6a4155b32c2fed6b9f913d98e057d0f5f85e6af82efc19eb4e2a97a82df +b2c2cdffa82f614e9cb5769b7c33c7d555e264e604e9b6138e19bcfc49284721180b0781ecbf321d7e60259174da9c3c +95789960f848797abbe1c66ef05d01d920228ca1f698130c7b1e6ca73bfda82cee672d30a9787688620554e8886554ee +98444018fa01b7273d3370eeb01adc8db902d5a69b9afc0aa9eadfeb43c4356863f19078d3c0d74e80f06ecf5a5223f4 +87d20b058050542f497c6645de59b8310f6eeec53acbc084e38b85414c3ea3016da3da690853498bde1c14de1db6f391 +a5c12b3a40e54bee82a315c503c1ce431309a862458030dde02376745ec1d6b9c1dbeea481ae6883425e9dae608e444e +b9daa3bf33f0a2979785067dcece83250e7bf6deb75bb1dbbab4af9e95ddfb3d38c288cbef3f80519a8916a77a43b56c +b682ec3118f71bde6c08f06ea53378ea404f8a1c4c273dd08989f2df39d6634f6463be1d172ac0e06f0fa19ac4a62366 +a4f94fd51ecf9d2065177593970854d3dce745eebb2a6d49c573cbf64a586ae949ddfa60466aaef0c0afb22bd92e0b57 +86cd5609efd570c51adbc606c1c63759c5f4f025fcbefab6bc3045b6ad2423628c68f5931ff56fdda985168ce993cc24 +981192e31e62e45572f933e86cdd5b1d28b1790b255c491c79bd9bb4964359b0e5f94f2ae0e00ef7fe7891b5c3904932 +9898f52b57472ebc7053f7bf7ab6695ce8df6213fc7f2d6f6ea68b5baad86ec1371a29304cae1baadf15083296958d27 +b676c4a8a791ae00a2405a0c88b9544878749a7235d3a5a9f53a3f822e0c5c1b147a7f3f0fc228049dc46e87aa6b6368 +9976e10beff544e5c1645c81a807739eff90449df58ffdd8d1aa45dd50b4c62f9370538b9855a00dd596480f38ebe7a5 +a0e91404894187ec23c16d39d647ada912a2c4febfd050a1ea433c4bfdc1568b4e97a78a89ba643aca3e2782033c3c58 +91a6ea9a80476ed137eb81558ff1d55b8581663cccd41db4fc286876226b6515fd38661557419e1e46b6a3bc9cda3741 +b9e8a1e23c60335a37a16f8085f80178a17d5e055d87ffe8cf63c532af923e5a5a2d76cf078164fb577996683796caa6 +ad8e151d87a37e8df438d0a6a7c02c3f511143efb93fde8aef334d218cb25932baf9e97c2f36c633620a024a5626af3d +978f942f210e8a482015e6fdc35a4c967c67b66e6e2a17a05cc7a0f2163aed227b775d4352b0c3cca6cbf4bd5bafaf75 +b5e2e3d8b2e871c07f5899e108e133f87479959b80cb8a103fbecde00ccdbfbd997540eef33079c5cc14b1c00c009fd1 +88a164b3fefd36857f429ab10002243b053f5d386466dbb9e5135ed3c72dd369a5a25e5e2aaa11f25488535e044e2f12 +a66091c0db4e7cf05a089ec2b9ff74744354d0196968201f5e201699144b52bb13b4e68e12502727163e6db96e3565f2 +8e65aff8e37240461b7374c20bfd1d58b73a525c28994a98f723daed9486130b3189f8efe5c5efcd7f5390cc366038da +8b37c21dd7304c3aa366959ba8c77ea8b22164a67e136808b6f8e48604297f7429a6c6ecf67b1d09b8b7ec083eacd7e0 +b689b1277ad050f53da91a702516a06d7406ff33a4714ea859b3b2b69f8d0aa8f983c7e039b19c0759a3815d841fa409 +b17f7a0a182ed4937f88489e4c4e6163dcf49fd2ea4d9efbba8126c743bea951cd769752acd02e921774dc8ebcfae33b +8b7fab4f90be825ac5d782a438e55c0a86be1c314a5dbc3cc6ed60760a8a94ef296391f1f6363652200cce4c188dae67 +ab8410c4eaa2bb43b0dd271aa2836061bc95cb600b0be331dada76ddb46711ff7a4ad8c466cc1078b9f9131f0dc9d879 +9194bd7b3cc218624459d51c4d6dbc13da5d3de313448f8175650fa4cfab7cc4afcda5427b6676c3c13897dc638b401e +980f61a0f01349acd8fc9fdc88fc2c5813610c07eecb6ab14af0845a980792a60dadf13bb4437b0169ae3eff8f5984ce +b783bee24acea9c99d16434195c6940cf01fc2db135e21f16acae45a509eca3af6b9232a8aa3a86f9715c5f6a85cb1c3 +a3079931c4b90966d1faa948db847741878b5828bc60325f5ebe554dcab4adcc19ee8bce645e48a8f4a9413bb3c6a093 +801f61ac9318f6e033a99071a46ae06ed249394638c19720831fff850226363a4ae8486dd00967746298ee9f1d65462f +b34dbbed4f3bb91f28285c40f64ce60c691737cc2b2d2be5c7d0210611cd58341bb5bda51bb642d3ee2d80882e642a13 +8750af19abfb915e63c81542b13d84526a0c809179bbcc1cd8a52b29f3aba3ae0f7cf6f4f01790bf64ef7db01d8ee887 +a6ea10000eb2dd4efc242ac95bc3b3873cdd882fbeb7c9538c87e3143a263ca3a2e192b2159316a625cfb5fb0b6cdcb3 +aa40ca54bc758a6c64cb932924917581062e088b3ad43976b28f2e11d8a7dea73f1fb50aeaa0e70182bb2dc07d805bb9 +a4779dfd25b5ec9d75dfb54a4bb030364899a5e75c1492403acb19f2adc782c7ac4daeb66d2f5aeb74135afe9f318e3f +b4551e2805d63ca453f4f38b1921ac87ff687e1d70575ad38f3469d6f0608ef76b7b1b98ae1e6b1e7d928773aaab6e3b +99490ee722f96aad2743b08dd37bfeb75a8c59efaee4c9b694eaa05eb8a6bb23861a4480544c7617d04d23fd5e2543b4 +8a7050d964d295fff98ae30d77ce730a055719313457e773fcce94c4d71a9b7cf63db67e54a8aab20fb1335b0130b5d5 +903144e6bbee0a4fec17ff80fef0d2103981140c3d41776cfb184ced17f480a687dd093f6b538584327e6142812e3cd5 +a5b30f7c6939bdc24a84ae784add927fec798b5a5ee3dd156c652df020728dd6d43898be364cf5ee181725fbcffc0964 +b43d97ec2bc66af92d921a5c5c20a03ef2be2bc2c9b345f46d8287409fcbfd88ebc49d4509d64468222cd1d2021bf236 +82dc23c7f5086c9ac6b4566359bfb830d203544b0d8332a210775670f899cd9ff48b94bfeba40040c25664ebdd5cfad8 +9294cd017fea581dabb73dcc8c619904d7e022b664b0a8502c9d30f3807668af279948e7e41030ae296d492225297e95 +8d6c9dc636c8e884f9a4299e5cff06d044ebc94ad783a4b71788347ea4a336d4d048b8a9ecabae789e8fcdc459723dfb +801a80bc49e882ec81b04e37407713f033f7bdac79252dfa3dc8c5bd0229fcbd4019890e402cf843b9378df08f72ab84 +b4313ca32569d973900f6196363c0b280ddfa1b47c88d019e5f399b805b444a777950fc21ae198fc23ece52674b94abf +96f06056fd255fdabf78986e315e7c4fdf5495cf850536b7976baa97a994cc6a99c34609c33a0f2facba5e6f1026dce6 +983ed80220a5545ffd70ef5e6ac10217d82ec9cd8f9a27ee77a5ff4074092308c0e6396fc4e9932a77ddd474e61f8b55 +872a059aa630af73c4abbd076e8b333a973ffc5bdecf5dcc0600b00162184213cb19d4f601795030033beb808d5810ce +b040f318d9d3b8833da854014a44296dbd6762dd17cab13f91987256c54353b7f0800547cb645a7cc231997454209fdd +a8c4731a555308e8ce0b8325eb7a4cbf6113d07e9f41932df04480b72628d313b941c7055f1cc2ac45c7353b56e96ca9 +8c24031440b77637e045a52e5ea3f488926ab0b426148975edf066c40a4581beecc1bfb18fc4cf5f9f96dc6681b4bd28 +b39254b475abf342f301298feaa17a4b3051f30ea23a18acf59e003e2704ac96fe40691f1da387913bdf7aee6389f9a8 +a1dbf938b604ccc6d60881cc71f38df568aa02752aa44d123514154017503f6c1c335ae43e359f1487bc8934073cd9c1 +8d52aa1be9f429ece0580498d8fe9fef46d4a11f49436a82b8927f9503dacc41245907f126594c1cd30701286f8c092c +b826f396486942c0326d16f30a01b00a682c30a75553dc6ac34fd5b3e96b13c33b94738f522eebaffb59ff8c571c76e9 +aa89f51cbf6e6c3e2aa2806187b69ab3361c84e89f393f3ed284fe84db46fc3944aa44f8928e3964f9c1a1ec27048f68 +a254df0efa4203fb92b42a1cd81ca955922e14bf408262c8f7cb7dc703da0ca2c71556bd2d05b22ce9a90ad77309833d +93263c507e4d5f4e5df88e85b3d85c46ea729fb542a718b196333e2d9fb8a2e62dc1347cf146466a54ba12d200ef09d9 +922e3c4a84246d89a07aa3e90f02e04b2cea9bebc0e68b742156f702aed31b28c6dfa7ac936ea2fc2e029adf68361f98 +9a00628eeeda4ccbed3ef7834149aec4c77aac1a14bc2491ba5d1a4a2c5d29afb82ceaa5aac1c5ce1e42cdcaf53e30ba +ab3a88df36d703920f6648a295a70ffa5316c96044f39ff132937bfda768937cb6a479e9ba4a4e66b377f3a9996a88c4 +966b11526ab099d550ab33c6a9667e5cfdedf255da17a80a519d09acd78d2ea24ec18bd1ea7d8d63cf0a408f1c1fe0b3 +b5c21b9817dc32f3df9d9988aa3560e1e840d586d01cd596bc0f850ab416b6013cbf7dbfd05ac981f26014c74bd2d2b2 +9040abef5e2523e7f139c9f744a64b98fea3a57952059ffe4d5ed77fa87068203c090ef4e7f52c88fb82ea8a6fdca33e +a0dcdaeb7d3f5d30d49c004c5f478818c470187f4b0b4856812dcd1b3a86de58a99acb8ceb44c6b80c3060cf967c43a4 +b5f4be9a69e4a6719ea91104820df8623b6d1073e8ee4168de10a7e49c8babea772bcbc6b0908185e98d607e49cd3609 +8634020a5a78650015763c06121c606d2dd7b324aa17387910513dd6480fb797df541fc15b70d269b2794ad190595084 +9504d1d0fb31ff1926c89040c04d51fd1f5cddf9d7ca3d036e7fd17e7a0f767ef33cee1d8bf7e17e2bc40949e7630417 +812c72846ef6d692cf11d8f8c3de8fa78cc287303315114492667b19c702cd24d462020f1276895df26e937c38f361f8 +8c97aa5e9ef2aa9a1435ef9ddfe62e850f0360864ed5fb82bf9fef4ef04d8fb4f827dc078bc911ee275e4501edd6617c +ac5f7af5e23c8e429aaa6b6825129922b59d25b4608f07b65f21388a9ac3aa89096712f320afe6d56e44e1f0d51a4eb9 +a8c84d9a8593a0cb5be1e450960f59878a4e6b70da54a7613dfc25911b7cc9e6d789d39401b0a0d6471ab9dcdc707976 +8c9d5fd89611392c0f085ffa4fa642a181f0b9b23593deb5e10fdd1642722ca75ef34a037e88a8d03f2888fe7461f27c +8c74b05f91fb95c85e7bd41f6d9a1e41e667e68f3d19b325c1f25df1767019919edab89b92af237896cbc4e6d6dc1854 +a3caecb91640821f0b2c4981b23f2069df8d2b98ce026c1538bc096b292f5f956a5d52c1c8d6a8165a1608083ba6494b +8ae8e0c36f8b79a69176ff29855df45d0fcd9e4d1dbaed8899f8fcdece676e418ec034a6c161e2a894f0c834aaecbfd1 +b88d18c67dc3b1b6ed60ee437c441c1ed14ecddebccf43683605716f30058b1aa4ba05ff10cd8171ee97d8f58d70c094 +94f43d84dcdfd9cd19115c7d8e9c1e856828eafbfdec93b876cf0007e317e30b2ad951dbabc186aa6ef90fdee4d91990 +b44e4723f41fc1d5b0057f371e3381ae02566590b3f964b6eb07b2104f66ff78410c407235fa98d04f635694f3baca09 +addd8390173d29ca0811534d389253831fed75fed135398617836b6e70767269eacb1560b39a58f02042ca3b97fe59c4 +80bdbdacc0c358c7ea52aeacdc5f9ceb6928bcf6e7dee7c17d8ae3bf7c2372aa7a0372363888968fc0921aaf4776d5d0 +a486e2b6f04f403f9e609d69dfb3cfb992af56ecad1683271df3e3faa3b86638b81e73b39978fb829ee7133d72901f2d +a19472da57457e10c6a6307895393ddaec8f523760d66937fe26a025817319e234eaf69756ffdf1b84c81733424a96d7 +ad6a195397cbc2d75171f5e82090441eed60bd1ba42c39ef565b8b5a8281b04400678625b1dc46d617f694a7652a8e5d +8f98e721c06cec432e2221f2e1b06bb1469d916a8d88d6973acf68d1e003441d00390dafcead8ecdbf9eae4509baf5aa +91d62a0f9d13c59adfe1376ed6d057eae244d13c6b3d99be49a49e0075cf20f4085cf127774644ac93615be9ac9e5db6 +af45dec199245e2b326a0d79c4899ed44b1c0219db42602a4a6184ace0ff831a3276297af28f92e8b008ba412318e33e +8754bde54e8d2d169e6a7d6f0eae6097bc0461c395192bd00dd6f105677ea56ab384c02553ea5eeac0a65adcb0df77ee +b676afd2f5afc37a314c943d496e31b4885efcbcc2061036e370a74cfde5642bb035622d78d693bfc3136fc036c7edb4 +aab6ffe6cc234397cf1822e02912bc282dfb314e92fb5a9e10d0c34ee9b5856d4b76e166bc2bb6fcdd66aabea35ec4ef +ada6e62f90ee6b852ec4b72b22367acac2896f0df2c105beda27096583ddbedddc710d171330569f111c6e44a5b57ae7 +802139dd15241a6de663d9b810121bdd9cf11f7f8c8ca6de63f4f8e731409e40d1fd3558b4f619ed42ee54929dff1c7e +ad8e70531cec21b4e6f55be1751c2d025bd2d7d8158269b054cfe57fa29252d052ce4478ec7db6ec705789e2118d63b3 +a8e4a4271769480e1b33a28c87a150ecc0b48bfe8a15ae04152197881de4ce4b03453aefe574842424edbbe4173e1a3a +b98c65726296610cef16c5b58da5491acd33bd5c5c5af4d934a9840649ef85730fbce8018dee09ded14e278009ed094a +8e213a7861223287b860f040e5caaa563daa0b681e4e09ec79ad00cc459238e70bbeaf7486bbe182fc12650700034ec5 +a2879f9e1a556cf89b9b5b3bd8646a8cce6b60bcbc8095df44637f66a2da5858eee2dc9091475a8f64bb5aff849389cd +8a17cdb4077b9b0bcf28b93294ac5ae4c8bba8839fce0f1012b53187ac008f9858b02925fbfc421f1123afcdbd8b7753 +86fd9c11528aa43946e4415ff64a3ca6409ee6f807368c68997b18605da65e415ccd85ad913820d450cb386593de666d +8ed55923b963c3d85a91aca11c40ff9c6c7f1e2b9bc199d1a270e5fb16aa62dec0136e97866145ae9d58a493e8b1cbbb +ae32af5b5d418668ae123c639b149e5eed602404e8516da4a61db944b537a3620545e8e3d38cf10cdaea980ab2f80973 +95cb8d9e9d6762d78dde0ad73869ffaca904a7d763a378b8cc11a7933d3e7d1c8aec4271a079b1b00f8887ee5b1ea21f +b5ea20b42a3ca247f00ab5328c05f0cf194973d5f7271c66c41c5055b1ffdca136be179709e0c1de209fbe07b9820bf3 +98682f7cce471c92a8d6d15fee4ddf4d43dd97c3e3811d2913618ecacc6440b737717c07736ae4558c910e11ee98104e +a67da2c7cbba48e929ca4e4b9a6299fe01ef79eff8cc5cd3fdbdc0721a68130e4079f30ae151a573a7dcca8ecf2e684e +a9981c9f9dcbb3b0f6996f664fb2acd7573189f203be37b2b714662aa273551396abfb1f612ccde4e4c8127a050dbe4b +92d55eff8da600f886da9bf68e8eecf482faa4b268f3f286b3b3e5cc91b19604081498d4905b201bb4ec68e32b5591d9 +963e3f1728de9d719c86d390f3eb9c3f99d1928347fab0abf10dbb37d76b59ddb64d4734c977863a6cd03ffece5ca895 +93480e2de83c921056b6d8628ac37cd5ef7555ba43b0308fc13386cb0515d42c12ecd06057137aa71a7931beaf90b9ce +8feae57ff0e6a162cc81c99f45c6187d268fc0bee8c2bffc92142ef76c253d201f0e932943cf2fa312982b281ce1066b +8f8f4bd4200fb87afcd743274480220d77571928000d4197410dbb75439d368df6a06d941a6152206371d2ca9cac99e4 +8ee7f11e79af4478e0a70eb424fe8078237ad99ba6d7e6bf1a8d5e44e40abd22d404bd39b718ad6fdf4c6601f2a47665 +a98acfcec612b574943195b9ba95bebcc9c0b945c9f6b3e8760b2a4635909246a9d73b0b095c27b4ecb3339704e389b7 +b520efd19f65e81dc285031ea3593f8c5dad793e4426beb9196ab46e45346f265fd71e50adb0da657977c60ed5724128 +a3d9d0b7415280ce4dfa2429d47b2b8e37604a5157280a72cc81d541ffe44612dbb3ef7d03693fc42a569169d5842dc3 +8c29e2d0b33801f6d9a9c065a76c5cad1fb0a001506b970307e21765ee97c732a4cbf1d7c1b72d95e0ad340b3b075224 +839e21f292892a6eb596b9b1e9c4bd7c22a6fe71d3d04487c77840028d48392c5cbe73140a4e742338e0c8475cd0c1ad +8bea5c68e7743998619185bb662e958f1b4d3ca81019d84ac43c88911aab3abe4ee9bcc73cb95aa3ae87c0138801bde3 +b8f262d21a94604049e008ce03dc857848168e1efca4522acb0ccc827ffb37f545e1947843a356563a76bc6489605b66 +a7bd0842b0bb38d9943b82aa883f36f4eb8a6e8a7790d4f87faf306608f51d250a19b73984f1156cef5dd2581664614b +a993e649bd953627a88a2539dac3a12ec7f37a4c65b01425d9d34edf7ee10a71aa98f65c9e013107f824faf8aee041a9 +8e07eced75c67cb4d2ec01857f6ac1408482e6b31cb2faa249e8cf99f180575587df530c7782a7539b5221121ef48aa0 +b2f4578f26c05ecb9e2669ca744eb19d4f737321ac7d04fafd18beb7866e0fec9dd063953ae1f077b44b9c6f54db1279 +b6b3788a6c7bcaf467d19daf6ab884d549aa866970c05a9181f544ff190d043192c84fe437a75a30b78b425461cca062 +a270684903c61544b85a7041e81f65e787e1c1e23e57538fa8a69836bed0ca1673861dd29f743a1280f2f38eddd3aa83 +a9c2397c4773dcad2821266dadfd2401d013d9f35de6744f2ec201f3507700adb1e6ec4f5a453be4764da8bf68543f26 +83a3025ed6fd5df9d98be32a74e10a0d9728b560942d33ba028536fb148fc34ae87e92be2df3e420a8dfec08da495982 +90dc70c183a90bab988b4a85b7b921c8070af0e5f220364fe11afa0722990b2c971e1e98eef62d3287fedfd9411f1df7 +82d940937a6c636224d04f8e2536f93dcf20dc97a5f188875ad76c21b804aef9af10839419b61143c1f88a695959a6b4 +8017f9473ce49d498d6f168137e77e62fe553e5a51e75b519cf2cbd1ab9afdafad80fd5e6fd0860e640b0d78ca8ed947 +80573a0ec049fe1f7b3013b2839e145cd87e07c0e43826a29ef8c92516f9a30896c2ffcf3ed77ed22a6cf3101b1789d5 +953349abd2559f9824db07cec857ad54f1a05018f3076425f8dbae37f8d92a46af2c04ab7c8ec0250449541187696e98 +ab7bd2c4f05ee9a9f252c4e16a20993a12c535c3809d124bae24642616521a9768d3f19eceaf8524583f47ae1f527684 +9883b77ee834ee0112ca2f366d2a6fc213e0cf454e061438c2901a5ba35b7378f64da8adf6a476eb1562991ef5b4a5bc +89291811db308637356dbf7ed22cf07bfce33eb977734ee346e8c15a231b35d8b4443574f3fa97a40867b3e23b0bbfa4 +93d753849d7d9588d39e38217500b123a6b628a873876612d9f98b5d611f52c89c573432d2176752b5d1cc2d94899b8b +a45add3c4844db3b7a237295fc85fddc788ac1ec395a0524d2fc90a539571a247146aea4aa10eec30a95e9617c85b98d +90f94578842db7a4de672da1e483858ece5e466c73c12f725a0fc71f42ff880c9447a33fa9096839bee817536f2591e2 +b2c1b6fb031bb30460f157356562b44b4de096a0a112eab4fb3cc500aad38bc770da1fc2e73caf687a0da5e8537049c0 +afb15e15fd930929c0e3c66482068a5afe0c7b7f82e216a76c5eb1113625bfa0b045a52259d472284cfbaf4796c71456 +ad222a9a3d907713418c151b8793d5e37634354322068f8206b9d0da1a3f53b0004193713d23ec35990639a1b6c2e075 +b44a128dce97e8c4b178cdbca0a5c1b3f6e164490fac0fd68dbfe0aafa89920bb4ea420a8527e06c80dd19c2f135e3ef +8596e993ef18b8d94e9c42a90cb7060affc586b8e9b526820d25124285de5590134e2e86592e9dc4dd45ccf5d578fa60 +b71bb0ad138141ed506b2253e84110d2db97cc2d24a3fd0d096b0022d9f38f87aa74e2f505074632d64e90bcc491aa30 +84841eafd357309de47b92ca5ec163dec094a2e5271bc65898c31932e0160bee165e4decb23af339cfe09c83e1cc5441 +8a2915ee39a6fd4a240b98533d7690ef1773ce578ed1fb05ed414ebe36f7ef289fa46f41768df57190438c356331e329 +90bb337165386f1990cbd8ed2e8321ef21bc18125b015b4da0c37e5fcc446b26005379ee4fad8ce9348ceb4ab49e82e2 +b707b50ea2ab05c6d183671587f25fe29eef23fe569d731459a1ac111a0b83a2cd65b88242876b34aeead3b05a15d745 +ae1f159f79b7996315c4f9acce7e21a6ed59d4ef76331196fc86911fda3035edd5c11d568b105175a36c948d0263b382 +922bc525bace05e5dff6b5cabde5469ddd2c1c601f7131abc04ecefdd35095e6ac015b1aec3c3b25c5dee8d139baf60d +a7b060405b2740f82db64683187b1bb89e5f40c8438663c7cbc8ef2513929fe5f92625667a7f2f599a72a96b1fc8f08a +b9dfe94a08651db5efefbb813269bce80d814e3089b80c0654491e438d820bf521f8a4a4477909344ba88f7683eebb43 +841817a9729465743576950b6e8eea32ebf39cca99ace86c4792f9f35926e2d6830c52854a3b2eaeb61694e6845008bd +934128034bde8fc7b93b952aa56e0ed28b36cfa04cfa1f0d5b38266dd40beedff5e0bab86e4717b0fb56c56be2eae26b +aee9d64caf28596308782cd8f3cf819506daf3378f86157ff775e618596411adf94efd0e9542787ca942066f02cbd332 +85871184db314411a49575fee088c52ed5dba4e916ee001ec24d90898a0154d9790a06aa8a707ca7a8b986c0293b8d89 +8d3d87edcc0187a099c97b581a598d357a41ac152303bb27c849eb78e72e15cb97cf9a0468fc36f245c3e152c76bb7dd +900475d165dec18b99eb7b5f9e9ad1d2d4f632e55fdcc4c5ecd7775fed462990e6aaafe9c669f40508f9b15f00bda31f +a25b5954edd57e7811a0d18532043d975c7b44b80f65cd630935d7b16ada05f30fe2b7be7ae8a2f54c25957faf3f1950 +a089019afa3a7a15f7e7874e73b6773c0a824e6d3379b4c928e173321fb165ad979a6be004d394c28d19d410b2655d3e +b28f46797dee0c538bd3de815df641a0ef718ad3e52b2764aec380d6905b38b50ad6f60d0f68e096ca39960ba7734355 +b0ac155d3d05851b04104e6b459f1a68e9e155437c92421a7c0e4dd511ef89cf71dfa3cc920769492ee283a65ebf029e +813c69a810745580d43d5b5480f0ba81000fbef0071e6b655c7346bef5ed774e9214a7816d40eb1774a5bd033767a046 +b176345ca75c64f10ec33daa0dcf1f282b66a862fcd3d8d66c913f9a02db4c9d283dadc02eff13aaab94bc932a42234e +92560f67e5b995db4a489bb86ee78b4aee0800143b3535ad557a53e9e08716bd0202d9f5714722c2a5e8310046e3f5b3 +8adb427bad9cc15fc6c457a96a6750dda8c46d859c5f69bf0e7ab8fc0964430b33967fd47cf0675b6ba1757f91255e6e +b120f723b80389a025b2daa891b140b3d7b8d520ae2a6a313f6e3d365a217af73292dcb249dca1f414ec05e865e3cdc7 +a61a5d261a8dfe5996c42ea0a5ae703a2adcfda80e86837074d868eee16f87d38da19596c48b55dbd7a7cbec1a9b4996 +99dc921eacc6bb867c5825ad4c83bc4af9dd78a18b3d0e1a60ad493e3805b8fb9b7922b577da1adb3d805edfc128d51d +85455fa165a07282aaab4a5bfb88027f47b9532e4af8195c048515f88b0db7e80f42e7a385fd4944faaa7f2a6544ad17 +96dff2d1c8a879d443fe576d46bcceaf5f4551d2e8aad9c1a30883637c91090de99ad5eec228eb5febf93911502d3cbb +a87eb7f439377fb26c6bfe779701f4aea78dd7980b452a386afec62905e75217a1996c5234853432a62ef8bab21c31c3 +b598278293823e9ccb638232a799211173b906444376337fdf044d0227d28fcc4c5867e6ecb3200e59ca0b139e71cac9 +aa6fe147edc95027654d68140f428ec53cede3552c5f49c09d18bc6f6ae8c739a63042eb7291d14d717a4e1f0778abcb +ae8ee18913d328b2fba71efe65526d3ee9c81beda53cf776baec4019ea30212010758cbb5dc85ed6620ce04b189f01f2 +ae9fb686777e88dffdd42805fe4114aa0da1b350d92a27ff3f8a817fb25af1fcfc9a06155affe0273bf13caad16a5351 +95d372ba3a2ee38371538f34aae91b4844488e273f70c02f1992370f89fc2343eff95692d52ce9f21206abbee4959958 +b15260376f0a34ca2827ff53acd7eaaef94c9acc2f244b36500423069cb1cdaa57ac8dd74adb5b53d0fd4265fcbb28ea +b0ffce6a8059537ef6affdbbc300547ef86e00109289239b0c6930456c562b4ed97f2e523963af17736dd71b46c44ac7 +b5499a1277d34f9892f7579731ff53f423f2ffffa9ea43a6e929df8c525e301396249a2324818a6a03daa0e71fcd47b3 +98dbfb8e97a377a25605a7665d4d53e66146204d8953afda661ae506858c5cd77ff7f21f5f10232e06dbc37378638948 +84177e27e6da0e900c51f17077f5991e0e61bff00ca62c1623e627c5aea1b743f86eef6d55b13219a1947515150bade6 +b50407bb5c61b057ab8935df94fd43ca04870015705b4f30ceac85c1035db0eb8293babc3d40e513b6fb6792ecbc27a9 +988699a16917514e37f41ab5c24f4835ed8a2ca85d99972646fcc47c7e2a83c2816011144a8968a119657c4cda78d517 +920c43fdcb738239ad542cb6504ab34498bce892311c781971d7db4dec70e288676de4d8697024b108cfa8757fa74035 +aaa106329aac882e8d46b523f126a86d3cee2d888035ce65c0be4eaae3e92fd862f6ac2da458a835539cccafaba9e626 +96e4c1562d14b7556f3d3e8a1b34ea4addc5a8170e1df541dc344728bcb74cd1630eb7ba4c70e9c68fd23c5c5d5a729b +a616ac5016d4e68e03074273cd3df9693ee0ce3458e8758b117a5c1bc6306dd2c7fad96b1bb37219c57ac62c78ad7a3e +8db7d9b20abfb1445babd484ae9e38ff9153ac8492230d7591e14e3fca7388a5ca6ef7d92ed445c8943cf5263e4a6ad7 +88464134221aa7134878eb10928f31c8bd752ab68c27c9061c1de3f145c85731a4b76acdc7e939b399b6e497f9e6c136 +a5f7c794f70b7c191c835dded21d442b6514bab5e4d19b56f630b6a2f1a84a1d69102d7a0dcca256aab5882d3f30f3ca +b96b6f98b6817b5fa6b1b1044e2411bdf08bf3ffaa9f38915d59e1d2b9bed8b3d645eee322ee611102ce308be19dbc15 +92c26ade2e57257f498ac4ff0672d60b7ea26dad3eb39ed9a265162ccd205c36b882dba3689758c675f29e20836b62d9 +8379a0299e75774930577071d258e89e471951642b98e5e664c148af584d80df4caa4bd370174dae258848c306f44be5 +a0e53beda02bd82bf3d24bd1b65b656238128e734b6c7a65e3e45d3658d934f909c86ca4c3f2d19e0ac3c7aae58b342e +8ca5ceaeaf139188afd48f9bf034d8baf77bbf9669791c7e56ebf783394d7fcdf2a25fa4bdfcddfde649aa0dc67ccccd +a8060e6448844e9db4e9fb4da1c04bcf88fda4542def5d223f62c161490cf1408a85b7c484341929c0f9ce2a1d63e84b +af6e1a5ecf50b754bb9eb2723096c9e9a8e82c29e9dcaa8856ab70074430534c5395534e1c0ed9ce98f4b84d4082fa67 +81c8dbbef98f1b561e531683d5ae0f9b27b7f45dc6b2f6d61119ca0d559bf4ceb676d320afc5aba1811eeef7547a59d8 +85b46cd64d605c7090a2faf1a2aadf22403b3692b3de1d83e38b2de0108d90ac56be35b0dca92c7a41c4b179a3567268 +8dd3cc3062ddbe17fd962c2452c2968c73739608f007ad81fa1788931c0e0dda65032f344a12249d743852eb1a6d52a9 +8630f1707aea9c90937b915f1f3d9d7ba6bda6d7fdef7a40877a40c1ee52471fd888f84c2b2c30b125451b2834f90d3b +b4a747e0bd4e1e0357861184dacec6714b2b7e4ee52fa227724369334cf54861d2f61724a4666dae249aa967d8e3972f +a72de682e6f9490b808d58f34a0d67f25db393c6941f9342a375de9ca560e4c5825c83797d7df6ed812b71a25e582fff +8d5ea7d5c01f1f41fffe282a334262cc4c31b5dcf31f42cc31d6c8e37c9bd2f1620a45519dab71e108fe21211c275b6c +8ccdc7e3642c2894acbf9367f3e99c85963cea46dc5473d175339a2391be57dd8815feacadec766e13645971213b9eb8 +858e9b5fc8c13b651ff8eb92324bdda281db4cf39f7e7bd0472908b3e50b761fa06687f3d46f4047643029dc3e0ceeaa +ae20d36c70cd754128c07cbc18dcb8d58b17d7e83416e84964b71ccff9701f63d93b2b44ec3fddc13bbe42ebdd66221e +860dbf7013da7709e24b491de198cb2fa2ffd49a392a7714ad2ab69a656ca23f6eafa90d6fdc2aa04a70f2c056af2703 +8f809e5119429840cb464ed0a1428762ba5e177a16c92581679d7a63f59e510fdc651c6cc84d11e3f663834fcafeafdd +8d8a8dce82c3c8ea7d1cb771865c618d1e3da2348e5d216c4cbbd0ac541107e19b8f8c826220ca631d6f0a329215a8d6 +86e3115c895ae965b819e9161511540445e887815502562930cedc040b162ecb1e8bdc1b6705f74d52bf3e927bc6b057 +b9833b81a14115865ca48c9c6a3855f985228e04cbc285f59bf163dca5e966d69579ea4dba530b1e53f20bd4dccdc919 +a71f5801838a6dbb162aa6f0be7beea56fadac1a4bcd8113a0a74ab14fc470a03775908c76822d64eb52a79b35530c05 +a77ab73ae94b6d3378884f57eee400eff4a2969aa26e76281f577a61257347de704794761ea1465dd22a6cc6304fbc4a +acd1c5df3c487c04cf27f002e81f2348a0119349b3691012526a7b0d3bf911cdd3accbc9883112ed2ba852145e57fe68 +8a28515a48832ac9eaf8a3fb3ad0829c46c944b4cb28acbcdbca1d0d4c3c623a36cda53a29291b8f2e0ea8ee056b1dee +846bafca11a7f45b674237359b2966b7bf5161916a18cf69f3ec42c855792d967d3bf3f3799b72d008766206bb7a1aa3 +b24b341675b1db9a72c3405bbe4a95ccdfd18fa96f876ec946ccb5108f73e8816019998218a036b005ef9a458e75aeb3 +b99c267b4a09193f3448bc8c323e91ef5b97e23aeff227033fe5f00e19bab5583f6e5fcb472ec84f12b13a54d5c0e286 +a088aa478dbe45973b04ecafbcbd7ee85c9a77f594046545cdb83697a0c2b01b22b1af0b97dd75d387bb889e17f17aa7 +a0c6b0cdff2d69964134a014e36c3709d9e63f6463c5cd7b01b6f0be673731b202d577539d89dd57a888326da1df95af +b4e6dc4ef11b2b41794ece70a8968e56705199d183366759568b6fa845d2cae127486e926b5b27ae9118bb21d1682c1d +a007804353f174098f02540a57e96227232444d5ae0a24232c244647148b6c049848cbd2b50d0a25af3ca9164bfff8ee +873fb034cc39c9cee553ece908fbf315f62efbc412b9afdde6a1889326b7f6f813e050b0601ba9921688e958cb75942e +b5676c90f0106c40d8683299e59d564f505ec990230cb076caef3ae33f2021e6aa5c9b27bb8fead05fc076df034c28f5 +b5a67fc4c5539ad1ddf946a063110f824f7f08d2e4d30762c9d437748c96c9147a88efc22260573803ab545c18b108f2 +817ff2b748a949973a91b69b0ec38efbd945aeb26a176d19f0fb76e261c7526c759e6f5516f9ed34de6eb1ac7838c9cb +99b76bda3526a5d841e059010fdb14eb2fa035a7d10463373a062a98c3c1a123e2da0848421dd7546d776438fd05e304 +aa0d363270f90d56bbee7ea577b0c358532bda36d9247af6c57d000044a97ba41e35bb0db438f4c94551c6350e4e0674 +acdae205d05f54b9544be96c9032350511895ccf413dbbc56d1f03053185df22a6d5b7ffcc3fbe96c3e2ce898ccfa73e +b091c220a1de18d384f50dd071dca4648ca4e708162c52a60e2cedc0188e77c54639f75bce9a468a64b2549119c07ded +878676133e5c700b1d4844564fa92a9930badb5293d882aa25ee6721a9f2cfab02088c31d62cf1342ae3edaea99a1ea0 +9756d0793e6aba3b4dff48100bb49a5ec08ec733f966cb438379b91caf52fc2a5930830ec3f49aa15a02c82c1914dc7a +9722f760184d3b2d67cb2cea7fa41b1ff920a63446006bd98c6347c03d224d2d8328fa20ccd057690093d284b9a80360 +b5a68489de4f253715a67f0879437bfe8f4dfc4e655ca344848980e6153b1d728acde028bb66fd626fa72eedd46ff683 +a8cfc900b34835d9fd3add08044636f69614eff9ae929eac616c39bd760fd275ee89bf24b0f275dd77a66e54fd6b94e5 +89967479bebf70b2893cad993bf7236a9efe4042d4408022fdbb47788fabedcec27d3bba99db778fcde41e43887e45af +889235938fcec60275c2cf0f19d73a44d03877d817b60bb26f4cbce09db0afae86d42d6847b21f07b650af9b9381fa82 +b7fc321fa94557d8fbdd9fff55ab5c8788764614c1300d5ef1024290b2dbb9216bce15cb125da541f47b411a2e7e3c2d +b11b0c4dc9477176b3cda6b17858dbd8c35a933ed31364801093f310af082cb5a61700f36851e94835c5d4625bf89e32 +9874e54d2939ee0600f4194f183877c30da26d7515e9e268fea8d24a675dd2945d1565d9016b62b1baab875ac892f4d2 +90df3a77280d6f1fa25a986309bba9d5b89c3cf13656c933069bc78e6c314058716b62eacfa7ab4aff43518b8b815698 +962b08299a287d77f28d3609f39fd31bc0069f7d478de17539e61fcc517045050644b0307c917208b300ce5d32affcca +b30eedca41afb6f083442aaa00f2e4d5dc0fda58e66aaf0f44e93d4af5c4bf8ea22afec888cacbf3fae26d88e8d344cc +847747a22fab3fe3c8cd67f3f1d54440f0b34ce7b513225dc8eb4fa789d7d9f3577631c0890a3d251e782a78418fecfa +8d1ef3cb5836e4039b34ee4e1b4820128eb1e8540e350309e4b8fea80f3ae803d1f25f4b9c115482b324adf7c8178bc7 +8f8a2b0b0f24f09920b58c76f7d99ec2eb2e780b5a66f2f30a9ed267dcaea0ec63b472282076c7bf8548211376c72f6e +831ee6dc8889bbf4d345eaeb2f425959c112d2190764abbbe33bc44e1d9698af87ff5a54d01fac00cfee5878dee7c0f6 +a7eb2479ac80d0ee23f2648fd46c5e819ad3a1f4752b613607ae712961b300e37f98704880ac0a75f700f87d67853c7a +aa4d1b9cec62db549833000d51e83b930db21af1d37c250fdc15d97bc98de7a5af60dbf7268c8ec9c194d5d5ccda3c1d +87396fd7e78c4bcf270369c23bc533b7fb363ca50d67262937dab40c7f15bd8448a8ba42e93cf35fb8b22af76740d5e1 +a958b2a9ffccbca13c0c408f41afcfc14d3c7a4d30ea496ce786927399baaf3514ff70970ef4b2a72740105b8a304509 +a5963a9dd3fe5507e3453b3b8ed4b593a4d2ced75293aee21bfed7280283348d9e08bf8244c1fce459aa2470211d41ea +8b06ddc3359827558b2bb57caf78b3e5a319504f8047735fcc8ec0becf099c0104a60d4d86773e7b841eb5b6b3c0cc03 +9437e7278283f6d4d1a53d976c3c2c85c5fe9b5aec7e29d54a5423e425b4be15400ed314f72e22e7c44ee4bacf0e681c +b56067ee26a485ed532c16ec622bb09135a36c29b0451949aa36fee0b0954d4bf012e30d7e3fc56e9f153616b19349bc +a5c72f7f5d9f5b35e789830a064a59c10175093a0ce17654da7048827d0b9709b443a947346b0e5d96b5ea89b8d7c575 +a8318d01182d4c9af2847a29a6b947feef5795fc12e487a30001cc1ec482b48450c77af4837edfa1aedf69f0642c7e5e +82ea421c091552d3dafa7da161420cb5601b819e861dd2ba1a788c3d1b5e8fa75cc3f2b0db125dde8742eb45b335efa2 +8679fd1c7771ea3b12006d4a972f4f2892e61f108107d4586f58ee7f2533d95d89b9695d369cdace665f19c6bc3bc85e +b5ab3e8adee4c950fce4d33a0e2f85d3d886e60a6e2f4454b57bc68725f0cf246372d863167482cce1ea10a7c67c3af2 +a85696927075ec188979180326c689016a0dc7a2f14ae02ea27c39ef91418cd44177d3fca5752cf6b298fd75fa012e26 +a44f87b7232f102cd092f86c952a88afb635484a984da90a41a57a3d883c9469064bf105b9026024090486b6c6baa939 +866ac91a437db945bbfdc11fcee583f3669fa0a78a7cecf50fbfa6ed1026d63ad6125deba8291452bf0c04f2a50e5981 +b780d5a1e278fd4eef6139982e093ceafea16cb71d930768dea07c9689369ff589d0c7f47d5821d75fe93b28c5f41575 +b025d0046e643506e66642c2c6a5397a8117bbfe086cee4175ff8b7120e4f1e6794e1e3f6ec11390993cca26d207ae43 +a04a22b6e28c959ab265c7f48cde42bb6a00832c6beb2595b5df2879080a9424890960417d7d7ceb013d697d0ebf7267 +81de9c656ac27f54d60d0252e33aff4e9e9e9c3363a50740baf15a2b9061f730a51ae1704e8c4a626153cf66d47f19b1 +a15fab90599df889df11fa60c752948b68fba54005491180dafb66c5775547976d0eef33945e55d4818653e0818c6f92 +b06f9be44ddb103a72fa4ebc242c8ee1975fe9bf9ef7124afeda9967ff3db644dbf31440151b824869406851a90984a2 +99abdfe6806ae5efa2d11577da17bd874d847c5f810460148bc045bcf38c4fd564917eacb6ed61bb9164ed58055cd684 +ac53231077f83f0ae5f25e52b70bb6105d561c0ba178040c11c3df8450c508ed5df34f067fdaacf716f90b4926f36df5 +99e3f509af44fc8d4ebc693d3682db45fd282971659f142c1b9c61592573a008fc00502c6af296c59c2e3e43ed31ec7a +98f2f5819670aff9a344e1c401f9faf5db83f5c0953d3244cfa760762560e1c3a3c7692bb7107ea6eaf5247ac6fd7cc8 +b5b9f90391cec935db8d2b142571650fcbb6f6eb65b89c9329e84b10bfa1c656026674d70280ade4ba87eeaf9333714d +b0696b77ca8a0cdbe86cad12f358880926906fb50e14f55b1afc1e08478ae6376215cbb79bc9035de2808c7cd2b13b85 +a51d746833062a65fd458a48a390631d5d59e98e2230b80d8f852cfc57d77f05eefcfd3c395ade1e86d4a39c2141365c +812d67654319f4ef3c9e4a2d4f027a4cb7768f1ea3f5fdde8d1b79187a4b874ff9a5c70f15b7efa079c2dc69d1b9b1fe +968978b653c6416bf810f6c2ffa3d1abbefbd06f66b6686e9a4fdce3f869e0ab1e43cce14dc83786596761c100ae17e1 +98e1e6ab562ca7743783b802faeb0a24f1341abfb9655f106920aef08964a3c0e8083e1acda7ae28fed7cdd5478decb6 +a91c0b982a0a7085a103600edf99e9d0bee4c4e7db6d9f8f376c215c7d42476218462a3765f2928e12c3dd49d688e4fd +8a43395b3124fab9e2438635bf88952e8e3084dad7ecb3a9927f9af0e0887bce4707084043671fc98ad03621e40a149e +b0b37626143d4a8c6f5693d5f1fe871525b4dd946c4239cde032b91f60a4d7a930d7ba28959737550d71c4a870a3a3be +b01c74acae1715c19df08d5f4a10e0c19d1356264eb17938d97127bf57e09ced05ba30d0fc1a9f32d6cff8b0d5f91c9a +b4c2328eb8a5a673406faed8f0aebb8540d2791646e37ce46e0e382506570ca276eb6f8e166dbbf9e0a84064873473b9 +85cb9f769a185e3538e4a4beda9a008694e1bf8dfeea9dc07c5c40a9ceb1d31fcb13cacfaa52849ba1894b5027cb8c30 +8742f91cddc9a115ddc73982f980f750d82d3760f2d46ee4490d5b17c6c3bb57c7d4c7b8d6311b7b41e59464c009b6a5 +948ef86d17128a061e1bdd3ea7fcc7348e3ec87ec35dc20a58dd757d5d18037fe5e052bb359e27ab4c2320d9a52a6a0b +a70f6a214097c271e0d2d95e30fce72d38c30a2f186271fdff0e38e005aff5baed53739b8c4f9501aa7f529c5cb2da59 +892a7574cf6704ad75b346c95ae6f2668904f1218c35b89b07a0c2dbf3c62173c348f6fd9473926eef56a37c0f635c04 +837e85a41f39b4ded1420aa8fc3be46a7adb99305e0928c6d7643b7c44434b72984cea08eb68f5f803661df0db78c87d +94e495329f2aab3eeb68f347961d1006e69d990095877a4dcc376546233adf29a14bf6b16a0c39aa477e15368e87014c +851860a8fdf76a97048396553262637dade27f1f63f926997e74c7c72b14b10293eae7824e8dedffad1aead57c124f79 +90481017a250972055ab1cf45ff17d2469517f10f18c9d4ef79a9bdc97a49093289bbacfefa8a1e491bbb75388b34ac0 +983db15f7463df28091c691608ca9c51095530fa6b1b7b5b099c612e673d29e16787cc9ae1c64370ba6560582ce623c0 +a477dab41014c778a1b78a7ce5936b7b842124509424e3bfc02cc58878c841c45f9e04ccc58b4f2ff8231488fff0b627 +868ebba1c85d1f2a3bf34c0ab18721ea725378b24f6b6785637ee4019e65d4850e051c8408fe94a995cc918c7b193089 +93cbf4238a37ccd4c8654f01a96af809a7d5b81b9e1eab04be2f861d9d2470996fb67367e5bf9dcd602dc11a3e4cf185 +83113f4e696030cca9fdc2efc96ba179cf26887c677f76cde13820940ad6891cb106bb5b436d6b0f8867f2fd03933f7d +90c709f4e3359a6d215d03f45ad5cf8067aedd4aab03512dd62229696485a41dcd64e2acce327fda390e0352152fce13 +9945cfced107a36f3cf028ba04c653360afc5013858b9a12fac48802efcbc198c9baf3a7f9b23dfdd5036e88bc7274c8 +832ae60192b47fc735a8ddeaf68314b16256c90ab68099f58e43073e249c6939895c544a02fa34e40805bc6b5db33461 +8b12c335818b643c1d22cbc2869606cf64e7ae54a7713617fc4dd3b2f052ebd6b920ca59ba2e9c7aa8cf71bb4f40f9e8 +a2033eb7a373931c65d66989644aa0892ac3778b9a811b2f413d8bf534e282c339717979f9aa742162abb3468c195f87 +aba2b4c37dea36bed6d39323e5f628ab607699c66767f9bf24ef5df1bfcad00c2664123c0d8d5bd782f1e14a06f4c769 +b71963777535b4d407286d08f6f55da8f50418486392a0018ee10f9ae007a377b8b8336f33386b0eb01c45695c3ed2da +88dc87826941340913b564a4f9b74985a311371c8e7b47881235d81c081f1682bef313c2f86561a038757fb7d6a1a8dc +869e13e3fcf91396750150f9dc9307460494c1d365f57893fd06fb8acf87ac7dddc24e4320d9cad0414119013ea739b8 +92194e292303d32b91ae9cecb8d6367c8799c2d928b2e2846dab1b901371a4e522fc4089aad8f4ee676f0614ff8b19d7 +aa589a3e512cb4f8589bc61e826a06d9f9cb9fdfd57cf5c8a5a63841435b0548e30a424ca3d9ef52bf82cc83c6cb1134 +81802e0194bc351b9a5e7a0a47911d3a0a331b280cf1936c6cf86b839d3a4ab64e800a3fe80ea6c72c3751356005a38b +88e5e9e3c802314ddd21cb86f2014948b7618502a70321c1caf72401654e361aac6990a674239afa1f46698545614c93 +abac1e0f85d5c3ff6d54ed94930c81716d0ac92be49e3d393bed858833f4796c2b80bf7c943e7110de7b2d148463bfbf +b7eb416004febd574aef281745464f93ef835fd65b77d460b6ad5d5a85a24b536b4dec800cfe80ae98489e54447e8bb6 +b3fd8ed1c30e7c15b0bc0baf0d9d1ecad266bafb281cd4e37c55edc76c202fb1e4ea315a91a2848f40f481793ae35058 +86ef674ddf4b7d303c68bbfb53db00b925ccbf11d7d775ca09e458f4ecd868ca828103e8e7cd9d99672a193e81b83923 +95ef414e9f7e93f0aaaeb63cd84eb37fc059eb8b6eced2f01b24835b043b1afb3458069c45218da790c44de7246860c9 +93ec8f84c20b7752bfc84bb88c11d5f76456136377272b9ac95d46c34fce6dcfc54c0e4f45186dd8df6e2f924f7726ab +95df5f3f677c03a238a76582d7cb22ed998b9f89aecf701475467616335c18e435283764fb733fb7099810fec35932ae +8cda640695c6bc1497d19b9edc5ff4ea94c1c135d86f573d744358758f6066c1458901f9367190dcd24432ae41684cf0 +b19aedf5569435ff62019d71baa5e0a970c6d95fe4758081604f16b8e6120e6b557209cdea0ccd2efec6ff9e902d6ce6 +b3041f21f07d52e6bd723068df610aa894dfdde88094897593e50c5694c23025e412ef87a9d16cadd1adbb1c6e89ced4 +a7f8d6ab0a7beb4f8d1cfef6960ebdaa364239eca949b535607dee5caeff8e5dfc2a9cfb880cc4466780c696cff2c3a6 +99a565b4796e2b990bfcb234772d93c5ffdbe10453b5aa94662272009a606ba6ea30cc0c3c26aa22982c1e90738418a5 +90c54b55ff19157c1e679d8d4f7f0687a70a27d88f123179a973c62565adfcc9347cfe31f54539038cf2f34556c86870 +8612f34bcd018d742202d77d7ce26cf9bc4e0d78e50ddf75250b9944583b2c6648f992b635ea13fdaae119764e7c28d5 +a04fb38e5529bf9c76ec2b5e3a1ef3c6f9effb6246c7f67301cfed707356ba1bf774f2867c77a5805933f0c8ad0ec644 +b4800e7b503da0164885d253135c3b989690794d145182572181995e6fa1989f3d0324993e871bbd5f48fadd869d8a18 +9981cd4f28ae7b7dadf454fb3aec29746dc2e0ca3bd371b2a57cd2135a7d93559e02132528ccd2d305b639d7ac51613d +a3ceec012dd1fbad3ef9f9f1d6fe7618e13d4d59e3f50540d2a57010d651092979c75442ec8b38a1ab678505e30b710d +8b97b8654d067fb4319a6e4ee439fb8de0f22fd9db5569ba0935a02235cb4edd40a4740836c303ec2394c59a0b96308b +b3d1bf4410fec669a269622c3ce63282c9ac864620d7b46c9dfcec52d8e79b90c4c90a69c32763136a7f2d148493524e +93174eba1e03f879e44921084aa0ee3562e48c2be49085de96ed7621c768ff52324d14c8cc81f17d7ed50c38ffb2c964 +aa2194cd0fb7aec3dac9a1bd8ea08be785926ed6812538be6d3c54218ea4b563646af1f5c5f95cb914f37edfae55137d +93f2c0dd59364f6061d3da189e04d6c64389a3563b062e8f969a982cd68cc55b4f38b21546c8a67c8df466ff4f61f9c5 +aa7dd497cc949c10209c7010ba4ce8a1efd3cd806a849971e3e01716ea06a62e9d5e122ad1d2b8e5a535fae0a01a7761 +ad402424b2a32bca775a66aa087580d7a81f0867f293f1c35580b9e87ccc5a2bab00c29a50fd0d7bd711085ae2248965 +96237843d8e29ac77fc6ebf4acc12946ad11697de8e5f152fe5776f2475b790226a7d156ac48968dd68b89512dc55943 +a45c25cdbb9fc327cc49a1666988af9ab4c5f79cea751437d576793a01c3eeea4c962c05c0947852fe0e4c63e1c84771 +93dcf834a614a6f5484cc4ba059e733ab5dcc54253229df65ff5ad57b447353ebbc930736a4c96322e264e65736948dc +b9a94f82a82c0c5a26f2c1d5381afec3645e8ee04c947dc3b7ad59a73018db1e9965ab3642f2bbf60f32c430b074fb22 +94eab29b3524ccbe0c4b928e5fa5dd8f684074b332fcf301c634d11083653ffee4f7e92ddbcb87ed038024954ad1747b +b8dca5f679931d6abef0674bad0639aefad64c2b80572d646aaab17adf5ca1ab2ebeecd5a526cadc230bec92ed933fc2 +944d394958e539251b475c4304f103a09f62448b7d8a8eaef2f58e7de4f6e2e657d58d5b38e8513474115f323f6ec601 +8a5ae1f13d433962d05df79d049b28e63fe72688fc3e6660aa28e0876a860c3dbc5fc889d79f5c4dec4b3a34cdf89277 +afa5278724998eced338bb5932ecf1043d2be5dd93f4d231d05d2ea05b4455f2ffdc0eadcb335dcace96dd8b2b4926fb +b91153a2f4647ae82fc4ee7396d2ca23270ec7f8884ce9eead7e9376270678edd42dd3d4d6c003dfc2dde9fd88cc6e7c +adc932f1c679bf7889cb1ff4a2d2897d7973483fa283979a0ea3640c80ed106ea0934c1961dd42d74b22504be49851f2 +a82e90761fae684d1415cee0649bb031bcb325ae0b28f128ab8e3650bccedd302a70de1a341ca8decfdda76f3349cad0 +8ae353188b4b98835f4ef0333cccb9e29e1ac3ec11d554bc96f5880c101cb3c84b8eefe72f2287b0812735339fe66cfa +b8b41135bb1a1ffb64afbd83e2189e755f2c350e1273cf47c38ae9b8c4800d831436a69458b8ef9fa8b95a148d8ec9fd +96f75a04d8752fa93dc1eaf85ad333cff4eeec902a345576139e16de3a88eeb71b6726224349bb9844065cc454d959e9 +ab82b05e3923ad4c26f5727c60dc0d23063c03f5a4fd8077da66aa87042cad1bd99586d4ab35aa5e4ce6f4da6fecf3c1 +a50c83db91c26ef7bf1720d8815b41bd056b49fd99710943679a162ccf46097a7a24585750ece886e38eb4fdb866fa37 +a719f667914a84f62350dcc6f4f30b9ab428eac6837b70318c3ac491c1e69d48af5e1656c021818f377d911fe947c113 +a148807aafddfa0a5624c7cb9e42468219e4bdb9994ec36bc19b6e6d7c4a54d3a0763d13ca80624af48bbd96d73afca5 +aa012f205daf22a03e9fb13a63783dda7666f788a237232598d02a4d4becec7a699ab493f78d722ce68519262924c708 +97fc15fab5952c5a2d698fd6f7ad48aff1c8aa589f7d3b14285fea5e858c471cf72f09a892e814104fa2b27eb9771e73 +8da8840236812667c4c51c8fc8ab96d20dae8e2025290b9cde0147570a03384370b0fcbe20339c6aff09cca5d63e726f +b477d85359a8e423fed73409f61417a806cb89c9a401967622aba32bf85b569e82bca1b3394c79e180114a0d60b97316 +b3d6ee2ed1e4c5cf8ba2c3a4f329832e41c7fdcbcda8a3fcbe8f60967fdb1717665610b7c1ac65582534d269d762aa09 +a0b3b30b1b830b8331ee19f96b4a4321a6b93a3395b95d3a895682c65ec6ea64774b878b93514eaf353f2e4be28617b8 +a2b88e9617f4d30ef4e686d1932ad43cd555fadcb5102e51bea19e6fca649284ccf4debb37b5cb2090ef386fa5bf5327 +8a4446f7e8463ea977a68d6217a9046ad4356d6fc1c18d46c5d2ab681ea977b8faff136d65abea6bbf8936369cb33117 +91e7464bc56e03f436228104939ddd50caace5a38f68817bb2991e193b57adf6835152bbf3dbcdebf0382ac9823f60c9 +961a441e6cdf8106c4f45e5b47190d35644faec701c9cfc41ced40cfdd1fa83752fd56c1ac49131a47f1970a8f825904 +94b7b165cc71c2ae82976b8f03c035fb70e90028992b853aa902c0467b384c7bcf01d56166bec5def4453e4d0c907e52 +a5d32cffabbf547f900026b34ef46f08075b7a244565f615370d2f04edf50b094c95088a4a139ce07caf55bcd99afa07 +b4e06e73660745f75ab2f34d9f6d2675b58f80f911ab6dd4c5a6ce1095f9a2b50d86f6ff9a05394190bdf96af0827920 +ad3fd8f83c0103b29d41319209dffca201d2b98094362da08da3fd6ff0ba96796b49d6bed525c9adb96c2954858e7f48 +b0c27430695f0fd20ae31e1ec621da090094f2203e17411db9384695ffcf5c7c6badf461ba49ba70164aacebd6f278ee +b9bc6e972fc3b532fd2b1eeafc4bceb77604885f32132af6a9a842fa2440df452f49ec0cd9d86da1180e8deb0723b260 +9729e22d6104b0174c136a854920f542b384d375040adcebe36acc253bdb55845eb43e34dc5a7cc27d22c417973c24d0 +a8b420b36d48786c9231d454468a6e855dd7f71dcfd095efc9855ee70dbece0f06ad277f7829c5813fc30524c3e40308 +8757dff5499668c93fc5d9cea0a8db61817b8ed407200d623030b5849a913d12f8371b667cfde8d8082026eda7407e8c +b859ad747ca5af661fbd03a1a282df6e84c224ecea645bc2d4ba5e35fa06cbf047387319fca0cbc76b712398c0798968 +8e3173c27875f1460297af0fa736c945dc842ec3e476a973d3d5f790bf183ad3ffe96ac13868c5101d8e299890791864 +a9d725e2b92c878be42b5eecc2c3081c63c7231ccc7e2dee17ca6a4caaeae22788fab1f1465fcbd7fc236613fc2bae4c +86f6c4f04a354cb2470ef91914816fd740f8d5795ce7ff981f55a2634695fde5951bbae7a4bbc4c63747040f8644170a +851773cb26f320f0c3f252d95ea7e058ffcc795dd0dc35e459aa1b6b448238909230d809e82022e64b7fca5d40b8324c +8962641e0306220d9892fe2d452caa286301a3c465185757be7bce2d9b2c9beb3040280099606cc86773e43941fd3439 +8beb6e08c440b0de5fb85251d39d9e72db4e556a2dfe3dae59efd8b359d08492064cebd8d8993254b43bde8bd67d969a +a7e047894466ffe3dec4ab8d5462f2b1d8ac0df006b1d2dd26caf499ea857d93a811cf42233f9e948c9cb903beec004c +92eedd95557a91691a5e2835170390ce2401e223da43b78615a804c49566f9d31cbb7f10c8a8390c4bdcf691544fdba9 +a5e5b5d8fa65824e958bbae98d146b4b332f97ed50e0bc2c58851dc2c174ab71bcbb1ae015cd2955c26b368487dd862f +853a494eafb308175629d581ed04bed71bbc3af9ca4c0dc483d03d27c993a2bbd88cea47c2085a6928d166fe6938fb77 +83f06b88d29afbfbe8f61811690322ac4fdd6abb9a23612162e7a2dd6bcbb5f14cee298ebebc1a382484f7346dc51e60 +8c9cf05735ea5a0e563490bdc7ed29a4426643711c651e35c8551ca6f855c8458ae8f0933a022d0bb9a952edfed411f6 +b906b48d807748a26cc2a8848455a76ce502261afe31f61777b71917bdf7de2fece419db636439478c7582058f626c29 +97efe1fa7c9b25d8bea79d74b6cdcf88f63f1e865f54b58512a2e60428630b0b40b8b6af1b5f71df47520507548c3cad +8ef5ca6e753818906bb3fc71405928d8e4108854ef0ef01c1009071b353bc2852e771fcb619d5fea45590e8f61003d7f +8e4d901661e2913740d70ba4d0745df5e8c9c0a260149d9362beadc7e669630ba909ff0e8a6cc85c54d6b7435d0d351e +b7c6ba3bebbd9592967954e3a480ee8df1d9f5965f04e7d78a5415b645128deae7ddaf6ed507c8877bfca91ce078e529 +840bedb0ad4e25acf6cd25dee4f98fea495b2312dc5cb7a8388c5ab00b2acb9cd25da08e9fbead145a3107972b1ccd5d +a8d4578dbafdb27f3911af59962d89e75dea74db55346720357790da677312c203107d9c7911535aa563446fde7d4c47 +86d3b77f231bfa09251b7fd2ce09c27ac520ec35d783e912476f9a4863f83d269eb175790d6e735da9260293d707f8ee +b34909f1cc033232652da0c34051a769dc76adb1aee00674a59dc1b860f6e610974c3b4bb69a69ccc73e01f042431242 +90799854d0cf34e1d91ff8e101bc7c5007423d34d2f3bd9adea2ecac57e83f3a65a506bb93d4caea49b29f6d18149957 +8ef94cde29b037e19a1ce7bf4418ad3c95cd9457412796ea385750c19a6690f13a3bb5bb6a9ee81e7a40face1e0a8bca +97053d21ae8d75972fb37f6fe516c38c32ab162fb56b9f510f954858f4e3ef6ac8c3a9557ed3f41b7b6aef05fe97f931 +90a9f9f0f40991f3bddc58b92d40382147db22cce50d092d4a05aad251b46b94e71ec9f7107a180243288059fcc5ce29 +a14265b1344ac2921b0f890d13bcfc432e4f648ce403e261fce4d3bb32ffee9e2794c02830346054f998e82784c77040 +91928402ae121e56a3e64cd6f390127e6e92fbfb1967ec6efa4f52f3e8058f1f41a0f4fe96b5bcc11641c1139e790b2b +921c8c92b6d40da6c5a7b592acc74fc0f577d93767b9aa4a1cd302a72dbf503a1ea5b2c29fa0d0359bff3b8f252246d1 +93ae0ebe0e8e133fd80cf67a499047e30ec4c4660ccec9d49098717ef57721a030f423e00c5e74af4ff4acf014a10497 +82c865e21905aebfe0496af1c6ac7e342b5f446a9edb4f7da0f2fb0340abfd8e6fc545da874459d9aabe6bce0dd9bfcb +aee3961d8d2687c0f134b9c28b920bdc4021d925fbe14323c84224a9fe161248789249fb85436a5891d0bbff42c2a3e9 +91aee420b98b6949482b8ff4be996b97245b4e8f583a6e085226539074f42aa89818395efd1a6699735a569bfe19d623 +a48eec22c192e495b01722d0016a54acc45ff837e2a95c4294ce81d5a4e43e0053a6f0ead8a4fb3ddd35faf6607275b0 +a26e15937c11faa30ffa64817f035e294cab0e839f73d29de8a244ad039be4e221eb47ea08d9a4658b0152fc3caf6110 +b84450f948aa7c8682fccb9cae84d8e3558adf2d0ca5fb81eb200415291158720f8f3470542ab5b88c6873ad08e7fa9a +a8e8ec27d0608d020169a85d6ecdb40eb402f006a3b97afe32cc01987721b3a68a92ec693aeb4d357e189e05fadf699e +ac87cd535ef5699312cc26f86adb71baa0be42e858bd5a2d94ac05737dac63430691e29b9a30d2559ad581a172519b2c +a4481e67b524f8cddf2046625efd3d75efee6aab87ddd2c1b22835647e918157e5e924ac760db2195c86d326f3db1615 +891f29ded231486ee826840c8895cb325f7e84a5a6d2eac246cb3573612cde274720233b1978318a57ed337a046330a6 +906b6e750e6178289012769807d2598925d7e51c260c14497d8af978b1695990e3352e6e809a752f376597a68083870c +b7a056898ee1e46f7f29702fb39232f678ec173eccd170303b3b0a30c8d8cf1a5321384e3513e3b03bb742c238deaa54 +8f2f035fd96c3a336354c89ec9b8222803bf42e95fb2412c28d4e75eec99c1d4d402501ccae17357b757db8bdb0bfeab +81228625ffcedf977fba9cfa13f6edead3985e2651d5974789c394a69401cd7face9e20ae6694be4c0d4bab5e99c61a8 +885a83eae25e61439ad809567a2ab148583402e01cfdd77b0e37ab4038935425c64b4e0886949bf06438c35e80aa13f4 +8926387f48752f6933899c48e038cf14e7941ec6a58bcc0a436614b396296a17aa53e6873803dd3041dae470bd493fcb +95d0d3fa061f4d856eca78a569aa132db14cede7646f97e2aceb6da0c8ea53195d3b7a566fe5ec8c41b95ecdd89a1c6b +a3c817f4062ed6aa94064ea695d76c1825f3bf77b310fe1db28b8bedc9aaacbf1019dbd128adfd53042fb943d863a2b7 +af1208417aa584052da309169854149ede38a3ad63c76cad6e43afb6f1a7b854edf8310a0b00088c039259cedf0f859b +8b713fc3196bad35dbf364089049ada5477e540d78d76a5f0a9df98f7ba4a0e65dd0644509c149f9b07887298bf74b04 +89c09c43c5b733c4a417cd9ebc0795cc3348b72778d31828a9171427779a82ef023c1a4fcfcdc919ae25056f9c826fde +a0759c850ed320c8c874435e90ace6edfb8e7b3f2a09d942b8ad8339c508044ee2ee26c70f1b626ec49a77971433b6a8 +b85cbc58d4fd52286e714ac4eaaa0b2743a1de06fa03ddf8f6668ec6f1d204acccce93b10620272afb8c0b49bc4b0a43 +814e0a87384e159892a8d23036985fa3f489c53bce192e107bd2d64f57b1bf5ea0acc1ef46c7a42bbc5cd0924d92b4a0 +aa6821da96ad89d7881b878e141076522f104ea9a5bbdd1fce9f641898f7d6232c518a87a0f666871d7e3165c26081e4 +a9041d714bfc067b5427252186fa3557bad598fc0067dc8521aa9bc1ae298f6e96113db5ac9f6bade9a85d5a950c9755 +b8669340f3064692625e1bf682d34fbe69a61689e3aa6d6a3e822c781d406b0300dba9c3f7b8152a8c2513f1310d4291 +a78c53316ce768a1dc5968030bf4fc885f4029b1ddb6a5d84a61c85af686c73727f62823891edfcb6ccf4545de366cff +ad1d3aa29ea28292ddd438c865e2b5d93f32cdf009e6d5f5dc726de996583925727e6348bf1c28c22dec0bd86aaf867f +ae1447a2062e9e28af5f38aecc60fe150cd10c2edeaf2110034aa144f6235ed7fbce432a58805d4fe1f6b12652d6e1cd +a32146634332d3303934550705353c6d4fae5fa5985105bba35041e74cd71e2aad67b45da171221f6ed80f36bf6dffa3 +a232e8286184196ea77427b53d8b52c44d758ecc42d22556529db3136379b4989dec61cff610cc6cf6700a450a847a94 +8a72c7255125a736da52dff5f77e44c3de29f88fc05f5ff9227c69df296930caaa11446595e6bea3bd946baac5ef957c +9688a981a9457678067f629f8efa6b522e7318b529f88d37ef56c5bf8f1c34fb9bb3a918ab73caab82bf5abb0c03518b +88286f3eabd71115fc3b17a6bf6981340a81cf7e5f96b0a1a016d4ec8c18fb486d46c70919123d0c189a6f5d6ff29a1e +b535e701b40d793c02ac0d625ca91620d3f4a512aa9741f71389e58381008b2f93d597586d06213c4e103d67d0ddf6c5 +80d0c9dd941e8d8d3700cc51a434a5aaa3308cf8ebfd14128ccfd258f826b27cc3cf5c3ad7851340393abb1eeab3a157 +87049225fa2380d93f18d3d90cb0697a56b373b66d7f24ab209966aed8b55a2790194d5885399db29dd5b1f189eda64f +a52df158ce8670e0290551e8878d63dd33b4759d6f50e448e63fc7fe6ea99dddb6f180be5fc0fc3918ce54c05f80b356 +8b2a728b39c465fb0f60b0c486e5dc8d5845ccec03d3dd93b393cedeeb3fe1b44518359f1ed55fc770a8f74bfeb9923d +91fc05419dba718fa4a910dcf256ebea356bbea00522d8d5ec3e7ba4271a26035aac15e8d9f707969df1d655d92dac55 +97c8779ae80c24c1f82d5a714762d6ee81069224e39515e41d8a71c9310dc5d1c55cc92bc5c6a4bd391ae4c321d1d4d2 +b5e5aedba378c4484e3a7a4ed41b75b0844f674261c2501497de6f91f7274b5a4c1be0e055f2e0c0cab843d891169fbf +8a26212f27211b295beea500abc8e9d430a8500d3a350cc62f895d39e8b4668aa638c17633804ba353010000165637ae +864a95118e5d394e00e99efebd505df0125525c9ebe165764c453b80ad3edc730feebde3d93850745dfd88a27bb8f20b +a092e0b78290e826cc1ae56afffdd08f7c10954f549a3ea6666f3db1b6cdaeb7df53db28dd2a92446342930fe60a27ce +a1720224c0626a081b6c637b2a6d37da85d9a82241e5efef3bc15699b02a69f6304e43d8ff3144d60c16e00225d6b39e +a7b3d098cebea9cf32e19c5195608182b6afe9d4af6b9df532c047eb7a941a971279b2ae6a4b80f2f9d9313a6d788ce3 +a3d2451e6788944802c5077a778d7b7299dbb9d1612676bb6baae78f39976e0fd879493cc4a4d737b8174b472a456850 +930121b73da844571b1411d56760e80923a4ee09917b3e9cff4d3dcb0bc27026ff2c4e2c44e7aca7d3f8383f129c7f9b +b4b0119d163ee00a2b74bdf188a5cdcf054daaa48c483b94bbb4d09ff615afb4a91347db6363bc7535e2af9054ec2214 +a5846decee706780201095a8cdd48fbf3d3a2eac8d089a818e5e22c29457494bbfb4399323b067f3d2be2197c33dbd98 +96ba600df10ee7af5a9df29c0ca31dbed275d647faf9c66c7342de927ceb25b5bdd852dd7aae0228b27897f90fdd5d62 +b6ac51ddc98edd9fb9f54ef84bf372a041d58dfdf0dfdbdc4b08ddc1a7ba93ddbb1413dda3c1545a3fd7386c6b85975c +b35f3efd91a0723e0d486188ea9675a3462106470455118392d7610470b623caca2fa33829721c05fbeb0fabcf570bfc +87f49e85df5f8055714a8ce7adf37f6a278e64e76ed74c60abe3edfc3611ef5b0426d4c6da45e5f3b74d30be1dc6f539 +8ff8bb06902a71b1e9177a77367318b2e3e0a88f5d74d6907ca9943f4f9f1ceb5f297132c2a025259d17a67e880d1bad +85eb6de6c70fe5c53ab0ab27aa0fec439f136c979c557d317337cafa6e6c5cb3169679c9169567dec5f6c72b3c057d83 +ac18715ed1080771d760cb7066c6328faf65d9b30517903f8a5cad8d66d5c6381156b521107d7cd75ebb8c30e250706c +b95b9eae4703727e4ac9ddf2ae675906487bb78905a5f9cba74a4cbfd118d96b7afb6ef3ed5edf14fd963b830d71338c +a3b47b52fda16b62b11c8aa4daa56b0b669c4d5c56a3059b7d063284d8a91f6fff9ccccab23d6ceb9650483b2d353039 +96a95b3f327df94c85e92f2e406f1649ac621533c256b062738f3c3ee137059a735a3e6072247acf57b1b0d8c219bd7f +b19b33cc04570be94eae8e943d5bb17bb0c96e9de4ca84f9f41b37320a1a03d397d53747dc13275fef1b356de557214f +a1faa3dcb931dd91507f3f12a17c43f6627fa2bc5c71fbdd27548e091eaaaba262477949cd51290e81196bffb954a492 +b060a16079dca1d28a1fb33cbc26f368630ee042d980ce305230005d5b9ab533a7a695281ab76e9214458303932d8bbc +b303783196a858fe45d67e0520c30576da605fd69964449c20009fbd5099cf1de52a32d326d7c3b864de07440195ef40 +aa550a4c20d1003d137ffd8fbdc1196d09ad53cfa0e202302093a80fa3bbc4c9aff83f34f2151785cc1ce5f30255693b +a7f8585f45566a351058e10c6f1ff4a7ba24811f1482a47202f581525615ca770da93f2f58878788b45b92cb446ef4ec +8206f63a9a5b59bd68e64a843e68fcdf706f4c13bbfcdfa9928298e5b9251006ae0bbd80c715aa3c9957d2c0148b5059 +ac9490abe1241319658f1c2c645cfa01296f5d4106020c7894b7ba4a65cdd52f6c5401bd3b3cf1c9863e088cd8c9a16f +85dd6d9c80a1b58c24c4d2cb7590d33d2454f381f58e820979948e5831972360cde67bbd56e1860077ef5192fcacb904 +8b0285944c676fe2519cb68da0973275fa29c0718d838d363ce46651b068d29f867cf9fe579ff8da0bb8b37d202bb23c +95147275da658d43a758b203b9ca1f1c1478853e9bf77b5218593142e2bd9c0bf46d2206ab64cef99295de6e9a268edc +b8efa187fdd3e1f46c15cd596e9567690c10e253b5beaa5be8074b6ea4e6d3d06e0f2b05323453239e419ae1e7128521 +8340464f52c92e31806fd3e8e65f56e27194d1f6daa4a0f0b3831e8102aba16f88bb5a621633ddb7dd0342e1d2d12343 +8615d87dcab85a78dc052f05a01e751176b756b5dc9985014347454ce5752f459dd6464e1c5aff36cb6c51b783fa2692 +80c6e35c0d3defbe4d3968792724a23f0b8830dd2fac58663583a49339ea20f1812cc4140e3ee867c7e716177319bbbe +a7aa63dbfc201dde8f29bb6e23d7aa5020dd35bd18a0cc93c8a10c35d695913fe25b9e8cf9b5fd1899e9657b22bc8863 +97c2a4ba80c4caba2e729a603d2faa0120915e3fe64cbb065f7ff33de5f877f1ec9461cf455e88ec9e9ded9393939dba +a54bd1419f0e2d2d87757870f37c476c7e3a13502f1ada82fd7394fd29f8a00c4986473d753034d0954a2550badbac0b +8d3e2bf900d0d2b9b46e6e2f37620f0cc90526dbbcfaad4e4a37ed53f39fdd23bd3a6f21aa7e800eaec937d9710dd6e3 +a88d2b1c7802b2dc216c2b6532406c091bfb12f29121b9a82c1154470e250188413ddd3e79f7e009ea987a4c45b332e5 +8c552c2101dfdc3f99c2da436115452e4d364eefe029b12946f05673c5ce1cfb48d39a579625849236dc6c8e7277dd30 +8415c252d52a26a6400c3189c928a98559bf24162ecf3eef1d10e439269c31d854b0b4f6ec7a2430e3f11b5d77de78d6 +8b38905bad93a8d42339dbdb5e510003c51fcaf05e04f88fd7083753353bc1c4c00a5dd4a67431cd4456d0669c7040e2 +b1d0ed8862250d0f0d9ef9dcf0cd16d84313d1a795dc0c08e0b150dadf9ce73d32d735e04632b289cafa69a6ee75dc89 +9434e18a5fb631b10edb02057f2d1fe16000ee55ada3c26a079c9fc3943e29d6de99e52829fe7b333e962270c712e51e +b1b9f3914007e6fca8ad3e7e848a1108988cb2318da36df24767d804e95d1272943fda948451135cc1b5052a3953b081 +8c02947a76d7b6c0a700a83dfb971dc105bfe996e18c521445f036310914b349ab28e57571e36ae08d13a46fb01c2f43 +893472fbc225f973a0ac6a0a0130b9cfb7ab6869dff80df71a62b1f6beb4afd069bbf35b4f327165bc31dff39e4fcaa4 +a7c176c0903175f3540d62f9afee994d5d9bf37081e094644b22f017e94c515afefde7bb07f638342abef7de657f8848 +860186c2b1d3b1e657729bc804275fb5f5ee89eaa60848fcabd3871289665ea9f0efc8a95792d884972bcfa2de96223b +865b38aea6386d0ac8f501a7d934e23d01dc50105324e354d4c4fa3cb1d4c29c26f4566df7b1a728e10cfaa9d24552e6 +b4eea5548de6969dada658df604b5d9c49002e2258352838003e0fdf7b299d81fb025807a7f37cf5b547cebd7f2c1f93 +8982de11ba68d63a649a3b296d4d56c71e3c3eec016db250d733ab7c3b9a620c09c5a5d0b64fd30d3bc03037ca4b17c9 +84d8b8a10d67eda4716673167c360fc9b95717cf36ef1d5bc6f2ef5b9d2624f0e76c2a704d016adf03e775ea8e28d83a +834d03ebd51aff4d777714783e750b84c16cb6627f8311bd8ff17c3b97fc4a5bba57d6c8f6d74f195d3030bcb5f07612 +aaf49e0def0c4d5f2c1e9c17b51e931d2f754b19e80070954980b6c160178349f6d3c8d4808801d362e77f41a0008918 +8ef4115edec841854e89f2bbd11498dac7396bca35dda554290d3db1c459ffc17be671f4a46d29fa78cbd6064cc2da20 +9641dc8a64f4acd38e343a3062787c48c312f1382f7e310ccea3e95e066ab6dc980f6ed90a633236a435e68bf6b3c625 +8a84cfc2cbeb18a11dd6c2a0aebb3f6fd58a33bb4b26101e826add03748595022e816afac79a4e7c20b3805252839dca +9770782d729017659844421e1639ffcda66a2044df9e19769b90292df87dcb146b20c6b9141bb2302029d84a5310665d +98c7ec9696454868ac52799d1c098c15ec4e08b34884dda186ebfe87d32840b81fd3282295df141c91137faf4cc02da8 +a3f6eb921247617292162dfc8eec5b830ddc294a0fb92f5b4828a541091ffdaff34c392c1d7168259d6204405d90ec72 +b185f77a468f07a54222d968a95635234e74fc942485604909308a9028ed2753b15902b9134749f381f7cd6b89cc8c3d +867608a682d53bd691dbc92eeb460d1c300b362ca49c11a280f6768ccec217f1145f9d59fe50d994f715ce89d38a74e1 +afaad630ad8827cd71aade80edf3d7aeb65a344878db12fa848759e6233f6fceca563aa437e506ea9e0f1e47b126d45b +a12afbc84e3441594aecf85d089423dd3bb8bb33a1a384ddf7cc14caa72284caaa56aa179c15e3140fd56bb532491a67 +98757b0b5e5837ddc156a4a01ce78f33bb1fce51e0c1254ee9b6d3942268d0feb50b93edbf6aa88f9ea7b3c0309830d8 +89573f4a4ae752e9f964e42bec77d28a41840c28e4bcdf86a98a131d0b85367b885077823a6f916972de6ac110821bd2 +a17f2745052de5de9c059307308fc49f56cb5230e7a41cb7e14a61c9efa742ee14c41023ce90c7f2261adc71e31045f8 +914b07c53a41c0d480083f41a61c10429ea42dafea9a0db93862d2269ff69c41db8b110b4768687b88089b5e095523cf +b380cc3e0d26370976fe891d24ea4eeb1b6be8cfce01f47fd68838a27190e644fd57b049d3aa0a9589370de20e276944 +906385fdfad60feec79eb1c303e750c659ceb22d9c16a95faaae093daadd53e7aa039a45d57e20951d6e1ca0dc899ef2 +b5211ceee31b194dba60b616bfd91536e71b9213a3aaaf5aaf9b2f4cbdeb05191861d78b97eec58e3c81abe4f0488c04 +97878e9e38c2f69d697800e7a2f132fc4babaacf471c79c26a757f771606e55fe696ece68a3163a0ffeb2f72274cf214 +959431c1f54c46500c05aaa9a2bc4230531dad97ae768fa92bb85436c0ecc6374cf20fb0ef82d122db116820a943b401 +b69e5a1c6798f30d33e42cb8d124f025d2c77c993c4c7107a539aacddf44d8d4d2239e802ece32e60ee4dbfdce201bdb +a8b09e5e9f802ad273b2efa02bcbc3d4a65ac68510510b9400a08d75b47b31c6f61ffdb3704abf535a3d6d9362fc6244 +a41ace7f1efa930564544af9aa7d42a9f50f8ba834badcaf64b0801aaed0f1616b295284e74ca00c29a1e10c3de68996 +a8f2aa0bbbc19420a7c7cec3e8d4229129b4eb08fff814d959300cd7a017ddb6548c9a6efebad567d5a6fde679a6ac6a +9683da74490a2161252d671d0bc16eb07110f7af171a1080dc4d9e4684854336a44c022efe3074eb29958ae8a1a14ace +8ef44d78d10795050c161b36afa9ab2f2f004ccf50fdeef42fe9cdc72ebb15a09389ca72a00001cd6d9b1d7b3bb766c3 +adca54f3b14fb18298098970b0267301b7312afb75894deea1b2afa3e85b7a3b4efac9971ab54c5cbecba2da9f18507e +ac5d4528f06fdccfc1370d5c3d03ed982fed0861a93a3f6453aa64e99360b124926d1892faaf72d89459e663721dfa99 +98aa1c801bd615b8cba728fa993021e181e0ad717ba01c0290e7355694155407083eb53cb70819c4775da39d33224db7 +8b3aea4c7c2bfe1020de3261ec085d79c7bf8a7903b825d2c70ebbb84af197bcc54e3653c5373a2045c3021526b63b66 +a29f3de4cb3d99afff1daf7d431b38a33a9804fedc41626618928ed059df6f6fe9f298a046b594ffee951ed4d4e1400f +803fd346be540c5242667c18ee41b26bc812456ab13ff117196ed69b90ee608c8cb6554396b64066a546ec87a71ed6a9 +a9c18d81ffd029c0339c72c499bb51685392253b996b6eabd8b76f05c6191ed8444a1397d63b9923743661a319517f7e +a048d5c390d08f07161faac71c5994baf152c883b205f3bb10d3501709d6516ae54d491b486303a11b751857a31f0052 +9156fb4803e40e28d8d57d928481a8de4373687288da44fe88c5676a8ae013ed1fcc09d56a31140bf74e7f767253810e +98e289c725b18e0085afdfaf2acbc674dae7b0a2ecc2537a7d0b87e20eb785404ab05973a787f0495d2adb3e5565c09b +8a7237b249325bd67cdc1f9fb278710069033c304afbf270b7ea24dbc10c8eabe559a484d3edc733c77b4384932deb41 +9056f2e5b02e5c2e04a69fa1323bbf1859d143761268d18e74632e43800a2a9c76fd681e924a19bc141de0e128d3e462 +b9f2bf9e4e7263014296a82b9ecbb05d3f1efa4b2e675e3b38d3eace59da06a89c859256e1b77847886d6aa15f98f649 +83b22949cca19030289bbf7cd2a0d8b84e1d468e78bc85271a6753241b89122627632723bc293cf904a5eb2b5dc6c3ae +a919aaf35dd0116168d2ee845122026416bec9633df113fbd913d8db5996221e234f98470d029a8ff182825b59fda20a +91726901f49d32b41afa15219073842278f60dcee223640903d871e318a1c2b541136b7b38a7b2ab7d31e4242fc29674 +942b77666545bc9a858d36cfe857ab1a787c9528f4a0b87918a06bf510793264dcafd12ae6bd3ee300179dab7f40aed0 +80adc1f2f9c47a96d416e44fcba41628abc0fae1f88f6a26aea4648419ab726f7fcc2187c7d5145e3d8f5a75c03937f4 +8041e0f66ba9dcee01e336dd4d16ae5e4e1618512fc147cc8230003aa2940848162dc2187d4130bf550dc1f3559849d4 +999e8adc51bab54386af1c5e8822986ad1b7ecaf1f8a4c2baa5bb2fe9d10710e49545c5a8bd89ed0e61a3d73a908e5ef +89272ffd39b6e9f99fafdd58bd9dc00f66f26a1d36b38a1ac6215e3546d966739eecda7fc236335479207cef95cce484 +b8e0b7532af13f15dc04a0eb4ea8abd67e58f1b1c6ad2e70c0ffa04a5c18ec2018b5d7f4be2f9f86db5e0b3986f639d9 +b96bd11b0f6ead4abd5fe1e4c6e995da7583b901afd01cc05e87d04663fb997997d6d39dd9fb067c62cb1b1cbb67516f +94ab08914088b973e8dbd5685decb95f3bf9e7e4700d50a05dbf5aaac9aea4be2c10c83096c02252e9238ceea1351d05 +a188de419b062af21275d976494c131ba18d2b2ead8bdbfa38a777832448e64d4d9725c6a1d530ffb6513f18d5b68d9d +8f73c8c118fa25c76a4ec5611351953c491452743056a819c8c82ba4737a37d88da0b55f837e7239a5f46d2c05a1bbba +894a44769e0be1c26648b0d89c4c9f46dbdeb3a71b90c493093bee372bb9f2d3f319850fd886d51f4f58db0de5641742 +87d239923b0db024a8d9b0281111d47b0761d81c50652268b074efa3ea70d793e30f874a91ce33a4acecd0cf38c01951 +b1b48b75a97f9fc2dc9530dc69f6268829dd0ddd574516e7eb1b9f5c3a90058889a7bcf3d378738e6d4b02f5fbfa44db +83e3ee9526ffcb60c6e75b75550fc017912ec0daf96d0a0d5f58c1b229cce90c684ac7c3e17fb998def8e7e2e155d750 +b9b7bba579e474b0abdc7775ff5f84c9f117c6ca17788cf5a5f01b2c35a14aa39036031c8d799fec2cfb371d9f7471fd +90d7faf4891fbc368a32f575dfb69f13e37161ab4f63a7139be103285a49490c2851a907f8d36e09e7d1a190dddbc6cd +968c8b9affe18fc34a4e21f0d8c5518341c566099e6b45b8721c9912bab3693c9cc343406fe90279692a1eef2a3f7311 +8735baaf4704207550f77df73fb701d9a63329993a8cb355ccc0d80daf950145f37e9b4b22be2aba29898e974f9fd552 +90f52b2dccf525b9191d836b205ffe966d9a94f6c5800f8f51f51f6c822619e5abdf1257ee523597858032d2e21014ec +831209f8f5257bb3eb452d3ee643d5f063299f8e4bfea91b47fc27453ac49fd0ba3cf9d493c24f2ca10d3c06d7c51cd6 +a5a4db4571f69b0f60fb3e63af37c3c2f99b2add4fc0e5baf1a22de24f456e6146c8dc66a2ecaafeb71dce970083cd68 +b63da69108fad437e48bd5c4fc6f7a06c4274afc904b77e3993db4575d3275fce6cffa1246de1346c10a617074b57c07 +a449448d4156b6b701b1fa6e0fe334d7d5dd758432a0f91d785b4d45fb8a78e29d42631bc22aaa4ea26f8669e531fed7 +aabe43de1350b6831ef03b0eef52c49ffb0ccd6189cce6f87f97c57a510ac0440806700ce2902e2e0b7a57b851405845 +91015f144fe12d5d0b0808c61fa03efe0249058e1829bb18770242f5fb3811e4c8b57ff9cb43deccfc70552e4993892f +8e9c570811ce44133ce3e0a208053acb2493ef18aade57c319276ad532578a60d939ed0bde92f98b0e6a8d8aabd60111 +8b21839b5dc1c9a38515c1076b45cedec245d1c185c0faac1d3d317f71f1bfebba57c2559bcdb413d9d7f0a2b07f3563 +90413bbd162be1b711e9355d83769e6aac52fdfa74802d628ff009325aa174c68f5329ddd552ef93e8fdcb9b03b34af3 +8b6b02e3f9dd1031ebd3df9a30432a3c86e64306062ef00a6d1243620d0cb66dc76f8d0d412eceff877ff8768c2696ce +9894b41d9fc715f8f6addace65451f41dc5ce7b983dd8cb33757b4d7259bef12f144e0077d0b662aa847d5a45f33c563 +a353a9740f6188d73aa4175a6c5f97898a05ed7aae9d2a365f15b91dfa7c28b921fdef0a32d90b6fb82718b33d3ddb8d +984eab8faed87c403c9979f2d2340fb090cc26d00cb4092aeb187c3f4ee1df3f57cb8363f7764073188790b16dfc464b +a5c5ae0ba435fb7f3ddd5ad962358da326239ff236fc3b51bd22e88296236b109951cee1b98f444302badc58d1b5bfbe +880be1006b0156f2788813432f450f613d235f41aba52a6000d2ad310408ad73d86b79f6081aef1e8c51010d404ba670 +937da751aae68f865c7a33fa38d718f20e2a1c65cb18c8e08f8441f0cdc77662789d2793794dd0a427cad30cd0b33f42 +9496fde66c834ff86f205897db12bbf9a9bb78d9ba8b5fb539cd0a2c927cc6b4120c017b0a652750b45edbe5f650e5dd +97a6f409ffeb593e149307a14bc47befb632412d70565c5f13d6b7d032acd2e3ed0f7b6af701b387f11d69ee4a8094d7 +97ed94934263dc0260f4f7513745ed3483cdddb9adb85dc33193c3a8b4d52affaf1ded23b59c34651afbffe80d40dc36 +b2b26378d44f916bcf999db218b9892e06de8075f205c7dafd6d37a252185c2d1b58e2e809c717963d25627e31f068e4 +b8f9fa1fb45fb19a45223f7be06c37d3a3501dd227c3e15999d1c34b605f888123026590697d0ae24d6c421df8112520 +997aa71e3b2e8c780f6855e94453c682bee1356b5ce804619ef14834475511105b1e4d01470fe4e2215dc72182d9909c +ac2cb2a7cf55aaf990cfada0218453853047e813d3f51f5a623d09f4714da79de6592671358a5edf938a67f905b6cb5b +8d8340d0c3081cd30d34f3ff6191e1ff6ad7994b4ebac19e5936f1157ca84e1813228b7605ee226366d6bab1e2bf62a2 +9693b17669086003cb46c75fed26ea83914a54901a145e18c799a777db1df9c9ca6b2ea3ee91e7b0ab848dc89cf77f19 +a6b6b2a6cd8c4922d78c8ba379373b375d66ac6ea04b830a23d5a496cf714a9439d81c865da92d52600aa4e2e43afcf1 +89cb665020abc3f5e11a03c7ba5ec9d890fa9ed2630f1443a8e45a28c32786ed980b5343ffffaea60eeff5b313bc0d66 +b37b989106594221bc6cf33a1a83c3e65ecdef279e90333a9e105b8139dc28384bb2277edd4b77c9e59d15e6afe074c5 +98ce5aee5918d18b2326b30c1ba41669cce20bc7a1d1b585363305fbdea66055164a7ac398ca0f0e670291a3061022eb +b57f472d5f34beb4cf430d7c0f8ac5bd1c0621a284633ed36e6f7804bc2b7847f54b469c7ea163a436510d9e3b32f97e +ae673a6579dbf0504c8fd0c8fc0252d2f7ae8da615a06f4d215c2f8a8f516201f24e5cc42967630c252905e5dbbd6377 +97c1501835a31091a5a83f0546e01c85ee847a0ca52fb3cc0653f6a826e13d25ddc623a5dea139108f7270a1fd7043ea +9376ee667f3834f6c0da4324fdcca5c04712e0649877ee19da79a2d23be24640c38758fce562470ce2134ca34148ffe3 +818af89c40379a10074cfaba6d5968ecf667f1a68a7edaa18e8977ccb34e0829f237c5634fbd079e7f22928b277f1096 +b8e0af0be0a252b28df25d4a509f31878bcddf702af0e5553393c3dfd4a1f1247ad8dc2668bc8dedc9b41f6ad8e71b15 +811667ffb60bc4316e44bd04573503f5b4dc44d1ec824393a699c950e5fa085b146537ddd6a08a3fede7700396a0df7d +ad834cbf850b2f61ce799c4a0f8ab0c57039d4e1113933c50b0c00175171aadee84894d1376cf325bfd434c3deb44315 +a8b7dfcdb40373ba4d55e751ccfb9070554434df9e359fc165284ee3dc35db6fb6055657ecf5a9e9b7b8e2e1abea4375 +b56a5b9fd41c9d3f65532aa58bf71a38fcf07782e1ae0084dc537862fa02e6d66658b19d6f71c39cd5dbfac418da1837 +a935af5ed224b9533b41a7e79f872f6851591da9e9d906050ccd1b2c772a1d6d010c5fc7160c4f8cd7d3aa14c3bcdc26 +a81e580fc98692567b28323fc746f70c3139d989fb6aabf3529504d42d0620f05327e3385c2bd5faea010d60dd5c8bdf +a8b352054cdcde8ddb24989329a249b71498a5593a13edad1e913c795dcad3d24789abca9c7ed1d57efcc9e3156da479 +b0de8a2bd7f93284b2bc700e442f52ada16a22ad8d86329591547411c23fff0333b2ab0c9edf82bf7903ebf69916eed1 +843e9781b653d1a427f3534b2e86add49d308ca247546f9fcf565f9e08df921e4d969e1b8ed83f3f849e98c0f63e39be +84a4098c5dca9f73e827d44025473096101affd7193c40a0307e3215e850e753e9a08e6e74a442d57626ff26df77faac +b463eaaa2f3315b511c22a97fad353014d840a6a95fe0d457d0677e63e571407d7f5268f8775381a5e7adc3b4163eb88 +ad0417edaa16cfddc288eef4173aa7057ca4f81e815541ac588ef5f24b98d56fed6845deb6ae1a9740a28bb1cd8780a7 +9271963b8fb2288a96e07eac13c0543ec41abdc6d978bd7c44ae08251ea49994412b542c77c8208cd71fd8e7852d4a70 +8b68b6db9044d8bafc155d69e0daba95cd59d6afebb085791e999afed4f33a2479c633d31d534ff767b8cd433d591a23 +a6a06a0e433e385437d9996ce823abda9848754aa9cdd25ec8701af35c9ec15df999825669bbc2e17cedb597a96e8eeb +94d414bff8b6b8597634b77a77d1060db8e1af0d0ddfb737a9bf1c66c8430e93a425510af2464bce4a7b29bc66cf325b +b6514049562af1c6fb7d0e8df6987b020f0b7a6e721f4862e36b1ba0e19af19414ede04b346be22d348b50875803d1bf +a42c7fb34f2fbee8aaccd1d86672d0acdf4e6bb083ff0456512d7e1e43be041cc0924322fcd986e6e1bce5d5ecce6f92 +867cbdd169a52440ae0a75d33a28c7d00aa92b4b65aaac5e62aa53a8fc367c08ab8828cc8fa18b6e7d1f908d158e3382 +a6fe0b768fff3e4a6153e59a7b7508eb2ee8165eaf5274d41ac2812bd4563c4ca2b132f0e27ea2f1c98759cc3589b61c +b3eb1dba43d10b9e17ffec8def053fc96f9883bacb49330a089a0ca5b9ab0182e8b5111ad4aa55c1ce1b6f4afa5c70a3 +a1531351098bdfcda566ff4d811301c0305626c77f954a38420c490e7c684f517eb1a4e4bd2c3904a10bac889cba314a +92278d106ad2f27eacdb86bdb1faa0a07a93765bb79dcff191873c52253af83480114b2299ffe5324f9c31d0abbdbbd1 +8900ba95a90c447fb6fa1f528af3d7a378aec25feb0620516b6b97e54b328fc31af42e46a8ad5e6e3029d83a6f2bbe5f +86053d481179c1ac910d5e7b9a5de82794b442f20e854583512ce1f9c3f09e71d1bf97d6700fe776debfe1527ab97a82 +a32a60de492fc4340336416bccbd2591b5e414fca0aead82281212e24490acc01747537b3da783684e27aeb987245cc8 +9820fe8e0338f21797143f368177e3669a1f3894b40ae9fa3b353125f7c8e85cc424dcf89878f2c7667f65db3b1e4165 +934d64711b4348ac5e1395cc6a3215e5643b540f591380d254165486b0ec2a1d0d21c7d2c6310f9e0eed3d08ecf4b57c +b9fd32d589432eddcb66dc30ad78981360915854cc44b2afeb826b5d48a08e377dc91be66f5bf1e783d1a8bb320f7ccb +98c972cf01efff4fc2e485b47572e2d8dde22461d127ef401b71a111b0603203971e3cde40912643affd7341cd27e57a +8db6c1620760063edabd376f4399b6e1355462e04f5c81cdcb3989fdc00f9a466bc85ed899e886c89c149adad69edbad +ad7b7fda0aa6e2aa66a27235ac5cc680aa04b85dce329fc4be84f75c9c961120a3d9e446aa44539aaac8ea203eecb4eb +8ccb01eaf41d816ce69ebd57754859e263530915e775c4e7d9dac37b2457a9099b9ae9b4c6cb09eb5ff246e3c9320c59 +b895b83b5f7ca46e02697dbaa6157df6c7571864c83e504a8c77d965bc2ba97bf9353a71c56a020df64498bd40e30b21 +8018c07a81c522fbc25f2cb14f2321c61b98bd8962ed8eb7d5823dbe5d1958a5ec2fb5622fd0868e991bcb6cae016ea1 +95b16364e94d01b3664812264d7185032722a4afc23bdd33bc16ae87ee61816c741657c37138d9312cebfb5fcfbb3b2d +94a709209990a8b09bfb4b9581ab471aae3a29526eae861108b28edb84aab6d28f1d7a25dddd8150b70af34bee4ca2e4 +ae06c80839c5a13269b984ff4d8a5938c6f4d8d647b1b1daa8cf7f6145340b76a286cd615ec251a65501e6290162da50 +875cbd0694eeb90d3567da9dc7f570d97b02bd9cf17bfa011efdd48f1d580608a3213bff4006603b8b4079fa66bded10 +b27f88c455f025e1cd902097d6a224d76bdf9c9195adee30bef4a0b0411fff980787285896e1943a62271d0aca531446 +8024880cde783cdb2b863e3dd856be92bacc5b2a1347e96e039fe34279ce528560d2df7d4d1624a4595dbafb40529697 +8883d02c2a5c0e026d941c785128d4ac6f7a9de625ea735b7d6ff27a5ba10fa4d6370d450d99a855d919f40d64f86afc +a1beb985c45fdc30ac536f1c385b40b6113ef6fabc2f76d255490fe529468847a776efa674ba8fed72180f07d3f701f1 +ab83bd9b007561695210e3276fde72e507456ba277ad4c348a2aec7a6e9ebdc2277cb4bd0bca73bd79bd2240a1fc4456 +8db27f516153812149854fd6bb1250e843a3ae1c9637df818b08bd016a769d0497ab6087fe3b2fd4080882713607bf46 +b3891dde4e00d60386aeff161b4a0fbc30bb31ee7918ce5fc0b49aac3238a000ced192c9c4c08d90de3a0ba973d7cfd6 +90a2049a15c02e59024a7a1cb0adea97501c60b1c7442fbbe560054c3d69264e69627ac57b7d9be01bef498bb2a60198 +87df67a4bd72444b5faa4f3b067204c4927c869dd3b29ad192d859589a9b2c1d6d35ed68310081e140add254a9463092 +8f80986a8dc8a0d6408ebbcb4f234e76413c11cb0d66067f9436bb232373100f20a4fded60f08dec3525315abfaa8523 +b061e10beb12ba3683688a4ae3a91600d14878ef78a308d01b93e4918efc666450e3f7b0e56283468e218934231df98c +86b9e55f3783d62e381659d3e06699d788b88aab1ff99848db328a83c97d223f602201bf2127c5ecf419752fed0a224d +858d878e29925c87243e010020007f96fa33264e89c8693af12857b362aee3fac2244057e159651c476ebe1dfbd67bcb +8fd47cdef87d7a569ffce806d2c2dad100692d6c53e5f5dfc6e274f897dccadcee30fc6c6e61373961bbc1f3ecbfa698 +892f2822daf3df3a759bef03168c1cb07408df62e024747a788e94d2da325f880bb9c6e136c7f6643f45b021c6ccb654 +8714e37ac24f5a198f219e7c88a92172fc3db129e044e914663ac708d8101851e7c53fce79d32d0e6da74f2ccd1d30ff +ae95e1dbba8b9e2c8dfbe1c202e9ccfd04fa396470035a699b902fbd86d5e6a31732a7c8cae00b9a4f6e51c8d560c7c3 +b0cd058e77498e860fa20c5f8d9bd09bb249add1badf84ba8d1bd49e704b9b4bcd67a5c3d211840a2c8fefab3fea639b +b78e468d3a7da0dd481f333ae56534e2ef97587be2e259a458e25aa37952aed1cc5f835640f812d8052f5bada8f57b12 +835de7965c6b26e7ad1b92eb6f0261d1f376fa12d61eb618d9b342b597c9c117a5a8f6a36269aeea88072b4641e6b5bf +b4d0eb99136b3643468c9c48a20fad62785a60fbdd3c054efac4bd1fa7979b4c9ca6c2c0b18069c0912bea2f19832790 +a00c47315dc0700a850966836a95f3cebfde04dd094bde0742dee77b89a05b5ad655921f86fafd1e902938ff34d4c58d +ab13fa0afaa92229a71ee91efae6d1b15f14b6eacefffb7401d41d0d6db24e24a8dbe8ee19b4680ecb69d2a0cb4e84e7 +aa56c0fb18401210062dbc653df8e3732aa8921a1280e9737e99b26a0100a13a9cba8ad0317a69bba16193362ee0f030 +8b410324a6406b345df0fa25f541ac20b7313fa55832752f70cf4c79f43b0bd3d5b4cdc447e6ba7bca08d0edffa8e29c +893362241ae412d9e5df46506407595c58ffbd7fb1fdaf0694c3432470599291238997abe118bf7737e56a4f5c9dc292 +921618194a756be81cb49d6357cb392b32cc62d96c8ffb7e16d9659a0f226a0436bd378da7b835054dbe0de2c6372ef2 +94a2904f10994928ff5367b777e1430047736fbece33442cf452018bfdeae62e84cd75cf80f8468285e347d504c94111 +b4b81545b767f380bfe10e0fea9c3cc62ca8db40b43c83ffb245259378731298e3eb6c3bdc3a16932f88f5d8a86edc4d +936203c2453ff01c6fc635e4d54320d69e60047d805daae3b75633c2259108497b778f011e5a057249f11b2b888ea76c +b90bf6378d29339443c3f2008b1e2b5f0345f86e393027f14a295e583bf6e6c2b10f54b6dcc42079ff0d356c405b03bb +916913f550d327de2d8d6c7723dcef2e3869efaf95fd963d95c8980b97748c61ad8e2e629cead8577266d93fe39203bd +a033c6f3d5ecbabeb83eb363e54e5faa7ed2d7f4fb771b161762c4f003eac4e1afb236806b784baf2222cad54e2d3cd9 +ab289d4a5771147e6c29ff9ac2bf65d70081ea6c6af2d9b728c3c144574a31b5fd8632af57c18c389aa2cd994938bb0b +9488da2019ff13e290eeac132b491df58b5b7b23c2898ff1a67bffd7e9c9464c39bc8177a57950fd28589e3d9ff9c6c4 +a5abe42b2e0891851440fb2aa6c1d8a86b571bce8b80c8e9e2692e5cb6d45a1b2f055c9fc4c74a7cd292871604129ea9 +90bfef698e83c2ba4dc9304aa01edd274169a978b7154bca518daef394f55857d0d1922ebef3d91fc5ecb3b895d9e0ec +92328f1372b6406ec80786041b6d57018b8507e3881a08727aadfecfdfcfb0824394cbb1150117ac5da5d71b89e895ae +9719751c5f7a65ae2bed8aff7b4b8c34539ff011b259b7ff54f63f9d987b3fbdce5c99534ed561aadaf07bb6e939e208 +a151816774aa9379fccec21cf212429a1c68cf91b055cbb9d931f461a8d5616c693331a11ac5c6fcfbd17d84ee0b44e4 +a72977b1285618a45943ad00f33f37102e2885eccd2f76785254eeca495068fb1d8d49865343e9e8313c6c2c3b2024da +a6f5ad2e023a1585d90625c9f7094f0e8851c79f0eede8ec582ee8e063407cc5b8298e5fdc4c786e4fbbcecaf33e787e +82901e008febcea0c0a14ae21d985a397630e18ee6e346f4a449f23be228e8f338df567d30211a11180b94fbc5204bec +b9b57fdb8d14d1be87a25f89553b3966eb7869e0519ffdf4cc4d51f4cec90d68f7b81cdc0450e04207276e9c63ace721 +a06eabcf43585a001448f3dc30411f3d5b74fd0a695c81eda9981842ba2bb0081d3f5a8360aa18b6d43ef13ea78b293d +926fe48a7e8f07559b7237beff9504476dd97b5b4d67acd01a3633358a6ba4c7abed5c87683a11209aa2ee759888e00e +a716cd3a84a963e2a5a46145b6ef4ebce705de52bf2945c374152a1e41c228a9c4eae0b6d1e222c1eea8b9c13c002177 +8a9b5985df6fb32cdb06ba1591a977545444478f2fe985ed1b10de61c630f0a4693c2185d63f0dc0256b208072c43b17 +a8eab26ae0ebcdf96a59fad1dc2d5e83b94abb2ea1774b607023f9d9e0fe065853b1e2242e794f989a80a47f550c0bd9 +84adbf38164cd04f3d770a7f4b8eae7a5d25b4a803fb63c02b95b71b33e454319c44e07a760d22bf5f58e7e372d09a16 +90f443a3ba1b9129a0bee400b5b29d42e50bb2aa56b0022bbfc3c6f8d69db40299871ec7c1b68421cc89e1af6b13a39a +81c5a94b379eb98c494a8d0067c748ba47e87a2ada0105202ed7651eb4e5111a0cd8569b06ae68d392c4fd74a37833d2 +8f92324b14a1549ee0b186073a26691088e41556d33b54258fc6e0b000e9624156db4e97861a0ec22960e6c47ca8a1dd +8b021cd0fffe055068cc460aec3cc455952e2ac32be5fa060e0d1b6cf30ed15381618f801249e893b1b9f10dd82077b0 +b3e9f0dcb3d6f0b138f589fa54dfb01f849890ab97016372d004aac55103f363a64bc0e606ddf75430f1534a30fc522d +8fdfe64af891db89b25daa859864d479cb7599486bd6f36e593f8f2f839f942261ffc3eed5001a93fde44cbcdc24c583 +a9e4554373c5073e135874e2bacbee69c65308eb0785532fec6a37834e8d0b437b77a2f11cc63c87d7183b82cd9b6bc9 +b4c47daca723ad7193ac5098cad4dcab654186ec5ea5c0fd014a3ac39726be954565a901694ba211820c011fa1c59e18 +8835427e86cdceb4c11cbea331ed724e4e78af15e3bab5be54f6b926bf66b5d99bcc40dbc456d86342c9fa83a033c2d5 +8ea84590a400cedba047c2661378921a42f5ca0421da58c1bcb37bc686a2aed98afab3fa5e6ba3a51029390ef3cdf4d4 +b48551170fc479d69fffb00fae4fba301e92e37cae08f596db6f6489c3b7020edc074f9e8d7465b84e9dcef1b6b3aecc +a6f318b1eaab00836a330710e88bfe400395b3081485f6a212e3cba9463f6fe7864ba4f71e57a411ecdf2bcb4d189f96 +848d5137a39999141a79f4bdf91150796ba36352d8525821bf3bd6e070b352792d79147341b8254dd60fa8c36e9e2618 +a8526f8904b1eac4ae2a25534aa91e8031e9aac7b8f58d8f49897e920c36c0232f4a30aa6eed305deb0f7793c115b267 +b8b6a727c44c37a8388383e959d195d1d0e51a657d4ba360633d219d43c5df645383e2406c25f1d418e72b862c3a6e9b +92e64adf65b42c978f36dd03ab22ba983bfbb61944efccdb45b337ceb486beda99818bf20d32a545503c4572bb0a4983 +9653bb83df66260a0bd059cd4244ef7c661b089e403d26ba777d2090783ff31f963f5d3a9c125b1ad1a1d19134f3fc8d +a74e72355e71ae5eb36dc75191643500ca3e67f18833ee981010e7e7e60a68e1b01b05901eff05014b9ef29aa4829f45 +8b2139a5da14524cf6acc593144db23db424b95b8c7041d8f6c7a14a6725dda1cd09c42bb3ae26a5a3650affaa742800 +a60ddff4300ca44a7c7a00a1f98441ad1438e07c30275bc46551cee1b681926d2c825cc8f90399ee5f36bb9fbd07d3dd +a04e5e9958867a5acc15fdea0d88951cfebd37c657102f6ba1dcdaa5e46cf1c823ad0d98718e88e436f260b770599102 +95e977abeb70d46fe8d7584204770f14c856a77680607304ce58077550152733758e7a8b98b11b378540542b1175fecd +8c9ec93ed35a25ce00d61609e92d567459a45e39922ccd1c64ab512e292787125bd4164c00af4cf89fd3cf9deddcd8bb +819819ad0338250d9c89aceda9e217df12ac54e940c77fb8420575caa3fa78930689d0377ba88f16d38179a807135dc6 +8baafb379d4150ac382b14a64788d819146480d7a1dccd3deef6889686ded375900f5df069843ef14d754ad3d7540401 +ab827236996bb79b447714c6993af941c5ae66248df4d9a6f3650d44b853badb5c0cb67804210e07a7b9d66ca43092f6 +927656c3eac8d2eb575e3daeb77f9605771170c325bee6aeade10c083d42bd8dcbf3bcc3d929ea437001c7cf9a95e2da +af22b212d5ee44fd4197966b9690487c38a119cd6536cfb8c181f38a94610dd9e057f95774047a446504dd96dd11e326 +a44bd94b9e01e3ba36340f2ac2201ecb477495d4f1fb6726a6b439302deabb5a35d237c6a6aeb7e3b0a65649f8656716 +af367aeeae3bba14fbdb05bcc1a521000dd9d37f5c34ae56fb306d3dfda201d0329a8b6e89d98e15825cb3c6bfdb1194 +abcc4fbdea43e50ded9e2fb01464f4e87fb136e960141e8d39214f92794cfab5634f22cd40b18d8c0e501f2307aad23e +920786cbd674348b9853689915dfcab02cce2a4596d117962bce36aadddf4bdd143891e22f2c8015517039a64e8aede3 +8cde63b9bd57cb3ef743f1f3e8250669eed739e5fbd68c500a3cc0c12f93862a69aebcdbc69dd8f476c2eb307f572a53 +b967e65a5f1cd8d5d570f5e87e7e186fba51b9504f8e466392a76d8a971fb91fd9b7565bcc1647f50d7d15e48b93bc95 +8d5a87b25fedf5edd57d870304bfd9081dc78c3e3e3b38b997260a92edac7feccdaf24feb51822d2edc223b70bb4ed5f +b6cd5d340a57f8ec73723c4f3ecd6601620dc8137a3e75a5d3c578bc79a9cae86b379950c644dee2ff99dad780d025c1 +b6f0a8e754b7f52a85a2a2e6512cfd017f7fb0418d19bb318308951c4e242d3c65bbcb9748da9cbc91a738f9ca577332 +a89dcf7d410bccec385400dd96b1cc6af89026a431d0f531aa992cbd7bc8bfd7c5f360bcb665bda1d72efa17bb982551 +97788e7522427a46c4b6258d15623ef7a565712812fa80d001e1de8dc1791392702f3fa3cce5a8cd1c5755625a0ad10a +b5338fb5e137ff625b27c5148298f27ce8f493e2527c5d0facaa49f29cae34580d0d6c3c1074a2e46cd8db3f56004ea9 +8962f006d7b1095dd0dd132ffe7e87e328510c95ad893cf3b2ab21c177c5cf2c27f47d8856f87e9762c547be009d25c0 +87fee9ce9c26aa476e67e0791a809e0a06a8a98facf3faea730d438d3e516cdf75d645fa75c906e4e44ab9237a22c016 +b75ab972e1a1214bab0b38cc3e973d44bb233acda5b4291f5e110b6fb78fdcab93dc63f01168debd898e165f615be1f7 +b5a0fb52bca279d3853761a94b206acaf313df33ae6303d9b71edae90b66fc507adbc60fb11e758888736c81d5d80c0a +849b8f0005010e684701cd3a4e59e8c89e5fec59af6d2de5b6332cde03b865ea84f07f0b80ec3404380b0e148fbd2c24 +96e2b0b6fe78408f9208f809f5c40398100b2dac202c8c5c33c2189560dea868270a598c419871a5a2b67783354f6014 +b234b81f996142d0df2c719760bf996544820a03195a6dc0ff6a72543692f5a369bf63d1f0b477ef2fe7b3234e41f685 +b85e39bcf40da1a12a535740176f4de749a93824079deb5fdaa004f3282fdefaf5275e3418c88c419bd42a3dd2ed2b3b +a27279304b89a18a4e2b443246f2368fb8b15f46a34533179b6bd2ef683f6e98e222b7a32880b39b8fac1afa90133803 +8923c22cf15c9c1964213d725b337ece9ea854775a06f75f232c4859c7142a3942f418354e33066298aedfba3cb27e62 +b109f714311fb9bc431ef57911e2cad6a3949455b9f23255cd7edea35be629e07f845fe53e2b12a32305ee2f4f264f27 +b51e82ae5c7d48050e405897d0053e9ea4b2714d002e88f78c9a307cd50b9c6b3ee7cb86f86527be9d964b01895fab20 +90db256931c7f98bcf3bffff4d496739185e7a20f329ee7bffd4e0850a37739948ec745285703967f4ca50ec370cf68b +a0485ac0445d88dafac56bfba2563b020cfc370f54c1606c89d12cfd8a4d1336d2ba50306e476155a6f5b0e0a1f2d092 +a00754c3462e74bda928da855bbf90f9077db395e32f03cce9b2955546d900b72330d247b7d607b65e130f5b0d883de0 +8547d56727c3ad8b5c8ce622ed9ad86fe8cd78e6e4848c9845914b5063b17330bd10b46d8d3f18f83ca09ecb28d1afb2 +95b937b2a979bce0e159ac75c7d5d659be8599c92305e73e942aab414793364a3ec28c7c1c8491a5750ba84a29828d8d +b011e150f0294e45a0f4c69409999d0c2e602449dbd67ab95e8258466687cd733a0329083a31b03722f4e2580ddc95e9 +924651a733ad5e5d9adadad3ea6a6babb8e455c8d5f2cb5bdc83fa422e7752592190ccedaa827b866861e73506a6968e +a4d5180122f8e31503ae027e54da50f72f5cfb910a6f7309bd882b5cd666f454672591f1f20e461e182a47d03b47052a +ab19ae659c4f73ea3d21895269dbec583c7029955a36469124ebe295027010faab56c4a475973497f28e9a77c03b8fd0 +ae7ea1a803d0f439e91494f8f35fc1167dae23834c0c699ffe65d3da8b09f8df5a53195a99ca7b8558242279e69578fa +b9d63cf0e30f9800101b43b980bcd2f229758e74b21ad5354866b4e684791c08a184330dc316228a0d67fe0210f2bc4d +8c41629744391ddb96dcbbf9cd99b13d36e57d65962e0aeb92ebccf1c4cc769626feb3ec0363def08eceb102b3dd4ad6 +b2848ff24faf9e667a8c19d050a93896e9e75b86595f7b762c7c74ccdfb9db126ae094961fee7f5d1192776c1ac1a524 +af013bc29206743ce934d5887b8d0fb3667c89bda465d2321835a3618513fba6a459dd7566268220ffce7e0c97e22b2c +8bb799e36db1132da8e8b028ea8487dd3266b4628c56dfae4ea275f3c47c78e3d7445ab8d0aaee4cbf42148b3a148175 +ae2b81fd47c038b5195a52ab8431f0d3cab4cf24c4237252d955aad2156adc16dda9d3270157e0bfe5a44022e5c051ef +8e0129213b1698d2ec6df132356805a8633ba79e672e586dfef664ffccca71834253ba14f296da962651fcba2c002622 +a1ae30b500ae77cd9bbb803d737b4a5991cc780618ac22b5cc179efd8fe10afb8c135457f2e7b86ded485ea12eae70e5 +8a39723077b7c0df6e3bf6548afa3910c214ee275951fbe5155a39473be98099626ea14d844630a6fa90292b9594665d +a628386c79b61aa7314b01d9814aeec20c2a66e3deda322a39957e7135c2e52b1da486d1b9cd61c87afb22c1d10f6462 +97867f469b01249820aadd9a54e12d4fdadd4555f2d530450e1f8f6d2dae57360578e2c2c8ba41e3b5950df596537a98 +97f192d0457c217affa5a24267dd16cb4c01de8fefde9df4884e1906d2f22e73382dcee6c7d910bf6430bb03f4a4f1e1 +86d5b5739de8442dc74d0d8dc78e49210fe11bf8c6ff0f0faecbc47b64812d6b28c8afddf6d9c0212f1988451d6ccb1c +8ff3312ce9693cd4a9f4b8e75bd805f65b0790ee43fd9e075fe4cebc87185bdf161335049819f22530f54fed2779a5b9 +8dc41d85548bee5d51941d55752a500bde3c5a8f3b362da4eec307a963968e26605048a111c9166d448b8dddf6f53892 +996bdfd004b534151e309ac925fa5ee7801c9da4f6b4c43e156d1158b134535a2a3956e1255e0dd72ac2af6bddaebcaf +aead652704b788bf4983c8f725c644c327a6e9f6683215f5c826c09f82fd2e40631791f51d14e6aded91fdc018d45501 +991ffab58a82b98ed8fc7b00c3faca153589fe09cebf6a137ad506387a1ca4dba475b0e4a1b9bdad829f1422facaec39 +9652e6c4ae084221d6bad855ec0bc11b5f855c6efba67f644e0902ab790a98861cecc6ce047c68273c3aa7eeb2f4c7d9 +b88b816507aaeea6dc92b861eabdc96988b74d7883f20a4b30ba249158acaff3c50d261742fc9ad2e9eba888a8d59065 +acd028a51e16c07a10d2073b9d03070457ac5f1246365295a1359d015c460b92b4861125fabe6f114de8197045df408d +806d3cd9d02d41c49179fe7dac5b05dcfc9a205a283135d4f008d0771c58e6f963d7ad0f6798606edda718eb5c7ff3ed +b9b71f1657a6b206fc40159a941e127f252a7b324dea864ecd804f48c0ed86da9778a925fb65491204a92bc2a26fef32 +80ed67bd0e74350c875abedc0e07fd42ce7cb926f0f3fb1949c6ac73f2300b5a14a5c6f6ff8aed99d5ea5029bb8e7ae6 +9875f67a7a473714e4dd75ee0c763ddf88101532d9680724b3848fef69e218b04a96b90f88e0f4409aa40b9a21507ecc +b4a2bb1b421e5243e5e7576a0672dc19f9f70315a03f6411c19f76616ffbb70fc5dc0e57fd4ab85e24ea2261b7ce38ab +879723002ce43e6c75ba2246f51436efe3376242beff987d025c3c4476495af32d52a54fad5d9ec329a442b93bcff1ce +a4121efbefd9c3eb143619afa52a916f199c75024908047763b29466cdfc837c2fcc894aca63044c33c41c777e529b5b +895f637b497a9766714a3d9e3c275a1f0c9ddab105bf4c8b7e663f36cd79492022415bb4938c1a4849bda73106ace77c +b119acb8b161ce4384a924645a248a656a831af526cd337d97e08405415b9dd22060849c76b88a4785eb5e7214961759 +802e712f4c0a17009c4be6c1e5ba2ca3b82adcb68793ec81f4489b7985babd8a3873d544de63d5e5de0cb4dc5048c030 +ab111051e4651b910c68ecfdc33f2d99e7bf4182df68cedbdbbcac219a543e04d93ecb2763fe32b40c095c7ca193c331 +855c73ef6afc6bcaab4c1e6388519fd5cbb682f91995bebd558167715db454f38012291beccea8186a3fb7045c685b67 +a29d02ec6d9baf84c19dfd0eb378307703bfafc0744b73335550f3cd1b647275e70215f02d1f4ab82a5df4d4e12dd938 +91510a45b8a50cac982d2db8faf8318352418c3f1c59bc6bc95eab0089d5d3a3a215533c415380e50b7928b9d388ff89 +8286e7a2751ca4e23ea7a15851ad96d2cadf5b47f39f43165dde40d38ddb33f63a07bc00600c22e41d68a66fd8a0fa51 +a413d4e619b63799dd0f42ac57e99628d338b676d52aec2bb0d1bb39155ad9344b50cdfe1fe643ff041f1bc9e2cec833 +85524e5bb43ae58784d7e0966a664717289e541c8fcaff651541718d79a718f040a70aa8daf735f6635dabfc85c00663 +97f0d48a4028ff4266faf1c6997b6ad27404daa50ca4420c00b90f0b3e2d82ef8134d0a04108a74955e61e8dfeac082c +8df6145c6cc39034c2f7331d488b8a411931c8faa25d99c5432831292637fd983d4f6b1a6f55522b4a42a462d63c6845 +98c2060f67a916991b391e67fcf23e5f305112807fe95bdddb8ce6c4084126557e4c5f003afb32e30bc6808b30d4b526 +8964246b3c2b8f7312f0a99647c38ef41daf70d2b99b112412356e680185da6810ab8ee0855ad7409d334173bcc4438f +b56c2c416a7069c14bdb3f2e208c5a6ad5aac1cbe5b1faf99dc89c7141d0259d1c6250be9d9195500c4a41182ad2ec3d +b7864583a4cae3b1083dcdcff7f123d24a69920a57d6594d0b7219e31bf0e236682442b6499a1f6795cfeb4f5f236695 +a064f94139bf1b70d476bde97099631b1284aa6b4d87f16bfc65c075e58b2f1b3c2d057605259f806e545674a1169881 +80d1bc4acf14c0f487cd57c5d6157b7f38917e93cb660f1c25e474fcdcac3c3dfda50f6bcccfd6676bae25c4b6b5014e +8ad9a4976c4e3e282843518149fcf5d454240740f4b91466f6310b7216d23d70b9b47c42870293252f29f092f330967a +914197593d2d99d784c704cad7ecd3f0b9f55dce03fc928d13e1a1034566c4de754f1c2a5ade047b0956415fe40399ec +8d77f5e29c572ec3c0ca39cbae2072ba4102403265b3d8c347a00386da9c0b8688d6e3280c96037c300d57b3545f3773 +abfdf79d935fd4f06a04938d6580a8cbf9735f0d498f49677f26e73d3b34b7075d525afcb4f14ef1632cb375bef7dd55 +a97a8c446e3edc86efac7bda5e2e5d0158c909552a3bf86151df20ece63b8d18b608f477286fb1c7f05605ab7e6a7c2c +8618d946c7fd62486551c35486fa466bdfcdc63c941e4cff5a01fbbe566b7ea9dc763cbe73e2acae063060b619a212a9 +8d03ee468070936004b06acf64b868963f721f37faa09887f8a82c155ad5c5732572a6855b531db58af03b1afe034a18 +8d3247f75966ea63935ef6049f7c889c1651374adb446f49499fc9191dbcde7ea33cbc1f1e2d3d1756b6e69870404643 +afc853c3a3facb4ba0267512b8242327cd88007cef3bf549184ee891b5ddc8c27267bae7700758ad5bc32753ebf55dae +80df863eaea289de5a2101f2288046fdbfaa64f2cf1d6419a0e0eb8c93e3880d3a3fdf4940f7524ea1514eef77fb514e +8434b5888c2b51d12d57da6fb7392fff29393c2e3bfee8e3f9d395e23ddc016f10ebe3e3182d9584fddbd93a6effcefc +b78cbb4c9e80e3808c8f006dc3148a59a9cace55bcbb20dd27597557f931e5df7eb3efd18d880fe63466636701a8925e +acb140e44098414ae513b6ef38480e4f6180c6d5f9d1ca40ae7fbadb8b046829f79c97fe2cc663cbccd5ccf3994180c6 +936cb8dc959e1fc574f6bb31f28b756499532ebb79b2c97ff58b720d1cd50dc24b1c17d3beb853ba76cb8334106ce807 +adda2116d9fab2c214ec10c0b75f7f1d75e0dd01e9c3e295a0a126af0ea2c66373d977f0aefdda2e569c0a25f4921d0e +89a5cefb80c92dcad7653b1545f11701d6312aef392986835d048f39d5bc062cabc8a9501c5439c2b922efc5f04954d0 +b9acb52747ce7f759b9cdc781f54938968c7eeacb27c1a080474e59394a55ae1d5734caf22d80289d3392aab76441e89 +8564f72ce60f15a4225f1a223d757ebd19300e341fd9c1fe5a8ece8776c69c601938fa2d5c21b0935bd2bb593293272b +a5567d7b277c4ebf80e09c7e200c20d6cb27acbaa118c66ef71cbccb33ee3ddce0e0f57b77277ae1db9c66ed6e2d8f30 +b82e9c2d8df1cdd3b2417bf316d53e9f3cb58473c4cb5383f521ef53e0af961ef916e4f6557a6d8b4655ec01415231cd +aa816dfd2814c8a25bd2cbaf66303ee49784df471bac4b3188074ea30816f00f425234454d40d8ad8035aa925d74da36 +9919f384df20faaa2d226b521cab207dd2b62420d25ebbda28c9b2ca76a2a52203b2ad7844c1a25f5c75f005c5a83149 +b24a6aa35c2d0f87e36598b36224c64427cd69642b6f9c1bd478a62c70f8ee69f85028648f6603b4f04fb21355f2afb1 +892e044bdb1276b455eac2204be105e1821f987c2570494b1f32aa09506caba7ed343cd09b1bc126fed5e0fda3d0eaad +af0e01a3ad954dc048de18bc46bb1c4971db2467e839698e4dd05cd1adcb9261013fe9fd0cafb946c0b586f6aad86d4e +ac152f0a9ace425378daf02510eb7923ff1ed2c0f8d1deb918e4efb63655de1ba58c96438e9aa23abdf2431dc771370d +ad8c7419c097709347e2394195924e09617b47ac5c7a84aeb9deab8975f22155de0f70cf20d8a976551b14e3a2683a2b +808f14f67ae801536fb70a5898ab86e50ad35340cffd0648daed2f2c4564c9ad538034b2a179a6a8bfa27e9d93b4cbe0 +80a74ab7ce4769db93cfa695a166db95f0a9c47885ff826ad5d93310f36d6b18b5351c67c858b9837b925e85a1995b63 +95b88c3cdd64401c345828f4e4754b1a88b4875a14c08a668b90acd499b3b858842669ecd73a46c5d9f1de32ec1a0120 +8ddbd770b7b18a5917eb43926fa05004e819f1d1ead05b915269e4a86b53e0633a90559007e59f6705a3769e2126ac56 +ab6db5fc220754f19948bef98844e6e38dd623565d1695e1198040c228ac4fd863c1f168cac1d036bbfb718d9d8dd036 +97bef628e977c069e60c395a17740e0e1bc1828f5607ae7f30ce5a0c95f02b53af2ad062700a75212e462aa22c3c5465 +b68d465e04fd17ca98501e61eccb0ce30401855e98046e0c1debba71c2153d6a7a704aa36a6f12454696e78e87181cdc +a79cfdd048f4181e005bd0fbac0a8424495474956b58ce858d2b700fb0f931c406282bd33bfa25c8991bc528d12a69c1 +843f55fa0a6a0969daf2b48080738f30b269b2e7ec123a799e5b203c0b3b4b956dc95d095bc6550b0013918cdff8a225 +b683cdf2823036827e5b454bfe04af9bec1850d25a7a7a44aee7696b6ff0468b7ed6885a41dde2b8f3ecc4aec880c3d2 +8b500796e82acdc89778e0c0f230f744fb05f762000fee877bcf57e8fb703d212dbc2374887bdc2e7b7a273d83a85798 +ac35a8ee87bafecb1a87f15abc7ccf4109aab4ac91d357821e417f9b1474d196c38cc41cd13667f68d1ffab5e79a6e92 +b6e517739390cfed5b395d33b14bce7cd7aaece57fe79a7eb3cbf150dc10765c3ea9fef7976a21a2243687e6eea38ef6 +b53901eeee26692273365b789f2a60afc9b5f0df229c6d21b07016cf4c0e7985beec748aeca52262f68084393ab038e1 +ac4804f33d8ba2b4854ca3537bd8bf2dda72d4e94ff7ecaaf9bd3b7f098343d74d765471ef80072ae34f860b052cbfb1 +8c6a30a93f1dde18039bbdd1ef294552bf79856e20bce863e4b8dd72d906be3ff22468ff3610e06b5a7d1745dde7ead9 +88f0607fa3b7cefe20a02115572b16fc3222be86bb19e592c86c48afbe7e0dd523492b0c29a3bceb9a20f5538bc3134c +a660b801bbddad725975ddf9a8f606f76ecef831f954be224d6178c368e1c72d346f00c4a4c95c289b62d36f2af323cf +a75b9a6aea9542b698938dcd6cc2f6fe0c43e29f64b2f54aeb05d35fac73d41aa7fd750af4fa9333644aab8db90775b9 +83e1b7129d963d1cd076c3baa5fe422148e939273db173e4d59d1858a7d841eacac7fe817d15ab8f8a493bf46c2045e6 +9060a2e9c24de11f9c70e039b5ffe9e6d32f1ae39f3dda263610df2265d917679e689898e4a8bd84ad34613dca5e3761 +b42fc8b863a2af15e04d1fe6693c09b46007c0b8298973fb4762b45b4590ad7fe0aa758918b2fe5ed1ed0359754fd955 +83e6de7860fb256ecf7b47506a5e557d0fb0aefe57fb513c7dee2bd9604712d08ca26adca7ba9a54b712372a7c585a26 +90586e9cbbf71475ecd3e7b5753b286804dcce61e165502a82b960099e79272de8b7494b8877b54ae838eb5d0f71af2f +b2e4b0d21208f73b7b75e08df80cde20c4578e117d37092a490af82354e2afd3a7dbab46fa2d12fcb731cdaece69c2ba +a010961239bb8809fc7fb4aa08fa30d33a130f9f417ee9ea60f587dcc5ef4e1b7abcdcbf8e848ecdcb7972ef6af46e78 +8f511fd58d1e3403a5eefdc0a4ba6b8af848c7efddbf9575ee84449facde05ae9a24aa41a5725416467f6fbd11369c52 +b24ebbd2d4482eb618cea1ac4fbfd9ed8c46c0988a27259300a7ce5ce1bb256aeca0357828cbbc4cf0dfafbf586040e1 +b3ea29e9cca55250e9b7b9bd854edae40f0f0cc65fe478cd468795d1288cc20d7b34ced33bd1356f1f54a4291faa877d +8a8b20f222d9e65bbde33638033972e7d44c6a310b92a9d9c5273b324c4ad1a94f2a10cbce8300c34dbd9beb618c877d +b2436a9a647dc3f12c550e4ddc5b010e6f9cb3f3504742d377384b625fc38f5b71710a49fb73ffaf95b9856047c98201 +a13f8b77c70621e421be94c7412454adc1937b9e09845c2853ef72cdbe500e5c1bf08e3c8b8d6b8eff4bce5b8dec9213 +b25de8780c80d779e6c2e3c4e839a5a107d55b9cccc3ad7c575f9fe37ef44b35db4c1b58f6114a5f2f9ca11e1eb9c5fa +96ba6ad4358c7a645e5edb07d23836cbd35c47d9a66937d09486570e68da3c8f72a578bd2e14188d3acc17e563a652d7 +a7f55989814051fda73f83b5f1a3d5385cd31dc34baf94b37c208b3eaca008ff696fd7f41e2ecffc2dd586de905bf613 +882d0c7c81e58eb9560349f35c35e4498dcde7af7be8d7974b79d262304c26ab67ffa5ed287bb193d5f0ab46b4096015 +a607158f0c1fd0377a8ee5e9715ac230abf97406c19b233d22f5911ebe716967cc10425546dc44e40c38bd6c2b4bca2e +87e8cde50e5d852d3f073a43d652f7186bac7354612517cfaecd4a1b942f06fef6f14546279c0dc0262e2997b835b2a4 +a1c93acc6db9d5ee426fb4a0b846bb7a7b8d5915bec777a9fe6907246b0beafb8938941c8c79ed6082155f75dbc1e332 +b1e4f61457b86f76cd93eafd7536f72baf239ce5a62bd5a8085a34e90576b1e118e25002d2de49b01d6e9a245ee7d3a2 +a0435fe9a4bd1031ec5973a103ec9396b2ce9fd982f6d9ed780fa80ac06a6e47a0a6eb2daf52df1dc9292db622ee9fa3 +b66d8e8a1717e4bfa42083b6ef4490e090a73168b2912f2111743e089027be0a4945a229ecf5d0b5eec11b23f0e11303 +8eb764f26904eea4f4169be6e75beaa6a39e4eb524625a15a78befe3d8e3cc82692d9b135590c20ed460d6e4ba630ef7 +b7e4aea6bb09829e53fe83e53f49a7a331a6d7bf76e0073d758577e6d6fbe63dab642b23657355cad48896ad8715119c +8f94207982373a99ffa282673f192aa98d0c4461fb77c31dc4549628bd9687a249f1b3c66b1840929341e42516c5c64a +a9c673cb247b13e17fa5e616f0399b7f5c7ad043e143e44ae68855a840870ab3d2aad737ebcf74c2cc9688d17ef3a794 +b02635104dd28c02068985256975c0af783899eb996e37d021d9a35238deeea9e836760db21869be7b6c82aa687ded29 +b33bc0966389710812b5f6698afa3e9c84839a1b85492ba11e6ded26695260abf66be6fb355d12d3a8524966f0f89e0f +a79c0dd09506951c33da3cbc23843fd02d641fc24c640a205e6e8150240372847312b9381fb03c5d301fe4dbee8d0da2 +b74de6f3a2c502b5b658ebe8a9b7edd78afd036f5a2736aa06502863b6865d131b9e3542e72a86fa2e1d2db4927661ed +99e365def1452ff9fb4b9eccd36ff4154d128469ba5bd73e83ae457ab53977cf6fc04a5d05bdcde357ab539e34bd9fe0 +b4f2bfb95abb47c67870aa6ca38ac8f3ae1b1a2bed064b1be7ff90865ea12e4930fcf66429c7ecd1183fae4a01539386 +ae4bde87f36b912e92398bf72e11d5389e93b2de1b277d7ed4b6fb5a9ab9f71a959ec3bcb734c11079440fe42b86fafd +b826459e568efdeeb66688482b67ef5020787275123fd3192f979b6175e3b0ed59e17cb734a0a052bf13f0afc7bd237c +a99dd735f4a7c85cb23dcc7f4835f9ab32026886909aaa95876b98029c37dc4d621726c872d3a9e50403443c958f4029 +99083545034768010988bf8a9f34486c2cd9da27a1d10db3ab86eb69a1dd9c8ee723e7da4ef2aced63c1dbd53ccc52cb +8ac3209349f0142546c714ef7e9d1b094aab5469b8f080c0a37cb0362da5349e108760f272fbba770aa468e48d9a34c4 +af5f48ed74b21e3f2c1430192adb4b804dc873cd7e8f07130c556c30e7b78df0ef5a14b205368848fa9185e5a68dee0d +b8b741b65d68df89443523ba74203226f1e0d13bab073d183662d124e83e76cd318b2bfff09879c04d81b577ac895638 +914abe4282d11176d4f2f08c6f15e6c2d0cde1ab4de00bbe888015c205f51929d97296a0a8d3ca5641f085a29ea89505 +83ec306b2a9a6780efafe799df90b1aebdbff7d47921a136ea8a5648b9708a97231245a1082fea38e47ecafbbe000528 +95d6b58d70b388dfcee4eda0c9805362ccfb60a87603add565b175b2c14ed92999dfdb0d3724ee3e5d30535f282641e9 +97eeb4de607c8306e1d4e494f0d5db126d53fd04983ab5674ec5996b971899e734fa4011f2c889da21154ea1e76dbd2f +84ff21977fbd873ea06bec444d4ec9ff0e3902edc29dfa25f3bed269b3709e3116e99dc06cc3e77f53c53b736bf8fc29 +8ecf483874a040a4a1c293af145094fedf203a5eb37c3e165857e108cce3e1210e0bfc0f26f4ae5e2194024929ba034d +97d9b92b2ef34609d69402167f81bce225ed3a95718a3b403f702b93e96a121a8f7f072d0ff47e8b25164e204d1576bf +ab87c39cca1803b4e84b32e40ff30289e3cbbcfbe16a70f9e025643824752359be1f10c3e5398df402b6fec64d5a3537 +af84ca57e6944332884b5c84750afe0d5950015e127acec161853d55d48fd864c7da8d59cc5aba4ceceac650b813fcc0 +b1d23d98edbe7089ce0a8432e0eb3b427c350fb4bb39eb2aca3c2bef68c432078cb9b4b2c4966255e00e734fa616638b +8e2b5252e0ea96d40835ebfb5693af49946509975682d68651396d6bb1463f09e75fd0afa04ccea49893b5b9c3e77e40 +8db25e762f1d4a89a9a1cbc61c01698e775906bc88a921b2905735457a35df9ab84bae12e1b1b8dafadd50212f1acda1 +b5f7cd163a801770a4034e2b837e00191b0ac63a2b91032ae9a99ec182d748798df48a14644935fabdbac9a43a26749a +998e7232e5906843d6272d4e04f3f00ca41a57e6dcc393c68b5b5899e6d3f23001913a24383ed00955d5ec823dbd3844 +ab2110a5174ae55ebb0a788f753597bd060ee8d6beafc5f7ce25046ea036dba939d67104bba91103d7838b50e36703d1 +a211972a4f6a0303bec6c86f5c23c0d25ab4df0ba25876cbaad66ae010b5a00aa0c5daded85e4326261a17a563508a25 +a49f53496a4041a01e07f2c2cf1e84e2ee726917bb103fd267451b9b7bb1331c0afde85a79a55409bfde27328b2a4745 +934e915c67c7fc47adeabdde49f63f04644fe234672003be2aa0a2454dc8d9288f94293478936a450f2e3f249d395b5b +b6e69e9d6808ff7f60a01b7aea6781495d7a20f5b547852d3f0af727a7434209d3015a9dd04cbe3e272918e32e345508 +b348d3462092b5c6fead7e515e09611438db8d69650876dd3b56226e303252bbeb9e9f3b888fb911445b0c87132a1d0e +8d6510334a905efe5a32001e167f1ba06f9bc4af7ffbf11b7f7bf3c0076b5cca373d8c47e98c1ba8755bb22632bfe0e7 +a2d5200f20985dcd473d119ee97e1c0fafafa0f191185bfed9cac429cef8198d17665dac4f70342eea66e6e4a7370d58 +8dd7eb6b1841b3f33425a158d33a172b79b2dc8a01378e4174e67a1a4c8f4b887f02c7c3a8f354ed9eac718155bcdf37 +b16ca19388642f71afcd9f7007b490d82f83210ac1a989da9d4bf4c419de07af8c048cd301ec7e01b9d06abda7c169d5 +93cb2d847d1a88de8c1c9d5b3c83efd0b7afb3682942bd2c8ab5ef35b33dc31a097a3e181daab8630d4e840b677216dc +a8b648c769e77a7b41c0c689fe2fba9bc585067e004bcb1732cb7b1618e97b317781c36c23a00680fc780b58c301a789 +918c321100d57712866bdae84edf7e42df30a32853af257e0cb4da028842a43b49e775f3cecb85cd817269c728de7319 +a7b0f6ce42e00c519e69b2c78fd9b75a2e7103e5892d3c1afd70c9b5b9e706180a4bf73dbb2d3eed52bfd521103ec5b3 +90041994af3322b010891356afd8115340bd7fd7ba328716fbc4fe458236c8cad8c7564ae473d6091ec3a54bdab524c0 +acb1ac83809573846231f9be2dc5f3e986cc36dd9574a620b1cced45bad0b11ea957ce8c6cbf964a0af916781c574f05 +ac54677dc002698fc4d454c7beb862ad085d0514f92576f3485a44c0cb47afb9db2c085058918a3508f9b3de0137d97c +8dea56e1bfa150e442f8484b2952b116781d08cfa3072d08657cc09b0217276efc4ab6f5fd726bfd826f6976ced8da29 +a2b09e25baf01d4364b5205fa0c4dea84ef8fe03709113b034f88a0f0a502a81bf92c1d4641e2ac9f3a6f4203d3645ee +b95fe37aa351b4292691a9c2e547224c37ec2751a31ecce59810cb2ae0993da6fbe5efe0ab82f164462fa3764b6eb20f +a3498947e91a3a540e86940be664fc82f1e83ff41a0d95eb84b925e820602a41b7393c8b458bd4ebbe574a754586787a +aa2516d3620c832e5728fefdb1af0be30c871cbad4b166a7a4565af676e73bddc2f2f51acc603b3a022056daad2b330e +a9251b56467fb55f64c70729e2ec77a59d7eac79cc0b4b25ee405ac02aea46bf1cbc858bc773934a6d9bea57cb528185 +ae8c0a4ca7ba6bdca8764bac98df0581f00358db904e57867e6ffdf15542e55f7bad2dedac152ef88038b466ed901934 +b0881e27e52cc6a57c4f3f278dffc7f63a9174b68bc867c16d8a151d9cc4d0aeb703d1074d1927faa9ffb43e10912c9a +b67138465d6654ded486d18e682f11a238d6a65d90f23d6b13eb6a1b7471efbac9ada6345dfb13e5432196d2a256829a +944c69a6f1126edd38f6eef60b8a5bd17147ab511e44e8e0a442e87244d8f35236ee0b8d3dac0631f8598f16486a5f74 +995679dbe03dec775da26708cb9200dabcad983825f1ba601eb9395f9da350ca71e8af61dbff4c668fd0eebac7e4e356 +89de362f02dc14de6995d43cdea3c854a0986c605ba5eb5dacf24e3a85983229bc99a2fcf50aba3df59f0fb20daffe29 +84607f0e2d078df22d0866285614f5d78cf7697c94a7d1b5e02b770101ceecbfd53806b377b124a7320d9fed65000b97 +93e3faab60050dac76ab44a29bcd521813e76ec8e4ae22712d77bb489bb49f98f9087acfd6a77016a09a42ddedab2d73 +b7d64a7a35f21747b8e6a874be31ba770c0d13cbd41448411994e8cebb59591295a26bacbf74ee91e248a5b111aacca0 +8dcad429a2b0d66b9eb8c1c3924d7a72979727db6a535526a3518bed2a9532d12aad1c5a778824ca4cb98e3e513f85f8 +980882895faa347bd2fd1dda7b8ee7ed49e69843afe646f677b371eecc7a10e0f4e40bb55f28995a40080df471876816 +89e8e7fb51df79971e2f7bf65783614abbb0d7f3f1b4a15d3f0d160deafa7ed1c446d9a5ae1a77160d4dd94ceed8af13 +93fda8d350392e9c4d4ffe6534f7e7be53f32483d9319093e8436fbb8166a3c01085dc858373e65c7f4d014e0dc2bab7 +897521a87b7ebf7152de5260c0875e3c7df1c53e734c672569219ee6f9bd196c5ecef159b6a1d3b7cd95e91b9b8803ff +b59affa408a0f7bd7930fa3b88750fd043ce672c10a3adeba95a12f23f0dda1793f761a86f7409ce1e6fd3b3b7195381 +b4422ccc12f4fe99c530cda610053af9ffe635b633d52492fd81271d1f6f91b87171d572d5bd0e46ff63e221fb2fc4a5 +a4542cdf3346ee0867c08d630c2aefc57442f1c05c0eba52d223bfdca5e9d0bb80775cff6ce2e28aa2730231fd7b1bb1 +a7d297bb09118b914d286e5d1e87bdf13f7d174b988e38fb5427902e8e8c674072f36b19055a1070abcf357f8668f35b +9213b0ae24b7cb43ae95e25c09fead8bdbac55141694137d67eb5eab5e90a348a13d4d4d2cbc6436fc4f4f9f7334ced2 +8aed71a0d116d832a372b42a0bb92a1980f3edf8189bdbaed7cde89fc0418b3ab21a04f5c6e1d3b8edf73f1f62bd6b15 +a6c47d77d714c285c84c6b9458cbec5e3b191c0502dffd10ce049cf1ea27ddf868ef0cff13a2377289fa6c932b8e4f28 +92f45622ec02483f2c1e07075a6695416d3768c8984856f284f40734346d56cb5b3322f20c2c9f0ef8e58ddc294a309a +af6450d02b79ac9fc79f35655b58fd3619cd5d38c5317564b453f5f2d79d7a030bf767e399fe01b658a72fbd2cac2356 +a3c01fed5240eb8a61ffa8ff4a120dbcebb53b8e19845949c77fb4f9b2c3dd52c7001df6219ad2f76c785a4ee0f64a2a +af3136bfe8f774187bdf87555a1ac505322a956229a285d28bab1c88d4f4d12245af8dff35914a62e90e49f3dce6acb0 +b20e21d28444fc96737958cd951858fda324b924b4d3d08932540fd4b87150f053db6985b96903906ce83dde0578cbb2 +b7978101071268d1f485134b4dfd1e35f89b82c7d99ae91f58b6745f5e0273b7e06f3b23009033ecc3e41b2e9e85219b +9104b7d75245b784187175912cc0ad869e12f1983b98e052710fb33663224362bffd69ceed43e7d4ad7f998c0a699eb7 +a7624cd71b92699ce3fde0e747976ee04ee820032ac45dd27d769edf3b3379a4b8db358e50c9d057c63b5a9b13d76bcd +9354a76f294005de8c59db10e638ae6e8c6d6b86a699d8da93143da8478d36116211c788d8285d8e01ea6647dfcaa1aa +b85935c04cae14af9848db5339ab6420122c041075ec1549314e3c9c5a610d9b794ea3617c50ca7af6b4aec8b06bc7dd +ad6835a62311c84b30ce90e86c91c0f31c4a44bf0a1db65bf331b7cf530cca0488efaac009ab9ed14c1d487da9e88feb +80339f0245cc37a42bd14cd58d2a8d50c554364d3a8485d0520ea6d2c83db3597bf51a858b10c838bfc8b6bc35619638 +b370420ac1a011f6d8f930511b788708ccf2fe23ca7b775b65faa5f5a15c112a4667ed6496ae452baf2204e9ce0dbf09 +8ceab3dadca807a1c8de58ac5788313419c37bc89603692c7a4d96e2311b7fe9e813cc691a7e25a242828cdf98f8bbcd +ac1526ebc6bd4ac92ee1b239f915e494d0279fbd065e4cab1f1b8a1663f67daa89560f6c99bbc3e63fa845520316d2e6 +8240ab0bc36a29d43ec3059c7e6355ff39567e135f93b243145d3ada97fd1c970743819e0d58bd5171967daec144e7a1 +a99743192a6f1967511b2d3038cc73edacb7e85f84b2926d8880d932d2fa12f5215592311a7548494b68a87ec70c93eb +8ffffc31c235997e59ab33c2f79f468399eb52b776fd7968f37a73e41949111957434f2c0a27645ab34c741eb627cd1f +8949d955309415d6d2cf6ee682ccd0427565142c1bfe43b17c38de05cd7185c48549a35b67665a0380f51aef10b62a8e +9614f727a9dac8ecd22b5b81b6e14d34f516db23a1a7d81771ddaa11f516ed04d4e78b78fda5dc9c276a55372f44c4d4 +aa85d3ef157407bd8aa74032f66bc375fddaff90c612470b5ff5d93659f8c3523b2d1b6937b3cc4201c2aa339621180e +86f8fe8bf4c262dc6a04620a848e3844f5e39a2e1700c960f20ee66d4a559a90141ef4e5091d0f32acb1e915af1e0472 +b3af2eb785b00588371beb3b49536b7919a3f2175d4817de5dcbf7fcc20c512852ef0f313327fd0589b10173f77b92e0 +8388703c512eea59190351f3bd2cce83ff8bcb3c5aefc114cccf9e9b3f78200d8034c3ebe60448aaf6c912f0ff8f0cc4 +95d0dbbbf08ec1ed3975fe7dd542be0a05156a2b3db5092825d918a849411ee536ed958201f74a5513e9743674d6658d +8d1a48802f1a2db247e633ddf61d3ef7a2c062c48dda59bf858916e04f56651a7d51e367d6535964ebf3ae6d2b21b421 +971436871bfe868f25247145a55802945409b3150008535b372c949760d7949dd2fdb40d9b96ae7473bc8f6e9b83ecdb +8ca431728ac0f156763090828a7b6d860bf591e5b9dd3bb3b7f3ba0ca74191f9710ee55efd32db7d18eab5b479cee8a4 +81e28f1a506e84c2b9aba1df720cb50e0b597b2c22f98acc34e710c934cc6f97dcaf33d589e845c2c1f6d8716d05ccac +8f43b11d3f00c41d16c9bc9bc0c44227c056bd77de4f1ca9a799418c5601e744f99066bef47da2d9088ae88eb259327c +8d330aa52744c08ef98cc5599eec8b9b4dd18aa01b803f1d1ca0e29b74f1aa2886ed0224390fc377af25852851fbee03 +a06f5b203b67134c685039ec2bdbcc787353e2575ce73a415db24a517c0c31b59d1de89f12b97cbef0219fb6a1e90a20 +9269a5f49bbb8fec1a387b5d105df88a027de615d5ca6afae20fe89b11746f8d23880db78dac238c955fc8bb3de18046 +af5074b3bc0656421c314547b45b5abd3045ca1b17f5e34ba39d8c1f7928a55d4ca5ea9c2ab59a55909b25255233e04e +8e7ee5d733c8e08f3fb7d85f0628de3de6835121672c65374905dc6d19e02fa2df14c13d5e9835dacd609a4df09abd26 +a9b9aaf83d31e879dfb8e73a0708801b4dbdb5d7c8654b27d2c0f5797ebcacc8d00a82143e2060f0917c9d41f1a03de6 +904872aa1c093cb00e1c8e369a3bdae6931c5b1ed705dd3bffba243dc4f42df3e7d7cf70303d513b34d2245743d765cf +8a4d6b3b1d6afe67383c66693f70b397e510be28e3d97dbc8ec543d699b6cbb0e72eb90a7f65e83cf9f7ef50fb18b128 +a914de13916e6a0dc0e0fefecb3a443cca80d83276513b70c22c6e566a2d41acbd33a0e2836ee09abeffd3a4894e437e +b9c408f5f05934b0aefab301ba22f8254c5ebbf5405b6aa788f76e4b328c150b395f441e3566015a0deb3eca89afe9ff +8d32aa2c81b2a8b89f347c2e0b6567b2117ddbb778fda8a3f19004b7f5aa9dd814b9b3ad35f9223715d2447b2d12f159 +8230e8b9c84cada1bf14ea6aa9ecdadd978d893cf5962fee6c7167ed21239210ea491987f2c8f2e8cfea8c140704ca28 +a5d7b6285fea51c6f21d0976a7c3a97baa3d733a201bfaac0994db6c65611d91c5fc0ebc2a7724ee02b371e575573649 +a54f00a9530f6930069f5e3a8b8b1d52ee1def0aad1763e3c609ec07f25410969b43d5943a94c235ed5eb207b33a402e +a8dc6e96399b81397734c61c3a8154e55a670fa25fa5854b3c66734cbb4ec0d8f6ba650ee3c71da3773ffc9e37abf8bd +8841fbfae1af4d400d49f74495f864804f043416c09c64705251d021b3ab7881f134a00b0241e61010617d04979d747d +95acea7ff4861cc969c1d8cc8775c5eae014ad6e2e0e2d0a911dd916c34ae69f53eef779cc24ff1eac18c2b478d3ba2b +a5dce74abcfb8c68031b47364bd9baf71a91db01e45514ab6216f5eb582ef8fe9b06aaa02f17be8b93392d9b19ab9c06 +89e111169e4ae2f4016c07c574a3bdacd8d2f359561fbbdaa3474de9bc24ef8936784dfe6fe0e29a13cac85a3e622b61 +a4c511af6bdf3892939aab651828259e4ef6ebecfdd503ecc14e61001575b313a89e209cb55a77ec19a64d29ada066ef +923c62156fbf3a44926ffb5dc71f7cef602dbe941a98c61f019a27a18a50c16b6135b6099fe04a2e1dc88a6cad989fb7 +afb9191c541b61afa0ef14652e563cc5a557842ce2afea13e21507dde0ebbe6da5233af949c998c00865c79bb3d45ec8 +8a1f0ad65cb2b225931f41dc53547d756111ecbf5bc57c5ee2cc1ffd61b126d0389d311ffe26cf06eaead95af09c5ca3 +9040b20b5ac2e1a9d30abf7a4eea1ec2db8f3077cb2cfc8736b37222d8d3937f5d9f421167086dc5551e9f0bd2522d07 +b6d888b8c6bd448dccaf99c3f690d47f802e134709ce102fb6f6fc68156943c0762be6f386338163e01eed2d1dd5f734 +b94f0e27bbcda793e4a272603b3dcc739d3bf3207798df7319f8dc9d37cbd850e3724bdd30498c929debad971950223c +9769827767be9d7bacba1b687289e0794c6fe630d33c9b607da1f6a65e3f34cb8bd65327d9287c8c5f3c8b5f6d3d133e +aaac72c993aa2356c9a6a030950441de42b2d746bace29865382f0ef54835bc96958b2f00237d805ee6a69ca82117c1b +a2b1f027d80c1b0e79bfc7dd252e095b436fba23a97a1b2b16cdd39fd39a49e06a1ca9a1345c4dbb3d601ffa99f42bdc +b3fa0ad1478ca571e8aa230921f95d81aed7eca00275a51b33aadabd5cb9c530030691d1242a6ff24e2d4cfd72a47203 +a43ed4368e78daad51b9bf1a685b1e1bfe05bed7340d4a00df718133f686690c99198b60031513328fc353c6825a5f2f +965e145711ecf998b01a18843cbb8db6b91ff46f668229281d4ca52236c4d40804ebc54276e9c168d2a2bfc299bcf397 +ae18e6efc6f54c1d9230210ac859c2f19180f31d2e37a94da2983a4264dbb58ad328ab3cbc6884ce4637c8c2390f7fc1 +83a9200486d4d85f5671643b6daf3d0290b2e41520fb7ea7030e7e342d7789023da6a293a3984308b27eb55f879ad99d +b925fb6ca83479355a44abbcdf182bfac8a3c7cce6cfc7962be277ce34460eb837c561257569be3cb28023208dea80dd +9583dd991b62ae4bd5f379ccd3cec72cfae1c08137ddfbacc659a9641e7d5a82083de60005f74fc807bd2acd218d0789 +ae73bc32e9ff5926e1e06c07a3963080881b976c9875777f8e4cf96af91bf41bdbed4bd77e91253b8ec3c15b4a6d3977 +b2a3ea90aa398717ba7d8c46743e4c487b63c5abb140555d8d20e5115df2f70d3c84a2cb9a5e0536b2d93d24f271b38d +91d119d3bf1d34cd839eb69c6de998b78482ab66bc93fa97e31fb9592f36cdfcd673f52366f8c8e8877e313b92d4a2ad +a1907e20120902cf68912cc3046f8806cabbd7673e80218814cb088e080dd93b5dccba395b13e0025f5755c183276c3a +b2e2011df72504065ec4c12cbc2137b95cfcd1355509671feb7b00dbf7f8d500476a49754cb7fb9219cb5cba7c8afe01 +a48589fb7a74a3dfd782cb3503e6294a81dbb6adb412887569f9408e9079371edbd9822388e0b7ec8d3297ba270f53ef +a203909bfe196ac65ed3e6800d577b6ca5c8fe1d40f7f925a43852951e38883f2ffd250a9e16fab3ed3dc1249650247b +997ac293722a8b98f7e819f8e6c2d4c5bd1103b82d489d8b8aabeb905e95450b9b75bd61442cf68cc957212ec1c55617 +9895a3de62395c33509b153b7820bd94fd2b011f0cac135fcf916482f1eda272ecc79f83a61837e99c3a3c4ab2c5c2a2 +98c2ece4d49a64ec8e06407a0585081003bcef88af35210e22eab91169f8f0c044d611494b755e5bd915804b1d857747 +8bc6dd083b36d076ddf0e0bb1bb87cfd059283ddabb3886f02eb7e27f1f0539b2819527b56b5c13436523c4603ac1d12 +85ab8b7a696333c82dd5e179e12b2e127e67d911de609ff9a03cab95cbeedb1f364aa1f2b5e59353e4ba0d177f996151 +a9478e214afa68c395aa2c7daf8ba1627feb71ad6d8bc7339734cdcdd5a42838e032736c28e6251c808d5a4875ef0d06 +8c53f62cf06a35321c8af3871ee4459768d0745ebf48942b9f464206309f42fc7b2c50f196ae1e43b664f0e2e718a23a +8ba80662f6642d8866e832ec8082a4204ebc993fc304c4b794666856de0407620131a18dc053597bb40a3de0bf8aca22 +8c8fac6b911785d1561a985580c03fb2ebc613ae33e486a92638aa7d4493374118d9a6d9d99121e29c68c3d67ee4e3f3 +90f2c793eee07ad90157040b30558bb3b0164e8ddf856389d6742cf5bd1c712e4c6a8e5678da70a8e9e242ec7864117e +954abed8f6d58896b7f6438c9780236c1c83b02d60a29fa7361559e619e5bc9d67b3646ee39ffafe2b3019bb3357fb50 +b79874f757a33085e1e751544de8fe3afbea92e0234f9c00254c2b36115a16ee46f085f22aa66e0c9177e5106f51b03b +aa148b287cf4f60c64f774282b421aae075f0eaa93a45aab4927750f47e2ef0b811d1846bbb15eeb2f293c80a7612e83 +a588d8825e7b0168d45499dcff6faf0dfe1ba4f090fdc7c06d50344960c0121f10ad109b0b9d13b06ef22de5a04eef87 +8f61ec93d14ebfa9c31731f9ef0fb8907505fedc79378e9a3f65c27bed4d74b41e129c97672ce5f567d897befbceec8c +a008218633f1da10efd01c155f7ed739faec902da6dc48e9f19ccbc8d32bb318d71806285cf2003de2c907bbdd4f8b22 +88ad82c66f7085632d7e348d69da84200c53594553acf5432b50dd1e87f410c802dfea91be3cf804e3117ce13103f23e +8498dba17de0318af227a3f9ed86df37a5c33f9a538be9823f8dce4efc3579e8296cb3b7200cee7c5e0bfd9da23a4b69 +b3c0342231dffe4c9bc7d9265597bc8cc4a82e2980ac6d1407108db5b00349dc91d5116fab51cf2802d58f05f653861d +b3f2730455f9bf5a058598bc60f47740117ba51f6a767e1134516a4e42338b513f377027acf8825da5c4d047a62984fd +816360914fbc9d8b865157bfab07aeb7b90bb5a7c5cd64847b1c3184a52266cd3f8f8f3ef99309ba2edc4622304bacc0 +8fd21b2315b44a52d60b39ebc45970a47b9495f42b88217ae057bebcd3ea0e2476c0c3d13de7f72016ae12ae966a008d +b62014485bc217a0fe892ef1aef0e59604ad5a868face7a93f77a70ba3d7413443fbe7a44552a784d8eae1acb1d1c52b +a905822507e431b35f56724f6c8d2e93b0607ed7a4533073a99cce2b7c1c35367382447073a53036dfdb0d04978ccf2a +81672e39c2b31845142963351de3d9cd04c67c806fdfe77467867463dbbd8a9b0e2400ccc55016e57cbedb02d83a0544 +90919c970ec668de8ec48a2a73bb75cb94f0f8380c79a7909fd8084df61ecd631476ddd474b27103c6817c8f3f260db9 +8fbe37dfb04bf1d3029f8070fd988fc5e4b585e61eab6a8b66caf0ffef979d3ed6a662cd99468ce98ec802e985da5fad +950939aabb90b57a3d667f9820880eb0c4fee5c27fe211ce8ecd34663c21b5543c810b3676111d079ac98644c75ee0ae +b06201ec3c3cfdaf864a66af128effee8ec42d25f1e173c1edf9207979fa52c871757000c591d71a9b6cde40f5001a06 +a79054e8febd0450c96ac7a5fd6bf419c4b17a5926f3bc23a8616f0cfbc2849d97470174cd1baa7c739b12615334b6b7 +81c7391b2a1844ed26a84f054b5f03865b442b7a8d614cd44805b5705fe6a356ac182b66a3c8d415132e389efac5f6b2 +825af1563d0fe53925ec9ac0df65d8211b333474e59359bf1bde8861eecd03f2ac74534d34b7e61031227c2fa7a74e1e +b60dd9bf036f1825295cd2014ef1f6d520cf729b4d6cee0b42cb871b60ae539b27c83aa3f96ee3d490ec27ce7e915115 +89ca43d5b7f3622b42df7887572297a7f52d5204d85e2e1ac6e5d7aa7f8aaea5e3a07280477d910db025d17cd2e7373b +b93a2bc9b1b597f0e514fde76ce5bfb6e61eee39cbf1971ea6db38c3ecb055e7913ec8cd07fb0b0ffae3ca345883101c +8d45546bc30266b20c6c59fc4339eb633155aa58f115a8f976d13789eaae20a95b064fedead247c46665cc13ba856663 +aa8eacfe00e8a4d9815de3f7619d9c420629ada6489933ca66a571bf6c044d08b391e0d9eec7d1cbebe8def1e7523f1e +b32fefc59a0d0319ccb1946b351ed70445d78d9fbb536fa710d3162b9659f10288f12d82b32ecc026d55f16cbad55441 +99c7c45c34044c056b24e8f57123ba5e2c2c039e9f038a66899362840cffe021733e078866a8708504cdc35816cb335d +80def162c134540d5ec071b25ccc3eef4efe158be453af41a310b7916c49ec0ce06bb43dfee96b6d77339e11587de448 +b5f2fa4f68f6a26bcb70d8eab62ad73509c08ee7aa622a14b3d16973ffff508ce6f1aff9ced77b8dcfef7319245cf2de +b4d0436019e779c789464716e1741c189e8945dab7f3072720bd9aa89882fa5b085a1755c48da21541f3cd70a41b0a71 +931e798ef672e1472f4f84c727a101e70d77b3a9f0c0803a5220958d6bbeb8aeeb56c769ab472a3d6451249a13a3f56e +918c10a84de268aa8f1ba24b38fe55ff907be07b1e86b4a4adbf305c0d705c1cf5f65ce99e03e11676cedc89f1a4f331 +8e55a8413b823715ccd92daee357cedd797e69a0e78b6fcdacb7318646b9903dfe05e5501f47b3c52e74055b9eb619a4 +8b329bb63e6c985d7d072dff4680b3f8b1217ed20543277386bd30ec25240d9dc378837dcd5cf4fd9548658635f4c537 +8c2be5386052b22986b33dbc63c5afacb6d0095495564ba4aa28fc8c880a3c78242fb083248d788ed928deb1e30a82c2 +83a2b7bdfcbd25d6b059f27218e009ecb5ecc4da68ead885e00216411d8222062ca42f21c4d9cfa19c31522080af677b +9620334d2633e85646b2e2fc48dc6c3f09c64ef1706ed78a3bb6ce1f6b274a727364df71e97531dfdcb392f70f27f536 +b6c84970ec04545121ec3b79376f4e45053c97e8bf2b11922cc2490a429c38735466097ecb81cc9d9692c74d2fb8abc8 +8e55d707dcf265c5ae29a32c27ce66f200fddb724faa5bbf145ef42280ef645fa2f0cc3cfe2db8599b26c83b91e077df +b910b96b763966402bbebd68a32c15a225ec21e1357fa298478c5981a4310e556103fef0c73bd8903e11c4ed2c065647 +a8fd933a0e9fe8c459809bd93b8ce153e2af55df94b61a1490736b19c89469954da8b72dbd072d798fc06fc3d7a3d60a +811b279c113828e114fd82c2070caa7eb089a46c8cabf865f9c77354a77ebebe0c4c6400dda0e66dd017cfc44d76851d +8ed03e91c331afb3ad6e42767e1b3e8d3a35fb831805ff1b5fd3e91878e04027ff5af1165a3ac295f1578faf2c83b581 +95bf53683d64a0621bf1ca6ee17446783f6c535b7a54d6ea57723487a215759a54f886597a55dfdd560424e368ab2759 +a9bea378768fb1d7ba365a16531c51fc1975f1c73caf2a0891da28509805fa84e2a8db7c6ccfbc620e9002317abf174c +b8308250891015deaf851c4e5a4cf4704d104f94064418488d7e3076d49f36240dcf6fdcf83f45fe8a1d97fb02e3db59 +adcda6b63da21f4074f142f8e7f3a2274f624c733e3a4001054a1809711529c61356aa087f73aed877a58ccb41d38d12 +b80e7869239ae26d1da2e6683f064d1dc93cf4a2b66e9439b3ad9b25324e969bf98014760d29e6b8de7ff152ef498d0f +8e9bf968911df3bb5e3a7655e9d8143e91ee87f14464d7ba9c86e1e31b03ab31b91eda121281b79cd974d9ed2657e33e +9007277e8335a43e6bc3c2f5f98c0ba7024a679b7156aeefe964f1a962e5ac82154ac39d1ffbad85a8f2440f3c1e354b +9422b9d670e997b7c919a429499f38e863c69c6a4d2bb28d85e36ae0895c620f68b71e39eba785e3d39a45be91507757 +926094e01132938000d82dd9a571fef5ef104cd25b4015a25e3442af0329e585aaad5472f0e7a69899ba2d6f734b40aa +95552d8057f7e32c24d69e4d6c51c98403f198a20c5be8826254d19cab2f84d5758e2220cea7e38b7c8a7a23178fd564 +8abcf8dcc8488bcc9ab23c51b9e7a0d91dfc7bebe88b7ed370ee68eceba643e939c5eae66a4aa5fe85120751780e351c +a91bf8198f029e6a4cf6f0cc39b629e9aeff1c77b8739e1d5c73d8c1d3fb5c8f6f23e27b435bf10b5b4ec1cf6a7249ed +b932d87ee3a4b81341511f90fe5aa36c571e8b914f25abcc33dd40ca67a3f6444fe9362c1434744e4af18d6e045c54a3 +a8e960c2be9b1d805d387b3ebe2134d421a65f1fd4c1b4cccdce78f9926f139eea78e3afb449b3d6dd19b5d16ace48fe +a7e2f57cce509fe66707eaba9b4c042c1be93fd6034a9b51d1d30c45c4363eac79d54663d525c9873ab0eec0b1cc4ed3 +aa162a31c2078f4b080199debf24494a8dfdfb9d8fc85b198a861b12a629c73128c55a883e4c2de3dfed6e0e1b83eeab +b5a4d075433eaf4115717a84b4dc37f843d44bba0bf820c92ecdedd5afb61be60f7708c8a151a678d9d5c0ae531bffb7 +b56ab96f7a463c0079e05dc766f3a6a31cae5c5044947734ebe0a26e01367c6763cc8de6c2ee2f3b8218f05bef217474 +b60792ac506b901065a8bc0180a86e028fe34b62ceae1ad640c759538ebf3a2ad9c8c927d662deed6f489ff3ff7813c4 +8c8c2cdf075504d12d441a58542e1f8e4bdf92b3ee4775e836b2734c5ec1e3df919b931386417d04489a1dca806c87d2 +8ed78e91e5c4a68894cefc2f7fa71f02e5e12d40f1bb74332139bc7be4d92c24e07d5ece0e82150ed474aa1337af4c18 +87119c22ff8aa31150bde537d863cad661cc5159b12f084cc319224c533f0deb28526ed8568d00a1441e7d8bb4f05673 +83a60ba5a9cccf22cebadf7318b706c9f29abd25db0e2fc1c802965351b53cbf316df72ee3e9b2d3ae7f3c4494cfdff1 +b73b6a9fdd3e7463fbdaabc9a885b7c82201ad867d1bced1c2484300a01cbbb3f1e21afa95d4c7cbb6cb983416b63b90 +b1d89ad16981ff9217708090d4017662d8838f21f3a3296cffe14590b533905fa06a20e40dd497bd291fa4dfd1bfc511 +8abde560083e071a402e3c7bf31930f537f67d2a7bbc734a7480b1b760aa712ebd1cbcb65b00e11e384e980222fe14a9 +89c731d8f31afea8bdc9c32527bdca257f2a840764d40f6e49403b8e75ae51017d505ea4fff91bf28b6f3a1bc65b8bbc +80e9ac8e077e86ad050ee73dfce268a69564ff1b8419e9c236d981fe7a5f0c2bc756e8603ec604b3b9e36da8fe10a49c +b4f1eea0f304898b1323c6382732e6f40e556bfc68af9ce73f6d54e92f5f23cc4f78eb3f43d578d81e7627fb40f092b3 +a0e3a8d1348f8f153e08ac4839232d75d1d6e81b5de184ec4724f8213baf98d3fe739a96f6b39d79a053b628c3a09981 +a6915ba0b52ffe4a381bbb8ff3791d9d3b848bf89b3bacbb2a7d2e5ae21f1353cdc304b3cb6e82416f7e604035c27d7e +b2c4c9cdfdd2fc9a340ba3ade9423344b9f429e8c7e20a8abbf26400376e312f3ae35d1c456be99dfb5c02fc8a36cbfa +9657d57ca0641825a0aa5687f3f87659d893f33aee819bafa5b1ca1db554811c1c844f971e278606e3a2f096defdc67c +a4ad24d0a557704ada24d8e27a15604bca28679e260b2c69ccc8e6cae5499866724b700605a90df7dfb35130756939b9 +b18d9ea6682f73a1f99a9a4fc98c38fcda02c1a18e8c5fc080cf935a2ac877dc5223fca273dcde190b906178d0fd05bc +8ea5fefad0799c885f50ff10d94bd0af5b99b0a446cd1f367ae5ff529cc47e09f3018115f3c0ccac2fa05bb65b84945e +92450d52e6c7d13ebfcdf5674d6761bbae2fc5aabc865d35d031b588c383e0a64cf69a73dc93948632e2b98f74a5ed86 +a356f171a98df4ec5a96d556eaccc6ad34b4238aafcf0e94ece27cdbb491749fc9692e78b84dfe80bdef2914079d34b5 +b918703a4d3507d266414712ba8eb7ad17da07cc5f952b5c62ef130cc6ed1ae3bf01237fc8848c179725bdddd465b301 +ad2b0554570bfc9d97510cf59bc38e10ca54a93649c30ac9919bd0255e43bf525ab11b74f78a51ac0973cd0c5a5dcb54 +a7ecaf4b631d179d32ac1632390d95196a0035e00da6c0e6e13b5c09ae44b15ae6c21538b5a31b73bc5f650ecd979b59 +a37704eb4d728df2a367e59fcb6c26023136230e37f3b8a2f3ceeb1467f5cd30186fc0116f98b64a8146fd2c5903e8d9 +b09373ce92314678299ae10ec1f93c702911beb4115c6b5ba6efbcab9c7afb599f59793912df70a98868bce6545a33dd +b52a878a1393094fd2b93f2d1eccabf2830ab10800ba4cc24dcc7849cd0978733263aef2fcb766a7cb575a7a99383db8 +8dac097e006fda4fb9d6d7ae52adabd9448ebc8d5bd5b38ac0c4ed38ceb510763174f7adfb0b473c38e52147ccab4239 +86b19c41efb949937d74a7875549ee5e997f9fdac7f7198085afda233cf74341a38d0ca3767c76cd35f875b89a35f78c +99f0d927e5ad25cd134f1c70b72631cc6b5cb4ddb86c0642b900464e33d971213a5239dddaf71f7a42f2d6d02a12dcc6 +8355c38806c335d747d4e97f0083fb96585677da18b409a85175ec35dc3f74671817b34203eb18c2f729717ce083ede8 +abb3603adb061a036eae0afa5f23d79c3b62442e0e3bcdeef896f88995585c1105cd3065410368456a4d36b5b0485a83 +9051c5c0011784885187d04749f774b9b4f6bc594b0e4e18226de79dedc4d7aefa3529c3d2c728e180f96f3e204d578b +91888213e7d321d0bfac884edbd5cb756b280753bb5f8bc6acfc208f525757beca24bdf86fc68d3d8736ef176a960b49 +91258bd7ce6e3b7516fe2f5391a368d826da299e0e99b1f82eaa44b62b110ab696adc92debab8ba098a52f38dfb3c5d8 +96e3907340dffa9da3602d3b94bacff7e1bb8649edd3b9bbd06e1bc6781e78f91ababab12c0b9be7c66dfedc7001b66e +9513555688fcfb12ba63952ab36a67b36affdd71f7b843e8eb99ccbd45421698024608233efbdc905eaeb26b334b33af +9913ca9bcf11eeb408da02e4317c5ca0010fb2f4490b282ddb758001c08b438c3b35351a8cbe10b7fffc1293ccd22d4b +85dc2471860ebca88e5a2766161fdd77f926d2a34825d1134a30418f91a741759668e32fd1e37c415d07ab5824338e8a +8b128917e828a0b5eb6fa8ed72b52fae2dfaf74febee69a2e2f87e8df702f0c5bc0fb620c8d1d2a07f35a15ec9c0f5a8 +964c39e7840c130b01bb481ae7bfc92682b0f124c9c383f9dbf3027f2249151925f4faf36905af476a54778d69da3f48 +80671ece658cf850e522d46d25678f934ce6df043f25f8707235125765d40c2eaaf39eda6092f75039b22cb58bf2c29d +ad4bb0e79fdaa340b1347a46b0f64e801c72a89770dda0a6e4bfd35f2df5146fce9934e4baecb1c2671077c771eb8089 +80b3bd3adc6cf198fcd997f8867d2839a2eb28f57390352ec423b8a14cc1f2ab21c6e286505d6a21fb134dcd8d8f11cf +a26d46a6b8a75748895a1d599e7fd120d896340e79813167a400b2fe463452532a4cab419074663fe1d29fa716b76a33 +82b1f3a8a1df29207d7ff020809113ab06080a7f0c631f76ad33f47cdfb6a567143144df97b4ed7f676d929195b04bba +ad96633a3744648ff0a2e4491e8219c9c6ba6e655cb058c36320a8f72cd5f72c00bddf97083d07650ea9ddc005fc1ff4 +91d0783788626c91662359dc3ff36a8bcc6831e3f4114f85c99910256b1d8f88a8612f53c7c417d55581dea486f38926 +84edd9e87ff3d193ebb25f43474c33fe502a1e2100fd3f93fda6520f5e42214cc12e9f8045f99aa2423a0ee35e671854 +b55e06a4b1fc3ff9a5520e0b7c8b5ac11b28385cce78d91ce93b82f1bd7f7afdd4195d0c13a76e80d0ed5a4f12325fa7 +b0b15c7ddede2b81d9c835ecaa887650622e75d0d85f81b8bbec7ef24e9a31a9c9e3de1f382d8c76d878d1b01373f6c8 +b1adb47c20f29784116b80f3670182d01b17612d5d91bd6502b0dcecdcf072541f582aafc5e7dd9a765cad52151684f4 +8efd1018df9c9e9814a9c48f68c168551b999914a6719229f0c5bf0f20a288a2f5ba4a48ba966c5bffb0fbd346a4fcc6 +b34ea2bd3269a4ddb2fbf2514401d2712fc46c22642f3557e3b9c7acbce9b454dcf789573ede9aa14f39605fdd03f8c4 +a9e1428ce24eacfc460aec2e787c053327ba612f50d93510d58b2cb0f13291ca3d16358325ab3e86693fe686e4f526f7 +91eac7361af4c66f725c153da665a3c55aca9ae73ead84ca2662cf736fe6a348a301be1954723206dda4a2120202954b +a6f02db89739c686407825fa7e84000ceedb9bd943e8a0908fef6f0d35dbc33c336072ba65e33e15ecfcd5714d01c2f0 +a25666faa12e843a80365c0fef7d328a480c6e3cb7f224763c11d8cbabd0e7e91a5b647585ee905cc036afca14842bae +b4348576439cd2e48c01cb9cded7cc4a0ea364ab936dd679ddc7d58b48807e7fab070f2f1ea88595b11af4500849026a +a8c6c731e0d0464ef7e4fc1b049065eb4ce100c01e1a376365c636a0b23851022bf55805963bc15eb57434a837e81167 +b0952937b154e3a4c206f96cd96c76ba37624956b0e4d43470bdd97b4af878326b589e3eaee82fc192437123096799a2 +97d07ec31ecc9923192e48d37df2cf08750050fb452dcfbdb350fbc43e146bae3590c5b732b31ebfa1ce5d884ad5ad57 +a69359aebbfe4cbc4d39d178150039fbf284cbc0edc68a6bd635ee3a1c76569a4a575c907fff691b2a4d82a384c2945f +b321c2c0f6b5902ee9056cce7404d858da9a573d27348c1a6bfea29b2746f2aee7abcb6192504e5a583b0caeaba117d7 +a74e738aa6eb4eea58855ae6f422af22812fb388c83aacca5bd5fa4a88d4c01463174a229aea2830c348dd9ab9307854 +94306a3b106bc1644346bc45c05cdc8287811d5c86cad691bde0c65d6a686eb9c0ce79ad91baa4547e5d058ae8bf7310 +b64140fd77a07633e4ca8d60786452311dcdb8ce7095ba51dad8486f57c3bf4e69bced92603f71da992a48ad817ab275 +affe7f4310f1dc68e5e3cd640bedf864f51bfb46bb752063bfc18e95930021f784e509261ff9c560f53000c361b142d1 +b0d2fee222c6f963ba3385547f921a48964da031d737892604f8f2677d4905dbf615046db57eae6c6dd756709ae6932a +81700c66aad7c2e51168e028b0fe086dea75d3b17d93a4dc1f47a6a0f025df0bae1c8c997901837ad859a84197e7bb00 +aa4ac5fdd602f8b79cace18690e67bad557a93d00c0e295074185e8c6b4059a65495d9971685de2fc01d2171ac8b706a +a8becb3a64fdf35d65d2857898dcf8053b5057a73ab8c5bb5324af1a8015cff47efb85dc3eae7364cd5c850b7962bedf +b72ea09bd0b72f8cde3466f359ea69b194ede93dced534efba1b9ebc6f3bd53942fe2965e992e82edb6050cac4ed88dd +85bb8dd7eef023a251fb6f220af54687747f4c91983ff728163c4618ffac40ee6edc29a0aa6d455276bbe017f63757c2 +85a485254a11b4c4a943d9ec509c0dd1cbfc0ff5273a00cf5c9f0babec973efb15348e5d9451b548293d778e3a2b62a5 +b109f3ac809391e772b589c196b013db69a9b2b10ac3898feb70b986973731f30722b573cd0c9324158ec20416825385 +8a4eb579a840d438bed008644f373ea9ba2f28470d50cf1d70af38ba0e17326c948527b1719dd1bd9ac656ebd5aedd10 +a52e9d66ead5ee1e02ce6108e4ded790d8ec83164a0fa275ab1f89a32200726c8e988d66df131df9e62dd80203c13dce +b541cee9febf15d252475507e11d65c4b7819c26cf6d90352f5e8a8f5c63e254eddf22df0c35a7be5b244233e8e4ee5e +8153c297772adf4603c39349142f98cc15baeccaeae10c3230ee87d62255f6814d88d6ed208c368d2c02332426589748 +970dc9782f1828474e9fab7dcdec19aa106725465a5844caed948eef5c9e48199c1b6bc1a637ed7864116927e84bc65a +a975a920624967f4ecc77ea5d9869c434caa64c330024194615a8d0640c5d4d4fb139ea11a0c73a5c6ae6dd3fbf0ab5d +811f0f9e0c12acfb4b9dca359eaef3bed18083bad96188befc036ad3143b121fff4777ca6dc70a835bbc4921bd25f5ff +82341c6ebdb97c8b72910da95c7eebccd1308b6a92999886aab552f0642882d5c7cc60931577d200efd6066530c998dd +860f7162c2f5fd1c0953c6ce75bd8c52eaa48032b914410681b8cc05e00b64130d1f96ec5a52df66a04c78a9f9f42981 +8a578e674875571fe1a0459843495a5ee1d9fb6cd684b244feb9488f999a46f43363938cd0542879ea18ed14fba10a6e +8df217aba4da6781f0f5139aced472025523ed6e17e504511c04b677ca8197488e237d8bb5dff7b6b3898cd5a6393dd5 +b2c9230ad35d7b471d3aee6f771517cf3145ad26200bd6fe9c7cf28120e2945fed402e212d2330a692f97bb9ac4dcf12 +b78b89e29e8b782603b222cc8724eeb83b2d9d56bc02f59a3c899ab76429dc721358b07dcdaf422f59520b7e7ab4fb55 +82682a5617843c4ac8d4efb4c3ce715c76c1da2c3bab1ede387db503f3489c1bfdfc07d9231d96f955df84fd225bc81b +b0f53725cc610e78b8e8a4e6823a2ffe44dd15a9a5bc8151ab7a3787ddd97e1d7f2f0e6efd2876e5f96417157143e3bf +92c5a93233085e2b244519078770c7192af62f3562113abc8902f9d72591eacf52bd15ce78653ab9170d5067606287f8 +a43ef97dcd9b6ad288846bf31fccf78df72f94bc7ad768baf5bf0d5dfa27bd74ffcc6b6c6ed1d1f09e09be3afa5eaedf +817d43bd684a261fb30f709f7926cc4e1a31fd3a1a5e7e53ba4d664856827b340d7867e23d55617ab3514c8a26a7040d +a599e22d3286b32fafaaf79bd5b0c5b72f6bf266ec68948478f055391336d756b58f9afea0167b961fd94234989f0f02 +b70db7d8e8356df2e2070f8d658e560081442f3f3b95e20f4bf30106835d76161101163659d5d12cc0f335fb042dc66e +b8f725b70c957aa3cd6b4bef0d9647393f7c9e0b7343e92439372f0e9aa3ceddd0cb9c30be331742b87c53f2eb030593 +b2fb5e7762f26036e7e966f4454f886758804d1f4c2da17f3d13b0b67ca337f1fd89fd3cc798b07da6e05e8582c9537b +a377f944dccc300921e238ed67989872338137fe57f04cb5a913c787842e08b8a1adcfb4d2200abdc911fc1c766a7092 +b82e98a606071c2a33f2ad44e7ace6d9471d5434500de8307b5d4e0083e3a5cbc67f0609ca8055f0ea0ee7501b9ed916 +8e58f9a04d33a41ace4944615041662dc35057e645f63e127cf0d70f96ac307d33a62ce98f164d6eed8536c1a747dcbe +b5b11388071ffbf57ac47fc195736613b964ebb91cc8e2c17b32646f91d64ea506282b881897fca96c317364d3290de2 +a40ee9b7551133856cfb3904837f9949a9558e59a418898affb78adf1500fd6ef6328fc4422161909aea2c79ad08c14b +81f9eb4ef28aacdb43e11dfc9aa92ba990be4d3c14b484fa677edad3a3fbfeaa859a7f9322b5e95818240d7326215abf +84939b2b6bc859437d1a7a8d6ec9a357c6b716c4b4cc22abc274af872655940cfc72c99f5d0283d90e05191fcdb1c232 +b78a5b74a90a805410b6225fb9576d6d73752520f25cc3fd1edf8ea9f6559d3080f9acaa2246809b6a66879cd2ae446b +8d0a92baa88bf38dce5385ccf15d345b28e2e5d0a2d469e689353d80eaed8e8408933816d70ad752f226c59a0d5b5f0c +a7e15f8a8c1655b7b346c9488cff278c793505379b781b31b273b4bf09b3bdfca1c8ab2334746075d636b2e05859f215 +b70daf14f2adce03c7b92d6aa181f0c507a80a37493d8dd12419d5ed5f943a98099fefb46ac827d6e4efb9b8233c99d6 +8c2480814661744d116fba7355bc6b1914975e44cf0e976d50b6a20092bb1c636b7b44ed3fe8d63b5555ffc89fa759d6 +a6059528a4fed36abb74ab992b22a4f9bf1d05c5de2bfe6837b9af1adfed98bc37ed7481b5a99675d432743021fcfdb3 +b7e19f1b25bc159e5a769811e773c3a8ffe8be8ac77ed0b711540915e5c6e7bafdb407cf9b85c551f67fd621ce8142a5 +a2f66d4f7d16ed3e7ef5fc90b42676c61a98ff18bd26ccce91de03b6a0130c1db17a6bc57be135e410a76d2255b15813 +a139c916927dc3d3fb83598da9217ca64f0ae127215332e9a7ed82be923b89a801c44580d5617297175f9dafb1c4eaf3 +af08e1e1b04ec95366a12d99c80a9a9ac40ac984a575dd0230cdf4eb346a7686da55ef0a276f3356f814af31f9cbf1aa +98840aefe287369221c0721cd7c1b15b1d670c3cbbfda191cdb5434bcad757e59c30ec82b2d8c75947405888d44da435 +b7c61c8d42daf2e278a12d8f6eed76090b71c82275f8b33504aba75d95103840e8acd083e97a5a5aa79897876a68940d +a0264048d2a2061d32eee4f661957ff351e78436bf49ef973c059612874ce9c91970869d011dc13a5b7c754476880a68 +897199a4d8db8aa2db5d9be3d4f4312e41fa0739eb06c62e2e046c4b9be829a447e5d47227e2d96195d3b7b66eb59da6 +b512a9082881f5dc90b02f8bc4f38b133348c2e933813852f6a8e7d8c270c9ce68a5524af7d1d3123e53b2d02a53d465 +80b332469254a96f53c95ec79bb5a8bb1c387d40e58b73d72f84384c696ba0d3c81d6ac90be2979c364c44294e90432e +ab680c2e547ea5cbf95bf813020beb461d50ee4341dea944eb48f6a8584d35682d20186e3b190b849a1ba25625a7f499 +9070581993a0531d6be372d370c2e4ab2ee53f30e04a75ae61ea0fc2c320914506c4d2d4b4487c1f8fa88356fc45c895 +8424303dad6b4051ab633ad27ee51783b2ead61c5a6dae1eb3ed72fc1f36e2a9b1f315504a4bd90f9664091f2f403d4c +82225611eee626556553b9316dab4043aff241a81826a33aebd9864a91e299b765ba1fb43eea2c2047e6b75b6d7fe3de +8a3fb221c616ad55c352dd5e0c09ee892022013d6965aef40d4f277a42e9fa01226fe973cb99aaf6ffe4f4f348fb54d1 +b07c07679aa51713e8a7d7bc304dc15ed5664b66bd371877023f3b110b3927e09e259ef22895c4001421a69c6c013cc6 +83556c76bdac0dd8db6da231b863c335be076e7299802eebc259e0818c369f933a4a4b18e2df8ca07e82f60767b462e0 +a516f659b7915d2f7cd0f0f5ea2491b15f0c84dcb191e7671b28adf7cf14a56d42cfc0da94b3c269b45c535f6eeded49 +80d7cc6f26066f753041b17ff1bd27f6d4b5603a43729d33d596e21a67356db84ca9710158089def425f6afaf3207f9e +b802a47f9009dbd48851209ea1e2739020e717f0ae80671d9f97a0e43de923273f66b7fcc136a064c8467372a5b02d28 +ac92fec1864a8a911633f377df87aab56713876316d48240fefeee49ab97f7406c22e70f4938b5912c5c4e766146b7a5 +89224225b9835d04428b0a74edbff53dee2be285ddd1e5a3a8c37307c0500578155f0c4052e4bc8be04c56862fac099d +b1d3c8492fbf22ea60732745edd3b0163ba5a20d1a3315e3773f2540ee38cf308d42ec72cbb3e3dcea457d1d132c3904 +8bd00e38ec30ee6c44a0e5b222f1f737c9ed2a4bb9225f1741d6334df966318c8a0fd2fbb109557fe8c9479694b8d8dc +a930ce5454efc0b247dc148aff869963fc5c240241d5590415cbd36634801a04d3873d93635911bb9c0c42ecb005cc63 +b83d4f80e9e0fa47b42175df74935ba8aad2e559b80e84478ab1685bc3eb65d51b93e5738d5ca968cc055ca0c552a03c +b3ae21258f98051f13af3878b8103bc541fe6f20b1c3f8fb4689ddb8800b3c25cca9b55f0a4104bdf15dc4d5844abb8c +831ef8684c1cd446c58c59d0152aeade5cc305bca6aa296b92162615f052ba280fe289edd62fda6d9f0667c186445f52 +97bf9659b14f133885916733b7d4ac7e215495953caba970fa259f7bf6b79e661090ec8d79e1c9ce8dfb17e8552f93af +84d5a89cc2332baaaf3d19627a65f4b107f8dd9228a1434b327732f59883bb54fb8ce60d6acd026ed4b0e94e545d1c33 +8e66cb743f95ca5486400b0d89d02e20b98044be1e3a12983ff9fe086179e5a0ebf4dcd5098703191552e9aa660a6de5 +87b4cfb35bacec805f8148786788db84eb8f4bcecdd0570ecb592c705450ce1a90b6d183d37ef58780ede3995be67497 +a72a4fece5478011973afa543f6d8a8ea06a64b241cf7d8bd81fa3740ac2a4cf10e5120abcc1c1101f94da89507a40ca +89dc6001a96adcd2679916f43dd19ea00508c8d5dd6b0090eab7982fd2f3571b62f3029588a0649e73f49124525407ea +8ca75edf1259599e873530eff6151c822a4018e71a340534219ef8641cb6683215891df41d4e3c0ca2560e57a7aa913e +9282d32f868e5ee6f7fc229dda5b94b603476de30cec0a44a30edf396b52dc0ebd472b8f726d4b67d76179fecc1666a1 +afa24704223707db89690bcf9761f07a093f6009ca9fc945e0a8801fc29f9f51292bf95243e466fe736088af36c55ca6 +b51332508ddd9a2610edd2b0ad120272ca342e96c28baae37a2c4f07e689303a46c237712d07e446b1d67c75aa8ce32f +9219249f3799dfa4eb4770ee323f821e559e7406bb11b1f1889286221b22c8b40ccacbd9ac50ea3fa9ed754860bc24f0 +993515270c128ede64fe6f06755259105d0ec74947b7eb05924a375fa5c6d14822f3d7d41dd04fa5df8aa2aa205a1dec +a83be4c2511bae430034ab15b194ac719d7b7041f9c0e321317f513a97db39e97b9ee1df92a1962f265b7a3e98cdd753 +8ac7feaecd26f7b99fda3ed0b8a08bd6dd33ed5ba687c913ec0ffc64bbbefcda6f265072add4d944f2005634601ce68b +b4e3ac6b09299db9e1a469f3a0b2d8d724ee47a417a517bebc4c2ac3efc5cde086b57b9aa4efccdef2bcf8f456d973f6 +9262a24a84fb7b2a84d700f98dcf3fefab8b47293778c20bfc356860cb84e0bf102bae9facd9986d92d1762e0a955836 +97be2041c42bd25e5eb519279163b0857f8bef627492c27b1182f8bf0033769246be5886422cbd2409c08a2615352465 +b0b87d059a00e3effa2e5e4925da913b245785f2932ac3ed364ad19a064d3561b8aa6afea22c951316074f0df179af36 +891644b7b3321b06a2a40cd96c2b8b29d81cde5b48546483fdda439000982a9cbf1f6333fb6c089d39da6492cdfaefe9 +8da9149b7f4783a24240b7b9c7e6df4abf8d699d3834e31ee591489bf4744141ab199c173db64397c1f9bd5f9c862ca1 +8ad7f9fb2742654aa2964fd468e7645436cefd1308b064fd63fdf0d3adb4caf6cfe5426354f6cc284f208b03d6b2d918 +8435e4668f7aeb027100d21e4e0b6ee22b401d21966a3736b95610de86c7e2f2c9ee5d0f901353675eee5ff458dad69e +9010895f045538bd11b47bb8996f27198c8d6cffd3220569e6b7407f68f35c47d1efdbcecbf9b5e241c3c2879a4f6936 +92a9aa443b5ee7bf13b6f43f2d8d8db7f6f33fd4073a606ec5772421a55f464831419726130dd97829a7d4bfeb1ab078 +843f3266560be6dcbe0258c3c7d7e332330e10630c069892954290288eda301e247f479505a8a1bf7e59c99ccafd104f +915bd1dad808f8a568725bd243f80b5476a2999d0ef60ea3ef6e754155bc4121b2b879d01570725b510c5a3f09cd83ef +97250d781815b1825be192714884630e9f564b9bd737d55b8ac79ab48d0fb3ca53bd21ead7b2fa82a05f24083f25645d +81e2d52333391ff2faab39611689a62d6ead77039e8703f4e012d53eea17a4d46f2e3342e44b6edbe73a542b461bda45 +89c9f9fd5f638156b018831c1bb70c91215f4a2f5a73c84b1208bdf6ad652a55df7213336ce12bd910a0e1a726474f95 +92bd02984d090ea7e2f3eb7d36d1e7b9d731b6b047e3cdd4af7cc4ee177415fea7a145205e484b366d84191f06af85c9 +85a86fc61d5d916ccbb219db52953e1495230aaaca63237e9165276405f07ad9644e253ae394f1ccdd231944e7143313 +a2ca5b3fbc9f3530f88c0ed7071ec3d89b272174c366eedb5d15d2b648c65d23c0faa4e92c776357e7c6883a0084d03c +ad171f5badcc99c8ffc9d8b707d792046f86cd0aa478e0e2fbb32fe095f96cd134ca548d1f7713057694dc6b26465315 +96bd15d57da9980870fbadc98c68db76824407dff2700c45b859bb70d98374d4a4ba99e3ed0b0c17f480fe08f16c6b8a +8300bac69ca088c3ff35749b437215e9e35a16393e9dc094f520516ba57a485def7029d30adfc72bca36eeb285c19301 +8a09e20be64f346668fcc7b07fee9c0ea8094c935cbf4f3a4cdbb613d4b936c1edb9256b7c884efb72393d97c0da00e1 +b1f85827ee6f041f93ab174d847a55710824fa131c9ade9561168c3962a25c617475ebc4105eba6e738961a754442bc8 +a131558f92e215969f41b6a57d1e2f424149eea531723821dd4cf8c54325cbe66b002de2c8287de6b41ab4b5c35f060a +81ba492b8956f73557f361a856c6c884ebb300d828287d5699e22e0cfa75c8e77a61616551d0be5178263898c461d6f7 +b2608f44d3c22fac8e13cb59e4ade8b9a98c4eb1ec0959ea400c97eb937ae3f66837e91917057148befade8389af2f6a +a6ff0323b5a18a4becb2cc6b376086b47cb2baffbfd1b0f2229ef2286fb4a34c5cd83a5faed5def7bbad519fcab8a856 +857d879cb9eff22501d883071382832730704bfcc5cd5b07cdce7ab8dc41c565a1eb0e7e4befce8e0e03a4975d3f11ef +a2879a20c0360c516811c490289be7dfbf7dbd41d2f172c9239f99e3d091957e0446854f9d0f753d90384a80feb6fa56 +83518624f33f19f87096a47d7b8e5f2d019b927e935a9021823fac6564c4f2328dcb172e25bb052748191e75ac682bd0 +817ec79132faa4e2950665712b2c503d7fb542aa57b7b36e324f77cda79f8b77bde12314e2df65c5b5296a6bca9bb0b4 +b2abf8fb7c3690816fa133d5b4aa509cd5a6e3257cfeb7513d1408b12371c4d58c44d123ac07360be0d0dd378e5bcf99 +a9fe1e4fb1574c1affac5560939face1af6657f5d6abce08d32fc9d98ef03186dbb2dbb9fd1decd6d8f4e4687afecce9 +89b2f41e51f33c3ca3e44b692e8a6681eb42a7f90b81c9e0a0bc538341df9e2039ee61f26d2ebe9e68df5ed1bccf8cdf +8b35aa7b1d9e2135b35a1d801f6c9f47c08a80e48603f3850b425f64e7fb9860d1adda04f92a1ba22d00dd0a26e781ca +960574978cadedbd4cd9f764bee92f94e08b7af65403de36b21bffc9424bcee845b3b028af2e9e545dd77cf1e69a6a7d +840aa0f34b5b6c39471f54d9e85f1eb946468c4fc01963a9027cd7864df01f73c2e864f1f07aeed4b1b1af72808dfa07 +834464a84a11200e3c60f816044c254a7d9baed64aed45a17325cef7fd62338e0a26da78d199d30ac3411714dc813223 +b4ac6fe2f5059546f4ad9a361426ead33237b6b9030b129bf0122085c85fe4ccb33cf90f5a7f23c5b708a5ac64b487f6 +a12aa9035464795f2a67f3eaba478d5ebc838ed9e997c7dfa241e1ed60a94b367d3f969ccf0ef02028c35215698b309f +ac8d926492ec2bb68c6d8aa9bce49085d3d266f3d5f1f924032b87c42b44e41da7c047eeb01e4618f9d0f123dcaa537d +a5142425825d813ed8ce1849d81aa40b11f1cc3daa89a9f798dd83065c74820b4da6122b3308f528b074531df66e1a5e +87ff55c9f5aae079e7bf24084dd9c6b3bc260727d942d79cbe8dc13341d98525b4ece3ed8169994b56a387642f09134a +88e680f148ef2ecdcfed33b61f9e0224790fddc9069bd6999e9bede1791e761637c0fd60b52990b6c93e6e5429e483ce +94bc20bf5aac6e9f1060d02eacd06c42aeac9a1c5635b15a83985dfb03938ddb4999a822e865635201489c7f75601b29 +849221cab7599f25f0b114df092bd5e8c2430503ae959bef1543a101de0790a78245db6a145e26f40b5f9bcf533219a3 +88b6f2c2e7a7954fad11009d839ce50780921f80292320868d481e38d26aecd80fa607e82219a99532d88cf33b39f562 +b0d82947dc23c0b88b86c321b582c15decdb825ed909a731b42d46bc895009515a3dc646c98dbec7d71b0722df82392e +a2cfb9f7c1a76c8073363c1c3bebe5dc29fa76533caea41046c51ea9bbdc693a121b957cd96be5b6da18704d1865cff7 +8f0ffab9a83355a22683a9d998d1c1089449eb308711eaad4265f05927ec6d0d1ca39217082a0b372e02234e78dbaaad +ab024661e2b2937ad374c8cf2e3669f1dc55558a3a881e9ec4d461f27e0fa92e2bc88230f038bfb051cf2145ca747a07 +b98d9b9ec9eefa56d38cca959ce1aee7b6d4b41a8dbbd34b3f50c0a5f97f84ed2502ded1ce8cdb5895872360d4ba6d61 +851244158b3184a62d2c98d148e2b1102cf0d5500906bbc2deda95acc5e3bc4b4a3344febbb31ce05a56dfee86a74913 +860d9e2cb886bd3620b5d7499d14b415532482569bd45fd76e3e8052d78a73ae4b2b41f139f9cfb136564108cd93c0f3 +8305a052a0fb2bcd41f3aca075c5f7f233bd8f861451d03f3a6e6e31f7d08dd89fe1eb4dd7b238a78b12ddceaad9768c +adb703e4778c7e14fb83541ab00b5fc344108243ec6827c5d9b302ee68321aa569da1718424e6a57979ab7536d5eb43b +b1a754b87b9e21aeb86217ec5b4fadb7535344567f1bd15e88ec12a833fed68e26bfbe03b7709ce24ba6c925ea0a0e07 +8c1e2f6bf820e1653f3b8213e9d959d8649196223c2aab57b7ebda094f4919f88d883bcc6a0cd0be335f26f5a2a9c962 +a082deb9865fe8668e91db0e4fd7fb50fb3fdae3e7bf1217ce0aa6f286a624624cf936d762bb2b6c3fead6826694f846 +a10540ca05fbcccdd0a2a66aabab3b36e9bb525794cbae68bc3dace6116f58942218e9d5e9af10d67b5f6fb6c774fdd4 +b81d22c4ab0ccaf447cc5fc2ff3bd21746617e6773bf43257c0d80331be2e8437b88c9c45309ee46402b38d3d4911caf +84c7c6e924713cab3b149f641dabf63ad5abbc17c1d8ee7802a6630507aa1137f7e034ba1d12ec13f1e31efbab79bf13 +8773b9d236e5fcfa8c32e471b555264692006bf9a869a3c327aed33da22dfbf5780ecea7158904d4d6ac4acfe9789388 +a4c2c1bb7290eb7af2013f7dde78282148593f066b09faf42e61a3fcf81297caa5a00fdbf6b93609c8c5782a0f25341a +a7bfa6e3f273da3dcfac7cb9906bbe9fa4fc2872b184d79813ee273e6cc4d7f37f46164362707a1976f5b6a2c5d7ed1a +8b71502019e4263fcda354a0fd10aaa7da47f4abb7a0c715c7b017e9eea14f2b64009b29b467394668c7ca995adedf82 +ad7460fba7deccc3f9a7d204233de47ce30ffa55e1e164975cdf06480a6108720bc397b93ca8c959df77d44a1e1f05f4 +a5b8df96ccb7b078a3918e74b1b10da21df982538d2c9313f5129b2797c8a6db9ff8707241ff72d3e9d5983397321736 +aa6cfa6386660c01879656da6c4e72497690708bae6c5cd1d088f443cb5bbbe75561d6eec256a72b9728377eb83ef973 +b9699ce7c5c878e44114ab7a598646c6c7616b8e08a9ef8ec291189ef9945c1a538d2abf1ce3b0da0f8eecb303b81b43 +b8d0fd1d278f53c455de92ec4357885fc6648dc5f276930263da7dc885b4a9628a2113e28b66b1e64fd08189427c614f +84ad8d262f6ef5d93e82ff6f4af995148eedf6d8e079124daee9b99f506e2968922eac2c7d4aea741fceb7733f20b2d2 +ab5e30ab54641e3a44450118b8235554e0fcfffdfbe1430ceb3f7ef33325725741995fbbbb0c16f0875aef0f1e0c98ec +80e2cf8bf386ebda46045852751611f2af80eca2e910d9ec5f6e2c7376611534604ceafa639272b3d503b02bd66525a6 +aaac69af8fbb87da1c1b7c1b9e59942887ae839a91f0c1d191c40fe8163d7f1dbe984e4fd33619c73e63abfa7058f1e3 +a6194224ad838ab86e84dc80e9b8abb121ae6c3c7fddc476463d81f14168131e429a9757e18219b3896a667edda2c751 +b68f36aa57aedc7d65752b74761e49127afa65466005a42556230dd608ecc8f5efdb2ce90bb445a8466e1fc780eea8c3 +886c3fa235d6977822846b3d6eccb77f1e2cd8ba3dc04780666cf070cae208b7513dc4525d19a3fb6385cb55f5048e2a +a9801273ef850b99eb28f3dee84ba4c4017c95398730c447efe8c1146b0719f252709d3397ce60509e05da74ed0f373f +a58c2a5dd13e08ffa26a6c5e5eb18bd8f761ab64a711e928e6101512401ef2b1c41f67ba6d0823e16e89395d6b03ebb7 +91318b564ec8b2d8c347ca827d4d3a060272aec585e1acd693b2bafa750565c72fec6a52c73bb3ae964fdaa479700532 +a058db5d76f329c7e6873e80c7b6a088974522390ccaf171896066f0476742fd87a12fe9606c20d80920786a88d42cec +9838e07f9ed8b3fbca701be0ef32a3f90752bbe325aca4eaea5150d99eb2243332745c9e544fd1bb17e7e917202edab9 +85a9ae7dd354f36e73baa5ecf8465d03f0c53b24caf510036b3e796e4764a2bc17f0373013af5b9f1b8973226eb58cd1 +896a4ff4508d069a7da6ef7bed66e1080991daee8b227f3c959b4f47feaf75fd1b9e03d0917b247c2db11e105395d685 +a36d9a6a037bf498dfc0e535f2034e6cd433c7b52e520469811eb2e9f04499a6ce40257d2905300df7d81f38d1bba075 +97aac3c5492aca879b4c06db1834b30b8850a244d29296046a84c637d9580c8521ab4752ef814c96f255a139660d7639 +8552bf592a84ab4b356d01643c90347377ebf1f2b38a8c2e55a3f34537b8c7dcbd62e6776d6c2114f2bc2d4344d1567c +84474ad163db8e590943ccd1dc50b4f444beb8275919b33f53d42cba89831e9d42ce2de52b26f4412e2a0676ce913277 +900799dfaf5eafeb297c7b4f892438bf2a65ce04034d66f8e5cc3836e4eaffe782fba4f4455a0fcab49102a240d1780e +817176415e35ad4a204b9fd5771bae6cc270f6ff050996cec89efbe461b2940ae5dd3c6c7d7e31b1da5285b207efed27 +965e5791c927d47569bc54ec9b4c5305788aecd87a26e402aabeaeccc03480df46f0586ca2e2a9918885cd03332af166 +b96d9ada4b5a04a94807d71726bd557de94fbd44042d7dba40560eebe8658d1da49eba54499360619f3b2c38e8b5ed6a +a07b6d641a43e02e7868f30db4dd5069a2f221b4f122ce9b11eac04abadc4f25f3207f1d2d86c7935b1a3d9992ea9814 +8250d4d8ccac846a4b1a9fa392d9279b5bf2283c8b95d8164c3c0d199fec8849eab85755f2a2a99d584a0407742e3200 +8324cf49f56fc14162f9a9ebda1ebda0388d09d8688f1938aef7dbf9505fc119069efc552f68cc7cd9213f96fda2c6de +a98e6f1e85268dccbe3bf4e92c9f455c58dcb53de1dba3b78589adf2e50e79f8e245f956e0d098eb46f5d3746826c6dd +b103ec12f266b4153d67b54d8fc079357ee342cbe5008adc3e0689a7f788534c4601e60e939731f49e4a1e24fd589f82 +b2d7681e866420413cc98eae67614d383943e3762d5742cb3c57e26157633c20880eea1209feaf68402d5d33dd699708 +99fed0ae4112ec9ed74baac70d202a885aa51cb555a3886b49016744dd4017640dd5dd564998c4d842a9f38f3e004e68 +95c35401314467219c8bfb1ccd1f1eae6ef4fa9e48fbea14f70d5315e67b16c46cd03554471840e4a5030b077d2a3856 +8d029380e0c294400d6b8673a23aed43697cb6460fc1bcf217aca3b47cf240886644ed09521d6a05f6abf56f99722d84 +8ef54d1dc0b84575d3a01ecba8a249739edfd25513714dd4d1941fbde99dbbc392f7eb9fb96690d7052609af23aa57f7 +b8ad2b7af4812417aa8de8f33a26547f84bb84f39501d4b7c484cc8bb54c7e166c849b95240fbe459a4719a6e3bf1651 +9858545de898721d19930d8b360cacc5ce262c8e004867a050f849f7a2f2aba968c28d51f24a9af56aaba23a9ded4349 +94ea5043b70df1db63f9b66b4f9d8082776f721b559f27d37b45e0a84faf47f948d7c4532dfd854a4bac49fb2ec8e69e +a2fd88d7b15e3c2778f6c74470d0f9e1a1f979a4d58bd205361eacadab9973d585a6508e685e640b272d6f8a448eae05 +88defd6bccd55db8ca84e3c8d0fc55a3456b41788f1e209d0aec19c9c70febebf3ae32cacaa1dbbf796d7ddea4b17995 +88b8cde2449d5ee7de2ee2f32e845d27e171a51ef64f1d3d8a5fd7dbb9f898ea70eb7f6410cddfd7b7ae70ea8073cc2e +8e044fff6ec557824866ac76301b6d93ed19b7177aa6baa95046330f5d69b572b59200e3653cf2f2b559455e782e8960 +b5446b4d6741c824885790d2d26258729dc0ba2f469c85a47d38886d933b785a4f38a951d37f3ef4bd5091c03fa3a071 +956c8afa8056e9a71ab2e8be5241ddbb3a8b3cff2110cb0e7389493d9fa45e6c4b769ebef540a952db6dcd8bd55baf64 +925950cae25615246e29d594ebf34fa7d52f78a9867338648158f2131e6eb4dc17e18f9db8a5fdd76d017b3a9798b3a7 +a17ea4b43211ba990270c21562690b3ef154a46c3d669c4674c80bd424cdfa95d8850c8e882b8d06504f929cba3d93af +b315ec723973a138508afc387ef651fd8a8804f93975fc36c2eeb796a304eeb1508518d8703e666a74d14318253f526f +a995742d7433b3f230e622de23cb2d81cac76de54831491cc29768eb4a56da60a5cbd573e1da81fddc359b489a98f85c +adb2e89f0d15294d7118fc06d4fdbd9c51d3ecbcc23c69797e5b8197eea0d6cd1240910cf22fcab4ef1e2dc2dd99da91 +b5ec9f9fcd0b5d176b643df989bb4c4c1c167112373d662fb414875662d1a93160dc0b5cdf540e8a30e5fcbe6cfbbd49 +b1291b53f90aed275df8b540c74a1f9c6f582e16c5df9f5393a453a3e95624ab7552e93d6e2999784e164046e92ef219 +8bc7b7b1a584a12d5ae63d0bbe4dc1b63c9df9c89bdd1095ff4b8e7c822bf8c1994c92310a3644033c7c9689f4b7d2b0 +ad7fc45506a10ca48f991714ecc055cea376c0cbe667f3b40ee8dad8446218835439ae59bccc474cf47b053748ceba6d +b134756828a5f5725c0b95109e09ca450e3834b127163a0aeeb544e63cc0cdcdf66f8ed98c331c7c98758f46af369a84 +94535bf1636be0974b112fcec480ed8eafc529933f3065c40e417e608e43a392206cfde8bb5a87b720263446c90de663 +a4df4f6efbc3701000fb072e5cbed2754b9ef5618386c51ff12f95d281d1b700fea81fc1365f4afc66a7c83bd0228fbf +b0336b3552b721087c7e2194976a9119aee13ebed9f1c3c494353707fffde52d004a712965f460062ec9443620716302 +99a39d1d1ee4283b75fa8c1fa42b6a3836b734be48bdd48050f9b05e48db6354fef509623c6ec8d447d630a9b3352b77 +8e3dc3583d40956f9e784e8bbd0b5e65671d2ff2a7c387b20fcb7da9b969f2d122aaf7f054d450dc611737604548c03a +b5068ec5b7bcb5d8583d51cb25345990f50d1f7b82fe535a6a6b17756355885047916f466ea3ab09eef5516bbf2dda90 +a8284ec1eb1d21e693f31a6c074199ee85d8a8da2167bffab5fe240defa2773971c8437e358a18f7e58d1e2954f57f6f +aa7415639d29081acbaac3e9c6b059d68e8702db3f430b86bb6e220d476fa74841c875e9d471c8a5423c58b6fee3cb54 +8afcfe6f65fa6e07c2cb3e1756c0ef2c589830be96edd50c3c248e3b17f51a4b08ba92ef7eed7991d81667ddfbf2bf7f +83b9c8dec8ca8f9b85f0e36c08c5523cfeafb15a544398e6f93b48b5fc4b15a0bd05c0f176a9c2469664acab8dffb0a8 +82a128a89ea46b9debe5c903b950c0ab30cd7570b979ca911500b5c2cca5c4ee6b2c2fa414b5f28e367f4671ffce60f4 +b79fd0ccd2629a361cd6f9307c02ecd4d1f07e4ee03ce4b542997e055b07a026cbc0ba05fe3da309efc58db2e401a8fe +b190751141093823b4b5324cc26c4f3258552f7893241201f2fca1ae9b1a1d4d4964a9abdde8642cf308ded61ce5ef09 +935fd48b95aa6f9eada0cf9a25a573f0ffe039888b3410788c41d173747bf384c0ec40371bb4383ddcc7d9f2db3d386b +b9affe100d878491ff345636ffd874ce1f27852a92417694afce4163e6a80c78b2f28d78102fd06c3283ef273ad37642 +a877670276d49ec1d16c9f1671e43ade11c0c1a1413755f6b92be9ad56bc283e4bd2ad860367c675d5b32ff567301fc4 +8c660d16464878590761bd1990fd0fc30766e7e49e97b82ec24346937856f43990e45aa8ad37283cb83fa16080d4a818 +ae1412087da5a88f3ccc45b1483096aeb4dcf4f519ff3dbe613f63712f484bdd8b2c98a152a9db54cf1a239ae808f075 +ad83cead97a9c3d26a141604268f8a627a100c3db7e5eefaf55a1787ddc1dd5ffc7544e4947784cb73b90d1729003c8f +97c3140ce435512a509e6ff3150da385fdf9e0883a5dc7cb83d616ec8d0a0014e4e0fa57a4d12c7997cd84e07d49a303 +a353773ff68f1615454555bf658eabdcca40a9c7bced8537ea6fa8d54764fd1f032889e910d2a2a342835513352e2d2e +89e8df0c17a36ffe08149c2ef8b27306d04cdf437135aaeba697abc65e3c8e91bcf1817919a8a826acdbbe7dce79a18a +9928c2da15ac6cb20b15859c22508cfcd452c5643cd22eb84abf5f0a1a694fdefcd8fc329c9b40babc52630743d6b65a +99d837b556f8d13108eef6c26333a183f59383b39958dd807b10590c3d37f62ade6c4a320ca2e70567e0218b0ad5807d +9272da080e4aa18720b634640b01bf1fe506c7c8a89dee8759a53e2ca5cdbbd4a4f3aca54924c46b935362cf1eca066e +b4d39752c882de1c1daf3854202c1d58c2bcf35c882006eb640fe54a97be2655281cdb91c30d1a41c698617c2cf64b01 +8bf827f4a7d47e07374d338a3d8b5c2cc3183015b5a474b64b6086fcf0cdcf4852046c9e34d7917d69caa65a9f80346c +901bffc7db9c9416e06f593a76d14f6d9e5dea1c5f9557bd8c93b9e70aa4782bab3518775c2a5b285739323579f7cf0a +af7e204388568627ca23e517bcf95112ca8afd4c6056b7f2c77c4da4b838c48791191565fd38398587761c8047d11c47 +ab2576b5366e6bd88b347703f9549da7947520d4e9de95d7e49966d98249406ed9270fe69347c7752dad47e42c4ea2f4 +b12e3b228b761dedd99d02928105494ded6d4fea3026d73d65ebffa2e85e2cd75b6d091135d418dd95ac102c22b5ee31 +a20b4a752685d5e31ee7e2353c8a1b9a5265f12bb775004d282a3ecd9deda44831bac1ac5151646428b66909b2a423f5 +91a1d4bc0062a86cc6786a96fd3eb4436d8a4a187b7cbba02190d1cd6ed3c3797d9ae7d6ddc413f1c94a21f62bd04ef5 +977f18da1a5df5cfdd0276f583cfba2b2a0fc6139520664e20068f8dfdde33e29d179abfd722f142448f4677aa47be6c +abc3ece90f0f7b1d80fd917de27ab0d88cca584ef959da520825e54cb5a71336b15f8b348532d08d47a6fa600527ef25 +888d36a2c7cc13a1c1aa338a183a74a1f57713e76cb825f9837f43279ce4741999b76a16928147537bcc20f2e0195b0f +af3f5dfdc2dcfe19de893f385f39f550cb1dab67c2e97f1d5fa735e5ec96d6680066803e8a0eb010dd4399f654195513 +a0fb4e08ff56530a940a86c28830956eb6dec2f020f7faaea7566faf0a4fafe0cffe01480e87763ec22f201be51a6451 +92343c5b107910b203c64a79c93d354f7ee5b7d1e62e56732386776e275285561cb887019cc00d3fdbe3b5d54460bec1 +acfe7df83c4624188a1011ad88c1e1490d31a8a8c8016b40aebcdd7590d9c0793e80d2d7ce6a7048876621c252a06a5e +a7da001dc1e33e0e129c192d469d2bd6e5d2982eb38f3ba78bae0670690c8e70f40e8114a57bd0718c870ca5dd25b648 +a903de5ff97dc83628290d781e206ef9d7c6b6d00cadc5bacffb31dc8935623ab96ade616413cb196a50f533e63641d6 +8f9658d42ad14a60bbf7263f6bd516cfee6b37b91a8f53715d69f718a090ad92484061c2cef999816760a78552fae45b +8c15b72b3d5fcb9ffd377fd67d9dfbdd706593fba9629002639973db12aac987bd1db70250ded31c88e19efff612cdb8 +88a2a4034decd854fb557960194ff3404e239953818a8a891bf72a0b26a8e570a65c4a630884de991ae7452b3234f31a +a09cae5c4c190537bf1dd75bd7bce56f7b799762af865bb9d1ee970f6a133c27cce0dd0f14a0e0516ceac41054e6998f +9760ebb1b40f9a97530c3b940d4ef772a225e5b63bf18283f8e302b9436c5209f6294980fd37058060e429fb7fdc3a56 +adaa9400eb86d857dc591b25dbe3bc8f207b69e77b03cb5ee01f7e4b006b5c8f6ba2b51b5a45687479885708509363de +949efe6b00b3248846747a9ad4a934d6e4255994c2b540a59fbbde395fe96d69bb67908441cfadd8c8bbb561fe52da03 +a19a45504b6b1dc3a0fe0e6a1384734a3dcd5a7cb8fb59eb70e49426c4fc44946547443d558e5719a04884ab3a2811ca +8934c9ee21e8d1435426fd0f64232a0670a7946ec524c054cd4f2cc8b1be9f89cc11002ca8aebae646a2050d91716b10 +b1150ff8ffb34ffdcf7d603348c0aed61e5f90ee0a1b814079fc2a41325c75f2f9ee81542797ede3f947884266a772e0 +86ce8cc7c1f92af68de2bca96ccb732f9b3374dad6657dfd523a95e8a931a0af2a80df74098514a06174406a40c16ba5 +90faabb9ace9e13fd9584932846ab28a618f50958d2ce0d50310a50c3bc6b0da4338288e06e5fcbaa499f24a42c000d5 +af4a935c2d8df73332a16dc6da490075cf93365bd0e53e2374ef397514c30c250bcac569b6df443985cf3720a4534889 +b7f948ee90f394789eb0644d9f5ad0b700c8e44e5e9ed0e49da4cc18483676d25740710b1c15a557965da635f425b62e +a917913091245beed6a997ff7043ecf60c4d655c4db0b1ef1c704fd9b0e1ea1335ce8b9f45d6e120f81805ce31555e30 +a48099da8406399bfb1ba834f6f7d864111d0036969a5cb64089947a63dd9467d3857b605e9f57f5ad5f4ec915088d9b +9784c3f9be42eed354542b1446d734521f8e3f01cd9d495ae98f2e4a3a16767fe2ad909e0def5d9a6267f3fc6a172cd2 +8d9afaa323847a3226ad7d7b60d87322ffcda2e4a8df89f58a076f7972d896588de685a2e155e243bcf9456b0a0d6d1f +994413faf0b843f4ec1842c706c45ea5f24351c68674a27887bc8b182eda756856e507a4e8bbfd937e2c4c581b629ee6 +b3e72d9d1ddaa00c7d22f25462d6e9f2faf55e30d138dce8bb1517eb0b67132db758668aac26164fd934d732633bdea5 +8e95875e338f714e9e293df104f0ad66833bbd7a49d53a4f7f5fd5b18a66a61aa0a0f65cc31d55e0c075e0d3e412cb90 +b980091862b1a9f9334b428eae14bbf1cecb4849e3a5809773b0d071d609727270f6ad97f329eca896c178ce65883db9 +915d7ae5ae780bdba27ba51a9788a8852a15355b569581d1f18f0d94bcdfed2c1ed5a4f58e049e9825cda11f92b2c2d4 +83e581058edf9259d0b06128282327cacbb6afc939578223cbf93544599f799a8dce1fb21d52464f990a877086f42506 +803612a38b6f6efb97941997e101ac1878e192456f8fbddb3359aa7f3023434ed8fa92e60ec8e7b4473b1948850e4311 +864a1bf4ac046161617dde282e44ab3cc1843da01a09ca58aa00ed00eaea9351a07a9ec16d910819e7dcc28b8d2c8ada +922eb142845975d5f6f7dcfee6cac8c299b3730400e6bf82cc0bdd9888de21de9d9f1530640f702c003e1ed63b140cc7 +a7db03c5be647dce1385ebc02f4825a654447fa8c4c8d4b22e635dbdd2b3ccdf219384e49a80cfb1e9e6182b6e4227ed +a167289ff0f0967bbab6479e4a8a6f508b001bbe0d16cad36ab4c105ad44f3f180e39a6694e6cd53bc300fe64dac1e8c +b7766431f6379ce62cba22ab938cdbb1b0c7903dfb43980a417e0ee96c10b86b447241e9dd4722fa716283061b847fb3 +90cda18c5d66f5945c07c8c7dc453dee1370217ccb851bbea32578599aa669b4dd245dd8a9711b27c5df918eadf9746c +ac690cd2af39932874385fbf73c22b5d0162f371c2d818ec8a83761e0a57d2db2fca1d757343e141e1a0348016d5fc44 +abac820f170ae9daa820661f32a603ed81013c6130d1ca1659137d94835e1546c39a2be898b187108662cdcbb99d24fe +b2ea5a5950096772f2b210d9f562f1a4cfacc021c2e3801ac3a935f2120d537471307d27b13d538dcbf877a35ff79a2e +ad94af4d0699cd49ba8ca3f15945bd09f3f7d20c3aa282a3113cdf89f943d7793e59468386b067e3c1d53425dfe84db4 +83788367ec97cc4bbc18241cbed465b19baa76fab51759355d5618067009298c79d0a62a22e2a1e6dc63c7b90f21a4a5 +a3e142d879096d90b1e0a778e726351fa71996466c39ee58a964e6b5a29855123d4a8af47e159027e8e6be0ca93d9955 +860831f8d3edaabd41be5d4d79c94921625252aaec806251fb508e364e39fde8808d38b10d557e487603a1b274c9bc3a +88da39f334bd656a73c414ec17dda532059183664bbbac44eb4686c2601629ef8ff9da992c337a842e3885b684dd0032 +b50addbdf7164e8303f33de5ce854d6f023d39c1c1984b214d9e5fb6f6001cd5bdda816f048a438ff3d696872672f805 +999e58c4c69a912b84561cb09610e415b43832beeb95897eca8c403ef4754f4277754d492eef3673afd4362f50060fc9 +b88ea0f60f8119c5a1fd9294796d387472dfad22442b29659713d1d88e7d854cb7cf5c9ef773627781188626bb2fb573 +a068b3844e9dbcf74b54fd55904d56af754d8ce4c619fead7a07f9bfb9d02118db7c512ccec2489d2a84374ec1d1fb6d +871dee023768636003c799e6f6fd8d31315a4c0da7286345cd64264a016693b3485e0732be1bbd34dd5fa04dfa58a983 +8021e8f508680df12e4a5a1bd49f2d7142df65158b0a7198ffa83abd16053a542fb93ffc33e5279020ba8c6a26feacf2 +b5d3cd64df5bc965228b0bd4ce9e5797c409f7b64a172ba165e44a8e4b38e3d5fabc3e0b9a19afbfe427f887c40a315d +a54fdebbb594bafcefb1a03697711e0091c072e1cc24fb441fefd4e0a0518675a1d7b0966cb8294051d7ec0ac175d0cd +93922202337f72969d6d6e14a29c9c75e0420dfba712029941d1504b9f6f9761d706cbc0652cd09a1aa5d22aec766af1 +9711ebf1c7c7426190d4afd5dd03b014a456bbd9d90ed101623866a280550df26a629dde400c03ee3699f7d827dc0bb9 +b4d686d8bc5c1e822a50124c1cc23c6bc3a1577a3d0b8d4b70d1797418aaa763283c09e8a0d31ae6d4e6115f39e713c4 +a533ea2ac683e4ba07e320501a5d82a1cfc4fa1d65451000c3043f0fdac0a765cc1125d6cc14fe69975f3b346be0fdde +94ee563134fe233a4a48cf1380df55ead2a8ec3bf58313c208659003fb615a71477e5c994dc4dcfb2a8c6f2d0cb27594 +93e97d3f3f70664d0925be7aee3a358e95ae7da394220928ae48da7251e287a6dfbd3e04003a31fab771c874328ae005 +b57440d34615e2e7b1f676f2a8e379e1d961209fe00a0cf6798f42b7c28dbd03172fce689305e5b83e54424bc3f4a47c +97644084c6f7b4162bc098bed781dd3af6e49e7661db510975528f1dea8154f3d87e979bcae90c3df3a7752eb0752889 +a923b27b225b2a6dd5bdc2e3d295b101cac5b629a86c483577e073cea1c7d942c457d7ff66b42fcf33e26c510b180bc2 +86698d3b3873ed3f8ab3269556f03ac8d53c6e2c47e5174ec5d14b3ed5c939750245441c00e2e9bb4d6f604179f255ef +87946826d3aa6c7d53435c78005509b178fdb9befc191c107aee0b48fbe4c88a54cebf1aae08c32c3df103c678bad0ca +860864896c32b5d4cb075176f4755ea87fea6b9cb541c255a83d56c0a4092f92396a3e2b357c71833979b23508865457 +b78fa75d687349e28b4ddfe9e2d32bb6a3be13220b8f3ff1ded712088bd0643da9b72778bcca9e3b103b80097f48bdd0 +8a188b940446598d1f0e8c6d81d3cada34c4c1ae0118ec7e0eacc70d1bced28ae34b99667d5793d9d315a414601c3b22 +842ac6f7dc14191ab6dddffcbc7cb9effba42700a77584aa6a8e17a855cd444c5d138f9d61bf55f43c6ffbcc83f92bc9 +b6742902c3d145a6af9738c01cf9880dd05c85f0d0ef7dbe93c06fdd6493333d218339ebc2a02be1895436a2f734a866 +98bf18488483c627b7181b049d3e6f849fce1f15794de59dcde6e5a9b0d76fd484a46e48822a6a93001d3aa12f48bc6d +8769cac10bda8c53a1c19419ef073a5998f73dcf2ba1b849561615a17cbc0a49bfe3eb4ff8801dd36a22fa34b9a3a7e2 +b45c084d58028fdfae792210fcd183abc4ffddeb4cf52ebf3f8a50e4c4eec2a2758f1241b0920bebcb24b757c778577c +85c1216eec8e1fbc1af9b36b93c5d073a81d5fba86a6daae38748ec1573eacc6bef209e76c87a6efbd7a3f80e11d4c3c +b8007e34bb3f927ec06a050b51e633d7eb9e9a44715d5b39712e69c36177a03cd68391090cc3293098e54f6cf65f6caf +8e85527b27c9152b1ba3fdd532a76a79064ab097570508f233e09978761dfe3012d537411b47d0e4b65265eb32cea2ae +899779f3c31a20b76068ec8d59d97a64d2249588ddfd69dcbaac6bfaee8ce0ff3c5afc4e17c934ae7cd041b760eb555d +a5dac3d8f5fbef018509612e25d179f60d2a62451c76426bf546e9666fcdc73263d34aa6fa7e2bfd4c9947bbf5095eff +896900eeef9be2b2e755128e7b1c436af6fb3984f1e66c444bc15fcf3959013b4902c381f0eab1247f878a6ebd1f4ee0 +8cb17f4b0af2e9b2cbb56f46e6a5d6874ea0daf147aae77303020b4e592ddc92e0dd058def7da96258b3a68b223bf22d +a1b6d3f09a9fa7ecc021ab7c5396541895da6e9bf1f9a156c08fc6f2b815a57f18c337ccfe540b62d79e0d261facb2be +ae70888811434ef93da60aeee44f113510069fd21161e5bb787295492eb8df85103794663fc9305f04adcbcf11ff0c5e +a84bbc8624100acfae080ba8cfb48fd4d0229a60b62d070bd08fade709efc6914dc232d3f7bed76a59204f9252321aad +aea47d54652abd8ca213cfc623c8e30780f37b095b59ac4795252a29c2b6bc703a5203acff8831314478b8ee8771d4d7 +8dd438eb8be14935f759aa93021c2b24e1d588f7a162c42c90ec3a647b0ff857f60e24c0a8953eb7bb04e04be70f11ce +922b07b5469680a10e7532766e099896f4dc3d70c522d8add18f5f7765d4ddb840df109146607b51ceddd2189fa7b9c0 +83ef6ebd0ae6c569d580093e8b0b78daa964760556272d202d343e824c38eccb424262e5b7809d3c586f9e2e9c5c5f22 +97f98bd357db6e093e967fe180cf67ed09fa711580a5ad48f07cf095b2e8fabbe6319f97d1f15d62c0ec2227569d8dbf +a1953a4a22fe6c2beaf2a5e39666b0eb53018af6976e3a7aab5515550ff2efa89400605a43fb2c4ac1e51961dbd271d8 +a5cbd67f4c0bc98e20aa74c09e6f5fb6f42c08e59aaa477b4b4e61434c8884bc14f17cf11faecf46dc4b6c055affbad2 +87d96818f2c4f12fd7705cf4060a97bd28037c5ac0f0cc38f71189ec49361e438ce863e6617651977708094d5336d1da +85e7c2daae5fe59f8a1541c94df50402a671a17dbb8838113fa4b7aaff6114cf2bb5969410cf21e6a162857f2f7a83a8 +a19575083e1731bb04bb4a49414e97aaadb36d883aa993d1f6847db50007315444814740e67e10177a14e0e074fd4c7d +a00ebfb5bcc3a6da835078189038a1e56b7dab6be74332b5ff7440e53b0f9e1eb9973effecbbf37000021fcf50c7c1ff +8969d7943abd3b1375fdfc7d6124dde82b0f7193068ed6ec83bcf908734daf3487a6a30f7b322e54a4818ae5f86d91c0 +b959c8d210fa43af9b20d1fe0ea8c4921280eb4544ef6ea913309ff9d61c9327096707e84dc1662960519be8e7d080a4 +9011d8ac651c42e0cb03931a9e960f58e02524c6b666047525e3b9097e9f35fb2b4b278efcce2bd5ad463c6d7fd56694 +937e3b22ed0fcdbd9ea5a1b97b84bbe86b7f5b2de3866a930611112f2217f4ee7d9822c4ab1253823f77bceeae0c8e10 +828997e5d121f4c305e018a0a0ba338bd6a34a7b4dc3c5ceab098ee57490311c130e2c045b9238a83908d07098d9fc32 +8d114808eac0f2e1a942d80dad16756ec24f0276763cd6771acb6049472e05a9bb1d3bbd5957f092936b415d25c746b0 +a063c5c26267ae12887387cbebbe51fd31bc604630b3a6e8e177e71d4f26263be89112cd12d139dd4c39f55f0e496be0 +ab1e1582c8d67196d10f969eeb44e6e16214f1316aa4a2a821f65ba5834326da6cba04373eabfd3b3072e79e5c9717e6 +a17b1dbaa11d41457e71a9d45d032448091df7a006c1a7836557923ab1a8d7290ec92a7a02b7e2a29fcea8f8e374c096 +a1ed7198da3591771c7c6802a1d547cf4fcd055ca9010756d2a89a49a3581dfe9886e02ee08c4a2f00b2688d0600509a +af09aa60c0a185e19b3d99ffdc8c6196d8806169086c8ff577bf3801c8ab371e74165ba0f7329981e9252bfe965be617 +98c04cc8bb26ffce187fa0051d068977c8f09303a08a575175072744e0a5fb61191b1769f663a426c30d405515329986 +a542bf1c9c3262d488ea896f973d62923be982e572172e2461e0146190f2a531f62acd44a5e955a9f1e242b3e46d63ae +aef7b7f30efd50e4a66c87482386f39f095bff6108e68f74fd3bb92156c71c75757912b111060cdee46a6b3452eed657 +8afe1e0ccd00079702f16ab364a23bbbd3da1889d07c4f8cb04fd994bf9353216360dbd364492932bfe20b8b69ae8028 +9896c690999db3c08cd7b25efb1b912c3e0f976db98a3e830f086aef93222d06ce570a7b2babcd7c81d8f9955169669c +ac7bcab6a281468907ef1ea8a6c1cd624159c88839131bef6aa0c22f331fc87ec6128a2c2a333fb79df549e4587e1a12 +987935c08a30b099d19f96901315a2e60591baf898581c40bf5eddcda806ff24a4536e30ed1e6c0b128a83fc77b6e81d +a0a6945bbede3bb09a4a09ef27baa20619d3e15af5673b9350601bcebe952597c989870746cf75767ffb73b32c6c9c6f +b0f5590079f0a0302b08a0cc1b7a5f39cc6900c2a5cdc7baa333d8328a731b2df5dbb67e27a154d3c44ed1a795fc4adb +a7294bdeea210e528f277f3d50e89e6d79950494478998181ecb38de675020130256f2f2a075899170be964d478458b0 +8ab3041b895a631869b439d5599a66facba919226ca9b39d915f19d59f9fc82393ea781377e9bd3bcc5a310e41376914 +8da399b59151fd48b2579948bb82698e3c9804d70ec7d6f3cc7e82901f9f2de5ee850349a7d6f43e5e9ebd47bd78620f +80e8c32de83d1083916d768b11a982955614a345d26d85b457f2280ff6c52bb776958add7c1c8878f7d520d815b8e014 +81bbec7bd99d2917d2dcd8a288722fb33ad5a4bf5416fba8609fa215fb80e0f873535349e7dc287f892aa56eb9e39c4a +9665796fe04c8519206fba58496bc84a8b9113e7ea8e152b65f7f732e88beea271dc97b1ea420dbc8257cc4b18a77463 +a97e342aaaf693ddc87e02790278e4bb50117af4413cd703bdf3b7cad2d1facf31fde1303b43ab2e0265467474f97a8a +925549ebebed348886e37773b05cd8ad04906eca4536bfed951d1ee41b3d362ddc6e1a302c21ff3a2d1e70e95117922c +818fdf74d7903502101551bbf48d3c7819786b04b192d9e94362d2fcb85760d8b6f45165a5443aa5221bef400525ddb4 +a9d29de7e8fd31b59f4a087168d062a478b1329cd3c81c31e56de4fb40de7a5be9a5269ef0be452c487443a0b097dd50 +a85286ad573db4c9aa56221135da1e31d742e0f6ff01d6b159086d7258f78b08dad55ec8eb5c91ee9d3404b2eeb67e1e +92a79b37db5e777f9ebbebde24a95430a199e866e56597c7d0b0e7fb54c7b092c2f6cf61fb24470ddf250cf609898281 +8d79f5ca67ed67d52c82949af342a9fc60fb793c47c76d84b4863c550796fcae2dd59e285897c6fb96fe31cee1efa62c +8ad2e0bda03415ab86324992bb62dfa3612d2d003765bcad1468087c27971d08bdbae5252681f0115a184f4885d444e4 +a08815af979286538c31b4aa5ec805053790af1ca58a8c4341be51136d094a8a05e569d876a079033298ad355ccb7ca8 +b96c2978d0165d619d08281d295e90df78bc2375d0afbc3142ebff9c2cd4b0f0aa97a9a0e3740bc4dce0ff8a9fac8252 +b7752cd0e582f35ab0d0036ca9c0a9fe893a6ad325164d78d865a604a85d3d23729e0362553e8b8a3d51816beeaa30cf +99cef1fafc29e7adfe247c753c475ad4bda7a5f9558b79c86e8a65968ede67adb38dc30071925c9d66a13860027a6735 +b9f6c65af178c791b6137d71980651fb09cb5b42f268999c728c6e129985a9c7d77b3dc3b50751bd29ec9ee0b3111dfc +8d73ae61fff5be883a281782698075c5650083f00399992688738856d76d159803be0059fbd9dec48f4f0432f0590bbb +a8a4a2865226de9bbf19e12c7e75318439fa6cf1cbf344d5e79a8f363439d3bc5bcf4df91b54581e7866e46db04eaf0d +894582aeff222e145f092ba15c60d3207340c38f2c6792ee2ab4d82d50fb544ae366c2985cc2b6c2f970bcc5f4b46385 +956014ba2d20a056fd86cb8c7ceeab9a2c6f905dae24fc1c5278fa5b84335148ebdefec5dcde8eb9b084700724fc93d7 +af217fe2b654eff6d11a2a79fe0339a1d4cb3708b7be9f09d852158b5a44b4f9b04406d6d67c4f144fb6b69a41ae9d0f +a90752a784bc00df94d960e523f5596695d16a534fc806179e0f878fc0e82a91b25e758e91a165debd815dd1af5f1028 +a697606fb32979549ad822b31df8eaaf50de4ead984439a0a33e955937d326519bb9f62c8243ad37f764655f8d32cc80 +a3ad4a30922e45a3e665551e5611384f1c2d414f6fa806184b0c826af05f014dc872585e255543794ee41e43cdadd856 +b29c255843a82ea74a013bac6c36a694646e61e6b9cefc4c130e2ee261e3bb5da3e0fe3ee7e6fbb009deed0530bc1c82 +87e1cc7febefa829cf050aa2aea59385d1048f8617abba691f7ea9ef58eb90ad12eeb9c439af228b0e34897ba1cf1b47 +994d3222f89e9c8c154362190be7167c8c2662f0cfa9d50eb4d8175b255ff0de09dc548ee312fc8226963c8c16f43e8b +8f1a980be640820f2d1e953264ca4c30330878971669852be3d5d6b41c488be1628b935388bfa2bd4de484acb0fe661d +854d90d0721579c8c88e147a4aa83553c960617b18075f8224b975562dccb30b0e02e81fa9df7070f356a0eeffc3b14f +8e156da9d4330a03e32a25a2f0b861fd3ea5c719fa4f834119baab6e5fa5236a9baaf0d44147bf0841418900037f6eac +96586fc49e53a6799242ddf617000db5a0ad20c6cb1686af2102623d64a71aaddb8e468b15fa6d100d0384e448548db4 +b44d8d85c8df95d504f82d597f8c515866d4d4a326fa1b816dcc5bb0cc4ef1a52647aa5d2e84c62e194c01cae0885d21 +b75c43e676a7efd199f8b32ae31f176ec667e714df355e9eecee97246f72af5bef9c5b04c11e7e90fc37bb9163f957ec +a49835ac0565a79f6a9078cf0443c5be20561a68b448289589721fded55188583f1d301925a34eea647f90a6e66c6774 +b47c17ff6824a00b8f29df0adb7f06223208d062bd703b0f763c6eee4ae62d4217eef2da4f4dde33f0b469c2f2db9e42 +957cf039cea6f6d41e368e2bd0cf77315938a0738f15ed9ca342f0a28658b763659ac1d1a85ecb362f13de12b77bb582 +903a52f8d2439fa63f59e1e9aba864d87b0464ded63814474947112375236a6f84e8fa003cc4433c8208d80e05fbd1b0 +8afd524209ff08d1eb6312b078f7afeb8e1155af649e930ab711dedda226dc2db6b0354aab9652eea7f433f90015bf7b +a95c3c9277b11bc8fe191773bf567641be57c0549913b973fb18740ff9cd7b3f7ce198fa4dc1086b2b8a446012459193 +9455ce8163fce04aeff61e7808ef3aac4725e51404f0858fe5d39d7344f55dcc7871ca332aa5cb1a63a4399529e48907 +809fa35b6958f94e781f2c584438b33f5ed528a6b492d08960cf22ecf63ea3aa1e2d29bc879e17296e0a6cc495439cb6 +b0f50774de212dd33e5837f6b496556215c665437e657f674fc5117e5c07dadbd0d057e6ac4c42d50a8eb81edfebf315 +844c65e263891d0b2fea7db6934cc4b7fb6bee2c1d0b9ab4c47f2eb3e9c5d7197dad828d38c54139123740151420280b +b13c78c9efcbb3b28eb3fe0b971380b7d5151c80948a99cd93c78b4c3ab0e86df6226a64d91e0a2ea4a1c0a46bc0404e +90300a541decad460c348b8f4257f7a29687b2362ebee8d92fd03cc0e85b285ccb0ab1cb2ff5e29c5cc5295e351017cd +ac49b409ded770c6d74f6e70104c2cdc95b7b90609da0743c9923179e8e5201ead03becc0ab10d65b3d91a5be0d52371 +a257b815bd8289dfdfc21af218aaba12ccfd84ebf77642cc4cf744d9b0174ca0b0d7ab2a545c2a314fd5f63c140f41ab +a34778d8446e4d74d8fe33de64b2694ef1e50bc140e252af6eff3ce7b57acf8b6577a02ba94b74a8ae32e5113cf0a29b +ab9e935bcf0d8607e3d66f013d9bce7909962cb7a81174923db02dc89e485c2b1c33d6065bdc7bbbe0450b5c49fbe640 +94d2c5c5c309c9eac04be4636f61bc47fd9579b47aded57cc6c736fefb8dfd8f8a5de32210f7baf2052d04c0219d3b4b +b8dda9046ae265214086355101be3460421f7cd0ed01bde9c1621da510941d42bc93cd8060fd73f374fb1b0a5f38d45e +a6674649dab5f92ab9fa811d9da1d342cf89ff6eff13ad49f4d81de45438e81a384098d3ae5ccce4c67bda5dbe246d95 +8d619f7564677bacba29c346c4ef67c211f7a3a14c73433dd1a7692e16a7e2562f1d0532454af62fc04c2fd2bb1789b0 +a2b93d2fd4c707f5908f624a0fc889e20164d3c61850af9125f47a1719757a6ce6375aa1910eafa4c1e8b6e20c312775 +a07d5585447654d82817ef4d199984542328b238157976eb9a267f0bdb2229acc25aee510be68f65a312b68fdd9e0447 +8ef55cf95e2b24d8ec88e4136399a7763bd1b73d5e90ea45e9845123e9d39a625cc336e9b67988374b8ebcbc75f2ed21 +b62c1fc32e27c767c461411b02fe9aa44a86586e1427406f4ef0b346d077db91952abce79318b382ec75b7be23058cac +b252900345f5fa15a4b77fb6af6a2d04db16e878b7bd98005333f7f6e3c8e6e46cf38fc5d1b2bc399c5c2ff4af730dc6 +a4ab5ac0cc15d3d17b1747c6e3133d586870eae0a0d9c8fa7fd990ebd4fbb62e9090557ca2792a6bc6271856aa3c9a05 +8e706b3f2e902faee10b22742c6c33bea6f670a8937c243db96885143c1db5c979e33ab73a38359b52b8d668ccd092a9 +8a6792190ee6c959d79f60c22980ca140c638d88d75660adaf9bcbe6dc4692ab5f01e0c460170f09f74d5e582e85ff1f +97ffeedfc94c98ec85ea937e064d7b290a326838e62cebd407facd1ab4f08d9c0c109d79af7cb6170fccfa6c8243c127 +b79970b67c09453614ffd83a0c923c17f857c6ce3c87a356298f8351cab0def7ed83efd4f6638f48df67e07bef4ad9d8 +b90f1931c7cf1822cc0a97401119910cdfd0482daf09a4d7612e4e05046295cfb4cc50d5214b31676bb1a1c9d15f9c7f +922921ad813c01fb5d12fa7fb7ed8e0b0abbf7b19affa190b36013c55b88fe3c7df0ae663c970eec7725ba37b95a7cb7 +a124f33e7f28feabb4089a063a08d52b7395d24eecd06857a720439dd9414b7073bb86fbd0b04e7bfac62d3dc0fdb2f2 +b252fe50bc6677c004550f240fe670974a33ffe7191ed7675da6ac36c780c2f8d02be7da5d92cbe2d0ce90147847f8b1 +ae5f8c9c56070f919f3df2d2284348fa4b2e39881f7bc42c9b2f5b7cb1ebeef8ecac000f37329bbe04cc1680cefc7f4e +b432a4575caf7337f11eecfcbd34a6705d0f82c216301725ceae2b3c9df20fa53d1ebef65513e305013d1e0c2df522b6 +b7c016fbbc4614cdbb12db1c9ac41f9a45d5e5ce82594d568a30cd2c66c3cc9d91a2c959697b67c582a0913de661505d +8f6f3e5e0347dddc1b2a34ec0dbbbb7cafbf976f19c9c902efb5c1427d1bbd4b71abd9f3fba20dda75c35a39393c989f +b0042a1d33a1ee9fdf3fad2299b8d70c4f1862d8393b5ebe3ac2189a2c5a58bb826128cd7a39b70d524a6dd976097e26 +85297c4e8ae8d9b44c3fe51aa926c77d55db766c2a9f91b659040de36e34c9a4fc6f44380f8d61704498f6fd52395a49 +8c61a988b6a00fe5a277450f30bf6daa932e42a2eae844568e3babf8815e09311f3c352dae6eb2d57a98d16b7beb2d22 +990be28aaecd932e7edb2a97b9be2789a3905cb88737b1c79881302585801c69a3dd5fb230808b39db1352fc06e0b4a8 +82fd14bdb335aa46f022dfe0ed4d631911e6b6f5eefb10d11e9e2e02a7df55012ed8162249d10b58eb76ced5a7b06cda +ac39cb058df764e161db9c39b185f09aa210bddbd66f681f1697ddbe6b305735612d5dd321d3ffbb4876771bdb321e2f +858a3f7e57ccb81387caf8e89f9b6039e9aadeab06886d8688fe6427151a59ab2e77e85ba850c67d099965426c97779a +b57fb9ea623cec432946819937c6bded0b5d03c8c67b52b44a4b67d34adfb055e6cabca67a48e4d859b4be45162c5083 +b84d2990b563d6d7fe1f4c1894989db25b81745090b94b1fe2ef708ac3b2110ef93d647820b2a51fcf78e3f00fef5412 +817d85b9f5e1521733d2b1fa6d4f4957ac445dc803f97fc495e20b819b14e651332f9e0573d684b854fd47824c53f0e8 +b09e18e97e93a8523101af594422fb71afc5b8826002314269016fcc1b44002d91bcb7c90d923d460f0cc03bddfe9af1 +b867cbede82102de7cf6cd0dae68506869576eaa66c3fc806e73585310602682fc912dc37adf5ff6f0f34a07831735b1 +b1126255798368b692f2796a3470ed16e5ffdee2d8c9e0f7ee3d2e92950c3e6365c32895171c3494aff2a6d6356f7e25 +b05f0a0996dec16335c770a5df3f0b08e20020c838c2caaa1d3a4a2490ede98552f5de349de2ce6e4c4a839731d80919 +98c512bb91c8fa191120ddf5d63c88076581cf41e15eec3c168822f12b3dd0ce4d6df74a7e3093d3e35cad1cb3135421 +84ce38fd97f7f90012c2c1e59a67bf9f465a7ccfb6f308bdd0446cc82b8a26ff7c30e5c7cc375011718cad1b31adaa9f +93139db52c9fb96dee97a0825f21e34c5d6d36838e1e42f4d12d01eacbe94426c85a811fe16ca78e89e08f1c27383d28 +81454037b1e7a1765f67e4288b8742eebf6d864d9b0f508ab44fa3243168ce0ed30cb5f33dfcdb995cd2c2710ff97a6d +828deb2a26efb2ff1842f735e2cc27162360f619b6e3e27a85bedf384912d4726bb2759a3016937973092ece1bf90540 +87e5a7d4e7bd301078f625d9a99b99e6e8e1207c9f8a679f8ebbbfb467bfa0b5f7ef4a4d577c7d2670efa88221153012 +b9dc9d0ea48deee201e34379447bec789c8924aecd030eeb93db159af77eff230976ef60ea9f4b4a9e9e95c1f9f4284e +aa6528268d46bf0627d87d58e243d3ac34b863513c725908a2617e4c6a46ccb1d8c8334bd6dd0eea7ffebec44259dae5 +8d26c9ce07293f6a32a664d31e6df9a7ace47e6c38001635918efd9872aceab62de7757b13b783d422eb67bd28ce7bbb +b0d3ca88d9829a7459b89b0dcbdb8bbb5180b00d750bd959bd110f53c2dd5d4db554b6005c4765fbe7ec5903669e5ebc +a94d1c72bf3b2dc6bfebc9dee40f6a89a516b252bd9f4fad96f156e3dbfc151a9b8a02324d764c7656d59230a18eb61f +88996e79171e30b16505638d8ecb25afd875e5f3cc3e29860937f2b5e751c66e78dc77f744a0cc454a8a655142a93ffb +af4d94f342665fe7ecda318de6cf1bc1c40c37dd83d060fedaf827459728152b5f0e280286ff5e6a0012036f6715f53f +96beaa7a2d565ec14a4e5cb895d33624c69da56b75c8d06ac729cb6d0cb64470ed4f9b0387083cd827b1609c8cabde8c +96b773fa2fcb7377bf71a7e286f37f1f24ee42cba5b4f33903c4566e5e5bcc501ea360e3c8435749107c3de84e272d8e +a69ac6218454c3f40ad0beb48821a218fb0a4f33ebade986d2fffd9a3900d8cfa613bc71676c46cfeaa5f644d1f239a9 +857f139c08fcc45370f448ce3e4915bcb30f23daa4134407fc6d78efac7d718b2cd89e9a743eec7bf2cc0eccf55eb907 +adeeba36af137fd3c371a2adbefea614c3ae3a69f8755ce892d0dd7102fb60717f5245d30119c69c582804e7e56f1626 +afa97ca3548b35aeda6bfed7fbb39af907ed82a09348004d5705b4bb000173270ce44eb5d181819088aa5a2f20a547a2 +8423bd2d07073b0e87819b4e81997e4d3188b0a5592621a30981dc0a5a9d0578fde1638a364f015078a001afb00891c2 +b92e9d4ec3966981ee574695d6e4865810b8e75313e48c1e4bc5eebae77eb28740e97ecc3e5c42040f9eb1ee4b13b0ea +b07b218321d54cecfcd2ed54a5fd588a6be8d7a5b6a66dff7facfe061222c40553e076e57cbdfa0bdb08e0a009c94ba5 +a71e1ae4d6096eac9ea4c21f621c875423de7c620544e520fb6ec3cb41a78554aedd79493cbd2c2ba4f0387f902ddd2a +807cdac291246a02f60c8937532c8969e689b1cfe811f239bfdee0791e7aa0545e9686cfb9ed0c1df84748e5efa5e3da +a1faeb4504c057304d27d54fb3ec681462384a354a4f0b6c759d4fa313253a789250c6b0f44f751b0718592637438a19 +996bcd3215182d49f1cd15a05e1e0a4bf57e264400bf14f7253c6611d2571de7130cce81fd28e0411e0a80e9054f4f98 +89d15b38f14bcd46f4b2dcae82b0e7bf9a35e40bf57aa947e9c4a8f87a440b5cea95229708de08ca596762062c34aaa0 +8d8ddcaf79374c750b8b0b3d196acb6bb921e51b4619876a29d09161ba82a42271066187211ef746f9f40a5ca17b75f7 +a3dc7f70f3a6c7edc483e712770abbaa94bfa3174cfee872b2cc011b267e0ef9baa1ab49e4a6c6c30dbba0e0a1237117 +aa9e958bbdcb192b19c43fc6fd34afcd754949fdada98e9f4848e8db0e23acb27d19dd073c951a8819000f2356aa22e1 +a4714e45ec853eadfe5c3bee7f683b81f97857bbd7833192a48936dd1460aee68f700a21658658b74b737c4fecf90c7f +a1ecab4215c1892e4a8ff3405d710163875e5dfef8a8cb84f5cac4e317d89c7696e3f496ed1747ca6f52b304190f4ba1 +b9b48943eca3686219575026d395b969e6ff8159dc5317005df090e79d26901984e40ae4b1af060ed3ff6f42e0417d76 +9644b9f90a66edb0396abd8c00066886f978ebf56fc22081031fbc9ce371bf9b04aa5a4ef59e59319b3a05bb7fb88b43 +b2bb14f1c055a78596488e4e2d4135a6470c1ee43961952160b8498f674a4d23040606e937c02c1fc23dbd47e9bd4633 +8c61f2fce9a42b94a389c7e52d7d093fc011099d0f4914f6d6f05b631df7b88182826edf9bbb1225971a080ca5c0d15a +aa6a7b8499cc7d256043eacad18528d38bf3be970bea4c6d4cb886690280bdb373688ceba3e506471e1d9493dc76f3f4 +8127703363b3b35b06762c2353d4de82b7b85bb860db1028d3640f46bdb78f2d104fa77ee3e0d9db83833d2b12a966f8 +b7b01f5909f2c66ae0fab156be5d79954e3a304615e1fe55945049dd4bd95f973bb3821117eb54db7e9ed1ee9a527652 +8be47ba5dfe212420649193490838670c40540e0ea24adbab18c4a66e7ac3dcf94f068dec2533b60e08c1f64e7533e54 +905a6c7e24b86aa54a05c329a6b4616d335bb0b1f1e9987562eee0acf82ad302c7c44981a1dd6b24c6121ca12fb92996 +86969ccfd91deed93b355a2c21319e3bb08cc652b741463bf68c626b7ba2afce3f7cc397f2fb74588c2893477c948ae2 +b5a9d20eb12c331d0d300fd4b85b0ac0bb74573178a5fac8ec9dce5e95acba07fab444260355ece442a846737a2dcd1c +a13497c11df21b11fc1a63b0ffdcf7f432da4dc2c98f8d07d36da4fa68aceb57af2158088e5b05e334fe0f264aeb7a97 +882e4597cc66498a45e86a2ed9ee24652da4699af00ad35f73b5e74fde6ac3cee70630962d5ddd86162d4aaf11bbc11c +b748858c2bafa4a14ce44af35195e9c52aa75e109719243bbe278095acbfd6a7ae7e084caf8dae6939039b5a4e8fd675 +83a2e0524507e74f51fe976441108f8226ba1b3a33f4e16ec45c5661ce80cb1840a93d17122cb8ca9e0f80d14f69877d +846cd2946c93ee5f24243d9ebc69936b3a1a6d59f45fec6c79b1eddf15ce30a8e73ad03cf606ee66baea3d8ff115f70f +8d98d0a3a94f6efe158f8423c041b546416145c5c2254bfa157efea0d1c99fe58acc7df6424ef29f75960b18d664ea4e +a39fa47e4b79f54dbf59d0b1726f1e78bc219fcfc56ad238c84b4b610e7892ff1e65d537baf5118a32f5e2eb80d5ee0c +8c30969a4519131de5e30121c84c04f67b98c8ad109fa4710dd3149cae303d51778add3f258f0482f1c89c169824dffc +af7f80d141ceb78b4762015de17fef49d7ff6202d292e9604deb508272ee7569f7fd5be3b2438da1dfecf0c26533ef86 +97cf82f70128251944d79b8845506975405bd720e150d836205b048ff36ba8801eb74cdcc6425f28f6bc0acec0a81463 +8c276c876eb88688957d1868bf3a1462375e608ff72b49870a5dac82cbf6584e00e3f36f236f732348a47502ccf9539d +964765f1a5c8a41d8025ddf56dc01b78424703d8a64a4e5539e477cb2445cb541c70127c561e717256d13f91a830ba83 +a2aacd9e21b8c8efaf2319611addea1b9f41430aee42e7f2a640cc693aa395287cc8fdc2806b76b577d84fbd05378ead +ab11eabbf5be4345a77323a3b75f9ee93b011fd2a9d0154e88183cafe47f82a7888666af16b40d3cb677c94bcc755ff7 +a0bfe715a7af5a29b1b6148b8cbee585d2b49fa6ce59bcd173ea3bbc60d71a62f9da27ffcbbd5a6da75502112fe44d70 +902e6cc38ee42245103d90b65028a471bc7a48b825599d361aa81d8c56e0fcf9fbe8d4c13802040d2cfb85b7e022eea1 +8832e2b5014fdef4003bdbb87e3298fdbdbbe49673f6b66e2373f1cb2605f9c4af2cdf9bfd45d1993208681d29ee1c9d +a7d39d3fa1ec1e0c87730fa43d4900e91932d1cafb36c76b2934907becf7d15a1d84d7234591ad4c322b5a24673bba8d +836ed5f09d99624204aa3aa7ac601980fda223f3b4b96b4a8fb235c574a3545d518787c12f81bd5851987f2860d41886 +94235e94445e6086f6e9331923262070a4c2ed930ec519eabb8a30133bd4fc6debb99185f4b668431fae1b485c5c81b7 +9828ffe20b9405f117dac044159be2d3c6e2b50ecdd1651d6a73f7633e6e2a7ba3d783ae939973604446d3a1ef0fb20f +92f03dc365dfe9154743ca70e6dd2758f064e3286fc543cf8c50f68effdf7c554bd17b3507c6ff4127046d9bbb5522ef +91ed07df479d8eb3d31292a0e987672a7f3d45ecafe72935b7abbc3f23493605134ce573f309e226c9efe830b6868220 +93bee582661e6d6cefeff29002afc2f36dd2c13dbf33f0574c35b290ddc426170a5f7f196369ad592efcd72cfb6f8fc0 +89a51467d966f48fed15dea5a12dda54d0015f69e2169b5e34f44c7b5a5d4c282d6f138116a0cd06a8476980e420f8d8 +b8ccebc14b6679ba2399370848864f15f63512fd6139df7359b7b93e82c1007fd85137ecb0597294b46643e1a9e7ab5e +841fa301567fc57b2cd09508ce75326684e12bfb8add671dc208f579b2500b93d5b641e9f59bba798ed4ed1259757f7d +b3cb45c15eb00b4ccb7013299f761cb8fefc17adf6db50e9ecb8abe927a3bc7f28e359e64693813e078e1dac800ad55b +96e55d3b9f445f5679e34fa5425b3e87cb221cfbdd07f8353868c7f7f4ba388ee3841cb9a1d638583bc20d03a9d071f2 +a7dee9377de740270c5b57cf86699004ba8dc2766af56b388b5cb0814ec71bb99ecf43ee3d82a552733854ecc7def0fe +b129dfff23b3c1c95ddb214c4711961fcb129efe2b6557ec9e116ada909593d0d2eec2c628434493393c58c52aa86847 +aed2670e201cb3e38a8be3c86735a4d76255e1e5a4c67b91df6ed262d09c8d10b0a3891da3e6ab934058cc9a7178931b +b20b8921ae52e5b3c94fa3a8b46489044174f7b897779e7763d6eb419e808d76705b7e7ba5131576f425aa81b6b0de53 +a7e45bbc3ba1bc36617291ba7663806e247f1b57a89e31520c64a90cbf8d426cac2e2f381338baf78c8f92fdbbcb7026 +a99e651e73a507e9e663e2364fcc193ec77e8afdc08c2bed6ad864e49b537ec31e9114ee72291a7657899f2033a849e2 +af966033636c2e9e8280d173f556fe07f8b6940bbcf6b2df7e2165c30bea66cced2596f6c17ca7c1aa0e614174953ba9 +b69ca7a79e3d55ef21e0ebdc6f0c4bd17182d30cf6290cccca7d2551c91c12b966020d8e40e4ee4179488c9809c03ae4 +b981cd36244e035fef043f70b1d7188d7cd045b4de0581c459fc5730e10eb7f3d5893b54cc4243849c0855e4e621167a +b20fea858a36921b35a3051ce787b73f70fdecd3fef283c15a2eb1bffb1dcba5991eee4a047ce4e87802da923fd9457b +b040e6f2e56dc1860274c263d4045837456f74b354a679f6b5ea70919835ebe5d32bf1f519e218730096c98ff396dc9d +8d2dd60e702c923a7204b530e7d6c193c6f93ca648c4f7bb38f4edbeb0aaed84184213afafb8db6aeb9197c24364276c +95dfa7348709e43d71285b28a0bfad3ca805b6ed4ae99753e9f736c79d58a35a3a50b42760ccdd03eda50f6e59494968 +b8585632a13f18c139a411bb2f02df809591834d127cd1ff081e26d0abfe0e3fbb54abea26538b25a0dcb4d7e969590e +b46ba47858a29c6d523c9982660949567666daf2582b93393a4802a9e077eedbc0d49d454731696bc8e46ca50c7caa40 +84b756b901b98a4404e58d70f39f6ccac877146c866732ae65e7e82727448d1550343bf7cdff1bfd4ee1ed73793db255 +83e5be888eaf877a2c755897410865f64a6d1169a8ccf0336092f3932abab915e542ab75a35ffe016042340d581ee987 +8cb274fc39285aed451a7def72cfbf73168ee10be02affe355a2bf87cf361a81ad284e9334cf00c5bf99a13d9f75e116 +91ff6220924b94ae13f50eeac16a159232e4f16a73fbd5c22c0e185cd1998403904d36bad203baa82b85819ee4a8ac10 +87f46e08e09aea2ab37b55fc300689d9b58ff3e72f1cffe023386035888f714fac4673c7c5193d3f3f3c568c640694f0 +835d7d84ca7641e1b15095830114aa6072fe12260d2202456cafe2308c22651af9ffbcf6b7e56af97167dd0c4e2a4cf2 +91202183f79794f114fd9e3b9bd05553c0e8985919965101a57d97ef666b028863e6cea9735af016dc1864f1542dee51 +81ab2b02a9b0a490a74ae615ddd4fe560734c1bfdde6b8dd13303c1481ba0e8ab14473535a93cfe4e824a0ab29445f8c +8a32d73f4fc006551d4e2c61eec6130355ec9b8c39a65c24ec1edc00e80155ca83a8ef2455e892521a3d47634d82a987 +af70d7b8f13bc90193cc1cfb0c400c4224cf10f1887848aa93e6380f7087782fc41a159926ab53c53eb95c2383b1a849 +989bf42f9d357c51774f1c7c0f7c0c46a8cb7398a74497141c32685be098e38b4230ffe833a6d880ec391a35b1a747b6 +94cb6715ee95700020c630b8c19e35f231de970219bd7e6ba7ced01899197da473b6c45cacfab0d652ddaf547b4ea58c +b12e3331f1f7d7458393a785e22e9a5e1d1daea521b4e78c0ee8ca59b41ade1735a29820e18f6afb2f2c3c56fecc16b6 +ad4b7cf654349d136fb41fb0dd65b588199f68b462b05f5c4e5c2b468bfaa6c26329033e3c3f7873dc8ace89cf873ea5 +a3279969e1ab596df0559ffc5ac7a6dc849680354e01c3f4fd34c6413a3f9f046f89c1e1be0b315d8b6dfab3d23d5c14 +ac74cc5562836ed89d09a9ae6a3644c936d64bdda9e77659d9982f1be29541b03ef2723236d5465e398373ea19a4ccc6 +98138ebce1af531dd8b631b3e74c84f0c700355a2a9bde31e5e51bb10c8bbd766559c63f6041f4002568803fe08438e0 +9006445da131349fe5714e0777a4f82a82da343612589a0c1596393e8b6894ce1cf42784f95ff67a8384ffe1f1a4ad76 +88502a84a85e4ce54cfed297b5d355867cc770a8ffd0714a6f23b1ab320a9903c6e42809e034bb67dbf94c4fc0d9c790 +aa8b4bf123d1a6ccaa44b86be8f980005f2a0a388a76cb111b0e85cd072ef64167fb0c097c7b23c4bca64c0260f6cce0 +ad49eb35dfea9feabb513a78dd1152ad7eba22fbb02a80cefc494a7037699c8df81202dfec12acc1b9e33ad680cb72d2 +8694da730231b29afd5196371ddcb15b4dcc499574bdd063f4864ab80749833ea38ab8b0ca1629a367fe378e87a60a86 +8eca7b488e810c479e7e32e24b8afcd837f7df183fe4f621a0336b53a9ed77603c84bdc365d8be68179a32b71a1deb7e +8875cd3e23c7e1af55af1b091025a08255743984186770bcd43f30b4a58d175cfdf1984bad97a15e08dac2da27198c3d +abdafcf58ec72997e494d4714645f40d09dcd0fbd0733e640eca44eeea67c25bb0c270299c459991f2fae59d13b4f4d5 +8f040970141e61489284f3efd907705eae6ec757fe8e1d284eac123d313e9ac1e8dc14ae3f04d281e1effc49d5d2f51d +a7ff115f0d2dbf66c0e8770b3d05157b37357b9e33e9a447f0f3fa9da69ad04e371fd1e4848cfb9e8d05e3165bd969d8 +a39b1a8c39d317fcc97bf6c396e6ed4a85640aeeadbf45166bd02bc3bdfb6266509159c03afd492e642384c635b824c0 +a2e1b90f3dd2d0038eaa5be52127844ccf35d997143179d95ffd3749c0896398b130094d01eb1bb31ffe80ef34b42b48 +a2bbe31f89b0c3c375ffaf63c8b7831860a921d5e388eb7907dbf61f2601ea40db86bb3952ecaa26a5eca4317a848ff9 +87d885bb0f2ce04b40ce94d2557c15f1698dc652e938f9a2d69a73ccf4899e08eafa1a59a20cae92823795f5b94f04b9 +8f7746370f8a24a2889d351f3e36b8a7d60e75e50e8f5abeea7dafc75441e95915721654e61ceac51bb6f112780d352c +a7272847526ed3d9e0d0fea1d8685b07b5b908971490bf8a46748c8b1783c629b8644feb5bac772ae615daae383d5e72 +978c9aa2996d8bd6fda7e0393fa8b38747f8f99712427705c00f6e9a12c36f8d8b4cedb03fcb9867155cbddb5200e6e1 +a4dec4a2354b2b32434c5bcdc380bf84580c6f9940f94dc0498a5bfe89c675a0921e66b807a3d859a6059a464cb2a9ac +99459ddecc7abce437f68722dae556d8ffaf8ed974f459e52e6d4a64f176caa4d42c2f2ec57e8a5b5f2034638e8acb0a +928c68c0c9213fe6258ab5bb0c693d97203d15da359784de7824dec143212da57d062a1fc70a79172cee31adc7aff382 +aad3f318f1622ea87e12541dfd982d71629b8f1ded4c301f9f6b6af9432716ad057773c33bdaa6f15dc151b0ee4505ea +8eb8e978f149a983fd6ad01773f9aacf57bd0cc622d8a301e404184b37e610123dd081faeda571a0ab1f149a3960af10 +851e7191d7b94bd422bcece5b92609fc1b1c8556229bc53e32963b2d2fd1cacd8ce5da9040b599eca6e610540f8a7987 +9414157fe9d50e5a0b5a7397417681bcb3a651eec1cab63f2a88d5df68ab1fef6e4c1d7ba657cbaf241a7cb790297633 +b5cb2dafdc5408959780754a58b2da55b2a9136672ebca42f34da4e329ddc89360e7218cde3efdbf784ddb390deacc57 +ac6b70f65503a8e94b773fda3e72615745824930114fe72b6d833484285462392617c1b2eea4a250fedbee88f503f3ba +b0829a5312f9ac6c06fddee2f835a3452fe994f6d42c9edfc390d7d5b3240ca544433b544cbbddd6516b38a6d5d7c21d +95f8e2c59905957e34d53be3d6fb85732f834e2cb9ab4c333fea2f502452a87ccd035fc9075d7c0bd8530bb0a0c96527 +b93f279b7045f2d97c674495f6e69a3e352f32f43cc60300193b936c2850b2805c15457251f7e3f633f435cb2b60405c +915abf16cba1a0b655b92a8a70c03e7fb306b86f3bbfb66967ca63e64c003b59c7a5953675efa4fa0bce9bed536b6700 +ac2047f50a319d09df1ec44d71afdcec5ac3bd2765dc98aba347734aa780863545df9f6d71214d443e3f37edc0dae45a +ad49c74ddb24c8a26b14ec08bc807313c77c5967fbb36237f55994d7511bbac8d7e7b9b8ec53eb1b3b066989f078dbd9 +961483105f605e959213fe9e8a52b76dac62d7efd2319ec71fc4e92d68fbe44cd2f65d7adefb2eb64d591b91648b8085 +b67fcafc97d8df2b3075bbff7b3d7471dbf1f3048f309e55d5e2c5bcbc7a73aebcb0697859be9f387cbc7ce98041e154 +8da70ac16468cab6066992389cb37c79ff5e0babbe67d76878aef9408b9597a3dc2eb5de87428bc761a0d78957b0eb28 +aec0ce89770d299b631f15ae12f94b1e1014ac57d38fcf037c2c7712d770d074affa06e97c60691bad8733874b6ad2ed +8b702c85fa4c915a09fc86507f44d7aeda0993b77af87780d70cc98d580c6e996b64b7c16cdb4dd4562cb0f75da36ee7 +aaeb43aa472aac2253e211fd1066c3a5422ea041cef20168702d0618a1a742a44f7fb30a76677640fea1a24e7fae1996 +a8820e92825d6e02b9b4ad5ebc86161d3244cddd3d244333ba1576b6ae10948145b68d9e926bf6b7a2c25dab4cf43f3e +8ffdae28a1f1d15d7ffa473628a66ee9a739073f59ba781248286b39cb8f7255f66d62337064246713cbb5017e615174 +adfc5dd142b7911326d8424881d5d92006f3b17de4cce91674d6ea37f00fbb266c791ac13f6c7a0f61d04f2a952e6a04 +87f98982444bf661f539bec73a10256f079a4baa88a1cea0351ae3de929e1c500485b2d1b5d933063cd7d9123d5050e4 +8f217ba4dd404c5ee384f0c9a126686db001ff0344c01c82174c5e5ef89d1a241b146008c534b13a0da6c8afe7450fbb +afc85476dddaf1cbb4ba8b22186789f3818c7964f9f613e55010278800cd95422702248bdf9c73760702ef24854795ec +a59e0f6ac2ccdfbd01f002008034390c0ea78716f5e0de4e474e3558755705c9c7afb6e3c5c4370e7bbc85958a9c7a63 +97c0695c58d792ec31d9b86d3b2fc1382f0855057b24d5f6a54c41f76f9e2f52882cadc89a8b2f121530e7f1393faa95 +8e49112de0b2649c08a96cf737af68fa8055f1af594846a2d0534c94df6f926f200405edaa6e6ac9db7e380707a2571d +99a1bd83a7ac5f8d77ddf044c80ebfc5745b998714696d67b94d185c97e9d6db989bacac646d9def463127a8b2febc00 +aba80725f9f9f7abe10760eca73ba427ca8df864a157122eb9af828a05b0199de3add02019a297750bdab5380e505c58 +ae18f62573275c1eb268f74c5e54e8958547f9e7d1d36a05b084eb53e5704fafe2200b8aff95cc7e9af5be2391c42b7c +908b8031d09d22b2aefeaa876a998e0a97c7a1070aad9e9c97836cc5aa6d2d5ef94230e1222074837b5e21b4e6490f01 +b3132282e8b41ca6789ec5c43c1fecf3a65b8eefbc2f3d10f746a843b9ba4ce6db664678e75e424f7b11a00c1440de15 +a1eb49440cc106ebc09cf198c93e8070271eb5a936d31c04858a2b311a037350100c7957d5545c9653f396aa968b91f4 +81df6ad1bdd5eee4cc2f94318467b8602d15cc1be2b48b09ade12cc46ee05cbaaf77a20397e5015030b1f1db5dd9dac0 +87236c68a2a93c8442d15d7f1d1dc01d1fd123439c183e1d843f4ddd2bcf638c128f66f1ef9b710e5d1f64a52726007a +84f2e7f85563bb2f61b10a712c7605d63f79af5be0dba056814fd3efebc20e9c53227c56577b72c68d185571b775eff6 +a36d4ae06688ece2927aeb2c7f058a3cd2aa1de1601282d4e688e1d76ef20728b892928deda2314eba41675eba3912f1 +b8326dcbcdcfce017b263c456c47692fb476c4225c95981666fff0b7d4522fc23b7f12273f0f47cf0442662124e6648f +84c66463ab277cda2cc7007d0509269e89cdd41c5e0d3773a92615f0fc5da63811186b05d7a11088048a5d4834a7e0df +b20d3571d970712ef4699b0e7034fd269c361f53e1572e2ea2676b4245e992d43b8b5931a801439a44d977a988cc360b +94dba6007e6d4998ca1eb84aa8e2a7e9f5c164b9d80df2825f2208ce5640a05aacac2e4f08918268990f43ae1ccab69a +a1c25f0b3ef9d1982153207570d9ce8d692e1b6963b509958dc4d9bcd80074bb221c46804a6d9a29e76149cc7787c282 +8857748fcdab1199fc96084323a81d3bd8b5a7f0b1abc5bc3b5252a19268344e2e7d2d086c90fc9b5fa4b92feedb93a4 +8b9c1d841447354b6c086549e4d1d435ab64c13933488c34bc30f0f6eb36c5c5b838b7b6bb018542247edd1ada091045 +8f5b655416da0e719a204fc567e93792c301acb4374cf7bbabc6ce51dbeaaadfd75c2db0e16ce073ab8e91fd3d7ea9d4 +90f2846b19be46a75c5cd0cafefcf9192e6fd80c479e8d6320c4b8d8d7d96703c9e77ff31a67afa9858e6b7bde1f7cce +a53e383947fd98aa1a55ac956214b46b20a52758461e8ba41341a23a835ebb713038bf048edb1202bbfd0b56a96bf292 +9542d7debbcfb9cda6fa279c699a7b655c03b9a9b456a5d3cfc41a826c94eafa43e01155a29e39ff0bcd965f4c0c512d +a43792864ec5fc549f7afc02622454afc0e425c310c4039ba615067243ebb26a4c7ebfd19bd4d57ff412a4bb2a7958a0 +b85123950e30c048465bf32365d24a5d4b21fffc6183cdbf71643a07b87463989b72dd9a6a47f134856f704909a6b38f +944ea689aec1376f855c0bc9c51378ad06ff758a2c075b95a60b535b88b36eca0be11e4edb5152e98cb2137d6e749f27 +a6bef52cda22325e4c62d323e2a0e3fa91c5552fcfce951edfd52ad6f652bfdcc2341f1cd349e6b5d447924dc569bfe2 +b56bff8ffe981bfcb30791836da10b87f2ccbe17ed969e7f7a650af07d27ae0223805b1264d985148208483be50578a6 +8b209cac898dd580c82d854a553e2517497ad1a4cd198e1360b8b50639b380aee70ee4b87625d9b2278228ff644cd25c +877cce233fec74c7158b3c5bf108365e98238418b8a71f058f1aca44a0fd3a1021e3e9025bd11fe244d9fe0f5034ce7f +b1b871aeedb03d6f6accc99816b89f5958178738d8d8cd9717527d04363c80fdb5f6848122ae19fdbc450cfa11e753c8 +858aca51b9e5b0a724e88688d5124eb24c9faf01a3d465e74d31de6da315f311143f22f60201ea09f62c92f61f09d889 +8521d409615dfc8c8289e00f6aaa6297c2c4e1439b25952afd76aac641b81c70b9cef07cd58c1c0198382bddd2bd8544 +88647c3e41666b88acca42505f1f5da226937e0522b538fe0cebb724e9a99730ca2522989e94a96cac94109aef675c0f +b417fdaf719caf38854e89ce52031b30ce61a632e6c3135adec9002280e022d82ab0ea4ac5ebdb21f1f0169e4c37bcda +9367a6feb5e23ea2eab8ddd5e7bdf32b4d2419fad1c71a1ed327b77362d8942dad971a1c2e6f7073885149cdf0a0c339 +a71c5c08d50c57d094d6a4f02e97d3799bada92f238ffc07bd223bbe8379507b7310d20b28f5bbbf331e5e153515e491 +9630a9a3bcb044b51299c4d3d3388a4ff47308dd27be3229601985478c0f6b55faa7e20815d8694f910611396a9d0d45 +b0bfaf56a5aa59b48960aa7c1617e832e65c823523fb2a5cd44ba606800501cf873e8db1d0dda64065285743dc40786e diff --git a/arbitrator/prover/src/kzg.rs b/arbitrator/prover/src/kzg.rs index 6c682183d2..dee4012771 100644 --- a/arbitrator/prover/src/kzg.rs +++ b/arbitrator/prover/src/kzg.rs @@ -2,48 +2,16 @@ // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md use arbutil::Bytes32; -use c_kzg::{ - KzgSettings, BYTES_PER_BLOB, BYTES_PER_G1_POINT, BYTES_PER_G2_POINT, FIELD_ELEMENTS_PER_BLOB, -}; +use c_kzg::{KzgSettings, BYTES_PER_BLOB, FIELD_ELEMENTS_PER_BLOB}; use eyre::{ensure, Result, WrapErr}; use num::BigUint; -use serde::{de::Error as _, Deserialize}; use sha2::{Digest, Sha256}; use std::{convert::TryFrom, io::Write}; -struct HexBytesParser; - -impl<'de, const N: usize> serde_with::DeserializeAs<'de, [u8; N]> for HexBytesParser { - fn deserialize_as(deserializer: D) -> Result<[u8; N], D::Error> - where - D: serde::Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - let mut s = s.as_str(); - if s.starts_with("0x") { - s = &s[2..]; - } - let mut bytes = [0; N]; - match hex::decode_to_slice(s, &mut bytes) { - Ok(()) => Ok(bytes), - Err(err) => Err(D::Error::custom(err.to_string())), - } - } -} - -#[derive(Deserialize)] -struct TrustedSetup { - #[serde(with = "serde_with::As::>")] - g1_lagrange: Vec<[u8; BYTES_PER_G1_POINT]>, - #[serde(with = "serde_with::As::>")] - g2_monomial: Vec<[u8; BYTES_PER_G2_POINT]>, -} - lazy_static::lazy_static! { pub static ref ETHEREUM_KZG_SETTINGS: KzgSettings = { - let trusted_setup = serde_json::from_str::(include_str!("kzg-trusted-setup.json")) - .expect("Failed to deserialize Ethereum trusted setup"); - KzgSettings::load_trusted_setup(&trusted_setup.g1_lagrange, &trusted_setup.g2_monomial) + let trusted_setup = include_str!("kzg-trusted-setup.txt"); + KzgSettings::parse_kzg_trusted_setup(trusted_setup, 0) .expect("Failed to load Ethereum trusted setup") }; @@ -70,7 +38,8 @@ pub fn prove_kzg_preimage( ); let blob = c_kzg::Blob::from_bytes(preimage).wrap_err("Failed to generate KZG blob from preimage")?; - let commitment = c_kzg::KzgCommitment::blob_to_kzg_commitment(&blob, ÐEREUM_KZG_SETTINGS) + let commitment = ETHEREUM_KZG_SETTINGS + .blob_to_kzg_commitment(&blob) .wrap_err("Failed to generate KZG commitment from blob")?; let mut expected_hash: Bytes32 = Sha256::digest(&*commitment).into(); expected_hash[0] = 1; @@ -100,9 +69,9 @@ pub fn prove_kzg_preimage( let mut padded_z_bytes = [0u8; 32]; padded_z_bytes[32 - z_bytes.len()..].copy_from_slice(&z_bytes); let z_bytes = c_kzg::Bytes32::from(padded_z_bytes); - let (kzg_proof, proven_y) = - c_kzg::KzgProof::compute_kzg_proof(&blob, &z_bytes, ÐEREUM_KZG_SETTINGS) - .wrap_err("Failed to generate KZG proof from blob and z")?; + let (kzg_proof, proven_y) = ETHEREUM_KZG_SETTINGS + .compute_kzg_proof(&blob, &z_bytes) + .wrap_err("Failed to generate KZG proof from blob and z")?; if !proving_past_end { ensure!( *proven_y == preimage[offset_usize..offset_usize + 32], diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index 06dd333480..a888e5a4b2 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -470,3 +470,11 @@ pub unsafe extern "C" fn arbitrator_module_root(mach: *mut Machine) -> Bytes32 { pub unsafe extern "C" fn arbitrator_gen_proof(mach: *mut Machine, out: *mut RustBytes) { (*out).write((*mach).serialize_proof()); } + +#[no_mangle] +pub unsafe extern "C" fn arbitrator_get_opcode(mach: *mut Machine) -> u16 { + match (*mach).get_next_instruction() { + Some(instruction) => return instruction.opcode.repr(), + None => panic!("Failed to get next opcode for Machine"), + } +} diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 7c18908201..bf86c043a1 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -3095,6 +3095,13 @@ impl Machine { { data.push(0); // inbox proof type out!(msg_data); + match inbox_identifier { + InboxIdentifier::Sequencer => { + out!(msg_idx.to_be_bytes()); + data.push(0x0); + } + InboxIdentifier::Delayed => data.push(0x1), + } } } else { unreachable!() diff --git a/arbitrator/prover/src/main.rs b/arbitrator/prover/src/main.rs index 69ca0754ea..e2b31dd3ef 100644 --- a/arbitrator/prover/src/main.rs +++ b/arbitrator/prover/src/main.rs @@ -399,8 +399,6 @@ fn main() -> Result<()> { } } } - let opts_binary = opts.binary; - let opts_libraries = opts.libraries; let format_pc = |module_num: usize, func_num: usize| -> (String, String) { let Some(names) = mach.get_module_names(module_num) else { return ( @@ -408,18 +406,7 @@ fn main() -> Result<()> { format!("[unknown {}]", func_num), ); }; - let module_name = if module_num == 0 { - names.module.clone() - } else if module_num == &opts_libraries.len() + 1 { - opts_binary.file_name().unwrap().to_str().unwrap().into() - } else { - opts_libraries[module_num - 1] - .file_name() - .unwrap() - .to_str() - .unwrap() - .into() - }; + let module_name = names.module.clone(); let func_idx = func_num as u32; let mut name = names .functions @@ -436,7 +423,7 @@ fn main() -> Result<()> { func_vector.sort_by(|a, b| b.1.total_cycles.cmp(&a.1.total_cycles)); let mut printed = 0; for (&(module_num, func), profile) in func_vector { - let (name, module_name) = format_pc(module_num, func); + let (module_name, name) = format_pc(module_num, func); let percent = (profile.total_cycles as f64) * 100.0 / (cycles_measured_total as f64); println!( diff --git a/arbitrator/prover/src/programs/config.rs b/arbitrator/prover/src/programs/config.rs index 98e967a4ea..9879655521 100644 --- a/arbitrator/prover/src/programs/config.rs +++ b/arbitrator/prover/src/programs/config.rs @@ -129,8 +129,6 @@ pub struct CompileDebugParams { pub debug_info: bool, /// Add instrumentation to count the number of times each kind of opcode is executed pub count_ops: bool, - /// Whether to use the Cranelift compiler - pub cranelift: bool, } impl Default for CompilePricingParams { @@ -181,10 +179,10 @@ impl CompileConfig { } #[cfg(feature = "native")] - pub fn engine(&self, target: Target) -> Engine { + fn engine_type(&self, target: Target, cranelift: bool) -> Engine { use wasmer::sys::EngineBuilder; - let mut wasmer_config: Box = match self.debug.cranelift { + let mut wasmer_config: Box = match cranelift { true => { let mut wasmer_config = Cranelift::new(); wasmer_config.opt_level(CraneliftOptLevel::Speed); @@ -220,7 +218,13 @@ impl CompileConfig { } #[cfg(feature = "native")] - pub fn store(&self, target: Target) -> Store { - Store::new(self.engine(target)) + // cranelift only matters for compilation and not usually needed + pub fn engine(&self, target: Target) -> Engine { + self.engine_type(target, true) + } + + #[cfg(feature = "native")] + pub fn store(&self, target: Target, cranelift: bool) -> Store { + Store::new(self.engine_type(target, cranelift)) } } diff --git a/arbitrator/prover/src/utils.rs b/arbitrator/prover/src/utils.rs index 716d305c04..cac40d5647 100644 --- a/arbitrator/prover/src/utils.rs +++ b/arbitrator/prover/src/utils.rs @@ -5,7 +5,7 @@ use crate::kzg::ETHEREUM_KZG_SETTINGS; use arbutil::PreimageType; #[cfg(feature = "native")] -use c_kzg::{Blob, KzgCommitment}; +use c_kzg::Blob; use digest::Digest; use eyre::{eyre, Result}; use serde::{Deserialize, Serialize}; @@ -199,7 +199,7 @@ pub fn hash_preimage(preimage: &[u8], ty: PreimageType) -> Result<[u8; 32]> { // TODO: really we should also accept what version it is, // but right now only one version is supported by this hash format anyways. let blob = Box::new(Blob::from_bytes(preimage)?); - let commitment = KzgCommitment::blob_to_kzg_commitment(&blob, ÐEREUM_KZG_SETTINGS)?; + let commitment = ETHEREUM_KZG_SETTINGS.blob_to_kzg_commitment(&blob)?; let mut commitment_hash: [u8; 32] = Sha256::digest(&*commitment.to_bytes()).into(); commitment_hash[0] = 1; Ok(commitment_hash) diff --git a/arbitrator/prover/test-cases/go/main.go b/arbitrator/prover/test-cases/go/main.go index 4ea3d2a261..729809120a 100644 --- a/arbitrator/prover/test-cases/go/main.go +++ b/arbitrator/prover/test-cases/go/main.go @@ -17,9 +17,10 @@ import ( "sync" "time" + merkletree "github.com/wealdtech/go-merkletree" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" - merkletree "github.com/wealdtech/go-merkletree" "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbos/programs" diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index e6b31aeb0b..d5fec6d8c5 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -153,21 +153,22 @@ pub unsafe extern "C" fn stylus_compile( wasm: GoSliceData, version: u16, debug: bool, - name: GoSliceData, + target: GoSliceData, + cranelift: bool, output: *mut RustBytes, ) -> UserOutcomeKind { let wasm = wasm.slice(); let output = &mut *output; - let name = match String::from_utf8(name.slice().to_vec()) { + let target = match String::from_utf8(target.slice().to_vec()) { Ok(val) => val, Err(err) => return write_err(output, err.into()), }; - let target = match target_cache_get(&name) { + let target = match target_cache_get(&target) { Ok(val) => val, Err(err) => return write_err(output, err), }; - let asm = match native::compile(wasm, version, debug, target) { + let asm = match native::compile(wasm, version, debug, target, cranelift) { Ok(val) => val, Err(err) => return write_err(output, err), }; diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index 4a5222a9dc..9bbb701e17 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -100,7 +100,7 @@ impl> NativeInstance { evm_data: EvmData, ) -> Result { let env = WasmEnv::new(compile, None, evm, evm_data); - let store = env.compile.store(target_native()); + let store = env.compile.store(target_native(), false); let module = unsafe { Module::deserialize_unchecked(&store, module)? }; Self::from_module(module, store, env) } @@ -153,7 +153,7 @@ impl> NativeInstance { target: Target, ) -> Result { let env = WasmEnv::new(compile.clone(), Some(config), evm_api, evm_data); - let store = env.compile.store(target); + let store = env.compile.store(target, false); let module = Module::new(&store, bytes)?; Self::from_module(module, store, env) } @@ -362,8 +362,13 @@ impl> StartlessMachine for NativeInstance { } } -pub fn module(wasm: &[u8], compile: CompileConfig, target: Target) -> Result> { - let mut store = compile.store(target); +pub fn module( + wasm: &[u8], + compile: CompileConfig, + target: Target, + cranelift: bool, +) -> Result> { + let mut store = compile.store(target, cranelift); let module = Module::new(&store, wasm)?; macro_rules! stub { (u8 <- $($types:tt)+) => { @@ -472,7 +477,13 @@ pub fn activate( Ok((module, stylus_data)) } -pub fn compile(wasm: &[u8], version: u16, debug: bool, target: Target) -> Result> { +pub fn compile( + wasm: &[u8], + version: u16, + debug: bool, + target: Target, + cranelift: bool, +) -> Result> { let compile = CompileConfig::version(version, debug); - self::module(wasm, compile, target) + self::module(wasm, compile, target, cranelift) } diff --git a/arbitrator/stylus/src/target_cache.rs b/arbitrator/stylus/src/target_cache.rs index 3312c34575..9f01d8dc0c 100644 --- a/arbitrator/stylus/src/target_cache.rs +++ b/arbitrator/stylus/src/target_cache.rs @@ -31,7 +31,24 @@ fn target_from_string(input: String) -> Result { for flag in parts { features.insert(CpuFeature::from_str(flag)?); } - + if features.contains(CpuFeature::AVX2) { + features.insert(CpuFeature::AVX); + } + if features.contains(CpuFeature::AVX) { + features.insert(CpuFeature::SSE42); + } + if features.contains(CpuFeature::SSE42) { + features.insert(CpuFeature::SSE41); + } + if features.contains(CpuFeature::SSE41) { + features.insert(CpuFeature::SSSE3); + } + if features.contains(CpuFeature::SSSE3) { + features.insert(CpuFeature::SSE3); + } + if features.contains(CpuFeature::SSE3) { + features.insert(CpuFeature::SSE2); + } Ok(Target::new(triple, features)) } diff --git a/arbitrator/stylus/src/test/api.rs b/arbitrator/stylus/src/test/api.rs index bf8bf6ac57..275337df9c 100644 --- a/arbitrator/stylus/src/test/api.rs +++ b/arbitrator/stylus/src/test/api.rs @@ -54,7 +54,7 @@ impl TestEvmApi { pub fn deploy(&mut self, address: Bytes20, config: StylusConfig, name: &str) -> Result<()> { let file = format!("tests/{name}/target/wasm32-unknown-unknown/release/{name}.wasm"); let wasm = std::fs::read(file)?; - let module = native::module(&wasm, self.compile.clone(), Target::default())?; + let module = native::module(&wasm, self.compile.clone(), Target::default(), false)?; self.contracts.lock().insert(address, module); self.configs.lock().insert(address, config); Ok(()) diff --git a/arbitrator/stylus/src/test/misc.rs b/arbitrator/stylus/src/test/misc.rs index 9cd54f2472..46f200d860 100644 --- a/arbitrator/stylus/src/test/misc.rs +++ b/arbitrator/stylus/src/test/misc.rs @@ -14,7 +14,7 @@ use wasmer::{imports, Function, Target}; #[test] fn test_bulk_memory() -> Result<()> { let (compile, config, ink) = test_configs(); - let mut store = compile.store(Target::default()); + let mut store = compile.store(Target::default(), false); let filename = "../prover/test-cases/bulk-memory.wat"; let imports = imports! { "env" => { diff --git a/arbitrator/stylus/src/test/mod.rs b/arbitrator/stylus/src/test/mod.rs index 9fc54ca56c..50dbf82679 100644 --- a/arbitrator/stylus/src/test/mod.rs +++ b/arbitrator/stylus/src/test/mod.rs @@ -36,7 +36,7 @@ type TestInstance = NativeInstance; impl TestInstance { fn new_test(path: &str, compile: CompileConfig) -> Result { - let mut store = compile.store(Target::default()); + let mut store = compile.store(Target::default(), false); let imports = imports! { "test" => { "noop" => Function::new_typed(&mut store, || {}), diff --git a/arbitrator/tools/stylus_benchmark/Cargo.lock b/arbitrator/tools/stylus_benchmark/Cargo.lock index 44a838fd15..323df6ab24 100644 --- a/arbitrator/tools/stylus_benchmark/Cargo.lock +++ b/arbitrator/tools/stylus_benchmark/Cargo.lock @@ -231,9 +231,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blst" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874" +checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" dependencies = [ "cc", "glob", @@ -293,15 +293,16 @@ checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "c-kzg" -version = "0.4.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94a4bc5367b6284358d2a6a6a1dc2d92ec4b86034561c3b9d3341909752fd848" +checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" dependencies = [ "blst", "cc", "glob", "hex", "libc", + "once_cell", "serde", ] @@ -1326,9 +1327,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "opaque-debug" diff --git a/arbitrator/tools/stylus_benchmark/src/benchmark.rs b/arbitrator/tools/stylus_benchmark/src/benchmark.rs index d6407740d4..87e97f0dad 100644 --- a/arbitrator/tools/stylus_benchmark/src/benchmark.rs +++ b/arbitrator/tools/stylus_benchmark/src/benchmark.rs @@ -72,7 +72,7 @@ fn run(compiled_module: Vec) -> (Duration, Ink) { pub fn benchmark(wat: Vec) -> eyre::Result<()> { let wasm = wasmer::wat2wasm(&wat)?; - let compiled_module = native::compile(&wasm, 2, true, Target::default())?; + let compiled_module = native::compile(&wasm, 2, true, Target::default(), false)?; let mut durations: Vec = Vec::new(); let mut ink_spent = Ink(0); diff --git a/arbnode/api.go b/arbnode/api.go index b1d2c0e35b..1dc4d75d0f 100644 --- a/arbnode/api.go +++ b/arbnode/api.go @@ -2,13 +2,12 @@ package arbnode import ( "context" - "errors" "fmt" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/staker" @@ -55,23 +54,7 @@ func (a *BlockValidatorDebugAPI) ValidateMessageNumber( return result, err } -func (a *BlockValidatorDebugAPI) ValidationInputsAt(ctx context.Context, msgNum hexutil.Uint64, target ethdb.WasmTarget, +func (a *BlockValidatorDebugAPI) ValidationInputsAt(ctx context.Context, msgNum hexutil.Uint64, target rawdb.WasmTarget, ) (server_api.InputJSON, error) { return a.val.ValidationInputsAt(ctx, arbutil.MessageIndex(msgNum), target) } - -type MaintenanceAPI struct { - runner *MaintenanceRunner -} - -func (a *MaintenanceAPI) SecondsSinceLastMaintenance(ctx context.Context) (int64, error) { - running, since := a.runner.TimeSinceLastMaintenance() - if running { - return 0, errors.New("maintenance currently running") - } - return int64(since.Seconds()), nil -} - -func (a *MaintenanceAPI) Trigger(ctx context.Context) error { - return a.runner.Trigger() -} diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 92f87fd65d..1166e5f8b0 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -33,7 +33,6 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/bold/solgen/go/bridgegen" "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbnode/parent" @@ -45,6 +44,7 @@ import ( "github.com/offchainlabs/nitro/cmd/genericconf" "github.com/offchainlabs/nitro/daprovider" "github.com/offchainlabs/nitro/execution" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/blobs" @@ -106,8 +106,8 @@ type BatchPoster struct { bridgeAddr common.Address gasRefunderAddr common.Address building *buildingBatch - dapWriter daprovider.Writer dapReaders []daprovider.Reader + dapWriter daprovider.Writer dataPoster *dataposter.DataPoster redisLock *redislock.Simple messagesPerBatch *arbmath.MovingAverage[uint64] @@ -1670,6 +1670,7 @@ func (b *BatchPoster) MaybePostSequencerBatch(ctx context.Context) (bool, error) batchPosterDAFailureCounter.Inc(1) return false, fmt.Errorf("%w: nonce changed from %d to %d while creating batch", storage.ErrStorageRace, nonce, gotNonce) } + // #nosec G115 sequencerMsg, err = b.dapWriter.Store(ctx, sequencerMsg, uint64(time.Now().Add(config.DASRetentionPeriod).Unix()), config.DisableDapFallbackStoreDataOnChain) if err != nil { @@ -1679,6 +1680,7 @@ func (b *BatchPoster) MaybePostSequencerBatch(ctx context.Context) (bool, error) batchPosterDASuccessCounter.Inc(1) batchPosterDALastSuccessfulActionGauge.Update(time.Now().Unix()) + } prevMessageCount := batchPosition.MessageCount @@ -1716,7 +1718,7 @@ func (b *BatchPoster) MaybePostSequencerBatch(ctx context.Context) (bool, error) if err != nil { return false, err } - delayProofNeeded := b.building.firstDelayedMsg != nil && delayBufferConfig != nil && delayBufferConfig.Enabled // checking if delayBufferConfig is non-nil isnt needed, but better to be safe + delayProofNeeded := b.building.firstDelayedMsg != nil && delayBufferConfig != nil && delayBufferConfig.Enabled // checking if delayBufferConfig is non-nil isn't needed, but better to be safe delayProofNeeded = delayProofNeeded && (config.DelayBufferAlwaysUpdatable || delayBufferConfig.isUpdatable(latestHeader.Number.Uint64())) if delayProofNeeded { delayProof, err = GenDelayProof(ctx, b.building.firstDelayedMsg, b.inbox) @@ -1976,7 +1978,10 @@ func (b *BatchPoster) Start(ctxIn context.Context) { logLevel = normalGasEstimationFailedEphemeralErrorHandler.LogLevel(err, logLevel) logLevel = accumulatorNotFoundEphemeralErrorHandler.LogLevel(err, logLevel) logLevel("error posting batch", "err", err) - batchPosterFailureCounter.Inc(1) + // Only increment batchPosterFailureCounter metric in cases of non-ephemeral errors + if util.CompareLogLevels(logLevel, log.Error) { + batchPosterFailureCounter.Inc(1) + } return b.config().ErrorDelay } else if posted { return 0 diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 1864a1a35a..26838ada9a 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -464,7 +464,7 @@ func (p *DataPoster) getNextNonceAndMaybeMeta(ctx context.Context, thisWeight ui // GetNextNonceAndMeta retrieves generates next nonce, validates that a // transaction can be posted with that nonce, and fetches "Meta" either last -// queued iterm (if queue isn't empty) or retrieves with last block. +// queued item (if queue isn't empty) or retrieves with last block. func (p *DataPoster) GetNextNonceAndMeta(ctx context.Context) (uint64, []byte, error) { p.mutex.Lock() defer p.mutex.Unlock() @@ -1115,7 +1115,7 @@ func (p *DataPoster) updateNonce(ctx context.Context) error { // Updates dataposter balance to balance at pending block. func (p *DataPoster) updateBalance(ctx context.Context) error { - // Use the pending (representated as -1) balance because we're looking at batches we'd post, + // Use the pending (represented as -1) balance because we're looking at batches we'd post, // so we want to see how much gas we could afford with our pending state. balance, err := p.client.BalanceAt(ctx, p.Sender(), big.NewInt(-1)) if err != nil { diff --git a/arbnode/dataposter/dbstorage/storage.go b/arbnode/dataposter/dbstorage/storage.go index 18d5b80356..9a5e8c397e 100644 --- a/arbnode/dataposter/dbstorage/storage.go +++ b/arbnode/dataposter/dbstorage/storage.go @@ -10,10 +10,10 @@ import ( "fmt" "strconv" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" - "github.com/offchainlabs/nitro/util/dbutil" ) // Storage implements db based storage for batch poster. @@ -60,7 +60,7 @@ func (s *Storage) Get(_ context.Context, index uint64) (*storage.QueuedTransacti key := idxToKey(index) value, err := s.db.Get(key) if err != nil { - if dbutil.IsErrNotFound(err) { + if rawdb.IsDbErrNotFound(err) { return nil, nil } return nil, err @@ -132,7 +132,7 @@ func (s *Storage) Prune(ctx context.Context, until uint64) error { func (s *Storage) valueAt(_ context.Context, key []byte) ([]byte, error) { val, err := s.db.Get(key) if err != nil { - if dbutil.IsErrNotFound(err) { + if rawdb.IsDbErrNotFound(err) { return s.encDec().Encode((*storage.QueuedTransaction)(nil)) } return nil, err @@ -166,10 +166,10 @@ func (s *Storage) Put(ctx context.Context, index uint64, prev, new *storage.Queu return fmt.Errorf("updating value at: %v: %w", key, err) } lastItemIdx, err := s.lastItemIdx(ctx) - if err != nil && !dbutil.IsErrNotFound(err) { + if err != nil && !rawdb.IsDbErrNotFound(err) { return err } - if dbutil.IsErrNotFound(err) { + if rawdb.IsDbErrNotFound(err) { lastItemIdx = []byte{} } if cnt == 0 || bytes.Compare(key, lastItemIdx) > 0 { @@ -186,7 +186,7 @@ func (s *Storage) Put(ctx context.Context, index uint64, prev, new *storage.Queu func (s *Storage) Length(context.Context) (int, error) { val, err := s.db.Get(countKey) if err != nil { - if dbutil.IsErrNotFound(err) { + if rawdb.IsDbErrNotFound(err) { return 0, nil } return 0, err diff --git a/arbnode/dataposter/externalsignertest/externalsignertest.go b/arbnode/dataposter/externalsignertest/externalsignertest.go index c71a36fc91..3e30ac5ef8 100644 --- a/arbnode/dataposter/externalsignertest/externalsignertest.go +++ b/arbnode/dataposter/externalsignertest/externalsignertest.go @@ -124,7 +124,7 @@ func NewServer(t *testing.T) *SignerServer { if err := httpServer.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) { t.Fatalf("Error shutting down http server: %v", err) } - // Explicitly close the listner in case the server was never started. + // Explicitly close the listener in case the server was never started. if err := ln.Close(); err != nil && !errors.Is(err, net.ErrClosed) { t.Fatalf("Error closing listener: %v", err) } diff --git a/arbnode/dataposter/redis/redisstorage.go b/arbnode/dataposter/redis/redisstorage.go index f1105f0d63..22eec0fbc7 100644 --- a/arbnode/dataposter/redis/redisstorage.go +++ b/arbnode/dataposter/redis/redisstorage.go @@ -211,7 +211,7 @@ func (s *Storage) Put(ctx context.Context, index uint64, prev, new *storage.Queu } return err } - // WATCH works with sorted sets: https://redis.io/docs/manual/transactions/#using-watch-to-implement-zpop + // WATCH works with sorted sets: https://redis.io/docs/latest/develop/clients/redis-py/transpipe/ return s.client.Watch(ctx, action, s.key) } diff --git a/arbnode/dataposter/storage_test.go b/arbnode/dataposter/storage_test.go index 373aff3b2c..0ecae84d35 100644 --- a/arbnode/dataposter/storage_test.go +++ b/arbnode/dataposter/storage_test.go @@ -47,7 +47,7 @@ func newLevelDBStorage(t *testing.T, encF storage.EncoderDecoderF) *dbstorage.St func newPebbleDBStorage(t *testing.T, encF storage.EncoderDecoderF) *dbstorage.Storage { t.Helper() - db, err := node.NewPebbleDBDatabase(path.Join(t.TempDir(), "pebble.db"), 0, 0, "default", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("pebble")) + db, err := node.NewPebbleDBDatabase(path.Join(t.TempDir(), "pebble.db"), 0, 0, "default", false, true, conf.PersistentConfigDefault.Pebble.ExtraOptions("pebble")) if err != nil { t.Fatalf("NewPebbleDBDatabase() unexpected error: %v", err) } diff --git a/arbnode/db-schema/schema.go b/arbnode/db-schema/schema.go new file mode 100644 index 0000000000..7c455be718 --- /dev/null +++ b/arbnode/db-schema/schema.go @@ -0,0 +1,29 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package dbschema + +var ( + MessagePrefix []byte = []byte("m") // maps a message sequence number to a message + BlockHashInputFeedPrefix []byte = []byte("b") // maps a message sequence number to a block hash received through the input feed + BlockMetadataInputFeedPrefix []byte = []byte("t") // maps a message sequence number to a blockMetaData byte array received through the input feed + MissingBlockMetadataInputFeedPrefix []byte = []byte("x") // maps a message sequence number whose blockMetaData byte array is missing to nil + MessageResultPrefix []byte = []byte("r") // maps a message sequence number to a message result + LegacyDelayedMessagePrefix []byte = []byte("d") // maps a delayed sequence number to an accumulator and a message as serialized on L1 + RlpDelayedMessagePrefix []byte = []byte("e") // maps a delayed sequence number to an accumulator and an RLP encoded message + ParentChainBlockNumberPrefix []byte = []byte("p") // maps a delayed sequence number to a parent chain block number + SequencerBatchMetaPrefix []byte = []byte("s") // maps a batch sequence number to BatchMetadata + DelayedSequencedPrefix []byte = []byte("a") // maps a delayed message count to the first sequencer batch sequence number with this delayed count + MelStatePrefix []byte = []byte("l") // maps a parent chain block number to its computed MEL state + MelDelayedMessagePrefix []byte = []byte("y") // maps a delayed sequence number to an accumulator and an RLP encoded message [Note: might need to replace or be replaced by RlpDelayedMessagePrefix] + + MessageCountKey []byte = []byte("_messageCount") // contains the current message count + LastPrunedMessageKey []byte = []byte("_lastPrunedMessageKey") // contains the last pruned message key + LastPrunedDelayedMessageKey []byte = []byte("_lastPrunedDelayedMessageKey") // contains the last pruned RLP delayed message key + DelayedMessageCountKey []byte = []byte("_delayedMessageCount") // contains the current delayed message count + SequencerBatchCountKey []byte = []byte("_sequencerBatchCount") // contains the current sequencer message count + DbSchemaVersion []byte = []byte("_schemaVersion") // contains a uint64 representing the database schema version + HeadMelStateBlockNumKey []byte = []byte("_headMelStateBlockNum") // contains the latest computed MEL state's parent chain block number +) + +const CurrentDbSchemaVersion uint64 = 1 diff --git a/arbnode/delay_buffer.go b/arbnode/delay_buffer.go index dab1986414..89b7b71318 100644 --- a/arbnode/delay_buffer.go +++ b/arbnode/delay_buffer.go @@ -15,8 +15,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/offchainlabs/bold/solgen/go/bridgegen" "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/util/headerreader" ) @@ -71,7 +71,7 @@ func GetDelayBufferConfig(ctx context.Context, sequencerInbox *bridgegen.Sequenc } // GenDelayProof generates the delay proof based on batch's first delayed message and the delayed -// accumulater from the inbox. +// accumulator from the inbox. func GenDelayProof(ctx context.Context, message *arbostypes.MessageWithMetadata, inbox *InboxTracker) ( *bridgegen.DelayProof, error) { diff --git a/arbnode/delayed_seq_reorg_test.go b/arbnode/delayed_seq_reorg_test.go index 22898d62e5..8505e20188 100644 --- a/arbnode/delayed_seq_reorg_test.go +++ b/arbnode/delayed_seq_reorg_test.go @@ -78,10 +78,10 @@ func TestSequencerReorgFromDelayed(t *testing.T) { AfterDelayedAcc: initMsgDelayed.AfterInboxAcc(), AfterDelayedCount: 1, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedInitMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedInitMsgBatch, } serializedUserMsgBatch := make([]byte, 40) binary.BigEndian.PutUint64(serializedUserMsgBatch[32:], 2) @@ -94,10 +94,10 @@ func TestSequencerReorgFromDelayed(t *testing.T) { AfterDelayedAcc: userDelayed2.AfterInboxAcc(), AfterDelayedCount: 3, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedUserMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedUserMsgBatch, } emptyBatch := &SequencerInboxBatch{ BlockHash: [32]byte{}, @@ -108,10 +108,10 @@ func TestSequencerReorgFromDelayed(t *testing.T) { AfterDelayedAcc: userDelayed2.AfterInboxAcc(), AfterDelayedCount: 3, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedUserMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedUserMsgBatch, } err = tracker.AddSequencerBatches(ctx, nil, []*SequencerInboxBatch{initMsgBatch, userMsgBatch, emptyBatch}) Require(t, err) @@ -194,10 +194,10 @@ func TestSequencerReorgFromDelayed(t *testing.T) { AfterDelayedAcc: initMsgDelayed.AfterInboxAcc(), AfterDelayedCount: 1, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedInitMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedInitMsgBatch, } err = tracker.AddSequencerBatches(ctx, nil, []*SequencerInboxBatch{emptyBatch}) Require(t, err) @@ -278,10 +278,10 @@ func TestSequencerReorgFromLastDelayedMsg(t *testing.T) { AfterDelayedAcc: initMsgDelayed.AfterInboxAcc(), AfterDelayedCount: 1, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedInitMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedInitMsgBatch, } serializedUserMsgBatch := make([]byte, 40) binary.BigEndian.PutUint64(serializedUserMsgBatch[32:], 2) @@ -294,10 +294,10 @@ func TestSequencerReorgFromLastDelayedMsg(t *testing.T) { AfterDelayedAcc: userDelayed2.AfterInboxAcc(), AfterDelayedCount: 3, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedUserMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedUserMsgBatch, } emptyBatch := &SequencerInboxBatch{ BlockHash: [32]byte{}, @@ -308,10 +308,10 @@ func TestSequencerReorgFromLastDelayedMsg(t *testing.T) { AfterDelayedAcc: userDelayed2.AfterInboxAcc(), AfterDelayedCount: 3, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedUserMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedUserMsgBatch, } err = tracker.AddSequencerBatches(ctx, nil, []*SequencerInboxBatch{initMsgBatch, userMsgBatch, emptyBatch}) Require(t, err) @@ -423,10 +423,10 @@ func TestSequencerReorgFromLastDelayedMsg(t *testing.T) { AfterDelayedAcc: initMsgDelayed.AfterInboxAcc(), AfterDelayedCount: 1, TimeBounds: bridgegen.IBridgeTimeBounds{}, - rawLog: types.Log{}, - dataLocation: 0, - bridgeAddress: [20]byte{}, - serialized: serializedInitMsgBatch, + RawLog: types.Log{}, + DataLocation: 0, + BridgeAddress: [20]byte{}, + Serialized: serializedInitMsgBatch, } err = tracker.AddSequencerBatches(ctx, nil, []*SequencerInboxBatch{emptyBatch}) Require(t, err) diff --git a/arbnode/inbox_test.go b/arbnode/inbox_test.go index 823284bb61..a47577f279 100644 --- a/arbnode/inbox_test.go +++ b/arbnode/inbox_test.go @@ -59,7 +59,15 @@ func (w *execClientWrapper) MarkFeedStart(to arbutil.MessageIndex) containers.Pr return containers.NewReadyPromise(markFeedStartWithReturn(to)) } -func (w *execClientWrapper) Maintenance() containers.PromiseInterface[struct{}] { +func (w *execClientWrapper) ShouldTriggerMaintenance() containers.PromiseInterface[bool] { + return containers.NewReadyPromise(false, nil) +} + +func (w *execClientWrapper) MaintenanceStatus() containers.PromiseInterface[*execution.MaintenanceStatus] { + return containers.NewReadyPromise(&execution.MaintenanceStatus{}, nil) +} + +func (w *execClientWrapper) TriggerMaintenance() containers.PromiseInterface[struct{}] { return containers.NewReadyPromise(struct{}{}, nil) } @@ -128,7 +136,7 @@ func NewTransactionStreamerForTest(t *testing.T, ctx context.Context, ownerAddre initReader := statetransfer.NewMemoryInitDataReader(&initData) cacheConfig := core.DefaultCacheConfigWithScheme(env.GetTestStateScheme()) - bc, err := gethexec.WriteOrTestBlockChain(chainDb, cacheConfig, initReader, chainConfig, nil, nil, arbostypes.TestInitMessage, gethexec.ConfigDefault.TxLookupLimit, 0) + bc, err := gethexec.WriteOrTestBlockChain(chainDb, cacheConfig, initReader, chainConfig, nil, nil, arbostypes.TestInitMessage, &gethexec.ConfigDefault.TxIndexer, 0) if err != nil { Fail(t, err) diff --git a/arbnode/maintenance.go b/arbnode/maintenance.go index 56475072a4..785675fa64 100644 --- a/arbnode/maintenance.go +++ b/arbnode/maintenance.go @@ -5,16 +5,11 @@ package arbnode import ( "context" - "errors" "fmt" - "strconv" - "strings" - "sync/atomic" "time" flag "github.com/spf13/pflag" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbnode/redislock" @@ -22,90 +17,47 @@ import ( "github.com/offchainlabs/nitro/util/stopwaiter" ) -// Regularly runs db compaction if configured type MaintenanceRunner struct { stopwaiter.StopWaiter - exec execution.ExecutionClient - config MaintenanceConfigFetcher - seqCoordinator *SeqCoordinator - dbs []ethdb.Database - lastMaintenance atomic.Int64 + exec execution.ExecutionClient + config MaintenanceConfigFetcher + seqCoordinator *SeqCoordinator - // lock is used to ensures that at any given time, only single node is on + // lock is used to ensure that at any given time, only single node is on // maintenance mode. lock *redislock.Simple } type MaintenanceConfig struct { - TimeOfDay string `koanf:"time-of-day" reload:"hot"` - Lock redislock.SimpleCfg `koanf:"lock" reload:"hot"` - Triggerable bool `koanf:"triggerable" reload:"hot"` - - // Generated: the minutes since start of UTC day to compact at - minutesAfterMidnight int - enabled bool -} - -// Returns true if successful -func (c *MaintenanceConfig) parseDbCompactionTime() bool { - if c.TimeOfDay == "" { - return true - } - parts := strings.Split(c.TimeOfDay, ":") - if len(parts) != 2 { - return false - } - hours, err := strconv.Atoi(parts[0]) - if err != nil || hours >= 24 { - return false - } - minutes, err := strconv.Atoi(parts[1]) - if err != nil || minutes >= 60 { - return false - } - c.enabled = true - c.minutesAfterMidnight = hours*60 + minutes - return true -} - -func (c *MaintenanceConfig) Validate() error { - if !c.parseDbCompactionTime() { - return fmt.Errorf("expected sequencer coordinator db compaction time to be in 24-hour HH:MM format but got \"%v\"", c.TimeOfDay) - } - return nil + Enable bool `koanf:"enable" reload:"hot"` + CheckInterval time.Duration `koanf:"check-interval" reload:"hot"` + Lock redislock.SimpleCfg `koanf:"lock" reload:"hot"` } func MaintenanceConfigAddOptions(prefix string, f *flag.FlagSet) { - f.String(prefix+".time-of-day", DefaultMaintenanceConfig.TimeOfDay, "UTC 24-hour time of day to run maintenance at (e.g. 15:00)") - f.Bool(prefix+".triggerable", DefaultMaintenanceConfig.Triggerable, "maintenance is triggerable via rpc") + f.Bool(prefix+".enable", DefaultMaintenanceConfig.Enable, "enable maintenance runner") + f.Duration(prefix+".check-interval", DefaultMaintenanceConfig.CheckInterval, "how often to check if maintenance should be run") redislock.AddConfigOptions(prefix+".lock", f) } var DefaultMaintenanceConfig = MaintenanceConfig{ - TimeOfDay: "", - Lock: redislock.DefaultCfg, - Triggerable: false, - - minutesAfterMidnight: 0, + Enable: false, + CheckInterval: time.Minute, + Lock: redislock.DefaultCfg, } type MaintenanceConfigFetcher func() *MaintenanceConfig -func NewMaintenanceRunner(config MaintenanceConfigFetcher, seqCoordinator *SeqCoordinator, dbs []ethdb.Database, exec execution.ExecutionClient) (*MaintenanceRunner, error) { +func NewMaintenanceRunner(config MaintenanceConfigFetcher, seqCoordinator *SeqCoordinator, exec execution.ExecutionClient) (*MaintenanceRunner, error) { cfg := config() - if err := cfg.Validate(); err != nil { - return nil, fmt.Errorf("validating config: %w", err) - } + res := &MaintenanceRunner{ exec: exec, config: config, seqCoordinator: seqCoordinator, - dbs: dbs, } - // node restart is considered "maintenance" - res.lastMaintenance.Store(time.Now().UnixMilli()) if seqCoordinator != nil { c := func() *redislock.SimpleCfg { return &cfg.Lock } r := func() bool { return true } // always ready to lock @@ -120,157 +72,89 @@ func NewMaintenanceRunner(config MaintenanceConfigFetcher, seqCoordinator *SeqCo func (mr *MaintenanceRunner) Start(ctxIn context.Context) { mr.StopWaiter.Start(ctxIn, mr) - mr.CallIteratively(mr.maybeRunScheduledMaintenance) -} - -func wentPastTimeOfDay(before time.Time, after time.Time, timeOfDay int) bool { - if !after.After(before) { - return false - } - if after.Sub(before) >= time.Hour*24 { - return true - } - prevMinutes := before.Hour()*60 + before.Minute() - newMinutes := after.Hour()*60 + after.Minute() - if newMinutes < prevMinutes { - newMinutes += 60 * 24 - } - dbCompactionMinutes := timeOfDay - if dbCompactionMinutes < prevMinutes { - dbCompactionMinutes += 60 * 24 - } - return prevMinutes < dbCompactionMinutes && newMinutes >= dbCompactionMinutes -} - -// bool if running currently, if false - time of last time it was running -func (mr *MaintenanceRunner) getPrevMaintenance() (bool, time.Time) { - milli := mr.lastMaintenance.Load() - if milli == 0 { - return true, time.Time{} - } - return false, time.UnixMilli(milli) -} - -// bool if running currently, if false - duration since last time it was running -func (mr *MaintenanceRunner) TimeSinceLastMaintenance() (bool, time.Duration) { - running, maintTime := mr.getPrevMaintenance() - if running { - return true, 0 - } - return false, time.Since(maintTime) + mr.CallIteratively(mr.MaybeRunMaintenance) } -func (mr *MaintenanceRunner) setMaintenanceDone() { - milli := time.Now().UnixMilli() - prev := mr.lastMaintenance.Swap(milli) - if prev != 0 { - log.Error("maintenance executed in parallel", "current", time.UnixMilli(milli), "prev", time.UnixMilli(prev)) - } -} - -func (mr *MaintenanceRunner) setMaintenanceStart() error { - prev := mr.lastMaintenance.Swap(0) - if prev == 0 { - return errors.New("already running") - } - return nil -} - -func (mr *MaintenanceRunner) maybeRunScheduledMaintenance(ctx context.Context) time.Duration { +// exported for testing +func (mr *MaintenanceRunner) MaybeRunMaintenance(ctx context.Context) time.Duration { config := mr.config() - if !config.enabled { - return time.Minute + if !config.Enable { + log.Debug("maintenance is disabled, skipping") + return config.CheckInterval } - now := time.Now().UTC() - - inMaintenance, lastMaintenance := mr.getPrevMaintenance() - if inMaintenance { - return time.Minute - } - - if !wentPastTimeOfDay(lastMaintenance, now, config.minutesAfterMidnight) { - return time.Minute - } - - err := mr.attemptMaintenance(ctx) + shouldTriggerMaintenance, err := mr.exec.ShouldTriggerMaintenance().Await(mr.GetContext()) if err != nil { - log.Warn("scheduled maintenance error", "err", err) + log.Error("error checking if maintenance should be triggered", "err", err) + return config.CheckInterval } - - return time.Minute -} - -func (mr *MaintenanceRunner) Trigger() error { - if !mr.config().Triggerable { - return errors.New("maintenance not configured to be triggerable") - } - if running, _ := mr.getPrevMaintenance(); running { - return nil + if !shouldTriggerMaintenance { + log.Debug("skipping maintenance, not triggered") + return config.CheckInterval } - // maintenance takes a long time, run on a separate thread - mr.LaunchThread(func(ctx context.Context) { - err := mr.attemptMaintenance(ctx) - if err != nil { - log.Warn("triggered maintenance returned error", "err", err) - } - }) - return nil -} -func (mr *MaintenanceRunner) attemptMaintenance(ctx context.Context) error { + // If seqCoordinator is nil there is no need to coordinate maintenance running with other sequecers. if mr.seqCoordinator == nil { - return mr.runMaintenance() + mr.runMaintenance() + return config.CheckInterval } release := make(chan struct{}) if !mr.lock.AttemptLockAndPeriodicallyRefreshIt(ctx, release) { - return errors.New("did not catch maintenance lock") + log.Warn("maintenance lock not acquired, skipping maintenance") + return config.CheckInterval } defer func() { - release <- struct{}{} + close(release) }() - res := errors.New("maintenance failed to hand-off chosen one") - - log.Info("Attempting avoiding lockout and handing off", "targetTime", mr.config().TimeOfDay) - // Avoid lockout for the sequencer and try to handoff. + log.Info("Attempting avoiding lockout and handing off") if mr.seqCoordinator.AvoidLockout(ctx) && mr.seqCoordinator.TryToHandoffChosenOne(ctx) { - res = mr.runMaintenance() + log.Info("Avoided lockout and handed off chosen one") + mr.runMaintenance() + } else { + log.Error("maintenance failed to hand-off chosen one") + } + mr.seqCoordinator.SeekLockout(ctx) + + return config.CheckInterval +} + +func (mr *MaintenanceRunner) waitMaintenanceToComplete(ctx context.Context) { + ticker := time.NewTicker(1 * time.Second) + for { + select { + case <-ctx.Done(): + log.Warn("Maintenance wait interrupted", "err", ctx.Err()) + return + default: + select { + case <-ctx.Done(): + log.Warn("Maintenance wait interrupted", "err", ctx.Err()) + return + case <-ticker.C: + maintenanceStatus, err := mr.exec.MaintenanceStatus().Await(ctx) + if err != nil { + log.Error("Error checking maintenance status", "err", err) + continue + } + if maintenanceStatus.IsRunning { + log.Debug("Maintenance is still running, waiting for completion") + } else { + log.Info("Execution is not running maintenance anymore, maintenance completed successfully") + return + } + } + } } - defer mr.seqCoordinator.SeekLockout(ctx) // needs called even if c.Zombify returns false - return res } -func (mr *MaintenanceRunner) runMaintenance() error { - err := mr.setMaintenanceStart() +func (mr *MaintenanceRunner) runMaintenance() { + log.Info("Triggering maintenance") + _, err := mr.exec.TriggerMaintenance().Await(mr.GetContext()) if err != nil { - return err - } - defer mr.setMaintenanceDone() - - log.Info("Compacting databases and flushing triedb to disk (this may take a while...)") - results := make(chan error, len(mr.dbs)) - expected := 0 - for _, db := range mr.dbs { - expected++ - db := db - go func() { - results <- db.Compact(nil, nil) - }() - } - expected++ - go func() { - _, res := mr.exec.Maintenance().Await(mr.GetContext()) - results <- res - }() - for i := 0; i < expected; i++ { - subErr := <-results - if subErr != nil { - err = errors.Join(err, subErr) - log.Warn("maintenance error", "err", subErr) - } + log.Error("Error triggering maintenance", "err", err) + return } - log.Info("Done compacting databases and flushing triedb to disk") - return err + mr.waitMaintenanceToComplete(mr.GetContext()) } diff --git a/arbnode/maintenance_test.go b/arbnode/maintenance_test.go deleted file mode 100644 index ea842e3c14..0000000000 --- a/arbnode/maintenance_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md - -package arbnode - -import ( - "testing" - "time" -) - -func TestWentPastTimeOfDay(t *testing.T) { - eleven_pm := time.Date(2000, 1, 1, 23, 0, 0, 0, time.UTC) - midnight := time.Date(2000, 1, 2, 0, 0, 0, 0, time.UTC) - one_am := time.Date(2000, 1, 2, 1, 0, 0, 0, time.UTC) - - for _, tc := range []struct { - before, after time.Time - timeOfDay string - want bool - }{ - {before: eleven_pm, after: eleven_pm, timeOfDay: "23:00"}, - {before: midnight, after: midnight, timeOfDay: "00:00"}, - {before: one_am, after: one_am, timeOfDay: "1:00"}, - {before: eleven_pm, after: midnight, timeOfDay: "23:30", want: true}, - {before: eleven_pm, after: midnight, timeOfDay: "00:00", want: true}, - {before: eleven_pm, after: one_am, timeOfDay: "00:00", want: true}, - {before: eleven_pm, after: one_am, timeOfDay: "01:00", want: true}, - {before: eleven_pm, after: one_am, timeOfDay: "02:00"}, - {before: eleven_pm, after: one_am, timeOfDay: "12:00"}, - {before: midnight, after: one_am, timeOfDay: "00:00"}, - {before: midnight, after: one_am, timeOfDay: "00:30", want: true}, - {before: midnight, after: one_am, timeOfDay: "01:00", want: true}, - } { - config := MaintenanceConfig{TimeOfDay: tc.timeOfDay} - Require(t, config.Validate(), "Failed to validate sample config") - - if got := wentPastTimeOfDay(tc.before, tc.after, config.minutesAfterMidnight); got != tc.want { - t.Errorf("wentPastTimeOfDay(%v, %v, %q) = %T want %T", tc.before, tc.after, tc.timeOfDay, got, tc.want) - } - } -} diff --git a/arbnode/message-extraction/extraction-function/abis.go b/arbnode/mel/extraction/abis.go similarity index 69% rename from arbnode/message-extraction/extraction-function/abis.go rename to arbnode/mel/extraction/abis.go index af74d7fa86..ba18f0e896 100644 --- a/arbnode/message-extraction/extraction-function/abis.go +++ b/arbnode/mel/extraction/abis.go @@ -1,4 +1,4 @@ -package extractionfunction +package melextraction import ( "github.com/ethereum/go-ethereum/accounts/abi" @@ -7,14 +7,21 @@ import ( "github.com/offchainlabs/nitro/solgen/go/bridgegen" ) +var batchDeliveredID common.Hash var inboxMessageDeliveredID common.Hash var inboxMessageFromOriginID common.Hash +var seqInboxABI *abi.ABI var iBridgeABI *abi.ABI var iInboxABI *abi.ABI var iDelayedMessageProviderABI *abi.ABI func init() { var err error + sequencerBridgeABI, err := bridgegen.SequencerInboxMetaData.GetAbi() + if err != nil { + panic(err) + } + batchDeliveredID = sequencerBridgeABI.Events["SequencerBatchDelivered"].ID parsedIBridgeABI, err := bridgegen.IBridgeMetaData.GetAbi() if err != nil { panic(err) @@ -27,9 +34,14 @@ func init() { iDelayedMessageProviderABI = parsedIMessageProviderABI inboxMessageDeliveredID = parsedIMessageProviderABI.Events["InboxMessageDelivered"].ID inboxMessageFromOriginID = parsedIMessageProviderABI.Events["InboxMessageDeliveredFromOrigin"].ID + seqInboxABI, err = bridgegen.SequencerInboxMetaData.GetAbi() + if err != nil { + panic(err) + } parsedIInboxABI, err := bridgegen.IInboxMetaData.GetAbi() if err != nil { panic(err) } iInboxABI = parsedIInboxABI + batchDeliveredID = sequencerBridgeABI.Events["SequencerBatchDelivered"].ID } diff --git a/arbnode/mel/extraction/batch_lookup.go b/arbnode/mel/extraction/batch_lookup.go new file mode 100644 index 0000000000..8ba61f4cb3 --- /dev/null +++ b/arbnode/mel/extraction/batch_lookup.go @@ -0,0 +1,101 @@ +package melextraction + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" +) + +type eventUnpacker interface { + unpackLogTo(event any, abi *abi.ABI, eventName string, log types.Log) error +} + +func parseBatchesFromBlock( + ctx context.Context, + melState *mel.State, + parentChainHeader *types.Header, + txsFetcher TransactionsFetcher, + receiptFetcher ReceiptFetcher, + eventUnpacker eventUnpacker, +) ([]*mel.SequencerInboxBatch, []*types.Transaction, []uint, error) { + allBatches := make([]*mel.SequencerInboxBatch, 0) + allBatchTxs := make([]*types.Transaction, 0) + allBatchTxIndices := make([]uint, 0) + parentChainBlockTxs, err := txsFetcher.TransactionsByHeader(ctx, parentChainHeader.Hash()) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to fetch transactions for parent chain block %v: %w", parentChainHeader.Hash(), err) + } + for i, tx := range parentChainBlockTxs { + // TODO: remove this temporary work around for handling init message, i.e skipping the check when msgCount==0 + if melState.MsgCount != 0 { + if tx.To() == nil { + continue + } + if *tx.To() != melState.BatchPostingTargetAddress { + continue + } + } + // Fetch the receipts for the transaction to get the logs. + txIndex := uint(i) // #nosec G115 + receipt, err := receiptFetcher.ReceiptForTransactionIndex(ctx, txIndex) + if err != nil { + return nil, nil, nil, err + } + if len(receipt.Logs) == 0 { + continue + } + batches := make([]*mel.SequencerInboxBatch, 0, len(receipt.Logs)) + txs := make([]*types.Transaction, 0, len(receipt.Logs)) + txIndices := make([]uint, 0, len(receipt.Logs)) + var lastSeqNum *uint64 + for _, log := range receipt.Logs { + if log == nil || log.Topics[0] != batchDeliveredID { + continue + } + event := new(bridgegen.SequencerInboxSequencerBatchDelivered) + if err := eventUnpacker.unpackLogTo(event, seqInboxABI, "SequencerBatchDelivered", *log); err != nil { + return nil, nil, nil, err + } + if !event.BatchSequenceNumber.IsUint64() { + return nil, nil, nil, errors.New("sequencer inbox event has non-uint64 sequence number") + } + if !event.AfterDelayedMessagesRead.IsUint64() { + return nil, nil, nil, errors.New("sequencer inbox event has non-uint64 delayed messages read") + } + + seqNum := event.BatchSequenceNumber.Uint64() + if lastSeqNum != nil { + if seqNum != *lastSeqNum+1 { + return nil, nil, nil, fmt.Errorf("sequencer batches out of order; after batch %v got batch %v", *lastSeqNum, seqNum) + } + } + lastSeqNum = &seqNum + batch := &mel.SequencerInboxBatch{ + BlockHash: log.BlockHash, + ParentChainBlockNumber: log.BlockNumber, + SequenceNumber: seqNum, + BeforeInboxAcc: event.BeforeAcc, + AfterInboxAcc: event.AfterAcc, + AfterDelayedAcc: event.DelayedAcc, + AfterDelayedCount: event.AfterDelayedMessagesRead.Uint64(), + RawLog: *log, + TimeBounds: event.TimeBounds, + DataLocation: mel.BatchDataLocation(event.DataLocation), + BridgeAddress: log.Address, + } + batches = append(batches, batch) + txs = append(txs, tx) + txIndices = append(txIndices, uint(i)) // #nosec G115 + } + allBatches = append(allBatches, batches...) + allBatchTxs = append(allBatchTxs, txs...) + allBatchTxIndices = append(allBatchTxIndices, txIndices...) + } + return allBatches, allBatchTxs, allBatchTxIndices, nil +} diff --git a/arbnode/mel/extraction/batch_lookup_test.go b/arbnode/mel/extraction/batch_lookup_test.go new file mode 100644 index 0000000000..42f55265e4 --- /dev/null +++ b/arbnode/mel/extraction/batch_lookup_test.go @@ -0,0 +1,553 @@ +package melextraction + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/trie" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" +) + +func Test_parseBatchesFromBlock(t *testing.T) { + ctx := context.Background() + batchPostingTargetAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") + event, packedLog, wantedBatch := setupParseBatchesTest(t, big.NewInt(1)) + + t.Run("block with no transactions", func(t *testing.T) { + blockHeader := &types.Header{} + blockBody := &types.Body{} + block := types.NewBlock( + blockHeader, + blockBody, + nil, + trie.NewStackTrie(nil), + ) + batches, txs, txIndices, err := parseBatchesFromBlock( + ctx, + nil, + block.Header(), + &mockTxsFetcher{}, + nil, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(batches)) + require.Equal(t, 0, len(txs)) + require.Equal(t, 0, len(txIndices)) + }) + t.Run("block with no transactions with a to field", func(t *testing.T) { + blockHeader := &types.Header{} + txData := &types.DynamicFeeTx{ + To: nil, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + block := types.NewBlock( + blockHeader, + blockBody, + nil, + trie.NewStackTrie(nil), + ) + batches, txs, txIndices, err := parseBatchesFromBlock( + ctx, + &mel.State{MsgCount: 1}, + block.Header(), + txsFetcher, + nil, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(batches)) + require.Equal(t, 0, len(txs)) + require.Equal(t, 0, len(txIndices)) + }) + t.Run("block with transactions not targeting the batch posting target address", func(t *testing.T) { + blockHeader := &types.Header{} + addr := common.BytesToAddress([]byte("deadbeef")) + txData := &types.DynamicFeeTx{ + To: &addr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + block := types.NewBlock( + blockHeader, + blockBody, + nil, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + MsgCount: 1, + } + batches, txs, txIndices, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + nil, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(batches)) + require.Equal(t, 0, len(txs)) + require.Equal(t, 0, len(txIndices)) + }) + t.Run("bad receipt fetcher request", func(t *testing.T) { + blockHeader := &types.Header{} + txData := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + block := types.NewBlock( + blockHeader, + blockBody, + nil, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: nil, + err: errors.New("oops"), + } + _, _, _, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + receiptFetcher, + nil, + ) + require.ErrorContains(t, err, "oops") + }) + t.Run("transactions with no receipt logs", func(t *testing.T) { + blockHeader := &types.Header{} + txData := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + receipt := &types.Receipt{ + Logs: []*types.Log{}, + } + receipts := []*types.Receipt{receipt} + block := types.NewBlock( + blockHeader, + blockBody, + receipts, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + batches, txs, txIndices, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + receiptFetcher, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(batches)) + require.Equal(t, 0, len(txs)) + require.Equal(t, 0, len(txIndices)) + }) + t.Run("receipt log with wrong topic id", func(t *testing.T) { + blockHeader := &types.Header{} + txData := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + receipt := &types.Receipt{ + Logs: []*types.Log{ + { + Topics: []common.Hash{common.BytesToHash([]byte("wrong topic"))}, + }, + }, + } + receipts := []*types.Receipt{receipt} + block := types.NewBlock( + blockHeader, + blockBody, + receipts, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + batches, txs, txIndices, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + receiptFetcher, + nil, + ) + require.NoError(t, err) + require.Equal(t, 0, len(batches)) + require.Equal(t, 0, len(txs)) + require.Equal(t, 0, len(txIndices)) + }) + t.Run("Unpack log fails", func(t *testing.T) { + blockHeader := &types.Header{} + txData := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + receipt := &types.Receipt{ + Logs: []*types.Log{ + { + Topics: []common.Hash{batchDeliveredID}, + Data: packedLog, + }, + }, + } + receipts := []*types.Receipt{receipt} + block := types.NewBlock( + blockHeader, + blockBody, + receipts, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + eventUnpacker := &mockEventUnpacker{ + events: []*bridgegen.SequencerInboxSequencerBatchDelivered{event}, + idx: 0, + err: errors.New("oops event unpacking error"), + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + _, _, _, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + receiptFetcher, + eventUnpacker, + ) + require.ErrorContains(t, err, "oops event unpacking error") + }) + t.Run("OK", func(t *testing.T) { + blockHeader := &types.Header{} + txData := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx}, + } + receipt := &types.Receipt{ + Logs: []*types.Log{ + { + Topics: []common.Hash{batchDeliveredID}, + Data: packedLog, + }, + }, + } + receipts := []*types.Receipt{receipt} + block := types.NewBlock( + blockHeader, + blockBody, + receipts, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + eventUnpacker := &mockEventUnpacker{ + events: []*bridgegen.SequencerInboxSequencerBatchDelivered{event}, + idx: 0, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } + batches, txs, txIndices, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + receiptFetcher, + eventUnpacker, + ) + require.NoError(t, err) + + require.Equal(t, 1, len(batches)) + require.Equal(t, 1, len(txs)) + require.Equal(t, 1, len(txIndices)) + require.Equal(t, wantedBatch.SequenceNumber, batches[0].SequenceNumber) + require.Equal(t, wantedBatch.BeforeInboxAcc, batches[0].BeforeInboxAcc) + require.Equal(t, wantedBatch.AfterInboxAcc, batches[0].AfterInboxAcc) + require.Equal(t, wantedBatch.AfterDelayedAcc, batches[0].AfterDelayedAcc) + require.Equal(t, wantedBatch.AfterDelayedCount, batches[0].AfterDelayedCount) + }) +} + +func Test_parseBatchesFromBlock_outOfOrderBatches(t *testing.T) { + ctx := context.Background() + batchPostingTargetAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") + event1, packedLog1, _ := setupParseBatchesTest(t, big.NewInt(2)) + event2, packedLog2, _ := setupParseBatchesTest(t, big.NewInt(1)) + + blockHeader := &types.Header{} + txData1 := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + txData2 := &types.DynamicFeeTx{ + To: &batchPostingTargetAddr, + Nonce: 2, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx1 := types.NewTx(txData1) + tx2 := types.NewTx(txData2) + blockBody := &types.Body{ + Transactions: []*types.Transaction{tx1, tx2}, + } + receipt := &types.Receipt{ + Logs: []*types.Log{ + { + Topics: []common.Hash{batchDeliveredID}, + Data: packedLog1, + }, + { + Topics: []common.Hash{batchDeliveredID}, + Data: packedLog2, + }, + }, + } + receipts := []*types.Receipt{receipt} + block := types.NewBlock( + blockHeader, + blockBody, + receipts, + trie.NewStackTrie(nil), + ) + melState := &mel.State{ + BatchPostingTargetAddress: batchPostingTargetAddr, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + eventUnpacker := &mockEventUnpacker{ + events: []*bridgegen.SequencerInboxSequencerBatchDelivered{ + event1, + event2, + }, + idx: 0, + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx1, tx2}, + } + _, _, _, err := parseBatchesFromBlock( + ctx, + melState, + block.Header(), + txsFetcher, + receiptFetcher, + eventUnpacker, + ) + require.ErrorContains(t, err, "sequencer batches out of order") +} + +func setupParseBatchesTest(t *testing.T, seqNumber *big.Int) ( + *bridgegen.SequencerInboxSequencerBatchDelivered, + []byte, + *mel.SequencerInboxBatch, +) { + event := &bridgegen.SequencerInboxSequencerBatchDelivered{ + BatchSequenceNumber: seqNumber, + BeforeAcc: common.BytesToHash([]byte{1}), + AfterAcc: common.BytesToHash([]byte{2}), + DelayedAcc: common.BytesToHash([]byte{3}), + AfterDelayedMessagesRead: big.NewInt(4), + TimeBounds: bridgegen.IBridgeTimeBounds{ + MinTimestamp: 0, + MaxTimestamp: 100, + MinBlockNumber: 0, + MaxBlockNumber: 100, + }, + DataLocation: 1, + } + eventABI := seqInboxABI.Events["SequencerBatchDelivered"] + packedLog, err := eventABI.Inputs.Pack( + event.BatchSequenceNumber, + event.BeforeAcc, + event.AfterAcc, + event.DelayedAcc, + event.AfterDelayedMessagesRead, + event.TimeBounds, + event.DataLocation, + ) + require.NoError(t, err) + wantedBatch := &mel.SequencerInboxBatch{ + SequenceNumber: event.BatchSequenceNumber.Uint64(), + BeforeInboxAcc: event.BeforeAcc, + AfterInboxAcc: event.AfterAcc, + AfterDelayedAcc: event.DelayedAcc, + AfterDelayedCount: event.AfterDelayedMessagesRead.Uint64(), + } + return event, packedLog, wantedBatch +} + +type mockEventUnpacker struct { + events []*bridgegen.SequencerInboxSequencerBatchDelivered + idx uint + err error +} + +func (m *mockEventUnpacker) unpackLogTo( + event any, abi *abi.ABI, eventName string, log types.Log, +) error { + if m.err != nil { + return m.err + } + ev, ok := event.(*bridgegen.SequencerInboxSequencerBatchDelivered) + if !ok { + return errors.New("wrong event type") + } + *ev = *m.events[m.idx] + m.idx += 1 + return nil +} + +type mockTxsFetcher struct { + txs types.Transactions + err error +} + +func (m *mockTxsFetcher) TransactionsByHeader( + ctx context.Context, + parentChainHeaderHash common.Hash, +) (types.Transactions, error) { + if m.err != nil { + return nil, m.err + } + return m.txs, nil +} + +type mockReceiptFetcher struct { + receipts []*types.Receipt + err error +} + +func (m *mockReceiptFetcher) ReceiptForTransactionIndex( + _ context.Context, + idx uint, +) (*types.Receipt, error) { + if m.err != nil { + return nil, m.err + } + return m.receipts[idx], nil +} diff --git a/arbnode/mel/extraction/batch_serialization.go b/arbnode/mel/extraction/batch_serialization.go new file mode 100644 index 0000000000..eea875e742 --- /dev/null +++ b/arbnode/mel/extraction/batch_serialization.go @@ -0,0 +1,126 @@ +package melextraction + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/daprovider" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" +) + +func serializeBatch( + ctx context.Context, + batch *mel.SequencerInboxBatch, + tx *types.Transaction, + txIndex uint, + receiptFetcher ReceiptFetcher, +) ([]byte, error) { + if batch.Serialized != nil { + return batch.Serialized, nil + } + + var fullData []byte + + // Serialize the header + headerVals := []uint64{ + batch.TimeBounds.MinTimestamp, + batch.TimeBounds.MaxTimestamp, + batch.TimeBounds.MinBlockNumber, + batch.TimeBounds.MaxBlockNumber, + batch.AfterDelayedCount, + } + for _, bound := range headerVals { + var intData [8]byte + binary.BigEndian.PutUint64(intData[:], bound) + fullData = append(fullData, intData[:]...) + } + + // Append the batch data + data, err := getSequencerBatchData( + ctx, + batch, + tx, + txIndex, + receiptFetcher, + ) + if err != nil { + return nil, err + } + fullData = append(fullData, data...) + + batch.Serialized = fullData + return fullData, nil +} + +func getSequencerBatchData( + ctx context.Context, + batch *mel.SequencerInboxBatch, + tx *types.Transaction, + txIndex uint, + receiptFetcher ReceiptFetcher, +) ([]byte, error) { + addSequencerL2BatchFromOriginCallABI := seqInboxABI.Methods["addSequencerL2BatchFromOrigin0"] + switch batch.DataLocation { + case mel.BatchDataTxInput: + data := tx.Data() + if len(data) < 4 { + return nil, errors.New("transaction data too short") + } + args := make(map[string]interface{}) + if err := addSequencerL2BatchFromOriginCallABI.Inputs.UnpackIntoMap(args, data[4:]); err != nil { + return nil, err + } + dataBytes, ok := args["data"].([]byte) + if !ok { + return nil, errors.New("args[\"data\"] not a byte array") + } + return dataBytes, nil + case mel.BatchDataSeparateEvent: + sequencerBatchDataABI := seqInboxABI.Events["SequencerBatchData"].ID + var numberAsHash common.Hash + // we want to convert a batch sequencer number which is a uint64 into a big-endian byte slice of size 32, + // so the last 8 bytes of that slice will contain the serialized batch.SequenceNumber + binary.BigEndian.PutUint64(numberAsHash[(32-8):], batch.SequenceNumber) + receipt, err := receiptFetcher.ReceiptForTransactionIndex(ctx, txIndex) + if err != nil { + return nil, err + } + if len(receipt.Logs) == 0 { + return nil, errors.New("no logs found in transaction receipt") + } + topics := [][]common.Hash{{sequencerBatchDataABI}, {numberAsHash}} + filteredLogs := types.FilterLogs(receipt.Logs, nil, nil, []common.Address{batch.BridgeAddress}, topics) + if len(filteredLogs) == 0 { + return nil, errors.New("expected to find sequencer batch data") + } + if len(filteredLogs) > 1 { + return nil, errors.New("expected to find only one matching sequencer batch data") + } + event := new(bridgegen.SequencerInboxSequencerBatchData) + err = seqInboxABI.UnpackIntoInterface(event, "SequencerBatchData", filteredLogs[0].Data) + if err != nil { + return nil, err + } + return event.Data, nil + case mel.BatchDataNone: + // No data when in a force inclusion batch + return nil, nil + case mel.BatchDataBlobHashes: + if len(tx.BlobHashes()) == 0 { + return nil, fmt.Errorf("blob batch transaction %v has no blobs", tx.Hash()) + } + data := []byte{daprovider.BlobHashesHeaderFlag} + for _, h := range tx.BlobHashes() { + data = append(data, h[:]...) + } + return data, nil + default: + return nil, fmt.Errorf("batch has invalid data location %v", batch.DataLocation) + } +} diff --git a/arbnode/mel/extraction/batch_serialization_test.go b/arbnode/mel/extraction/batch_serialization_test.go new file mode 100644 index 0000000000..de217a0590 --- /dev/null +++ b/arbnode/mel/extraction/batch_serialization_test.go @@ -0,0 +1,335 @@ +package melextraction + +import ( + "context" + "errors" + "math/big" + "testing" + + "github.com/holiman/uint256" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" +) + +func Test_serializeBatch(t *testing.T) { + ctx := context.Background() + t.Run("batch data retrieval fails", func(t *testing.T) { + batch := &mel.SequencerInboxBatch{ + TimeBounds: bridgegen.IBridgeTimeBounds{ + MinTimestamp: 1, + MaxTimestamp: 2, + MinBlockNumber: 3, + MaxBlockNumber: 4, + }, + AfterDelayedCount: 1, + DataLocation: mel.BatchDataLocation(99), + } + _, err := serializeBatch(ctx, batch, nil, 0, nil) + require.ErrorContains(t, err, "invalid data location") + }) + t.Run("OK", func(t *testing.T) { + txData := &types.BlobTx{ + To: common.Address{}, + Nonce: 1, + Gas: 1, + Value: uint256.NewInt(0), + BlobHashes: []common.Hash{ + common.BigToHash(big.NewInt(1)), + common.BigToHash(big.NewInt(2)), + }, + } + tx := types.NewTx(txData) + batch := &mel.SequencerInboxBatch{ + TimeBounds: bridgegen.IBridgeTimeBounds{ + MinTimestamp: 1, + MaxTimestamp: 2, + MinBlockNumber: 3, + MaxBlockNumber: 4, + }, + AfterDelayedCount: 1, + DataLocation: mel.BatchDataBlobHashes, + } + serialized, err := serializeBatch(ctx, batch, tx, 0, nil) + require.NoError(t, err) + // Serialization includes 5 uint64 values (8 bytes each) and the full batch + // data appended at the end of the batch. + // Our blob hashes serialization is 2 * 32 bytes + 1 byte prefix = 65 bytes + // So the total size is 5 * 8 + 65 = 105 bytes. + require.Equal(t, 105, len(serialized)) + + // Expect some caching of serialized data. + secondRound, err := serializeBatch(ctx, batch, tx, 0, nil) + require.NoError(t, err) + require.Equal(t, serialized, secondRound) + }) +} + +func Test_getSequencerBatchData(t *testing.T) { + ctx := context.Background() + t.Run("invalid data location", func(t *testing.T) { + _, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataLocation(99), + }, + nil, + 0, + nil, + ) + require.ErrorContains(t, err, "invalid data location") + }) + t.Run("arbnode.BatchDataNone", func(t *testing.T) { + data, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataNone, + }, + nil, + 0, + nil, + ) + require.NoError(t, err) + require.Empty(t, data) + }) + t.Run("arbnode.BatchDataBlobHashes", func(t *testing.T) { + txData := &types.BlobTx{ + To: common.Address{}, + Nonce: 1, + Gas: 1, + Value: uint256.NewInt(0), + BlobHashes: []common.Hash{}, + } + tx := types.NewTx(txData) + _, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataBlobHashes, + }, + tx, + 0, + nil, + ) + require.ErrorContains(t, err, "has no blobs") + txData = &types.BlobTx{ + To: common.Address{}, + Nonce: 1, + Gas: 1, + Value: uint256.NewInt(0), + BlobHashes: []common.Hash{ + common.BigToHash(big.NewInt(1)), + common.BigToHash(big.NewInt(2)), + }, + } + tx = types.NewTx(txData) + data, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataBlobHashes, + }, + tx, + 0, + nil, + ) + require.NoError(t, err) + require.Equal(t, 65, len(data)) // Includes a 1 byte prefix. + }) + t.Run("arbnode.BatchDataTxInput", func(t *testing.T) { + msgData := []byte("foobar") + addSequencerL2BatchFromOriginCallABI := seqInboxABI.Methods["addSequencerL2BatchFromOrigin0"] + seqNumber := big.NewInt(1) + afterDelayedRead := big.NewInt(1) + gasRefunder := common.Address{} + prevMsgCount := big.NewInt(1) + newMsgCount := big.NewInt(1) + originTxData, err := addSequencerL2BatchFromOriginCallABI.Inputs.Pack(seqNumber, msgData, afterDelayedRead, gasRefunder, prevMsgCount, newMsgCount) + require.NoError(t, err) + fullTxData := make([]byte, 0) + fullTxData = append(fullTxData, addSequencerL2BatchFromOriginCallABI.ID...) + fullTxData = append(fullTxData, originTxData...) + txData := &types.DynamicFeeTx{ + To: nil, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + _, err = getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataTxInput, + }, + tx, + 0, + nil, + ) + require.ErrorContains(t, err, "transaction data too short") + txData = &types.DynamicFeeTx{ + To: nil, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: fullTxData, + } + tx = types.NewTx(txData) + data, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataTxInput, + }, + tx, + 0, + nil, + ) + require.NoError(t, err) + require.Equal(t, msgData, data) + }) + t.Run("arbnode.BatchDataSeparateEvent", func(t *testing.T) { + txData := &types.DynamicFeeTx{ + To: nil, + Nonce: 1, + GasFeeCap: big.NewInt(1), + GasTipCap: big.NewInt(1), + Gas: 1, + Value: big.NewInt(0), + Data: nil, + } + tx := types.NewTx(txData) + receipts := []*types.Receipt{ + { + Logs: []*types.Log{}, + }, + } + receiptFetcher := &mockReceiptFetcher{ + receipts: receipts, + err: errors.New("oops"), + } + _, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataSeparateEvent, + }, + tx, + 0, + receiptFetcher, + ) + require.ErrorContains(t, err, "oops") + + receiptFetcher = &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + _, err = getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataSeparateEvent, + }, + tx, + 0, + receiptFetcher, + ) + require.ErrorContains(t, err, "no logs found") + + receipts = []*types.Receipt{ + { + Logs: []*types.Log{ + { + Topics: []common.Hash{{}}, + }, + }, + }, + } + receiptFetcher = &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + _, err = getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + DataLocation: mel.BatchDataSeparateEvent, + }, + tx, + 0, + receiptFetcher, + ) + require.ErrorContains(t, err, "expected to find sequencer batch data") + + sequencerBatchDataABI := seqInboxABI.Events["SequencerBatchData"].ID + bridgeAddr := common.HexToAddress("0x1234567890123456789012345678901234567890") + receipts = []*types.Receipt{ + { + Logs: []*types.Log{ + { + Address: bridgeAddr, + Topics: []common.Hash{sequencerBatchDataABI, {}}, + }, + { + Address: bridgeAddr, + Topics: []common.Hash{sequencerBatchDataABI, {}}, + }, + }, + }, + } + receiptFetcher = &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + _, err = getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + BridgeAddress: bridgeAddr, + DataLocation: mel.BatchDataSeparateEvent, + }, + tx, + 0, + receiptFetcher, + ) + require.ErrorContains(t, err, "expected to find only one") + + event := &bridgegen.SequencerInboxSequencerBatchData{ + Data: []byte("foobar"), + } + eventABI := seqInboxABI.Events["SequencerBatchData"] + packedLog, err := eventABI.Inputs.NonIndexed().Pack( + event.Data, + ) + require.NoError(t, err) + receipts = []*types.Receipt{ + { + Logs: []*types.Log{ + { + Address: bridgeAddr, + Topics: []common.Hash{sequencerBatchDataABI, common.BigToHash(big.NewInt(1))}, + Data: packedLog, + }, + }, + }, + } + receiptFetcher = &mockReceiptFetcher{ + receipts: receipts, + err: nil, + } + data, err := getSequencerBatchData( + ctx, + &mel.SequencerInboxBatch{ + SequenceNumber: 1, + BridgeAddress: bridgeAddr, + DataLocation: mel.BatchDataSeparateEvent, + }, + tx, + 0, + receiptFetcher, + ) + require.NoError(t, err) + require.Equal(t, event.Data, data) + }) +} diff --git a/arbnode/message-extraction/extraction-function/delayed_message_lookup.go b/arbnode/mel/extraction/delayed_message_lookup.go similarity index 86% rename from arbnode/message-extraction/extraction-function/delayed_message_lookup.go rename to arbnode/mel/extraction/delayed_message_lookup.go index a99ab31056..5385fc2121 100644 --- a/arbnode/message-extraction/extraction-function/delayed_message_lookup.go +++ b/arbnode/mel/extraction/delayed_message_lookup.go @@ -1,4 +1,4 @@ -package extractionfunction +package melextraction import ( "bytes" @@ -12,23 +12,28 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/eth/filters" - "github.com/offchainlabs/nitro/arbnode" - meltypes "github.com/offchainlabs/nitro/arbnode/message-extraction/types" + "github.com/offchainlabs/nitro/arbnode/mel" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/solgen/go/bridgegen" ) func parseDelayedMessagesFromBlock( ctx context.Context, - melState *meltypes.State, - parentChainBlockNum *big.Int, - parentChainBlockTxs []*types.Transaction, + melState *mel.State, + parentChainHeader *types.Header, receiptFetcher ReceiptFetcher, -) ([]*arbnode.DelayedInboxMessage, error) { - msgScaffolds := make([]*arbnode.DelayedInboxMessage, 0) + txsFetcher TransactionsFetcher, +) ([]*mel.DelayedInboxMessage, error) { + msgScaffolds := make([]*mel.DelayedInboxMessage, 0) messageDeliveredEvents := make([]*bridgegen.IBridgeMessageDelivered, 0) + parentChainBlockTxs, err := txsFetcher.TransactionsByHeader( + ctx, + parentChainHeader.Hash(), + ) + if err != nil { + return nil, err + } for i, tx := range parentChainBlockTxs { if tx.To() == nil { continue @@ -52,7 +57,7 @@ func parseDelayedMessagesFromBlock( continue } delayedMessageScaffolds, parsedLogs, err := delayedMessageScaffoldsFromLogs( - parentChainBlockNum, + parentChainHeader.Number, relevantLogs, ) if err != nil { @@ -73,12 +78,15 @@ func parseDelayedMessagesFromBlock( } messageData := make(map[common.Hash][]byte) for i, tx := range parentChainBlockTxs { - if tx.To() == nil { - continue - } - _, ok := inboxAddressSet[*tx.To()] - if !ok { - continue + // TODO: remove this temporary work around for handling init message, i.e skipping the check when msgCount==0 + if melState.MsgCount != 0 { + if tx.To() == nil { + continue + } + _, ok := inboxAddressSet[*tx.To()] + if !ok { + continue + } } txIndex := uint(i) // #nosec G115 receipt, err := receiptFetcher.ReceiptForTransactionIndex(ctx, txIndex) @@ -92,7 +100,7 @@ func parseDelayedMessagesFromBlock( {inboxMessageDeliveredID, inboxMessageFromOriginID}, // matches either of these IDs. messageIds, // matches any of the message IDs. } - filteredInboxMessageLogs := filters.FilterLogs(receipt.Logs, nil, nil, inboxAddressList, topics) + filteredInboxMessageLogs := types.FilterLogs(receipt.Logs, nil, nil, inboxAddressList, topics) for _, inboxMsgLog := range filteredInboxMessageLogs { msgNum, msg, err := parseDelayedMessage( inboxMsgLog, @@ -123,7 +131,7 @@ func parseDelayedMessagesFromBlock( func delayedMessageScaffoldsFromLogs( parentChainBlockNum *big.Int, logs []*types.Log, -) ([]*arbnode.DelayedInboxMessage, []*bridgegen.IBridgeMessageDelivered, error) { +) ([]*mel.DelayedInboxMessage, []*bridgegen.IBridgeMessageDelivered, error) { if len(logs) == 0 { return nil, nil, nil } @@ -132,7 +140,7 @@ func delayedMessageScaffoldsFromLogs( // First, do a pass over the logs to extract message delivered events, which // contain an inbox address and a message index. for _, ethLog := range logs { - if ethLog == nil { + if ethLog == nil || len(ethLog.Topics) == 0 || ethLog.Topics[0] != iBridgeABI.Events["MessageDelivered"].ID { continue } event := new(bridgegen.IBridgeMessageDelivered) @@ -144,14 +152,14 @@ func delayedMessageScaffoldsFromLogs( // A list of delayed messages that do not have nil L2msg data within, which // will be filled in later after another pass over logs. - delayedMessageScaffolds := make([]*arbnode.DelayedInboxMessage, 0, len(parsedLogs)) + delayedMessageScaffolds := make([]*mel.DelayedInboxMessage, 0, len(parsedLogs)) // Next, we construct the messages themselves from the parsed logs. for _, parsedLog := range parsedLogs { msgKey := common.BigToHash(parsedLog.MessageIndex) _ = msgKey requestId := common.BigToHash(parsedLog.MessageIndex) - msg := &arbnode.DelayedInboxMessage{ + msg := &mel.DelayedInboxMessage{ BlockHash: parsedLog.Raw.BlockHash, BeforeInboxAcc: parsedLog.BeforeInboxAcc, Message: &arbostypes.L1IncomingMessage{ @@ -210,7 +218,7 @@ func parseDelayedMessage( } } -type sortableMessageList []*arbnode.DelayedInboxMessage +type sortableMessageList []*mel.DelayedInboxMessage func (l sortableMessageList) Len() int { return len(l) diff --git a/arbnode/message-extraction/extraction-function/delayed_message_lookup_test.go b/arbnode/mel/extraction/delayed_message_lookup_test.go similarity index 91% rename from arbnode/message-extraction/extraction-function/delayed_message_lookup_test.go rename to arbnode/mel/extraction/delayed_message_lookup_test.go index 1c5623c0c9..19c9f21dc4 100644 --- a/arbnode/message-extraction/extraction-function/delayed_message_lookup_test.go +++ b/arbnode/mel/extraction/delayed_message_lookup_test.go @@ -1,4 +1,4 @@ -package extractionfunction +package melextraction import ( "context" @@ -13,8 +13,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/trie" - "github.com/offchainlabs/nitro/arbnode" - meltypes "github.com/offchainlabs/nitro/arbnode/message-extraction/types" + "github.com/offchainlabs/nitro/arbnode/mel" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/solgen/go/bridgegen" ) @@ -22,21 +21,26 @@ import ( func Test_parseDelayedMessagesFromBlock(t *testing.T) { ctx := context.Background() delayedMsgPostingAddr := common.BytesToAddress([]byte("deadbeef")) - melState := &meltypes.State{ + melState := &mel.State{ + MsgCount: 1, DelayedMessagePostingTargetAddress: delayedMsgPostingAddr, } - parentChainBlockNum := big.NewInt(1) - parentChainBlockTxs := []*types.Transaction{} + header := &types.Header{ + Number: big.NewInt(1), + } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{}, + } receiptFetcher := &mockReceiptFetcher{} t.Run("no transactions", func(t *testing.T) { msgs, err := parseDelayedMessagesFromBlock( ctx, melState, - parentChainBlockNum, - parentChainBlockTxs, + header, receiptFetcher, + txsFetcher, ) require.NoError(t, err) require.Empty(t, msgs) @@ -55,6 +59,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx}, } + txsFetcher = &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } block := types.NewBlock( &types.Header{}, blockBody, @@ -64,9 +71,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { msgs, err := parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.NoError(t, err) require.Empty(t, msgs) @@ -85,6 +92,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx}, } + txsFetcher = &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } receipt := &types.Receipt{ Logs: []*types.Log{}, } @@ -101,9 +111,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { msgs, err := parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.NoError(t, err) require.Empty(t, msgs) @@ -123,6 +133,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx}, } + txsFetcher = &mockTxsFetcher{ + txs: []*types.Transaction{tx}, + } messageIndexBytes := common.BigToHash(event.MessageIndex) receipt := &types.Receipt{ Logs: []*types.Log{ @@ -150,9 +163,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { _, err := parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.ErrorContains(t, err, "message 1 data not found") }) @@ -188,6 +201,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx1, tx2}, } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx1, tx2}, + } messageIndexBytes := common.BigToHash(delayedMsgEvent.MessageIndex) receipt1 := &types.Receipt{ Logs: []*types.Log{ @@ -227,9 +243,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { _, err = parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.ErrorContains(t, err, "mismatched hash") }) @@ -285,6 +301,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx1, tx2}, } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx1, tx2}, + } messageIndexBytes := common.BigToHash(delayedMsgEvent.MessageIndex) receipt1 := &types.Receipt{ Logs: []*types.Log{ @@ -324,9 +343,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { delayedMessages, err := parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.NoError(t, err) require.Equal(t, 1, len(delayedMessages)) @@ -377,6 +396,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx1, tx2}, } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx1, tx2}, + } messageIndexBytes := common.BigToHash(delayedMsgEvent.MessageIndex) receipt1 := &types.Receipt{ Logs: []*types.Log{ @@ -415,9 +437,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { _, err = parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.ErrorContains(t, err, "too short") }) @@ -447,9 +469,7 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { l2MessageFromOriginCallABI := iInboxABI.Methods["sendL2MessageFromOrigin"] originTxData, err := l2MessageFromOriginCallABI.Inputs.Pack(msgData) require.NoError(t, err) - fullTxData := make([]byte, 0) - fullTxData = append(fullTxData, l2MessageFromOriginCallABI.ID...) - fullTxData = append(fullTxData, originTxData...) + fullTxData := append(l2MessageFromOriginCallABI.ID, originTxData...) //nolint:gocritic txData1 := &types.DynamicFeeTx{ To: &delayedMsgPostingAddr, @@ -474,6 +494,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { blockBody := &types.Body{ Transactions: []*types.Transaction{tx1, tx2}, } + txsFetcher := &mockTxsFetcher{ + txs: []*types.Transaction{tx1, tx2}, + } messageIndexBytes := common.BigToHash(delayedMsgEvent.MessageIndex) receipt1 := &types.Receipt{ Logs: []*types.Log{ @@ -512,9 +535,9 @@ func Test_parseDelayedMessagesFromBlock(t *testing.T) { delayedMessages, err := parseDelayedMessagesFromBlock( ctx, melState, - block.Number(), - block.Transactions(), + block.Header(), receiptFetcher, + txsFetcher, ) require.NoError(t, err) require.Equal(t, 1, len(delayedMessages)) @@ -535,21 +558,12 @@ func Test_parseMessageScaffoldsFromLogs(t *testing.T) { require.Empty(t, delayedMsgs) require.Empty(t, events) }) - t.Run("log unpacking fails", func(t *testing.T) { - log := &types.Log{ - Address: common.BytesToAddress([]byte("deadbeef")), - Data: []byte("foobar"), - Topics: []common.Hash{}, - } - _, _, err := delayedMessageScaffoldsFromLogs(nil, []*types.Log{log}) - require.ErrorContains(t, err, "no event signature") - }) } func Test_sortableMessageList(t *testing.T) { hash1 := common.BigToHash(big.NewInt(1)) hash2 := common.BigToHash(big.NewInt(2)) - messages := []*arbnode.DelayedInboxMessage{ + messages := []*mel.DelayedInboxMessage{ { Message: &arbostypes.L1IncomingMessage{ Header: &arbostypes.L1IncomingMessageHeader{ @@ -593,18 +607,3 @@ func setupParseDelayedMessagesTest(t *testing.T) (*bridgegen.IBridgeMessageDeliv require.NoError(t, err) return event, packedLog } - -type mockReceiptFetcher struct { - receipts []*types.Receipt - err error -} - -func (m *mockReceiptFetcher) ReceiptForTransactionIndex( - _ context.Context, - idx uint, -) (*types.Receipt, error) { - if m.err != nil { - return nil, m.err - } - return m.receipts[idx], nil -} diff --git a/arbnode/mel/extraction/message_extraction_function.go b/arbnode/mel/extraction/message_extraction_function.go new file mode 100644 index 0000000000..51ac8ab6ac --- /dev/null +++ b/arbnode/mel/extraction/message_extraction_function.go @@ -0,0 +1,228 @@ +package melextraction + +import ( + "bytes" + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/daprovider" +) + +// Defines a method that can read a delayed message from an external database. +type DelayedMessageDatabase interface { + ReadDelayedMessage( + ctx context.Context, + state *mel.State, + index uint64, + ) (*mel.DelayedInboxMessage, error) +} + +// Defines a method that can fetch the receipt for a specific +// transaction index in a parent chain block. +type ReceiptFetcher interface { + ReceiptForTransactionIndex( + ctx context.Context, + txIndex uint, + ) (*types.Receipt, error) +} + +// Defines a method that can fetch transactions for +// a parent chain block by its header hash. +type TransactionsFetcher interface { + TransactionsByHeader( + ctx context.Context, + parentChainHeaderHash common.Hash, + ) (types.Transactions, error) +} + +// ExtractMessages is a pure function that can read a parent chain block and +// and input MEL state to run a specific algorithm that extracts Arbitrum messages and +// delayed messages observed from transactions in the block. This function can be proven +// through a replay binary, and should also compile to WAVM in addition to running in native mode. +func ExtractMessages( + ctx context.Context, + inputState *mel.State, + parentChainHeader *types.Header, + dataProviders []daprovider.Reader, + delayedMsgDatabase DelayedMessageDatabase, + receiptFetcher ReceiptFetcher, + txsFetcher TransactionsFetcher, +) (*mel.State, []*arbostypes.MessageWithMetadata, []*mel.DelayedInboxMessage, error) { + return extractMessagesImpl( + ctx, + inputState, + parentChainHeader, + dataProviders, + delayedMsgDatabase, + txsFetcher, + receiptFetcher, + &logUnpacker{}, + parseBatchesFromBlock, + parseDelayedMessagesFromBlock, + serializeBatch, + messagesFromBatchSegments, + arbstate.ParseSequencerMessage, + arbostypes.ParseBatchPostingReportMessageFields, + ) +} + +// Defines an internal implementation of the ExtractMessages function where many internal details +// can be mocked out for testing purposes, while the public function is clear about what dependencies it +// needs from callers. +func extractMessagesImpl( + ctx context.Context, + inputState *mel.State, + parentChainHeader *types.Header, + dataProviders []daprovider.Reader, + delayedMsgDatabase DelayedMessageDatabase, + txsFetcher TransactionsFetcher, + receiptFetcher ReceiptFetcher, + eventUnpacker eventUnpacker, + lookupBatches batchLookupFunc, + lookupDelayedMsgs delayedMsgLookupFunc, + serialize batchSerializingFunc, + extractBatchMessages batchMsgExtractionFunc, + parseSequencerMessage sequencerMessageParserFunc, + parseBatchPostingReport batchPostingReportParserFunc, +) (*mel.State, []*arbostypes.MessageWithMetadata, []*mel.DelayedInboxMessage, error) { + + state := inputState.Clone() + // Clones the state to avoid mutating the input pointer in case of errors. + // Check parent chain block hash linkage. + if state.ParentChainBlockHash != parentChainHeader.ParentHash { + return nil, nil, nil, fmt.Errorf( + "parent chain block hash in MEL state does not match incoming block's parent hash: expected %s, got %s", + state.ParentChainPreviousBlockHash.Hex(), + parentChainHeader.ParentHash.Hex(), + ) + } + // Updates the fields in the state to corresponding to the + // incoming parent chain block. + state.ParentChainBlockHash = parentChainHeader.Hash() + state.ParentChainBlockNumber = parentChainHeader.Number.Uint64() + state.ParentChainPreviousBlockHash = parentChainHeader.ParentHash + // Now, check for any logs emitted by the sequencer inbox by txs + // included in the parent chain block. + batches, batchTxs, batchTxIndices, err := lookupBatches( + ctx, + state, + parentChainHeader, + txsFetcher, + receiptFetcher, + eventUnpacker, + ) + if err != nil { + return nil, nil, nil, err + } + delayedMessages, err := lookupDelayedMsgs( + ctx, + state, + parentChainHeader, + receiptFetcher, + txsFetcher, + ) + if err != nil { + return nil, nil, nil, err + } + // Update the delayed message accumulator in the MEL state. + batchPostingReports := make([]*mel.DelayedInboxMessage, 0) + for _, delayed := range delayedMessages { + // If this message is a batch posting report, we save it for later + // use in this function once we extract messages from batches and + // need to fill in their batch posting report. + if delayed.Message.Header.Kind == arbostypes.L1MessageType_BatchPostingReport || delayed.Message.Header.Kind == arbostypes.L1MessageType_Initialize { // Let's consider the init message as a batch posting report, since it is seen as a batch as well, we can later ignore filling its batchGasCost anyway + batchPostingReports = append(batchPostingReports, delayed) + } + if err = state.AccumulateDelayedMessage(delayed); err != nil { + return nil, nil, nil, err + } + state.DelayedMessagedSeen += 1 + } + + // Batch posting reports are included in the same transaction as a batch, so there should + // always be the same number of reports as there are batches. + if len(batchPostingReports) != len(batches) { + return nil, nil, nil, fmt.Errorf( + "batch posting reports %d do not match the number of batches %d", + len(batchPostingReports), + len(batches), + ) + } + + var messages []*arbostypes.MessageWithMetadata + for i, batch := range batches { + batchTx := batchTxs[i] + txIndex := batchTxIndices[i] + serialized, err := serialize( + ctx, + batch, + batchTx, + txIndex, + receiptFetcher, + ) + if err != nil { + return nil, nil, nil, err + } + + batchPostReport := batchPostingReports[i] + if batchPostReport.Message.Header.Kind != arbostypes.L1MessageType_Initialize { + _, _, batchHash, _, _, _, err := parseBatchPostingReport(bytes.NewReader(batchPostReport.Message.L2msg)) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to parse batch posting report: %w", err) + } + gotHash := crypto.Keccak256Hash(serialized) + if gotHash != batchHash { + return nil, nil, nil, fmt.Errorf( + "batch data hash incorrect %v (wanted %v for batch %v)", + gotHash, + batchHash, + batch.SequenceNumber, + ) + } + gas := arbostypes.ComputeBatchGasCost(serialized) + + // Fill in the batch gas cost into the batch posting report. + batchPostReport.Message.BatchGasCost = &gas + } else if !(inputState.DelayedMessagedSeen == 0 && i == 0 && delayedMessages[i] == batchPostReport) { + return nil, nil, nil, errors.New("encountered initialize message that is not the first delayed message and the first batch ") + } + + rawSequencerMsg, err := parseSequencerMessage( + ctx, + batch.SequenceNumber, + batch.BlockHash, + serialized, + dataProviders, + daprovider.KeysetValidate, + ) + if err != nil { + return nil, nil, nil, err + } + messagesInBatch, err := extractBatchMessages( + ctx, + state, + rawSequencerMsg, + delayedMsgDatabase, + ) + if err != nil { + return nil, nil, nil, err + } + for _, msg := range messagesInBatch { + messages = append(messages, msg) + state.MsgCount += 1 + if err = state.AccumulateMessage(msg); err != nil { + return nil, nil, nil, fmt.Errorf("failed to accumulate message: %w", err) + } + } + state.BatchCount += 1 + } + return state, messages, delayedMessages, nil +} diff --git a/arbnode/mel/extraction/message_extraction_function_test.go b/arbnode/mel/extraction/message_extraction_function_test.go new file mode 100644 index 0000000000..97b82b4210 --- /dev/null +++ b/arbnode/mel/extraction/message_extraction_function_test.go @@ -0,0 +1,372 @@ +package melextraction + +import ( + "context" + "errors" + "io" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/daprovider" +) + +func TestExtractMessages(t *testing.T) { + ctx := context.Background() + prevParentBlockHash := common.HexToHash("0x1234") + + tests := []struct { + name string + melStateParentHash common.Hash + useExtractMessages bool // If true, use ExtractMessages instead of extractMessagesImpl + lookupBatches func(context.Context, *mel.State, *types.Header, TransactionsFetcher, ReceiptFetcher, eventUnpacker) ([]*mel.SequencerInboxBatch, []*types.Transaction, []uint, error) + lookupDelayedMsgs func(context.Context, *mel.State, *types.Header, ReceiptFetcher, TransactionsFetcher) ([]*mel.DelayedInboxMessage, error) + serializer func(context.Context, *mel.SequencerInboxBatch, *types.Transaction, uint, ReceiptFetcher) ([]byte, error) + parseReport func(io.Reader) (*big.Int, common.Address, common.Hash, uint64, *big.Int, uint64, error) + parseSequencerMsg func(context.Context, uint64, common.Hash, []byte, []daprovider.Reader, daprovider.KeysetValidationMode) (*arbstate.SequencerMessage, error) + extractBatchMessages func(context.Context, *mel.State, *arbstate.SequencerMessage, DelayedMessageDatabase) ([]*arbostypes.MessageWithMetadata, error) + expectedError string + expectedMsgCount uint64 + expectedDelayedSeen uint64 + expectedMessages int + expectedDelayedMsgs int + }{ + { + name: "parent chain block hash mismatch", + melStateParentHash: common.HexToHash("0x5678"), // Different from block's parent hash + useExtractMessages: true, + expectedError: "parent chain block hash in MEL state does not match", + }, + { + name: "looking up batches fails", + melStateParentHash: prevParentBlockHash, + lookupBatches: failingLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + expectedError: "failed to lookup batches", + }, + { + name: "looking up delayed messages fails", + melStateParentHash: prevParentBlockHash, + lookupBatches: emptyLookupBatches, + lookupDelayedMsgs: failingLookupDelayedMsgs, + expectedError: "failed to lookup delayed messages", + }, + { + name: "mismatched number of batch posting reports vs batches", + melStateParentHash: prevParentBlockHash, + lookupBatches: emptyLookupBatches, // 0 batches + lookupDelayedMsgs: successfulLookupDelayedMsgs, // 1 batch posting report + expectedError: "batch posting reports 1 do not match the number of batches 0", + }, + { + name: "batch serialization fails", + melStateParentHash: prevParentBlockHash, + lookupBatches: successfulLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + serializer: failingSerializer, + expectedError: "serialization error", + }, + { + name: "parsing batch posting report fails", + melStateParentHash: prevParentBlockHash, + lookupBatches: successfulLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + serializer: emptySerializer, + parseReport: failingParseReport, + expectedError: "batch posting report parsing error", + }, + { + name: "mismatched batch posting report batch hash and actual batch hash", + melStateParentHash: prevParentBlockHash, + lookupBatches: successfulLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + serializer: emptySerializer, // Returns nil, hash will be different from parseReport + parseReport: emptyParseReport, // Returns empty hash + expectedError: "batch data hash incorrect", + }, + { + name: "parse sequencer message fails", + melStateParentHash: prevParentBlockHash, + lookupBatches: successfulLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + serializer: successfulSerializer, + parseReport: successfulParseReport, + parseSequencerMsg: failingParseSequencerMsg, + expectedError: "failed to parse sequencer message", + }, + { + name: "extracting batch messages fails", + melStateParentHash: prevParentBlockHash, + lookupBatches: successfulLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + serializer: successfulSerializer, + parseReport: successfulParseReport, + parseSequencerMsg: successfulParseSequencerMsg, + extractBatchMessages: failingExtractBatchMessages, + expectedError: "failed to extract batch messages", + }, + { + name: "OK", + melStateParentHash: prevParentBlockHash, + lookupBatches: successfulLookupBatches, + lookupDelayedMsgs: successfulLookupDelayedMsgs, + serializer: successfulSerializer, + parseReport: successfulParseReport, + parseSequencerMsg: successfulParseSequencerMsg, + extractBatchMessages: successfulExtractBatchMessages, + expectedMsgCount: 2, + expectedDelayedSeen: 1, + expectedMessages: 2, + expectedDelayedMsgs: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + header := createBlockHeader(prevParentBlockHash) + melState := createMelState(tt.melStateParentHash) + txsFetcher := &mockTxsFetcher{} + + var postState *mel.State + var messages []*arbostypes.MessageWithMetadata + var delayedMessages []*mel.DelayedInboxMessage + var err error + + if tt.useExtractMessages { + // Test the public ExtractMessages function + postState, messages, delayedMessages, err = ExtractMessages( + ctx, + melState, + header, + nil, + nil, + nil, + txsFetcher, + ) + } else { + // Test the internal extractMessagesImpl function + postState, messages, delayedMessages, err = extractMessagesImpl( + ctx, + melState, + header, + nil, + nil, + txsFetcher, + nil, + nil, + tt.lookupBatches, + tt.lookupDelayedMsgs, + tt.serializer, + tt.extractBatchMessages, + tt.parseSequencerMsg, + tt.parseReport, + ) + } + + if tt.expectedError != "" { + require.ErrorContains(t, err, tt.expectedError) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expectedMsgCount, postState.MsgCount) + require.Equal(t, tt.expectedDelayedSeen, postState.DelayedMessagedSeen) + require.Len(t, messages, tt.expectedMessages) + require.Len(t, delayedMessages, tt.expectedDelayedMsgs) + }) + } +} + +func createMelState(parentHash common.Hash) *mel.State { + melState := &mel.State{ + ParentChainBlockHash: parentHash, + } + return melState +} + +func createBlockHeader(parentHash common.Hash) *types.Header { + return &types.Header{ + ParentHash: parentHash, + Number: big.NewInt(0), + } +} + +// Mock functions +func successfulLookupBatches( + ctx context.Context, + melState *mel.State, + parentChainBlock *types.Header, + txsFetcher TransactionsFetcher, + receiptFetcher ReceiptFetcher, + eventUnpacker eventUnpacker, +) ([]*mel.SequencerInboxBatch, []*types.Transaction, []uint, error) { + batches := []*mel.SequencerInboxBatch{{}} + txs := []*types.Transaction{{}} + txIndices := []uint{0} + return batches, txs, txIndices, nil +} + +func emptyLookupBatches( + ctx context.Context, + melState *mel.State, + parentChainBlock *types.Header, + txsFetcher TransactionsFetcher, + receiptFetcher ReceiptFetcher, + eventUnpacker eventUnpacker, +) ([]*mel.SequencerInboxBatch, []*types.Transaction, []uint, error) { + return nil, nil, nil, nil +} + +func failingLookupBatches( + ctx context.Context, + melState *mel.State, + parentChainBlock *types.Header, + txsFetcher TransactionsFetcher, + receiptFetcher ReceiptFetcher, + eventUnpacker eventUnpacker, +) ([]*mel.SequencerInboxBatch, []*types.Transaction, []uint, error) { + return nil, nil, nil, errors.New("failed to lookup batches") +} + +func successfulLookupDelayedMsgs( + ctx context.Context, + melState *mel.State, + parentChainBlock *types.Header, + receiptFetcher ReceiptFetcher, + txsFetcher TransactionsFetcher, +) ([]*mel.DelayedInboxMessage, error) { + hash := common.MaxHash + delayedMsgs := []*mel.DelayedInboxMessage{ + { + Message: &arbostypes.L1IncomingMessage{ + L2msg: []byte("foobar"), + Header: &arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_BatchPostingReport, + RequestId: &hash, + L1BaseFee: common.Big0, + }, + }, + }, + } + return delayedMsgs, nil +} + +func failingLookupDelayedMsgs( + ctx context.Context, + melState *mel.State, + parentChainBlock *types.Header, + receiptFetcher ReceiptFetcher, + txsFetcher TransactionsFetcher, +) ([]*mel.DelayedInboxMessage, error) { + return nil, errors.New("failed to lookup delayed messages") +} + +func successfulSerializer(ctx context.Context, + batch *mel.SequencerInboxBatch, + tx *types.Transaction, + txIndex uint, + receiptFetcher ReceiptFetcher, +) ([]byte, error) { + return []byte("foobar"), nil +} + +func emptySerializer(ctx context.Context, + batch *mel.SequencerInboxBatch, + tx *types.Transaction, + txIndex uint, + receiptFetcher ReceiptFetcher, +) ([]byte, error) { + return nil, nil +} + +func failingSerializer(ctx context.Context, + batch *mel.SequencerInboxBatch, + tx *types.Transaction, + txIndex uint, + receiptFetcher ReceiptFetcher, +) ([]byte, error) { + return nil, errors.New("serialization error") +} + +func successfulParseReport( + rd io.Reader, +) (*big.Int, common.Address, common.Hash, uint64, *big.Int, uint64, error) { + return nil, common.Address{}, crypto.Keccak256Hash([]byte("foobar")), 0, nil, 0, nil +} + +func emptyParseReport( + rd io.Reader, +) (*big.Int, common.Address, common.Hash, uint64, *big.Int, uint64, error) { + return nil, common.Address{}, common.Hash{}, 0, nil, 0, nil +} + +func failingParseReport( + rd io.Reader, +) (*big.Int, common.Address, common.Hash, uint64, *big.Int, uint64, error) { + return nil, common.Address{}, common.Hash{}, 0, nil, 0, errors.New("batch posting report parsing error") +} + +func successfulParseSequencerMsg( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + data []byte, + dapReaders []daprovider.Reader, + keysetValidationMode daprovider.KeysetValidationMode, +) (*arbstate.SequencerMessage, error) { + return nil, nil +} + +func failingParseSequencerMsg( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + data []byte, + dapReaders []daprovider.Reader, + keysetValidationMode daprovider.KeysetValidationMode, +) (*arbstate.SequencerMessage, error) { + return nil, errors.New("failed to parse sequencer message") +} + +func successfulExtractBatchMessages( + ctx context.Context, + melState *mel.State, + seqMsg *arbstate.SequencerMessage, + delayedMsgDB DelayedMessageDatabase, +) ([]*arbostypes.MessageWithMetadata, error) { + return []*arbostypes.MessageWithMetadata{ + { + Message: &arbostypes.L1IncomingMessage{ + L2msg: []byte("foobar"), + Header: &arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_L2Message, + }, + }, + }, + { + Message: &arbostypes.L1IncomingMessage{ + L2msg: []byte("nyancat"), + Header: &arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_L2Message, + }, + }, + }, + }, nil +} + +func failingExtractBatchMessages( + ctx context.Context, + melState *mel.State, + seqMsg *arbstate.SequencerMessage, + delayedMsgDB DelayedMessageDatabase, +) ([]*arbostypes.MessageWithMetadata, error) { + return nil, errors.New("failed to extract batch messages") +} diff --git a/arbnode/mel/extraction/messages_in_batch.go b/arbnode/mel/extraction/messages_in_batch.go new file mode 100644 index 0000000000..bf866ea174 --- /dev/null +++ b/arbnode/mel/extraction/messages_in_batch.go @@ -0,0 +1,216 @@ +package melextraction + +import ( + "bytes" + "context" + "errors" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbos/l1pricing" + "github.com/offchainlabs/nitro/arbstate" +) + +// Extracts a list of arbos messages from the sequencer batch, which looks at the +// segments contained in a batch and extracts the correct ordering of messages +// from the segments, possibly reading delayed messages when a delayed message +// virtual segment in encountered, or at the end if we have not read enough +// delayed messages in our MEL state when compared to the sequencer message's. +func messagesFromBatchSegments( + ctx context.Context, + melState *mel.State, + seqMsg *arbstate.SequencerMessage, + delayedMsgDB DelayedMessageDatabase, +) ([]*arbostypes.MessageWithMetadata, error) { + messages := make([]*arbostypes.MessageWithMetadata, 0) + timestamp := uint64(0) + blockNumber := uint64(0) + for idx, segment := range seqMsg.Segments { + msg, newBlockNumber, newTimestamp, err := messageFromSegment( + ctx, + melState, + delayedMsgDB, + seqMsg, + idx, + segment, + timestamp, + blockNumber, + ) + if err != nil { + if errors.Is(err, ErrParsingAdvancingSegment) { + continue // We ignore being able to parse an advance segment. + } + return nil, fmt.Errorf( + "error parsing segment %d: %w", idx, err, + ) + } + timestamp = newTimestamp + blockNumber = newBlockNumber + if msg == nil { + continue + } + messages = append(messages, msg) + } + + // If the mel state delayed messages read even after we completed reading + // all segments is less than the sequencer message's + // after delayed messages, we need to read more delayed messages here. + for melState.DelayedMessagesRead < seqMsg.AfterDelayedMessages { + msg, err := extractDelayedMessageFromSegment( + ctx, + melState, + seqMsg, + delayedMsgDB, + ) + if err != nil { + return nil, fmt.Errorf("error extracting delayed message: %w", err) + } + messages = append(messages, msg) + } + + return messages, nil +} + +var ErrParsingAdvancingSegment = fmt.Errorf("error parsing advancing segment") + +func messageFromSegment( + ctx context.Context, + melState *mel.State, + delayedMsgDB DelayedMessageDatabase, + seqMsg *arbstate.SequencerMessage, + segmentIdx int, + segment []byte, + timestamp uint64, + blockNumber uint64, +) (*arbostypes.MessageWithMetadata, uint64, uint64, error) { + if len(segment) == 0 { + log.Warn("Empty segment in sequencer message", "seqMsg", seqMsg) + return nil, blockNumber, timestamp, nil + } + kind := segment[0] + if kind == arbstate.BatchSegmentKindAdvanceTimestamp || kind == arbstate.BatchSegmentKindAdvanceL1BlockNumber { + rd := bytes.NewReader(segment[1:]) + advancing, err := rlp.NewStream(rd, 16).Uint64() + if err != nil { + log.Warn("Error parsing sequencer advancing segment", "err", err) + return nil, blockNumber, timestamp, ErrParsingAdvancingSegment + } + if kind == arbstate.BatchSegmentKindAdvanceTimestamp { + timestamp += advancing + } else if kind == arbstate.BatchSegmentKindAdvanceL1BlockNumber { + blockNumber += advancing + } + return nil, blockNumber, timestamp, nil + } else if kind == arbstate.BatchSegmentKindL2Message || kind == arbstate.BatchSegmentKindL2MessageBrotli { + segment = segment[1:] + msg := produceL2Message( + kind, + seqMsg, + segment, + blockNumber, + timestamp, + melState.DelayedMessagesRead, + ) + return msg, blockNumber, timestamp, nil + } else if kind == arbstate.BatchSegmentKindDelayedMessages { + msg, err := extractDelayedMessageFromSegment( + ctx, + melState, + seqMsg, + delayedMsgDB, + ) + if err != nil { + return nil, blockNumber, timestamp, err + } + return msg, blockNumber, timestamp, nil + } else { + log.Error("Bad sequencer message segment kind", "segmentNum", segmentIdx, "kind", kind) + return &arbostypes.MessageWithMetadata{ + Message: arbostypes.InvalidL1Message, + DelayedMessagesRead: melState.DelayedMessagesRead, + }, blockNumber, timestamp, nil + } +} + +func produceL2Message( + kind byte, + seqMsg *arbstate.SequencerMessage, + segment []byte, + blockNumber uint64, + timestamp uint64, + delayedMessagesRead uint64, +) *arbostypes.MessageWithMetadata { + // We constrain the bounds of the timestamp and block number. + if timestamp < seqMsg.MinTimestamp { + timestamp = seqMsg.MinTimestamp + } else if timestamp > seqMsg.MaxTimestamp { + timestamp = seqMsg.MaxTimestamp + } + if blockNumber < seqMsg.MinL1Block { + blockNumber = seqMsg.MinL1Block + } else if blockNumber > seqMsg.MaxL1Block { + blockNumber = seqMsg.MaxL1Block + } + seg := segment + if kind == arbstate.BatchSegmentKindL2MessageBrotli { + decompressed, err := arbcompress.Decompress(segment, arbostypes.MaxL2MessageSize) + if err != nil { + log.Info("dropping compressed message", "err", err) + return &arbostypes.MessageWithMetadata{ + Message: arbostypes.InvalidL1Message, + DelayedMessagesRead: delayedMessagesRead, + } + } + seg = decompressed + } + return &arbostypes.MessageWithMetadata{ + Message: &arbostypes.L1IncomingMessage{ + Header: &arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_L2Message, + Poster: l1pricing.BatchPosterAddress, + BlockNumber: blockNumber, + Timestamp: timestamp, + RequestId: nil, + L1BaseFee: big.NewInt(0), + }, + L2msg: seg, + }, + DelayedMessagesRead: delayedMessagesRead, + } +} + +func extractDelayedMessageFromSegment( + ctx context.Context, + melState *mel.State, + seqMsg *arbstate.SequencerMessage, + delayedMsgDB DelayedMessageDatabase, +) (*arbostypes.MessageWithMetadata, error) { + if melState.DelayedMessagesRead >= seqMsg.AfterDelayedMessages { + return &arbostypes.MessageWithMetadata{ + Message: arbostypes.InvalidL1Message, + DelayedMessagesRead: seqMsg.AfterDelayedMessages, + }, nil + } + delayed, err := delayedMsgDB.ReadDelayedMessage(ctx, melState, melState.DelayedMessagesRead) + if err != nil { + return nil, err + } + if delayed == nil { + log.Error("No more delayed messages in queue", "delayedMessagesRead", melState.DelayedMessagesRead) + return nil, fmt.Errorf("no more delayed messages in db") + } + + // Increment the delayed messages read count in the mel state. + melState.DelayedMessagesRead += 1 + + return &arbostypes.MessageWithMetadata{ + Message: delayed.Message, + DelayedMessagesRead: melState.DelayedMessagesRead, + }, nil +} diff --git a/arbnode/mel/extraction/messages_in_batch_test.go b/arbnode/mel/extraction/messages_in_batch_test.go new file mode 100644 index 0000000000..70b4cc340e --- /dev/null +++ b/arbnode/mel/extraction/messages_in_batch_test.go @@ -0,0 +1,374 @@ +package melextraction + +import ( + "context" + "encoding/binary" + "errors" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbstate" +) + +func Test_messagesFromBatchSegments_expectedFieldBounds_simple(t *testing.T) { + compressedData, _ := arbcompress.CompressWell([]byte("foobar")) + adv1, _ := rlp.EncodeToBytes(uint64(7)) + adv2, _ := rlp.EncodeToBytes(uint64(3)) + segments := [][]byte{ + append([]byte{arbstate.BatchSegmentKindAdvanceTimestamp}, adv1...), + append([]byte{arbstate.BatchSegmentKindAdvanceTimestamp}, adv2...), + append([]byte{arbstate.BatchSegmentKindL2MessageBrotli}, compressedData...), + } + ctx := context.Background() + melState := &mel.State{ + DelayedMessagesRead: 0, + } + seqMsg := &arbstate.SequencerMessage{ + Segments: segments, + MinTimestamp: 10, + MaxTimestamp: 20, + } + mockDB := &mockDelayedMessageDB{} + msgs, err := messagesFromBatchSegments( + ctx, + melState, + seqMsg, + mockDB, + ) + require.NoError(t, err) + require.Len(t, msgs, 1) + require.Equal(t, msgs[0].Message.L2msg, []byte("foobar")) + require.Equal(t, msgs[0].Message.Header.Timestamp, uint64(10)) +} + +func Test_messagesFromBatchSegments_expectedFieldBounds_complex(t *testing.T) { + compressedData, _ := arbcompress.CompressWell([]byte("foobar")) + adv1, _ := rlp.EncodeToBytes(uint64(7)) + adv2, _ := rlp.EncodeToBytes(uint64(3)) + segments := [][]byte{ + append([]byte{arbstate.BatchSegmentKindAdvanceTimestamp}, adv1...), + append([]byte{arbstate.BatchSegmentKindL2MessageBrotli}, compressedData...), + append([]byte{arbstate.BatchSegmentKindAdvanceTimestamp}, adv2...), + append([]byte{arbstate.BatchSegmentKindL2MessageBrotli}, compressedData...), + } + ctx := context.Background() + melState := &mel.State{ + DelayedMessagesRead: 0, + } + seqMsg := &arbstate.SequencerMessage{ + Segments: segments, + MinTimestamp: 10, + MaxTimestamp: 20, + } + mockDB := &mockDelayedMessageDB{} + msgs, err := messagesFromBatchSegments( + ctx, + melState, + seqMsg, + mockDB, + ) + require.NoError(t, err) + require.Len(t, msgs, 2) + require.Equal(t, msgs[0].Message.L2msg, []byte("foobar")) + require.Equal(t, msgs[0].Message.Header.Timestamp, uint64(10)) + require.Equal(t, msgs[1].Message.L2msg, []byte("foobar")) + require.Equal(t, msgs[1].Message.Header.Timestamp, uint64(10)) +} + +func Test_messagesFromBatchSegments_delayedMessages(t *testing.T) { + ctx := context.Background() + melState := &mel.State{ + DelayedMessagesRead: 0, + } + seqMsg := &arbstate.SequencerMessage{ + AfterDelayedMessages: 2, + Segments: [][]byte{}, // No segments, but the + // sequencer message says that we must read 2 delayed messages. + } + mockDB := &mockDelayedMessageDB{ + DelayedMessages: map[uint64]*mel.DelayedInboxMessage{ + 0: { + Message: &arbostypes.L1IncomingMessage{ + L2msg: []byte("foobar"), + }, + }, + 1: { + Message: &arbostypes.L1IncomingMessage{ + L2msg: []byte("barfoo"), + }, + }, + }, + } + msgs, err := messagesFromBatchSegments( + ctx, + melState, + seqMsg, + mockDB, + ) + require.NoError(t, err) + require.Len(t, msgs, 2) + require.Equal(t, msgs[0].Message.L2msg, []byte("foobar")) + require.Equal(t, msgs[1].Message.L2msg, []byte("barfoo")) +} + +func Test_messagesFromBatchSegments(t *testing.T) { + tests := []struct { + name string + setupSegments func() [][]byte + setupMelState func() *mel.State + setupSeqMsg func(segments [][]byte) *arbstate.SequencerMessage + setupMockDB func() *mockDelayedMessageDB + wantErr bool + wantErrContains string + validateResult func(t *testing.T, msg *arbostypes.MessageWithMetadata) + }{ + { + name: "segment advances timestamp", + setupSegments: func() [][]byte { + compressedData, _ := arbcompress.CompressWell([]byte("foobar")) + encodedTimestampAdvance, _ := rlp.EncodeToBytes(uint64(50)) + return [][]byte{ + append([]byte{arbstate.BatchSegmentKindAdvanceTimestamp}, encodedTimestampAdvance...), + append([]byte{arbstate.BatchSegmentKindL2MessageBrotli}, compressedData...), + } + }, + setupMelState: func() *mel.State { + return &mel.State{} + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + Segments: segments, + MinTimestamp: 0, + MaxTimestamp: 1_000_000, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return nil + }, + wantErr: false, + validateResult: func(t *testing.T, msg *arbostypes.MessageWithMetadata) { + require.Equal(t, []byte("foobar"), msg.Message.L2msg) + require.Equal(t, uint64(50), msg.Message.Header.Timestamp) + }, + }, + { + name: "segment advances blocknum", + setupSegments: func() [][]byte { + compressedData, _ := arbcompress.CompressWell([]byte("foobar")) + encodedBlockNum, _ := rlp.EncodeToBytes(uint64(20)) + return [][]byte{ + append([]byte{arbstate.BatchSegmentKindAdvanceL1BlockNumber}, encodedBlockNum...), + append([]byte{arbstate.BatchSegmentKindL2MessageBrotli}, compressedData...), + } + }, + setupMelState: func() *mel.State { + return &mel.State{} + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + Segments: segments, + MinTimestamp: 0, + MaxTimestamp: 1_000_000, + MaxL1Block: 1_000_000, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return nil + }, + wantErr: false, + validateResult: func(t *testing.T, msg *arbostypes.MessageWithMetadata) { + require.Equal(t, []byte("foobar"), msg.Message.L2msg) + require.Equal(t, uint64(20), msg.Message.Header.BlockNumber) + }, + }, + { + name: "brotli compressed message", + setupSegments: func() [][]byte { + compressedData, _ := arbcompress.CompressWell([]byte("foobar")) + encodedTimestampAdvance := make([]byte, 8) + binary.BigEndian.PutUint64(encodedTimestampAdvance, 50) + return [][]byte{ + append([]byte{arbstate.BatchSegmentKindAdvanceTimestamp}, encodedTimestampAdvance...), + append([]byte{arbstate.BatchSegmentKindL2MessageBrotli}, compressedData...), + } + }, + setupMelState: func() *mel.State { + return &mel.State{} + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + Segments: segments, + MinTimestamp: 0, + MaxTimestamp: 1_000_000, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return nil + }, + wantErr: false, + validateResult: func(t *testing.T, msg *arbostypes.MessageWithMetadata) { + require.Equal(t, []byte("foobar"), msg.Message.L2msg) + }, + }, + { + name: "delayed message segment greater than what has been read", + setupSegments: func() [][]byte { + return [][]byte{ + {arbstate.BatchSegmentKindDelayedMessages}, + } + }, + setupMelState: func() *mel.State { + return &mel.State{ + DelayedMessagesRead: 1, + } + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + AfterDelayedMessages: 1, + Segments: segments, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return nil + }, + wantErr: false, + validateResult: func(t *testing.T, msg *arbostypes.MessageWithMetadata) { + require.Equal(t, arbostypes.InvalidL1Message, msg.Message) + }, + }, + { + name: "gets error fetching delayed message from database", + setupSegments: func() [][]byte { + return [][]byte{{}} + }, + setupMelState: func() *mel.State { + return &mel.State{ + DelayedMessagesRead: 0, + } + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + AfterDelayedMessages: 1, + Segments: segments, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return &mockDelayedMessageDB{ + err: errors.New("oops"), + } + }, + wantErr: true, + wantErrContains: "oops", + }, + { + name: "gets nil delayed message from database", + setupSegments: func() [][]byte { + return [][]byte{{}} + }, + setupMelState: func() *mel.State { + return &mel.State{ + DelayedMessagesRead: 0, + } + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + AfterDelayedMessages: 1, + Segments: segments, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return &mockDelayedMessageDB{ + DelayedMessages: map[uint64]*mel.DelayedInboxMessage{}, + } + }, + wantErr: true, + wantErrContains: "no more delayed messages in db", + }, + { + name: "reading delayed message OK", + setupSegments: func() [][]byte { + return [][]byte{{}} + }, + setupMelState: func() *mel.State { + return &mel.State{ + DelayedMessagesRead: 0, + } + }, + setupSeqMsg: func(segments [][]byte) *arbstate.SequencerMessage { + return &arbstate.SequencerMessage{ + AfterDelayedMessages: 1, + Segments: segments, + } + }, + setupMockDB: func() *mockDelayedMessageDB { + return &mockDelayedMessageDB{ + DelayedMessages: map[uint64]*mel.DelayedInboxMessage{ + 0: { + Message: &arbostypes.L1IncomingMessage{ + L2msg: []byte("foobar"), + }, + }, + }, + } + }, + wantErr: false, + validateResult: func(t *testing.T, msg *arbostypes.MessageWithMetadata) { + require.Equal(t, []byte("foobar"), msg.Message.L2msg) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + segments := tt.setupSegments() + melState := tt.setupMelState() + seqMsg := tt.setupSeqMsg(segments) + mockDB := tt.setupMockDB() + + msgs, err := messagesFromBatchSegments( + context.Background(), + melState, + seqMsg, + mockDB, + ) + if tt.wantErr { + require.Error(t, err) + if tt.wantErrContains != "" { + require.ErrorContains(t, err, tt.wantErrContains) + } + return + } + + require.NoError(t, err) + if tt.validateResult != nil { + require.Equal(t, 1, len(msgs), "Expected exactly one message for this test") + tt.validateResult(t, msgs[0]) + } + }) + } +} + +type mockDelayedMessageDB struct { + DelayedMessagesRead uint64 + DelayedMessages map[uint64]*mel.DelayedInboxMessage + err error +} + +func (m *mockDelayedMessageDB) ReadDelayedMessage( + _ context.Context, + _ *mel.State, + delayedMsgsRead uint64, +) (*mel.DelayedInboxMessage, error) { + if m.err != nil { + return nil, m.err + } + if delayedMsg, ok := m.DelayedMessages[delayedMsgsRead]; ok { + return delayedMsg, nil + } + return nil, nil +} diff --git a/arbnode/mel/extraction/types.go b/arbnode/mel/extraction/types.go new file mode 100644 index 0000000000..1b1de95931 --- /dev/null +++ b/arbnode/mel/extraction/types.go @@ -0,0 +1,82 @@ +package melextraction + +import ( + "context" + "io" + "math/big" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/daprovider" +) + +// Satisfies an eventUnpacker interface that can unpack logs +// to a specific event type using the provided ABI and event name. +type logUnpacker struct{} + +func (*logUnpacker) unpackLogTo( + event any, abi *abi.ABI, eventName string, log types.Log) error { + return unpackLogTo(event, abi, eventName, log) +} + +// Defines a function that can lookup batches for a given parent chain block. +// See: parseBatchesFromBlock. +type batchLookupFunc func( + ctx context.Context, + melState *mel.State, + parentChainHeader *types.Header, + txsFetcher TransactionsFetcher, + receiptFetcher ReceiptFetcher, + eventUnpacker eventUnpacker, +) ([]*mel.SequencerInboxBatch, []*types.Transaction, []uint, error) + +// Defines a function that can lookup delayed messages for a given parent chain block. +// See: parseDelayedMessagesFromBlock. +type delayedMsgLookupFunc func( + ctx context.Context, + melState *mel.State, + parentChainHeader *types.Header, + receiptFetcher ReceiptFetcher, + txsFetcher TransactionsFetcher, +) ([]*mel.DelayedInboxMessage, error) + +// Defines a function that can serialize a batch. +// See: serializeBatch. +type batchSerializingFunc func( + ctx context.Context, + batch *mel.SequencerInboxBatch, + tx *types.Transaction, + txIndex uint, + receiptFetcher ReceiptFetcher, +) ([]byte, error) + +// Defines a function that can parse a sequencer message from a batch. +// See: arbstate.ParseSequencerMessage. +type sequencerMessageParserFunc func( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + data []byte, + dapReaders []daprovider.Reader, + keysetValidationMode daprovider.KeysetValidationMode, +) (*arbstate.SequencerMessage, error) + +// Defines a function that can extract messages from a batch. +// See: extractMessagesFromBatch. +type batchMsgExtractionFunc func( + ctx context.Context, + melState *mel.State, + seqMsg *arbstate.SequencerMessage, + delayedMsgDB DelayedMessageDatabase, +) ([]*arbostypes.MessageWithMetadata, error) + +// Defines a function that can parse a batch posting report. +// See: arbostypes.ParseBatchPostingReportMessageFields. +type batchPostingReportParserFunc func( + rd io.Reader, +) (*big.Int, common.Address, common.Hash, uint64, *big.Int, uint64, error) diff --git a/arbnode/mel/messages.go b/arbnode/mel/messages.go new file mode 100644 index 0000000000..3bab5d214d --- /dev/null +++ b/arbnode/mel/messages.go @@ -0,0 +1,77 @@ +package mel + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/util/arbmath" +) + +type BatchDataLocation uint8 + +const ( + BatchDataTxInput BatchDataLocation = iota + BatchDataSeparateEvent + BatchDataNone + BatchDataBlobHashes +) + +type SequencerInboxBatch struct { + BlockHash common.Hash + ParentChainBlockNumber uint64 + SequenceNumber uint64 + BeforeInboxAcc common.Hash + AfterInboxAcc common.Hash + AfterDelayedAcc common.Hash + AfterDelayedCount uint64 + TimeBounds bridgegen.IBridgeTimeBounds + RawLog types.Log + DataLocation BatchDataLocation + BridgeAddress common.Address + Serialized []byte // nil if serialization isn't cached yet +} + +type DelayedInboxMessage struct { + BlockHash common.Hash + BeforeInboxAcc common.Hash + Message *arbostypes.L1IncomingMessage + ParentChainBlockNumber uint64 +} + +func (m *DelayedInboxMessage) AfterInboxAcc() common.Hash { + hash := crypto.Keccak256( + []byte{m.Message.Header.Kind}, + m.Message.Header.Poster.Bytes(), + arbmath.UintToBytes(m.Message.Header.BlockNumber), + arbmath.UintToBytes(m.Message.Header.Timestamp), + m.Message.Header.RequestId.Bytes(), + arbmath.U256Bytes(m.Message.Header.L1BaseFee), + crypto.Keccak256(m.Message.L2msg), + ) + return crypto.Keccak256Hash(m.BeforeInboxAcc[:], hash) +} + +// Hash will replace AfterInboxAcc +func (m *DelayedInboxMessage) Hash() common.Hash { + hash := crypto.Keccak256( + []byte{m.Message.Header.Kind}, + m.Message.Header.Poster.Bytes(), + arbmath.UintToBytes(m.Message.Header.BlockNumber), + arbmath.UintToBytes(m.Message.Header.Timestamp), + m.Message.Header.RequestId.Bytes(), + arbmath.U256Bytes(m.Message.Header.L1BaseFee), + crypto.Keccak256(m.Message.L2msg), + ) + return crypto.Keccak256Hash(hash) +} + +type BatchMetadata struct { + Accumulator common.Hash + MessageCount arbutil.MessageIndex + DelayedMessageCount uint64 + ParentChainBlock uint64 +} diff --git a/arbnode/mel/runner/database.go b/arbnode/mel/runner/database.go new file mode 100644 index 0000000000..6141cd8096 --- /dev/null +++ b/arbnode/mel/runner/database.go @@ -0,0 +1,159 @@ +package melrunner + +import ( + "context" + "encoding/binary" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/rlp" + + dbschema "github.com/offchainlabs/nitro/arbnode/db-schema" + "github.com/offchainlabs/nitro/arbnode/mel" +) + +// Database holds an ethdb.Database underneath and implements StateDatabase interface defined in 'mel' +type Database struct { + db ethdb.Database +} + +func NewDatabase(db ethdb.Database) *Database { + return &Database{db} +} + +func (d *Database) GetHeadMelState(ctx context.Context) (*mel.State, error) { + headMelStateBlockNum, err := d.GetHeadMelStateBlockNum() + if err != nil { + return nil, fmt.Errorf("error getting HeadMelStateBlockNum from database: %w", err) + } + return d.State(ctx, headMelStateBlockNum) +} + +// SaveState should exclusively be called for saving the recently generated "head" MEL state +func (d *Database) SaveState(ctx context.Context, state *mel.State) error { + dbBatch := d.db.NewBatch() + if err := d.setMelState(dbBatch, state.ParentChainBlockNumber, *state); err != nil { + return err + } + if err := d.setHeadMelStateBlockNum(dbBatch, state.ParentChainBlockNumber); err != nil { + return err + } + return dbBatch.Write() +} + +func (d *Database) setMelState(batch ethdb.KeyValueWriter, parentChainBlockNumber uint64, state mel.State) error { + key := dbKey(dbschema.MelStatePrefix, parentChainBlockNumber) + melStateBytes, err := rlp.EncodeToBytes(state) + if err != nil { + return err + } + if err := batch.Put(key, melStateBytes); err != nil { + return err + } + return nil +} + +func (d *Database) setHeadMelStateBlockNum(batch ethdb.KeyValueWriter, parentChainBlockNumber uint64) error { + parentChainBlockNumberBytes, err := rlp.EncodeToBytes(parentChainBlockNumber) + if err != nil { + return err + } + err = batch.Put(dbschema.HeadMelStateBlockNumKey, parentChainBlockNumberBytes) + if err != nil { + return err + } + return nil +} + +func (d *Database) GetHeadMelStateBlockNum() (uint64, error) { + parentChainBlockNumberBytes, err := d.db.Get(dbschema.HeadMelStateBlockNumKey) + if err != nil { + return 0, err + } + var parentChainBlockNumber uint64 + err = rlp.DecodeBytes(parentChainBlockNumberBytes, &parentChainBlockNumber) + if err != nil { + return 0, err + } + return parentChainBlockNumber, nil +} + +func (d *Database) State(ctx context.Context, parentChainBlockNumber uint64) (*mel.State, error) { + key := dbKey(dbschema.MelStatePrefix, parentChainBlockNumber) + data, err := d.db.Get(key) + if err != nil { + return nil, err + } + var state mel.State + err = rlp.DecodeBytes(data, &state) + if err != nil { + return nil, err + } + return &state, nil +} + +func (d *Database) SaveDelayedMessages(ctx context.Context, state *mel.State, delayedMessages []*mel.DelayedInboxMessage) error { + dbBatch := d.db.NewBatch() + if state.DelayedMessagedSeen < uint64(len(delayedMessages)) { + return fmt.Errorf("mel state's DelayedMessagedSeen: %d is lower than number of delayed messages: %d queued to be added", state.DelayedMessagedSeen, len(delayedMessages)) + } + firstPos := state.DelayedMessagedSeen - uint64(len(delayedMessages)) + for i, msg := range delayedMessages { + key := dbKey(dbschema.MelDelayedMessagePrefix, firstPos+uint64(i)) // #nosec G115 + delayedBytes, err := rlp.EncodeToBytes(*msg) + if err != nil { + return err + } + err = dbBatch.Put(key, delayedBytes) + if err != nil { + return err + } + + } + return dbBatch.Write() +} + +func (d *Database) ReadDelayedMessage(ctx context.Context, state *mel.State, index uint64) (*mel.DelayedInboxMessage, error) { + if index == 0 { // Init message + // TODO: to be implemented + return nil, nil + } + delayed, err := d.fetchDelayedMessage(index) + if err != nil { + return nil, err + } + if ok, err := d.checkAgainstAccumulator(ctx, state, delayed, index); err != nil { + return nil, fmt.Errorf("error checking if delayed message is part of the mel state accumulator: %w", err) + } else if !ok { + return nil, errors.New("delayed message message not part of the mel state accumulator") + } + return delayed, nil +} + +func (d *Database) fetchDelayedMessage(index uint64) (*mel.DelayedInboxMessage, error) { + key := dbKey(dbschema.MelDelayedMessagePrefix, index) + delayedBytes, err := d.db.Get(key) + if err != nil { + return nil, err + } + var delayed mel.DelayedInboxMessage + if err = rlp.DecodeBytes(delayedBytes, &delayed); err != nil { + return nil, err + } + return &delayed, nil +} + +func (d *Database) checkAgainstAccumulator(ctx context.Context, state *mel.State, msg *mel.DelayedInboxMessage, index uint64) (bool, error) { + // TODO: to be implemented + return true, nil +} + +func dbKey(prefix []byte, pos uint64) []byte { + var key []byte + key = append(key, prefix...) + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, pos) + key = append(key, data...) + return key +} diff --git a/arbnode/mel/runner/database_test.go b/arbnode/mel/runner/database_test.go new file mode 100644 index 0000000000..99e249ab0e --- /dev/null +++ b/arbnode/mel/runner/database_test.go @@ -0,0 +1,54 @@ +package melrunner + +import ( + "context" + "reflect" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + + "github.com/offchainlabs/nitro/arbnode/mel" +) + +func TestMelDatabase(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Create database + arbDb := rawdb.NewMemoryDatabase() + melDb := NewDatabase(arbDb) + + headMelState := &mel.State{ + ParentChainBlockNumber: 2, + ParentChainBlockHash: common.MaxHash, + } + require.NoError(t, melDb.SaveState(ctx, headMelState)) + + headMelStateBlockNum, err := melDb.GetHeadMelStateBlockNum() + require.NoError(t, err) + require.True(t, headMelStateBlockNum == headMelState.ParentChainBlockNumber) + + var melState *mel.State + checkMelState := func() { + require.NoError(t, err) + if !reflect.DeepEqual(melState, headMelState) { + t.Fatal("unexpected melState retrieved via GetState using parentChainBlockHash") + } + } + melState, err = melDb.State(ctx, headMelState.ParentChainBlockNumber) + checkMelState() + +} + +func TestMelDatabaseReadAndWriteDelayedMessages(t *testing.T) { + t.Skip("to be implemented") +} + +func TestMelDelayedMessagesAccumulation(t *testing.T) { + t.Skip("to be implemented") +} diff --git a/arbnode/message-extraction/fsm.go b/arbnode/mel/runner/fsm.go similarity index 88% rename from arbnode/message-extraction/fsm.go rename to arbnode/mel/runner/fsm.go index 7323bbd95b..b155bf13c7 100644 --- a/arbnode/message-extraction/fsm.go +++ b/arbnode/mel/runner/fsm.go @@ -1,11 +1,10 @@ -package mel +package melrunner import ( "fmt" "github.com/offchainlabs/bold/containers/fsm" - "github.com/offchainlabs/nitro/arbnode" - meltypes "github.com/offchainlabs/nitro/arbnode/message-extraction/types" + "github.com/offchainlabs/nitro/arbnode/mel" "github.com/offchainlabs/nitro/arbos/arbostypes" ) @@ -46,14 +45,15 @@ type backToStart struct{} // An action that transitions the FSM to the processing next block state. type processNextBlock struct { - melState *meltypes.State + melState *mel.State } // An action that transitions the FSM to the saving messages state. type saveMessages struct { - postState *meltypes.State - messages []*arbostypes.MessageWithMetadata - delayedMessages []*arbnode.DelayedInboxMessage + preStateMsgCount uint64 + postState *mel.State + messages []*arbostypes.MessageWithMetadata + delayedMessages []*mel.DelayedInboxMessage } // An action that transitions the FSM to the reorging state. diff --git a/arbnode/message-extraction/mel.go b/arbnode/mel/runner/mel.go similarity index 79% rename from arbnode/message-extraction/mel.go rename to arbnode/mel/runner/mel.go index c93bb797a2..646cd62604 100644 --- a/arbnode/message-extraction/mel.go +++ b/arbnode/mel/runner/mel.go @@ -1,4 +1,4 @@ -package mel +package melrunner import ( "context" @@ -13,8 +13,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/bold/containers/fsm" - extractionfunction "github.com/offchainlabs/nitro/arbnode/message-extraction/extraction-function" - meltypes "github.com/offchainlabs/nitro/arbnode/message-extraction/types" + "github.com/offchainlabs/nitro/arbnode/mel" + melextraction "github.com/offchainlabs/nitro/arbnode/mel/extraction" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/daprovider" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -26,6 +26,7 @@ const defaultRetryInterval = time.Second type ParentChainReader interface { BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) TransactionInBlock(ctx context.Context, blockHash common.Hash, index uint) (*types.Transaction, error) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) @@ -36,9 +37,9 @@ type ParentChainReader interface { type MessageExtractor struct { stopwaiter.StopWaiter parentChainReader ParentChainReader - initialStateFetcher meltypes.StateFetcher addrs *chaininfo.RollupAddresses - melDB meltypes.StateDatabase + melDB *Database + msgConsumer mel.MessageConsumer dataProviders []daprovider.Reader startParentChainBlockHash common.Hash fsm *fsm.Fsm[action, FSMState] @@ -51,8 +52,8 @@ type MessageExtractor struct { func NewMessageExtractor( parentChainReader ParentChainReader, rollupAddrs *chaininfo.RollupAddresses, - initialStateFetcher meltypes.StateFetcher, - melDB meltypes.StateDatabase, + melDB *Database, + msgConsumer mel.MessageConsumer, dataProviders []daprovider.Reader, startParentChainBlockHash common.Hash, retryInterval time.Duration, @@ -67,8 +68,8 @@ func NewMessageExtractor( return &MessageExtractor{ parentChainReader: parentChainReader, addrs: rollupAddrs, - initialStateFetcher: initialStateFetcher, melDB: melDB, + msgConsumer: msgConsumer, dataProviders: dataProviders, startParentChainBlockHash: startParentChainBlockHash, fsm: fsm, @@ -122,6 +123,29 @@ func (rf *blockReceiptFetcher) ReceiptForTransactionIndex( return rf.client.TransactionReceipt(ctx, tx.Hash()) } +type blockTxsFetcher struct { + client ParentChainReader + parentChainBlock *types.Block +} + +func newBlockTxsFetcher(client ParentChainReader, parentChainBlock *types.Block) *blockTxsFetcher { + return &blockTxsFetcher{ + client: client, + parentChainBlock: parentChainBlock, + } +} + +func (tf *blockTxsFetcher) TransactionsByHeader( + ctx context.Context, + parentChainHeaderHash common.Hash, +) (types.Transactions, error) { + blk, err := tf.client.BlockByHash(ctx, parentChainHeaderHash) + if err != nil { + return nil, err + } + return blk.Transactions(), nil +} + func (m *MessageExtractor) CurrentFSMState() FSMState { return m.fsm.Current().State } @@ -136,7 +160,6 @@ func (m *MessageExtractor) Act(ctx context.Context) (time.Duration, error) { // the `ProcessingNextBlock` state after successfully fetching the initial // MEL state struct for the message extraction process. case Start: - // TODO: Start from the latest MEL state we have in the database if it exists as the first step. // Check if the specified start block hash exists in the parent chain. if _, err := m.parentChainReader.HeaderByHash( ctx, @@ -148,14 +171,15 @@ func (m *MessageExtractor) Act(ctx context.Context) (time.Duration, error) { err, ) } - // Fetch the initial state for MEL from a state fetcher interface by parent chain block hash. - melState, err := m.initialStateFetcher.GetState( - ctx, - m.startParentChainBlockHash, - ) + // Start from the latest MEL state we have in the database + melState, err := m.melDB.GetHeadMelState(ctx) if err != nil { return m.retryInterval, err } + // We check if our current head mel state corresponds to this parentChainBlockHash + if melState.ParentChainBlockHash != m.startParentChainBlockHash { + return m.retryInterval, fmt.Errorf("head mel state's parentChainBlockHash in db: %v does not match the given parentChainBlockHash: %v ", melState.ParentChainBlockHash, m.startParentChainBlockHash) + } // Begin the next FSM state immediately. return 0, m.fsm.Do(processNextBlock{ melState: melState, @@ -190,22 +214,25 @@ func (m *MessageExtractor) Act(ctx context.Context) (time.Duration, error) { // Creates a receipt fetcher for the specific parent chain block, to be used // by the message extraction function. receiptFetcher := newBlockReceiptFetcher(m.parentChainReader, parentChainBlock) - postState, msgs, delayedMsgs, err := extractionfunction.ExtractMessages( + txsFetcher := newBlockTxsFetcher(m.parentChainReader, parentChainBlock) + postState, msgs, delayedMsgs, err := melextraction.ExtractMessages( ctx, preState, - parentChainBlock, + parentChainBlock.Header(), m.dataProviders, m.melDB, receiptFetcher, + txsFetcher, ) if err != nil { return m.retryInterval, err } // Begin the next FSM state immediately. return 0, m.fsm.Do(saveMessages{ - postState: postState, - messages: msgs, - delayedMessages: delayedMsgs, + preStateMsgCount: preState.MsgCount, + postState: postState, + messages: msgs, + delayedMessages: delayedMsgs, }) // `SavingMessages` is the state responsible for saving the extracted messages // and delayed messages to the database. It stores data in the node's consensus database @@ -218,12 +245,14 @@ func (m *MessageExtractor) Act(ctx context.Context) (time.Duration, error) { if !ok { return m.retryInterval, fmt.Errorf("invalid action: %T", current.SourceEvent) } - // TODO: Use a DB batch to ensure these writes atomic, so if one fails, nothing will be persisted. - // The ArbDB batcher has support for this. if err := m.melDB.SaveDelayedMessages(ctx, saveAction.postState, saveAction.delayedMessages); err != nil { return m.retryInterval, err } - if err := m.melDB.SaveState(ctx, saveAction.postState, saveAction.messages); err != nil { + if err := m.msgConsumer.PushMessages(ctx, saveAction.preStateMsgCount, saveAction.messages); err != nil { + return m.retryInterval, err + } + if err := m.melDB.SaveState(ctx, saveAction.postState); err != nil { + log.Error("Error saving messages from MessageExtractor to MessageConsumer", "err", err) return m.retryInterval, err } return 0, m.fsm.Do(processNextBlock{ diff --git a/arbnode/message-extraction/mel_test.go b/arbnode/mel/runner/mel_test.go similarity index 76% rename from arbnode/message-extraction/mel_test.go rename to arbnode/mel/runner/mel_test.go index 4706281aac..89b19cf502 100644 --- a/arbnode/message-extraction/mel_test.go +++ b/arbnode/mel/runner/mel_test.go @@ -1,4 +1,4 @@ -package mel +package melrunner import ( "context" @@ -11,19 +11,19 @@ import ( "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" - "github.com/offchainlabs/nitro/arbnode" - meltypes "github.com/offchainlabs/nitro/arbnode/message-extraction/types" + "github.com/offchainlabs/nitro/arbnode/mel" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/daprovider" ) var _ ParentChainReader = (*mockParentChainReader)(nil) -var _ meltypes.StateDatabase = (*mockMELDB)(nil) func TestMessageExtractor(t *testing.T) { + t.Skip("Skipping as requires more MEL items merged in before it fully works") ctx := context.Background() parentChainReader := &mockParentChainReader{ blocks: map[common.Hash]*types.Block{ @@ -34,12 +34,14 @@ func TestMessageExtractor(t *testing.T) { {}: {}, }, } - initialStateFetcher := &mockInitialStateFetcher{} + arbDb := rawdb.NewMemoryDatabase() + melDb := NewDatabase(arbDb) + messageConsumer := &mockMessageConsumer{} extractor, err := NewMessageExtractor( parentChainReader, &chaininfo.RollupAddresses{}, - initialStateFetcher, - &mockMELDB{}, + melDb, + messageConsumer, []daprovider.Reader{}, common.Hash{}, 0, @@ -57,18 +59,16 @@ func TestMessageExtractor(t *testing.T) { require.True(t, extractor.CurrentFSMState() == Start) parentChainReader.returnErr = nil - initialStateFetcher.returnErr = errors.New("failed to get state") _, err = extractor.Act(ctx) - require.ErrorContains(t, err, "failed to get state") + require.ErrorContains(t, err, "error getting HeadMelStateBlockNum from database: not found") // Expect that we can now transition to the process // next block state. - melState := &meltypes.State{ + melState := &mel.State{ Version: 42, ParentChainBlockNumber: 0, } - initialStateFetcher.returnErr = nil - initialStateFetcher.state = melState + require.NoError(t, melDb.SaveState(ctx, melState)) _, err = extractor.Act(ctx) require.NoError(t, err) @@ -105,18 +105,10 @@ func TestMessageExtractor(t *testing.T) { }) } -type mockInitialStateFetcher struct { - state *meltypes.State - returnErr error -} +type mockMessageConsumer struct{ returnErr error } -func (m *mockInitialStateFetcher) GetState( - _ context.Context, _ common.Hash, -) (*meltypes.State, error) { - if m.returnErr != nil { - return nil, m.returnErr - } - return m.state, nil +func (m *mockMessageConsumer) PushMessages(ctx context.Context, firstMsgIdx uint64, messages []*arbostypes.MessageWithMetadata) error { + return m.returnErr } type mockParentChainReader struct { @@ -136,6 +128,17 @@ func (m *mockParentChainReader) BlockByNumber(ctx context.Context, number *big.I return block, nil } +func (m *mockParentChainReader) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + if m.returnErr != nil { + return nil, m.returnErr + } + block, ok := m.blocks[hash] + if !ok { + return nil, fmt.Errorf("block not found") + } + return block, nil +} + func (m *mockParentChainReader) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { if m.returnErr != nil { return nil, m.returnErr @@ -169,36 +172,3 @@ func (m *mockParentChainReader) TransactionReceipt(ctx context.Context, txHash c // Mock implementation, return a dummy receipt return &types.Receipt{}, nil } - -type mockMELDB struct { -} - -func (m *mockMELDB) State( - _ context.Context, - _ common.Hash, -) (*meltypes.State, error) { - return nil, errors.New("unimplemented") -} - -func (m *mockMELDB) SaveState( - _ context.Context, - _ *meltypes.State, - _ []*arbostypes.MessageWithMetadata, -) error { - return nil -} - -func (m *mockMELDB) SaveDelayedMessages( - _ context.Context, - _ *meltypes.State, - _ []*arbnode.DelayedInboxMessage, -) error { - return nil -} -func (m *mockMELDB) ReadDelayedMessage( - _ context.Context, - _ *meltypes.State, - _ uint64, -) (*arbnode.DelayedInboxMessage, error) { - return nil, errors.New("unimplemented") -} diff --git a/arbnode/message-extraction/types/state.go b/arbnode/mel/state.go similarity index 77% rename from arbnode/message-extraction/types/state.go rename to arbnode/mel/state.go index 9235593557..45cba51ac6 100644 --- a/arbnode/message-extraction/types/state.go +++ b/arbnode/mel/state.go @@ -1,11 +1,10 @@ -package meltypes +package mel import ( "context" "github.com/ethereum/go-ethereum/common" - "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbostypes" ) @@ -22,41 +21,48 @@ type State struct { ParentChainBlockHash common.Hash ParentChainPreviousBlockHash common.Hash MessageAccumulator common.Hash - DelayedMessageAccumulator common.Hash + DelayedMessagesSeenRoot common.Hash MsgCount uint64 + BatchCount uint64 DelayedMessagesRead uint64 DelayedMessagedSeen uint64 } +// DelayedMessageDatabase can read delayed messages by their global index. +type DelayedMessageDatabase interface { + ReadDelayedMessage( + ctx context.Context, + state *State, + index uint64, + ) (*DelayedInboxMessage, error) +} + // Defines a basic interface for MEL, including saving states, messages, // and delayed messages to a database. type StateDatabase interface { + DelayedMessageDatabase State( ctx context.Context, - parentChainBlockHash common.Hash, + parentChainBlockNumber uint64, ) (*State, error) SaveState( ctx context.Context, state *State, - messages []*arbostypes.MessageWithMetadata, ) error SaveDelayedMessages( ctx context.Context, state *State, - delayedMessages []*arbnode.DelayedInboxMessage, + delayedMessages []*DelayedInboxMessage, ) error - ReadDelayedMessage( - ctx context.Context, - state *State, - index uint64, - ) (*arbnode.DelayedInboxMessage, error) } -// Defines an interface for fetching a MEL state by parent chain block hash. -type StateFetcher interface { - GetState( - ctx context.Context, parentChainBlockHash common.Hash, - ) (*State, error) +// MessageConsumer is an interface to be implemented by readers of MEL such as transaction streamer of the nitro node +type MessageConsumer interface { + PushMessages( + ctx context.Context, + firstMsgIdx uint64, + messages []*arbostypes.MessageWithMetadata, + ) error } // Performs a deep clone of the state struct to prevent any unintended @@ -67,13 +73,13 @@ func (s *State) Clone() *State { parentChainHash := common.Hash{} parentChainPrevHash := common.Hash{} msgAcc := common.Hash{} - delayedMsgAcc := common.Hash{} + delayedMsgSeenRoot := common.Hash{} copy(batchPostingTarget[:], s.BatchPostingTargetAddress[:]) copy(delayedMessageTarget[:], s.DelayedMessagePostingTargetAddress[:]) copy(parentChainHash[:], s.ParentChainBlockHash[:]) copy(parentChainPrevHash[:], s.ParentChainPreviousBlockHash[:]) copy(msgAcc[:], s.MessageAccumulator[:]) - copy(delayedMsgAcc[:], s.DelayedMessageAccumulator[:]) + copy(delayedMsgSeenRoot[:], s.DelayedMessagesSeenRoot[:]) return &State{ Version: s.Version, ParentChainId: s.ParentChainId, @@ -83,19 +89,19 @@ func (s *State) Clone() *State { ParentChainBlockHash: parentChainHash, ParentChainPreviousBlockHash: parentChainPrevHash, MessageAccumulator: msgAcc, - DelayedMessageAccumulator: delayedMsgAcc, + DelayedMessagesSeenRoot: delayedMsgSeenRoot, MsgCount: s.MsgCount, DelayedMessagesRead: s.DelayedMessagesRead, DelayedMessagedSeen: s.DelayedMessagedSeen, } } -func (s *State) AccumulateMessage(msg *arbostypes.MessageWithMetadata) *State { +func (s *State) AccumulateMessage(msg *arbostypes.MessageWithMetadata) error { // TODO: Unimplemented. - return s + return nil } -func (s *State) AccumulateDelayedMessage(msg *arbnode.DelayedInboxMessage) *State { +func (s *State) AccumulateDelayedMessage(msg *DelayedInboxMessage) error { // TODO: Unimplemented. - return s + return nil } diff --git a/arbnode/message-extraction/extraction-function/message_extraction_function.go b/arbnode/message-extraction/extraction-function/message_extraction_function.go deleted file mode 100644 index 4c3f411c16..0000000000 --- a/arbnode/message-extraction/extraction-function/message_extraction_function.go +++ /dev/null @@ -1,45 +0,0 @@ -package extractionfunction - -import ( - "context" - - "github.com/ethereum/go-ethereum/core/types" - - "github.com/offchainlabs/nitro/arbnode" - meltypes "github.com/offchainlabs/nitro/arbnode/message-extraction/types" - "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/daprovider" -) - -// Defines a method that can read a delayed message from an external database. -type DelayedMessageDatabase interface { - ReadDelayedMessage( - ctx context.Context, - state *meltypes.State, - index uint64, - ) (*arbnode.DelayedInboxMessage, error) -} - -// Defines a method that can fetch the receipt for a specific -// transaction index in a parent chain block. -type ReceiptFetcher interface { - ReceiptForTransactionIndex( - ctx context.Context, - txIndex uint, - ) (*types.Receipt, error) -} - -// ExtractMessages is a pure function that can read a parent chain block and -// and input MEL state to run a specific algorithm that extracts Arbitrum messages and -// delayed messages observed from transactions in the block. This function can be proven -// through a replay binary, and should also compile to WAVM in addition to running in native mode. -func ExtractMessages( - ctx context.Context, - inputState *meltypes.State, - parentChainBlock *types.Block, - dataProviders []daprovider.Reader, - delayedMsgDatabase DelayedMessageDatabase, - receiptFetcher ReceiptFetcher, -) (*meltypes.State, []*arbostypes.MessageWithMetadata, []*arbnode.DelayedInboxMessage, error) { - return nil, nil, nil, nil -} diff --git a/arbnode/node.go b/arbnode/node.go index a3489aa7bc..51e93992f2 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -101,9 +101,6 @@ func (c *Config) Validate() error { if err := c.BlockValidator.Validate(); err != nil { return err } - if err := c.Maintenance.Validate(); err != nil { - return err - } if err := c.InboxReader.Validate(); err != nil { return err } @@ -116,6 +113,9 @@ func (c *Config) Validate() error { if err := c.Staker.Validate(); err != nil { return err } + if err := c.SeqCoordinator.Validate(); err != nil { + return err + } if c.TransactionStreamer.TrackBlockMetadataFrom != 0 && !c.BlockMetadataFetcher.Enable { log.Warn("track-block-metadata-from is set but blockMetadata fetcher is not enabled") } @@ -472,17 +472,11 @@ func getBPVerifier( } func getMaintenanceRunner( - arbDb ethdb.Database, configFetcher ConfigFetcher, coordinator *SeqCoordinator, exec execution.ExecutionClient, ) (*MaintenanceRunner, error) { - dbs := []ethdb.Database{arbDb} - maintenanceRunner, err := NewMaintenanceRunner(func() *MaintenanceConfig { return &configFetcher.Get().Maintenance }, coordinator, dbs, exec) - if err != nil { - return nil, err - } - return maintenanceRunner, nil + return NewMaintenanceRunner(func() *MaintenanceConfig { return &configFetcher.Get().Maintenance }, coordinator, exec) } func getBroadcastClients( @@ -1067,7 +1061,7 @@ func createNodeImpl( return nil, err } - maintenanceRunner, err := getMaintenanceRunner(arbDb, configFetcher, coordinator, executionClient) + maintenanceRunner, err := getMaintenanceRunner(configFetcher, coordinator, executionClient) if err != nil { return nil, err } @@ -1234,16 +1228,6 @@ func registerAPIs(currentNode *Node, stack *node.Node) { Public: false, }) } - if currentNode.MaintenanceRunner != nil { - apis = append(apis, rpc.API{ - Namespace: "maintenance", - Version: "1.0", - Service: &MaintenanceAPI{ - runner: currentNode.MaintenanceRunner, - }, - Public: false, - }) - } stack.RegisterAPIs(apis) } @@ -1496,7 +1480,7 @@ func (n *Node) StopAndWait() { n.TxStreamer.StopAndWait() } // n.BroadcastServer is stopped after txStreamer and inboxReader because if done before it would lead to a deadlock, as the threads from these two components - // attempt to Broadcast i.e send feedMessage to clientManager's broadcastChan when there wont be any reader to read it as n.BroadcastServer would've been stopped + // attempt to Broadcast i.e send feedMessage to clientManager's broadcastChan when there won't be any reader to read it as n.BroadcastServer would've been stopped if n.BroadcastServer != nil && n.BroadcastServer.Started() { n.BroadcastServer.StopAndWait() } diff --git a/arbnode/redislock/redis.go b/arbnode/redislock/redis.go index 882657a22d..3753ffbdc3 100644 --- a/arbnode/redislock/redis.go +++ b/arbnode/redislock/redis.go @@ -47,7 +47,7 @@ func AddConfigOptions(prefix string, f *flag.FlagSet) { f.Duration(prefix+".lockout-duration", DefaultCfg.LockoutDuration, "how long lock is held") f.Duration(prefix+".refresh-duration", DefaultCfg.RefreshDuration, "how long between consecutive calls to redis") f.String(prefix+".key", DefaultCfg.Key, "key for lock") - f.Bool(prefix+".background-lock", DefaultCfg.BackgroundLock, "should node always try grabing lock in background") + f.Bool(prefix+".background-lock", DefaultCfg.BackgroundLock, "should node always try grabbing lock in background") } func NewSimple(client redis.UniversalClient, config SimpleCfgFetcher, readyToLock func() bool) (*Simple, error) { diff --git a/arbnode/schema.go b/arbnode/schema.go index 77375ec2db..6be52ada00 100644 --- a/arbnode/schema.go +++ b/arbnode/schema.go @@ -3,24 +3,26 @@ package arbnode +import dbschema "github.com/offchainlabs/nitro/arbnode/db-schema" + var ( - messagePrefix []byte = []byte("m") // maps a message sequence number to a message - blockHashInputFeedPrefix []byte = []byte("b") // maps a message sequence number to a block hash received through the input feed - blockMetadataInputFeedPrefix []byte = []byte("t") // maps a message sequence number to a blockMetaData byte array received through the input feed - missingBlockMetadataInputFeedPrefix []byte = []byte("x") // maps a message sequence number whose blockMetaData byte array is missing to nil - messageResultPrefix []byte = []byte("r") // maps a message sequence number to a message result - legacyDelayedMessagePrefix []byte = []byte("d") // maps a delayed sequence number to an accumulator and a message as serialized on L1 - rlpDelayedMessagePrefix []byte = []byte("e") // maps a delayed sequence number to an accumulator and an RLP encoded message - parentChainBlockNumberPrefix []byte = []byte("p") // maps a delayed sequence number to a parent chain block number - sequencerBatchMetaPrefix []byte = []byte("s") // maps a batch sequence number to BatchMetadata - delayedSequencedPrefix []byte = []byte("a") // maps a delayed message count to the first sequencer batch sequence number with this delayed count + messagePrefix = dbschema.MessagePrefix + blockHashInputFeedPrefix = dbschema.BlockHashInputFeedPrefix + blockMetadataInputFeedPrefix = dbschema.BlockMetadataInputFeedPrefix + missingBlockMetadataInputFeedPrefix = dbschema.MissingBlockMetadataInputFeedPrefix + messageResultPrefix = dbschema.MessageResultPrefix + legacyDelayedMessagePrefix = dbschema.LegacyDelayedMessagePrefix + rlpDelayedMessagePrefix = dbschema.RlpDelayedMessagePrefix + parentChainBlockNumberPrefix = dbschema.ParentChainBlockNumberPrefix + sequencerBatchMetaPrefix = dbschema.SequencerBatchMetaPrefix + delayedSequencedPrefix = dbschema.DelayedSequencedPrefix - messageCountKey []byte = []byte("_messageCount") // contains the current message count - lastPrunedMessageKey []byte = []byte("_lastPrunedMessageKey") // contains the last pruned message key - lastPrunedDelayedMessageKey []byte = []byte("_lastPrunedDelayedMessageKey") // contains the last pruned RLP delayed message key - delayedMessageCountKey []byte = []byte("_delayedMessageCount") // contains the current delayed message count - sequencerBatchCountKey []byte = []byte("_sequencerBatchCount") // contains the current sequencer message count - dbSchemaVersion []byte = []byte("_schemaVersion") // contains a uint64 representing the database schema version + messageCountKey = dbschema.MessageCountKey + lastPrunedMessageKey = dbschema.LastPrunedMessageKey + lastPrunedDelayedMessageKey = dbschema.LastPrunedDelayedMessageKey + delayedMessageCountKey = dbschema.DelayedMessageCountKey + sequencerBatchCountKey = dbschema.SequencerBatchCountKey + dbSchemaVersion = dbschema.DbSchemaVersion ) -const currentDbSchemaVersion uint64 = 1 +const currentDbSchemaVersion uint64 = dbschema.CurrentDbSchemaVersion diff --git a/arbnode/seq_coordinator.go b/arbnode/seq_coordinator.go index e18c2f2fbe..f7b9a74685 100644 --- a/arbnode/seq_coordinator.go +++ b/arbnode/seq_coordinator.go @@ -42,7 +42,7 @@ type SeqCoordinator struct { stopwaiter.StopWaiter redisCoordinatorMutex sync.RWMutex - redisCoordinator redisutil.RedisCoordinator + redisCoordinator *redisutil.RedisCoordinator prevRedisCoordinator *redisutil.RedisCoordinator prevRedisMessageCount arbutil.MessageIndex @@ -155,6 +155,16 @@ var TestSeqCoordinatorConfig = SeqCoordinatorConfig{ Signer: signature.DefaultSignVerifyConfig, } +func (c *SeqCoordinatorConfig) Validate() error { + if !c.Enable { + return nil + } + if c.RedisUrl == "" { + return errors.New("seq-coordinator.redis-url is required when seq-coordinator is enabled") + } + return nil +} + func NewSeqCoordinator( dataSigner signature.DataSignerFunc, bpvalidator *contracts.AddressVerifier, @@ -172,7 +182,7 @@ func NewSeqCoordinator( return nil, err } coordinator := &SeqCoordinator{ - redisCoordinator: *redisCoordinator, + redisCoordinator: redisCoordinator, sync: sync, streamer: streamer, sequencer: sequencer, @@ -196,14 +206,14 @@ func (c *SeqCoordinator) SetDelayedSequencer(delayedSequencer *DelayedSequencer) func (c *SeqCoordinator) RedisCoordinator() *redisutil.RedisCoordinator { c.redisCoordinatorMutex.RLock() defer c.redisCoordinatorMutex.RUnlock() - return &c.redisCoordinator + return c.redisCoordinator } func (c *SeqCoordinator) setRedisCoordinator(redisCoordinator *redisutil.RedisCoordinator) { c.redisCoordinatorMutex.Lock() defer c.redisCoordinatorMutex.Unlock() - c.prevRedisCoordinator = &c.redisCoordinator - c.redisCoordinator = *redisCoordinator + c.prevRedisCoordinator = c.redisCoordinator + c.redisCoordinator = redisCoordinator } func StandaloneSeqCoordinatorInvalidateMsgIndex(ctx context.Context, redisClient redis.UniversalClient, keyConfig string, msgIndex arbutil.MessageIndex) error { @@ -464,7 +474,11 @@ func (c *SeqCoordinator) chosenOneRelease(ctx context.Context) error { return nil } // got error - was it still released? - current, _ := c.CurrentChosenSequencer(ctx) //nolint:errcheck + current, err := c.CurrentChosenSequencer(ctx) + if err != nil { + log.Warn("unable to verify sequencer release status due to Redis error: %v", err) + return releaseErr + } if current != c.config.Url() { return nil } diff --git a/arbnode/seq_coordinator_test.go b/arbnode/seq_coordinator_test.go index c3c5ebe6c9..a203616a41 100644 --- a/arbnode/seq_coordinator_test.go +++ b/arbnode/seq_coordinator_test.go @@ -128,7 +128,7 @@ func TestRedisSeqCoordinatorAtomic(t *testing.T) { redisCoordinator, err := redisutil.NewRedisCoordinator(config.RedisUrl, config.RedisQuorumSize) Require(t, err) coordinator := &SeqCoordinator{ - redisCoordinator: *redisCoordinator, + redisCoordinator: redisCoordinator, config: config, signer: nullSigner, } @@ -184,7 +184,7 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { redisCoordinator, err := redisutil.NewRedisCoordinator(config.RedisUrl, config.RedisQuorumSize) Require(t, err) coordinator := &SeqCoordinator{ - redisCoordinator: *redisCoordinator, + redisCoordinator: redisCoordinator, config: config, signer: nullSigner, } @@ -229,7 +229,7 @@ func TestSeqCoordinatorDeletesFinalizedMessages(t *testing.T) { t.Fatalf("incorrect finalizedMsgCount, want: 5, have: %d", finalized) } - // Try deleting finalized messages when theres already a finalizedMsgCount + // Try deleting finalized messages when there's already a finalizedMsgCount err = coordinator.deleteFinalizedMsgsFromRedis(ctx, 7) Require(t, err) exists, err = coordinator.RedisCoordinator().Client.Exists(ctx, keys[8:12]...).Result() @@ -275,7 +275,7 @@ func TestSeqCoordinatorAddsBlockMetadata(t *testing.T) { redisCoordinator, err := redisutil.NewRedisCoordinator(config.RedisUrl, config.RedisQuorumSize) Require(t, err) coordinator := &SeqCoordinator{ - redisCoordinator: *redisCoordinator, + redisCoordinator: redisCoordinator, config: config, signer: nullSigner, } diff --git a/arbnode/sequencer_inbox.go b/arbnode/sequencer_inbox.go index 21a781be68..30a3bda381 100644 --- a/arbnode/sequencer_inbox.go +++ b/arbnode/sequencer_inbox.go @@ -29,13 +29,13 @@ var sequencerBatchDataABI abi.Event const sequencerBatchDataEvent = "SequencerBatchData" -type batchDataLocation uint8 +type BatchDataLocation uint8 const ( - batchDataTxInput batchDataLocation = iota - batchDataSeparateEvent - batchDataNone - batchDataBlobHashes + BatchDataTxInput BatchDataLocation = iota + BatchDataSeparateEvent + BatchDataNone + BatchDataBlobHashes ) func init() { @@ -106,16 +106,16 @@ type SequencerInboxBatch struct { AfterDelayedAcc common.Hash AfterDelayedCount uint64 TimeBounds bridgegen.IBridgeTimeBounds - rawLog types.Log - dataLocation batchDataLocation - bridgeAddress common.Address - serialized []byte // nil if serialization isn't cached yet + RawLog types.Log + DataLocation BatchDataLocation + BridgeAddress common.Address + Serialized []byte // nil if serialization isn't cached yet } func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client *ethclient.Client) ([]byte, error) { - switch m.dataLocation { - case batchDataTxInput: - data, err := arbutil.GetLogEmitterTxData(ctx, client, m.rawLog) + switch m.DataLocation { + case BatchDataTxInput: + data, err := arbutil.GetLogEmitterTxData(ctx, client, m.RawLog) if err != nil { return nil, err } @@ -129,12 +129,12 @@ func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client *ethc return nil, errors.New("args[\"data\"] not a byte array") } return dataBytes, nil - case batchDataSeparateEvent: + case BatchDataSeparateEvent: var numberAsHash common.Hash binary.BigEndian.PutUint64(numberAsHash[(32-8):], m.SequenceNumber) query := ethereum.FilterQuery{ BlockHash: &m.BlockHash, - Addresses: []common.Address{m.bridgeAddress}, + Addresses: []common.Address{m.BridgeAddress}, Topics: [][]common.Hash{{sequencerBatchDataABI.ID}, {numberAsHash}}, } logs, err := client.FilterLogs(ctx, query) @@ -153,11 +153,11 @@ func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client *ethc return nil, err } return event.Data, nil - case batchDataNone: + case BatchDataNone: // No data when in a force inclusion batch return nil, nil - case batchDataBlobHashes: - tx, err := arbutil.GetLogTransaction(ctx, client, m.rawLog) + case BatchDataBlobHashes: + tx, err := arbutil.GetLogTransaction(ctx, client, m.RawLog) if err != nil { return nil, err } @@ -170,13 +170,13 @@ func (m *SequencerInboxBatch) getSequencerData(ctx context.Context, client *ethc } return data, nil default: - return nil, fmt.Errorf("batch has invalid data location %v", m.dataLocation) + return nil, fmt.Errorf("batch has invalid data location %v", m.DataLocation) } } func (m *SequencerInboxBatch) Serialize(ctx context.Context, client *ethclient.Client) ([]byte, error) { - if m.serialized != nil { - return m.serialized, nil + if m.Serialized != nil { + return m.Serialized, nil } var fullData []byte @@ -202,7 +202,7 @@ func (m *SequencerInboxBatch) Serialize(ctx context.Context, client *ethclient.C } fullData = append(fullData, data...) - m.serialized = fullData + m.Serialized = fullData return fullData, nil } @@ -249,10 +249,10 @@ func (i *SequencerInbox) LookupBatchesInRange(ctx context.Context, from, to *big AfterInboxAcc: parsedLog.AfterAcc, AfterDelayedAcc: parsedLog.DelayedAcc, AfterDelayedCount: parsedLog.AfterDelayedMessagesRead.Uint64(), - rawLog: log, + RawLog: log, TimeBounds: parsedLog.TimeBounds, - dataLocation: batchDataLocation(parsedLog.DataLocation), - bridgeAddress: log.Address, + DataLocation: BatchDataLocation(parsedLog.DataLocation), + BridgeAddress: log.Address, } messages = append(messages, batch) } diff --git a/arbnode/simple_redis_lock_test.go b/arbnode/simple_redis_lock_test.go index f275cf53be..d5f77ebbdf 100644 --- a/arbnode/simple_redis_lock_test.go +++ b/arbnode/simple_redis_lock_test.go @@ -37,7 +37,7 @@ func attemptLock(ctx context.Context, s *redislock.Simple, flag *atomic.Int32, w } } -func simpleRedisLockTest(t *testing.T, redisKeySuffix string, chosen int, backgound bool) { +func simpleRedisLockTest(t *testing.T, redisKeySuffix string, chosen int, background bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -52,7 +52,7 @@ func simpleRedisLockTest(t *testing.T, redisKeySuffix string, chosen int, backgo LockoutDuration: test_delay * test_attempts * 10, RefreshDuration: test_delay * 2, Key: redisKey, - BackgroundLock: backgound, + BackgroundLock: background, } confFetcher := func() *redislock.SimpleCfg { return conf } @@ -72,7 +72,7 @@ func simpleRedisLockTest(t *testing.T, redisKeySuffix string, chosen int, backgo defer lock.StopAndWait() locks = append(locks, lock) } - if backgound { + if background { <-time.After(time.Second) } wg := sync.WaitGroup{} diff --git a/arbnode/sync_monitor.go b/arbnode/sync_monitor.go index fa16001d91..8374aa4319 100644 --- a/arbnode/sync_monitor.go +++ b/arbnode/sync_monitor.go @@ -63,7 +63,7 @@ func (s *SyncMonitor) updateSyncTarget(ctx context.Context) time.Duration { s.syncTarget = s.nextSyncTarget s.nextSyncTarget = nextSyncTarget } else { - log.Warn("failed readin max msg count", "err", err) + log.Warn("failed reading max msg count", "err", err) s.nextSyncTarget = 0 s.syncTarget = 0 } @@ -125,6 +125,11 @@ func (s *SyncMonitor) maxMessageCount() (arbutil.MessageIndex, error) { func (s *SyncMonitor) FullSyncProgressMap() map[string]interface{} { res := make(map[string]interface{}) + if !s.Started() { + res["err"] = "notStarted" + return res + } + if !s.initialized { res["err"] = "uninitialized" return res @@ -197,10 +202,10 @@ func (s *SyncMonitor) Start(ctx_in context.Context) { } func (s *SyncMonitor) Synced() bool { - if !s.initialized { + if !s.Started() { return false } - if !s.Started() { + if !s.initialized { return false } syncTarget := s.SyncTargetMessageCount() diff --git a/arbnode/transaction_streamer.go b/arbnode/transaction_streamer.go index ddeca6e029..d1cbb55cb8 100644 --- a/arbnode/transaction_streamer.go +++ b/arbnode/transaction_streamer.go @@ -21,6 +21,7 @@ import ( flag "github.com/spf13/pflag" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" @@ -34,7 +35,6 @@ import ( "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/util/arbmath" - "github.com/offchainlabs/nitro/util/dbutil" "github.com/offchainlabs/nitro/util/sharedmetrics" "github.com/offchainlabs/nitro/util/stopwaiter" ) @@ -70,6 +70,7 @@ type TransactionStreamer struct { delayedBridge *DelayedBridge trackBlockMetadataFrom arbutil.MessageIndex + syncTillMessage arbutil.MessageIndex } type TransactionStreamerConfig struct { @@ -137,6 +138,17 @@ func NewTransactionStreamer( } streamer.trackBlockMetadataFrom = trackBlockMetadataFrom } + if config().SyncTillBlock != 0 { + syncTillMessage, err := exec.BlockNumberToMessageIndex(config().SyncTillBlock).Await(ctx) + if err != nil { + return nil, err + } + streamer.syncTillMessage = syncTillMessage + msgCount, err := streamer.GetMessageCount() + if err == nil && msgCount >= streamer.syncTillMessage { + log.Info("Node has all messages", "sync-till-block", config().SyncTillBlock) + } + } return streamer, nil } @@ -504,7 +516,7 @@ func (s *TransactionStreamer) getMessageWithMetadataAndBlockInfo(msgIdx arbutil. return nil, err } blockHash = blockHashDBVal.BlockHash - } else if !dbutil.IsErrNotFound(err) { + } else if !rawdb.IsDbErrNotFound(err) { return nil, err } @@ -668,7 +680,7 @@ func (s *TransactionStreamer) AddBroadcastMessages(feedMessages []*m.BroadcastFe if broadcastFirstMsgIdx > 0 { _, err := s.GetMessage(broadcastFirstMsgIdx - 1) if err != nil { - if !dbutil.IsErrNotFound(err) { + if !rawdb.IsDbErrNotFound(err) { return err } // Message before current message doesn't exist in database, so don't add current messages yet @@ -750,7 +762,7 @@ func (s *TransactionStreamer) AddMessagesAndEndBatch(firstMsgIdx arbutil.Message if numberOfDuplicates == uint64(len(messages)) { return endBatch(batch) } - // cant keep reorg lock when catching insertionMutex. + // can't keep reorg lock when catching insertionMutex. // we have to re-evaluate all messages // happy cases for confirmed messages: // 1: were previously in feed. We saved work @@ -1183,7 +1195,7 @@ func (s *TransactionStreamer) broadcastMessages( // The mutex must be held, and firstMsgIdx must be the latest message count. // `batch` may be nil, which initializes a new batch. The batch is closed out in this function. func (s *TransactionStreamer) writeMessages(firstMsgIdx arbutil.MessageIndex, messages []arbostypes.MessageWithMetadataAndBlockInfo, batch ethdb.Batch) error { - if s.config().SyncTillBlock > 0 && uint64(firstMsgIdx) > s.config().SyncTillBlock { + if s.syncTillMessage > 0 && firstMsgIdx > s.syncTillMessage { return broadcastclient.TransactionStreamerBlockCreationStopped } if batch == nil { @@ -1222,7 +1234,7 @@ func (s *TransactionStreamer) BlockMetadataAtMessageIndex(msgIdx arbutil.Message key := dbKey(blockMetadataInputFeedPrefix, uint64(msgIdx)) blockMetadata, err := s.db.Get(key) if err != nil { - if dbutil.IsErrNotFound(err) { + if rawdb.IsDbErrNotFound(err) { return nil, nil } return nil, err @@ -1239,7 +1251,7 @@ func (s *TransactionStreamer) ResultAtMessageIndex(msgIdx arbutil.MessageIndex) if err == nil { return &msgResult, nil } - } else if !dbutil.IsErrNotFound(err) { + } else if !rawdb.IsDbErrNotFound(err) { return nil, err } log.Info(FailedToGetMsgResultFromDB, "msgIdx", msgIdx) @@ -1275,6 +1287,7 @@ func (s *TransactionStreamer) checkResult(msgIdx arbutil.MessageIndex, msgResult if msgResult.BlockHash != *msgAndBlockInfo.BlockHash { log.Error( BlockHashMismatchLogMsg, + "msgIdx", msgIdx, "expected", msgAndBlockInfo.BlockHash, "actual", msgResult.BlockHash, ) @@ -1337,7 +1350,8 @@ func (s *TransactionStreamer) ExecuteNextMsg(ctx context.Context) bool { return false } - if execHeadMsgIdx >= consensusHeadMsgIdx { + if execHeadMsgIdx >= consensusHeadMsgIdx || + (s.syncTillMessage > 0 && execHeadMsgIdx >= s.syncTillMessage) { return false } msgIdxToExecute := execHeadMsgIdx + 1 @@ -1391,10 +1405,6 @@ func (s *TransactionStreamer) ExecuteNextMsg(ctx context.Context) bool { } func (s *TransactionStreamer) executeMessages(ctx context.Context, ignored struct{}) time.Duration { - if s.config().SyncTillBlock > 0 && s.prevHeadMsgIdx != nil && uint64(*s.prevHeadMsgIdx) >= s.config().SyncTillBlock { - log.Info("stopping block creation in transaction streamer", "syncTillBlock", s.config().SyncTillBlock) - return s.config().ExecuteMessageLoopDelay - } if s.ExecuteNextMsg(ctx) { return 0 } @@ -1424,7 +1434,7 @@ func (s *TransactionStreamer) backfillTrackersForMissingBlockMetadata(ctx contex if err == nil { return true } - if !dbutil.IsErrNotFound(err) { + if !rawdb.IsDbErrNotFound(err) { log.Error("Error reading key in arbDB while back-filling trackers for missing blockMetadata", "key", key, "err", err) } return false diff --git a/arbos/arbosState/arbosstate.go b/arbos/arbosState/arbosstate.go index 8203dbb9a6..3fe2c1b4db 100644 --- a/arbos/arbosState/arbosstate.go +++ b/arbos/arbosState/arbosstate.go @@ -213,7 +213,7 @@ func InitializeArbosState(stateDB vm.StateDB, burner burn.Burner, chainConfig *p nativeTokenEnabledFromTime := uint64(0) if genesisArbOSInit != nil && genesisArbOSInit.NativeTokenSupplyManagementEnabled { // Since we're initializing the state from the beginning with the - // faeture eanbled, we set the enalbed time to 1 (which will always be) + // feature enabled, we set the enabled time to 1 (which will always be) // lower than the timestamp of the first block of the chain. nativeTokenEnabledFromTime = uint64(1) } diff --git a/arbos/block_processor.go b/arbos/block_processor.go index 5b4d2f3dcc..92d3e6af09 100644 --- a/arbos/block_processor.go +++ b/arbos/block_processor.go @@ -115,6 +115,8 @@ func createNewHeader(prevHeader *types.Header, l1info *L1Info, state *arbosState type ConditionalOptionsForTx []*arbitrum_types.ConditionalOptions type SequencingHooks struct { + NextTxToSequence func() (*types.Transaction, error) // Must be set + SequencedTx func(int) (*types.Transaction, error) // Must be set TxErrors []error // This can be unset DiscardInvalidTxsEarly bool // This can be unset PreTxFilter func(*params.ChainConfig, *types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, *arbitrum_types.ConditionalOptions, common.Address, *L1Info) error // This has to be set. Writes to *state.StateDB object should be avoided to prevent invalid state from permeating @@ -123,8 +125,43 @@ type SequencingHooks struct { ConditionalOptionsForTx []*arbitrum_types.ConditionalOptions // This can be unset } -func NoopSequencingHooks() *SequencingHooks { +type noopTxScheduler struct { + txs types.Transactions + scheduledTxsCount int +} + +func (s *noopTxScheduler) GetNextTx() (*types.Transaction, error) { + // This is not supposed to happen, if so we have a bug + if s.scheduledTxsCount > len(s.txs) { + return nil, errors.New("noopTxScheduler: requested too many transactions") + } + if s.scheduledTxsCount == len(s.txs) { + return nil, nil + } + s.scheduledTxsCount += 1 + return s.txs[s.scheduledTxsCount-1], nil +} + +func (s *noopTxScheduler) GetScheduledTx(txId int) (*types.Transaction, error) { + // This is not supposed to happen, if so we have a bug + if txId > len(s.txs) { + return nil, errors.New("transaction queried for does not exist in the noopTxScheduler") + } + // This is not supposed to happen, if so we have a bug + if txId > s.scheduledTxsCount { + return nil, errors.New("transaction queried for was not scheduled by the noopTxScheduler") + } + return s.txs[txId], nil +} + +func NoopSequencingHooks(txes types.Transactions) *SequencingHooks { + scheduler := &noopTxScheduler{ + txes, + 0, + } return &SequencingHooks{ + scheduler.GetNextTx, + scheduler.GetScheduledTx, []error{}, false, func(*params.ChainConfig, *types.Header, *state.StateDB, *arbosState.ArbosState, *types.Transaction, *arbitrum_types.ConditionalOptions, common.Address, *L1Info) error { @@ -145,7 +182,7 @@ func ProduceBlock( statedb *state.StateDB, chainContext core.ChainContext, isMsgForPrefetch bool, - runMode core.MessageRunMode, + runCtx *core.MessageRunContext, ) (*types.Block, types.Receipts, error) { chainConfig := chainContext.Config() txes, err := ParseL2Transactions(message, chainConfig.ChainID) @@ -153,24 +190,23 @@ func ProduceBlock( log.Warn("error parsing incoming message", "err", err) txes = types.Transactions{} } + hooks := NoopSequencingHooks(txes) - hooks := NoopSequencingHooks() return ProduceBlockAdvanced( - message.Header, txes, delayedMessagesRead, lastBlockHeader, statedb, chainContext, hooks, isMsgForPrefetch, runMode, + message.Header, delayedMessagesRead, lastBlockHeader, statedb, chainContext, hooks, isMsgForPrefetch, runCtx, ) } // A bit more flexible than ProduceBlock for use in the sequencer. func ProduceBlockAdvanced( l1Header *arbostypes.L1IncomingMessageHeader, - txes types.Transactions, delayedMessagesRead uint64, lastBlockHeader *types.Header, statedb *state.StateDB, chainContext core.ChainContext, sequencingHooks *SequencingHooks, isMsgForPrefetch bool, - runMode core.MessageRunMode, + runCtx *core.MessageRunContext, ) (*types.Block, types.Receipts, error) { arbState, err := arbosState.OpenSystemArbosState(statedb, nil, true) @@ -200,7 +236,6 @@ func ProduceBlockAdvanced( // Prepend a tx before all others to touch up the state (update the L1 block num, pricing pools, etc) startTx := InternalTxStartBlock(chainConfig.ChainID, l1Header.L1BaseFee, l1BlockNum, header, lastBlockHeader) - txes = append(types.Transactions{types.NewTx(startTx)}, txes...) complete := types.Transactions{} receipts := types.Receipts{} @@ -213,14 +248,19 @@ func ProduceBlockAdvanced( // We'll check that the block can fit each message, so this pool is set to not run out gethGas := core.GasPool(l2pricing.GethBlockGasLimit) - for len(txes) > 0 || len(redeems) > 0 { + firstTx := types.NewTx(startTx) + + for { // repeatedly process the next tx, doing redeems created along the way in FIFO order var tx *types.Transaction var options *arbitrum_types.ConditionalOptions - hooks := NoopSequencingHooks() + hooks := NoopSequencingHooks(nil) // TODO: NIT-3678 isUserTx := false - if len(redeems) > 0 { + if firstTx != nil { + tx = firstTx + firstTx = nil + } else if len(redeems) > 0 { tx = redeems[0] redeems = redeems[1:] @@ -234,8 +274,13 @@ func ProduceBlockAdvanced( continue } } else { - tx = txes[0] - txes = txes[1:] + tx, err = sequencingHooks.NextTxToSequence() + if err != nil { + return nil, nil, fmt.Errorf("error fetching next transaction to sequence, userTxsProcessed: %d, hookTxErrors: %d, err: %w", userTxsProcessed, len(sequencingHooks.TxErrors), err) + } + if tx == nil { + break + } if tx.Type() != types.ArbitrumInternalTxType { hooks = sequencingHooks // the sequencer has the ability to drop this tx isUserTx = true @@ -324,7 +369,7 @@ func ProduceBlockAdvanced( header, tx, &header.GasUsed, - runMode, + runCtx, func(result *core.ExecutionResult) error { return hooks.PostTxFilter(header, statedb, arbState, tx, sender, dataGas, result) }, diff --git a/arbos/constraints/constraints.go b/arbos/constraints/constraints.go new file mode 100644 index 0000000000..f5cf4660b3 --- /dev/null +++ b/arbos/constraints/constraints.go @@ -0,0 +1,57 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +// The constraints package tracks the multi-dimensional gas usage to apply constraint-based pricing. +package constraints + +import ( + "time" + + "github.com/ethereum/go-ethereum/arbitrum/multigas" +) + +// The period duration for a resource constraint. +type PeriodSecs uint32 + +// resourceConstraint defines the max gas target per second for the given period for a single resource. +type resourceConstraint struct { + period time.Duration + target uint64 +} + +// ResourceConstraints is a set of constraints for all resources. +// +// The chain owner defines constraints to limit the usage of each resource. A resource can have +// multiple constraints with different periods, but there may be a single constraint given the +// resource and period. +// +// Example constraints: +// - X amount of computation over 12 seconds so nodes can keep up. +// - Y amount of computation over 7 days so fresh nodes can catch up with the chain. +// - Z amount of history growth over one month to avoid bloat. +type ResourceConstraints map[multigas.ResourceKind]map[PeriodSecs]resourceConstraint + +// NewResourceConstraints creates a new set of constraints. +// This type can be used as a reference. +func NewResourceConstraints() ResourceConstraints { + c := ResourceConstraints{} + for resource := multigas.ResourceKindUnknown + 1; resource < multigas.NumResourceKind; resource++ { + c[resource] = map[PeriodSecs]resourceConstraint{} + } + return c +} + +// SetConstraint adds or updates the given resource constraint. +func (rc ResourceConstraints) SetConstraint( + resource multigas.ResourceKind, periodSecs PeriodSecs, targetPerPeriod uint64, +) { + rc[resource][periodSecs] = resourceConstraint{ + period: time.Duration(periodSecs) * time.Second, + target: targetPerPeriod / uint64(periodSecs), + } +} + +// ClearConstraint removes the given resource constraint. +func (rc ResourceConstraints) ClearConstraint(resource multigas.ResourceKind, periodSecs PeriodSecs) { + delete(rc[resource], periodSecs) +} diff --git a/arbos/constraints/constraints_test.go b/arbos/constraints/constraints_test.go new file mode 100644 index 0000000000..27e03ccb8e --- /dev/null +++ b/arbos/constraints/constraints_test.go @@ -0,0 +1,83 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package constraints + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/arbitrum/multigas" +) + +func TestResourceConstraints(t *testing.T) { + rc := NewResourceConstraints() + + const ( + minuteSecs = 60 + daySecs = 24 * 60 * 60 + weekSecs = 7 * daySecs + monthSecs = 30 * daySecs + ) + + // Adds a few constraints + rc.SetConstraint(multigas.ResourceKindComputation, minuteSecs, 5_000_000*minuteSecs) + rc.SetConstraint(multigas.ResourceKindComputation, weekSecs, 3_000_000*weekSecs) + rc.SetConstraint(multigas.ResourceKindHistoryGrowth, monthSecs, 1_000_000*monthSecs) + if got, want := len(rc[multigas.ResourceKindComputation]), 2; got != want { + t.Fatalf("unexpected number of computation constraints: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindComputation][minuteSecs].period, time.Duration(minuteSecs)*time.Second; got != want { + t.Errorf("unexpected constraint period: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindComputation][minuteSecs].target, uint64(5_000_000); got != want { + t.Errorf("unexpected constraint target: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindComputation][weekSecs].period, time.Duration(weekSecs)*time.Second; got != want { + t.Errorf("unexpected constraint period: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindComputation][weekSecs].target, uint64(3_000_000); got != want { + t.Errorf("unexpected constraint target: got %v, want %v", got, want) + } + if got, want := len(rc[multigas.ResourceKindHistoryGrowth]), 1; got != want { + t.Fatalf("unexpected number of history growth constraints: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindHistoryGrowth][monthSecs].period, time.Duration(monthSecs)*time.Second; got != want { + t.Errorf("unexpected constraint period: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindHistoryGrowth][monthSecs].target, uint64(1_000_000); got != want { + t.Errorf("unexpected constraint target: got %v, want %v", got, want) + } + if got, want := len(rc[multigas.ResourceKindStorageAccess]), 0; got != want { + t.Errorf("unexpected number of storage access constraints: got %v, want %v", got, want) + } + if got, want := len(rc[multigas.ResourceKindStorageGrowth]), 0; got != want { + t.Errorf("unexpected number of storage growth constraints: got %v, want %v", got, want) + } + + // Updates a constraint + rc.SetConstraint(multigas.ResourceKindHistoryGrowth, monthSecs, 500_000*monthSecs) + if got, want := len(rc[multigas.ResourceKindHistoryGrowth]), 1; got != want { + t.Fatalf("unexpected number of history growth constraints: got %v, want %v", got, want) + } + if got, want := rc[multigas.ResourceKindHistoryGrowth][monthSecs].target, uint64(500_000); got != want { + t.Errorf("unexpected constraint target: got %v, want %v", got, want) + } + + // Clear constraints + rc.ClearConstraint(multigas.ResourceKindComputation, minuteSecs) + rc.ClearConstraint(multigas.ResourceKindComputation, weekSecs) + rc.ClearConstraint(multigas.ResourceKindHistoryGrowth, monthSecs) + if got, want := len(rc[multigas.ResourceKindComputation]), 0; got != want { + t.Errorf("unexpected number of computation constraints: got %v, want %v", got, want) + } + if got, want := len(rc[multigas.ResourceKindHistoryGrowth]), 0; got != want { + t.Errorf("unexpected number of history growth constraints: got %v, want %v", got, want) + } + if got, want := len(rc[multigas.ResourceKindStorageAccess]), 0; got != want { + t.Errorf("unexpected number of storage access constraints: got %v, want %v", got, want) + } + if got, want := len(rc[multigas.ResourceKindStorageGrowth]), 0; got != want { + t.Errorf("unexpected number of storage growth constraints: got %v, want %v", got, want) + } +} diff --git a/arbos/l1pricing/l1pricing.go b/arbos/l1pricing/l1pricing.go index 08e20d75e9..40dc658165 100644 --- a/arbos/l1pricing/l1pricing.go +++ b/arbos/l1pricing/l1pricing.go @@ -562,7 +562,7 @@ func makeFakeTxForMessage(message *core.Message) *types.Transaction { } // During gas estimation, we don't want the gas limit variability to change the L1 cost. gas := message.GasLimit - if gas == 0 || message.TxRunMode == core.MessageGasEstimationMode { + if gas == 0 || message.TxRunContext.IsGasEstimation() { gas = RandomGas } return types.NewTx(&types.DynamicFeeTx{ diff --git a/arbos/l2pricing/l2pricing_test.go b/arbos/l2pricing/l2pricing_test.go index b5cbb00c91..0f32243851 100644 --- a/arbos/l2pricing/l2pricing_test.go +++ b/arbos/l2pricing/l2pricing_test.go @@ -48,7 +48,7 @@ func TestPricingModelExp(t *testing.T) { } // show that running at the speed limit with a target pool is close to a steady-state - // note that for large enough spans of time the price will rise a miniscule amount due to the pool's avg + // note that for large enough spans of time the price will rise a minuscule amount due to the pool's avg colors.PrintBlue("pool target & speed limit") for seconds := 0; seconds < 4; seconds++ { // #nosec G115 diff --git a/arbos/l2pricing/model.go b/arbos/l2pricing/model.go index 4ff67f4436..0cc1250a29 100644 --- a/arbos/l2pricing/model.go +++ b/arbos/l2pricing/model.go @@ -17,14 +17,9 @@ const InitialSpeedLimitPerSecondV6 = 7000000 const InitialPerBlockGasLimitV6 uint64 = 32 * 1000000 const InitialMinimumBaseFeeWei = params.GWei / 10 const InitialBaseFeeWei = InitialMinimumBaseFeeWei -const InitialGasPoolSeconds = 10 * 60 -const InitialRateEstimateInertia = 60 const InitialPricingInertia = 102 const InitialBacklogTolerance = 10 -var InitialGasPoolTargetBips = arbmath.PercentToBips(80) -var InitialGasPoolWeightBips = arbmath.PercentToBips(60) - func (ps *L2PricingState) AddToGasPool(gas int64) error { backlog, err := ps.GasBacklog() if err != nil { diff --git a/arbos/programs/api.go b/arbos/programs/api.go index 3e9bf099ce..b46b393546 100644 --- a/arbos/programs/api.go +++ b/arbos/programs/api.go @@ -154,7 +154,7 @@ func newApiClosures( switch opcode { case vm.CALL: - ret, returnGas, err = evm.Call(scope.Contract.Address(), contract, input, gas, value) + ret, returnGas, _, err = evm.Call(scope.Contract.Address(), contract, input, gas, value) case vm.DELEGATECALL: ret, returnGas, err = evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), contract, input, gas, scope.Contract.Value()) case vm.STATICCALL: diff --git a/arbos/programs/cgo_test.go b/arbos/programs/cgo_test.go index 39b660b08f..0393bea207 100644 --- a/arbos/programs/cgo_test.go +++ b/arbos/programs/cgo_test.go @@ -31,7 +31,11 @@ func TestCompileArch(t *testing.T) { fmt.Print("use -test_compile=[STORE|LOAD] to allow store/load in compile test") } store := strings.Contains(*testflag.CompileFlag, "STORE") - err := testCompileArch(store) + err := testCompileArch(store, false) + if err != nil { + t.Fatal(err) + } + err = testCompileArch(store, true) if err != nil { t.Fatal(err) } diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 0d05a5704a..b55db976a2 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -22,19 +22,20 @@ import "C" import ( "errors" "fmt" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/containers" ) type u8 = C.uint8_t @@ -71,15 +72,14 @@ func activateProgram( arbosVersionForGas uint64, debug bool, burner burn.Burner, + runCtx *core.MessageRunContext, ) (*activationInfo, error) { - targets := db.Database().WasmTargets() moduleActivationMandatory := true - info, asmMap, err := activateProgramInternal(program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft(), targets, moduleActivationMandatory) + info, asmMap, err := activateProgramInternal(program, codehash, wasm, page_limit, stylusVersion, arbosVersionForGas, debug, burner.GasLeft(), runCtx.WasmTargets(), moduleActivationMandatory) if err != nil { return nil, err } - db.ActivateWasm(info.moduleHash, asmMap) - return info, nil + return info, db.ActivateWasm(info.moduleHash, asmMap) } func activateModule( @@ -134,21 +134,33 @@ func compileNative( wasm []byte, stylusVersion uint16, debug bool, - target ethdb.WasmTarget, + target rawdb.WasmTarget, + cranelift bool, + timeout time.Duration, ) ([]byte, error) { - output := &rustBytes{} - status_asm := C.stylus_compile( - goSlice(wasm), - u16(stylusVersion), - cbool(debug), - goSlice([]byte(target)), - output, - ) - asm := rustBytesIntoBytes(output) - if status_asm != 0 { - return nil, fmt.Errorf("%w: %s", ErrProgramActivation, string(asm)) + result := containers.NewPromise[[]byte](func() {}) + go func() { + output := &rustBytes{} + status_asm := C.stylus_compile( + goSlice(wasm), + u16(stylusVersion), + cbool(debug), + goSlice([]byte(target)), + cbool(cranelift), + output, + ) + asm := rustBytesIntoBytes(output) + if status_asm != 0 { + result.ProduceError(fmt.Errorf("%w: %s", ErrProgramActivation, string(asm))) + } else { + result.Produce(asm) + } + }() + select { + case <-result.ReadyChan(): + case <-time.After(timeout): } - return asm, nil + return result.Current() } func activateProgramInternal( @@ -160,11 +172,11 @@ func activateProgramInternal( arbosVersionForGas uint64, debug bool, gasLeft *uint64, - targets []ethdb.WasmTarget, + targets []rawdb.WasmTarget, moduleActivationMandatory bool, -) (*activationInfo, map[ethdb.WasmTarget][]byte, error) { +) (*activationInfo, map[rawdb.WasmTarget][]byte, error) { var wavmFound bool - var nativeTargets []ethdb.WasmTarget + var nativeTargets []rawdb.WasmTarget for _, target := range targets { if target == rawdb.TargetWavm { wavmFound = true @@ -173,14 +185,14 @@ func activateProgramInternal( } } type result struct { - target ethdb.WasmTarget + target rawdb.WasmTarget asm []byte err error } results := make(chan result) // info will be set in separate thread, make sure to wait before reading var info *activationInfo - asmMap := make(map[ethdb.WasmTarget][]byte, len(nativeTargets)+1) + asmMap := make(map[rawdb.WasmTarget][]byte, len(nativeTargets)+1) if moduleActivationMandatory || wavmFound { go func() { var err error @@ -201,7 +213,13 @@ func activateProgramInternal( for _, target := range nativeTargets { target := target go func() { - asm, err := compileNative(wasm, stylusVersion, debug, target) + cranelift := false + timeout := time.Second * 15 + asm, err := compileNative(wasm, stylusVersion, debug, target, cranelift, timeout) + if err != nil { + log.Warn("initial stylus compilation failed", "address", addressForLogging, "cranelift", cranelift, "timeout", timeout, "err", err) + asm, err = compileNative(wasm, stylusVersion, debug, target, !cranelift, timeout) + } results <- result{target, asm, err} }() } @@ -233,11 +251,19 @@ func activateProgramInternal( return info, asmMap, err } -func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codehash common.Hash, maxWasmSize uint32, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) { - localTarget := rawdb.LocalTarget() - localAsm, err := statedb.TryGetActivatedAsm(localTarget, moduleHash) - if err == nil && len(localAsm) > 0 { - return localAsm, nil +// getCompiledProgram gets compiled wasm for all targets and recompiles missing ones. +func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codehash common.Hash, maxWasmSize uint32, pagelimit uint16, time uint64, debugMode bool, program Program, runCtx *core.MessageRunContext) (map[rawdb.WasmTarget][]byte, error) { + targets := runCtx.WasmTargets() + // even though we need only asm for local target, make sure that all configured targets are available as they are needed during multi-target recording of a program call + asmMap, missingTargets, err := statedb.ActivatedAsmMap(targets, moduleHash) + if err != nil { + // exit early in case of an unexpected error + // e.g. caused by inconsistent recent activations stored in statedb (not yet committed activation that doesn't contain asms for all targets) + log.Error("unexpected error reading asm map", "err", err) + return nil, err + } + if len(missingTargets) == 0 { + return asmMap, nil } // addressForLogging may be empty or may not correspond to the code, so we need to be careful to use the code passed in separately @@ -251,10 +277,10 @@ func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging c zeroArbosVersion := uint64(0) zeroGas := uint64(0) - targets := statedb.Database().WasmTargets() // we know program is activated, so it must be in correct version and not use too much memory moduleActivationMandatory := false - info, asmMap, err := activateProgramInternal(addressForLogging, codehash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas, targets, moduleActivationMandatory) + // compile only missing targets + info, newlyBuilt, err := activateProgramInternal(addressForLogging, codehash, wasm, pagelimit, program.version, zeroArbosVersion, debugMode, &zeroGas, missingTargets, moduleActivationMandatory) if err != nil { log.Error("failed to reactivate program", "address", addressForLogging, "expected moduleHash", moduleHash, "err", err) return nil, fmt.Errorf("failed to reactivate program address: %v err: %w", addressForLogging, err) @@ -265,30 +291,29 @@ func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging c return nil, fmt.Errorf("failed to reactivate program. address: %v, expected ModuleHash: %v", addressForLogging, moduleHash) } + // merge in the newly built asms + for target, asm := range newlyBuilt { + asmMap[target] = asm + } currentHoursSince := hoursSinceArbitrum(time) if currentHoursSince > program.activatedAt { // stylus program is active on-chain, and was activated in the past // so we store it directly to database batch := statedb.Database().WasmStore().NewBatch() - rawdb.WriteActivation(batch, moduleHash, asmMap) + // rawdb.WriteActivation iterates over the asms map and writes each entry separately to wasmdb, so the writes for the same module hash can be incremental + // we know that all targets for which asms were found initially, were read from disk as oppose to from newly activated asms from memory, as otherwise statedb.ActivatedAsmMap would have failed with an error because of missing targets within newly activated asms + rawdb.WriteActivation(batch, moduleHash, newlyBuilt) if err := batch.Write(); err != nil { log.Error("failed writing re-activation to state", "address", addressForLogging, "err", err) } } else { - // program activated recently, possibly in this eth_call - // store it to statedb. It will be stored to database if statedb is committed - statedb.ActivateWasm(moduleHash, asmMap) - } - asm, exists := asmMap[localTarget] - if !exists { - var availableTargets []ethdb.WasmTarget - for target := range asmMap { - availableTargets = append(availableTargets, target) + // we need to add asms for all targets to the newly activated targets (not only the newly built) to maintain consistency the newly activated targets map + if err := statedb.ActivateWasm(moduleHash, asmMap); err != nil { + log.Error("Failed to store recent activation of wasm in statedb", "err", err) + return nil, err } - log.Error("failed to reactivate program - missing asm for local target", "address", addressForLogging, "local target", localTarget, "available targets", availableTargets) - return nil, fmt.Errorf("failed to reactivate program - missing asm for local target, address: %v, local target: %v, available targets: %v", addressForLogging, localTarget, availableTargets) } - return asm, nil + return asmMap, nil } func callProgram( @@ -302,7 +327,7 @@ func callProgram( evmData *EvmData, stylusParams *ProgParams, memoryModel *MemoryModel, - arbos_tag uint32, + runCtx *core.MessageRunContext, ) ([]byte, error) { db := interpreter.Evm().StateDB debug := stylusParams.DebugMode @@ -312,8 +337,13 @@ func callProgram( panic("missing asm") } - if stateDb, ok := db.(*state.StateDB); ok { - stateDb.RecordProgram(db.Database().WasmTargets(), moduleHash) + if runCtx.IsRecording() { + if stateDb, ok := db.(*state.StateDB); ok { + if err := stateDb.RecordProgram(runCtx.WasmTargets(), moduleHash); err != nil { + log.Error("failed to record program", "program", address, "module", moduleHash, "err", err) + panic(fmt.Sprintf("failed to record program: %v", err)) + } + } } evmApi := newApi(interpreter, tracingInfo, scope, memoryModel) @@ -329,7 +359,7 @@ func callProgram( cbool(debug), output, (*u64)(&scope.Contract.Gas), - u32(arbos_tag), + u32(runCtx.WasmCacheTag()), )) depth := interpreter.Depth() @@ -356,24 +386,29 @@ func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out // Caches a program in Rust. We write a record so that we can undo on revert. // For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU. -func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codehash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { - if runMode == core.MessageCommitMode { +func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codehash common.Hash, params *StylusParams, debug bool, time uint64, runCtx *core.MessageRunContext) { + if runCtx.IsCommitMode() { // address is only used for logging - asm, err := getLocalAsm(db, module, addressForLogging, code, codehash, params.MaxWasmSize, params.PageLimit, time, debug, program) - if err != nil { - panic("unable to recreate wasm") + asmMap, err := getCompiledProgram(db, module, addressForLogging, code, codehash, params.MaxWasmSize, params.PageLimit, time, debug, program, runCtx) + var ok bool + var localAsm []byte + if asmMap != nil { + localAsm, ok = asmMap[rawdb.LocalTarget()] + } + if err != nil || !ok { + panic(fmt.Sprintf("failed to get compiled program for caching, program: %v, local target missing: %v, err: %v", addressForLogging.Hex(), !ok, err)) } - tag := db.Database().WasmCacheTag() - state.CacheWasmRust(asm, module, program.version, tag, debug) + tag := runCtx.WasmCacheTag() + state.CacheWasmRust(localAsm, module, program.version, tag, debug) db.RecordCacheWasm(state.CacheWasm{ModuleHash: module, Version: program.version, Tag: tag, Debug: debug}) } } // Evicts a program in Rust. We write a record so that we can undo on revert, unless we don't need to (e.g. expired) // For gas estimation and eth_call, we ignore permanent updates and rely on Rust's LRU. -func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, runMode core.MessageRunMode, forever bool) { - if runMode == core.MessageCommitMode { - tag := db.Database().WasmCacheTag() +func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, runCtx *core.MessageRunContext, forever bool) { + if runCtx.IsCommitMode() { + tag := runCtx.WasmCacheTag() state.EvictWasmRust(module, version, tag, debug) if !forever { db.RecordEvictWasm(state.EvictWasm{ModuleHash: module, Version: version, Tag: tag, Debug: debug}) @@ -463,7 +498,7 @@ func GetEntrySizeEstimateBytes(module []byte, version uint16, debug bool) uint64 const DefaultTargetDescriptionArm = "arm64-linux-unknown+neon" const DefaultTargetDescriptionX86 = "x86_64-linux-unknown+sse4.2+lzcnt+bmi" -func SetTarget(name ethdb.WasmTarget, description string, native bool) error { +func SetTarget(name rawdb.WasmTarget, description string, native bool) error { output := &rustBytes{} status := userStatus(C.stylus_target_set( goSlice([]byte(name)), diff --git a/arbos/programs/programs.go b/arbos/programs/programs.go index 7969cc389a..c11ff06097 100644 --- a/arbos/programs/programs.go +++ b/arbos/programs/programs.go @@ -10,6 +10,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" @@ -86,7 +87,7 @@ func (p Programs) CacheManagers() *addressSet.AddressSet { return p.cacheManagers } -func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode core.MessageRunMode, debugMode bool) ( +func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runCtx *core.MessageRunContext, debugMode bool) ( uint16, common.Hash, common.Hash, *big.Int, bool, error, ) { statedb := evm.StateDB @@ -120,7 +121,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode c // require the program's footprint not exceed the remaining memory budget pageLimit := am.SaturatingUSub(params.PageLimit, statedb.GetStylusPagesOpen()) - info, err := activateProgram(statedb, address, codeHash, wasm, pageLimit, stylusVersion, p.ArbosVersion, debugMode, burner) + info, err := activateProgram(statedb, address, codeHash, wasm, pageLimit, stylusVersion, p.ArbosVersion, debugMode, burner, runCtx) if err != nil { return 0, codeHash, common.Hash{}, nil, true, err } @@ -132,7 +133,7 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode c return 0, codeHash, common.Hash{}, nil, true, err } - evictProgram(statedb, oldModuleHash, currentVersion, debugMode, runMode, expired) + evictProgram(statedb, oldModuleHash, currentVersion, debugMode, runCtx, expired) } if err := p.moduleHashes.Set(codeHash, info.moduleHash); err != nil { return 0, codeHash, common.Hash{}, nil, true, err @@ -160,27 +161,12 @@ func (p Programs) ActivateProgram(evm *vm.EVM, address common.Address, runMode c // replace the cached asm if cached { code := statedb.GetCode(address) - cacheProgram(statedb, info.moduleHash, programData, address, code, codeHash, params, debugMode, time, runMode) + cacheProgram(statedb, info.moduleHash, programData, address, code, codeHash, params, debugMode, time, runCtx) } return stylusVersion, codeHash, info.moduleHash, dataFee, false, p.setProgram(codeHash, programData) } -func runModeToString(runMode core.MessageRunMode) string { - switch runMode { - case core.MessageCommitMode: - return "commit_runmode" - case core.MessageGasEstimationMode: - return "gas_estimation_runmode" - case core.MessageEthcallMode: - return "eth_call_runmode" - case core.MessageReplayMode: - return "replay_runmode" - default: - return "unknown_runmode" - } -} - func (p Programs) CallProgram( scope *vm.ScopeContext, statedb vm.StateDB, @@ -188,7 +174,7 @@ func (p Programs) CallProgram( tracingInfo *util.TracingInfo, calldata []byte, reentrant bool, - runMode core.MessageRunMode, + runCtx *core.MessageRunContext, ) ([]byte, error) { evm := interpreter.Evm() contract := scope.Contract @@ -234,9 +220,14 @@ func (p Programs) CallProgram( statedb.AddStylusPages(program.footprint) defer statedb.SetStylusPagesOpen(open) - localAsm, err := getLocalAsm(statedb, moduleHash, contract.Address(), contract.Code, contract.CodeHash, params.MaxWasmSize, params.PageLimit, evm.Context.Time, debugMode, program) - if err != nil { - panic("failed to get local wasm for activated program: " + contract.Address().Hex()) + asmMap, err := getCompiledProgram(statedb, moduleHash, contract.Address(), contract.Code, contract.CodeHash, params.MaxWasmSize, params.PageLimit, evm.Context.Time, debugMode, program, runCtx) + var ok bool + var localAsm []byte + if asmMap != nil { + localAsm, ok = asmMap[rawdb.LocalTarget()] + } + if err != nil || !ok { + panic(fmt.Sprintf("failed to get compiled program for activated program, program: %v, local target missing: %v, err: %v", contract.Address().Hex(), !ok, err)) } evmData := &EvmData{ @@ -259,27 +250,22 @@ func (p Programs) CallProgram( } address := contract.Address() - var arbos_tag uint32 - if runMode == core.MessageCommitMode { - arbos_tag = statedb.Database().WasmCacheTag() - } - - metrics.GetOrRegisterCounter(fmt.Sprintf("arb/arbos/stylus/program_calls/%s", runModeToString(runMode)), nil).Inc(1) - ret, err := callProgram(address, moduleHash, localAsm, scope, interpreter, tracingInfo, calldata, evmData, goParams, model, arbos_tag) + metrics.GetOrRegisterCounter(fmt.Sprintf("arb/arbos/stylus/program_calls/%s", runCtx.RunModeMetricName()), nil).Inc(1) + ret, err := callProgram(address, moduleHash, localAsm, scope, interpreter, tracingInfo, calldata, evmData, goParams, model, runCtx) if len(ret) > 0 && p.ArbosVersion >= gethParams.ArbosVersion_StylusFixes { // Ensure that return data costs as least as much as it would in the EVM. evmCost := evmMemoryCost(uint64(len(ret))) if startingGas < evmCost { contract.Gas = 0 // #nosec G115 - metrics.GetOrRegisterCounter(fmt.Sprintf("arb/arbos/stylus/gas_used/%s", runModeToString(runMode)), nil).Inc(int64(startingGas)) + metrics.GetOrRegisterCounter(fmt.Sprintf("arb/arbos/stylus/gas_used/%s", runCtx.RunModeMetricName()), nil).Inc(int64(startingGas)) return nil, vm.ErrOutOfGas } maxGasToReturn := startingGas - evmCost contract.Gas = am.MinInt(contract.Gas, maxGasToReturn) } // #nosec G115 - metrics.GetOrRegisterCounter(fmt.Sprintf("arb/arbos/stylus/gas_used/%s", runModeToString(runMode)), nil).Inc(int64(startingGas - contract.Gas)) + metrics.GetOrRegisterCounter(fmt.Sprintf("arb/arbos/stylus/gas_used/%s", runCtx.RunModeMetricName()), nil).Inc(int64(startingGas - contract.Gas)) return ret, err } @@ -417,7 +403,7 @@ func (p Programs) SetProgramCached( cache bool, time uint64, params *StylusParams, - runMode core.MessageRunMode, + runCtx *core.MessageRunContext, debug bool, ) error { program, err := p.getProgram(codeHash, time) @@ -453,9 +439,9 @@ func (p Programs) SetProgramCached( if err != nil { return err } - cacheProgram(db, moduleHash, program, address, code, codeHash, params, debug, time, runMode) + cacheProgram(db, moduleHash, program, address, code, codeHash, params, debug, time, runCtx) } else { - evictProgram(db, moduleHash, program.version, debug, runMode, expired) + evictProgram(db, moduleHash, program.version, debug, runCtx, expired) } program.cached = cache return p.setProgram(codeHash, program) diff --git a/arbos/programs/testcompile.go b/arbos/programs/testcompile.go index 84963477b8..dd08066157 100644 --- a/arbos/programs/testcompile.go +++ b/arbos/programs/testcompile.go @@ -25,6 +25,7 @@ import ( "fmt" "os" "runtime" + "time" "github.com/ethereum/go-ethereum/core/rawdb" ) @@ -41,41 +42,37 @@ func Wat2Wasm(wat []byte) ([]byte, error) { return rustBytesIntoBytes(output), nil } -func testCompileArch(store bool) error { +func testCompileArch(store bool, cranelift bool) error { localTarget := rawdb.LocalTarget() nativeArm64 := localTarget == rawdb.TargetArm64 nativeAmd64 := localTarget == rawdb.TargetAmd64 + timeout := time.Minute - arm64CompileName := []byte(rawdb.TargetArm64) - amd64CompileName := []byte(rawdb.TargetAmd64) - - arm64TargetString := []byte(DefaultTargetDescriptionArm) - amd64TargetString := []byte(DefaultTargetDescriptionX86) - - output := &rustBytes{} - + nameSuffix := ".bin" + if cranelift { + nameSuffix = "_cranelift.bin" + } _, err := fmt.Print("starting test.. native arm? ", nativeArm64, " amd? ", nativeAmd64, " GOARCH/GOOS: ", runtime.GOARCH+"/"+runtime.GOOS, "\n") if err != nil { return err } - status := C.stylus_target_set(goSlice(arm64CompileName), - goSlice(arm64TargetString), - output, - cbool(nativeArm64)) - - if status != 0 { - return fmt.Errorf("failed setting compilation target arm: %v", string(rustBytesIntoBytes(output))) + err = SetTarget(rawdb.TargetArm64, DefaultTargetDescriptionArm, nativeArm64) + if err != nil { + return err } - status = C.stylus_target_set(goSlice(amd64CompileName), - goSlice(amd64TargetString), - output, - cbool(nativeAmd64)) + err = SetTarget(rawdb.TargetAmd64, DefaultTargetDescriptionX86, nativeAmd64) + if err != nil { + return err + } - if status != 0 { - return fmt.Errorf("failed setting compilation target amd: %v", string(rustBytesIntoBytes(output))) + if !(nativeArm64 || nativeAmd64) { + err = SetTarget(localTarget, "", true) + if err != nil { + return err + } } source, err := os.ReadFile("../../arbitrator/stylus/tests/add.wat") @@ -99,26 +96,15 @@ func testCompileArch(store bool) error { } } - status = C.stylus_compile( - goSlice(wasm), - u16(1), - cbool(true), - goSlice([]byte("booga")), - output, - ) - if status == 0 { - return fmt.Errorf("succeeded compiling non-existent arch: %v", string(rustBytesIntoBytes(output))) + _, err = compileNative(wasm, 2, true, "booga", false, timeout) + if err == nil { + return fmt.Errorf("succeeded compiling non-existent arch: %w", err) } - status = C.stylus_compile( - goSlice(wasm), - u16(1), - cbool(true), - goSlice([]byte{}), - output, - ) - if status != 0 { - return fmt.Errorf("failed compiling native: %v", string(rustBytesIntoBytes(output))) + outBytes, err := compileNative(wasm, 1, true, localTarget, false, timeout) + + if err != nil { + return fmt.Errorf("failed compiling native: %w", err) } if store && !nativeAmd64 && !nativeArm64 { _, err := fmt.Printf("writing host file\n") @@ -126,21 +112,16 @@ func testCompileArch(store bool) error { return err } - err = os.WriteFile("../../target/testdata/host.bin", rustBytesIntoBytes(output), 0644) + err = os.WriteFile("../../target/testdata/host"+nameSuffix, outBytes, 0644) if err != nil { return err } } - status = C.stylus_compile( - goSlice(wasm), - u16(1), - cbool(true), - goSlice(arm64CompileName), - output, - ) - if status != 0 { - return fmt.Errorf("failed compiling arm: %v", string(rustBytesIntoBytes(output))) + outBytes, err = compileNative(wasm, 1, true, rawdb.TargetArm64, false, timeout) + + if err != nil { + return fmt.Errorf("failed compiling arm: %w", err) } if store { _, err := fmt.Printf("writing arm file\n") @@ -148,21 +129,16 @@ func testCompileArch(store bool) error { return err } - err = os.WriteFile("../../target/testdata/arm64.bin", rustBytesIntoBytes(output), 0644) + err = os.WriteFile("../../target/testdata/arm64"+nameSuffix, outBytes, 0644) if err != nil { return err } } - status = C.stylus_compile( - goSlice(wasm), - u16(1), - cbool(true), - goSlice(amd64CompileName), - output, - ) - if status != 0 { - return fmt.Errorf("failed compiling amd: %v", string(rustBytesIntoBytes(output))) + outBytes, err = compileNative(wasm, 1, true, rawdb.TargetAmd64, false, timeout) + + if err != nil { + return fmt.Errorf("failed compiling amd: %w", err) } if store { _, err := fmt.Printf("writing amd64 file\n") @@ -170,7 +146,7 @@ func testCompileArch(store bool) error { return err } - err = os.WriteFile("../../target/testdata/amd64.bin", rustBytesIntoBytes(output), 0644) + err = os.WriteFile("../../target/testdata/amd64"+nameSuffix, outBytes, 0644) if err != nil { return err } @@ -201,16 +177,7 @@ func resetNativeTarget() error { return nil } -func testCompileLoad() error { - filePath := "../../target/testdata/host.bin" - localTarget := rawdb.LocalTarget() - if localTarget == rawdb.TargetArm64 { - filePath = "../../target/testdata/arm64.bin" - } - if localTarget == rawdb.TargetAmd64 { - filePath = "../../target/testdata/amd64.bin" - } - +func testCompileLoadFor(filePath string) error { _, err := fmt.Print("starting load test. FilePath: ", filePath, " GOARCH/GOOS: ", runtime.GOARCH+"/"+runtime.GOOS, "\n") if err != nil { return err @@ -267,3 +234,19 @@ func testCompileLoad() error { return err } + +func testCompileLoad() error { + filePathStart := "../../target/testdata/host" + localTarget := rawdb.LocalTarget() + if localTarget == rawdb.TargetArm64 { + filePathStart = "../../target/testdata/arm64" + } + if localTarget == rawdb.TargetAmd64 { + filePathStart = "../../target/testdata/amd64" + } + err := testCompileLoadFor(filePathStart + ".bin") + if err != nil { + return err + } + return testCompileLoadFor(filePathStart + "_cranelift.bin") +} diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index f5ccc50713..8e93087ca6 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -12,8 +12,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbos/burn" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" @@ -64,6 +66,7 @@ func activateProgram( arbosVersion uint64, debug bool, burner burn.Burner, + runCtx *core.MessageRunContext, ) (*activationInfo, error) { errBuf := make([]byte, 1024) debugMode := arbmath.BoolToUint32(debug) @@ -98,9 +101,9 @@ func activateProgram( } // stub any non-consensus, Rust-side caching updates -func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runMode core.MessageRunMode) { +func cacheProgram(db vm.StateDB, module common.Hash, program Program, addressForLogging common.Address, code []byte, codeHash common.Hash, params *StylusParams, debug bool, time uint64, runCtx *core.MessageRunContext) { } -func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, mode core.MessageRunMode, forever bool) { +func evictProgram(db vm.StateDB, module common.Hash, version uint16, debug bool, runCtx *core.MessageRunContext, forever bool) { } //go:wasmimport programs new_program @@ -131,8 +134,9 @@ func startProgram(module uint32) uint32 //go:wasmimport programs send_response func sendResponse(req_id uint32) uint32 -func getLocalAsm(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, maxWasmSize uint32, pagelimit uint16, time uint64, debugMode bool, program Program) ([]byte, error) { - return nil, nil +func getCompiledProgram(statedb vm.StateDB, moduleHash common.Hash, addressForLogging common.Address, code []byte, codeHash common.Hash, maxWasmSize uint32, pagelimit uint16, time uint64, debugMode bool, program Program, runCtx *core.MessageRunContext) (map[rawdb.WasmTarget][]byte, error) { + // we need to return asm map with an entry for local target to make checks for local target work + return map[rawdb.WasmTarget][]byte{rawdb.LocalTarget(): {}}, nil } func callProgram( @@ -146,7 +150,7 @@ func callProgram( evmData *EvmData, params *ProgParams, memoryModel *MemoryModel, - _arbos_tag uint32, + runCtx *core.MessageRunContext, ) ([]byte, error) { reqHandler := newApiClosures(interpreter, tracingInfo, scope, memoryModel) gasLeft, retData, err := CallProgramLoop(moduleHash, calldata, scope.Contract.Gas, evmData, params, reqHandler) diff --git a/arbos/programs/wasmstorehelper.go b/arbos/programs/wasmstorehelper.go index dc524fe598..c5489010bd 100644 --- a/arbos/programs/wasmstorehelper.go +++ b/arbos/programs/wasmstorehelper.go @@ -16,7 +16,7 @@ import ( ) // SaveActiveProgramToWasmStore is used to save active stylus programs to wasm store during rebuilding -func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash common.Hash, code []byte, time uint64, debugMode bool, rebuildingStartBlockTime uint64) error { +func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash common.Hash, code []byte, time uint64, debugMode bool, rebuildingStartBlockTime uint64, targets []rawdb.WasmTarget) error { progParams, err := p.Params() if err != nil { return err @@ -43,10 +43,12 @@ func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash return err } - targets := statedb.Database().WasmTargets() + _, missingTargets, err := statedb.ActivatedAsmMap(targets, moduleHash) + if err != nil { + return err + } // If already in wasm store then return early - _, err = statedb.TryGetActivatedAsmMap(targets, moduleHash) - if err == nil { + if len(missingTargets) == 0 { return nil } @@ -63,13 +65,14 @@ func (p Programs) SaveActiveProgramToWasmStore(statedb *state.StateDB, codeHash // We know program is activated, so it must be in correct version and not use too much memory // Empty program address is supplied because we dont have access to this during rebuilding of wasm store moduleActivationMandatory := false - info, asmMap, err := activateProgramInternal(common.Address{}, codeHash, wasm, progParams.PageLimit, program.version, zeroArbosVersion, debugMode, &zeroGas, targets, moduleActivationMandatory) + // recompile only missing targets + info, asmMap, err := activateProgramInternal(common.Address{}, codeHash, wasm, progParams.PageLimit, program.version, zeroArbosVersion, debugMode, &zeroGas, missingTargets, moduleActivationMandatory) if err != nil { log.Error("failed to reactivate program while rebuilding wasm store", "expected moduleHash", moduleHash, "err", err) return fmt.Errorf("failed to reactivate program while rebuilding wasm store: %w", err) } - if info.moduleHash != moduleHash { + if info != nil && info.moduleHash != moduleHash { log.Error("failed to reactivate program while rebuilding wasm store", "expected moduleHash", moduleHash, "got", info.moduleHash) return fmt.Errorf("failed to reactivate program while rebuilding wasm store, expected ModuleHash: %v", moduleHash) } diff --git a/arbos/storage/storage_test.go b/arbos/storage/storage_test.go index dd2c40b8f0..750f257d48 100644 --- a/arbos/storage/storage_test.go +++ b/arbos/storage/storage_test.go @@ -116,14 +116,14 @@ func TestOpenCachedSubStorage(t *testing.T) { j := i % len(subSpaceIDs) subSpaceID, expectedKey := subSpaceIDs[j], expectedKeys[j] wg.Add(1) - go func() { + go func(subSpaceID []byte, expectedKey []byte) { defer wg.Done() <-start ss := s.OpenCachedSubStorage(subSpaceID) if !bytes.Equal(ss.storageKey, expectedKey) { errs <- fmt.Errorf("unexpected storage key, want: %v, have: %v", expectedKey, ss.storageKey) } - }() + }(subSpaceID, expectedKey) } close(start) wg.Wait() @@ -152,14 +152,14 @@ func TestMapAddressCache(t *testing.T) { j := i % len(keys) key, expected := keys[j], expectedMapped[j] wg.Add(1) - go func() { + go func(key common.Hash, expected common.Hash) { defer wg.Done() <-start mapped := s.mapAddress(key) if !bytes.Equal(mapped.Bytes(), expected.Bytes()) { errs <- fmt.Errorf("unexpected storage key, want: %v, have: %v", expected, mapped) } - }() + }(key, expected) } close(start) wg.Wait() diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index e3d1a42374..f59a7ab846 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -125,7 +125,7 @@ func (p *TxProcessor) ExecuteWASM(scope *vm.ScopeContext, input []byte, interpre tracingInfo, input, reentrant, - p.RunMode(), + p.RunContext(), ) } @@ -410,8 +410,8 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r return false, 0, nil, nil } -func GetPosterGas(state *arbosState.ArbosState, baseFee *big.Int, runMode core.MessageRunMode, posterCost *big.Int) uint64 { - if runMode == core.MessageGasEstimationMode { +func GetPosterGas(state *arbosState.ArbosState, baseFee *big.Int, runCtx *core.MessageRunContext, posterCost *big.Int) uint64 { + if runCtx.IsGasEstimation() { // Suggest the amount of gas needed for a given amount of ETH is higher in case of congestion. // This will help the user pad the total they'll pay in case the price rises a bit. // Note, reducing the poster cost will increase share the network fee gets, not reduce the total. @@ -446,13 +446,13 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err } var poster common.Address - if !p.msg.TxRunMode.ExecutedOnChain() { + if !p.msg.TxRunContext.IsExecutedOnChain() { poster = l1pricing.BatchPosterAddress } else { poster = p.evm.Context.Coinbase } - if p.msg.TxRunMode.ExecutedOnChain() { + if p.msg.TxRunContext.IsExecutedOnChain() { p.msg.SkipL1Charging = false } if basefee.Sign() > 0 && !p.msg.SkipL1Charging { @@ -467,7 +467,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err if calldataUnits > 0 { p.state.Restrict(p.state.L1PricingState().AddToUnitsSinceUpdate(calldataUnits)) } - p.posterGas = GetPosterGas(p.state, basefee, p.msg.TxRunMode, posterCost) + p.posterGas = GetPosterGas(p.state, basefee, p.msg.TxRunContext, posterCost) p.PosterFee = arbmath.BigMulByUint(basefee, p.posterGas) // round down gasNeededToStartEVM = p.posterGas } @@ -478,7 +478,7 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err } *gasRemaining -= gasNeededToStartEVM - if p.msg.TxRunMode != core.MessageEthcallMode { + if !p.msg.TxRunContext.IsEthcall() { // If this is a real tx, limit the amount of computed based on the gas pool. // We do this by charging extra gas, and then refunding it later. gasAvailable, _ := p.state.L2PricingState().PerBlockGasLimit() @@ -490,8 +490,8 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err return tipReceipient, nil } -func (p *TxProcessor) RunMode() core.MessageRunMode { - return p.msg.TxRunMode +func (p *TxProcessor) RunContext() *core.MessageRunContext { + return p.msg.TxRunContext } func (p *TxProcessor) NonrefundableGas() uint64 { @@ -501,7 +501,8 @@ func (p *TxProcessor) NonrefundableGas() uint64 { return p.posterGas } -func (p *TxProcessor) ForceRefundGas() uint64 { +func (p *TxProcessor) HeldGas() uint64 { + // Gas held back to limit computation, Must be refunded as soon as computation is complete. return p.computeHoldGas } @@ -519,7 +520,7 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { if underlyingTx != nil && underlyingTx.Type() == types.ArbitrumRetryTxType { inner, _ := underlyingTx.GetInner().(*types.ArbitrumRetryTx) effectiveBaseFee := inner.GasFeeCap - if p.msg.TxRunMode.ExecutedOnChain() && !arbmath.BigEquals(effectiveBaseFee, p.evm.Context.BaseFee) { + if p.msg.TxRunContext.IsExecutedOnChain() && !arbmath.BigEquals(effectiveBaseFee, p.evm.Context.BaseFee) { log.Error( "ArbitrumRetryTx GasFeeCap doesn't match basefee in commit mode", "txHash", underlyingTx.Hash(), @@ -782,8 +783,7 @@ func (p *TxProcessor) MsgIsNonMutating() bool { if p.msg == nil { return false } - mode := p.msg.TxRunMode - return mode == core.MessageGasEstimationMode || mode == core.MessageEthcallMode + return p.msg.TxRunContext.IsNonMutating() } func (p *TxProcessor) IsCalldataPricingIncreaseEnabled() bool { diff --git a/arbos/util/storage_cache.go b/arbos/util/storage_cache.go index 5e366908e5..3a52f4f947 100644 --- a/arbos/util/storage_cache.go +++ b/arbos/util/storage_cache.go @@ -60,7 +60,7 @@ func (s *storageCache) Flush() []storageCacheStores { stores := []storageCacheStores{} for key, entry := range s.cache { if entry.dirty() { - v := entry.Value // Create new var to avoid alliasing + v := entry.Value // Create new var to avoid aliasing entry.Known = &v s.cache[key] = entry stores = append(stores, storageCacheStores{ diff --git a/arbstate/inbox.go b/arbstate/inbox.go index 80de62b136..fe0ea5d903 100644 --- a/arbstate/inbox.go +++ b/arbstate/inbox.go @@ -37,30 +37,30 @@ type InboxBackend interface { ReadDelayedInbox(seqNum uint64) (*arbostypes.L1IncomingMessage, error) } -type sequencerMessage struct { - minTimestamp uint64 - maxTimestamp uint64 - minL1Block uint64 - maxL1Block uint64 - afterDelayedMessages uint64 - segments [][]byte +type SequencerMessage struct { + MinTimestamp uint64 + MaxTimestamp uint64 + MinL1Block uint64 + MaxL1Block uint64 + AfterDelayedMessages uint64 + Segments [][]byte } const MaxDecompressedLen int = 1024 * 1024 * 16 // 16 MiB const maxZeroheavyDecompressedLen = 101*MaxDecompressedLen/100 + 64 const MaxSegmentsPerSequencerMessage = 100 * 1024 -func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash common.Hash, data []byte, dapReaders []daprovider.Reader, keysetValidationMode daprovider.KeysetValidationMode) (*sequencerMessage, error) { +func ParseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash common.Hash, data []byte, dapReaders []daprovider.Reader, keysetValidationMode daprovider.KeysetValidationMode) (*SequencerMessage, error) { if len(data) < 40 { return nil, errors.New("sequencer message missing L1 header") } - parsedMsg := &sequencerMessage{ - minTimestamp: binary.BigEndian.Uint64(data[:8]), - maxTimestamp: binary.BigEndian.Uint64(data[8:16]), - minL1Block: binary.BigEndian.Uint64(data[16:24]), - maxL1Block: binary.BigEndian.Uint64(data[24:32]), - afterDelayedMessages: binary.BigEndian.Uint64(data[32:40]), - segments: [][]byte{}, + parsedMsg := &SequencerMessage{ + MinTimestamp: binary.BigEndian.Uint64(data[:8]), + MaxTimestamp: binary.BigEndian.Uint64(data[8:16]), + MinL1Block: binary.BigEndian.Uint64(data[16:24]), + MaxL1Block: binary.BigEndian.Uint64(data[24:32]), + AfterDelayedMessages: binary.BigEndian.Uint64(data[32:40]), + Segments: [][]byte{}, } payload := data[40:] @@ -106,6 +106,8 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash if !foundDA { if daprovider.IsDASMessageHeaderByte(payload[0]) { log.Error("No DAS Reader configured, but sequencer message found with DAS header") + } else if daprovider.IsCelestiaMessageHeaderByte(payload[0]) { + log.Error("No Celestia Reader configured, but sequencer message found with Celestia header") } else if daprovider.IsBlobHashesHeaderByte(payload[0]) { return nil, daprovider.ErrNoBlobReader } @@ -140,11 +142,11 @@ func parseSequencerMessage(ctx context.Context, batchNum uint64, batchBlockHash } break } - if len(parsedMsg.segments) >= MaxSegmentsPerSequencerMessage { + if len(parsedMsg.Segments) >= MaxSegmentsPerSequencerMessage { log.Warn("too many segments in sequence batch") break } - parsedMsg.segments = append(parsedMsg.segments, segment) + parsedMsg.Segments = append(parsedMsg.Segments, segment) } } else { log.Warn("sequencer msg decompression failed", "err", err) @@ -166,7 +168,7 @@ type inboxMultiplexer struct { backend InboxBackend delayedMessagesRead uint64 dapReaders []daprovider.Reader - cachedSequencerMessage *sequencerMessage + cachedSequencerMessage *SequencerMessage cachedSequencerMessageNum uint64 cachedSegmentNum uint64 cachedSegmentTimestamp uint64 @@ -201,7 +203,7 @@ func (r *inboxMultiplexer) Pop(ctx context.Context) (*arbostypes.MessageWithMeta } r.cachedSequencerMessageNum = r.backend.GetSequencerInboxPosition() var err error - r.cachedSequencerMessage, err = parseSequencerMessage(ctx, r.cachedSequencerMessageNum, batchBlockHash, bytes, r.dapReaders, r.keysetValidationMode) + r.cachedSequencerMessage, err = ParseSequencerMessage(ctx, r.cachedSequencerMessageNum, batchBlockHash, bytes, r.dapReaders, r.keysetValidationMode) if err != nil { return nil, err } @@ -225,7 +227,7 @@ func (r *inboxMultiplexer) Pop(ctx context.Context) (*arbostypes.MessageWithMeta func (r *inboxMultiplexer) advanceSequencerMsg() { if r.cachedSequencerMessage != nil { - r.delayedMessagesRead = r.cachedSequencerMessage.afterDelayedMessages + r.delayedMessagesRead = r.cachedSequencerMessage.AfterDelayedMessages } r.backend.SetPositionWithinMessage(0) r.backend.AdvanceSequencerInbox() @@ -244,11 +246,11 @@ func (r *inboxMultiplexer) advanceSubMsg() { func (r *inboxMultiplexer) IsCachedSegementLast() bool { seqMsg := r.cachedSequencerMessage // we issue delayed messages until reaching afterDelayedMessages - if r.delayedMessagesRead < seqMsg.afterDelayedMessages { + if r.delayedMessagesRead < seqMsg.AfterDelayedMessages { return false } - for segmentNum := r.cachedSegmentNum + 1; segmentNum < uint64(len(seqMsg.segments)); segmentNum++ { - segment := seqMsg.segments[segmentNum] + for segmentNum := r.cachedSegmentNum + 1; segmentNum < uint64(len(seqMsg.Segments)); segmentNum++ { + segment := seqMsg.Segments[segmentNum] if len(segment) == 0 { continue } @@ -274,10 +276,10 @@ func (r *inboxMultiplexer) getNextMsg() (*arbostypes.MessageWithMetadata, error) submessageNumber := r.cachedSubMessageNumber var segment []byte for { - if segmentNum >= uint64(len(seqMsg.segments)) { + if segmentNum >= uint64(len(seqMsg.Segments)) { break } - segment = seqMsg.segments[segmentNum] + segment = seqMsg.Segments[segmentNum] if len(segment) == 0 { segmentNum++ continue @@ -308,22 +310,22 @@ func (r *inboxMultiplexer) getNextMsg() (*arbostypes.MessageWithMetadata, error) r.cachedSegmentTimestamp = timestamp r.cachedSegmentBlockNumber = blockNumber r.cachedSubMessageNumber = submessageNumber - if timestamp < seqMsg.minTimestamp { - timestamp = seqMsg.minTimestamp - } else if timestamp > seqMsg.maxTimestamp { - timestamp = seqMsg.maxTimestamp + if timestamp < seqMsg.MinTimestamp { + timestamp = seqMsg.MinTimestamp + } else if timestamp > seqMsg.MaxTimestamp { + timestamp = seqMsg.MaxTimestamp } - if blockNumber < seqMsg.minL1Block { - blockNumber = seqMsg.minL1Block - } else if blockNumber > seqMsg.maxL1Block { - blockNumber = seqMsg.maxL1Block + if blockNumber < seqMsg.MinL1Block { + blockNumber = seqMsg.MinL1Block + } else if blockNumber > seqMsg.MaxL1Block { + blockNumber = seqMsg.MaxL1Block } - if segmentNum >= uint64(len(seqMsg.segments)) { + if segmentNum >= uint64(len(seqMsg.Segments)) { // after end of batch there might be "virtual" delayedMsgSegments - log.Warn("reading virtual delayed message segment", "delayedMessagesRead", r.delayedMessagesRead, "afterDelayedMessages", seqMsg.afterDelayedMessages) + log.Warn("reading virtual delayed message segment", "delayedMessagesRead", r.delayedMessagesRead, "afterDelayedMessages", seqMsg.AfterDelayedMessages) segment = []byte{BatchSegmentKindDelayedMessages} } else { - segment = seqMsg.segments[segmentNum] + segment = seqMsg.Segments[segmentNum] } if len(segment) == 0 { log.Error("empty sequencer message segment", "sequence", r.cachedSegmentNum, "segmentNum", segmentNum) @@ -358,17 +360,17 @@ func (r *inboxMultiplexer) getNextMsg() (*arbostypes.MessageWithMetadata, error) DelayedMessagesRead: r.delayedMessagesRead, } } else if kind == BatchSegmentKindDelayedMessages { - if r.delayedMessagesRead >= seqMsg.afterDelayedMessages { - if segmentNum < uint64(len(seqMsg.segments)) { + if r.delayedMessagesRead >= seqMsg.AfterDelayedMessages { + if segmentNum < uint64(len(seqMsg.Segments)) { log.Warn( "attempt to read past batch delayed message count", "delayedMessagesRead", r.delayedMessagesRead, - "batchAfterDelayedMessages", seqMsg.afterDelayedMessages, + "batchAfterDelayedMessages", seqMsg.AfterDelayedMessages, ) } msg = &arbostypes.MessageWithMetadata{ Message: arbostypes.InvalidL1Message, - DelayedMessagesRead: seqMsg.afterDelayedMessages, + DelayedMessagesRead: seqMsg.AfterDelayedMessages, } } else { delayed, realErr := r.backend.ReadDelayedInbox(r.delayedMessagesRead) diff --git a/arbutil/correspondingl1blocknumber.go b/arbutil/correspondingl1blocknumber.go index 50a4c6e42a..bec6d997c3 100644 --- a/arbutil/correspondingl1blocknumber.go +++ b/arbutil/correspondingl1blocknumber.go @@ -27,7 +27,7 @@ func CorrespondingL1BlockNumber(ctx context.Context, client ParentHeaderFetcher, // #nosec G115 header, err := client.HeaderByNumber(ctx, big.NewInt(int64(parentBlockNumber))) if err != nil { - return 0, fmt.Errorf("error getting L1 block number %d header : %w", parentBlockNumber, err) + return 0, fmt.Errorf("error getting parent block header %d: %w", parentBlockNumber, err) } return ParentHeaderToL1BlockNumber(header), nil } diff --git a/audits/celestia/arbitrum_nitro_celestia_audit_report.pdf b/audits/celestia/arbitrum_nitro_celestia_audit_report.pdf new file mode 100644 index 0000000000..a2fa72183c Binary files /dev/null and b/audits/celestia/arbitrum_nitro_celestia_audit_report.pdf differ diff --git a/bold b/bold deleted file mode 160000 index 301f479f4b..0000000000 --- a/bold +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 301f479f4bb09a6ab77bab324a441e6e83cddf2b diff --git a/bold/README.md b/bold/README.md new file mode 100644 index 0000000000..b40f3953c0 --- /dev/null +++ b/bold/README.md @@ -0,0 +1,63 @@ +# BOLD + +This repository implements Offchain Labs' BOLD (Bounded Liquidity Delay) Protocol: a dispute system to enable permissionless validation of Arbitrum chains. It is an efficient, all-vs-all challenge protocol that enables anyone on Ethereum to challenge invalid rollup state transitions. + +BOLD provides a fixed, upper-bound on challenge confirmations for Arbitrum chains. + +Given state transitions are deterministic, this guarantees only one correct result for any given assertion. An **honest participant** will always win against malicious entities when challenging assertions posted to the settlement chain. + +## Directory Structure + +For our research specification of BOLD, see [BOLDChallengeProtocol.pdf](docs/research-specs/BOLDChallengeProtocol.pdf). + +For our technical deep dive into BOLD, see [TechnicalDeepDive.pdf](docs/research-specs/TechnicalDeepDive.pdf) + +For documentation on the economics of BOLD, see [Economics.pdf](docs/research-specs/Economics.pdf) + +For detailed information on how our code is architected, see [ARCHITECTURE.md](docs/ARCHITECTURE.md). + +``` +api/ + API for monitoring and visualizing challenges +assertions/ + Logic for scanning and posting assertions +chain-abstraction/ + High-level wrappers around Solidity bindings for the Rollup contracts +challenge-manager/ + All logic related to challenging, managing challenges +containers/ + Data structures used in the repository, including FSMs +contracts/ + All Rollup / challenge smart contracts +docs/ + Diagrams and architecture +layer2-state-provider/ + Interface to request state and proofs from an L2 backend +math/ + Utilities for challenge calculations +runtime/ + Tools for managing function lifecycles +state-commitments/ + Proofs, history commitments, and Merkleizations +testing/ + All non-production code +third_party/ + Build artifacts for dependencies +time/ + Abstract time utilities +``` + +## Research Specification + +BOLD has an accompanying research specification that outlines the foundations of the protocol in more detail, found under [docs/research-specs/BOLDChallengeProtocol.pdf](./docs/research-specs/BOLDChallengeProtocol.pdf). + + +## Security Audit + +BOLD has been audited by [Trail of Bits](https://www.trailofbits.com/) as of commit [60f97068c12cca73c45117a05ba1922f949fd6ae](https://github.com/OffchainLabs/bold/commit/60f97068c12cca73c45117a05ba1922f949fd6ae), and a more updated audit is being completed, to be finalized in the coming few weeks. + +The audit report can be found under [docs/audits/TrailOfBitsAudit](./docs/audits/TrailOfBitsAudit.pdf). + +## Credits + +Huge credits on this project go to those who created BOLD and were involved in its implementation: Ed Felten, Yafah Edelman, Chris Buckland, Harry Ng, Lee Bousfield, Terence Tsao, Mario Alvarez, Preston Van Loon, Mahimna Kelkar, Aman Sanghi, Daniel Goldman, Raul Jordan, Henry Arneson, Derek Lee, Victor Shoup diff --git a/bold/api/backend/backend.go b/bold/api/backend/backend.go new file mode 100644 index 0000000000..824b3ef6c8 --- /dev/null +++ b/bold/api/backend/backend.go @@ -0,0 +1,288 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package backend handles the business logic for API data fetching +// for BOLD challenge information. It is meant to be fairly abstract and +// well-tested. +package backend + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/api" + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + watcher "github.com/offchainlabs/bold/challenge-manager/chain-watcher" + edgetracker "github.com/offchainlabs/bold/challenge-manager/edge-tracker" + "github.com/offchainlabs/bold/containers/option" +) + +type BusinessLogicProvider interface { + GetAssertions(ctx context.Context, opts ...db.AssertionOption) ([]*api.JsonAssertion, error) + GetCollectMachineHashes(ctx context.Context, opts ...db.CollectMachineHashesOption) ([]*api.JsonCollectMachineHashes, error) + GetEdges(ctx context.Context, opts ...db.EdgeOption) ([]*api.JsonEdge, error) + GetTrackedRoyalEdges(ctx context.Context) ([]*api.JsonEdgesByChallengedAssertion, error) + GetMiniStakes(ctx context.Context, assertionHash protocol.AssertionHash, opts ...db.EdgeOption) (*api.JsonMiniStakes, error) +} + +type EdgeTrackerFetcher interface { + GetEdgeTracker(edgeId protocol.EdgeId) option.Option[*edgetracker.Tracker] +} + +type Backend struct { + db db.ReadUpdateDatabase + chainDataFetcher protocol.AssertionChain + chainWatcher *watcher.Watcher + trackerFetcher EdgeTrackerFetcher +} + +func NewBackend( + db db.ReadUpdateDatabase, + chainDataFetcher protocol.AssertionChain, + chainWatcher *watcher.Watcher, +) *Backend { + return &Backend{ + db: db, + chainDataFetcher: chainDataFetcher, + chainWatcher: chainWatcher, + trackerFetcher: nil, // Must be set after construction. + } +} + +// SetEdgeTrackerFetcher sets the edge tracker fetcher for the backend. +// +// This method must be called to inject this dependency before starting the +// backend. +func (b *Backend) SetEdgeTrackerFetcher(fetcher EdgeTrackerFetcher) { + b.trackerFetcher = fetcher +} + +func (b *Backend) GetAssertions(ctx context.Context, opts ...db.AssertionOption) ([]*api.JsonAssertion, error) { + query := &db.AssertionQuery{} + for _, o := range opts { + o(query) + } + assertions, err := b.db.GetAssertions(opts...) + if err != nil { + return nil, err + } + if query.ShouldForceUpdate() { + opts := &bind.CallOpts{Context: ctx} + for _, a := range assertions { + fetchedAssertion, err := b.chainDataFetcher.GetAssertion(ctx, opts, protocol.AssertionHash{Hash: a.Hash}) + if err != nil { + return nil, err + } + status, err := fetchedAssertion.Status(ctx, opts) + if err != nil { + return nil, err + } + isFirstChild, err := fetchedAssertion.IsFirstChild(ctx, opts) + if err != nil { + return nil, err + } + firstChildBlock, err := fetchedAssertion.FirstChildCreationBlock(ctx, opts) + if err != nil { + return nil, err + } + secondChildBlock, err := fetchedAssertion.SecondChildCreationBlock(ctx, opts) + if err != nil { + return nil, err + } + a.Status = status.String() + a.IsFirstChild = isFirstChild + a.FirstChildBlock = &firstChildBlock + a.SecondChildBlock = &secondChildBlock + } + if err := b.db.UpdateAssertions(assertions); err != nil { + return nil, err + } + } + return assertions, nil +} + +func (b *Backend) GetCollectMachineHashes(ctx context.Context, opts ...db.CollectMachineHashesOption) ([]*api.JsonCollectMachineHashes, error) { + query := &db.CollectMachineHashesQuery{} + for _, o := range opts { + o(query) + } + collectMachineHashes, err := b.db.GetCollectMachineHashes(opts...) + for _, cmh := range collectMachineHashes { + if cmh.RawStepHeights != "" { + stepHeightsStr := strings.Split(cmh.RawStepHeights, ",") + stepHeights := make([]uint64, len(stepHeightsStr)) + for i, stepHeightStr := range stepHeightsStr { + stepHeight := 0 + if stepHeightStr == "" { + continue + } + stepHeight, err = strconv.Atoi(stepHeightStr) + if err != nil { + return nil, fmt.Errorf("could not parse step height %s: %w", stepHeightStr, err) + } + stepHeights[i], err = safecast.ToUint64(stepHeight) + if err != nil { + return nil, fmt.Errorf("could not cast step height %d to uint64: %w", stepHeight, err) + } + } + cmh.StepHeights = stepHeights + } + } + if err != nil { + return nil, err + } + return collectMachineHashes, nil +} + +func (b *Backend) GetEdges(ctx context.Context, opts ...db.EdgeOption) ([]*api.JsonEdge, error) { + query := &db.EdgeQuery{} + for _, o := range opts { + o(query) + } + edges, err := b.db.GetEdges(opts...) + if err != nil { + return nil, err + } + if query.ShouldForceUpdate() { + chalManager := b.chainDataFetcher.SpecChallengeManager() + for _, e := range edges { + edgeOpt, err := chalManager.GetEdge(ctx, protocol.EdgeId{Hash: e.Id}) + if err != nil { + return nil, err + } + if edgeOpt.IsNone() { + return nil, fmt.Errorf("edge with id %#x was nil onchain", e.Id) + } + edge := edgeOpt.Unwrap() + status, err := edge.Status(ctx) + if err != nil { + return nil, err + } + hasRival, err := edge.HasRival(ctx) + if err != nil { + return nil, err + } + hasLengthOneRival, err := edge.HasLengthOneRival(ctx) + if err != nil { + return nil, err + } + timeUnrivaled, err := edge.TimeUnrivaled(ctx) + if err != nil { + return nil, err + } + var lowerChildId, upperChildId common.Hash + var hasChildren bool + lowerChild, err := edge.LowerChild(ctx) + if err != nil { + return nil, err + } + upperChild, err := edge.UpperChild(ctx) + if err != nil { + return nil, err + } + assertionHash, err := edge.AssertionHash(ctx) + if err != nil { + return nil, err + } + if lowerChild.IsSome() { + hasChildren = true + lowerChildId = lowerChild.Unwrap().Hash + } + if upperChild.IsSome() { + hasChildren = true + upperChildId = upperChild.Unwrap().Hash + } + e.Status = status.String() + e.HasRival = hasRival + e.HasLengthOneRival = hasLengthOneRival + e.LowerChildId = lowerChildId + e.UpperChildId = upperChildId + e.HasChildren = hasChildren + e.TimeUnrivaled = timeUnrivaled + isRoyal := b.chainWatcher.IsRoyal(assertionHash, edge.Id()) + if isRoyal { + inheritedTimer, err := b.chainWatcher.InheritedTimerForEdge(ctx, edge.Id()) + if err != nil { + return nil, err + } + e.InheritedTimer = uint64(inheritedTimer) + } + e.IsRoyal = isRoyal + trackerOpt := b.trackerFetcher.GetEdgeTracker(edge.Id()) + if trackerOpt.IsSome() { + fsmState := trackerOpt.Unwrap().FSMSummary() + e.FSMState = fsmState.CurrentState + if fsmState.Error != nil { + e.FSMError = fsmState.Error.Error() + } + } + } + if err := b.db.UpdateEdges(edges); err != nil { + return nil, err + } + } + return edges, nil +} + +func (b *Backend) GetMiniStakes(ctx context.Context, assertionHash protocol.AssertionHash, opts ...db.EdgeOption) (*api.JsonMiniStakes, error) { + edgeOpts := opts + edgeOpts = append( + edgeOpts, + db.WithMiniStakerDefined(), + db.WithEdgeAssertionHash(assertionHash), + db.WithRootEdges(), + ) + edges, err := b.db.GetEdges(edgeOpts...) + if err != nil { + return nil, err + } + stakeInfo := &api.JsonMiniStakes{ + ChallengedAssertionHash: assertionHash.Hash, + StakesByLvlAndOrigin: make(map[protocol.ChallengeLevel][]*api.JsonMiniStakeInfo), + } + edgesByOriginId := make(map[common.Hash][]*api.JsonEdge) + for _, e := range edges { + edgesByOriginId[e.OriginId] = append(edgesByOriginId[e.OriginId], e) + } + for originId, originDefinedEdges := range edgesByOriginId { + lvl := protocol.ChallengeLevel(originDefinedEdges[0].ChallengeLevel) + if stakeInfo.StakesByLvlAndOrigin[lvl] == nil { + stakeInfo.StakesByLvlAndOrigin[lvl] = make([]*api.JsonMiniStakeInfo, 0) + } + info := &api.JsonMiniStakeInfo{ + ChallengeOriginId: originId, + StakerAddresses: []common.Address{}, + NumberOfMiniStakes: 0, + } + for _, e := range originDefinedEdges { + info.StakerAddresses = append(info.StakerAddresses, e.MiniStaker) + info.NumberOfMiniStakes += 1 + } + stakeInfo.StakesByLvlAndOrigin[lvl] = append(stakeInfo.StakesByLvlAndOrigin[lvl], info) + } + return stakeInfo, nil +} + +func (b *Backend) GetTrackedRoyalEdges(ctx context.Context) ([]*api.JsonEdgesByChallengedAssertion, error) { + resp, err := b.chainWatcher.GetRoyalEdges(ctx) + if err != nil { + return nil, err + } + edgesByAssertion := make([]*api.JsonEdgesByChallengedAssertion, 0) + for assertionHash, e := range resp { + edgesByAssertion = append(edgesByAssertion, &api.JsonEdgesByChallengedAssertion{ + AssertionHash: assertionHash.Hash, + RoyalEdges: e, + }) + } + return edgesByAssertion, nil +} diff --git a/bold/api/db/db.go b/bold/api/db/db.go new file mode 100644 index 0000000000..e4d5320bcb --- /dev/null +++ b/bold/api/db/db.go @@ -0,0 +1,972 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package db handles the interface to an underlying database of BOLD data +// for easy querying of information used by the BOLD API. +package db + +import ( + "fmt" + "os" + "strings" + "sync" + + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/api" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/state-commitments/history" +) + +type Database interface { + ReadUpdateDatabase + InsertEdges(edges []*api.JsonEdge) error + InsertEdge(edge *api.JsonEdge) error + InsertAssertions(assertions []*api.JsonAssertion) error + InsertAssertion(assertion *api.JsonAssertion) error + InsertCollectMachineHash(collectMachineHashes *api.JsonCollectMachineHashes) error +} + +type ReadUpdateDatabase interface { + ReadOnlyDatabase + UpdateAssertions(assertion []*api.JsonAssertion) error + UpdateEdges(edge []*api.JsonEdge) error + UpdateCollectMachineHash(collectMachineHashes *api.JsonCollectMachineHashes) error +} + +type ReadOnlyDatabase interface { + GetAssertions(opts ...AssertionOption) ([]*api.JsonAssertion, error) + GetCollectMachineHashes(opts ...CollectMachineHashesOption) ([]*api.JsonCollectMachineHashes, error) + GetChallengedAssertions(opts ...AssertionOption) ([]*api.JsonAssertion, error) + GetEdges(opts ...EdgeOption) ([]*api.JsonEdge, error) +} + +type SqliteDatabase struct { + sqlDB *sqlx.DB + lock sync.Mutex + currentTableVersion int +} + +func NewDatabase(path string) (*SqliteDatabase, error) { + //#nosec G304 + if _, err := os.Stat(path); err != nil { + _, err = os.Create(path) + if err != nil { + return nil, err + } + } + db, err := sqlx.Open("sqlite3", path) + if err != nil { + return nil, err + } + err = dbInit(db, schemaList) + if err != nil { + return nil, err + } + return &SqliteDatabase{ + sqlDB: db, + currentTableVersion: -1, + }, nil +} + +func dbInit(db *sqlx.DB, schemaList []string) error { + version, err := fetchVersion(db) + if err != nil { + return err + } + for index, schema := range schemaList { + // If the current version is less than the version of the schema, update the database + if index+1 > version { + err = executeSchema(db, schema, index+1) + if err != nil { + return err + } + } + } + return nil +} + +func fetchVersion(db *sqlx.DB) (int, error) { + flagValue := make([]int, 0) + // Fetch the current version of the database + err := db.Select(&flagValue, "SELECT FlagValue FROM Flags WHERE FlagName = 'CurrentVersion'") + if err != nil { + if !strings.Contains(err.Error(), "no such table") { + return 0, err + } + // If the table doesn't exist, create it + _, err = db.Exec(flagSetup) + if err != nil { + return 0, err + } + // Fetch the current version of the database + err = db.Select(&flagValue, "SELECT FlagValue FROM Flags WHERE FlagName = 'CurrentVersion'") + if err != nil { + return 0, err + } + } + if len(flagValue) > 0 { + return flagValue[0], nil + } else { + return 0, fmt.Errorf("no version found") + } +} + +func executeSchema(db *sqlx.DB, schema string, version int) error { + // Begin a transaction, so that we update the version and execute the schema atomically + tx, err := db.Beginx() + if err != nil { + return err + } + + // Execute the schema + _, err = tx.Exec(schema) + if err != nil { + return err + } + // Update the version of the database + _, err = tx.Exec(fmt.Sprintf("UPDATE Flags SET FlagValue = %d WHERE FlagName = 'CurrentVersion'", version)) + if err != nil { + return err + } + return tx.Commit() +} + +type AssertionQuery struct { + filters []string + args []interface{} + limit int + offset int + orderBy string + withChallenge bool + fromCreationBlock option.Option[uint64] + toCreationBlock option.Option[uint64] + forceUpdate bool +} + +func NewAssertionQuery(opts ...AssertionOption) *AssertionQuery { + query := &AssertionQuery{ + fromCreationBlock: option.None[uint64](), + toCreationBlock: option.None[uint64](), + } + for _, opt := range opts { + opt(query) + } + return query +} + +func (q *AssertionQuery) ShouldForceUpdate() bool { + return q.forceUpdate +} + +type AssertionOption func(*AssertionQuery) + +func WithAssertionForceUpdate() AssertionOption { + return func(q *AssertionQuery) { + q.forceUpdate = true + } +} +func WithChallenge() AssertionOption { + return func(q *AssertionQuery) { + q.withChallenge = true + } +} +func WithAssertionHash(hash protocol.AssertionHash) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "Hash = ?") + q.args = append(q.args, hash.Hash) + } +} +func WithConfirmPeriodBlocks(confirmPeriodBlocks uint64) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "ConfirmPeriodBlocks = ?") + q.args = append(q.args, confirmPeriodBlocks) + } +} +func WithRequiredStake(requiredStake string) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "RequiredStake = ?") + q.args = append(q.args, requiredStake) + } +} +func WithParentAssertionHash(hash protocol.AssertionHash) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "ParentAssertionHash = ?") + q.args = append(q.args, hash.Hash) + } +} +func WithInboxMaxCount(inboxMaxCount string) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "InboxMaxCount = ?") + q.args = append(q.args, inboxMaxCount) + } +} +func WithAfterInboxBatchAcc(afterInboxBatchAcc common.Hash) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "AfterInboxBatchAcc = ?") + q.args = append(q.args, afterInboxBatchAcc) + } +} +func WithWasmModuleRoot(wasmModuleRoot common.Hash) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "WasmModuleRoot = ?") + q.args = append(q.args, wasmModuleRoot) + } +} +func WithChallengeManager(challengeManager common.Address) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "ChallengeManager = ?") + q.args = append(q.args, challengeManager) + } +} +func WithTransactionHash(hash common.Hash) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "TransactionHash = ?") + q.args = append(q.args, hash) + } +} +func WithBeforeState(state *protocol.ExecutionState) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "BeforeStateBlockHash = ?") + q.args = append(q.args, state.GlobalState.BlockHash) + q.filters = append(q.filters, "BeforeStateSendRoot = ?") + q.args = append(q.args, state.GlobalState.SendRoot) + q.filters = append(q.filters, "BeforeStateBatch = ?") + q.args = append(q.args, state.GlobalState.Batch) + q.filters = append(q.filters, "BeforeStatePosInBatch = ?") + q.args = append(q.args, state.GlobalState.PosInBatch) + q.filters = append(q.filters, "BeforeStateMachineStatus = ?") + q.args = append(q.args, state.MachineStatus) + } +} +func WithAfterState(state *protocol.ExecutionState) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "AfterStateBlockHash = ?") + q.args = append(q.args, state.GlobalState.BlockHash) + q.filters = append(q.filters, "AfterStateSendRoot = ?") + q.args = append(q.args, state.GlobalState.SendRoot) + q.filters = append(q.filters, "AfterStateBatch = ?") + q.args = append(q.args, state.GlobalState.Batch) + q.filters = append(q.filters, "AfterStatePosInBatch = ?") + q.args = append(q.args, state.GlobalState.PosInBatch) + q.filters = append(q.filters, "AfterStateMachineStatus = ?") + q.args = append(q.args, state.MachineStatus) + } +} +func WithFirstChildBlock(n uint64) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "FirstChildBlock = ?") + q.args = append(q.args, n) + } +} +func WithSecondChildBlock(n uint64) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "SecondChildBlock = ?") + q.args = append(q.args, n) + } +} +func WithIsFirstChild() AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "IsFirstChild = true") + } +} +func WithAssertionStatus(status protocol.AssertionStatus) AssertionOption { + return func(q *AssertionQuery) { + q.filters = append(q.filters, "Status = ?") + q.args = append(q.args, status.String()) + } +} +func FromAssertionCreationBlock(n uint64) AssertionOption { + return func(q *AssertionQuery) { + q.fromCreationBlock = option.Some(n) + } +} +func ToAssertionCreationBlock(n uint64) AssertionOption { + return func(q *AssertionQuery) { + q.toCreationBlock = option.Some(n) + } +} +func WithAssertionLimit(limit int) AssertionOption { + return func(q *AssertionQuery) { + q.limit = limit + } +} +func WithAssertionOffset(offset int) AssertionOption { + return func(q *AssertionQuery) { + q.offset = offset + } +} +func WithAssertionOrderBy(orderBy string) AssertionOption { + return func(q *AssertionQuery) { + q.orderBy = orderBy + } +} + +func (q *AssertionQuery) ToSQL() (string, []interface{}) { + baseQuery := "SELECT * FROM Assertions a" + if q.withChallenge { + baseQuery += " INNER JOIN Challenges c ON a.Hash = c.Hash" + } + if q.fromCreationBlock.IsSome() { + q.filters = append(q.filters, "a.CreationBlock >= ?") + q.args = append(q.args, q.fromCreationBlock.Unwrap()) + } + if q.toCreationBlock.IsSome() { + q.filters = append(q.filters, "a.CreationBlock < ?") + q.args = append(q.args, q.toCreationBlock.Unwrap()) + } + if len(q.filters) > 0 { + baseQuery += " WHERE " + strings.Join(q.filters, " AND ") + } + + if q.orderBy != "" { + baseQuery += " ORDER BY " + q.orderBy + } + if q.limit > 0 { + baseQuery += " LIMIT ?" + q.args = append(q.args, q.limit) + } + if q.offset > 0 { + baseQuery += " OFFSET ?" + q.args = append(q.args, q.offset) + } + return baseQuery, q.args +} + +func (d *SqliteDatabase) GetAssertions(opts ...AssertionOption) ([]*api.JsonAssertion, error) { + query := NewAssertionQuery(opts...) + sql, args := query.ToSQL() + assertions := make([]*api.JsonAssertion, 0) + d.lock.Lock() + defer d.lock.Unlock() + err := d.sqlDB.Select(&assertions, sql, args...) + if err != nil { + return nil, err + } + return assertions, nil +} + +func (d *SqliteDatabase) GetCollectMachineHashes(opts ...CollectMachineHashesOption) ([]*api.JsonCollectMachineHashes, error) { + query := NewCollectMachineHashes(opts...) + sql, args := query.ToSQL() + collectMachineHashes := make([]*api.JsonCollectMachineHashes, 0) + d.lock.Lock() + defer d.lock.Unlock() + err := d.sqlDB.Select(&collectMachineHashes, sql, args...) + if err != nil { + return nil, err + } + return collectMachineHashes, nil +} + +func (d *SqliteDatabase) GetChallengedAssertions(opts ...AssertionOption) ([]*api.JsonAssertion, error) { + newOpts := []AssertionOption{ + WithChallenge(), + } + newOpts = append(newOpts, opts...) + return d.GetAssertions(newOpts...) +} + +type EdgeQuery struct { + filters []string + args []interface{} + limit int + offset int + orderBy string + fromCreationBlock option.Option[uint64] + toCreationBlock option.Option[uint64] + forceUpdate bool + onlySubchallenged bool +} + +func (q *EdgeQuery) ShouldForceUpdate() bool { + return q.forceUpdate +} + +func NewEdgeQuery(opts ...EdgeOption) *EdgeQuery { + query := &EdgeQuery{} + for _, opt := range opts { + opt(query) + } + return query +} + +type EdgeOption func(e *EdgeQuery) + +func WithId(id protocol.EdgeId) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "Id = ?") + q.args = append(q.args, id) + } +} +func WithChallengeLevel(level uint8) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "ChallengeLevel = ?") + q.args = append(q.args, level) + } +} +func WithOriginId(originId protocol.OriginId) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "OriginId = ?") + q.args = append(q.args, common.Hash(originId)) + } +} +func WithStartHeight(start uint64) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "StartHeight = ?") + q.args = append(q.args, start) + } +} +func WithEndHeight(end uint64) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "EndHeight = ?") + q.args = append(q.args, end) + } +} +func WithStartHistoryCommitment(startHistory history.History) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "StartHistoryRoot = ?") + q.args = append(q.args, startHistory.Merkle) + q.filters = append(q.filters, "StartHeight = ?") + q.args = append(q.args, startHistory.Height) + } +} +func WithEndHistoryCommitment(endHistory history.History) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "EndHistoryRoot = ?") + q.args = append(q.args, endHistory.Merkle) + q.filters = append(q.filters, "EndHeight = ?") + q.args = append(q.args, endHistory.Height) + } +} +func WithMutualId(mutualId protocol.MutualId) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "MutualId = ?") + q.args = append(q.args, common.Hash(mutualId)) + } +} +func WithClaimId(claimId protocol.ClaimId) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "ClaimId = ?") + q.args = append(q.args, common.Hash(claimId)) + } +} +func HasChildren(x bool) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "HasChildren = ?") + q.args = append(q.args, x) + } +} +func WithLowerChildId(id protocol.EdgeId) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "LowerChildId = ?") + q.args = append(q.args, id.Hash) + } +} +func WithUpperChildId(id protocol.EdgeId) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "UpperChildId = ?") + q.args = append(q.args, id.Hash) + } +} +func WithMiniStaker(staker common.Address) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "MiniStaker = ?") + q.args = append(q.args, staker) + } +} +func WithMiniStakerDefined() EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "MiniStaker != ?") + q.args = append(q.args, common.Address{}) + } +} +func WithEdgeAssertionHash(hash protocol.AssertionHash) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "AssertionHash = ?") + q.args = append(q.args, hash.Hash) + } +} +func WithRival(x bool) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "HasRival = ?") + q.args = append(q.args, x) + } +} +func WithSubchallenge() EdgeOption { + return func(q *EdgeQuery) { + q.onlySubchallenged = true + } +} +func WithEdgeStatus(st protocol.EdgeStatus) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "Status = ?") + q.args = append(q.args, st.String()) + } +} +func WithRoyal(x bool) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "IsRoyal = ?") + q.args = append(q.args, x) + } +} +func WithEdgeForceUpdate() EdgeOption { + return func(q *EdgeQuery) { + q.forceUpdate = true + } +} +func WithRootEdges() EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "ClaimId != ?") + q.args = append(q.args, common.Hash{}) + } +} +func WithInheritedTimerGreaterOrEq(n uint64) EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "InheritedTimer >= ?") + q.args = append(q.args, n) + } +} +func FromEdgeCreationBlock(n uint64) EdgeOption { + return func(q *EdgeQuery) { + q.fromCreationBlock = option.Some(n) + } +} +func ToEdgeCreationBlock(n uint64) EdgeOption { + return func(q *EdgeQuery) { + q.toCreationBlock = option.Some(n) + } +} +func WithLengthOneRival() EdgeOption { + return func(q *EdgeQuery) { + q.filters = append(q.filters, "HasLengthOneRival = true") + } +} +func WithLimit(limit int) EdgeOption { + return func(q *EdgeQuery) { + q.limit = limit + } +} +func WithOffset(offset int) EdgeOption { + return func(q *EdgeQuery) { + q.offset = offset + } +} +func WithOrderBy(orderBy string) EdgeOption { + return func(q *EdgeQuery) { + q.orderBy = orderBy + } +} + +func (q *EdgeQuery) ToSQL() (string, []interface{}) { + baseQuery := "SELECT * FROM Edges e" + if q.onlySubchallenged { + baseQuery += ` INNER JOIN EdgeClaims ec ON e.Id = ec.ClaimId + WHERE ec.RefersTo = 'edge'` + } + if q.fromCreationBlock.IsSome() { + q.filters = append(q.filters, "e.CreatedAtBlock >= ?") + q.args = append(q.args, q.fromCreationBlock.Unwrap()) + } + if q.toCreationBlock.IsSome() { + q.filters = append(q.filters, "e.CreatedAtBlock < ?") + q.args = append(q.args, q.toCreationBlock.Unwrap()) + } + if len(q.filters) > 0 { + if !q.onlySubchallenged { + baseQuery += " WHERE " + } else { + baseQuery += " AND " + } + baseQuery += strings.Join(q.filters, " AND ") + } + if q.orderBy != "" { + baseQuery += " ORDER BY " + q.orderBy + } + if q.limit > 0 { + baseQuery += " LIMIT ?" + q.args = append(q.args, q.limit) + } + if q.offset > 0 { + baseQuery += " OFFSET ?" + q.args = append(q.args, q.offset) + } + return baseQuery, q.args +} + +type CollectMachineHashesQuery struct { + args []interface{} + limit int + offset int + orderBy string + ongoing bool +} + +func NewCollectMachineHashes(opts ...CollectMachineHashesOption) *CollectMachineHashesQuery { + query := &CollectMachineHashesQuery{} + for _, opt := range opts { + opt(query) + } + return query +} + +type CollectMachineHashesOption func(*CollectMachineHashesQuery) + +func WithCollectMachineHashesOngoing() CollectMachineHashesOption { + return func(q *CollectMachineHashesQuery) { + q.ongoing = true + } +} + +func WithCollectMachineHashesOffset(offset int) CollectMachineHashesOption { + return func(q *CollectMachineHashesQuery) { + q.offset = offset + } + +} + +func WithCollectMachineHashesLimit(limit int) CollectMachineHashesOption { + return func(q *CollectMachineHashesQuery) { + q.limit = limit + } +} + +func WithCollectMachineHashesOrderBy(orderBy string) CollectMachineHashesOption { + return func(q *CollectMachineHashesQuery) { + q.orderBy = orderBy + } +} + +func (q *CollectMachineHashesQuery) ToSQL() (string, []interface{}) { + baseQuery := "SELECT * FROM CollectMachineHashes" + if q.ongoing { + baseQuery += " WHERE FinishTime IS NULL" + } + if q.orderBy != "" { + baseQuery += " ORDER BY " + q.orderBy + } + if q.limit > 0 { + baseQuery += " LIMIT ?" + q.args = append(q.args, q.limit) + } + if q.offset > 0 { + baseQuery += " OFFSET ?" + q.args = append(q.args, q.offset) + } + return baseQuery, q.args +} + +func (d *SqliteDatabase) GetEdges(opts ...EdgeOption) ([]*api.JsonEdge, error) { + query := NewEdgeQuery(opts...) + sql, args := query.ToSQL() + edges := make([]*api.JsonEdge, 0) + d.lock.Lock() + defer d.lock.Unlock() + err := d.sqlDB.Select(&edges, sql, args...) + if err != nil { + return nil, err + } + return edges, nil +} + +func (d *SqliteDatabase) InsertAssertions(assertions []*api.JsonAssertion) error { + for _, a := range assertions { + if err := d.InsertAssertion(a); err != nil { + return err + } + } + return nil +} + +func (d *SqliteDatabase) InsertAssertion(a *api.JsonAssertion) error { + d.lock.Lock() + defer d.lock.Unlock() + var assertionExists int + err := d.sqlDB.Get(&assertionExists, "SELECT COUNT(*) FROM Assertions WHERE Hash = ?", a.Hash) + if err != nil { + return err + } + if assertionExists != 0 { + return nil + } + query := `INSERT INTO Assertions ( + Hash, ConfirmPeriodBlocks, RequiredStake, ParentAssertionHash, InboxMaxCount, + AfterInboxBatchAcc, WasmModuleRoot, ChallengeManager, CreationBlock, TransactionHash, + BeforeStateBlockHash, BeforeStateSendRoot, BeforeStateBatch, BeforeStatePosInBatch, BeforeStateMachineStatus, AfterStateBlockHash, + AfterStateSendRoot, AfterStateBatch, AfterStatePosInBatch, AfterStateMachineStatus, FirstChildBlock, SecondChildBlock, + IsFirstChild, Status + ) VALUES ( + :Hash, :ConfirmPeriodBlocks, :RequiredStake, :ParentAssertionHash, :InboxMaxCount, + :AfterInboxBatchAcc, :WasmModuleRoot, :ChallengeManager, :CreationBlock, :TransactionHash, + :BeforeStateBlockHash, :BeforeStateSendRoot, :BeforeStateBatch, :BeforeStatePosInBatch, :BeforeStateMachineStatus, :AfterStateBlockHash, + :AfterStateSendRoot,:AfterStateBatch,:AfterStatePosInBatch, :AfterStateMachineStatus, :FirstChildBlock, :SecondChildBlock, + :IsFirstChild, :Status + )` + _, err = d.sqlDB.NamedExec(query, a) + if err != nil { + return err + } + return nil +} + +func (d *SqliteDatabase) InsertEdges(edges []*api.JsonEdge) error { + for _, e := range edges { + if err := d.InsertEdge(e); err != nil { + return err + } + } + return nil +} + +func (d *SqliteDatabase) InsertEdge(edge *api.JsonEdge) error { + tx, err := d.sqlDB.Beginx() + if err != nil { + return err + } + // Check if the edge already exists + var edgeExists int + err = tx.Get(&edgeExists, "SELECT COUNT(*) FROM Edges WHERE Id = ?", edge.Id) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + if edgeExists != 0 { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return nil + } + var assertionExists int + err = tx.Get(&assertionExists, "SELECT COUNT(*) FROM Assertions WHERE Hash = ?", edge.AssertionHash) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + // Check if an associated assertion for the edge exists. + if assertionExists != 0 { + // Check if a challenge exists for the assertion. + var challengeExists int + err = tx.Get(&challengeExists, "SELECT COUNT(*) FROM Challenges WHERE Hash = ?", edge.AssertionHash) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + // If the assertion exists but not the challenge, create the challenge + if challengeExists == 0 { + insertChallengeQuery := `INSERT INTO Challenges (Hash) VALUES (?)` + _, err = tx.Exec(insertChallengeQuery, edge.AssertionHash) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + } + } + insertEdgeQuery := `INSERT INTO Edges ( + Id, ChallengeLevel, OriginId, StartHistoryRoot, StartHeight, + EndHistoryRoot, EndHeight, CreatedAtBlock, MutualId, ClaimId, + HasChildren, LowerChildId, UpperChildId, MiniStaker, AssertionHash, + HasRival, Status, HasLengthOneRival, RawAncestors, IsRoyal, InheritedTimer, CumulativePathTimer + ) VALUES ( + :Id, :ChallengeLevel, :OriginId, :StartHistoryRoot, :StartHeight, + :EndHistoryRoot, :EndHeight, :CreatedAtBlock, :MutualId, :ClaimId, + :HasChildren, :LowerChildId, :UpperChildId, :MiniStaker, :AssertionHash, + :HasRival, :Status, :HasLengthOneRival, :RawAncestors, :IsRoyal, :InheritedTimer, :CumulativePathTimer + )` + + if _, err = tx.NamedExec(insertEdgeQuery, edge); err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + // Create an edge claim or an assertion claim. + if edge.ClaimId != (common.Hash{}) { + var claimExistsInDb int + err = tx.Get(&claimExistsInDb, "SELECT COUNT(*) FROM EdgeClaims WHERE ClaimId = ?", edge.ClaimId) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + if claimExistsInDb == 0 { + var refersTo string + if edge.ChallengeLevel == 0 { + refersTo = "assertion" + } else { + refersTo = "edge" + } + insertClaimQuery := `INSERT INTO EdgeClaims + (ClaimId, RefersTo) VALUES (?, ?)` + _, err = tx.Exec(insertClaimQuery, edge.ClaimId, refersTo) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + } + } + return tx.Commit() +} + +func (d *SqliteDatabase) UpdateEdges(edges []*api.JsonEdge) error { + d.lock.Lock() + defer d.lock.Unlock() + query := `UPDATE Edges SET + ChallengeLevel = :ChallengeLevel, + OriginId = :OriginId, + StartHistoryRoot = :StartHistoryRoot, + StartHeight = :StartHeight, + EndHistoryRoot = :EndHistoryRoot, + EndHeight = :EndHeight, + CreatedAtBlock = :CreatedAtBlock, + MutualId = :MutualId, + ClaimId = :ClaimId, + MiniStaker = :MiniStaker, + AssertionHash = :AssertionHash, + HasChildren = :HasChildren, + LowerChildId = :LowerChildId, + UpperChildId = :UpperChildId, + HasRival = :HasRival, + Status = :Status, + HasLengthOneRival = :HasLengthOneRival, + IsRoyal = :IsRoyal, + InheritedTimer = :InheritedTimer, + CumulativePathTimer = :CumulativePathTimer, + RawAncestors = :RawAncestors + WHERE Id = :Id` + tx, err := d.sqlDB.Beginx() + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + for _, e := range edges { + _, err := tx.NamedExec(query, e) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + } + return tx.Commit() +} + +func (d *SqliteDatabase) InsertCollectMachineHash(h *api.JsonCollectMachineHashes) error { + d.lock.Lock() + defer d.lock.Unlock() + query := `INSERT INTO CollectMachineHashes ( + WasmModuleRoot, + FromBatch, + PositionInBatch, + BatchLimit, + BlockChallengeHeight, + RawStepHeights, + NumDesiredHashes, + MachineStartIndex, + StepSize, + StartTime + ) VALUES ( + :WasmModuleRoot, + :FromBatch, + :PositionInBatch, + :BatchLimit, + :BlockChallengeHeight, + :RawStepHeights, + :NumDesiredHashes, + :MachineStartIndex, + :StepSize, + :StartTime + )` + _, err := d.sqlDB.NamedExec(query, h) + if err != nil { + return err + } + return nil +} + +func (d *SqliteDatabase) UpdateCollectMachineHash(h *api.JsonCollectMachineHashes) error { + d.lock.Lock() + defer d.lock.Unlock() + query := `UPDATE CollectMachineHashes SET + FinishTime = :FinishTime + WHERE WasmModuleRoot = :WasmModuleRoot + AND FromBatch = :FromBatch + AND PositionInBatch = :PositionInBatch + AND BatchLimit = :BatchLimit + AND BlockChallengeHeight = :BlockChallengeHeight + AND RawStepHeights = :RawStepHeights + AND NumDesiredHashes = :NumDesiredHashes + AND MachineStartIndex = :MachineStartIndex + AND StepSize = :StepSize + AND StartTime = :StartTime` + _, err := d.sqlDB.NamedExec(query, h) + if err != nil { + return err + } + return nil +} + +func (d *SqliteDatabase) UpdateAssertions(assertions []*api.JsonAssertion) error { + d.lock.Lock() + defer d.lock.Unlock() + // Construct the query + query := `UPDATE Assertions SET + ConfirmPeriodBlocks = :ConfirmPeriodBlocks, + RequiredStake = :RequiredStake, + ParentAssertionHash = :ParentAssertionHash, + InboxMaxCount = :InboxMaxCount, + AfterInboxBatchAcc = :AfterInboxBatchAcc, + WasmModuleRoot = :WasmModuleRoot, + ChallengeManager = :ChallengeManager, + CreationBlock = :CreationBlock, + TransactionHash = :TransactionHash, + BeforeStateBlockHash = :BeforeStateBlockHash, + BeforeStateSendRoot = :BeforeStateSendRoot, + BeforeStateBatch = :BeforeStateBatch, + BeforeStatePosInBatch = :BeforeStatePosInBatch, + BeforeStateMachineStatus = :BeforeStateMachineStatus, + AfterStateBlockHash = :AfterStateBlockHash, + AfterStateSendRoot = :AfterStateSendRoot, + AfterStateBatch = :AfterStateBatch, + AfterStatePosInBatch = :AfterStatePosInBatch, + AfterStateMachineStatus = :AfterStateMachineStatus, + FirstChildBlock = :FirstChildBlock, + SecondChildBlock = :SecondChildBlock, + IsFirstChild = :IsFirstChild, + Status = :Status + WHERE Hash = :Hash` + tx, err := d.sqlDB.Beginx() + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + for _, a := range assertions { + _, err := tx.NamedExec(query, a) + if err != nil { + if err2 := tx.Rollback(); err2 != nil { + return err2 + } + return err + } + } + return tx.Commit() +} diff --git a/bold/api/db/db_test.go b/bold/api/db/db_test.go new file mode 100644 index 0000000000..aa50d97c39 --- /dev/null +++ b/bold/api/db/db_test.go @@ -0,0 +1,613 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package db + +import ( + "fmt" + "testing" + "time" + + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/api" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/state-commitments/history" + "github.com/offchainlabs/bold/testing/casttest" +) + +func TestSqliteDatabase_CollectMachineHashes(t *testing.T) { + sqlDB, err := sqlx.Connect("sqlite3", ":memory:") + require.NoError(t, err) + defer sqlDB.Close() + + err = dbInit(sqlDB, schemaList) + require.NoError(t, err) + + db := &SqliteDatabase{sqlDB: sqlDB} + machineHashes := &api.JsonCollectMachineHashes{ + WasmModuleRoot: common.BytesToHash([]byte("foo")), + FromBatch: 1, + PositionInBatch: 0, + BatchLimit: 7, + BlockChallengeHeight: 2, + RawStepHeights: "3, 4, 5, 6", + NumDesiredHashes: 4, + MachineStartIndex: 5, + StepSize: 6, + StartTime: time.Now().UTC(), + } + require.NoError(t, db.InsertCollectMachineHash(machineHashes)) + + machineHashesFromDb, err := db.GetCollectMachineHashes() + require.NoError(t, err) + require.Equal(t, len(machineHashesFromDb), 1) + require.Equal(t, machineHashes, machineHashesFromDb[0]) + + ongoingMachineHashesFromDb, err := db.GetCollectMachineHashes(WithCollectMachineHashesOngoing()) + require.NoError(t, err) + require.Equal(t, len(ongoingMachineHashesFromDb), 1) + + finishTime := time.Now().UTC() + machineHashes.FinishTime = &finishTime + require.NoError(t, db.UpdateCollectMachineHash(machineHashes)) + + machineHashesFromDb, err = db.GetCollectMachineHashes() + require.NoError(t, err) + require.Equal(t, len(machineHashesFromDb), 1) + require.Equal(t, machineHashes, machineHashesFromDb[0]) + + ongoingMachineHashesFromDb, err = db.GetCollectMachineHashes(WithCollectMachineHashesOngoing()) + require.NoError(t, err) + require.Equal(t, len(ongoingMachineHashesFromDb), 0) +} + +func TestSqliteDatabase_UpdateEdgeSchema(t *testing.T) { + t.Skip() + sqlDB, err := sqlx.Connect("sqlite3", ":memory:") + require.NoError(t, err) + defer sqlDB.Close() + + err = dbInit(sqlDB, []string{version1}) + require.NoError(t, err) + + db := &SqliteDatabase{sqlDB: sqlDB} + + edge := baseEdge() + + require.NoError(t, db.InsertEdge(edge)) + + edgesFromDB, err := db.GetEdges() + require.NoError(t, err) + + require.Equal(t, len(edgesFromDB), 1) + edgesFromDB[0].LastUpdatedAt = time.Time{} + require.Equal(t, edge, edgesFromDB[0]) + + // Make sure that the DB schema initialization is idempotent for version 1 + // and adds fields to the edge table from version 2. + err = dbInit(sqlDB, []string{version1, version2}) + require.NoError(t, err) + + edgesFromDB, err = db.GetEdges() + require.NoError(t, err) + require.Equal(t, len(edgesFromDB), 1) + edgesFromDB[0].LastUpdatedAt = time.Time{} + require.Equal(t, edge, edgesFromDB[0]) +} + +func TestSqliteDatabase_Updates(t *testing.T) { + sqlDB, err := sqlx.Connect("sqlite3", ":memory:") + require.NoError(t, err) + defer sqlDB.Close() + + err = dbInit(sqlDB, schemaList) + require.NoError(t, err) + + db := &SqliteDatabase{sqlDB: sqlDB} + numAssertions := 10 + assertionsToCreate := make([]*api.JsonAssertion, numAssertions) + for i := 0; i < numAssertions; i++ { + base := baseAssertion() + base.Hash = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + base.CreationBlock = casttest.ToUint64(t, i) + assertionsToCreate[i] = base + } + require.NoError(t, db.InsertAssertions(assertionsToCreate)) + + // Get the inserted assertions. + assertions, err := db.GetAssertions() + require.NoError(t, err) + require.Equal(t, numAssertions, len(assertions)) + + time.Sleep(time.Second) + + lastAssertion := assertions[len(assertions)-1] + lastUpdated := lastAssertion.LastUpdatedAt + lastAssertion.Status = "confirmed" + require.NoError(t, db.UpdateAssertions([]*api.JsonAssertion{lastAssertion})) + + // Check the last updated timestamp gets increased. + updatedAssertions, err := db.GetAssertions(WithAssertionHash(protocol.AssertionHash{Hash: lastAssertion.Hash}), WithAssertionLimit(1)) + require.NoError(t, err) + require.Equal(t, 1, len(updatedAssertions)) + require.Equal(t, "confirmed", updatedAssertions[0].Status) + require.Equal(t, true, lastUpdated.Before(updatedAssertions[0].LastUpdatedAt)) + + // Insert an edge, update it, then check the last updated timestamp increased. + edge := baseEdge() + edge.AssertionHash = lastAssertion.Hash + require.NoError(t, db.InsertEdge(edge)) + + edges, err := db.GetEdges() + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + lastUpdated = edges[0].LastUpdatedAt + + time.Sleep(time.Second) + + edge.Status = "confirmed" + require.NoError(t, db.UpdateEdges([]*api.JsonEdge{edge})) + + // Check the last updated timestamp gets increased. + updatedEdges, err := db.GetEdges(WithEdgeAssertionHash(protocol.AssertionHash{Hash: lastAssertion.Hash}), WithLimit(1)) + require.NoError(t, err) + require.Equal(t, 1, len(updatedEdges)) + require.Equal(t, "confirmed", updatedEdges[0].Status) + require.Equal(t, true, lastUpdated.Before(updatedEdges[0].LastUpdatedAt)) +} + +func TestSqliteDatabase_Assertions(t *testing.T) { + sqlDB, err := sqlx.Connect("sqlite3", ":memory:") + require.NoError(t, err) + defer sqlDB.Close() + + err = dbInit(sqlDB, schemaList) + require.NoError(t, err) + + // Inserting edges that don't have an associated assertion should fail. + db := &SqliteDatabase{sqlDB: sqlDB} + + numAssertions := 10 + assertionsToCreate := make([]*api.JsonAssertion, numAssertions) + for i := 0; i < numAssertions; i++ { + base := baseAssertion() + base.Hash = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + base.CreationBlock = casttest.ToUint64(t, i) + if i == 1 { + base.ConfirmPeriodBlocks = 20 + base.BeforeStateBlockHash = common.BytesToHash([]byte("block")) + base.BeforeStateSendRoot = common.BytesToHash([]byte("send")) + base.BeforeStateBatch = 4 + base.BeforeStatePosInBatch = 0 + } + if i == 2 || i == 3 { + base.RequiredStake = "1000" + base.ChallengeManager = common.BytesToAddress([]byte("foo")) + b1 := uint64(2) + b2 := uint64(3) + base.FirstChildBlock = &b1 + base.SecondChildBlock = &b2 + base.Status = protocol.AssertionConfirmed.String() + } + if i == 4 { + base.ParentAssertionHash = common.BytesToHash([]byte("3")) + base.InboxMaxCount = "1000" + base.AfterInboxBatchAcc = common.BytesToHash([]byte("nyan")) + base.WasmModuleRoot = common.BytesToHash([]byte("nyan")) + base.TransactionHash = common.BytesToHash([]byte("baz")) + base.AfterStateBlockHash = common.BytesToHash([]byte("block2")) + base.AfterStateSendRoot = common.BytesToHash([]byte("send2")) + base.AfterStateBatch = 6 + base.AfterStatePosInBatch = 2 + base.IsFirstChild = true + } + base.CreationBlock = casttest.ToUint64(t, i) + assertionsToCreate[i] = base + } + require.NoError(t, db.InsertAssertions(assertionsToCreate)) + + assertions, err := db.GetAssertions() + require.NoError(t, err) + require.Equal(t, numAssertions, len(assertions)) + + // There should be no challenged assertions. + challengedAssertions, err := db.GetChallengedAssertions() + require.NoError(t, err) + require.Equal(t, 0, len(challengedAssertions)) + + t.Run("query options", func(t *testing.T) { + assertions, err := db.GetAssertions(WithAssertionHash(protocol.AssertionHash{Hash: common.BytesToHash([]byte("3"))})) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithAssertionHash(protocol.AssertionHash{Hash: common.BytesToHash([]byte("100"))})) + require.NoError(t, err) + require.Equal(t, 0, len(assertions)) + + assertions, err = db.GetAssertions(WithConfirmPeriodBlocks(20)) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithRequiredStake("1000")) + require.NoError(t, err) + require.Equal(t, 2, len(assertions)) + + assertions, err = db.GetAssertions(WithParentAssertionHash(protocol.AssertionHash{Hash: common.BytesToHash([]byte("3"))})) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithInboxMaxCount("1000")) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithAfterInboxBatchAcc(common.BytesToHash([]byte("nyan")))) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithWasmModuleRoot(common.BytesToHash([]byte("nyan")))) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithChallengeManager(common.BytesToAddress([]byte("foo")))) + require.NoError(t, err) + require.Equal(t, 2, len(assertions)) + + assertions, err = db.GetAssertions(FromAssertionCreationBlock(5), ToAssertionCreationBlock(6)) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(FromAssertionCreationBlock(1), ToAssertionCreationBlock(4)) + require.NoError(t, err) + require.Equal(t, 3, len(assertions)) + + assertions, err = db.GetAssertions(WithTransactionHash(common.BytesToHash([]byte("baz")))) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithBeforeState(&protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("block")), + SendRoot: common.BytesToHash([]byte("send")), + PosInBatch: 0, + Batch: 4, + }, + MachineStatus: protocol.MachineStatusFinished, + })) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithAfterState(&protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("block2")), + SendRoot: common.BytesToHash([]byte("send2")), + PosInBatch: 2, + Batch: 6, + }, + MachineStatus: protocol.MachineStatusFinished, + })) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithFirstChildBlock(2)) + require.NoError(t, err) + require.Equal(t, 2, len(assertions)) + + assertions, err = db.GetAssertions(WithSecondChildBlock(3)) + require.NoError(t, err) + require.Equal(t, 2, len(assertions)) + + assertions, err = db.GetAssertions(WithIsFirstChild()) + require.NoError(t, err) + require.Equal(t, 1, len(assertions)) + + assertions, err = db.GetAssertions(WithAssertionStatus(protocol.AssertionConfirmed)) + require.NoError(t, err) + require.Equal(t, 2, len(assertions)) + }) + t.Run("orderings limits and offsets", func(t *testing.T) { + gotIds := make([]protocol.AssertionHash, 0) + wantIds := make([]protocol.AssertionHash, 0) + + expectedAssertions := assertionsToCreate[2:4] + for _, a := range expectedAssertions { + wantIds = append(wantIds, protocol.AssertionHash{Hash: a.Hash}) + } + + assertions, err := db.GetAssertions(WithAssertionLimit(2), WithAssertionOffset(2), WithAssertionOrderBy("CreationBlock ASC")) + require.NoError(t, err) + for _, a := range assertions { + gotIds = append(gotIds, protocol.AssertionHash{Hash: a.Hash}) + } + require.Equal(t, wantIds, gotIds) + }) +} + +func TestSqliteDatabase_Edges(t *testing.T) { + sqlDB, err := sqlx.Connect("sqlite3", ":memory:") + require.NoError(t, err) + defer sqlDB.Close() + + err = dbInit(sqlDB, schemaList) + require.NoError(t, err) + + // Inserting edges that don't have an associated assertion should fail. + db := &SqliteDatabase{sqlDB: sqlDB} + + numAssertions := 10 + assertionsToCreate := make([]*api.JsonAssertion, numAssertions) + for i := 0; i < numAssertions; i++ { + base := baseAssertion() + base.Hash = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + base.CreationBlock = casttest.ToUint64(t, i) + assertionsToCreate[i] = base + } + require.NoError(t, db.InsertAssertions(assertionsToCreate)) + + numEdges := 5 + endHeight := uint64(32) + edgesToCreate := make([]*api.JsonEdge, numEdges) + for i := 0; i < numEdges; i++ { + base := baseEdge() + base.Id = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + base.AssertionHash = common.BytesToHash([]byte("1")) + base.CreatedAtBlock = casttest.ToUint64(t, i) + base.EndHeight = endHeight + if i == 0 { + base.OriginId = common.BytesToHash([]byte("foo")) + base.MutualId = common.BytesToHash([]byte("bar")) + base.MiniStaker = common.BytesToAddress([]byte("nyan")) + base.Status = "confirmed" + } + if i == 2 || i == 3 { + base.HasChildren = true + base.LowerChildId = common.BytesToHash([]byte("0")) + base.UpperChildId = common.BytesToHash([]byte("1")) + base.HasRival = true + base.HasLengthOneRival = true + base.ClaimId = common.BytesToHash([]byte("1")) + base.IsRoyal = true + base.InheritedTimer = 10 + } + edgesToCreate[i] = base + endHeight = endHeight / 2 + } + require.NoError(t, db.InsertEdges(edgesToCreate)) + + // Check the edges retrieved. + edges, err := db.GetEdges() + require.NoError(t, err) + require.Equal(t, numEdges, len(edges)) + + // A challenge should have been created for the edges that were inserted + // for their associated assertion in the database. There should only be one challenged assertion. + challengedAssertions, err := db.GetChallengedAssertions() + require.NoError(t, err) + require.Equal(t, 1, len(challengedAssertions)) + + t.Run("query options", func(t *testing.T) { + edges, err = db.GetEdges(WithId(protocol.EdgeId{Hash: common.BytesToHash([]byte("0"))})) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges(WithChallengeLevel(0)) + require.NoError(t, err) + require.Equal(t, numEdges, len(edges)) + + edges, err = db.GetEdges(WithChallengeLevel(1)) + require.NoError(t, err) + require.Equal(t, 0, len(edges)) + + edges, err = db.GetEdges(WithOriginId(protocol.OriginId(common.BytesToHash([]byte("foo"))))) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges(WithStartHistoryCommitment(history.History{ + Height: 0, + Merkle: common.Hash{}, + })) + require.NoError(t, err) + require.Equal(t, 5, len(edges)) + + edges, err = db.GetEdges(WithEndHistoryCommitment(history.History{ + Height: 32, + Merkle: common.Hash{}, + })) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges( + WithStartHistoryCommitment(history.History{ + Height: 0, + Merkle: common.Hash{}, + }), + WithEndHistoryCommitment(history.History{ + Height: 16, + Merkle: common.Hash{}, + }), + ) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges(WithMutualId(protocol.MutualId(common.BytesToHash([]byte("bar"))))) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges(HasChildren(true)) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithLowerChildId(protocol.EdgeId{Hash: common.BytesToHash([]byte("0"))})) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithUpperChildId(protocol.EdgeId{Hash: common.BytesToHash([]byte("1"))})) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithMiniStaker(common.BytesToAddress([]byte("nyan")))) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges(WithEdgeAssertionHash(protocol.AssertionHash{Hash: common.BytesToHash([]byte("1"))})) + require.NoError(t, err) + require.Equal(t, numEdges, len(edges)) + + edges, err = db.GetEdges(WithEdgeAssertionHash(protocol.AssertionHash{Hash: common.BytesToHash([]byte("0"))})) + require.NoError(t, err) + require.Equal(t, 0, len(edges)) + + edges, err = db.GetEdges(WithRival(true)) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithEdgeStatus(protocol.EdgeConfirmed)) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + + edges, err = db.GetEdges(WithLengthOneRival()) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithClaimId(protocol.ClaimId(common.BytesToHash([]byte("1"))))) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithRoyal(true)) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithInheritedTimerGreaterOrEq(1)) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithInheritedTimerGreaterOrEq(10)) + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + + edges, err = db.GetEdges(WithInheritedTimerGreaterOrEq(11)) + require.NoError(t, err) + require.Equal(t, 0, len(edges)) + }) + t.Run("orderings limits and offsets", func(t *testing.T) { + gotIds := make([]protocol.EdgeId, 0) + wantIds := make([]protocol.EdgeId, 0) + + expectedEdges := edgesToCreate[2:4] + for _, e := range expectedEdges { + wantIds = append(wantIds, protocol.EdgeId{Hash: e.Id}) + } + + edges, err = db.GetEdges(WithLimit(2), WithOffset(2), WithOrderBy("CreatedAtBlock ASC")) + require.NoError(t, err) + for _, e := range edges { + gotIds = append(gotIds, protocol.EdgeId{Hash: e.Id}) + } + require.Equal(t, wantIds, gotIds) + }) +} + +func TestEdgeClaims(t *testing.T) { + sqlDB, err := sqlx.Connect("sqlite3", ":memory:") + require.NoError(t, err) + defer sqlDB.Close() + + err = dbInit(sqlDB, schemaList) + require.NoError(t, err) + db := &SqliteDatabase{sqlDB: sqlDB} + + base := baseAssertion() + base.Hash = common.BytesToHash([]byte("challenged_assertion")) + claimedAssertion := baseAssertion() + claimedAssertion.Hash = common.BytesToHash([]byte("claimed_assertion")) + require.NoError(t, db.InsertAssertions([]*api.JsonAssertion{base, claimedAssertion})) + + // Insert a top level edge + edge := baseEdge() + edge.AssertionHash = base.Hash + edge.ClaimId = claimedAssertion.Hash + edge.Id = common.BytesToHash([]byte("top_level_edge")) + edge.StartHeight = 0 + edge.EndHeight = 32 + edge.ChallengeLevel = 0 + + // Insert a lower level edge that claims the higher level one. + lowerEdge := baseEdge() + lowerEdge.AssertionHash = base.Hash + lowerEdge.ClaimId = edge.Id + lowerEdge.Id = common.BytesToHash([]byte("lower_level_edge")) + lowerEdge.StartHeight = 0 + lowerEdge.EndHeight = 32 + lowerEdge.ChallengeLevel = 1 + require.NoError(t, db.InsertEdges([]*api.JsonEdge{edge, lowerEdge})) + + // Get all edges and check their ids. + edges, err := db.GetEdges() + require.NoError(t, err) + require.Equal(t, 2, len(edges)) + require.Equal(t, edge.Id, edges[0].Id) + require.Equal(t, lowerEdge.Id, edges[1].Id) + + // Get only edges with a subchallenge and expect it is the top-level edge. + edges, err = db.GetEdges(WithSubchallenge()) + require.NoError(t, err) + require.Equal(t, 1, len(edges)) + require.Equal(t, edge.Id, edges[0].Id) +} + +func baseAssertion() *api.JsonAssertion { + return &api.JsonAssertion{ + Hash: common.Hash{}, + ConfirmPeriodBlocks: 100, + RequiredStake: "1", + ParentAssertionHash: common.Hash{}, + InboxMaxCount: "1", + AfterInboxBatchAcc: common.Hash{}, + WasmModuleRoot: common.Hash{}, + ChallengeManager: common.Address{}, + CreationBlock: 1, + TransactionHash: common.Hash{}, + BeforeStateBlockHash: common.Hash{}, + BeforeStateSendRoot: common.Hash{}, + BeforeStateBatch: 0, + BeforeStatePosInBatch: 0, + BeforeStateMachineStatus: protocol.MachineStatusFinished, + AfterStateBlockHash: common.Hash{}, + AfterStateSendRoot: common.Hash{}, + AfterStateBatch: 0, + AfterStatePosInBatch: 0, + AfterStateMachineStatus: protocol.MachineStatusFinished, + FirstChildBlock: nil, + SecondChildBlock: nil, + IsFirstChild: false, + Status: protocol.AssertionPending.String(), + } +} + +func baseEdge() *api.JsonEdge { + return &api.JsonEdge{ + Id: common.Hash{}, + ChallengeLevel: 0, + OriginId: common.Hash{}, + AssertionHash: common.Hash{}, + StartHistoryRoot: common.Hash{}, + StartHeight: 0, + EndHistoryRoot: common.Hash{}, + EndHeight: 0, + MutualId: common.Hash{}, + ClaimId: common.Hash{}, + HasChildren: false, + LowerChildId: common.Hash{}, + UpperChildId: common.Hash{}, + MiniStaker: common.Address{}, + HasRival: false, + Status: "pending", + HasLengthOneRival: false, + CreatedAtBlock: 1, + } +} diff --git a/bold/api/db/schema.go b/bold/api/db/schema.go new file mode 100644 index 0000000000..6c18094bac --- /dev/null +++ b/bold/api/db/schema.go @@ -0,0 +1,133 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package db + +var ( + // flagSetup is the initial setup for the flags table. + // It creates the table if it doesn't exist and sets the CurrentVersion to 0. + flagSetup = ` +CREATE TABLE IF NOT EXISTS Flags ( + FlagName TEXT NOT NULL PRIMARY KEY, + FlagValue INTEGER NOT NULL +); +INSERT INTO Flags (FlagName, FlagValue) VALUES ('CurrentVersion', 0); +` + // schemaList is a list of schema versions. + // The first element is the initial schema, + // and each subsequent element is a migration from the previous version to the new version. + version1 = ` +CREATE TABLE IF NOT EXISTS Challenges ( + Hash TEXT NOT NULL PRIMARY KEY, + UNIQUE(Hash) +); + +CREATE TABLE IF NOT EXISTS EdgeClaims ( + ClaimId TEXT NOT NULL PRIMARY KEY, + RefersTo TEXT NOT NULL, -- 'edge' or 'assertion' + FOREIGN KEY(ClaimId) REFERENCES Edges(Id), + FOREIGN KEY(ClaimId) REFERENCES Assertions(Hash) +); + +CREATE TABLE IF NOT EXISTS Edges ( + Id TEXT NOT NULL PRIMARY KEY, + ChallengeLevel INTEGER NOT NULL, + OriginId TEXT NOT NULL, + StartHistoryRoot TEXT NOT NULL, + StartHeight INTEGER NOT NULL, + EndHistoryRoot TEXT NOT NULL, + EndHeight INTEGER NOT NULL, + CreatedAtBlock INTEGER NOT NULL, + MutualId TEXT NOT NULL, + ClaimId TEXT NOT NULL, + MiniStaker TEXT NOT NULL, + AssertionHash TEXT NOT NULL, + HasChildren BOOLEAN NOT NULL, + LowerChildId TEXT NOT NULL, + UpperChildId TEXT NOT NULL, + HasRival BOOLEAN NOT NULL, + Status TEXT NOT NULL, + HasLengthOneRival BOOLEAN NOT NULL, + IsRoyal BOOLEAN NOT NULL, + RawAncestors TEXT NOT NULL, + LastUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(LowerChildID) REFERENCES Edges(Id), + FOREIGN KEY(ClaimId) REFERENCES EdgeClaims(ClaimId), + FOREIGN KEY(UpperChildID) REFERENCES Edges(Id), + FOREIGN KEY(AssertionHash) REFERENCES Challenges(Hash) +); + +CREATE TABLE IF NOT EXISTS Assertions ( + Hash TEXT NOT NULL PRIMARY KEY, + ConfirmPeriodBlocks INTEGER NOT NULL, + RequiredStake TEXT NOT NULL, + ParentAssertionHash TEXT NOT NULL, + InboxMaxCount TEXT NOT NULL, + AfterInboxBatchAcc TEXT NOT NULL, + WasmModuleRoot TEXT NOT NULL, + ChallengeManager TEXT NOT NULL, + CreationBlock INTEGER NOT NULL, + TransactionHash TEXT NOT NULL, + BeforeStateBlockHash TEXT NOT NULL, + BeforeStateSendRoot TEXT NOT NULL, + BeforeStateBatch INTEGER NOT NULL, + BeforeStatePosInBatch INTEGER NOT NULL, + BeforeStateMachineStatus INTEGER NOT NULL, + AfterStateBlockHash TEXT NOT NULL, + AfterStateSendRoot TEXT NOT NULL, + AfterStateBatch INTEGER NOT NULL, + AfterStatePosInBatch INTEGER NOT NULL, + AfterStateMachineStatus INTEGER NOT NULL, + FirstChildBlock INTEGER, + SecondChildBlock INTEGER, + IsFirstChild BOOLEAN NOT NULL, + Status TEXT NOT NULL, + LastUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(Hash) REFERENCES Challenges(Hash), + FOREIGN KEY(ParentAssertionHash) REFERENCES Assertions(Hash) +); + +CREATE INDEX IF NOT EXISTS idx_edge_assertion ON Edges(AssertionHash); +CREATE INDEX IF NOT EXISTS idx_assertions_assertion ON Assertions(Hash); +CREATE INDEX IF NOT EXISTS idx_edge_claim_id ON Edges(ClaimId); +CREATE INDEX IF NOT EXISTS idx_edge_end_height ON Edges(EndHeight); +CREATE INDEX IF NOT EXISTS idx_edge_end_history_root ON Edges(EndHistoryRoot); + +CREATE TRIGGER IF NOT EXISTS UpdateEdgeTimestamp +AFTER UPDATE ON Edges +FOR EACH ROW +BEGIN + UPDATE Edges SET LastUpdatedAt = CURRENT_TIMESTAMP WHERE Id = NEW.Id; +END; + +CREATE TRIGGER IF NOT EXISTS UpdateAssertionTimestamp +AFTER UPDATE ON Assertions +FOR EACH ROW +BEGIN + UPDATE Assertions SET LastUpdatedAt = CURRENT_TIMESTAMP WHERE Hash = NEW.Hash; +END; +` + version2 = ` +ALTER TABLE Edges ADD COLUMN InheritedTimer INTEGER NOT NULL DEFAULT 0; + +CREATE TABLE IF NOT EXISTS CollectMachineHashes ( + WasmModuleRoot TEXT NOT NULL, + FromBatch INTEGER NOT NULL, + PositionInBatch INTEGER NOT NULL, + BatchLimit INTEGER NOT NULL, + BlockChallengeHeight INTEGER NOT NULL, + RawStepHeights TEXT NOT NULL, + NumDesiredHashes INTEGER NOT NULL, + MachineStartIndex INTEGER NOT NULL, + StepSize INTEGER NOT NULL, + StartTime DATETIME NOT NULL, + FinishTime DATETIME +); +` + version3 = ` + ALTER TABLE Edges ADD COLUMN CumulativePathTimer INTEGER NOT NULL DEFAULT 0; +` + // schemaList is a list of schema versions. + schemaList = []string{version1, version2, version3} +) diff --git a/bold/api/server/methods.go b/bold/api/server/methods.go new file mode 100644 index 0000000000..5b5bd29460 --- /dev/null +++ b/bold/api/server/methods.go @@ -0,0 +1,614 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package server + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/gorilla/mux" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + + "github.com/offchainlabs/bold/api" + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/state-commitments/history" +) + +var contentType = "application/json" + +// Healthz checks if the API server is ready to serve queries. Returns 200 if it is ready. +// +// method: +// - GET +// - /api/v1/db/healthz +func (s *Server) Healthz(w http.ResponseWriter, r *http.Request) { + // TODO: Respond with a 503 if the client the BOLD validator is + // connected to is syncing. + w.WriteHeader(http.StatusOK) +} + +// ListAssertions up to chain head +// +// method: +// - GET +// - /api/v1/assertions +// +// request query params: +// - limit: the max number of items in the response +// - offset: the offset index in the DB +// - inbox_max_count: assertions that have a specified value for InboxMaxCount +// - from_block_number: items that were created since a specific block number. Defaults to latest confirmed assertion +// - to_block_number: caps the response to assertions up to and including a block number +// - challenged: fetch only assertions that have been challenged +// - force_update: refetch the updatable fields of each item in the response +// +// response: +// - []*JsonAssertion +func (s *Server) ListAssertions(w http.ResponseWriter, r *http.Request) { + opts := make([]db.AssertionOption, 0) + query := r.URL.Query() + if val, ok := query["limit"]; ok && len(val) > 0 { + if v, err := strconv.Atoi(val[0]); err == nil { + opts = append(opts, db.WithAssertionLimit(v)) + } + } + if val, ok := query["offset"]; ok && len(val) > 0 { + if v, err := strconv.Atoi(val[0]); err == nil { + opts = append(opts, db.WithAssertionOffset(v)) + } + } + if val, ok := query["inbox_max_count"]; ok && len(val) > 0 { + opts = append(opts, db.WithInboxMaxCount(strings.Join(val, ""))) + } + if val, ok := query["from_block_number"]; ok && len(val) > 0 { + if v, err := strconv.ParseUint(val[0], 10, 64); err == nil { + opts = append(opts, db.FromAssertionCreationBlock(v)) + } + } + if val, ok := query["to_block_number"]; ok && len(val) > 0 { + if v, err := strconv.ParseUint(val[0], 10, 64); err == nil { + opts = append(opts, db.ToAssertionCreationBlock(v)) + } + } + if _, ok := query["challenged"]; ok { + opts = append(opts, db.WithChallenge()) + } + if _, ok := query["force_update"]; ok { + opts = append(opts, db.WithAssertionForceUpdate()) + } + assertions, err := s.backend.GetAssertions(r.Context(), opts...) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get assertions from backend: %v", err), http.StatusInternalServerError) + return + } + writeJSONResponse(w, assertions) +} + +// CollectMachineHashes fetches all the collectMachineHashes calls that have been made +// along with their start and end times. +// +// method: +// - GET +// - /api/v1/state-provider/requests/collect-machine-hashes +// +// request query params: +// - limit: the max number of items in the response +// - offset: the offset index in the DB +// - ongoing: fetch only collectMachineHashes calls that are ongoing or did not finish +// +// response: +// - []*JsonCollectMachineHashes +func (s *Server) CollectMachineHashes(w http.ResponseWriter, r *http.Request) { + opts := make([]db.CollectMachineHashesOption, 0) + query := r.URL.Query() + if val, ok := query["limit"]; ok && len(val) > 0 { + if v, err := strconv.Atoi(val[0]); err == nil { + opts = append(opts, db.WithCollectMachineHashesLimit(v)) + } + } + if val, ok := query["offset"]; ok && len(val) > 0 { + if v, err := strconv.Atoi(val[0]); err == nil { + opts = append(opts, db.WithCollectMachineHashesOffset(v)) + } + } + if _, ok := query["ongoing"]; ok { + opts = append(opts, db.WithCollectMachineHashesOngoing()) + } + assertions, err := s.backend.GetCollectMachineHashes(r.Context(), opts...) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get CollectMachineHashes from backend: %v", err), http.StatusInternalServerError) + return + } + writeJSONResponse(w, assertions) +} + +// AssertionByIdentifier since the latest confirmed assertion. +// +// method: +// - GET +// - /api/v1/assertions/ +// +// identifier options: +// - an assertion hash (0x-prefixed): gets the assertion by hash +// +// query params +// - force_update: refetch the updatable fields of each item in the response +// +// response: +// - *JsonAssertion +func (s *Server) AssertionByIdentifier(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + identifier := vars["identifier"] + + var assertion *api.JsonAssertion + opts := []db.AssertionOption{ + db.WithAssertionLimit(1), + } + query := r.URL.Query() + if _, ok := query["force_update"]; ok { + opts = append(opts, db.WithAssertionForceUpdate()) + } + hash, err := hexutil.Decode(identifier) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse assertion hash: %v", err), http.StatusBadRequest) + return + } + opts = append(opts, db.WithAssertionHash(protocol.AssertionHash{Hash: common.BytesToHash(hash)})) + assertions, err := s.backend.GetAssertions(r.Context(), opts...) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get assertions from backend: %v", err), http.StatusInternalServerError) + return + } + if len(assertions) != 1 { + http.Error( + w, + fmt.Sprintf("Got more than 1 matching assertion: got %d", len(assertions)), + http.StatusInternalServerError, + ) + return + } + assertion = assertions[0] + writeJSONResponse(w, assertion) +} + +// AllChallengeEdges fetches all the edges corresponding to a challenged +// assertion with a specific hash. This assertion hash must be the "parent assertion" +// of two child assertions that originated a challenge. +// +// method: +// - GET +// - /api/v1/challenge//edges +// +// identifier options: +// - 0x-prefixed assertion hash +// +// request query params: +// - limit: the max number of items in the response +// - offset: the offset index in the DB +// - status: filter edges that have status "confirmed", "confirmable", or "pending" +// - royal: boolean true or false to get royal edges. If not set, fetches all edges in the challenge. +// - root_edges: boolean true or false to filter out only root edges (those that have a claim_id) +// - rivaled: boolean true or false to get only rivaled edges +// - has_length_one_rival: boolean true or false to get only edges that have a length one rival +// - only_subchallenged_edges: boolean true or false to get only edges that have a subchallenge claiming them +// - from_block_number: items that were created since a specific block number. +// - to_block_number: caps the response to edges up to a block number +// - inherited_timer_geq: edges with an inherited timer greater than some N number of blocks +// - to_block_number: caps the response to edges up to a block number +// - origin_id: edges that have a 0x-prefixed origin id +// - mutual_id: edges that have a 0x-prefixed mutual id +// - claim_id: edges that have a 0x-prefixed claim id +// - start_height: edges with a start height +// - end_height: edges with an end height +// - start_commitment: edges with a start history commitment of format "height:hash", such as 32:0xdeadbeef +// - end_commitment: edges with an end history commitment of format "height:hash", such as 32:0xdeadbeef +// - challenge_level: edges in a specific challenge level. level 0 is the block challenge level +// - force_update: refetch the updatable fields of each item in the response +// response: +// - []*JsonEdge +func (s *Server) AllChallengeEdges(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + assertionHashStr := vars["assertion-hash"] + hash, err := hexutil.Decode(assertionHashStr) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse assertion hash: %v", err), http.StatusBadRequest) + return + } + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash(hash)} + opts := []db.EdgeOption{ + db.WithEdgeAssertionHash(assertionHash), + } + query := r.URL.Query() + if val, ok := query["limit"]; ok && len(val) > 0 { + if v, err2 := strconv.Atoi(val[0]); err2 == nil { + opts = append(opts, db.WithLimit(v)) + } + } + if val, ok := query["offset"]; ok && len(val) > 0 { + if v, err2 := strconv.Atoi(val[0]); err2 == nil { + opts = append(opts, db.WithOffset(v)) + } + } + if val, ok := query["status"]; ok && len(val) > 0 { + status, err2 := parseEdgeStatus(strings.Join(val, "")) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse status: %v", err2), http.StatusBadRequest) + return + } + opts = append(opts, db.WithEdgeStatus(status)) + } + if val, ok := query["royal"]; ok { + v := strings.Join(val, "") + switch v { + case "false": + opts = append(opts, db.WithRoyal(false)) + case "true": + opts = append(opts, db.WithRoyal(true)) + } + } + if _, ok := query["has_length_one_rival"]; ok { + opts = append(opts, db.WithLengthOneRival()) + } + if val, ok := query["rivaled"]; ok { + v := strings.Join(val, "") + switch v { + case "false": + opts = append(opts, db.WithRival(false)) + case "true": + opts = append(opts, db.WithRival(true)) + } + } + if _, ok := query["only_subchallenged_edges"]; ok { + opts = append(opts, db.WithSubchallenge()) + } + if _, ok := query["root_edges"]; ok { + opts = append(opts, db.WithRootEdges()) + } + if _, ok := query["force_update"]; ok { + opts = append(opts, db.WithEdgeForceUpdate()) + } + if val, ok := query["from_block_number"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 64); err2 == nil { + opts = append(opts, db.FromEdgeCreationBlock(v)) + } + } + if val, ok := query["to_block_number"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 64); err2 == nil { + opts = append(opts, db.ToEdgeCreationBlock(v)) + } + } + if val, ok := query["start_height"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 64); err2 == nil { + opts = append(opts, db.WithStartHeight(v)) + } + } + if val, ok := query["end_height"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 64); err2 == nil { + opts = append(opts, db.WithEndHeight(v)) + } + } + if val, ok := query["inherited_timer_geq"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 64); err2 == nil { + opts = append(opts, db.WithInheritedTimerGreaterOrEq(v)) + } + } + if val, ok := query["origin_id"]; ok && len(val) > 0 { + hash, err2 := hexutil.Decode(strings.Join(val, "")) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse origin_id: %v", err2), http.StatusBadRequest) + return + } + opts = append(opts, db.WithOriginId(protocol.OriginId(common.BytesToHash(hash)))) + } + if val, ok := query["mutual_id"]; ok && len(val) > 0 { + hash, err2 := hexutil.Decode(strings.Join(val, "")) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse mutual_id: %v", err2), http.StatusBadRequest) + return + } + opts = append(opts, db.WithMutualId(protocol.MutualId(common.BytesToHash(hash)))) + } + if val, ok := query["claim_id"]; ok && len(val) > 0 { + hash, err2 := hexutil.Decode(strings.Join(val, "")) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse claim_id: %v", err2), http.StatusBadRequest) + return + } + opts = append(opts, db.WithClaimId(protocol.ClaimId(common.BytesToHash(hash)))) + } + if val, ok := query["start_commitment"]; ok && len(val) > 0 { + commitStr := strings.Join(val, "") + commitParts := strings.Split(commitStr, ":") + if len(commitParts) != 2 { + http.Error(w, "Wrong start history commitment format, wanted height:hash", http.StatusBadRequest) + return + } + startHeight, err2 := strconv.ParseUint(commitParts[0], 10, 64) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse start commit height: %v", err2), http.StatusBadRequest) + return + } + startHash, err2 := hexutil.Decode(commitParts[1]) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse start commit hash: %v", err2), http.StatusBadRequest) + return + } + opts = append(opts, db.WithStartHistoryCommitment(history.History{ + Height: startHeight, + Merkle: common.BytesToHash(startHash), + })) + } + if val, ok := query["end_commitment"]; ok && len(val) > 0 { + commitStr := strings.Join(val, "") + commitParts := strings.Split(commitStr, ":") + if len(commitParts) != 2 { + http.Error(w, "Wrong start history commitment format, wanted height:hash", http.StatusBadRequest) + return + } + endHeight, err2 := strconv.ParseUint(commitParts[0], 10, 64) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse end commit height: %v", err2), http.StatusBadRequest) + return + } + endHash, err2 := hexutil.Decode(commitParts[1]) + if err2 != nil { + http.Error(w, fmt.Sprintf("Could not parse end commit hash: %v", err2), http.StatusBadRequest) + return + } + opts = append(opts, db.WithEndHistoryCommitment(history.History{ + Height: endHeight, + Merkle: common.BytesToHash(endHash), + })) + } + if val, ok := query["challenge_level"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 8); err2 == nil { + opts = append(opts, db.WithChallengeLevel(uint8(v))) + } + } + edges, err := s.backend.GetEdges(r.Context(), opts...) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get edges from backend: %v", err), http.StatusInternalServerError) + return + } + writeJSONResponse(w, edges) +} + +func parseEdgeStatus(str string) (protocol.EdgeStatus, error) { + s := strings.TrimSpace(strings.ToLower(str)) + switch s { + case "pending": + return protocol.EdgePending, nil + case "confirmed": + return protocol.EdgeConfirmed, nil + } + return protocol.EdgePending, errors.New("unknown edge status, expected pending or confirmed") +} + +// EdgeByIdentifier fetches an edge by its specific id in a challenge. +// +// method: +// - GET +// - /api/v1/challenge//edges/id/ +// +// identifier options: +// - 0x-prefixed assertion hash +// - 0x-prefixed edge id +// +// query params: +// - force_update: refetch the updatable fields of each item in the response +// +// response: +// - *JsonEdge +func (s *Server) EdgeByIdentifier(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + assertionHashStr := vars["assertion-hash"] + edgeIdStr := vars["edge-id"] + hash, err := hexutil.Decode(assertionHashStr) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse assertion hash: %v", err), http.StatusBadRequest) + return + } + id, err := hexutil.Decode(edgeIdStr) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse edge id: %v", err), http.StatusBadRequest) + return + } + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash(hash)} + edgeId := protocol.EdgeId{Hash: common.BytesToHash(id)} + opts := []db.EdgeOption{ + db.WithLimit(1), + db.WithEdgeAssertionHash(assertionHash), + db.WithId(edgeId), + } + query := r.URL.Query() + if _, ok := query["force_update"]; ok { + opts = append(opts, db.WithEdgeForceUpdate()) + } + edges, err := s.backend.GetEdges( + r.Context(), + opts..., + ) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get edges from backend: %v", err), http.StatusInternalServerError) + return + } + if len(edges) != 1 { + http.Error(w, fmt.Sprintf("Got more edges than expected: %d", len(edges)), http.StatusInternalServerError) + return + } + writeJSONResponse(w, edges[0]) +} + +// RoyalTrackedChallengeEdges dumps the locally-tracked, royal edges kept in-memory by the BOLD software. +// +// method: +// - GET +// - /api/v1/tracked/royal-edges +func (s *Server) RoyalTrackedChallengeEdges(w http.ResponseWriter, r *http.Request) { + resp, err := s.backend.GetTrackedRoyalEdges(r.Context()) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get tracked royal edges: %v", err), http.StatusInternalServerError) + return + } + writeJSONResponse(w, resp) +} + +// EdgeByHistoryCommitment fetches an edge by its specific history commitment in a challenge. +// +// method: +// - GET +// - /api/v1/challenge//edges/history/ +// +// identifier options: +// - 0x-prefixed assertion hash +// - history commitment with the format startheight:starthash:endheight:endhash, such as +// 0:0xdeadbeef:32:0xdeadbeef +// +// query params: +// - force_update: refetch the updatable fields of each item in the response +// +// response: +// - *JsonEdge +func (s *Server) EdgeByHistoryCommitment(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + assertionHashStr := vars["assertion-hash"] + hash, err := hexutil.Decode(assertionHashStr) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse assertion hash: %v", err), http.StatusBadRequest) + return + } + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash(hash)} + historyCommitment := vars["history-commitment"] + parts := strings.Split(historyCommitment, ":") + if len(parts) != 4 { + http.Error(w, "Wrong history commitment format, wanted startheight:starthash:endheight:endhash", http.StatusBadRequest) + return + } + startHeight, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse start height: %v", err), http.StatusBadRequest) + return + } + startHash, err := hexutil.Decode(parts[1]) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse start hash: %v", err), http.StatusBadRequest) + return + } + endHeight, err := strconv.ParseUint(parts[2], 10, 64) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse end height: %v", err), http.StatusBadRequest) + return + } + endHash, err := hexutil.Decode(parts[3]) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse end hash: %v", err), http.StatusBadRequest) + return + } + opts := []db.EdgeOption{ + db.WithEdgeAssertionHash(assertionHash), + db.WithStartHistoryCommitment(history.History{ + Height: startHeight, + Merkle: common.BytesToHash(startHash), + }), + db.WithEndHistoryCommitment(history.History{ + Height: endHeight, + Merkle: common.BytesToHash(endHash), + }), + db.WithLimit(1), + } + query := r.URL.Query() + if _, ok := query["force_update"]; ok { + opts = append(opts, db.WithEdgeForceUpdate()) + } + edges, err := s.backend.GetEdges( + r.Context(), + opts..., + ) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get edges from backend: %v", err), http.StatusInternalServerError) + return + } + if len(edges) != 1 { + http.Error(w, fmt.Sprintf("Got more edges than expected: %d", len(edges)), http.StatusInternalServerError) + return + } + writeJSONResponse(w, edges[0]) +} + +// MiniStakes fetches all the mini-stakes present in a single challenged assertion. +// +// method: +// - GET +// - /api/v1/challenge//ministakes +// +// identifier options: +// - 0x-prefixed assertion hash +// +// request query params: +// - limit: the max number of items in the response +// - offset: the offset index in the DB +// - force_update: refetch the updatable fields of each item in the response +// - challenge_level: items in a specific challenge level. level 0 is the block challenge level +// response: +// - []*MiniStake +func (s *Server) MiniStakes(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + assertionHashStr := vars["assertion-hash"] + hash, err := hexutil.Decode(assertionHashStr) + if err != nil { + http.Error(w, fmt.Sprintf("Could not parse assertion hash: %v", err), http.StatusBadRequest) + return + } + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash(hash)} + query := r.URL.Query() + opts := make([]db.EdgeOption, 0) + if val, ok := query["limit"]; ok && len(val) > 0 { + if v, err2 := strconv.Atoi(val[0]); err2 == nil { + opts = append(opts, db.WithLimit(v)) + } + } + if val, ok := query["offset"]; ok && len(val) > 0 { + if v, err2 := strconv.Atoi(val[0]); err2 == nil { + opts = append(opts, db.WithOffset(v)) + } + } + if val, ok := query["challenge_level"]; ok && len(val) > 0 { + if v, err2 := strconv.ParseUint(val[0], 10, 8); err2 == nil { + opts = append(opts, db.WithChallengeLevel(uint8(v))) + } + } + if _, ok := query["force_update"]; ok { + opts = append(opts, db.WithEdgeForceUpdate()) + } + miniStakes, err := s.backend.GetMiniStakes(r.Context(), assertionHash, opts...) + if err != nil { + http.Error(w, fmt.Sprintf("Could not get ministakes from backend: %v", err), http.StatusInternalServerError) + return + } + writeJSONResponse(w, miniStakes) +} + +func writeJSONResponse(w http.ResponseWriter, data any) { + body, err := json.Marshal(data) + if err != nil { + http.Error(w, fmt.Sprintf("Could not write response: %v", err), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", contentType) + w.WriteHeader(http.StatusOK) + _, err = w.Write(body) + if err != nil { + log.Error("could not write response body", "err", err, "status", http.StatusInternalServerError) + return + } +} diff --git a/bold/api/server/server.go b/bold/api/server/server.go new file mode 100644 index 0000000000..20b67d3047 --- /dev/null +++ b/bold/api/server/server.go @@ -0,0 +1,88 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package server defines the client-facing API methods for fetching data +// related to BOLD challenges. It handles HTTP methods with their requests and responses. +package server + +import ( + "context" + "errors" + "net/http" + "time" + + "github.com/gorilla/mux" + + "github.com/ethereum/go-ethereum/log" + + "github.com/offchainlabs/bold/api/backend" + "github.com/offchainlabs/bold/util/stopwaiter" +) + +var apiVersion = "/api/v1" + +type Server struct { + stopwaiter.StopWaiter + srv *http.Server + router *mux.Router + registered bool + backend backend.BusinessLogicProvider +} + +func New(addr string, backend backend.BusinessLogicProvider) (*Server, error) { + if addr == "" { + addr = ":8080" + } + r := mux.NewRouter() + + s := &Server{ + backend: backend, + srv: &http.Server{ + Handler: r, + Addr: addr, + WriteTimeout: 15 * time.Second, + ReadTimeout: 30 * time.Second, + ReadHeaderTimeout: 30 * time.Second, + }, + router: r, + } + if err := s.registerMethods(); err != nil { + return nil, err + } + return s, nil +} + +func (s *Server) Start(ctx context.Context) error { + s.StopWaiter.Start(ctx, s) + go func() { + <-ctx.Done() + if err := s.srv.Shutdown(ctx); err != nil { + log.Error("Could not shutdown API server", "err", err) + } + }() + return s.srv.ListenAndServe() +} + +func (s *Server) Addr() string { + return s.srv.Addr +} + +func (s *Server) registerMethods() error { + if s.registered { + return errors.New("API server methods already registered") + } + + r := s.router.PathPrefix(apiVersion).Subrouter() + r.HandleFunc("/healthz", s.Healthz).Methods("GET") + r.HandleFunc("/assertions", s.ListAssertions).Methods("GET") + r.HandleFunc("/assertions/{identifier}", s.AssertionByIdentifier).Methods("GET") + r.HandleFunc("/challenge/{assertion-hash}/edges", s.AllChallengeEdges).Methods("GET") + r.HandleFunc("/challenge/{assertion-hash}/edges/id/{edge-id}", s.EdgeByIdentifier).Methods("GET") + r.HandleFunc("/challenge/{assertion-hash}/edges/history/{history-commitment}", s.EdgeByHistoryCommitment).Methods("GET") + r.HandleFunc("/challenge/{assertion-hash}/ministakes", s.MiniStakes).Methods("GET") + r.HandleFunc("/tracked/royal-edges", s.RoyalTrackedChallengeEdges).Methods("GET") + r.HandleFunc("/state-provider/requests/collect-machine-hashes", s.CollectMachineHashes).Methods("GET") + s.registered = true + return nil +} diff --git a/bold/api/types.go b/bold/api/types.go new file mode 100644 index 0000000000..0e821e7d5b --- /dev/null +++ b/bold/api/types.go @@ -0,0 +1,127 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package api + +import ( + "reflect" + "time" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +type JsonAssertion struct { + Hash common.Hash `json:"hash" db:"Hash"` + ConfirmPeriodBlocks uint64 `json:"confirmPeriodBlocks" db:"ConfirmPeriodBlocks"` + RequiredStake string `json:"requiredStake" db:"RequiredStake"` + ParentAssertionHash common.Hash `json:"parentAssertionHash" db:"ParentAssertionHash"` + InboxMaxCount string `json:"inboxMaxCount" db:"InboxMaxCount"` + AfterInboxBatchAcc common.Hash `json:"afterInboxBatchAcc" db:"AfterInboxBatchAcc"` + WasmModuleRoot common.Hash `json:"wasmModuleRoot" db:"WasmModuleRoot"` + ChallengeManager common.Address `json:"challengeManager" db:"ChallengeManager"` + CreationBlock uint64 `json:"creationBlock" db:"CreationBlock"` + TransactionHash common.Hash `json:"transactionHash" db:"TransactionHash"` + BeforeStateBlockHash common.Hash `json:"beforeStateBlockHash" db:"BeforeStateBlockHash"` + BeforeStateSendRoot common.Hash `json:"beforeStateSendRoot" db:"BeforeStateSendRoot"` + BeforeStateBatch uint64 `json:"beforeStateBatch" db:"BeforeStateBatch"` + BeforeStatePosInBatch uint64 `json:"beforeStatePosInBatch" db:"BeforeStatePosInBatch"` + BeforeStateMachineStatus protocol.MachineStatus `json:"beforeStateMachineStatus" db:"BeforeStateMachineStatus"` + AfterStateBlockHash common.Hash `json:"afterStateBlockHash" db:"AfterStateBlockHash"` + AfterStateSendRoot common.Hash `json:"afterStateSendRoot" db:"AfterStateSendRoot"` + AfterStateBatch uint64 `json:"afterStateBatch" db:"AfterStateBatch"` + AfterStatePosInBatch uint64 `json:"afterStatePosInBatch" db:"AfterStatePosInBatch"` + AfterStateMachineStatus protocol.MachineStatus `json:"afterStateMachineStatus" db:"AfterStateMachineStatus"` + FirstChildBlock *uint64 `json:"firstChildBlock" db:"FirstChildBlock"` + SecondChildBlock *uint64 `json:"secondChildBlock" db:"SecondChildBlock"` + IsFirstChild bool `json:"isFirstChild" db:"IsFirstChild"` + Status string `json:"status" db:"Status"` + LastUpdatedAt time.Time `json:"lastUpdatedAt" db:"LastUpdatedAt"` +} + +type JsonEdge struct { + Id common.Hash `json:"id" db:"Id"` + ChallengeLevel uint8 `json:"challengeLevel" db:"ChallengeLevel"` + StartHistoryRoot common.Hash `json:"startHistoryRoot" db:"StartHistoryRoot"` + StartHeight uint64 `json:"startHeight" db:"StartHeight"` + EndHistoryRoot common.Hash `json:"endHistoryRoot" db:"EndHistoryRoot"` + EndHeight uint64 `json:"endHeight" db:"EndHeight"` + CreatedAtBlock uint64 `json:"createdAtBlock" db:"CreatedAtBlock"` + MutualId common.Hash `json:"mutualId" db:"MutualId"` + OriginId common.Hash `json:"originId" db:"OriginId"` + ClaimId common.Hash `json:"claimId" db:"ClaimId"` + HasChildren bool `json:"hasChildren" db:"HasChildren"` + LowerChildId common.Hash `json:"lowerChildId" db:"LowerChildId"` + UpperChildId common.Hash `json:"upperChildId" db:"UpperChildId"` + MiniStaker common.Address `json:"miniStaker" db:"MiniStaker"` + AssertionHash common.Hash `json:"assertionHash" db:"AssertionHash"` + TimeUnrivaled uint64 `json:"timeUnrivaled" db:"TimeUnrivaled"` + HasRival bool `json:"hasRival" db:"HasRival"` + Status string `json:"status" db:"Status"` + HasLengthOneRival bool `json:"hasLengthOneRival" db:"HasLengthOneRival"` + LastUpdatedAt time.Time `json:"lastUpdatedAt" db:"LastUpdatedAt"` + // Honest validator's point of view + Ancestors []common.Hash `json:"ancestors"` + RawAncestors string `json:"-" db:"RawAncestors"` + IsRoyal bool `json:"isRoyal" db:"IsRoyal"` + CumulativePathTimer uint64 `json:"cumulativePathTimer" db:"CumulativePathTimer"` + InheritedTimer uint64 `json:"inheritedTimer" db:"InheritedTimer"` + RefersTo string `json:"refersTo" db:"RefersTo"` + FSMState string `json:"fsmState"` + FSMError string `json:"fsmError"` +} + +type JsonTrackedRoyalEdge struct { + Id common.Hash `json:"id"` + ChallengeLevel uint8 `json:"challengeLevel"` + StartHistoryRoot common.Hash `json:"startHistoryRoot"` + StartHeight uint64 `json:"startHeight"` + EndHistoryRoot common.Hash `json:"endHistoryRoot"` + EndHeight uint64 `json:"endHeight"` + CreatedAtBlock uint64 `json:"createdAtBlock"` + MutualId common.Hash `json:"mutualId"` + OriginId common.Hash `json:"originId"` + ClaimId common.Hash `json:"claimId"` + MiniStaker common.Address `json:"miniStaker" db:"MiniStaker"` + AssertionHash common.Hash `json:"assertionHash" db:"AssertionHash"` + TimeUnrivaled uint64 `json:"timeUnrivaled" db:"TimeUnrivaled"` + HasRival bool `json:"hasRival" db:"HasRival"` + Ancestors []common.Hash `json:"ancestors"` +} + +type JsonEdgesByChallengedAssertion struct { + AssertionHash common.Hash `json:"challengedAssertionHash"` + RoyalEdges []*JsonTrackedRoyalEdge `json:"royalEdges"` +} + +type JsonMiniStakes struct { + ChallengedAssertionHash common.Hash `json:"challengedAssertionHash"` + StakesByLvlAndOrigin map[protocol.ChallengeLevel][]*JsonMiniStakeInfo `json:"stakesByLvlAndOrigin"` +} + +type JsonMiniStakeInfo struct { + ChallengeOriginId common.Hash `json:"challengeOriginId"` + StakerAddresses []common.Address `json:"stakerAddresses"` + NumberOfMiniStakes uint64 `json:"numberOfMiniStakes"` +} + +type JsonCollectMachineHashes struct { + WasmModuleRoot common.Hash `json:"wasmModuleRoot" db:"WasmModuleRoot"` + FromBatch uint64 `json:"fromBatch" db:"FromBatch"` + PositionInBatch uint64 `json:"positionInBatch" db:"PositionInBatch"` + BatchLimit uint64 `json:"batchLimit" db:"BatchLimit"` + BlockChallengeHeight uint64 `json:"blockChallengeHeight" db:"BlockChallengeHeight"` + StepHeights []uint64 `json:"stepHeights"` + RawStepHeights string `json:"-" db:"RawStepHeights"` + NumDesiredHashes uint64 `json:"numDesiredHashes" db:"NumDesiredHashes"` + MachineStartIndex uint64 `json:"machineStartIndex" db:"MachineStartIndex"` + StepSize uint64 `json:"stepSize" db:"StepSize"` + StartTime time.Time `json:"startTime" db:"StartTime"` + FinishTime *time.Time `json:"finishTime" db:"FinishTime"` +} + +func IsNil(i any) bool { + return i == nil || reflect.ValueOf(i).IsNil() +} diff --git a/bold/assertions/confirmation.go b/bold/assertions/confirmation.go new file mode 100644 index 0000000000..e55d6628e2 --- /dev/null +++ b/bold/assertions/confirmation.go @@ -0,0 +1,161 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package assertions + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/log" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + "github.com/offchainlabs/bold/challenge-manager/types" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/logs/ephemeral" + retry "github.com/offchainlabs/bold/runtime" +) + +func (m *Manager) queueCanonicalAssertionsForConfirmation(ctx context.Context) { + for { + select { + case canonical := <-m.observedCanonicalAssertions: + m.LaunchThread(func(ctx context.Context) { m.keepTryingAssertionConfirmation(ctx, canonical) }) + case <-ctx.Done(): + return + } + } +} + +func (m *Manager) keepTryingAssertionConfirmation(ctx context.Context, assertionHash protocol.AssertionHash) { + // Only resolve mode strategies or higher should be confirming assertions. + if m.mode < types.ResolveMode { + return + } + creationInfo, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return m.chain.ReadAssertionCreationInfo(ctx, assertionHash) + }) + if err != nil { + log.Error("Could not get assertion creation info", "err", err) + return + } + if m.enableFastConfirmation { + var confirmed bool + confirmed, err = m.chain.FastConfirmAssertion(ctx, creationInfo) + if err != nil { + log.Error("Could not fast confirm latest assertion", "err", err) + } else if confirmed { + assertionConfirmedCounter.Inc(1) + log.Info("Fast Confirmed assertion", "assertionHash", creationInfo.AssertionHash) + return + } + } + prevCreationInfo, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return m.chain.ReadAssertionCreationInfo(ctx, creationInfo.ParentAssertionHash) + }) + if err != nil { + log.Error("Could not get prev assertion creation info", "err", err) + return + } + exceedsMaxMempoolSizeEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "posting this transaction will exceed max mempool size", 0) + gasEstimationEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "gas estimation errored for tx with hash", 0) + ticker := time.NewTicker(m.times.confInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + if m.enableFastConfirmation { + var confirmed bool + confirmed, err = m.chain.FastConfirmAssertion(ctx, creationInfo) + if err != nil { + log.Error("Could not fast confirm latest assertion", "err", err) + } else if confirmed { + assertionConfirmedCounter.Inc(1) + log.Info("Fast Confirmed assertion", "assertionHash", creationInfo.AssertionHash) + return + } + } + opts := m.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + parentAssertion, err := m.chain.GetAssertion( + ctx, + opts, + creationInfo.ParentAssertionHash, + ) + if err != nil { + log.Error("Could not get parent assertion", "err", err) + continue + } + parentAssertionHasSecondChild, err := parentAssertion.HasSecondChild(ctx, opts) + if err != nil { + log.Error("Could not confirm if parent assertion has second child", "err", err) + continue + } + // Assertions that have a rival assertion cannot be confirmed by time. + if parentAssertionHasSecondChild { + return + } + confirmed, err := solimpl.TryConfirmingAssertion(ctx, creationInfo.AssertionHash, prevCreationInfo.ConfirmPeriodBlocks+creationInfo.CreationL1Block, m.chain, m.times.avgBlockTime, option.None[protocol.EdgeId]()) + if err != nil { + if !strings.Contains(err.Error(), "PREV_NOT_LATEST_CONFIRMED") { + logLevel := log.Error + logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) + logLevel = gasEstimationEphemeralErrorHandler.LogLevel(err, logLevel) + + logLevel("Could not confirm assertion", "err", err, "assertionHash", assertionHash.Hash) + errorConfirmingAssertionByTimeCounter.Inc(1) + } + continue + } + + exceedsMaxMempoolSizeEphemeralErrorHandler.Reset() + gasEstimationEphemeralErrorHandler.Reset() + + if confirmed { + assertionConfirmedCounter.Inc(1) + log.Info("Confirmed assertion by time", "assertionHash", creationInfo.AssertionHash) + return + } + } + } +} + +func (m *Manager) updateLatestConfirmedMetrics(ctx context.Context) { + ticker := time.NewTicker(m.times.confInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + latestConfirmed, err := m.chain.LatestConfirmed(ctx, m.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + log.Debug("Could not fetch latest confirmed assertion", "err", err) + continue + } + info, err := m.chain.ReadAssertionCreationInfo(ctx, latestConfirmed.Id()) + if err != nil { + log.Debug("Could not fetch latest confirmed assertion", "err", err) + continue + } + afterState := protocol.GoExecutionStateFromSolidity(info.AfterState) + log.Info("Latest confirmed assertion", "assertionAfterState", fmt.Sprintf("%+v", afterState)) + + // TODO: Check if the latest assertion that was confirmed is one we agree with. + latestConfirmedBlockNum, err := safecast.ToInt64(latestConfirmed.CreatedAtBlock()) + if err != nil { + log.Error("Could not convert latest confirmed block number to int64", "err", err) + continue + } + latestConfirmedAssertionGauge.Update(latestConfirmedBlockNum) + case <-ctx.Done(): + return + } + } +} diff --git a/bold/assertions/manager.go b/bold/assertions/manager.go new file mode 100644 index 0000000000..babd20134a --- /dev/null +++ b/bold/assertions/manager.go @@ -0,0 +1,410 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package assertions contains testing utilities for posting and scanning for +// assertions on chain, which are useful for simulating the responsibilities of +// Arbitrum Nitro and initiating challenges as needed using our challenge +// manager. +package assertions + +import ( + "context" + "math/big" + "sync" + "time" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/challenge-manager/types" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + retry "github.com/offchainlabs/bold/runtime" + "github.com/offchainlabs/bold/util/stopwaiter" +) + +var ( + evilAssertionCounter = metrics.NewRegisteredCounter("arb/validator/scanner/evil_assertion", nil) + assertionConfirmedCounter = metrics.GetOrRegisterCounter("arb/validator/scanner/assertion_confirmed", nil) + errorConfirmingAssertionByTimeCounter = metrics.NewRegisteredCounter("arb/validator/scanner/error_confirming_assertion_by_time", nil) + latestConfirmedAssertionGauge = metrics.NewRegisteredGauge("arb/validator/scanner/latest_confirmed_assertion_block_number", nil) + safeBlockDelayCounter = metrics.GetOrRegisterCounter("arb/validator/scanner/safe_block_delay", nil) +) + +type timings struct { + pollInterval time.Duration + confInterval time.Duration + postInterval time.Duration + avgBlockTime time.Duration + minGapToParent time.Duration +} + +var defaultTimings = timings{ + pollInterval: time.Minute, + confInterval: time.Second * 10, + postInterval: time.Hour, + avgBlockTime: time.Second * 12, + minGapToParent: time.Minute * 15, +} + +// The Manager struct is responsible for several tasks related to the assertion +// chain: +// +// 1. It continuously polls the assertion chain to check for posted, on-chain +// assertions starting from the latest confirmed assertion up to the newest one. +// 2. As the assertion chain advances, the Manager keeps polling to stay +// updated. +// 3. Upon observing each new assertion, the Manager evaluates whether it should +// challenge the assertion or not. +// 4. The Manager frequently posts new assertions to the assertion chain at +// specific intervals. +// 5. When posting assertions, it relies on the most recent execution state +// available in its local execution provider. +type Manager struct { + stopwaiter.StopWaiter + chain protocol.AssertionChain + backend protocol.ChainBackend + execProvider l2stateprovider.ExecutionProvider + times timings + rollupAddr common.Address + validatorName string + forksDetectedCount uint64 + assertionsProcessedCount uint64 + submittedRivalsCount uint64 + submittedAssertions *threadsafe.LruSet[protocol.AssertionHash] + apiDB db.Database + assertionChainData *assertionChainData + observedCanonicalAssertions chan protocol.AssertionHash + isReadyToPost bool + disablePosting bool + startPostingSignal chan struct{} + enableFastConfirmation bool + mode types.Mode + rivalHandler types.RivalHandler + delegatedStaking bool + autoDeposit bool + autoAllowanceApproval bool + maxGetLogBlocks uint64 + confirming *threadsafe.LruSet[protocol.AssertionHash] + confirmQueueMutex sync.Mutex +} + +type assertionChainData struct { + sync.RWMutex + latestAgreedAssertion protocol.AssertionHash + canonicalAssertions map[protocol.AssertionHash]*protocol.AssertionCreatedInfo +} + +type Opt func(*Manager) + +func WithPostingDisabled() Opt { + return func(m *Manager) { + m.disablePosting = true + } +} + +func WithFastConfirmation() Opt { + return func(m *Manager) { + m.enableFastConfirmation = true + } +} + +func WithDangerousReadyToPost() Opt { + return func(m *Manager) { + m.isReadyToPost = true + } +} + +func WithDelegatedStaking() Opt { + return func(m *Manager) { + m.delegatedStaking = true + } +} + +func WithoutAutoDeposit() Opt { + return func(m *Manager) { + m.autoDeposit = false + } +} + +func WithoutAutoAllowanceApproval() Opt { + return func(m *Manager) { + m.autoAllowanceApproval = false + } +} + +// WithAPIDB sets the database to use for the assertion manager. +func WithAPIDB(db db.Database) Opt { + return func(m *Manager) { + m.apiDB = db + } +} + +// WithPostingInterval overrides the default posting interval. +// +// This interval is the amount of time the assertsion manager will wait between +// attempts to post assertions, unless the previous assertion was an overflow +// asseartion. If the previous assertion was an overflow assertion, and the +// assertion manager has the data it needs to post an additional assertion, +// it will disregard the posting interval and post right away. +func WithPostingInterval(t time.Duration) Opt { + return func(m *Manager) { + m.times.postInterval = t + } +} + +// WithPollingInterval overrides the default polling interval. +// +// This interval is the amount of time the assertion manager will wait between +// atteampts to read new asseartions from the parent chain. +func WithPollingInterval(t time.Duration) Opt { + return func(m *Manager) { + m.times.pollInterval = t + } +} + +// WithConfirmationInterval overrides the default a confiramtion interval. +// +// This is the interval the assertion manager will wait between attempts to +// persist information about which assertions can be confirmed to the parent +// chain. +func WithConfirmationInterval(t time.Duration) Opt { + return func(m *Manager) { + m.times.confInterval = t + } +} + +// WithAverageBlockCreationTime overrides the default average block creation +// time. +// +// The average block cretion time is used by the assertion manager to emit +// warnings if the parent chain hasn't had any new blocks for considerably +// longer than this expected delay. +func WithAverageBlockCreationTime(t time.Duration) Opt { + return func(m *Manager) { + m.times.avgBlockTime = t + } +} + +// WithMaxGetLogBlocks overrides the default maximum number of blocks to get +// logs for in a single call. +func WithMaxGetLogBlocks(n uint64) Opt { + return func(m *Manager) { + m.maxGetLogBlocks = n + } +} + +// WithMinimumGapToParentAssertion overrides the default minimum gap (in duration) +// to parent assertion creation time. +// +// The minimum gap to parent assertion is used by the assertion manager to wait +// until this much amount of duration is passed since the parent assertion was created +// before posting a new assertion. +func WithMinimumGapToParentAssertion(t time.Duration) Opt { + return func(m *Manager) { + m.times.minGapToParent = t + } +} + +// NewManager creates a manager from the required dependencies. +func NewManager( + chain protocol.AssertionChain, + execProvider l2stateprovider.ExecutionProvider, + validatorName string, + mode types.Mode, + opts ...Opt, +) (*Manager, error) { + maxAssertions, err := safecast.ToInt(chain.MaxAssertionsPerChallengePeriod()) + if err != nil { + return nil, errors.Wrap(err, "could not convert max assertions to int") + } + m := &Manager{ + chain: chain, + apiDB: nil, + backend: chain.Backend(), + execProvider: execProvider, + rollupAddr: chain.RollupAddress(), + validatorName: validatorName, + times: defaultTimings, + forksDetectedCount: 0, + assertionsProcessedCount: 0, + submittedAssertions: threadsafe.NewLruSet(maxAssertions, threadsafe.LruSetWithMetric[protocol.AssertionHash]("submittedAssertions")), + assertionChainData: &assertionChainData{ + latestAgreedAssertion: protocol.AssertionHash{}, + canonicalAssertions: make(map[protocol.AssertionHash]*protocol.AssertionCreatedInfo), + }, + observedCanonicalAssertions: make(chan protocol.AssertionHash, maxAssertions), + isReadyToPost: false, + startPostingSignal: make(chan struct{}), + mode: mode, + rivalHandler: nil, // Must be set after construction if mode > DefensiveMode + autoDeposit: true, + autoAllowanceApproval: true, + maxGetLogBlocks: 1000, + confirming: threadsafe.NewLruSet[protocol.AssertionHash](maxAssertions), + confirmQueueMutex: sync.Mutex{}, + } + for _, o := range opts { + o(m) + } + if m.times.pollInterval == 0 { + return nil, errors.New("assertion polling interval must be greater than 0") + } + if m.times.confInterval == 0 { + return nil, errors.New("assertion confirmation attempt interval must be greater than 0") + } + return m, nil +} + +// SetRivalHandler sets the rival handler for the assertion manager. +func (m *Manager) SetRivalHandler(handler types.RivalHandler) { + m.rivalHandler = handler +} + +func (m *Manager) Start(ctx context.Context) { + m.StopWaiter.Start(ctx, m) + if m.mode != types.WatchTowerMode { + if m.delegatedStaking { + // Attempt to become a new staker onchain until successful. + // This is only relevant for delegated stakers that will be funded + // by another party. + _, err := retry.UntilSucceeds(ctx, func() (bool, error) { + if err2 := m.chain.NewStake(ctx); err2 != nil { + return false, err2 + } + return true, nil + }) + if err != nil { + log.Error("Could not become a delegated staker onchain", "err", err) + return + } + } + if m.autoDeposit { + // Attempt to auto-deposit funds until successful into the stake token. + _, err := retry.UntilSucceeds(ctx, func() (bool, error) { + callOpts := m.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + latestConfirmed, err2 := m.chain.LatestConfirmed(ctx, callOpts) + if err2 != nil { + return false, err2 + } + latestConfirmedInfo, err2 := m.chain.ReadAssertionCreationInfo(ctx, latestConfirmed.Id()) + if err2 != nil { + return false, err2 + } + if err2 := m.chain.AutoDepositTokenForStaking(ctx, latestConfirmedInfo.RequiredStake); err2 != nil { + return false, err2 + } + return true, nil + }) + if err != nil { + log.Error("Could not auto-deposit funds to become a staker", "err", err) + return + } + } + if m.autoAllowanceApproval { + // Attempt to auto-approve the stake token spending by the challenge manager + // and rollup address until successful. + _, err := retry.UntilSucceeds(ctx, func() (bool, error) { + if err2 := m.chain.ApproveAllowances(ctx); err2 != nil { + return false, err2 + } + return true, nil + }) + if err != nil { + log.Error("Could not auto-approve allowances", "err", err) + return + } + } + } + if !m.disablePosting { + m.LaunchThread(m.postAssertionRoutine) + } + m.LaunchThread(m.updateLatestConfirmedMetrics) + m.LaunchThread(m.syncAssertions) + m.LaunchThread(m.queueCanonicalAssertionsForConfirmation) + m.LaunchThread(m.checkLatestDesiredBlock) +} + +func (m *Manager) checkLatestDesiredBlock(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Minute): + latestSafeBlock, err := m.backend.HeaderByNumber(ctx, big.NewInt(int64(rpc.SafeBlockNumber))) + if err != nil { + log.Error("Error getting latest safe block", "err", err) + continue + } + if !latestSafeBlock.Number.IsUint64() { + log.Error("Latest safe block number not a uint64") + continue + } + + latestBlock, err := m.backend.HeaderByNumber(ctx, nil) + if err != nil { + log.Error("Error getting latest block", "err", err) + continue + } + if !latestBlock.Number.IsUint64() { + log.Error("Latest block number not a uint64") + continue + } + latestBlockTime, err := safecast.ToInt64(latestBlock.Time) + if err != nil { + log.Error("Error casting latest block time to int64", "err", err) + continue + } + latestSafeBlockTime, err := safecast.ToInt64(latestSafeBlock.Time) + if err != nil { + log.Error("Error casting latest safe block time to int64", "err", err) + continue + } + safeBlockDelayInSeconds := time.Unix(latestBlockTime, 0).Sub(time.Unix(latestSafeBlockTime, 0)).Seconds() + if safeBlockDelayInSeconds > 1200 { + log.Warn("Latest safe block is delayed by more that 20 minutes", "latestSafeBlock", latestSafeBlock.Number.Uint64(), "latestBlock", latestBlock.Number.Uint64()) + safeBlockDelayCounter.Inc(1) + } + } + } +} + +func (m *Manager) ExecutionStateAfterParent(ctx context.Context, parentInfo *protocol.AssertionCreatedInfo) (*protocol.ExecutionState, error) { + goGlobalState := protocol.GoGlobalStateFromSolidity(parentInfo.AfterState.GlobalState) + return m.execProvider.ExecutionStateAfterPreviousState(ctx, parentInfo.InboxMaxCount.Uint64(), goGlobalState) +} + +func (m *Manager) ForksDetected() uint64 { + return m.forksDetectedCount +} + +func (m *Manager) AssertionsProcessed() uint64 { + return m.assertionsProcessedCount +} + +func (m *Manager) SubmittedRivals() uint64 { + return m.submittedRivalsCount +} + +func (m *Manager) AssertionsSubmittedInProcess() []protocol.AssertionHash { + hashes := make([]protocol.AssertionHash, 0) + m.submittedAssertions.ForEach(func(elem protocol.AssertionHash) { + hashes = append(hashes, elem) + }) + return hashes +} + +func (m *Manager) LatestAgreedAssertion() protocol.AssertionHash { + m.assertionChainData.RLock() + defer m.assertionChainData.RUnlock() + return m.assertionChainData.latestAgreedAssertion +} diff --git a/bold/assertions/manager_test.go b/bold/assertions/manager_test.go new file mode 100644 index 0000000000..1995f5cea1 --- /dev/null +++ b/bold/assertions/manager_test.go @@ -0,0 +1,594 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package assertions_test + +import ( + "context" + "math/big" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/assertions" + protocol "github.com/offchainlabs/bold/chain-abstraction" + cm "github.com/offchainlabs/bold/challenge-manager" + "github.com/offchainlabs/bold/challenge-manager/types" + retry "github.com/offchainlabs/bold/runtime" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/casttest" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func TestSkipsProcessingAssertionFromEvilFork(t *testing.T) { + t.Skip("Flakey test, needs investigation") + testData, err := setup.ChainsWithEdgeChallengeManager( + setup.WithMockOneStepProver(), + setup.WithMockBridge(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 64, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + ) + require.NoError(t, err) + + bridgeBindings, err := mocksgen.NewBridgeStub(testData.Addrs.Bridge, testData.Backend) + require.NoError(t, err) + + msgCount, err := bridgeBindings.SequencerMessageCount(testData.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, uint64(1), msgCount.Uint64()) + + aliceChain := testData.Chains[0] + bobChain := testData.Chains[1] + charlieChain := testData.Chains[2] + + ctx := context.Background() + genesisHash, err := testData.Chains[1].GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisCreationInfo, err := testData.Chains[1].ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + + stateManagerOpts := testData.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + ) + aliceStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + // Bob diverges from Alice at batch 1. + stateManagerOpts = testData.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + statemanager.WithBlockDivergenceHeight(1), + statemanager.WithMachineDivergenceStep(1), + ) + bobStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + aliceChalManager, err := cm.NewChallengeStack( + aliceChain, + aliceStateManager, + cm.StackWithMode(types.DefensiveMode), + cm.StackWithName("alice"), + ) + require.NoError(t, err) + aliceChalManager.Start(ctx) + + // We have bob post an assertion at batch 1. + // + // It is important that this assertion is posted after alice's challenge + // manager is started, and before Charlie's assertion manager is started. If + // this happens before alice's challenge manager then, alice and charlie will + // race to post the rival assertion to the one from bob. Even though, alice's + // polling interval is a minute, the very first time she poll's, she'll + // already see bob's rival assertion and attempt to post the rival. Only one + // rival assertion can be posted with identical content, so, if alice wins, + // charlie's attempt below will fail because the rival assertion he is trying + // to post already exists. + genesisGlobalState := protocol.GoGlobalStateFromSolidity(genesisCreationInfo.AfterState.GlobalState) + bobPostState, err := bobStateManager.ExecutionStateAfterPreviousState(ctx, 1, genesisGlobalState) + require.NoError(t, err) + t.Logf("%+v", bobPostState) + bobAssertion, err := bobChain.NewStakeOnNewAssertion( + ctx, + genesisCreationInfo, + bobPostState, + ) + require.NoError(t, err) + bobAssertionInfo, err := bobChain.ReadAssertionCreationInfo(ctx, bobAssertion.Id()) + require.NoError(t, err) + + // Setup an assertion manager for Charlie, and have it process Alice's + // assertion creation event at batch 1. + charlieAssertionManager, err := assertions.NewManager( + charlieChain, + aliceStateManager, + "charlie", + types.DefensiveMode, + assertions.WithPollingInterval(time.Millisecond*200), + assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), + assertions.WithPostingDisabled(), + ) + require.NoError(t, err) + charlieAssertionManager.SetRivalHandler(aliceChalManager) + + charlieAssertionManager.Start(ctx) + + // Check that charlie submitted a rival to Bob's assertion after some time. + // Charlie does this because he agrees with Alice's assertion at batch 1. + time.Sleep(time.Second) + require.Equal(t, uint64(1), charlieAssertionManager.SubmittedRivals()) + + // We have bob post an assertion at batch 2. + dataHash := [32]byte{1} + enqueueSequencerMessageAsExecutor( + t, + testData.Accounts[0].TxOpts, + testData.Addrs.UpgradeExecutor, + testData.Backend, + testData.Addrs.Bridge, + seqMessage{ + dataHash: dataHash, + afterDelayedMessagesRead: big.NewInt(1), + prevMessageCount: big.NewInt(1), + newMessageCount: big.NewInt(2), + }, + ) + + genesisState, err := bobStateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + require.NoError(t, err) + preState, err := bobStateManager.ExecutionStateAfterPreviousState(ctx, 1, genesisState.GlobalState) + require.NoError(t, err) + bobPostState, err = bobStateManager.ExecutionStateAfterPreviousState(ctx, 2, preState.GlobalState) + require.NoError(t, err) + _, err = bobChain.StakeOnNewAssertion( + ctx, + bobAssertionInfo, + bobPostState, + ) + require.NoError(t, err) + + // Once Charlie sees this, he should do nothing as it is from a bad fork and + // he already posted the correct child to their earliest valid ancestor here. + // Charlie should only have attempted to submit 1 honest rival. + time.Sleep(time.Second) + require.Equal(t, uint64(1), charlieAssertionManager.SubmittedRivals()) +} + +func TestComplexAssertionForkScenario(t *testing.T) { + // Chain state looks like this: + // 1 ->2->3->4 + // \->2' + // + // and then we have another validator that disagrees with 4, so Charlie + // should open a 4' that branches off 3. + testData, err := setup.ChainsWithEdgeChallengeManager( + setup.WithMockOneStepProver(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 64, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + ) + require.NoError(t, err) + + bridgeBindings, err := mocksgen.NewBridgeStub(testData.Addrs.Bridge, testData.Backend) + require.NoError(t, err) + + msgCount, err := bridgeBindings.SequencerMessageCount(testData.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, uint64(1), msgCount.Uint64()) + + aliceChain := testData.Chains[0] + bobChain := testData.Chains[1] + + ctx := context.Background() + genesisHash, err := testData.Chains[1].GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisCreationInfo, err := testData.Chains[1].ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + + stateManagerOpts := testData.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + ) + aliceStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + // Bob diverges from Alice at batch 1. + stateManagerOpts = testData.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + statemanager.WithBlockDivergenceHeight(1), + statemanager.WithMachineDivergenceStep(1), + ) + bobStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + genesisState, err := aliceStateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + require.NoError(t, err) + alicePostState, err := aliceStateManager.ExecutionStateAfterPreviousState(ctx, 1, genesisState.GlobalState) + require.NoError(t, err) + + t.Logf("New stake from alice at post state %+v\n", alicePostState) + aliceAssertion, err := aliceChain.NewStakeOnNewAssertion( + ctx, + genesisCreationInfo, + alicePostState, + ) + require.NoError(t, err) + + bobPostState, err := bobStateManager.ExecutionStateAfterPreviousState(ctx, 1, genesisState.GlobalState) + require.NoError(t, err) + _, err = bobChain.NewStakeOnNewAssertion( + ctx, + genesisCreationInfo, + bobPostState, + ) + require.NoError(t, err) + t.Logf("New stake from bob at post state %+v\n", bobPostState) + + // Next, Alice posts more assertions on top of her assertion at batch 1. + // We then create Charlie, who diverges from Alice at batch 4. + count := int64(1) + for batch := 2; batch <= 4; batch++ { + dataHash := [32]byte{1} + enqueueSequencerMessageAsExecutor( + t, + testData.Accounts[0].TxOpts, + testData.Addrs.UpgradeExecutor, + testData.Backend, + testData.Addrs.Bridge, + seqMessage{ + dataHash: dataHash, + afterDelayedMessagesRead: big.NewInt(1), + prevMessageCount: big.NewInt(count), + newMessageCount: big.NewInt(count + 1), + }, + ) + count += 1 + + prevInfo, err2 := aliceChain.ReadAssertionCreationInfo(ctx, aliceAssertion.Id()) + require.NoError(t, err2) + prevGlobalState := protocol.GoGlobalStateFromSolidity(prevInfo.AfterState.GlobalState) + preState, err2 := aliceStateManager.ExecutionStateAfterPreviousState(ctx, casttest.ToUint64(t, batch-1), prevGlobalState) + require.NoError(t, err2) + alicePostState, err2 = aliceStateManager.ExecutionStateAfterPreviousState(ctx, casttest.ToUint64(t, batch), preState.GlobalState) + require.NoError(t, err2) + t.Logf("Moving stake from alice at post state %+v\n", alicePostState) + aliceAssertion, err = aliceChain.StakeOnNewAssertion( + ctx, + prevInfo, + alicePostState, + ) + require.NoError(t, err) + } + + // Charlie should process Alice's creation event and determine it disagrees with batch 4 + // and then post the competing assertion. + charlieChain := testData.Chains[2] + + stateManagerOpts = []statemanager.Opt{ + statemanager.WithNumBatchesRead(5), + statemanager.WithBlockDivergenceHeight(36), // TODO: Make this more intuitive. This translates to batch 4 due to how our mock works. + statemanager.WithMachineDivergenceStep(1), + } + charlieStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + // Setup an assertion manager for Charlie, and have it process Alice's + // assertion creation event at batch 4. + charlieAssertionManager, err := assertions.NewManager( + charlieChain, + charlieStateManager, + "charlie", + types.DefensiveMode, + assertions.WithPollingInterval(time.Millisecond*200), + assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), + assertions.WithPostingDisabled(), + ) + require.NoError(t, err) + + chalManager, err := cm.NewChallengeStack( + charlieChain, + charlieStateManager, + cm.OverrideAssertionManager(charlieAssertionManager), + cm.StackWithMode(types.DefensiveMode), + cm.StackWithName("charlie"), + ) + require.NoError(t, err) + chalManager.Start(ctx) + + time.Sleep(time.Second * 2) + + // Assert that Charlie posted the rival assertion at batch 4. + charlieSubmitted := charlieAssertionManager.AssertionsSubmittedInProcess() + require.Equal(t, true, len(charlieSubmitted) > 0) + charlieAssertion := charlieSubmitted[0] + charlieAssertionInfo, err := charlieChain.ReadAssertionCreationInfo(ctx, charlieAssertion) + require.NoError(t, err) + charliePostState := protocol.GoExecutionStateFromSolidity(charlieAssertionInfo.AfterState) + + // Alice and Charlie batch should match. + require.Equal(t, charliePostState.GlobalState.Batch, alicePostState.GlobalState.Batch) + require.Equal(t, charliePostState.GlobalState.PosInBatch, alicePostState.GlobalState.PosInBatch) + + // But blockhash should not match. + require.NotEqual(t, charliePostState.GlobalState.BlockHash, alicePostState.GlobalState.BlockHash) +} + +func TestFastConfirmation(t *testing.T) { + ctx := context.Background() + testData, err := setup.ChainsWithEdgeChallengeManager( + setup.WithMockOneStepProver(), + setup.WithAutoDeposit(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 64, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + setup.WithFastConfirmation(), + ) + require.NoError(t, err) + + bridgeBindings, err := mocksgen.NewBridgeStub(testData.Addrs.Bridge, testData.Backend) + require.NoError(t, err) + + msgCount, err := bridgeBindings.SequencerMessageCount(testData.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, uint64(1), msgCount.Uint64()) + + aliceChain := testData.Chains[0] + + stateManagerOpts := testData.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + ) + stateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + assertionManager, err := assertions.NewManager( + aliceChain, + stateManager, + "alice", + types.ResolveMode, + assertions.WithPollingInterval(time.Millisecond*200), + assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), + assertions.WithFastConfirmation(), + ) + require.NoError(t, err) + + chalManager, err := cm.NewChallengeStack( + aliceChain, + stateManager, + cm.OverrideAssertionManager(assertionManager), + cm.StackWithMode(types.ResolveMode), + cm.StackWithName("alice"), + ) + require.NoError(t, err) + chalManager.Start(ctx) + + preState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + require.NoError(t, err) + postState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState) + require.NoError(t, err) + + time.Sleep(time.Second) + + posted, err := assertionManager.PostAssertion(ctx) + require.NoError(t, err) + require.Equal(t, true, posted.IsSome()) + creationInfo, err := aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id()) + require.NoError(t, err) + require.Equal(t, postState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState)) + + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + expectAssertionConfirmed(t, ctx, aliceChain.Backend(), aliceChain.RollupAddress()) +} + +func TestFastConfirmationWithSafe(t *testing.T) { + ctx := context.Background() + testData, err := setup.ChainsWithEdgeChallengeManager( + setup.WithAutoDeposit(), + setup.WithMockOneStepProver(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 64, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + setup.WithSafeFastConfirmation(), + ) + require.NoError(t, err) + + bridgeBindings, err := mocksgen.NewBridgeStub(testData.Addrs.Bridge, testData.Backend) + require.NoError(t, err) + + msgCount, err := bridgeBindings.SequencerMessageCount(testData.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, uint64(1), msgCount.Uint64()) + + aliceChain := testData.Chains[0] + bobChain := testData.Chains[1] + + stateManagerOpts := testData.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + ) + stateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + assertionManagerAlice, err := assertions.NewManager( + aliceChain, + stateManager, + "alice", + types.ResolveMode, + assertions.WithPollingInterval(time.Millisecond*200), + assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), + assertions.WithDangerousReadyToPost(), + assertions.WithPostingDisabled(), + assertions.WithFastConfirmation(), + ) + require.NoError(t, err) + + chalManagerAlice, err := cm.NewChallengeStack( + aliceChain, + stateManager, + cm.OverrideAssertionManager(assertionManagerAlice), + cm.StackWithMode(types.ResolveMode), + cm.StackWithName("alice"), + ) + require.NoError(t, err) + chalManagerAlice.Start(ctx) + + preState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + require.NoError(t, err) + postState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState) + require.NoError(t, err) + + time.Sleep(time.Second) + + posted, err := assertionManagerAlice.PostAssertion(ctx) + require.NoError(t, err) + require.Equal(t, true, posted.IsSome()) + creationInfo, err := aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id()) + require.NoError(t, err) + require.Equal(t, postState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState)) + + <-time.After(time.Second) + status, err := aliceChain.AssertionStatus(ctx, posted.Unwrap().Id()) + require.NoError(t, err) + // Just one fast confirmation is not enough to confirm the assertion. + require.Equal(t, protocol.AssertionPending, status) + + assertionManagerBob, err := assertions.NewManager( + bobChain, + stateManager, + "bob", + types.ResolveMode, + assertions.WithPollingInterval(time.Millisecond*200), + assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(0), + assertions.WithDangerousReadyToPost(), + assertions.WithPostingDisabled(), + assertions.WithFastConfirmation(), + ) + require.NoError(t, err) + + chalManagerBob, err := cm.NewChallengeStack( + bobChain, + stateManager, + cm.OverrideAssertionManager(assertionManagerBob), + cm.StackWithMode(types.ResolveMode), + cm.StackWithName("bob"), + ) + require.NoError(t, err) + chalManagerBob.Start(ctx) + + // Only after both Alice and Bob confirm the assertion, it should be confirmed. + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + expectAssertionConfirmed(t, ctx, aliceChain.Backend(), aliceChain.RollupAddress()) +} + +type seqMessage struct { + dataHash common.Hash + afterDelayedMessagesRead *big.Int + prevMessageCount *big.Int + newMessageCount *big.Int +} + +func enqueueSequencerMessageAsExecutor( + t *testing.T, + opts *bind.TransactOpts, + executor common.Address, + backend *setup.SimulatedBackendWrapper, + bridge common.Address, + msg seqMessage, +) { + execBindings, err := mocksgen.NewUpgradeExecutorMock(executor, backend) + require.NoError(t, err) + seqInboxABI, err := abi.JSON(strings.NewReader(bridgegen.AbsBridgeABI)) + require.NoError(t, err) + data, err := seqInboxABI.Pack( + "setSequencerInbox", + executor, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, bridge, data) + require.NoError(t, err) + backend.Commit() + + data, err = seqInboxABI.Pack( + "enqueueSequencerMessage", + msg.dataHash, msg.afterDelayedMessagesRead, msg.prevMessageCount, msg.newMessageCount, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, bridge, data) + require.NoError(t, err) + backend.Commit() +} + +func expectAssertionConfirmed( + t *testing.T, + ctx context.Context, + backend protocol.ChainBackend, + rollupAddr common.Address, +) { + rc, err := rollupgen.NewRollupCore(rollupAddr, backend) + require.NoError(t, err) + var confirmed bool + for ctx.Err() == nil && !confirmed { + i, err := retry.UntilSucceeds(ctx, func() (*rollupgen.RollupCoreAssertionConfirmedIterator, error) { + return rc.FilterAssertionConfirmed(nil, nil) + }) + require.NoError(t, err) + for i.Next() { + assertionNode, err := retry.UntilSucceeds(ctx, func() (rollupgen.AssertionNode, error) { + return rc.GetAssertion(&bind.CallOpts{Context: ctx}, i.Event.AssertionHash) + }) + require.NoError(t, err) + if assertionNode.Status != uint8(protocol.AssertionConfirmed) { + t.Fatal("Confirmed assertion with unfinished state") + } + confirmed = true + break + } + time.Sleep(50 * time.Millisecond) + } + + if !confirmed { + t.Fatal("assertion was not confirmed") + } +} diff --git a/bold/assertions/poster.go b/bold/assertions/poster.go new file mode 100644 index 0000000000..208da9d8d6 --- /dev/null +++ b/bold/assertions/poster.go @@ -0,0 +1,246 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package assertions + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/logs/ephemeral" +) + +var ( + assertionPostedCounter = metrics.NewRegisteredCounter("arb/validator/poster/assertion_posted", nil) + errorPostingAssertionCounter = metrics.NewRegisteredCounter("arb/validator/poster/error_posting_assertion", nil) + chainCatchingUpCounter = metrics.NewRegisteredCounter("arb/validator/poster/chain_catching_up", nil) +) + +func (m *Manager) postAssertionRoutine(ctx context.Context) { + if !m.mode.SupportsStaking() { + log.Warn("Staker strategy not configured to stake on latest assertions") + return + } + + exceedsMaxMempoolSizeEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "posting this transaction will exceed max mempool size", 0) + gasEstimationEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "gas estimation errored for tx with hash", 0) + + log.Info("Ready to post") + ticker := time.NewTicker(m.times.postInterval) + defer ticker.Stop() + for { + _, err := m.PostAssertion(ctx) + if err != nil { + switch { + case errors.Is(err, solimpl.ErrAlreadyExists): + case errors.Is(err, solimpl.ErrBatchNotYetFound): + log.Info("Waiting for more batches to post assertions about them onchain") + default: + logLevel := log.Error + logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) + logLevel = gasEstimationEphemeralErrorHandler.LogLevel(err, logLevel) + + logLevel("Could not submit latest assertion", "err", err, "validatorName", m.validatorName) + errorPostingAssertionCounter.Inc(1) + + if ctx.Err() != nil { + return + } + continue // We retry again in case of a non ctx error + } + } else { + exceedsMaxMempoolSizeEphemeralErrorHandler.Reset() + gasEstimationEphemeralErrorHandler.Reset() + } + + select { + case <-ticker.C: + case <-ctx.Done(): + return + } + } +} + +func (m *Manager) awaitPostingSignal(ctx context.Context) { + for { + select { + case <-ctx.Done(): + return + case <-m.startPostingSignal: + m.isReadyToPost = true + return + } + } +} + +// PostAssertion differs depending on whether or not the validator is currently staked. +func (m *Manager) PostAssertion(ctx context.Context) (option.Option[protocol.Assertion], error) { + if !m.isReadyToPost { + m.awaitPostingSignal(ctx) + } + // Ensure that we only build on a valid parent from this validator's perspective. + // the validator should also have ready access to historical commitments to make sure it can select + // the valid parent based on its commitment state root. + m.assertionChainData.Lock() + parentAssertionCreationInfo, ok := m.assertionChainData.canonicalAssertions[m.assertionChainData.latestAgreedAssertion] + m.assertionChainData.Unlock() + none := option.None[protocol.Assertion]() + if !ok { + return none, fmt.Errorf( + "latest agreed assertion %#x not part of canonical mapping, something is wrong", + m.assertionChainData.latestAgreedAssertion.Hash, + ) + } + staked, err := m.chain.IsStaked(ctx) + if err != nil { + return none, err + } + // If the validator is already staked, we post an assertion and move existing stake to it. + var assertionOpt option.Option[protocol.Assertion] + var postErr error + if staked { + assertionOpt, postErr = m.PostAssertionBasedOnParent( + ctx, parentAssertionCreationInfo, m.chain.StakeOnNewAssertion, + ) + } else { + // Otherwise, we post a new assertion and place a new stake on it. + assertionOpt, postErr = m.PostAssertionBasedOnParent( + ctx, parentAssertionCreationInfo, m.chain.NewStakeOnNewAssertion, + ) + } + if postErr != nil { + return none, postErr + } + if assertionOpt.IsSome() { + m.submittedAssertions.Insert(assertionOpt.Unwrap().Id()) + } + return assertionOpt, nil +} + +// Posts a new assertion onchain based on a parent assertion we agree with. +func (m *Manager) PostAssertionBasedOnParent( + ctx context.Context, + parentCreationInfo *protocol.AssertionCreatedInfo, + submitFn func( + ctx context.Context, + parentCreationInfo *protocol.AssertionCreatedInfo, + newState *protocol.ExecutionState, + ) (protocol.Assertion, error), +) (option.Option[protocol.Assertion], error) { + none := option.None[protocol.Assertion]() + if !parentCreationInfo.InboxMaxCount.IsUint64() { + return none, errors.New("inbox max count not a uint64") + } + // The parent assertion tells us what the next posted assertion's batch should be. + // We read this value and use it to compute the required execution state we must post. + batchCount := parentCreationInfo.InboxMaxCount.Uint64() + parentBlockHash := protocol.GoGlobalStateFromSolidity(parentCreationInfo.AfterState.GlobalState).BlockHash + newState, err := m.ExecutionStateAfterParent(ctx, parentCreationInfo) + if err != nil { + if errors.Is(err, l2stateprovider.ErrChainCatchingUp) { + chainCatchingUpCounter.Inc(1) + log.Info( + "Waiting for more batches to post next assertion", + "latestStakedAssertionBatchCount", batchCount, + "latestStakedAssertionBlockHash", containers.Trunc(parentBlockHash[:]), + ) + // If the chain is catching up, we wait for a bit and try again. + time.Sleep(m.times.avgBlockTime / 10) + return none, nil + } + return none, errors.Wrapf(err, "could not get execution state at batch count %d with parent block hash %v", batchCount, parentBlockHash) + } + + // If the assertion is not an overflow assertion i.e !(newState.GlobalState.Batch < batchCount) derived from + // contracts check for overflow assertion => assertion.afterState.globalState.u64Vals[0] < assertion.beforeStateData.configData.nextInboxPosition) + // then should check if we need to wait for the minimum number of blocks between assertions and a minimum time since parent assertion creation. + // Overflow ones are not subject to this check onchain. + isOverflowAssertion := newState.MachineStatus != protocol.MachineStatusErrored && newState.GlobalState.Batch < batchCount + if !isOverflowAssertion { + if err = m.waitToPostIfNeeded(ctx, parentCreationInfo); err != nil { + return none, err + } + } + + log.Info( + "Posting assertion for batch we agree with", + "requiredInboxMaxCount", batchCount, + "validatorName", m.validatorName, + ) + assertion, err := submitFn( + ctx, + parentCreationInfo, + newState, + ) + if err != nil { + return none, err + } + assertionPostedCounter.Inc(1) + log.Info("Successfully submitted assertion", + "validatorName", m.validatorName, + "requiredInboxMaxCount", batchCount, + "postedExecutionState", fmt.Sprintf("%+v", newState), + "assertionHash", assertion.Id(), + ) + + m.sendToConfirmationQueue(assertion.Id(), "PostAssertionBasedOnParent") + return option.Some(assertion), nil +} + +func (m *Manager) waitToPostIfNeeded( + ctx context.Context, + parentCreationInfo *protocol.AssertionCreatedInfo, +) error { + if m.times.minGapToParent != 0 { + parentCreationBlock, err := m.backend.HeaderByNumber(ctx, new(big.Int).SetUint64(parentCreationInfo.CreationParentBlock)) + if err != nil { + return fmt.Errorf("error getting parent assertion creation block header: %w", err) + } + parentCreationTime, err := safecast.ToInt64(parentCreationBlock.Time) + if err != nil { + return fmt.Errorf("error casting parent assertion creation time to int64: %w", err) + } + targetTime := time.Unix(parentCreationTime, 0).Add(m.times.minGapToParent) + time.Sleep(time.Until(targetTime)) + } + minPeriodBlocks := m.chain.MinAssertionPeriodBlocks() + for { + latestL1BlockNumber, err := m.chain.DesiredL1HeaderU64(ctx) + if err != nil { + return err + } + blocksSinceLast := uint64(0) + if parentCreationInfo.CreationL1Block < latestL1BlockNumber { + blocksSinceLast = latestL1BlockNumber - parentCreationInfo.CreationL1Block + } + if blocksSinceLast >= minPeriodBlocks { + return nil + } + // If we cannot post just yet, we can wait. + log.Info( + fmt.Sprintf("Need to wait %d blocks before posting next assertion. Current block number: %d", + minPeriodBlocks-blocksSinceLast, + latestL1BlockNumber, + ), + ) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(m.times.avgBlockTime): + } + } +} diff --git a/bold/assertions/poster_test.go b/bold/assertions/poster_test.go new file mode 100644 index 0000000000..a84fc9e314 --- /dev/null +++ b/bold/assertions/poster_test.go @@ -0,0 +1,206 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package assertions_test + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + "github.com/offchainlabs/bold/assertions" + protocol "github.com/offchainlabs/bold/chain-abstraction" + cm "github.com/offchainlabs/bold/challenge-manager" + "github.com/offchainlabs/bold/challenge-manager/types" + challenge_testing "github.com/offchainlabs/bold/testing" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" + "github.com/offchainlabs/nitro/solgen/go/testgen" +) + +func TestPostAssertion(t *testing.T) { + ctx := context.Background() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + chainSetup, chalManager, assertionManager, stateManager := setupAssertionPosting(t) + aliceChain := chainSetup.Chains[0] + + // Force the posting of a new sequencer batch. + forceSequencerMessageBatchPosting( + t, ctx, chainSetup.Accounts[0].TxOpts, chainSetup.Addrs.SequencerInbox, chainSetup.Backend, + ) + + // We have enabled auto-deposits for this test, so we expect that before starting the challenge manager, + // Alice has an ERC20 deposit token balance of 0. After starting, we should expect a non-zero balance. + rollup, err := rollupgen.NewRollupUserLogic(chainSetup.Addrs.Rollup, chainSetup.Backend) + require.NoError(t, err) + requiredStake, err := rollup.BaseStake(&bind.CallOpts{}) + require.NoError(t, err) + stakeTokenAddr, err := rollup.StakeToken(&bind.CallOpts{}) + require.NoError(t, err) + + erc20, err := testgen.NewERC20Token(stakeTokenAddr, chainSetup.Backend) + require.NoError(t, err) + balance, err := erc20.BalanceOf(&bind.CallOpts{}, aliceChain.StakerAddress()) + require.NoError(t, err) + require.True(t, big.NewInt(0).Cmp(balance) == 0) + + chalManager.Start(ctx) + + // Wait a little for the chain watcher to be ready. + time.Sleep(time.Second) + + preState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + require.NoError(t, err) + postState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState) + require.NoError(t, err) + nextState, err := stateManager.ExecutionStateAfterPreviousState(ctx, 2, postState.GlobalState) + require.NoError(t, err) + + // Expect a non-zero balance equal to the required stake after the challenge manager auto-deposited. + balance, err = erc20.BalanceOf(&bind.CallOpts{}, aliceChain.StakerAddress()) + require.NoError(t, err) + require.True(t, requiredStake.Cmp(balance) == 0) + + // Verify that alice can post an assertion correctly. + posted, err := assertionManager.PostAssertion(ctx) + require.NoError(t, err) + require.Equal(t, true, posted.IsSome()) + + creationInfo, err := aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id()) + require.NoError(t, err) + require.Equal(t, postState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState)) + + // Wait a little and advance the chain to allow the next assertion to be posted. + time.Sleep(time.Second * 5) + chainSetup.Backend.Commit() + + // Expect a zero ERC20 balance after the first staked assertion was posted. + balance, err = erc20.BalanceOf(&bind.CallOpts{}, aliceChain.StakerAddress()) + require.NoError(t, err) + require.True(t, big.NewInt(0).Cmp(balance) == 0) + + posted, err = assertionManager.PostAssertion(ctx) + require.NoError(t, err) + require.Equal(t, true, posted.IsSome()) + + creationInfo, err = aliceChain.ReadAssertionCreationInfo(ctx, posted.Unwrap().Id()) + require.NoError(t, err) + require.Equal(t, nextState, protocol.GoExecutionStateFromSolidity(creationInfo.AfterState)) + + // Continue to expect a zero ERC20 balance after the second assertion was posted, as no new + // stake was expected for the validator. + time.Sleep(time.Second * 5) + chainSetup.Backend.Commit() + + balance, err = erc20.BalanceOf(&bind.CallOpts{}, chainSetup.Accounts[0].TxOpts.From) + require.NoError(t, err) + require.True(t, big.NewInt(0).Cmp(balance) == 0) + + // We then filter all the transactions to the staken address from the validator and expect + // there was only a single deposit event (a transfer event with from set to 0x0). + it, err := erc20.FilterTransfer( + &bind.FilterOpts{ + Start: 0, + End: nil, + }, + []common.Address{{}}, + []common.Address{aliceChain.StakerAddress()}, + ) + require.NoError(t, err) + defer func() { + if err = it.Close(); err != nil { + t.Error(err) + } + }() + totalTransfers := 0 + for it.Next() { + totalTransfers++ + } + require.Equal(t, 1, totalTransfers, "Expected only one deposit event by the staker") +} + +func setupAssertionPosting(t *testing.T) (*setup.ChainSetup, *cm.Manager, *assertions.Manager, *statemanager.L2StateBackend) { + setup, err := setup.ChainsWithEdgeChallengeManager( + setup.WithMockOneStepProver(), + setup.WithAutoDeposit(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 64, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + ) + require.NoError(t, err) + bridgeBindings, err := mocksgen.NewBridgeStub(setup.Addrs.Bridge, setup.Backend) + require.NoError(t, err) + msgCount, err := bridgeBindings.SequencerMessageCount(setup.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, uint64(1), msgCount.Uint64()) + aliceChain := setup.Chains[0] + stateManagerOpts := setup.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithNumBatchesRead(5), + ) + stateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + // Set MinimumGapToBlockCreationTime as 1 second to verify that a new assertion is only posted after 1 sec has passed + // from parent assertion creation. This will make the test run for ~19 seconds as the parent assertion time is + // ~18 seconds in the future + assertionManager, err := assertions.NewManager( + aliceChain, + stateManager, + "alice", + types.DefensiveMode, + assertions.WithPollingInterval(time.Millisecond*200), + assertions.WithAverageBlockCreationTime(time.Second), + assertions.WithMinimumGapToParentAssertion(time.Second), + ) + require.NoError(t, err) + chalManager, err := cm.NewChallengeStack( + aliceChain, + stateManager, + cm.StackWithMode(types.DefensiveMode), + cm.StackWithName("alice"), + cm.OverrideAssertionManager(assertionManager), + ) + require.NoError(t, err) + return setup, chalManager, assertionManager, stateManager +} + +func forceSequencerMessageBatchPosting( + t *testing.T, + ctx context.Context, + sequencerOpts *bind.TransactOpts, + seqInboxAddr common.Address, + backend *setup.SimulatedBackendWrapper, +) { + batchCompressedBytes := hexutil.MustDecode("0x94643ec208c5558027fa768281f28aa273f01537942cd58cdd9c17e97e30281f") + message := append([]byte{0}, batchCompressedBytes...) + seqNum := new(big.Int).Lsh(common.Big1, 256) + seqNum.Sub(seqNum, common.Big1) + seqInbox, err := bridgegen.NewSequencerInbox(seqInboxAddr, backend) + require.NoError(t, err) + tx, err := seqInbox.AddSequencerL2BatchFromOrigin8f111f3c( + sequencerOpts, seqNum, message, big.NewInt(1), common.Address{}, big.NewInt(0), big.NewInt(0), + ) + require.NoError(t, err) + require.NoError(t, challenge_testing.WaitForTx(ctx, backend, tx)) + receipt, err := backend.TransactionReceipt(ctx, tx.Hash()) + require.NoError(t, err) + require.Equal(t, gethtypes.ReceiptStatusSuccessful, receipt.Status) +} diff --git a/bold/assertions/sync.go b/bold/assertions/sync.go new file mode 100644 index 0000000000..30470c2ff1 --- /dev/null +++ b/bold/assertions/sync.go @@ -0,0 +1,585 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package assertions + +import ( + "context" + "fmt" + "time" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + + "github.com/offchainlabs/bold/api" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + retry "github.com/offchainlabs/bold/runtime" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func (m *Manager) syncAssertions(ctx context.Context) { + latestConfirmed, err := retry.UntilSucceeds(ctx, func() (protocol.Assertion, error) { + return m.chain.LatestConfirmed(ctx, m.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + }) + if err != nil { + log.Error("Could not get latest confirmed assertion", "err", err) + return + } + latestConfirmedInfo, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return m.chain.ReadAssertionCreationInfo(ctx, latestConfirmed.Id()) + }) + if err != nil { + log.Error("Could not get latest confirmed assertion", "err", err) + return + } + + m.assertionChainData.Lock() + m.assertionChainData.latestAgreedAssertion = latestConfirmed.Id() + m.assertionChainData.canonicalAssertions[latestConfirmed.Id()] = latestConfirmedInfo + if !m.disablePosting { + close(m.startPostingSignal) + } + m.assertionChainData.Unlock() + + fromBlock, err := m.chain.GetAssertionCreationParentBlock(ctx, latestConfirmed.Id().Hash) + if err != nil { + return + } + filterer, err := retry.UntilSucceeds(ctx, func() (*rollupgen.RollupUserLogicFilterer, error) { + return rollupgen.NewRollupUserLogicFilterer(m.rollupAddr, m.backend) + }) + if err != nil { + log.Error("Could not get rollup user logic filterer", "err", err) + return + } + toBlock, err := retry.UntilSucceeds(ctx, func() (uint64, error) { + return m.chain.DesiredHeaderU64(ctx) + }) + if err != nil { + log.Error("Could not get header by number", "err", err) + return + } + if fromBlock != toBlock { + for startBlock := fromBlock; startBlock <= toBlock; startBlock = startBlock + m.maxGetLogBlocks { + endBlock := startBlock + m.maxGetLogBlocks + if endBlock > toBlock { + endBlock = toBlock + } + filterOpts := &bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + Context: ctx, + } + _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + innerErr := m.processAllAssertionsInRange(ctx, filterer, filterOpts) + if innerErr != nil { + log.Error("Could not process assertions in range", "err", innerErr) + return false, innerErr + } + return true, nil + }) + if err != nil { + log.Error("Could not check for assertion added event", "err", err) + return + } + } + fromBlock = toBlock + } + + ticker := time.NewTicker(m.times.pollInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + toBlock, err := m.chain.DesiredHeaderU64(ctx) + if err != nil { + log.Error("Could not get header by number", "err", err) + continue + } + if fromBlock == toBlock { + continue + } + for startBlock := fromBlock; startBlock <= toBlock; startBlock = startBlock + m.maxGetLogBlocks { + endBlock := startBlock + m.maxGetLogBlocks + if endBlock > toBlock { + endBlock = toBlock + } + filterOpts := &bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + Context: ctx, + } + _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + innerErr := m.processAllAssertionsInRange(ctx, filterer, filterOpts) + if innerErr != nil { + log.Error("Could not process assertions in range", "err", innerErr) + return false, innerErr + } + return true, nil + }) + if err != nil { + log.Error("Could not check for assertion added event", "err", err) + return + } + } + fromBlock = toBlock + case <-ctx.Done(): + return + } + } +} + +type assertionAndParentCreationInfo struct { + assertion *protocol.AssertionCreatedInfo + parent *protocol.AssertionCreatedInfo +} + +// This function will scan for all assertion creation events to determine which +// ones are canonical and which ones must be challenged. +func (m *Manager) processAllAssertionsInRange( + ctx context.Context, + filterer *rollupgen.RollupUserLogicFilterer, + filterOpts *bind.FilterOpts, +) error { + it, err := filterer.FilterAssertionCreated(filterOpts, nil, nil) + if err != nil { + return err + } + defer func() { + if err = it.Close(); err != nil { + log.Error("Could not close filter iterator", "err", err) + } + }() + + // Extract all assertion creation events from the log filter iterator. + assertions := make([]assertionAndParentCreationInfo, 0) + assertionsByHash := make(map[protocol.AssertionHash]*protocol.AssertionCreatedInfo) + for it.Next() { + if it.Error() != nil { + return errors.Wrapf( + err, + "got iterator error when scanning assertion creations from block %d to %d", + filterOpts.Start, + *filterOpts.End, + ) + } + assertionOpt, err := retry.UntilSucceeds(ctx, func() (option.Option[*protocol.AssertionCreatedInfo], error) { + item, innerErr := m.extractAssertionFromEvent(ctx, it.Event) + if innerErr != nil { + log.Error("Could not extract assertion from event", "err", innerErr) + return option.None[*protocol.AssertionCreatedInfo](), innerErr + } + return item, nil + }) + if err != nil { + return err + } + if assertionOpt.IsSome() { + creationInfo := assertionOpt.Unwrap() + assertionsByHash[creationInfo.AssertionHash] = creationInfo + fullInfo := assertionAndParentCreationInfo{ + assertion: creationInfo, + parent: assertionsByHash[creationInfo.ParentAssertionHash], + } + if fullInfo.parent == nil { + parentInfo, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return m.chain.ReadAssertionCreationInfo(ctx, creationInfo.ParentAssertionHash) + }) + if err != nil { + return errors.Wrapf(err, "could not read assertion creation info for %#x (parent of %#x)", creationInfo.ParentAssertionHash, creationInfo.AssertionHash) + } + assertionsByHash[creationInfo.ParentAssertionHash] = parentInfo + fullInfo.parent = parentInfo + } + assertions = append(assertions, fullInfo) + } + } + + // Save all observed assertions to the database. + go func() { + for _, fullInfo := range assertions { + if _, err := retry.UntilSucceeds(ctx, func() (bool, error) { + if err := m.saveAssertionToDB(ctx, fullInfo.assertion); err != nil { + log.Error("Could not save assertion to DB", "err", err) + return false, err + } + return true, nil + }); err != nil { + log.Error("Could not save assertion to DB", "err", err) + } + } + }() + + m.assertionChainData.Lock() + defer m.assertionChainData.Unlock() + + // Determine the canonical branch of all assertions. + if _, err := retry.UntilSucceeds(ctx, func() (bool, error) { + if innerErr := m.findCanonicalAssertionBranch(ctx, assertions); innerErr != nil { + log.Error("Could not find canonical assertion branch", "err", innerErr) + return false, innerErr + } + return true, nil + }); err != nil { + return err + } + + // Now that we derived the canonical chain, we perform a pass over all assertions + // to figure out which ones are invalid and therefore should be challenged. + if _, err := retry.UntilSucceeds(ctx, func() (bool, error) { + if innerErr := m.respondToAnyInvalidAssertions(ctx, assertions, m); innerErr != nil { + log.Error("Could not find canonical assertion branch", "err", innerErr) + return false, innerErr + } + return true, nil + }); err != nil { + return err + } + return nil +} + +// Extracts a valid assertion creation from an event log. Returns none +// if the assertion is genesis or if the hash is the zero hash. +func (m *Manager) extractAssertionFromEvent( + ctx context.Context, + event *rollupgen.RollupUserLogicAssertionCreated, +) (option.Option[*protocol.AssertionCreatedInfo], error) { + none := option.None[*protocol.AssertionCreatedInfo]() + if event.AssertionHash == (common.Hash{}) { + log.Warn("Encountered an assertion with a zero hash", + "creationEvent", fmt.Sprintf("%+v", event), + ) + return none, nil + } + assertionHash := protocol.AssertionHash{Hash: event.AssertionHash} + creationInfo, err := m.chain.ReadAssertionCreationInfo(ctx, assertionHash) + if err != nil { + return none, errors.Wrapf(err, "could not read assertion creation info for %#x", assertionHash.Hash) + } + if creationInfo.ParentAssertionHash.Hash == (common.Hash{}) { + return none, nil + } + return option.Some(creationInfo), nil +} + +// Finds all canonical assertions from an ordered list by creation time. +// Starts by setting a cursor to the latest confirmed assertion, then finds all assertions parent == cursor. +// We then check which one we agree with. +// From there, checks all assertions that have that assertion as parent, etc. +// This function must hold the lock on m.assertionChainData. +func (m *Manager) findCanonicalAssertionBranch( + ctx context.Context, + assertions []assertionAndParentCreationInfo, +) error { + latestAgreedWithAssertion := m.assertionChainData.latestAgreedAssertion + cursor := latestAgreedWithAssertion + + for _, fullInfo := range assertions { + assertion := fullInfo.assertion + if assertion.ParentAssertionHash == cursor { + agreedWithAssertion, err := retry.UntilSucceeds(ctx, func() (bool, error) { + expectedState, err := m.ExecutionStateAfterParent(ctx, fullInfo.parent) + switch { + case errors.Is(err, l2stateprovider.ErrChainCatchingUp): + // Otherwise, we return the error that we are still catching up to the + // execution state claimed by the assertion, and this function will be retried + // by the caller if wrapped in a retryable call. + chainCatchingUpCounter.Inc(1) + log.Info("Chain still syncing "+ + "will reattempt processing when caught up", "err", err) + // If the chain is catching up, we wait for a bit and try again. + time.Sleep(m.times.avgBlockTime / 10) + return false, l2stateprovider.ErrChainCatchingUp + case err != nil: + return false, err + } + return expectedState.Equals(protocol.GoExecutionStateFromSolidity(assertion.AfterState)), nil + }, func(rc *retry.RetryConfig) { rc.LevelWarningError = "could not check if we have result at count" }) + if err != nil { + return errors.New("could not check for assertion agreements") + } + if agreedWithAssertion { + cursor = assertion.AssertionHash + m.assertionChainData.latestAgreedAssertion = cursor + m.assertionChainData.canonicalAssertions[cursor] = assertion + m.sendToConfirmationQueue(cursor, "findCanonicalAssertionBranch") + } + } + } + return nil +} + +type rivalPosterArgs struct { + canonicalParent *protocol.AssertionCreatedInfo + invalidAssertion *protocol.AssertionCreatedInfo +} + +type rivalPoster interface { + maybePostRivalAssertionAndChallenge( + ctx context.Context, + args rivalPosterArgs, + ) (*protocol.AssertionCreatedInfo, error) +} + +// Finds all canonical assertions from a list. Starts by setting a cursor to the +// latest confirmed assertion, then finds all assertions parent == cursor. +// We then check which one we agree with. +// From there, checks all assertions that have that assertion as parent, etc. +// This function must hold the lock on m.assertionChainData. +func (m *Manager) respondToAnyInvalidAssertions( + ctx context.Context, + assertions []assertionAndParentCreationInfo, + rivalPoster rivalPoster, +) error { + for _, fullInfo := range assertions { + assertion := fullInfo.assertion + canonicalParent, hasCanonicalParent := m.assertionChainData.canonicalAssertions[assertion.ParentAssertionHash] + _, isCanonical := m.assertionChainData.canonicalAssertions[assertion.AssertionHash] + // If an assertion has a canonical parent but is not canonical itself, + // then we should challenge the assertion if we are configured to do so, + // or raise an alarm if we are only a watchtower validator. + if hasCanonicalParent && !isCanonical { + postedRival, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + posted, innerErr := rivalPoster.maybePostRivalAssertionAndChallenge(ctx, rivalPosterArgs{ + canonicalParent: canonicalParent, + invalidAssertion: assertion, + }) + if innerErr != nil { + innerErr = errors.Wrapf(innerErr, "validator=%s could not post rival assertion and/or challenge", m.validatorName) + return nil, innerErr + } + return posted, nil + }) + if err != nil { + return err + } + if postedRival != nil { + postedAssertionHash := postedRival.AssertionHash + if _, ok := m.assertionChainData.canonicalAssertions[postedAssertionHash]; !ok { + m.assertionChainData.canonicalAssertions[postedAssertionHash] = postedRival + m.submittedAssertions.Insert(postedAssertionHash) + m.submittedRivalsCount++ + m.sendToConfirmationQueue(postedAssertionHash, "respondToAnyInvalidAssertions") + + } + } + } + } + return nil +} + +// Attempts to post a rival assertion to a given assertion and then attempts to +// open a challenge on that fork in the chain if configured to do so. +func (m *Manager) maybePostRivalAssertionAndChallenge( + ctx context.Context, + args rivalPosterArgs, +) (*protocol.AssertionCreatedInfo, error) { + if !args.invalidAssertion.InboxMaxCount.IsUint64() { + return nil, errors.New("inbox max count not a uint64") + } + if args.canonicalParent.AssertionHash != args.invalidAssertion.ParentAssertionHash { + return nil, errors.New("invalid assertion does not have correct canonical parent") + } + batchCount := args.invalidAssertion.InboxMaxCount.Uint64() + logFields := []any{ + "validatorName", m.validatorName, + "canonicalParentHash", args.invalidAssertion.ParentAssertionHash, + "detectedAssertionHash", args.invalidAssertion.AssertionHash, + "batchCount", batchCount, + } + if !m.mode.SupportsPostingRivals() { + log.Warn("Detected invalid assertion, but not configured to post a rival stake", logFields...) + evilAssertionCounter.Inc(1) + return nil, nil + } + + log.Warn("Disagreed with an observed assertion onchain", logFields...) + evilAssertionCounter.Inc(1) + + // Post what we believe is the correct rival assertion that follows the ancestor we agree with. + correctRivalAssertion, err := m.maybePostRivalAssertion(ctx, args.canonicalParent) + if err != nil { + return nil, err + } + if correctRivalAssertion.IsNone() { + log.Warn(fmt.Sprintf("Expected to post a rival assertion to %#x, but did not post anything", args.invalidAssertion.AssertionHash)) + return nil, nil + } + assertionHash := correctRivalAssertion.Unwrap().AssertionHash + postedRival, err := m.chain.ReadAssertionCreationInfo(ctx, assertionHash) + if err != nil { + return nil, errors.Wrapf(err, "could not read assertion creation info for %#x", assertionHash.Hash) + } + if !m.mode.SupportsPostingChallenges() { + log.Warn("Posted rival assertion and stake, but not configured to initiate a challenge", logFields...) + return postedRival, nil + } + + if args.canonicalParent.ChallengeManager != m.chain.SpecChallengeManager().Address() { + log.Warn("Posted rival assertion, but could not challenge as challenge manager address did not match, "+ + "start a new server with the right challenge manager address", + "correctAssertion", postedRival.AssertionHash, + "evilAssertion", args.invalidAssertion.AssertionHash, + "expectedChallengeManagerAddress", args.canonicalParent.ChallengeManager, + "configuredChallengeManagerAddress", m.chain.SpecChallengeManager().Address(), + ) + return nil, nil + } + + if m.rivalHandler == nil { + return nil, errors.New("rival handler not set") + } + err = m.rivalHandler.HandleCorrectRival(ctx, postedRival.AssertionHash) + if err != nil { + return nil, err + } + return postedRival, nil +} + +// Attempt to post a rival assertion based on the last agreed with ancestor +// of a given assertion. +// +// If this parent assertion already has a rival we agree with that arleady exists +// then this function will return that assertion. +func (m *Manager) maybePostRivalAssertion( + ctx context.Context, + canonicalParent *protocol.AssertionCreatedInfo, +) (option.Option[*protocol.AssertionCreatedInfo], error) { + none := option.None[*protocol.AssertionCreatedInfo]() + // Post what we believe is the correct assertion that follows the ancestor we agree with. + staked, err := m.chain.IsStaked(ctx) + if err != nil { + return none, err + } + // If the validator is already staked, we post an assertion and move existing stake to it. + var assertionOpt option.Option[protocol.Assertion] + var postErr error + if staked { + assertionOpt, postErr = m.PostAssertionBasedOnParent( + ctx, canonicalParent, m.chain.StakeOnNewAssertion, + ) + } else { + // Otherwise, we post a new assertion and place a new stake on it. + assertionOpt, postErr = m.PostAssertionBasedOnParent( + ctx, canonicalParent, m.chain.NewStakeOnNewAssertion, + ) + } + if postErr != nil { + return none, postErr + } + if assertionOpt.IsSome() { + creationInfo, err := m.chain.ReadAssertionCreationInfo(ctx, assertionOpt.Unwrap().Id()) + if err != nil { + return none, err + } + log.Info("Posted rival assertion to another that we disagreed with", + "parentAssertionHash", canonicalParent.AssertionHash, + "correctRivalAssertionHash", creationInfo.AssertionHash, + "transactionHash", creationInfo.TransactionHash, + "name", m.validatorName, + "postedAssertionState", fmt.Sprintf("%+v", creationInfo.AfterState), + ) + go func() { + if _, err2 := retry.UntilSucceeds(ctx, func() (bool, error) { + innerErr := m.saveAssertionToDB(ctx, creationInfo) + if innerErr != nil { + log.Error("Could not save assertion to DB", "err", innerErr) + return false, innerErr + } + return false, nil + }); err2 != nil { + log.Error("Could not save assertion to DB", "err", err2) + } + }() + return option.Some(creationInfo), nil + } + return none, nil +} + +func (m *Manager) saveAssertionToDB(ctx context.Context, creationInfo *protocol.AssertionCreatedInfo) error { + if api.IsNil(m.apiDB) { + return nil + } + beforeState := protocol.GoExecutionStateFromSolidity(creationInfo.BeforeState) + afterState := protocol.GoExecutionStateFromSolidity(creationInfo.AfterState) + assertionHash := creationInfo.AssertionHash + // Because the BoLD database is for exploratory purposes, we don't care about using a reorg-safe + // RPC block number for reading data here. Latest block number will suffice to ensure we capture + // all assertions for data analysis if needed. + opts := &bind.CallOpts{Context: ctx} + assertion, err := m.chain.GetAssertion(ctx, opts, assertionHash) + if err != nil { + return err + } + status, err := assertion.Status(ctx, opts) + if err != nil { + return err + } + isFirstChild, err := assertion.IsFirstChild(ctx, opts) + if err != nil { + return err + } + firstChildBlock, err := assertion.FirstChildCreationBlock(ctx, opts) + if err != nil { + return err + } + secondChildBlock, err := assertion.SecondChildCreationBlock(ctx, opts) + if err != nil { + return err + } + return m.apiDB.InsertAssertion(&api.JsonAssertion{ + Hash: assertionHash.Hash, + ConfirmPeriodBlocks: creationInfo.ConfirmPeriodBlocks, + RequiredStake: creationInfo.RequiredStake.String(), + ParentAssertionHash: creationInfo.ParentAssertionHash.Hash, + InboxMaxCount: creationInfo.InboxMaxCount.String(), + AfterInboxBatchAcc: creationInfo.AfterInboxBatchAcc, + WasmModuleRoot: creationInfo.WasmModuleRoot, + ChallengeManager: creationInfo.ChallengeManager, + CreationBlock: creationInfo.CreationParentBlock, + TransactionHash: creationInfo.TransactionHash, + BeforeStateBlockHash: beforeState.GlobalState.BlockHash, + BeforeStateSendRoot: beforeState.GlobalState.SendRoot, + BeforeStateBatch: beforeState.GlobalState.Batch, + BeforeStatePosInBatch: beforeState.GlobalState.PosInBatch, + BeforeStateMachineStatus: beforeState.MachineStatus, + AfterStateBlockHash: afterState.GlobalState.BlockHash, + AfterStateSendRoot: afterState.GlobalState.SendRoot, + AfterStateBatch: afterState.GlobalState.Batch, + AfterStatePosInBatch: afterState.GlobalState.PosInBatch, + AfterStateMachineStatus: afterState.MachineStatus, + FirstChildBlock: &firstChildBlock, + SecondChildBlock: &secondChildBlock, + IsFirstChild: isFirstChild, + Status: status.String(), + }) +} + +// Send assertion to confirmation queue +func (m *Manager) sendToConfirmationQueue(assertionHash protocol.AssertionHash, addedBy string) { + m.confirmQueueMutex.Lock() + defer m.confirmQueueMutex.Unlock() + + // Check if assertion is already in confirmation queue + if m.confirming.Has(assertionHash) { + log.Debug("Assertion already in confirmation queue", "assertionHash", assertionHash, "addedBy", addedBy) + return // Already in confirmation queue, skip + } + log.Info("Sending assertion to confirmation queue", "assertionHash", assertionHash, "addedBy", addedBy) + // Mark as confirming + m.confirming.Insert(assertionHash) + + // Send to confirmation queue + select { + case m.observedCanonicalAssertions <- assertionHash: + default: + m.confirming.Delete(assertionHash) + log.Warn("Failed to send assertion to confirmation queue: channel full", "assertionHash", assertionHash, "addedBy", addedBy) + } +} diff --git a/bold/assertions/sync_test.go b/bold/assertions/sync_test.go new file mode 100644 index 0000000000..f9d8cdbdfc --- /dev/null +++ b/bold/assertions/sync_test.go @@ -0,0 +1,397 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package assertions + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/threadsafe" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/casttest" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func Test_extractAssertionFromEvent(t *testing.T) { + m := &Manager{} + ctx := context.Background() + + t.Run("ignores empty hash", func(t *testing.T) { + opt, err := m.extractAssertionFromEvent(ctx, &rollupgen.RollupUserLogicAssertionCreated{ + AssertionHash: common.Hash{}, + }) + require.NoError(t, err) + require.Equal(t, true, opt.IsNone()) + }) + + setup, err := setup.ChainsWithEdgeChallengeManager( + setup.WithMockOneStepProver(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 64, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + ) + require.NoError(t, err) + _ = setup + + bridgeBindings, err := mocksgen.NewBridgeStub(setup.Addrs.Bridge, setup.Backend) + require.NoError(t, err) + + msgCount, err := bridgeBindings.SequencerMessageCount(setup.Chains[0].GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, uint64(1), msgCount.Uint64()) + + aliceChain := setup.Chains[0] + genesisHash, err := setup.Chains[1].GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisCreationInfo, err := setup.Chains[1].ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + + stateManagerOpts := setup.StateManagerOpts + aliceStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + require.NoError(t, err) + + preState, err := aliceStateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + require.NoError(t, err) + postState, err := aliceStateManager.ExecutionStateAfterPreviousState(ctx, 1, preState.GlobalState) + require.NoError(t, err) + assertion, err := aliceChain.NewStakeOnNewAssertion( + ctx, + genesisCreationInfo, + postState, + ) + require.NoError(t, err) + + t.Run("ignores genesis assertion", func(t *testing.T) { + m.chain = aliceChain + opt, err := m.extractAssertionFromEvent(ctx, &rollupgen.RollupUserLogicAssertionCreated{ + AssertionHash: genesisHash, + }) + require.NoError(t, err) + require.Equal(t, true, opt.IsNone()) + }) + t.Run("extracts assertion", func(t *testing.T) { + m.chain = aliceChain + opt, err := m.extractAssertionFromEvent(ctx, &rollupgen.RollupUserLogicAssertionCreated{ + AssertionHash: assertion.Id().Hash, + }) + require.NoError(t, err) + require.Equal(t, true, opt.IsSome()) + require.Equal(t, assertion.Id(), opt.Unwrap().AssertionHash) + }) +} + +func Test_findCanonicalAssertionBranch(t *testing.T) { + setup, err := setup.ChainsWithEdgeChallengeManager( + setup.WithMockOneStepProver(), + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(&protocol.LayerZeroHeights{ + BlockChallengeHeight: 32, + BigStepChallengeHeight: 32, + SmallStepChallengeHeight: 32, + }), + ), + ) + require.NoError(t, err) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + agreesWithIds := map[uint64]*protocol.AssertionCreatedInfo{ + 2: { + ParentAssertionHash: numToHash(1), + AssertionHash: numToHash(2), + AfterState: numToState(2, t), + }, + 4: { + ParentAssertionHash: numToHash(2), + AssertionHash: numToHash(4), + AfterState: numToState(4, t), + }, + 6: { + ParentAssertionHash: numToHash(4), + AssertionHash: numToHash(6), + AfterState: numToState(6, t), + }, + } + provider := &mockStateProvider{ + agreesWith: agreesWithIds, + } + manager := &Manager{ + execProvider: provider, + chain: setup.Chains[0], + observedCanonicalAssertions: make(chan protocol.AssertionHash), + confirming: threadsafe.NewLruSet[protocol.AssertionHash](1000), + assertionChainData: &assertionChainData{ + latestAgreedAssertion: numToAssertionHash(1), + canonicalAssertions: make(map[protocol.AssertionHash]*protocol.AssertionCreatedInfo), + }, + } + go func() { + for { + select { + case <-manager.observedCanonicalAssertions: + case <-ctx.Done(): + return + } + } + }() + require.NoError(t, manager.findCanonicalAssertionBranch( + ctx, + []assertionAndParentCreationInfo{ + { + parent: &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(2), + }, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(1), + AssertionHash: numToHash(2), + AfterState: numToState(2, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(3), + }, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(1), + AssertionHash: numToHash(3), + AfterState: numToState(3, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(4), + }, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(2), + AssertionHash: numToHash(4), + AfterState: numToState(4, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(5), + }, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(2), + AssertionHash: numToHash(5), + AfterState: numToState(5, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(6), + }, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(4), + AssertionHash: numToHash(6), + AfterState: numToState(6, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(7), + }, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(4), + AssertionHash: numToHash(7), + AfterState: numToState(7, t), + }, + }, + }, + )) + require.Equal(t, numToAssertionHash(6), manager.assertionChainData.latestAgreedAssertion) + wanted := make(map[protocol.AssertionHash]bool) + for id := range agreesWithIds { + wanted[numToAssertionHash(casttest.ToInt(t, id))] = true + } + for assertionHash := range manager.assertionChainData.canonicalAssertions { + require.Equal(t, true, wanted[assertionHash]) + } +} + +func numToAssertionHash(i int) protocol.AssertionHash { + return protocol.AssertionHash{Hash: common.BytesToHash([]byte(fmt.Sprintf("%d", i)))} +} + +func numToHash(i int) protocol.AssertionHash { + return protocol.AssertionHash{Hash: common.BytesToHash([]byte(fmt.Sprintf("%d", i)))} +} + +func numToState(i int, t *testing.T) rollupgen.AssertionState { + return rollupgen.AssertionState{ + GlobalState: rollupgen.GlobalState{ + U64Vals: [2]uint64{casttest.ToUint64(t, i), uint64(0)}, + }, + } +} + +type mockStateProvider struct { + agreesWith map[uint64]*protocol.AssertionCreatedInfo +} + +func (m *mockStateProvider) ExecutionStateAfterPreviousState( + ctx context.Context, + maxInboxCount uint64, + previousGlobalState protocol.GoGlobalState, +) (*protocol.ExecutionState, error) { + agreement, ok := m.agreesWith[maxInboxCount] + if !ok { + return &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("wrong")), + }, + }, nil + } + return protocol.GoExecutionStateFromSolidity(agreement.AfterState), nil +} + +func Test_respondToAnyInvalidAssertions(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + manager := &Manager{ + observedCanonicalAssertions: make(chan protocol.AssertionHash), + submittedAssertions: threadsafe.NewLruSet(1000, threadsafe.LruSetWithMetric[protocol.AssertionHash]("submittedAssertions")), + confirming: threadsafe.NewLruSet[protocol.AssertionHash](1000), + assertionChainData: &assertionChainData{ + latestAgreedAssertion: numToAssertionHash(1), + canonicalAssertions: make(map[protocol.AssertionHash]*protocol.AssertionCreatedInfo), + }, + } + go func() { + for { + select { + case <-manager.observedCanonicalAssertions: + case <-ctx.Done(): + return + } + } + }() + + manager.assertionChainData.canonicalAssertions[numToAssertionHash(1)] = &protocol.AssertionCreatedInfo{} + manager.assertionChainData.canonicalAssertions[numToAssertionHash(2)] = &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(1), + } + manager.assertionChainData.canonicalAssertions[numToAssertionHash(4)] = &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(2), + } + manager.assertionChainData.canonicalAssertions[numToAssertionHash(6)] = &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(4), + } + + t.Run("all assertions canonical no rivals posted", func(t *testing.T) { + poster := &mockRivalPoster{} + require.NoError(t, manager.respondToAnyInvalidAssertions( + ctx, + []assertionAndParentCreationInfo{ + { + parent: &protocol.AssertionCreatedInfo{}, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(2), + AssertionHash: numToHash(4), + AfterState: numToState(4, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{}, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(4), + AssertionHash: numToHash(6), + AfterState: numToState(6, t), + }, + }, + }, + poster, + )) + require.Equal(t, uint64(0), manager.submittedRivalsCount) + }) + t.Run("invalid assertions but no canonical parent in list", func(t *testing.T) { + poster := &mockRivalPoster{} + require.NoError(t, manager.respondToAnyInvalidAssertions( + ctx, + []assertionAndParentCreationInfo{ + { + parent: &protocol.AssertionCreatedInfo{}, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(200), + AssertionHash: numToHash(400), + AfterState: numToState(400, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{}, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(400), + AssertionHash: numToHash(600), + AfterState: numToState(600, t), + }, + }, + }, + poster, + )) + require.Equal(t, uint64(0), manager.submittedRivalsCount) + }) + t.Run("rivals posted successfully", func(t *testing.T) { + poster := &mockRivalPoster{} + require.NoError(t, manager.respondToAnyInvalidAssertions( + ctx, + []assertionAndParentCreationInfo{ + // Some evil hashes which must be acted upon. + { + parent: &protocol.AssertionCreatedInfo{}, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(2), + AssertionHash: numToHash(3), + AfterState: numToState(3, t), + }, + }, + { + parent: &protocol.AssertionCreatedInfo{}, + assertion: &protocol.AssertionCreatedInfo{ + ParentAssertionHash: numToHash(4), + AssertionHash: numToHash(5), + AfterState: numToState(5, t), + }, + }, + }, + poster, + )) + require.Equal(t, uint64(2), manager.submittedRivalsCount) + }) +} + +type mockRivalPoster struct { +} + +func (m *mockRivalPoster) maybePostRivalAssertionAndChallenge( + ctx context.Context, + args rivalPosterArgs, +) (*protocol.AssertionCreatedInfo, error) { + if args.invalidAssertion.AssertionHash == numToHash(3) { + return &protocol.AssertionCreatedInfo{ + AssertionHash: numToHash(300), + }, nil + } + if args.invalidAssertion.AssertionHash == numToHash(5) { + return &protocol.AssertionCreatedInfo{ + AssertionHash: numToHash(500), + }, nil + } + panic("must have been able to post") +} diff --git a/bold/chain-abstraction/execution_state.go b/bold/chain-abstraction/execution_state.go new file mode 100644 index 0000000000..07d4c57d25 --- /dev/null +++ b/bold/chain-abstraction/execution_state.go @@ -0,0 +1,119 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package protocol a series of interfaces for interacting with Arbitrum chains' rollup +// and challenge contracts via a developer-friendly, high-level API. +package protocol + +import ( + "encoding/binary" + "math" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +type GoGlobalState struct { + BlockHash common.Hash `json:"blockHash"` + SendRoot common.Hash `json:"sendRoot"` + Batch uint64 `json:"batch"` + PosInBatch uint64 `json:"positionInBatch"` +} + +func GoGlobalStateFromSolidity(globalState rollupgen.GlobalState) GoGlobalState { + return GoGlobalState{ + BlockHash: globalState.Bytes32Vals[0], + SendRoot: globalState.Bytes32Vals[1], + Batch: globalState.U64Vals[0], + PosInBatch: globalState.U64Vals[1], + } +} + +func u64ToBe(x uint64) []byte { + data := make([]byte, 8) + binary.BigEndian.PutUint64(data, x) + return data +} + +func ComputeSimpleMachineChallengeHash( + execState *ExecutionState, +) common.Hash { + return execState.GlobalState.Hash() +} + +func (s GoGlobalState) Hash() common.Hash { + data := []byte("Global state:") + data = append(data, s.BlockHash.Bytes()...) + data = append(data, s.SendRoot.Bytes()...) + data = append(data, u64ToBe(s.Batch)...) + data = append(data, u64ToBe(s.PosInBatch)...) + return crypto.Keccak256Hash(data) +} + +func (s GoGlobalState) AsSolidityStruct() challengeV2gen.GlobalState { + return challengeV2gen.GlobalState{ + Bytes32Vals: [2][32]byte{s.BlockHash, s.SendRoot}, + U64Vals: [2]uint64{s.Batch, s.PosInBatch}, + } +} + +func (s GoGlobalState) Equals(other GoGlobalState) bool { + // This is correct because we don't have any pointers or slices + return s == other +} + +type MachineStatus uint8 + +const ( + MachineStatusRunning MachineStatus = 0 + MachineStatusFinished MachineStatus = 1 + MachineStatusErrored MachineStatus = 2 +) + +type ExecutionState struct { + GlobalState GoGlobalState + MachineStatus MachineStatus + EndHistoryRoot common.Hash +} + +func GoExecutionStateFromSolidity(executionState rollupgen.AssertionState) *ExecutionState { + return &ExecutionState{ + GlobalState: GoGlobalStateFromSolidity(executionState.GlobalState), + MachineStatus: MachineStatus(executionState.MachineStatus), + EndHistoryRoot: executionState.EndHistoryRoot, + } +} + +func (s *ExecutionState) AsSolidityStruct() rollupgen.AssertionState { + return rollupgen.AssertionState{ + GlobalState: rollupgen.GlobalState(s.GlobalState.AsSolidityStruct()), + MachineStatus: uint8(s.MachineStatus), + EndHistoryRoot: s.EndHistoryRoot, + } +} + +func (s *ExecutionState) Equals(other *ExecutionState) bool { + return s.MachineStatus == other.MachineStatus && s.GlobalState.Equals(other.GlobalState) && s.EndHistoryRoot == other.EndHistoryRoot +} + +// RequiredBatches determines the batch count required to reach the execution state. +// If the machine errored or the state is after the beginning of the batch, +// the current batch is required to reach the state. +// That's because if the machine errored, it might've read the current batch before erroring, +// and if it's in the middle of a batch, it had to read prior parts of the batch to get there. +// However, if the machine finished successfully and the new state is the start of the batch, +// it hasn't read the batch yet, as it just finished the last batch. +// +// This logic is replicated in Solidity in a few places; search for RequiredBatches to find them. +func (s *ExecutionState) RequiredBatches() uint64 { + count := s.GlobalState.Batch + if (s.MachineStatus == MachineStatusErrored || s.GlobalState.PosInBatch > 0) && count < math.MaxUint64 { + // The current batch was read + count++ + } + return count +} diff --git a/bold/chain-abstraction/interfaces.go b/bold/chain-abstraction/interfaces.go new file mode 100644 index 0000000000..d0b53e18d0 --- /dev/null +++ b/bold/chain-abstraction/interfaces.go @@ -0,0 +1,462 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package protocol + +import ( + "context" + "fmt" + "math/big" + "regexp" + "strconv" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/state-commitments/history" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +// ErrCachedTimeSufficient is an error received from the challenge manager smart contract +// when attempting to update an edge's onchain cached timer to a value less than what it already has. +var ErrCachedTimeSufficient = "CachedTimeSufficient" + +// ChainBackend to interact with the underlying blockchain. +type ChainBackend interface { + bind.ContractBackend + ReceiptFetcher + TxFetcher + HeadSubscriber + ChainID(ctx context.Context) (*big.Int, error) + Close() + Client() rpc.ClientInterface + // HeaderU64 returns either latest, safe, or finalized block number from + // the current canonical chain, depending on how the underlying implementation + // of ChainBackend is configured. + HeaderU64(ctx context.Context) (uint64, error) +} + +// ReceiptFetcher defines the ability to retrieve transactions receipts from the chain. +type ReceiptFetcher interface { + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) +} + +// ReceiptFetcher defines the ability to retrieve transactions receipts from the chain. +type TxFetcher interface { + TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) +} + +// ReceiptFetcher defines the ability to retrieve transactions receipts from the chain. +type HeadSubscriber interface { + SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) +} + +// LayerZeroHeights for edges configured as parameters in the challenge manager contract. +type LayerZeroHeights struct { + BlockChallengeHeight Height + BigStepChallengeHeight Height + SmallStepChallengeHeight Height +} + +// AssertionHash represents a unique identifier for an assertion +// constructed as a keccak256 hash of some of its internals. +type AssertionHash struct { + common.Hash +} + +// Protocol -- +type Protocol interface { + AssertionChain +} + +type AssertionStatus uint8 + +const ( + NoAssertion AssertionStatus = iota + AssertionPending + AssertionConfirmed +) + +func (a AssertionStatus) String() string { + switch a { + case NoAssertion: + return "no_assertion" + case AssertionPending: + return "pending" + case AssertionConfirmed: + return "confirmed" + default: + return "unknown_status" + } +} + +const BeforeDeadlineAssertionConfirmationError = "BEFORE_DEADLINE" +const ChallengeGracePeriodNotPassedAssertionConfirmationError = "CHALLENGE_GRACE_PERIOD_NOT_PASSED" + +// Assertion represents a top-level claim in the protocol about the +// chain state created by a validator that stakes on their claim. +// Assertions can be challenged. +type Assertion interface { + Id() AssertionHash + CreatedAtBlock() uint64 + PrevId(ctx context.Context) (AssertionHash, error) + HasSecondChild(ctx context.Context, opts *bind.CallOpts) (bool, error) + FirstChildCreationBlock(ctx context.Context, opts *bind.CallOpts) (uint64, error) + SecondChildCreationBlock(ctx context.Context, opts *bind.CallOpts) (uint64, error) + IsFirstChild(ctx context.Context, opts *bind.CallOpts) (bool, error) + Status(ctx context.Context, opts *bind.CallOpts) (AssertionStatus, error) +} + +// AssertionCreatedInfo from an event creation. +type AssertionCreatedInfo struct { + ConfirmPeriodBlocks uint64 + RequiredStake *big.Int + ParentAssertionHash AssertionHash + BeforeState rollupgen.AssertionState + AfterState rollupgen.AssertionState + InboxMaxCount *big.Int + AfterInboxBatchAcc common.Hash + AssertionHash AssertionHash + WasmModuleRoot common.Hash + ChallengeManager common.Address + TransactionHash common.Hash + CreationParentBlock uint64 + CreationL1Block uint64 +} + +func (i AssertionCreatedInfo) ExecutionHash() common.Hash { + afterGlobalStateHash := GoGlobalStateFromSolidity(i.AfterState.GlobalState).Hash() + return crypto.Keccak256Hash(append([]byte{i.AfterState.MachineStatus}, afterGlobalStateHash.Bytes()...)) +} + +// AssertionChain can manage assertions in the protocol and retrieve +// information about them. It also has an associated challenge manager +// which is used for all challenges in the protocol. +type AssertionChain interface { + // Read-only methods. + IsStaked(ctx context.Context) (bool, error) + RollupUserLogic() *rollupgen.RollupUserLogic + GetAssertion(ctx context.Context, opts *bind.CallOpts, id AssertionHash) (Assertion, error) + IsChallengeComplete(ctx context.Context, challengeParentAssertionHash AssertionHash) (bool, error) + Backend() ChainBackend + DesiredHeaderU64(ctx context.Context) (uint64, error) + DesiredL1HeaderU64(ctx context.Context) (uint64, error) + RollupAddress() common.Address + StakerAddress() common.Address + AssertionStatus( + ctx context.Context, + assertionHash AssertionHash, + ) (AssertionStatus, error) + LatestConfirmed(ctx context.Context, opts *bind.CallOpts) (Assertion, error) + GetAssertionCreationParentBlock(ctx context.Context, assertionHash common.Hash) (uint64, error) + ReadAssertionCreationInfo( + ctx context.Context, id AssertionHash, + ) (*AssertionCreatedInfo, error) + GetCallOptsWithDesiredRpcHeadBlockNumber(opts *bind.CallOpts) *bind.CallOpts + GetDesiredRpcHeadBlockNumber() rpc.BlockNumber + + MinAssertionPeriodBlocks() uint64 + AssertionUnrivaledBlocks(ctx context.Context, assertionHash AssertionHash) (uint64, error) + TopLevelAssertion(ctx context.Context, edgeId EdgeId) (AssertionHash, error) + TopLevelClaimHeights(ctx context.Context, edgeId EdgeId) (OriginHeights, error) + + // Mutating methods. + AutoDepositTokenForStaking( + ctx context.Context, + amount *big.Int, + ) error + ApproveAllowances( + ctx context.Context, + ) error + NewStake( + ctx context.Context, + ) error + NewStakeOnNewAssertion( + ctx context.Context, + assertionCreationInfo *AssertionCreatedInfo, + postState *ExecutionState, + ) (Assertion, error) + StakeOnNewAssertion( + ctx context.Context, + assertionCreationInfo *AssertionCreatedInfo, + postState *ExecutionState, + ) (Assertion, error) + FastConfirmAssertion( + ctx context.Context, + assertionCreationInfo *AssertionCreatedInfo, + ) (bool, error) + ConfirmAssertionByTime( + ctx context.Context, + assertionHash AssertionHash, + ) error + ConfirmAssertionByChallengeWinner( + ctx context.Context, + assertionHash AssertionHash, + winningEdgeId EdgeId, + ) error + + // Spec-based implementation methods. + SpecChallengeManager() SpecChallengeManager + + // MaxAssertionsPerChallenge period returns maximum number of assertions that + // may need to be processed during a challenge period of blocks. + MaxAssertionsPerChallengePeriod() uint64 +} + +// InheritedTimer for an edge from its children or claiming edges. +type InheritedTimer uint64 + +// ChallengeLevel corresponds to the different challenge levels in the protocol. +// 0 is for block challenges and the last level is for small step challenges. +// Everything else is a big step challenge of level i where 0 < i < last. +type ChallengeLevel uint8 + +func NewBlockChallengeLevel() ChallengeLevel { + return 0 +} + +func (et ChallengeLevel) Uint8() uint8 { + return uint8(et) +} +func (et ChallengeLevel) IsBlockChallengeLevel() bool { + return et == 0 +} + +func (et ChallengeLevel) Next() ChallengeLevel { + return et + 1 +} + +func (et ChallengeLevel) String() string { + if et == 0 { + return "block_challenge_edge" + } + return fmt.Sprintf("challenge_level_%d_edge", et) +} + +func ChallengeLevelFromString(s string) (ChallengeLevel, error) { + switch s { + case "block_challenge_edge": + return 0, nil + default: + re := regexp.MustCompile("[0-9]+") + challengeLevel, err := strconv.Atoi(re.FindString(s)) + if err != nil { + return 0, err + } + clu8, err := safecast.ToUint8(challengeLevel) + if err != nil { + return 0, err + } + return ChallengeLevel(clu8), nil + } +} + +// OriginId is the id of the item that originated a challenge an edge +// is a part of. In a block challenge, the origin id is the id of the assertion +// being challenged. In a big step challenge, it is the mutual id of the edge at the block challenge +// level that was the source of the one step fork leading to the big step challenge. +// In a small step challenge, it is the mutual id of the edge at the big step level that was +// the source of the one step fork leading to the small step challenge. +type OriginId common.Hash + +// MutualId is a unique identifier for an edge's start commitment and edge type. +// Rival edges share a mutual id. For example, an edge going A --> B, and another +// going from A --> C would share A, and we define the mutual id as the unique identifier +// for A. +type MutualId common.Hash + +// EdgeId is a unique identifier for an edge. Edge IDs encompass the edge type +// along with the start and end height + commitment for an edge. +type EdgeId struct { + common.Hash +} + +// ClaimId is the unique identifier of the commitment of a level zero edge corresponds to. +// For example, if assertion A has two children, B and C, and a block challenge is initiated +// on A, the level zero edges will have claim ids corresponding to assertions B and C when opened. +// The same occurs in the subchallenge layers, where claim ids are the edges at the higher challenge +// level corresponding to the level zero edges in the respective subchallenge. +type ClaimId common.Hash + +// OneStepData used for confirming edges by one step proofs. +type OneStepData struct { + BeforeHash common.Hash + AfterHash common.Hash + Proof []byte +} + +// SpecChallengeManager implements the research specification. +type SpecChallengeManager interface { + // Address of the challenge manager contract. + Address() common.Address + // Layer zero edge heights defined the challenge manager contract. + LayerZeroHeights() LayerZeroHeights + // Number of big step challenge levels defined in the challenge manager contract. + NumBigSteps() uint8 + // Duration of the challenge period in blocks. + ChallengePeriodBlocks() uint64 + // Gets an edge by its id. + GetEdge(ctx context.Context, edgeId EdgeId) (option.Option[SpecEdge], error) + MultiUpdateInheritedTimers( + ctx context.Context, + challengeBranch []ReadOnlyEdge, + desiredNewTimerForLastEdge uint64, + ) (*types.Transaction, error) + // Calculates an edge id for an edge. + CalculateEdgeId( + ctx context.Context, + edgeType ChallengeLevel, + originId OriginId, + startHeight Height, + startHistoryRoot common.Hash, + endHeight Height, + endHistoryRoot common.Hash, + ) (EdgeId, error) + // Adds a level-zero edge to a block challenge given an assertion and a history commitments. + AddBlockChallengeLevelZeroEdge( + ctx context.Context, + assertion Assertion, + startCommit, + endCommit history.History, + startEndPrefixProof []byte, + ) (VerifiedRoyalEdge, error) + // Adds a level-zero edge to subchallenge given a source edge and history commitments. + AddSubChallengeLevelZeroEdge( + ctx context.Context, + challengedEdge SpecEdge, + startCommit, + endCommit history.History, + startParentInclusionProof []common.Hash, + endParentInclusionProof []common.Hash, + startEndPrefixProof []byte, + ) (VerifiedRoyalEdge, error) + ConfirmEdgeByOneStepProof( + ctx context.Context, + tentativeWinnerId EdgeId, + oneStepData *OneStepData, + preHistoryInclusionProof []common.Hash, + postHistoryInclusionProof []common.Hash, + ) error +} + +// Height if defined as the height of a history commitment in the specification. +// Heights are 0-indexed. +type Height uint64 + +func (h Height) Uint64() uint64 { + return uint64(h) +} + +// EdgeStatus of an edge in the protocol. +type EdgeStatus uint8 + +const ( + EdgePending EdgeStatus = iota + EdgeConfirmed +) + +func (e EdgeStatus) String() string { + switch e { + case EdgePending: + return "pending" + case EdgeConfirmed: + return "confirmed" + default: + return "unknown" + } +} + +type OriginHeights struct { + ChallengeOriginHeights []Height `json:"challengeOriginHeights"` +} + +// ReadOnlyEdge defines methods that only retrieve data from the chain +// regarding for a given edge. +type ReadOnlyEdge interface { + // The unique identifier for an edge. + Id() EdgeId + // The challenge level the edge is a part of. + GetChallengeLevel() ChallengeLevel + // GetReversedChallengeLevel obtains the challenge level for the edge. The lowest level starts at 0, and goes all way + // up to the max number of levels. The reason we go from the lowest challenge level being 0 instead of 2 + // is to make our code a lot more readable. If we flipped the order, we would need to do + // a lot of backwards for loops instead of simple range loops over slices. + GetReversedChallengeLevel() ChallengeLevel + // Total number possible challenge levels. + GetTotalChallengeLevels(ctx context.Context) uint8 + // The start height and history commitment for an edge. + StartCommitment() (Height, common.Hash) + // The end height and history commitment for an edge. + EndCommitment() (Height, common.Hash) + // The block number the edge was created at. + CreatedAtBlock() (uint64, error) + // The mutual id of the edge. + MutualId() MutualId + // The origin id of the edge. + OriginId() OriginId + // The claim id of the edge, if any + ClaimId() option.Option[ClaimId] + // Checks if the edge has children. + HasChildren(ctx context.Context) (bool, error) + // The lower child of the edge, if any. + LowerChild(ctx context.Context) (option.Option[EdgeId], error) + // The upper child of the edge, if any. + UpperChild(ctx context.Context) (option.Option[EdgeId], error) + // The ministaker of an edge. Only existing for level zero edges. + MiniStaker() option.Option[common.Address] + // The assertion hash of the parent assertion that originated the challenge + // at the top-level. + AssertionHash(ctx context.Context) (AssertionHash, error) + // The time in seconds an edge has been unrivaled. + TimeUnrivaled(ctx context.Context) (uint64, error) + // The inherited timer from the edge's children or claiming edges based on the configured block number. + LatestInheritedTimer(ctx context.Context) (InheritedTimer, error) + // Whether or not an edge has rivals. + HasRival(ctx context.Context) (bool, error) + // The status of an edge. + Status(ctx context.Context) (EdgeStatus, error) + // The block at which the edge was confirmed. + ConfirmedAtBlock(ctx context.Context) (uint64, error) + // Checks if an edge has a length one rival. + HasLengthOneRival(ctx context.Context) (bool, error) + // The history commitment for the top-level edge the current edge's challenge is made upon. + // This is used at subchallenge creation boundaries. + TopLevelClaimHeight(ctx context.Context) (OriginHeights, error) +} + +// SpecEdge according to the protocol specification. +type SpecEdge interface { + ReadOnlyEdge + MarkAsHonest() + AsVerifiedHonest() (VerifiedRoyalEdge, bool) +} + +// VerifiedRoyalEdge marks edges that are known to be royal. For example, +// when a local validator creates an edge, it is known to be royal and several types +// expensive or duplicate computation can be avoided in methods that take in this type. +// A sentinel method `Honest()` is used to mark an edge as satisfying this interface. +type VerifiedRoyalEdge interface { + SpecEdge + // Bisect defines a method to bisect an edge into two children. + // Returns the two child edges that are created as a result. + // Only honest edges should be bisected. + Bisect( + ctx context.Context, + prefixHistoryRoot common.Hash, + prefixProof []byte, + ) (VerifiedRoyalEdge, VerifiedRoyalEdge, error) + // ConfirmByTimer confirms an edge for having a total timer >= one challenge period. + // The claimed assertion hash the edge corresponds to is required as part of the onchain + // transaction to confirm the edge. + // Only honest edges should be confirmed by timer. + ConfirmByTimer(ctx context.Context, claimedAssertion AssertionHash) (*types.Transaction, error) + Honest() +} diff --git a/bold/chain-abstraction/sol-implementation/assertion_chain.go b/bold/chain-abstraction/sol-implementation/assertion_chain.go new file mode 100644 index 0000000000..3151db8356 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/assertion_chain.go @@ -0,0 +1,1143 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package solimpl includes an easy-to-use abstraction +// around the challenge protocol contracts using their Go +// bindings and exposes minimal details of Ethereum's internals. +package solimpl + +import ( + "context" + "flag" + "fmt" + "math/big" + "strings" + "time" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" + retry "github.com/offchainlabs/bold/runtime" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" + "github.com/offchainlabs/nitro/solgen/go/testgen" +) + +var ( + ErrNotFound = errors.New("item not found on-chain") + ErrBatchNotYetFound = errors.New("batch not yet found") + ErrAlreadyExists = errors.New("item already exists on-chain") + ErrPrevDoesNotExist = errors.New("assertion predecessor does not exist") + ErrTooLate = errors.New("too late to create assertion sibling") +) + +var assertionCreatedId common.Hash + +var defaultBaseGas = int64(500000) + +func init() { + rollupAbi, err := rollupgen.RollupCoreMetaData.GetAbi() + if err != nil { + panic(err) + } + assertionCreatedEvent, ok := rollupAbi.Events["AssertionCreated"] + if !ok { + panic("RollupCore ABI missing AssertionCreated event") + } + assertionCreatedId = assertionCreatedEvent.ID +} + +// ReceiptFetcher defines the ability to retrieve transactions receipts from the chain. +type ReceiptFetcher interface { + TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) +} + +// Transactor defines the ability to send transactions to the chain. +type Transactor interface { + SendTransaction(ctx context.Context, fn func(opts *bind.TransactOpts) (*types.Transaction, error), opts *bind.TransactOpts, gas uint64) (*types.Transaction, error) +} + +type ChainBackendTransactor struct { + protocol.ChainBackend + fifo *FIFO +} + +func NewChainBackendTransactor(backend protocol.ChainBackend) *ChainBackendTransactor { + return &ChainBackendTransactor{ + ChainBackend: backend, + fifo: NewFIFO(1000), + } +} + +func (d *ChainBackendTransactor) SendTransaction(ctx context.Context, fn func(opts *bind.TransactOpts) (*types.Transaction, error), opts *bind.TransactOpts, gas uint64) (*types.Transaction, error) { + // Try to acquire lock and if it fails, wait for a bit and try again. + for !d.fifo.Lock() { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(100 * time.Millisecond): + } + } + defer d.fifo.Unlock() + tx, err := fn(opts) + if err != nil { + return nil, err + } + return tx, d.ChainBackend.SendTransaction(ctx, tx) +} + +// AssertionChain is a wrapper around solgen bindings +// that implements the protocol interface. +type AssertionChain struct { + backend protocol.ChainBackend + rollup *rollupgen.RollupCore + userLogic *rollupgen.RollupUserLogic + txOpts *bind.TransactOpts + rollupAddr common.Address + chalManagerAddr common.Address + confirmedChallengesByParentAssertionHash *threadsafe.LruSet[protocol.AssertionHash] + specChallengeManager protocol.SpecChallengeManager + averageTimeForBlockCreation time.Duration + minAssertionPeriodBlocks uint64 + transactor Transactor + withdrawalAddress common.Address + stakeTokenAddr common.Address + autoDeposit bool + enableFastConfirmation bool + fastConfirmSafe *FastConfirmSafe + // rpcHeadBlockNumber is the block number of the latest block on the chain. + // It is set to rpc.FinalizedBlockNumber by default. + // WithRpcHeadBlockNumber can be used to set a different block number. + rpcHeadBlockNumber rpc.BlockNumber +} + +type Opt func(*AssertionChain) + +func WithTrackedContractBackend() Opt { + return func(a *AssertionChain) { + a.backend = NewTrackedContractBackend(a.backend) + } +} + +func WithMetricsContractBackend() Opt { + return func(a *AssertionChain) { + a.backend = NewMetricsContractBackend(a.backend) + } +} + +func WithRpcHeadBlockNumber(rpcHeadBlockNumber rpc.BlockNumber) Opt { + return func(a *AssertionChain) { + a.rpcHeadBlockNumber = rpcHeadBlockNumber + } +} + +// WithCustomWithdrawalAddress specifies a custom withdrawal address for validators that +// choose to perform a delegated stake to participate in BoLD. +func WithCustomWithdrawalAddress(address common.Address) Opt { + return func(a *AssertionChain) { + a.withdrawalAddress = address + } +} + +// WithoutAutoDeposit prevents the assertion chain from automatically depositing stake token +// funds when making stakes on assertions or challenge edges. +func WithoutAutoDeposit() Opt { + return func(a *AssertionChain) { + a.autoDeposit = false + } +} + +// WithFastConfirmation enables fast confirmation for the assertion chain. +func WithFastConfirmation() Opt { + return func(a *AssertionChain) { + a.enableFastConfirmation = true + } +} + +// WithParentChainBlockCreationTime sets the average time for block creation of the chain where +// assertions are posted to and fetched from. +func WithParentChainBlockCreationTime(d time.Duration) Opt { + return func(a *AssertionChain) { + a.averageTimeForBlockCreation = d + } +} + +// NewAssertionChain instantiates an assertion chain +// instance from a chain backend and provided options. +func NewAssertionChain( + ctx context.Context, + rollupAddr common.Address, + chalManagerAddr common.Address, + txOpts *bind.TransactOpts, + backend protocol.ChainBackend, + transactor Transactor, + opts ...Opt, +) (*AssertionChain, error) { + // We disable sending txs by default, as we will first estimate their gas before + // we commit them onchain through the transact method in this package. + copiedOpts := copyTxOpts(txOpts) + chain := &AssertionChain{ + backend: backend, + txOpts: copiedOpts, + rollupAddr: rollupAddr, + chalManagerAddr: chalManagerAddr, + confirmedChallengesByParentAssertionHash: threadsafe.NewLruSet(1000, threadsafe.LruSetWithMetric[protocol.AssertionHash]("confirmedChallengesByParentAssertionHash")), + averageTimeForBlockCreation: time.Second * 12, + transactor: transactor, + rpcHeadBlockNumber: rpc.LatestBlockNumber, + withdrawalAddress: copiedOpts.From, // Default to the tx opts' sender. + autoDeposit: true, + } + for _, opt := range opts { + opt(chain) + } + coreBinding, err := rollupgen.NewRollupCore( + rollupAddr, chain.backend, + ) + if err != nil { + return nil, err + } + assertionChainBinding, err := rollupgen.NewRollupUserLogic( + rollupAddr, chain.backend, + ) + if err != nil { + return nil, err + } + chain.rollup = coreBinding + callOpts := chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + minPeriod, err := chain.rollup.MinimumAssertionPeriod(callOpts) + if err != nil { + return nil, err + } + if !minPeriod.IsUint64() { + return nil, errors.New("minimum assertion period was not a uint64") + } + if minPeriod.Uint64() == 0 { + minPeriod = big.NewInt(1) + } + stakeTokenAddr, err := chain.rollup.StakeToken(callOpts) + if err != nil { + return nil, err + } + code, err := backend.CodeAt(ctx, stakeTokenAddr, nil) + if err != nil { + return nil, err + } + if len(code) == 0 { + return nil, fmt.Errorf("stake token address %#x has no code", stakeTokenAddr) + } + chain.stakeTokenAddr = stakeTokenAddr + log.Info("Minimum assertion period", "blocks", minPeriod.Uint64()) + chain.minAssertionPeriodBlocks = minPeriod.Uint64() + chain.userLogic = assertionChainBinding + specChallengeManager, err := NewSpecChallengeManager( + ctx, + chain.chalManagerAddr, + chain, + chain.backend, + chain.txOpts, + ) + if err != nil { + return nil, err + } + chain.specChallengeManager = specChallengeManager + err = chain.setupFastConfirmation(callOpts) + if err != nil { + return nil, err + } + return chain, nil +} + +func (a *AssertionChain) setupFastConfirmation(callOpts *bind.CallOpts) error { + if !a.enableFastConfirmation { + return nil + } + fastConfirmer, err := retry.UntilSucceeds(callOpts.Context, func() (common.Address, error) { + return a.rollup.AnyTrustFastConfirmer(callOpts) + }) + if err != nil { + return fmt.Errorf("getting rollup fast confirmer address: %w", err) + } + log.Info("Setting up fast confirmation", "stakerAddress", a.StakerAddress(), "fastConfirmer", fastConfirmer) + if fastConfirmer == a.StakerAddress() { + // We can directly fast confirm nodes + return nil + } else if fastConfirmer == (common.Address{}) { + // No fast confirmer enabled + return errors.New("fast confirmation enabled in config, but no fast confirmer set in rollup contract") + } + // The fast confirmer address is a contract address, not sure if it's a safe contract yet. + fastConfirmSafe, err := NewFastConfirmSafe(callOpts, fastConfirmer, a) + if err != nil { + // Unknown while loading the safe contract. + return fmt.Errorf("loading fast confirm safe: %w", err) + } + // Fast confirmer address implements getOwners() and is probably a safe. + isOwner, err := retry.UntilSucceeds(callOpts.Context, func() (bool, error) { + return fastConfirmSafe.safe.IsOwner(callOpts, a.StakerAddress()) + }) + if err != nil { + return fmt.Errorf("checking if wallet is owner of safe: %w", err) + } + if !isOwner { + return fmt.Errorf("staker wallet address %v is not an owner of the fast confirm safe %v", a.StakerAddress(), fastConfirmer) + } + a.fastConfirmSafe = fastConfirmSafe + return nil +} +func (a *AssertionChain) RollupUserLogic() *rollupgen.RollupUserLogic { + return a.userLogic +} + +func (a *AssertionChain) RollupCore() *rollupgen.RollupCore { + return a.rollup +} + +func (a *AssertionChain) Backend() protocol.ChainBackend { + return a.backend +} + +func (a *AssertionChain) DesiredHeaderU64(ctx context.Context) (uint64, error) { + header, err := a.backend.HeaderByNumber(ctx, big.NewInt(int64(a.rpcHeadBlockNumber))) + if err != nil { + return 0, err + } + if !header.Number.IsUint64() { + return 0, errors.New("block number is not uint64") + } + return header.Number.Uint64(), nil +} + +func (a *AssertionChain) DesiredL1HeaderU64(ctx context.Context) (uint64, error) { + header, err := a.backend.HeaderByNumber(ctx, big.NewInt(int64(a.rpcHeadBlockNumber))) + if err != nil { + return 0, err + } + headerInfo := types.DeserializeHeaderExtraInformation(header) + if headerInfo.ArbOSFormatVersion > 0 { + return headerInfo.L1BlockNumber, nil + } + if !header.Number.IsUint64() { + return 0, errors.New("block number is not uint64") + } + return header.Number.Uint64(), nil +} + +func (a *AssertionChain) GetAssertion(ctx context.Context, opts *bind.CallOpts, assertionHash protocol.AssertionHash) (protocol.Assertion, error) { + var b [32]byte + copy(b[:], assertionHash.Bytes()) + res, err := a.userLogic.GetAssertion(opts, b) + if err != nil { + return nil, err + } + if res.Status == uint8(protocol.NoAssertion) { + return nil, errors.Wrapf( + ErrNotFound, + "assertion with id %#x", + assertionHash, + ) + } + return &Assertion{ + id: assertionHash, + chain: a, + createdAt: res.CreatedAtBlock, + }, nil +} + +func (a *AssertionChain) AssertionStatus(ctx context.Context, assertionHash protocol.AssertionHash) (protocol.AssertionStatus, error) { + res, err := a.rollup.GetAssertion(&bind.CallOpts{Context: ctx}, assertionHash.Hash) + if err != nil { + return protocol.NoAssertion, err + } + return protocol.AssertionStatus(res.Status), nil +} + +func (a *AssertionChain) LatestConfirmed(ctx context.Context, opts *bind.CallOpts) (protocol.Assertion, error) { + res, err := a.rollup.LatestConfirmed(opts) + if err != nil { + return nil, err + } + return a.GetAssertion(ctx, opts, protocol.AssertionHash{Hash: res}) +} + +// Returns true if the staker's address is currently staked in the assertion chain. +func (a *AssertionChain) IsStaked(ctx context.Context) (bool, error) { + return a.rollup.IsStaked(&bind.CallOpts{Context: ctx}, a.StakerAddress()) +} + +// RollupAddress for the assertion chain. +func (a *AssertionChain) RollupAddress() common.Address { + return a.rollupAddr +} + +// StakerAddress for the staker which initialized this chain interface. +func (a *AssertionChain) StakerAddress() common.Address { + return a.txOpts.From +} + +// IsChallengeComplete checks if a challenge is complete by using the challenge's parent assertion hash. +func (a *AssertionChain) IsChallengeComplete( + ctx context.Context, + challengeParentAssertionHash protocol.AssertionHash, +) (bool, error) { + if a.confirmedChallengesByParentAssertionHash.Has(challengeParentAssertionHash) { + return true, nil + } + parentAssertionStatus, err := a.AssertionStatus(ctx, challengeParentAssertionHash) + if err != nil { + return false, err + } + // Parent must be confirmed for a challenge to be considered complete, so we can + // short-circuit early here. + parentIsConfirmed := parentAssertionStatus == protocol.AssertionConfirmed + if !parentIsConfirmed { + return false, nil + } + latestConfirmed, err := a.LatestConfirmed(ctx, a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return false, err + } + // A challenge is complete if the parent assertion of the challenge is confirmed + // and the latest confirmed assertion hash is not equal to the challenge's parent assertion hash. + challengeConfirmed := latestConfirmed.Id() != challengeParentAssertionHash + if challengeConfirmed { + a.confirmedChallengesByParentAssertionHash.Insert(challengeParentAssertionHash) + } + return challengeConfirmed, nil +} + +// AutoDepositTokenForStaking ensures that the validator has enough funds to stake +// on assertions if not already staked, and then deposits the difference required to participate. +func (a *AssertionChain) AutoDepositTokenForStaking( + ctx context.Context, + amount *big.Int, +) error { + staked, err := a.IsStaked(ctx) + if err != nil { + return err + } + if staked { + return nil + } + return a.autoDepositFunds(ctx, amount) +} + +// Attempts to auto-wrap ETH to WETH with the required amount that is specified to the function. +// This function uses `latest` onchain data to determine the current balance of the staker +// and deposits the difference between the required amount and the current balance. +func (a *AssertionChain) autoDepositFunds(ctx context.Context, amount *big.Int) error { + if !a.autoDeposit { + return nil + } + // The validity of the stake token address containing code is checked in the constructor + // of the assertion chain. + erc20, err := testgen.NewERC20Token(a.stakeTokenAddr, a.backend) + if err != nil { + return err + } + balance, err := erc20.BalanceOf(&bind.CallOpts{Context: ctx}, a.txOpts.From) + if err != nil { + return err + } + // Get the difference between the required amount and the current balance. + // If we have more than enough balance, we exit early. + if balance.Cmp(amount) >= 0 { + return nil + } + diff := new(big.Int).Sub(amount, balance) + weth, err := mocksgen.NewIWETH9(a.stakeTokenAddr, a.backend) + if err != nil { + return err + } + // Otherwise, we deposit the difference. + receipt, err := a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + opts.Value = diff + return weth.Deposit(opts) + }) + if err != nil { + return err + } + _ = receipt + return nil +} + +func (a *AssertionChain) ApproveAllowances( + ctx context.Context, +) error { + // The validity of the stake token address containing code is checked in the constructor + // of the assertion chain. + erc20, err := testgen.NewERC20Token(a.stakeTokenAddr, a.backend) + if err != nil { + return err + } + rollupAllowance, err := erc20.Allowance(&bind.CallOpts{Context: ctx}, a.txOpts.From, a.rollupAddr) + if err != nil { + return err + } + chalManagerAllowance, err := erc20.Allowance(&bind.CallOpts{Context: ctx}, a.txOpts.From, a.chalManagerAddr) + if err != nil { + return err + } + maxUint256 := new(big.Int) + maxUint256.SetString("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) + if rollupAllowance.Cmp(maxUint256) == 0 { + return nil + } + // Approve the rollup and challenge manager spending the user's stake token. + if _, err = a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return erc20.Approve(opts, a.rollupAddr, maxUint256) + }); err != nil { + return err + } + if chalManagerAllowance.Cmp(maxUint256) == 0 { + return nil + } + if _, err = a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return erc20.Approve(opts, a.chalManagerAddr, maxUint256) + }); err != nil { + return err + } + return nil +} + +// NewStake is a function made for stakers that are delegated. It allows them to mark themselves as a "pending" +// staker in the rollup contracts with some required stake and allows another party to fund the staker onchain +// to proceed with its activities. +func (a *AssertionChain) NewStake( + ctx context.Context, +) error { + staked, err := a.IsStaked(ctx) + if err != nil { + return err + } + if staked { + return nil + } + _, err = a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return a.userLogic.NewStake(opts, new(big.Int), a.withdrawalAddress) + }) + return err +} + +// NewStakeOnNewAssertion makes an onchain claim given a previous assertion hash, execution state, +// and a commitment to a post-state. It also adds a new stake to the newly created assertion. +// if the validator is already staked, use StakeOnNewAssertion instead. +func (a *AssertionChain) NewStakeOnNewAssertion( + ctx context.Context, + parentAssertionCreationInfo *protocol.AssertionCreatedInfo, + postState *protocol.ExecutionState, +) (protocol.Assertion, error) { + stakeFn := func( + opts *bind.TransactOpts, + tokenAmount *big.Int, + assertionInputs rollupgen.AssertionInputs, + expectedAssertionHash [32]byte, + ) (*types.Transaction, error) { + return a.userLogic.NewStakeOnNewAssertion50f32f68( + opts, + tokenAmount, + assertionInputs, + expectedAssertionHash, + a.withdrawalAddress, + ) + } + return a.createAndStakeOnAssertion( + ctx, + parentAssertionCreationInfo, + postState, + stakeFn, + ) +} + +// StakeOnNewAssertion makes an onchain claim given a previous assertion hash, execution state, +// and a commitment to a post-state. It also adds moves an existing stake to the newly created assertion. +// if the validator is not staked, use NewStakeOnNewAssertion instead. +func (a *AssertionChain) StakeOnNewAssertion( + ctx context.Context, + parentAssertionCreationInfo *protocol.AssertionCreatedInfo, + postState *protocol.ExecutionState, +) (protocol.Assertion, error) { + stakeFn := func(opts *bind.TransactOpts, _ *big.Int, assertionInputs rollupgen.AssertionInputs, assertionHash [32]byte) (*types.Transaction, error) { + return a.userLogic.StakeOnNewAssertion( + opts, + assertionInputs, + assertionHash, + ) + } + return a.createAndStakeOnAssertion( + ctx, + parentAssertionCreationInfo, + postState, + stakeFn, + ) +} + +func (a *AssertionChain) createAndStakeOnAssertion( + ctx context.Context, + parentAssertionCreationInfo *protocol.AssertionCreatedInfo, + postState *protocol.ExecutionState, + stakeFn func(opts *bind.TransactOpts, requiredStake *big.Int, assertionInputs rollupgen.AssertionInputs, assertionHash [32]byte) (*types.Transaction, error), +) (protocol.Assertion, error) { + if !parentAssertionCreationInfo.InboxMaxCount.IsUint64() { + return nil, errors.New("prev assertion creation info inbox max count not a uint64") + } + if postState.GlobalState.Batch == 0 { + return nil, errors.New("assertion post state cannot have a batch count of 0, as only genesis can") + } + bridgeAddr, err := a.userLogic.Bridge(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, errors.Wrap(err, "could not retrieve bridge address for user rollup logic contract") + } + bridge, err := bridgegen.NewIBridgeCaller(bridgeAddr, a.backend) + if err != nil { + return nil, errors.Wrapf(err, "could not initialize bridge at address %#x", bridgeAddr) + } + inboxBatchAcc, err := bridge.SequencerInboxAccs( + a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), + new(big.Int).SetUint64(postState.GlobalState.Batch-1), + ) + if err != nil { + return nil, ErrBatchNotYetFound + } + computedHash, err := a.userLogic.ComputeAssertionHash( + a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), + parentAssertionCreationInfo.AssertionHash.Hash, + postState.AsSolidityStruct(), + inboxBatchAcc, + ) + if err != nil { + return nil, errors.Wrap(err, "could not compute assertion hash") + } + // Check if the assertion already exists based on latest head, which means we should not make a mutating call. + existingAssertion, err := a.GetAssertion(ctx, &bind.CallOpts{Context: ctx}, protocol.AssertionHash{Hash: computedHash}) + switch { + case err == nil: + return existingAssertion, nil + case !errors.Is(err, ErrNotFound): + return nil, errors.Wrapf(err, "could not fetch assertion with computed hash %#x", computedHash) + default: + } + staked, err := a.IsStaked(ctx) + if err != nil { + return nil, err + } + if !staked { + if err = a.autoDepositFunds(ctx, parentAssertionCreationInfo.RequiredStake); err != nil { + return nil, errors.Wrapf(err, "could not auto-deposit funds for assertion creation") + } + } + receipt, err := a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return stakeFn( + opts, + parentAssertionCreationInfo.RequiredStake, + rollupgen.AssertionInputs{ + BeforeStateData: rollupgen.BeforeStateData{ + PrevPrevAssertionHash: parentAssertionCreationInfo.ParentAssertionHash.Hash, + SequencerBatchAcc: parentAssertionCreationInfo.AfterInboxBatchAcc, + ConfigData: rollupgen.ConfigData{ + RequiredStake: parentAssertionCreationInfo.RequiredStake, + ChallengeManager: parentAssertionCreationInfo.ChallengeManager, + ConfirmPeriodBlocks: parentAssertionCreationInfo.ConfirmPeriodBlocks, + WasmModuleRoot: parentAssertionCreationInfo.WasmModuleRoot, + NextInboxPosition: parentAssertionCreationInfo.InboxMaxCount.Uint64(), + }, + }, + BeforeState: parentAssertionCreationInfo.AfterState, + AfterState: postState.AsSolidityStruct(), + }, + computedHash, + ) + }) + opts := a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + if createErr := handleCreateAssertionError(err, postState.GlobalState.BlockHash); createErr != nil { + if strings.Contains(err.Error(), "already exists") { + assertionItem, err2 := a.GetAssertion(ctx, opts, protocol.AssertionHash{Hash: computedHash}) + if err2 != nil { + return nil, err2 + } + return assertionItem, nil + } + return nil, fmt.Errorf("could not create assertion: %w", createErr) + } + if len(receipt.Logs) == 0 { + return nil, errors.New("no logs observed from assertion creation") + } + var assertionCreated *rollupgen.RollupCoreAssertionCreated + var found bool + for _, log := range receipt.Logs { + creationEvent, err := a.rollup.ParseAssertionCreated(*log) + if err == nil { + assertionCreated = creationEvent + found = true + break + } + } + if !found { + return nil, errors.New("could not find assertion created event in logs") + } + return a.GetAssertion(ctx, opts, protocol.AssertionHash{Hash: assertionCreated.AssertionHash}) +} + +func (a *AssertionChain) GenesisAssertionHash(ctx context.Context) (common.Hash, error) { + return a.userLogic.GenesisAssertionHash(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) +} + +func (a *AssertionChain) MinAssertionPeriodBlocks() uint64 { + return a.minAssertionPeriodBlocks +} + +// MaxAssertionsPerChallenge period returns maximum number of assertions that +// may need to be processed during a challenge period of blocks. +func (a *AssertionChain) MaxAssertionsPerChallengePeriod() uint64 { + cb := a.SpecChallengeManager().ChallengePeriodBlocks() + return cb / a.minAssertionPeriodBlocks +} + +func TryConfirmingAssertion( + ctx context.Context, + assertionHash protocol.AssertionHash, + confirmableAfterBlock uint64, + chain protocol.AssertionChain, + averageTimeForBlockCreation time.Duration, + winningEdgeId option.Option[protocol.EdgeId], +) (bool, error) { + status, err := chain.AssertionStatus(ctx, assertionHash) + if err != nil { + return false, fmt.Errorf("could not get assertion by hash: %#x: %w", assertionHash, err) + } + if status == protocol.NoAssertion { + return false, fmt.Errorf("no assertion found by hash: %#x", assertionHash) + } + if status == protocol.AssertionConfirmed { + return true, nil + } + for { + var latestL1HeaderNumber uint64 + latestL1HeaderNumber, err = chain.DesiredL1HeaderU64(ctx) + if err != nil { + return false, err + } + confirmable := latestL1HeaderNumber >= confirmableAfterBlock + + // If the assertion is not yet confirmable, we can simply wait. + if !confirmable { + var blocksLeftForConfirmation int64 + if latestL1HeaderNumber > confirmableAfterBlock { + blocksLeftForConfirmation = 0 + } else { + blocksLeftForConfirmation, err = safecast.ToInt64(confirmableAfterBlock - latestL1HeaderNumber) + if err != nil { + return false, err + } + } + timeToWait := averageTimeForBlockCreation * time.Duration(blocksLeftForConfirmation) + log.Info( + fmt.Sprintf( + "Assertion with hash %s needs at least %d blocks before being confirmable, waiting for %s", + containers.Trunc(assertionHash.Bytes()), + blocksLeftForConfirmation, + timeToWait, + ), + ) + select { + case <-time.After(timeToWait): + case <-ctx.Done(): + return false, ctx.Err() + } + } else { + break + } + } + + if winningEdgeId.IsSome() { + err = chain.ConfirmAssertionByChallengeWinner(ctx, assertionHash, winningEdgeId.Unwrap()) + if err != nil { + if strings.Contains(err.Error(), protocol.ChallengeGracePeriodNotPassedAssertionConfirmationError) { + return false, nil + } + if strings.Contains(err.Error(), "is not the latest confirmed assertion") { + return false, nil + } + return false, err + + } + } else { + err = chain.ConfirmAssertionByTime(ctx, assertionHash) + if err != nil { + if strings.Contains(err.Error(), protocol.BeforeDeadlineAssertionConfirmationError) { + return false, nil + } + if strings.Contains(err.Error(), "is not the latest confirmed assertion") { + return false, nil + } + return false, err + } + } + return true, nil +} + +func (a *AssertionChain) ConfirmAssertionByTime(ctx context.Context, assertionHash protocol.AssertionHash) error { + return a.ConfirmAssertionByChallengeWinner(ctx, assertionHash, protocol.EdgeId{}) +} + +// ConfirmAssertionByChallengeWinner attempts to confirm an assertion onchain +// if there is a winning, level zero, block challenge edge that claims it. +func (a *AssertionChain) ConfirmAssertionByChallengeWinner( + ctx context.Context, + assertionHash protocol.AssertionHash, + winningEdgeId protocol.EdgeId, +) error { + var b [32]byte + copy(b[:], assertionHash.Bytes()) + node, err := a.userLogic.GetAssertion(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), b) + if err != nil { + return err + } + if node.Status == uint8(protocol.AssertionConfirmed) { + return nil + } + creationInfo, err := a.ReadAssertionCreationInfo(ctx, assertionHash) + if err != nil { + return err + } + // If the assertion is genesis, return nil. + if creationInfo.ParentAssertionHash.Hash == [32]byte{} { + return nil + } + prevCreationInfo, err := a.ReadAssertionCreationInfo(ctx, creationInfo.ParentAssertionHash) + if err != nil { + return err + } + latestConfirmed, err := a.LatestConfirmed(ctx, &bind.CallOpts{Context: ctx}) + if err != nil { + return err + } + if creationInfo.ParentAssertionHash != latestConfirmed.Id() { + return fmt.Errorf( + "parent id %#x is not the latest confirmed assertion %#x", + creationInfo.ParentAssertionHash, + latestConfirmed.Id(), + ) + } + if !prevCreationInfo.InboxMaxCount.IsUint64() { + return errors.New("assertion prev creation info inbox max count was not a uint64") + } + receipt, err := a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return a.userLogic.ConfirmAssertion( + opts, + b, + creationInfo.ParentAssertionHash.Hash, + creationInfo.AfterState, + winningEdgeId.Hash, + rollupgen.ConfigData{ + WasmModuleRoot: prevCreationInfo.WasmModuleRoot, + ConfirmPeriodBlocks: prevCreationInfo.ConfirmPeriodBlocks, + RequiredStake: prevCreationInfo.RequiredStake, + ChallengeManager: prevCreationInfo.ChallengeManager, + NextInboxPosition: prevCreationInfo.InboxMaxCount.Uint64(), + }, + creationInfo.AfterInboxBatchAcc, + ) + }) + if err != nil { + return err + } + if len(receipt.Logs) == 0 { + return errors.New("no logs observed from assertion confirmation") + } + return nil +} + +// FastConfirmAssertion attempts to fast confirm an assertion onchain. +func (a *AssertionChain) FastConfirmAssertion( + ctx context.Context, + assertionCreationInfo *protocol.AssertionCreatedInfo, +) (bool, error) { + if a.fastConfirmSafe != nil { + return a.fastConfirmSafe.fastConfirmAssertion(ctx, assertionCreationInfo) + } + receipt, err := a.transact(ctx, a.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return a.userLogic.FastConfirmAssertion( + opts, + assertionCreationInfo.AssertionHash.Hash, + assertionCreationInfo.ParentAssertionHash.Hash, + assertionCreationInfo.AfterState, + assertionCreationInfo.AfterInboxBatchAcc, + ) + }) + if err != nil { + return false, err + } + if len(receipt.Logs) == 0 { + return false, errors.New("no logs observed from assertion confirmation") + } + return true, nil +} + +// SpecChallengeManager returns the assertions chain's spec challenge manager. +func (a *AssertionChain) SpecChallengeManager() protocol.SpecChallengeManager { + return a.specChallengeManager +} + +// AssertionUnrivaledBlocks gets the number of blocks an assertion was unrivaled. That is, it looks up the +// assertion's parent, and from that parent, computes second_child_creation_block - first_child_creation_block. +// If an assertion is a second child, this function will return 0. +func (a *AssertionChain) AssertionUnrivaledBlocks(ctx context.Context, assertionHash protocol.AssertionHash) (uint64, error) { + var b [32]byte + copy(b[:], assertionHash.Bytes()) + wantNode, err := a.rollup.GetAssertion(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), b) + if err != nil { + return 0, err + } + if wantNode.Status == uint8(protocol.NoAssertion) { + return 0, errors.Wrapf( + ErrNotFound, + "assertion with id %#x", + assertionHash, + ) + } + // If the assertion requested is not the first child, it was never unrivaled. + if !wantNode.IsFirstChild { + return 0, nil + } + assertion := &Assertion{ + id: assertionHash, + chain: a, + createdAt: wantNode.CreatedAtBlock, + } + prevId, err := assertion.PrevId(ctx) + if err != nil { + return 0, err + } + copy(b[:], prevId.Bytes()) + prevNode, err := a.rollup.GetAssertion(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), b) + if err != nil { + return 0, err + } + if prevNode.Status == uint8(protocol.NoAssertion) { + return 0, errors.Wrapf( + ErrNotFound, + "assertion with id %#x", + assertionHash, + ) + } + // If there is no second child, we simply return the number of blocks + // since the assertion was created and its parent. + if prevNode.SecondChildBlock == 0 { + l1BlockNum, err := a.DesiredL1HeaderU64(ctx) + if err != nil { + return 0, err + } + + // Should never happen. + if assertion.CreatedAtBlock() > l1BlockNum { + return 0, fmt.Errorf( + "assertion creation block %d > latest block number %d for assertion hash %#x", + assertion.CreatedAtBlock(), + l1BlockNum, + assertionHash, + ) + } + return l1BlockNum - assertion.CreatedAtBlock(), nil + } + // Should never happen. + if prevNode.FirstChildBlock > prevNode.SecondChildBlock { + return 0, fmt.Errorf( + "first child creation block %d > second child creation block %d for assertion hash %#x", + prevNode.FirstChildBlock, + prevNode.SecondChildBlock, + prevId, + ) + } + return prevNode.SecondChildBlock - prevNode.FirstChildBlock, nil +} + +// GetAssertionCreationParentBlock returns parent chain block number when the assertion was created. +// assertion.CreatedAtBlock is the block number when the assertion was created on L1. +// But in case of L3, we need to look up the block number when the assertion was created on L2. +// To do this, we use getAssertionCreationBlockForLogLookup which returns the block number when the assertion was created +// on parent chain be it L2 or L1. +func (a *AssertionChain) GetAssertionCreationParentBlock(ctx context.Context, assertionHash common.Hash) (uint64, error) { + callOpts := a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + createdAtBlock, err := a.userLogic.GetAssertionCreationBlockForLogLookup(callOpts, assertionHash) + if err != nil { + return 0, errors.Wrapf(err, "could not get assertion creation block for assertion hash %#x", assertionHash) + } + if !createdAtBlock.IsUint64() { + return 0, errors.New(fmt.Sprintf("for assertion hash %#x, createdAtBlock was not a uint64", assertionHash)) + + } + return createdAtBlock.Uint64(), nil +} + +func (a *AssertionChain) TopLevelAssertion(ctx context.Context, edgeId protocol.EdgeId) (protocol.AssertionHash, error) { + cm := a.SpecChallengeManager() + edgeOpt, err := cm.GetEdge(ctx, edgeId) + if err != nil { + return protocol.AssertionHash{}, err + } + if edgeOpt.IsNone() { + return protocol.AssertionHash{}, errors.New("edge was nil") + } + return edgeOpt.Unwrap().AssertionHash(ctx) +} + +func (a *AssertionChain) TopLevelClaimHeights(ctx context.Context, edgeId protocol.EdgeId) (protocol.OriginHeights, error) { + cm := a.SpecChallengeManager() + edgeOpt, err := cm.GetEdge(ctx, edgeId) + if err != nil { + return protocol.OriginHeights{}, err + } + if edgeOpt.IsNone() { + return protocol.OriginHeights{}, errors.New("edge was nil") + } + edge := edgeOpt.Unwrap() + return edge.TopLevelClaimHeight(ctx) +} + +// ReadAssertionCreationInfo for an assertion sequence number by looking up its creation +// event from the rollup contracts. +func (a *AssertionChain) ReadAssertionCreationInfo( + ctx context.Context, id protocol.AssertionHash, +) (*protocol.AssertionCreatedInfo, error) { + var assertionCreationBlock uint64 + var topics [][]common.Hash + if id == (protocol.AssertionHash{}) { + rollupDeploymentBlock, err := a.rollup.RollupDeploymentBlock(&bind.CallOpts{Context: ctx}) + if err != nil { + return nil, err + } + if !rollupDeploymentBlock.IsUint64() { + return nil, errors.New("rollup deployment block was not a uint64") + } + assertionCreationBlock = rollupDeploymentBlock.Uint64() + topics = [][]common.Hash{{assertionCreatedId}} + } else { + var b [32]byte + copy(b[:], id.Bytes()) + var err error + assertionCreationBlock, err = a.GetAssertionCreationParentBlock(ctx, b) + if err != nil { + return nil, err + } + topics = [][]common.Hash{{assertionCreatedId}, {id.Hash}} + } + var query = ethereum.FilterQuery{ + FromBlock: new(big.Int).SetUint64(assertionCreationBlock), + ToBlock: new(big.Int).SetUint64(assertionCreationBlock), + Addresses: []common.Address{a.rollupAddr}, + Topics: topics, + } + logs, err := a.backend.FilterLogs(ctx, query) + if err != nil { + return nil, err + } + if len(logs) == 0 { + return nil, errors.New("no assertion creation logs found") + } + if len(logs) > 1 { + return nil, errors.New("found multiple instances of requested node") + } + ethLog := logs[0] + parsedLog, err := a.rollup.ParseAssertionCreated(ethLog) + if err != nil { + return nil, err + } + afterState := parsedLog.Assertion.AfterState + res, err := a.rollup.GetAssertion(a.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), parsedLog.AssertionHash) + if err != nil { + return nil, err + } + creationL1Block := res.CreatedAtBlock + return &protocol.AssertionCreatedInfo{ + ConfirmPeriodBlocks: parsedLog.ConfirmPeriodBlocks, + RequiredStake: parsedLog.RequiredStake, + ParentAssertionHash: protocol.AssertionHash{Hash: parsedLog.ParentAssertionHash}, + BeforeState: parsedLog.Assertion.BeforeState, + AfterState: afterState, + InboxMaxCount: parsedLog.InboxMaxCount, + AfterInboxBatchAcc: parsedLog.AfterInboxBatchAcc, + AssertionHash: protocol.AssertionHash{Hash: parsedLog.AssertionHash}, + WasmModuleRoot: parsedLog.WasmModuleRoot, + ChallengeManager: parsedLog.ChallengeManager, + TransactionHash: ethLog.TxHash, + CreationParentBlock: ethLog.BlockNumber, + CreationL1Block: creationL1Block, + }, nil +} + +func handleCreateAssertionError(err error, blockHash common.Hash) error { + if err == nil { + return nil + } + errS := err.Error() + switch { + case strings.Contains(errS, "EXPECTED_ASSERTION_SEEN"): + return errors.Wrapf( + ErrAlreadyExists, + "commit block hash %#x", + blockHash, + ) + case strings.Contains(errS, "already known"): + return errors.Wrapf( + ErrAlreadyExists, + "commit block hash %#x", + blockHash, + ) + case strings.Contains(errS, "Assertion already exists"): + return errors.Wrapf( + ErrAlreadyExists, + "commit block hash %#x", + blockHash, + ) + case strings.Contains(errS, "Assertion does not exist"): + return ErrPrevDoesNotExist + case strings.Contains(errS, "Too late to create sibling"): + return ErrTooLate + default: + return err + } +} + +func (a *AssertionChain) GetCallOptsWithDesiredRpcHeadBlockNumber(opts *bind.CallOpts) *bind.CallOpts { + if opts == nil { + opts = &bind.CallOpts{} + } + // If we are running tests, we want to use the latest block number since + // simulated backends only support the latest block number. + if flag.Lookup("test.v") != nil { + return opts + } + opts.BlockNumber = big.NewInt(int64(a.rpcHeadBlockNumber)) + return opts +} + +func (a *AssertionChain) GetCallOptsWithSafeBlockNumber(opts *bind.CallOpts) *bind.CallOpts { + if opts == nil { + opts = &bind.CallOpts{} + } + // If we are running tests, we want to use the latest block number since + // simulated backends only support the latest block number. + if flag.Lookup("test.v") != nil { + return nil + } + opts.BlockNumber = big.NewInt(int64(rpc.SafeBlockNumber)) + return opts +} + +func (a *AssertionChain) GetDesiredRpcHeadBlockNumber() rpc.BlockNumber { + return a.rpcHeadBlockNumber +} diff --git a/bold/chain-abstraction/sol-implementation/assertion_chain_helper_test.go b/bold/chain-abstraction/sol-implementation/assertion_chain_helper_test.go new file mode 100644 index 0000000000..80e82ea3a9 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/assertion_chain_helper_test.go @@ -0,0 +1,11 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import protocol "github.com/offchainlabs/bold/chain-abstraction" + +func (a *AssertionChain) SetBackend(b protocol.ChainBackend) { + a.backend = b +} diff --git a/bold/chain-abstraction/sol-implementation/assertion_chain_test.go b/bold/chain-abstraction/sol-implementation/assertion_chain_test.go new file mode 100644 index 0000000000..c4bfb42dd8 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/assertion_chain_test.go @@ -0,0 +1,602 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl_test + +import ( + "context" + "math/big" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func TestNewEmptyStake(t *testing.T) { + ctx := context.Background() + cfg, err := setup.ChainsWithEdgeChallengeManager() + require.NoError(t, err) + chain := cfg.Chains[0] + require.NoError(t, chain.NewStake(ctx)) + isStaked, err := chain.IsStaked(ctx) + require.NoError(t, err) + require.True(t, isStaked) +} + +func TestNewStakeOnNewAssertion(t *testing.T) { + ctx := context.Background() + cfg, err := setup.ChainsWithEdgeChallengeManager() + require.NoError(t, err) + chain := cfg.Chains[0] + backend := cfg.Backend + + genesisHash, err := chain.GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisInfo, err := chain.ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + + t.Run("OK", func(t *testing.T) { + latestBlockHash := common.Hash{} + for i := uint64(0); i < 100; i++ { + latestBlockHash = backend.Commit() + } + + postState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: latestBlockHash, + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + assertion, err := chain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + + existingAssertion, err := chain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + require.Equal(t, assertion.Id(), existingAssertion.Id()) + }) + t.Run("can create fork", func(t *testing.T) { + assertionChain := cfg.Chains[1] + + for i := uint64(0); i < 100; i++ { + backend.Commit() + } + + postState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("evil hash")), + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + _, err := assertionChain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + }) +} + +func TestStakeOnNewAssertion(t *testing.T) { + ctx := context.Background() + cfg, err := setup.ChainsWithEdgeChallengeManager() + require.NoError(t, err) + chain := cfg.Chains[0] + backend := cfg.Backend + + genesisHash, err := chain.GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisInfo, err := chain.ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + + latestBlockHash := common.Hash{} + for i := uint64(0); i < 100; i++ { + latestBlockHash = backend.Commit() + } + + postState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: latestBlockHash, + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + assertion, err := chain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + + assertionInfo, err := chain.ReadAssertionCreationInfo(ctx, assertion.Id()) + require.NoError(t, err) + + postState = &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("foo")), + SendRoot: common.Hash{}, + Batch: postState.GlobalState.Batch + 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + + enqueueSequencerMessageAsExecutor( + t, + cfg.Accounts[0].TxOpts, + cfg.Addrs.UpgradeExecutor, + cfg.Backend, + cfg.Addrs.Bridge, + seqMessage{ + dataHash: common.BytesToHash([]byte("foo")), + afterDelayedMessagesRead: big.NewInt(1), + prevMessageCount: big.NewInt(1), + newMessageCount: big.NewInt(2), + }, + ) + + for i := uint64(0); i < 100; i++ { + backend.Commit() + } + + newAssertion, err := chain.StakeOnNewAssertion(ctx, assertionInfo, postState) + require.NoError(t, err) + + newAssertionCreatedInfo, err := chain.ReadAssertionCreationInfo(ctx, newAssertion.Id()) + require.NoError(t, err) + + // Expect the post state has indeed the number of messages we expect. + gotPostState := protocol.GoExecutionStateFromSolidity(newAssertionCreatedInfo.AfterState) + require.Equal(t, postState, gotPostState) +} + +type seqMessage struct { + dataHash common.Hash + afterDelayedMessagesRead *big.Int + prevMessageCount *big.Int + newMessageCount *big.Int +} + +func enqueueSequencerMessageAsExecutor( + t *testing.T, + opts *bind.TransactOpts, + executor common.Address, + backend *setup.SimulatedBackendWrapper, + bridge common.Address, + msg seqMessage, +) { + execBindings, err := mocksgen.NewUpgradeExecutorMock(executor, backend) + require.NoError(t, err) + seqInboxABI, err := abi.JSON(strings.NewReader(bridgegen.AbsBridgeABI)) + require.NoError(t, err) + data, err := seqInboxABI.Pack( + "setSequencerInbox", + executor, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, bridge, data) + require.NoError(t, err) + backend.Commit() + + data, err = seqInboxABI.Pack( + "enqueueSequencerMessage", + msg.dataHash, msg.afterDelayedMessagesRead, msg.prevMessageCount, msg.newMessageCount, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, bridge, data) + require.NoError(t, err) + backend.Commit() +} + +func TestAssertionUnrivaledBlocks(t *testing.T) { + ctx := context.Background() + cfg, err := setup.ChainsWithEdgeChallengeManager() + require.NoError(t, err) + chain := cfg.Chains[0] + backend := cfg.Backend + + latestBlockHash := common.Hash{} + for i := uint64(0); i < 100; i++ { + latestBlockHash = backend.Commit() + } + genesisHash, err := chain.GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisInfo, err := chain.ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + + postState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: latestBlockHash, + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + assertion, err := chain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + + unrivaledBlocks, err := chain.AssertionUnrivaledBlocks(ctx, assertion.Id()) + require.NoError(t, err) + + // Should have been zero blocks since creation. + require.Equal(t, uint64(0), unrivaledBlocks) + + backend.Commit() + backend.Commit() + backend.Commit() + + unrivaledBlocks, err = chain.AssertionUnrivaledBlocks(ctx, assertion.Id()) + require.NoError(t, err) + + // Three blocks since creation. + require.Equal(t, uint64(3), unrivaledBlocks) + + // We then post a second child assertion. + assertionChain := cfg.Chains[1] + + postState = &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("evil hash")), + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + forkedAssertion, err := assertionChain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + + // We advance the chain by three blocks and check the assertion unrivaled times + // of both created assertions. + backend.Commit() + backend.Commit() + backend.Commit() + + unrivaledFirstChild, err := assertionChain.AssertionUnrivaledBlocks(ctx, assertion.Id()) + require.NoError(t, err) + unrivaledSecondChild, err := assertionChain.AssertionUnrivaledBlocks(ctx, forkedAssertion.Id()) + require.NoError(t, err) + + // The amount of blocks unrivaled should not change for the first child (except for + // the addition of one more block to account for the creation of its rival) and should + // be zero for the second child block. + require.Equal(t, uint64(4), unrivaledFirstChild) + require.Equal(t, uint64(0), unrivaledSecondChild) + + // 100 blocks later, results should be unchanged. + for i := 0; i < 100; i++ { + backend.Commit() + } + + unrivaledFirstChild, err = assertionChain.AssertionUnrivaledBlocks(ctx, assertion.Id()) + require.NoError(t, err) + unrivaledSecondChild, err = assertionChain.AssertionUnrivaledBlocks(ctx, forkedAssertion.Id()) + require.NoError(t, err) + + // The amount of blocks unrivaled should not change for the first child (except for + // the addition of one more block to account for the creation of its rival) and should + // be zero for the second child block. + require.Equal(t, uint64(4), unrivaledFirstChild) + require.Equal(t, uint64(0), unrivaledSecondChild) +} + +func TestConfirmAssertionByChallengeWinner(t *testing.T) { + ctx := context.Background() + _, err := setup.ChainsWithEdgeChallengeManager(setup.WithMockOneStepProver()) + require.NoError(t, err) + + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + challengeManager := createdData.Chains[0].SpecChallengeManager() + + // Honest assertion being added. + leafAdder := func(stateManager l2stateprovider.Provider, leaf protocol.Assertion) protocol.VerifiedRoyalEdge { + assertionMetadata := &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: common.Hash{}, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 0, + }, + BatchLimit: 1, + } + startCommit, startErr := stateManager.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + }, + ) + require.NoError(t, startErr) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)), + } + endCommit, endErr := stateManager.HistoryCommitment( + ctx, + req, + ) + require.NoError(t, endErr) + prefixProof, proofErr := stateManager.PrefixProof(ctx, req, l2stateprovider.Height(0)) + require.NoError(t, proofErr) + + edge, edgeErr := challengeManager.AddBlockChallengeLevelZeroEdge( + ctx, + leaf, + startCommit, + endCommit, + prefixProof, + ) + require.NoError(t, edgeErr) + return edge + } + honestEdge := leafAdder(createdData.HonestStateManager, createdData.Leaf1) + s0, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgePending, s0) + + hasRival, err := honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, hasRival) + + // Adjust well beyond a challenge period. + for i := 0; i < 200; i++ { + createdData.Backend.Commit() + } + + chain := createdData.Chains[0] + + latestConfirmed, err := chain.LatestConfirmed(ctx, &bind.CallOpts{Context: ctx}) + require.NoError(t, err) + + t.Run("genesis case", func(t *testing.T) { + err = chain.ConfirmAssertionByChallengeWinner( + ctx, latestConfirmed.Id(), protocol.EdgeId{}, + ) + require.NoError(t, err) + }) + t.Run("no level zero edge confirmed yet for the assertion", func(t *testing.T) { + err = chain.ConfirmAssertionByChallengeWinner( + ctx, createdData.Leaf1.Id(), honestEdge.Id(), + ) + require.ErrorContains(t, err, "EDGE_NOT_CONFIRMED") + }) + t.Run("level zero block edge confirmed allows assertion confirmation", func(t *testing.T) { + _, err = honestEdge.ConfirmByTimer(ctx, createdData.Leaf1.Id()) + require.NoError(t, err) + + // Adjust beyond the grace period. + for i := 0; i < 10; i++ { + createdData.Backend.Commit() + } + + err = chain.ConfirmAssertionByChallengeWinner( + ctx, createdData.Leaf1.Id(), honestEdge.Id(), + ) + require.NoError(t, err) + + latestConfirmed, err = chain.LatestConfirmed(ctx, &bind.CallOpts{Context: ctx}) + require.NoError(t, err) + require.Equal(t, createdData.Leaf1.Id(), latestConfirmed.Id()) + + // Confirming again should just be a no-op. + err = chain.ConfirmAssertionByChallengeWinner( + ctx, createdData.Leaf1.Id(), honestEdge.Id(), + ) + require.NoError(t, err) + }) +} + +func TestAssertionBySequenceNum(t *testing.T) { + ctx := context.Background() + cfg, err := setup.ChainsWithEdgeChallengeManager() + require.NoError(t, err) + chain := cfg.Chains[0] + latestConfirmed, err := chain.LatestConfirmed(ctx, &bind.CallOpts{Context: ctx}) + require.NoError(t, err) + opts := &bind.CallOpts{Context: ctx} + _, err = chain.GetAssertion(ctx, opts, latestConfirmed.Id()) + require.NoError(t, err) + + _, err = chain.GetAssertion(ctx, opts, protocol.AssertionHash{Hash: common.BytesToHash([]byte("foo"))}) + require.ErrorIs(t, err, solimpl.ErrNotFound) +} + +func TestChallengePeriodBlocks(t *testing.T) { + cfg, err := setup.ChainsWithEdgeChallengeManager() + require.NoError(t, err) + chain := cfg.Chains[0] + + manager := chain.SpecChallengeManager() + chalPeriod := manager.ChallengePeriodBlocks() + require.Equal(t, cfg.RollupConfig.ConfirmPeriodBlocks, chalPeriod) +} + +func TestIsChallengeComplete(t *testing.T) { + ctx := context.Background() + _, err := setup.ChainsWithEdgeChallengeManager(setup.WithMockOneStepProver()) + require.NoError(t, err) + + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + challengeManager := createdData.Chains[0].SpecChallengeManager() + + // Honest assertion being added. + leafAdder := func(stateManager l2stateprovider.Provider, leaf protocol.Assertion) protocol.VerifiedRoyalEdge { + assertionMetadata := &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: common.Hash{}, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 0, + }, + BatchLimit: 1, + } + startCommit, startErr := stateManager.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + }, + ) + require.NoError(t, startErr) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)), + } + endCommit, endErr := stateManager.HistoryCommitment( + ctx, + req, + ) + require.NoError(t, endErr) + prefixProof, proofErr := stateManager.PrefixProof(ctx, req, l2stateprovider.Height(0)) + require.NoError(t, proofErr) + + edge, edgeErr := challengeManager.AddBlockChallengeLevelZeroEdge( + ctx, + leaf, + startCommit, + endCommit, + prefixProof, + ) + require.NoError(t, edgeErr) + return edge + } + honestEdge := leafAdder(createdData.HonestStateManager, createdData.Leaf1) + s0, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgePending, s0) + + // We then check if the edge is part of a complete challenge. It should not be. + chain := createdData.Chains[0] + challengeParentAssertionHash, err := honestEdge.AssertionHash(ctx) + require.NoError(t, err) + chalComplete, err := chain.IsChallengeComplete(ctx, challengeParentAssertionHash) + require.NoError(t, err) + require.Equal(t, false, chalComplete) + + // Adjust well beyond a challenge period. + for i := 0; i < 200; i++ { + createdData.Backend.Commit() + } + + _, err = honestEdge.ConfirmByTimer(ctx, createdData.Leaf1.Id()) + require.NoError(t, err) + + // Adjust beyond the grace period. + for i := 0; i < 10; i++ { + createdData.Backend.Commit() + } + + // Confirm the assertion by challenge. + err = chain.ConfirmAssertionByChallengeWinner( + ctx, createdData.Leaf1.Id(), honestEdge.Id(), + ) + require.NoError(t, err) + + // Now, the edge should be part of a completed challenge. + chalComplete, err = chain.IsChallengeComplete(ctx, challengeParentAssertionHash) + require.NoError(t, err) + require.Equal(t, true, chalComplete) +} + +func Test_autoDepositFunds(t *testing.T) { + setupCfg, err := setup.ChainsWithEdgeChallengeManager(setup.WithMockOneStepProver()) + require.NoError(t, err) + rollupAddr := setupCfg.Addrs.Rollup + rollup, err := rollupgen.NewRollupUserLogic(rollupAddr, setupCfg.Backend) + require.NoError(t, err) + stakeTokenAddr, err := rollup.StakeToken(&bind.CallOpts{}) + require.NoError(t, err) + erc20, err := mocksgen.NewTestWETH9(stakeTokenAddr, setupCfg.Backend) + require.NoError(t, err) + account := setupCfg.Accounts[1] + + balance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + + expectedNewBalance := new(big.Int).Add(balance, big.NewInt(20)) + ctx := context.Background() + require.NoError(t, setupCfg.Chains[0].AutoDepositTokenForStaking(ctx, expectedNewBalance)) + + newBalance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + require.Equal(t, expectedNewBalance, newBalance) +} + +func Test_autoDepositFunds_SkipsIfAlreadyStaked(t *testing.T) { + setupCfg, err := setup.ChainsWithEdgeChallengeManager(setup.WithMockOneStepProver()) + require.NoError(t, err) + rollupAddr := setupCfg.Addrs.Rollup + rollup, err := rollupgen.NewRollupUserLogic(rollupAddr, setupCfg.Backend) + require.NoError(t, err) + stakeTokenAddr, err := rollup.StakeToken(&bind.CallOpts{}) + require.NoError(t, err) + erc20, err := mocksgen.NewTestWETH9(stakeTokenAddr, setupCfg.Backend) + require.NoError(t, err) + account := setupCfg.Accounts[1] + assertionChain := setupCfg.Chains[0] + + balance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + + expectedNewBalance := new(big.Int).Add(balance, big.NewInt(20)) + ctx := context.Background() + require.NoError(t, assertionChain.AutoDepositTokenForStaking(ctx, expectedNewBalance)) + + newBalance, err := erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + require.Equal(t, expectedNewBalance, newBalance) + + // Tries to stake on an assertion. + genesisHash, err := assertionChain.GenesisAssertionHash(ctx) + require.NoError(t, err) + genesisInfo, err := assertionChain.ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + require.NoError(t, err) + postState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: common.BytesToHash([]byte("foo")), + SendRoot: common.Hash{}, + Batch: 1, + PosInBatch: 0, + }, + MachineStatus: protocol.MachineStatusFinished, + } + _, err = assertionChain.NewStakeOnNewAssertion(ctx, genesisInfo, postState) + require.NoError(t, err) + + // Check we are staked. + staked, err := assertionChain.IsStaked(ctx) + require.NoError(t, err) + require.True(t, staked) + + // Attempt to auto-deposit again. + oldBalance := newBalance + evenBiggerBalance := new(big.Int).Add(oldBalance, big.NewInt(100)) + require.NoError(t, setupCfg.Chains[0].AutoDepositTokenForStaking(ctx, evenBiggerBalance)) + + // Check that we our balance does not increase if we try to auto-deposit again given we are + // already staked as a validator. In fact, expect it decreased. + newBalance, err = erc20.BalanceOf(&bind.CallOpts{}, account.AccountAddr) + require.NoError(t, err) + require.True(t, oldBalance.Cmp(newBalance) > 0) +} diff --git a/bold/chain-abstraction/sol-implementation/edge_challenge_manager.go b/bold/chain-abstraction/sol-implementation/edge_challenge_manager.go new file mode 100644 index 0000000000..47bfd88897 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/edge_challenge_manager.go @@ -0,0 +1,1187 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "context" + "fmt" + "math/big" + "strings" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/metrics" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + challengetree "github.com/offchainlabs/bold/challenge-manager/challenge-tree" + edgetracker "github.com/offchainlabs/bold/challenge-manager/edge-tracker" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/state-commitments/history" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/ospgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +var ( + errorConfirmingEdgeByOneStepProofCounter = metrics.GetOrRegisterCounter("arb/validator/tracker/error_confirming_edge_by_one_step_proof", nil) + invalidInclusionProofCounter = metrics.GetOrRegisterCounter("arb/validator/tracker/invalid_inclusion_proof", nil) +) + +const InvalidInclusionProofError = "invalid inclusion proof" + +func (e *specEdge) Id() protocol.EdgeId { + return protocol.EdgeId{Hash: e.id} +} + +func (e *specEdge) GetChallengeLevel() protocol.ChallengeLevel { + return protocol.ChallengeLevel(e.inner.Level) +} + +// GetReversedChallengeLevel obtains the challenge level for the edge. The lowest level starts at 0, and goes all way +// up to the max number of levels. The reason we go from the lowest challenge level being 0 instead of 2 +// is to make our code a lot more readable. If we flipped the order, we would need to do +// a lot of backwards for loops instead of simple range loops over slices. +func (e *specEdge) GetReversedChallengeLevel() protocol.ChallengeLevel { + return protocol.ChallengeLevel(e.totalChallengeLevels - 1 - e.inner.Level) +} + +func (e *specEdge) GetTotalChallengeLevels(ctx context.Context) uint8 { + return e.totalChallengeLevels +} + +func (e *specEdge) MiniStaker() option.Option[common.Address] { + return e.miniStaker +} + +func (e *specEdge) StartCommitment() (protocol.Height, common.Hash) { + return protocol.Height(e.startHeight), e.inner.StartHistoryRoot +} + +func (e *specEdge) EndCommitment() (protocol.Height, common.Hash) { + return protocol.Height(e.endHeight), e.inner.EndHistoryRoot +} + +func (e *specEdge) AssertionHash(_ context.Context) (protocol.AssertionHash, error) { + return e.assertionHash, nil +} + +func (e *specEdge) TimeUnrivaled(ctx context.Context) (uint64, error) { + if e.hasRival && e.timeUnrivaled.IsSome() { + return e.timeUnrivaled.Unwrap(), nil + } + timer, err := e.manager.caller.TimeUnrivaled(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), e.id) + if err != nil { + return 0, err + } + if !timer.IsUint64() { + return 0, fmt.Errorf("received time unrivaled > max uint64 for edge %#x", e.id) + } + return timer.Uint64(), nil +} + +func (e *specEdge) HasRival(ctx context.Context) (bool, error) { + if e.hasRival { + return e.hasRival, nil + } + hasRival, err := e.manager.caller.HasRival(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), e.id) + if err != nil { + return false, err + } + if hasRival { + e.hasRival = true + } + return hasRival, nil +} + +func (e *specEdge) Status(ctx context.Context) (protocol.EdgeStatus, error) { + if e.isConfirmed { + return protocol.EdgeConfirmed, nil + } + edge, err := e.fetchEdge(ctx) + if err != nil { + return 0, err + } + return protocol.EdgeStatus(edge.Status), nil +} + +func (e *specEdge) ConfirmedAtBlock(ctx context.Context) (uint64, error) { + if e.confirmedAtBlock.IsSome() { + return e.confirmedAtBlock.Unwrap(), nil + } + edge, err := e.fetchEdge(ctx) + if err != nil { + return 0, err + } + return edge.ConfirmedAtBlock, nil +} + +// CreatedAtBlock the block number the edge was created at. +func (e *specEdge) CreatedAtBlock() (uint64, error) { + return e.inner.CreatedAtBlock, nil +} + +// HasChildren checks if the edge has children. +func (e *specEdge) HasChildren(ctx context.Context) (bool, error) { + if e.lowerChild.IsSome() && e.upperChild.IsSome() { + return true, nil + } + edge, err := e.fetchEdge(ctx) + if err != nil { + return false, err + } + return edge.LowerChildId != ([32]byte{}) && edge.UpperChildId != ([32]byte{}), nil +} + +// LowerChild of the edge, if any. +func (e *specEdge) LowerChild(ctx context.Context) (option.Option[protocol.EdgeId], error) { + if e.lowerChild.IsSome() { + return e.lowerChild, nil + } + edge, err := e.fetchEdge(ctx) + if err != nil { + return option.None[protocol.EdgeId](), err + } + if edge.LowerChildId == ([32]byte{}) { + return option.None[protocol.EdgeId](), nil + } + return option.Some(protocol.EdgeId{ + Hash: edge.LowerChildId, + }), nil +} + +// UpperChild of the edge, if any. +func (e *specEdge) UpperChild(ctx context.Context) (option.Option[protocol.EdgeId], error) { + if e.upperChild.IsSome() { + return e.upperChild, nil + } + edge, err := e.fetchEdge(ctx) + if err != nil { + return option.None[protocol.EdgeId](), err + } + if edge.LowerChildId == ([32]byte{}) { + return option.None[protocol.EdgeId](), nil + } + return option.Some(protocol.EdgeId{ + Hash: edge.UpperChildId, + }), nil +} + +// MutualId of the edge. +func (e *specEdge) MutualId() protocol.MutualId { + return e.mutualId +} + +func (e *specEdge) OriginId() protocol.OriginId { + return e.inner.OriginId +} + +// ClaimId of the edge, if any. +func (e *specEdge) ClaimId() option.Option[protocol.ClaimId] { + if e.inner.ClaimId == [32]byte{} { + return option.None[protocol.ClaimId]() + } + return option.Some(protocol.ClaimId(e.inner.ClaimId)) +} + +// HasLengthOneRival returns true if there's a length one rival. +func (e *specEdge) HasLengthOneRival(ctx context.Context) (bool, error) { + if e.hasLengthOneRival { + return e.hasLengthOneRival, nil + } + ok, err := e.manager.caller.HasLengthOneRival(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), e.id) + if err != nil { + errS := err.Error() + switch { + case strings.Contains(errS, "not length 1"): + return false, nil + case strings.Contains(errS, "is unrivaled"): + return false, nil + default: + return false, err + } + } + if ok { + e.hasLengthOneRival = true + } + return ok, nil +} + +func (e *specEdge) MarkAsHonest() { + e.verifiedHonest = true +} + +func (e *specEdge) AsVerifiedHonest() (protocol.VerifiedRoyalEdge, bool) { + if e.verifiedHonest { + return &honestEdge{e}, true + } + return nil, false +} + +// Bisect the edge, returning the upper and lower edges. +// If the upper child exists, both edges will be returned. +// Lower child may optionally exist so the method will bisect regardless. +func (h *honestEdge) Bisect( + ctx context.Context, + prefixHistoryRoot common.Hash, + prefixProof []byte, +) (protocol.VerifiedRoyalEdge, protocol.VerifiedRoyalEdge, error) { + e := h.specEdge + upperId, err := e.UpperChild(ctx) + if err != nil { + return nil, nil, err + } + var upperEdge option.Option[protocol.SpecEdge] + var lowerId option.Option[protocol.EdgeId] + var lowerEdge option.Option[protocol.SpecEdge] + if !upperId.IsNone() { + upperEdge, err = e.manager.GetEdge(ctx, upperId.Unwrap()) + if err != nil { + return nil, nil, err + } + if upperEdge.IsNone() { + return nil, nil, errors.New("could not refresh upper edge after bisecting, got empty result") + } + lowerId, err = e.LowerChild(ctx) + if err != nil { + return nil, nil, err + } + lowerEdge, err = e.manager.GetEdge(ctx, lowerId.Unwrap()) + if err != nil { + return nil, nil, err + } + if lowerEdge.IsNone() { + return nil, nil, errors.New("could not refresh lower edge after bisecting, got empty result") + } + lowerSpecEdge, ok := lowerEdge.Unwrap().(*specEdge) + if !ok { + return nil, nil, errors.New("not a *specEdge") + } + upperSpecEdge, ok := upperEdge.Unwrap().(*specEdge) + if !ok { + return nil, nil, errors.New("not a *specEdge") + } + lower := &honestEdge{lowerSpecEdge} + upper := &honestEdge{upperSpecEdge} + return lower, upper, nil + } + + _, err = e.manager.assertionChain.transact(ctx, e.manager.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return e.manager.writer.BisectEdge(opts, e.id, prefixHistoryRoot, prefixProof) + }) + if err != nil { + return nil, nil, err + } + someEdge, err := e.manager.GetEdge(ctx, protocol.EdgeId{Hash: e.id}) + if err != nil { + return nil, nil, err + } + if someEdge.IsNone() { + return nil, nil, errors.New("could not refresh edge after bisecting, got empty result") + } + edge, ok := someEdge.Unwrap().(*specEdge) + if !ok { + return nil, nil, errors.New("not a *SpecEdge") + } + // Refresh the edge. + e = edge + someLowerChild, err := e.manager.GetEdge(ctx, protocol.EdgeId{Hash: e.inner.LowerChildId}) + if err != nil { + return nil, nil, err + } + someUpperChild, err := e.manager.GetEdge(ctx, protocol.EdgeId{Hash: e.inner.UpperChildId}) + if err != nil { + return nil, nil, err + } + if someLowerChild.IsNone() || someUpperChild.IsNone() { + return nil, nil, errors.New("expected edge to have children post-bisection, but has none") + } + lowerSpecEdge, ok := someLowerChild.Unwrap().(*specEdge) + if !ok { + return nil, nil, errors.New("not a *specEdge") + } + upperSpecEdge, ok := someUpperChild.Unwrap().(*specEdge) + if !ok { + return nil, nil, errors.New("not a *specEdge") + } + lower := &honestEdge{lowerSpecEdge} + upper := &honestEdge{upperSpecEdge} + return lower, upper, nil +} + +func (h *honestEdge) ConfirmByTimer(ctx context.Context, claimedAssertion protocol.AssertionHash) (*types.Transaction, error) { + e := h.specEdge + s, err := e.Status(ctx) + if err != nil { + return nil, err + } + if s == protocol.EdgeConfirmed { + return nil, nil + } + assertionCreation, err := e.manager.assertionChain.ReadAssertionCreationInfo(ctx, claimedAssertion) + if err != nil { + return nil, err + } + // The confirm by timer used to require a list of ancestors, but it has since + // been refactored to use them. However, the function signature still needs this empty list. + receipt, err := e.manager.assertionChain.transact(ctx, e.manager.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return e.manager.writer.ConfirmEdgeByTime(opts, e.id, challengeV2gen.AssertionStateData{ + AssertionState: challengeV2gen.AssertionState{ + GlobalState: challengeV2gen.GlobalState(assertionCreation.AfterState.GlobalState), + MachineStatus: assertionCreation.AfterState.MachineStatus, + EndHistoryRoot: assertionCreation.AfterState.EndHistoryRoot, + }, + PrevAssertionHash: assertionCreation.ParentAssertionHash.Hash, + InboxAcc: assertionCreation.AfterInboxBatchAcc, + }) + }) + if err != nil { + return nil, errors.Wrapf( + err, + "could not confirm edge %s by time with tx", + containers.Trunc(e.id[:]), + ) + } + tx, _, err := e.manager.backend.TransactionByHash(ctx, receipt.TxHash) + if err != nil { + return nil, errors.Wrapf(err, "could not get transaction by hash: %#x", receipt.TxHash) + } + return tx, nil +} + +// TopLevelClaimHeight gets the height at the BlockChallenge level that originated a subchallenge. +// For example, if two validators open a subchallenge S at edge A in a BlockChallenge, the TopLevelClaimHeight of S is the height of A. +// If two validators open a subchallenge S' at edge B in BigStepChallenge, the TopLevelClaimHeight +// is the height of A. +func (e *specEdge) TopLevelClaimHeight(ctx context.Context) (protocol.OriginHeights, error) { + challengeLevel := e.GetChallengeLevel() + if challengeLevel == 0 { + startHeight, _ := e.StartCommitment() + return protocol.OriginHeights{ + ChallengeOriginHeights: []protocol.Height{startHeight}, + }, nil + } + challengeOriginHeights := make([]protocol.Height, challengeLevel) + originId := e.inner.OriginId + for challengeLevel > 0 { + if ctx.Err() != nil { + return protocol.OriginHeights{}, ctx.Err() + } + rivalId, err := e.manager.caller.FirstRival(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), originId) + if err != nil { + return protocol.OriginHeights{}, err + } + challengeOneStepForkSource, err := e.manager.GetEdge(ctx, protocol.EdgeId{Hash: rivalId}) + if err != nil { + return protocol.OriginHeights{}, errors.Wrapf(err, "big step challenge one step fork source does not exist: origin id %#x, rival %#x, challenge level %d", originId, rivalId, challengeLevel) + } + if challengeOneStepForkSource.IsNone() { + return protocol.OriginHeights{}, errors.New("source edge is none") + } + bigStepEdge, ok := challengeOneStepForkSource.Unwrap().(*specEdge) + if !ok { + return protocol.OriginHeights{}, errors.New("not *SpecEdge") + } + bigStepStartHeight, _ := bigStepEdge.StartCommitment() + + challengeOriginHeights[challengeLevel-1] = bigStepStartHeight + originId = bigStepEdge.inner.OriginId + + challengeLevel-- + } + return protocol.OriginHeights{ + ChallengeOriginHeights: challengeOriginHeights, + }, nil +} + +// Wrapper around the challenge manager contract with developer-friendly methods. +type specChallengeManager struct { + addr common.Address + backend protocol.ChainBackend + assertionChain *AssertionChain + txOpts *bind.TransactOpts + caller *challengeV2gen.EdgeChallengeManagerCaller + writer *challengeV2gen.EdgeChallengeManagerTransactor + filterer *challengeV2gen.EdgeChallengeManagerFilterer + layerZeroHeights *protocol.LayerZeroHeights + challengePeriodBlocks uint64 + numBigStepLevel uint8 +} + +// NewSpecChallengeManager returns an instance of the spec challenge manager +// used by the assertion chain. +func NewSpecChallengeManager( + ctx context.Context, + addr common.Address, + assertionChain *AssertionChain, + backend protocol.ChainBackend, + txOpts *bind.TransactOpts, +) (protocol.SpecChallengeManager, error) { + managerBinding, err := challengeV2gen.NewEdgeChallengeManager(addr, backend) + if err != nil { + return nil, err + } + caller := managerBinding.EdgeChallengeManagerCaller + numBigStepLevel, err := caller.NUMBIGSTEPLEVEL(assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, err + } + challengePeriodBlocks, err := caller.ChallengePeriodBlocks(assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, err + } + lzh, err := lookupLayerZeroHeights(ctx, assertionChain, caller) + if err != nil { + return nil, err + } + return &specChallengeManager{ + addr: addr, + assertionChain: assertionChain, + backend: backend, + txOpts: txOpts, + caller: &managerBinding.EdgeChallengeManagerCaller, + writer: &managerBinding.EdgeChallengeManagerTransactor, + filterer: &managerBinding.EdgeChallengeManagerFilterer, + layerZeroHeights: lzh, + challengePeriodBlocks: challengePeriodBlocks, + numBigStepLevel: numBigStepLevel, + }, nil +} + +func (cm *specChallengeManager) Address() common.Address { + return cm.addr +} + +func (cm *specChallengeManager) LayerZeroHeights() protocol.LayerZeroHeights { + return *cm.layerZeroHeights +} + +func lookupLayerZeroHeights(ctx context.Context, chain *AssertionChain, caller challengeV2gen.EdgeChallengeManagerCaller) (*protocol.LayerZeroHeights, error) { + h, err := caller.LAYERZEROBLOCKEDGEHEIGHT(chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, err + } + if !h.IsUint64() { + return nil, errors.New("layer zero block edge height was not a uint64") + } + bs, err := caller.LAYERZEROBIGSTEPEDGEHEIGHT(chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, err + } + if !bs.IsUint64() { + return nil, errors.New("layer zero big step edge height was not a uint64") + } + ss, err := caller.LAYERZEROSMALLSTEPEDGEHEIGHT(chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, err + } + if !ss.IsUint64() { + return nil, errors.New("layer zero small step height was not a uint64") + } + return &protocol.LayerZeroHeights{ + BlockChallengeHeight: protocol.Height(h.Uint64()), + BigStepChallengeHeight: protocol.Height(bs.Uint64()), + SmallStepChallengeHeight: protocol.Height(ss.Uint64()), + }, nil +} + +func (cm *specChallengeManager) NumBigSteps() uint8 { + return cm.numBigStepLevel +} + +func (cm *specChallengeManager) LevelZeroBlockEdgeHeight(ctx context.Context) (uint64, error) { + h, err := cm.caller.LAYERZEROBLOCKEDGEHEIGHT(cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return 0, err + } + if !h.IsUint64() { + return 0, errors.New("level zero block edge height was not a uint64") + } + return h.Uint64(), nil +} + +// ChallengePeriodBlocks is the duration of the challenge period in blocks. +func (cm *specChallengeManager) ChallengePeriodBlocks() uint64 { + return cm.challengePeriodBlocks +} + +var uint8Type = newStaticType("uint8", "", nil) +var uint256Type = newStaticType("uint256", "", nil) +var mutualIdAbi = abi.Arguments{ + {Type: uint8Type, Name: "level"}, + {Type: bytes32Type, Name: "originId"}, + {Type: uint256Type, Name: "startHeight"}, + {Type: bytes32Type, Name: "startHistoryRoot"}, + {Type: uint256Type, Name: "endHeight"}, +} + +func calculateMutualId(level uint8, originId [32]byte, startHeight *big.Int, startHistoryRoot [32]byte, endHeight *big.Int) (common.Hash, error) { + mutualIdByte, err := mutualIdAbi.Pack( + level, + originId, + startHeight, + startHistoryRoot, + endHeight, + ) + if err != nil { + return common.Hash{}, err + } + // Pack stores level(uint8) as 32 bytes, so we need to slice off the first 31 bytes + return crypto.Keccak256Hash(mutualIdByte[31:]), nil +} + +// GetEdge gets an edge by its hash. +func (cm *specChallengeManager) GetEdge( + ctx context.Context, + edgeId protocol.EdgeId, +) (option.Option[protocol.SpecEdge], error) { + edge, err := cm.caller.GetEdge(cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), edgeId.Hash) + if err != nil { + return option.None[protocol.SpecEdge](), err + } + miniStaker := option.None[common.Address]() + if edge.Staker != (common.Address{}) { + miniStaker = option.Some(edge.Staker) + } + mutual, err := calculateMutualId( + edge.Level, + edge.OriginId, + edge.StartHeight, + edge.StartHistoryRoot, + edge.EndHeight, + ) + if err != nil { + return option.None[protocol.SpecEdge](), err + } + if !edge.StartHeight.IsUint64() { + return option.None[protocol.SpecEdge](), errors.New("start height not a uint64") + } + if !edge.EndHeight.IsUint64() { + return option.None[protocol.SpecEdge](), errors.New("end height not a uint64") + } + assertionHash, err := cm.caller.GetPrevAssertionHash(cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), edgeId.Hash) + if err != nil { + return option.Option[protocol.SpecEdge]{}, err + } + return option.Some(protocol.SpecEdge(&specEdge{ + id: edgeId.Hash, + mutualId: mutual, + manager: cm, + inner: edge, + startHeight: edge.StartHeight.Uint64(), + endHeight: edge.EndHeight.Uint64(), + miniStaker: miniStaker, + totalChallengeLevels: cm.NumBigSteps() + 2, + assertionHash: protocol.AssertionHash{Hash: common.Hash(assertionHash)}, + })), nil +} + +func (e *specEdge) LatestInheritedTimer(ctx context.Context) (protocol.InheritedTimer, error) { + edge, err := e.manager.caller.GetEdge(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), e.id) + if err != nil { + return 0, err + } + if edgetracker.IsRootBlockChallengeEdge(e) { + // TODO: Use latest here as well. + assertionUnrivaledBlocks, err := e.manager.assertionChain.AssertionUnrivaledBlocks( + ctx, + protocol.AssertionHash{Hash: common.Hash(e.ClaimId().Unwrap())}, + ) + if err != nil { + return 0, err + } + return protocol.InheritedTimer(edge.TotalTimeUnrivaledCache + assertionUnrivaledBlocks), nil + } + return protocol.InheritedTimer(edge.TotalTimeUnrivaledCache), nil +} + +func (e *specEdge) fetchEdge( + ctx context.Context, +) (challengeV2gen.ChallengeEdge, error) { + edge, err := e.manager.caller.GetEdge(e.manager.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), e.id) + if err != nil { + return challengeV2gen.ChallengeEdge{}, err + } + + // Update the edge with the latest data, if they are in now in constant state. + if protocol.EdgeStatus(edge.Status) == protocol.EdgeConfirmed { + e.isConfirmed = true + } + if edge.ConfirmedAtBlock != 0 { + e.confirmedAtBlock = option.Some(edge.ConfirmedAtBlock) + } + if edge.LowerChildId != ([32]byte{}) { + e.lowerChild = option.Some(protocol.EdgeId{Hash: edge.LowerChildId}) + } + if edge.UpperChildId != ([32]byte{}) { + e.upperChild = option.Some(protocol.EdgeId{Hash: edge.UpperChildId}) + } + return edge, nil +} + +// CalculateEdgeId calculates an edge hash given its challenge id, start history, and end history. +func (cm *specChallengeManager) CalculateEdgeId( + ctx context.Context, + challengeLevel protocol.ChallengeLevel, + originId protocol.OriginId, + startHeight protocol.Height, + startHistoryRoot common.Hash, + endHeight protocol.Height, + endHistoryRoot common.Hash, +) (protocol.EdgeId, error) { + startInt64, err := safecast.ToInt64(startHeight) + if err != nil { + return protocol.EdgeId{}, err + } + endInt64, err := safecast.ToInt64(endHeight) + if err != nil { + return protocol.EdgeId{}, err + } + id, err := cm.caller.CalculateEdgeId( + cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), + challengeLevel.Uint8(), + originId, + big.NewInt(startInt64), + startHistoryRoot, + big.NewInt(endInt64), + endHistoryRoot, + ) + return protocol.EdgeId{Hash: id}, err +} + +func (cm *specChallengeManager) MultiUpdateInheritedTimers( + ctx context.Context, + challengeBranch []protocol.ReadOnlyEdge, + desiredNewTimerForLastEdge uint64, +) (*types.Transaction, error) { + if len(challengeBranch) == 0 { + return nil, errors.New("no edges to update") + } + edgeIds := make([][32]byte, 0) + var lastReceipt *types.Receipt + for _, edgeId := range challengeBranch { + edgeIds = append(edgeIds, edgeId.Id().Hash) + if challengetree.IsClaimingAnEdge(edgeId) { + _, err := cm.assertionChain.transact( + ctx, + cm.assertionChain.backend, + func(opts *bind.TransactOpts) (*types.Transaction, error) { + return cm.writer.MultiUpdateTimeCacheByChildren( + opts, + edgeIds, + new(big.Int).SetUint64(desiredNewTimerForLastEdge), + ) + }, + withoutSafeWait(), + ) + if err != nil { + return nil, errors.Wrap( + err, + "could not update inherited timer for multiple edge ids", + ) + } + receipt, err := cm.assertionChain.transact( + ctx, + cm.assertionChain.backend, + func(opts *bind.TransactOpts) (*types.Transaction, error) { + return cm.writer.UpdateTimerCacheByClaim( + opts, + edgeId.ClaimId().Unwrap(), + edgeId.Id().Hash, + new(big.Int).SetUint64(desiredNewTimerForLastEdge), + ) + }, + withoutSafeWait(), + ) + if err != nil { + return nil, errors.Wrap( + err, + "could not update inherited timer for multiple edge ids", + ) + } + edgeIds = make([][32]byte, 0) + lastReceipt = receipt + } + } + if len(edgeIds) > 0 { + receipt, err := cm.assertionChain.transact( + ctx, + cm.assertionChain.backend, + func(opts *bind.TransactOpts) (*types.Transaction, error) { + return cm.writer.MultiUpdateTimeCacheByChildren( + opts, + edgeIds, + new(big.Int).SetUint64(desiredNewTimerForLastEdge), + ) + }, + withoutSafeWait(), + ) + if err != nil { + return nil, errors.Wrap( + err, + "could not update inherited timer for multiple edge ids", + ) + } + lastReceipt = receipt + } + tx, _, err := cm.backend.TransactionByHash(ctx, lastReceipt.TxHash) + if err != nil { + return nil, errors.Wrapf(err, "could not get transaction by hash: %#x", lastReceipt.TxHash) + } + return tx, nil +} + +// ConfirmEdgeByOneStepProof checks a one step proof for a tentative winner edge id +// which will mark it as the winning claim of its associated challenge if correct. +// The edges along the winning branch and the corresponding assertion then need to be confirmed +// through separate transactions, if this succeeds. +func (cm *specChallengeManager) ConfirmEdgeByOneStepProof( + ctx context.Context, + tentativeWinnerId protocol.EdgeId, + oneStepData *protocol.OneStepData, + preHistoryInclusionProof []common.Hash, + postHistoryInclusionProof []common.Hash, +) error { + edge, err := cm.GetEdge(ctx, tentativeWinnerId) + if err != nil { + return err + } + s, err := edge.Unwrap().Status(ctx) + if err != nil { + return err + } + if s == protocol.EdgeConfirmed { + return nil + } + + assertionHash, err := edge.Unwrap().AssertionHash(ctx) + if err != nil { + return err + } + creationInfo, err := cm.assertionChain.ReadAssertionCreationInfo(ctx, assertionHash) + if err != nil { + return err + } + if !creationInfo.InboxMaxCount.IsUint64() { + return errors.New("inbox max count not a uint64") + } + + pre := make([][32]byte, len(preHistoryInclusionProof)) + for i, r := range preHistoryInclusionProof { + pre[i] = r + } + post := make([][32]byte, len(postHistoryInclusionProof)) + for i, r := range postHistoryInclusionProof { + post[i] = r + } + + machineStep, _ := edge.Unwrap().StartCommitment() + ospEntryAddr, err := cm.caller.OneStepProofEntry(cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return err + } + ospBindings, err := ospgen.NewOneStepProofEntryCaller(ospEntryAddr, cm.backend) + if err != nil { + return err + } + bridgeAddr, err := cm.assertionChain.rollup.Bridge(cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return err + } + execCtx := ospgen.ExecutionContext{ + MaxInboxMessagesRead: creationInfo.InboxMaxCount, + Bridge: bridgeAddr, + InitialWasmModuleRoot: creationInfo.WasmModuleRoot, + } + machStepInt64, err := safecast.ToInt64(machineStep) + if err != nil { + return err + } + result, err := ospBindings.ProveOneStep( + cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}), + execCtx, + big.NewInt(machStepInt64), + oneStepData.BeforeHash, + oneStepData.Proof, + ) + if err != nil { + return errors.Wrapf( + err, + "could not pre-check one step proof at machine step %d: before hash %#x, computed after hash %#x", + machineStep, + oneStepData.BeforeHash, + oneStepData.AfterHash, + ) + } + if _, err = cm.assertionChain.transact( + ctx, + cm.assertionChain.backend, + func(opts *bind.TransactOpts) (*types.Transaction, error) { + return cm.writer.ConfirmEdgeByOneStepProof( + opts, + tentativeWinnerId.Hash, + challengeV2gen.OneStepData{ + BeforeHash: oneStepData.BeforeHash, + Proof: oneStepData.Proof, + }, + challengeV2gen.ConfigData{ + WasmModuleRoot: creationInfo.WasmModuleRoot, + RequiredStake: creationInfo.RequiredStake, + ChallengeManager: creationInfo.ChallengeManager, + ConfirmPeriodBlocks: creationInfo.ConfirmPeriodBlocks, + NextInboxPosition: creationInfo.InboxMaxCount.Uint64(), + }, + pre, + post, + ) + }); err != nil { + errorConfirmingEdgeByOneStepProofCounter.Inc(1) + return errors.Wrapf( + err, + "could not confirm one step proof at machine step %d: before hash %#x, computed after hash %#x, actual expected after hash %#x", + machineStep, + oneStepData.BeforeHash, + oneStepData.AfterHash, + result, + ) + } + + return err +} + +// Like abi.NewType but panics if it errors for use in constants +func newStaticType(t string, internalType string, components []abi.ArgumentMarshaling) abi.Type { + ty, err := abi.NewType(t, internalType, components) + if err != nil { + panic(err) + } + return ty +} + +var bytes32Type = newStaticType("bytes32", "", nil) +var bytes32ArrayType = newStaticType("bytes32[]", "", []abi.ArgumentMarshaling{{Type: "bytes32"}}) +var assertionStateData = newStaticType("tuple", "AssertionStateData", []abi.ArgumentMarshaling{ + { + Type: "tuple", + InternalType: "AssertionState", + Name: "assertionState", + Components: []abi.ArgumentMarshaling{ + { + Type: "tuple", + InternalType: "GlobalState", + Name: "globalState", + Components: []abi.ArgumentMarshaling{ + { + Type: "bytes32[2]", + Components: []abi.ArgumentMarshaling{{ + Type: "bytes32", + }}, + Name: "bytes32Vals", + }, + { + Type: "uint64[2]", + Components: []abi.ArgumentMarshaling{{ + Type: "uint64", + }}, + Name: "u64Vals", + }, + }, + }, + { + Type: "uint8", + InternalType: "MachineStatus", + Name: "machineStatus", + }, + { + Type: "bytes32", + Name: "endHistoryRoot", + }, + }, + }, + { + Type: "bytes32", + Name: "prevAssertionHash", + }, + { + Type: "bytes32", + Name: "inboxAcc", + }, +}) + +var blockEdgeCreateProofAbi = abi.Arguments{ + { + Name: "inclusionProof", + Type: bytes32ArrayType, + }, + { + Name: "startState", + Type: assertionStateData, + }, + { + Name: "endState", + Type: assertionStateData, + }, +} + +type AssertionStateData struct { + AssertionState rollupgen.AssertionState + PrevAssertionHash [32]byte + InboxAcc [32]byte +} + +func (cm *specChallengeManager) AddBlockChallengeLevelZeroEdge( + ctx context.Context, + assertion protocol.Assertion, + startCommit, + endCommit history.History, + startEndPrefixProof []byte, +) (protocol.VerifiedRoyalEdge, error) { + assertionCreation, err := cm.assertionChain.ReadAssertionCreationInfo(ctx, assertion.Id()) + if err != nil { + return nil, fmt.Errorf("could not read assertion %#x creation info: %w", assertion.Id(), err) + } + prevId, err := assertion.PrevId(ctx) + if err != nil { + return nil, errors.Wrapf(err, "could not get assertion prev id for assertion %#x", assertion.Id().Hash) + } + parentAssertionCreation, err := cm.assertionChain.ReadAssertionCreationInfo(ctx, prevId) + if err != nil { + return nil, errors.Wrapf(err, "could not read parent assertion %#x creation info", prevId) + } + levelZeroBlockHeight, err := cm.caller.LAYERZEROBLOCKEDGEHEIGHT(cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return nil, errors.Wrap(err, "could not get level zero block edge height") + } + if !levelZeroBlockHeight.IsUint64() { + return nil, errors.New("level zero block height not a uint64") + } + if endCommit.Height != levelZeroBlockHeight.Uint64() { + return nil, fmt.Errorf( + "end commit has unexpected height %v (expected %v)", + endCommit.Height, + levelZeroBlockHeight.Uint64(), + ) + } + blockEdgeProof, err := blockEdgeCreateProofAbi.Pack( + endCommit.LastLeafProof, + AssertionStateData{ + AssertionState: parentAssertionCreation.AfterState, + PrevAssertionHash: parentAssertionCreation.ParentAssertionHash.Hash, + InboxAcc: parentAssertionCreation.AfterInboxBatchAcc, + }, + AssertionStateData{ + AssertionState: assertionCreation.AfterState, + PrevAssertionHash: assertionCreation.ParentAssertionHash.Hash, + InboxAcc: assertionCreation.AfterInboxBatchAcc, + }, + ) + if err != nil { + return nil, fmt.Errorf("could not serialize block edge proof: %w", err) + } + + edgeId, err := cm.CalculateEdgeId( + ctx, + protocol.NewBlockChallengeLevel(), + protocol.OriginId(assertionCreation.ParentAssertionHash.Hash), + protocol.Height(startCommit.Height), + startCommit.Merkle, + protocol.Height(endCommit.Height), + endCommit.Merkle, + ) + if err != nil { + return nil, errors.Wrap(err, "could not calculate edge id") + } + someLevelZeroEdge, err := cm.GetEdge(ctx, edgeId) + if err == nil && !someLevelZeroEdge.IsNone() { + existingSpecEdge, ok := someLevelZeroEdge.Unwrap().(*specEdge) + if !ok { + return nil, errors.New("not a *specEdge") + } + return &honestEdge{existingSpecEdge}, nil + } + endCommitInt64, err := safecast.ToInt64(endCommit.Height) + if err != nil { + return nil, errors.Wrap(err, "could not convert end commit height to int64") + } + args := challengeV2gen.CreateEdgeArgs{ + Level: protocol.NewBlockChallengeLevel().Uint8(), + EndHistoryRoot: endCommit.Merkle, + EndHeight: big.NewInt(endCommitInt64), + ClaimId: assertionCreation.AssertionHash.Hash, + PrefixProof: startEndPrefixProof, + Proof: blockEdgeProof, + } + callOpts := cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + stakeAmount, err := cm.caller.StakeAmounts(callOpts, big.NewInt(0) /* block challenge level */) + if err != nil { + return nil, err + } + if err = cm.assertionChain.autoDepositFunds(ctx, stakeAmount); err != nil { + return nil, errors.Wrap(err, "could not auto-deposit funds to make block challenge root edge stake") + } + receipt, err := cm.assertionChain.transact(ctx, cm.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return cm.writer.CreateLayerZeroEdge( + opts, + args, + ) + }) + if err != nil { + if strings.Contains(err.Error(), InvalidInclusionProofError) { + invalidInclusionProofCounter.Inc(1) + } + return nil, fmt.Errorf("could not create root block challenge edge: %w", err) + } + if len(receipt.Logs) == 0 { + return nil, errors.New("no logs observed from root block challenge edge ") + } + var edgeAdded *challengeV2gen.EdgeChallengeManagerEdgeAdded + var found bool + for _, log := range receipt.Logs { + creationEvent, creationErr := cm.filterer.ParseEdgeAdded(*log) + if creationErr == nil { + edgeAdded = creationEvent + found = true + break + } + } + if !found { + return nil, errors.New("could not find edge added event in logs") + } + someLevelZeroEdge, err = cm.GetEdge(ctx, protocol.EdgeId{Hash: edgeAdded.EdgeId}) + if err != nil { + return nil, errors.Wrapf(err, "could not get created edge by id: %#x", edgeAdded.EdgeId) + } + if someLevelZeroEdge.IsNone() { + return nil, fmt.Errorf("edge with id %#x was not found onchain", edgeAdded.EdgeId) + } + someSpecEdge, ok := someLevelZeroEdge.Unwrap().(*specEdge) + if !ok { + return nil, errors.New("not a *specEdge") + } + return &honestEdge{someSpecEdge}, nil +} + +var subchallengeEdgeProofAbi = abi.Arguments{ + { + Name: "startState", + Type: bytes32Type, + }, + { + Name: "endState", + Type: bytes32Type, + }, + { + Name: "claimStartInclusionProof", + Type: bytes32ArrayType, + }, + { + Name: "claimEndInclusionProof", + Type: bytes32ArrayType, + }, + { + Name: "edgeInclusionProof", + Type: bytes32ArrayType, + }, +} + +func (cm *specChallengeManager) AddSubChallengeLevelZeroEdge( + ctx context.Context, + challengedEdge protocol.SpecEdge, + startCommit, + endCommit history.History, + startParentInclusionProof, + endParentInclusionProof []common.Hash, + startEndPrefixProof []byte, +) (protocol.VerifiedRoyalEdge, error) { + chalLevel := challengedEdge.GetChallengeLevel() + subChalTyp := chalLevel.Next() + + // First check if the edge already exists. + mutualId := challengedEdge.MutualId() + edgeId, err := cm.CalculateEdgeId( + ctx, + subChalTyp, + protocol.OriginId(mutualId), + protocol.Height(startCommit.Height), + startCommit.Merkle, + protocol.Height(endCommit.Height), + endCommit.Merkle, + ) + if err != nil { + return nil, err + } + e, err := cm.GetEdge(ctx, edgeId) + if err == nil { + if e.IsNone() { + return nil, errors.New("got empty, newly created level zero edge") + } + someSpecEdge, ok := e.Unwrap().(*specEdge) + if !ok { + return nil, errors.New("not a *specEdge") + } + return &honestEdge{someSpecEdge}, nil + } + + subchallengeEdgeProof, err := subchallengeEdgeProofAbi.Pack( + startCommit.FirstLeaf, + endCommit.LastLeaf, + startParentInclusionProof, + endParentInclusionProof, + endCommit.LastLeafProof, + ) + if err != nil { + return nil, err + } + endCommitInt64, err := safecast.ToInt64(endCommit.Height) + if err != nil { + return nil, err + } + callOpts := cm.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + stakeAmount, err := cm.caller.StakeAmounts(callOpts, big.NewInt(int64(chalLevel)) /* subchallenge level */) + if err != nil { + return nil, err + } + if err = cm.assertionChain.autoDepositFunds(ctx, stakeAmount); err != nil { + return nil, errors.Wrapf(err, "could not auto-deposit funds to make subchallenge level %d root edge", chalLevel) + } + _, err = cm.assertionChain.transact(ctx, cm.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return cm.writer.CreateLayerZeroEdge( + opts, + challengeV2gen.CreateEdgeArgs{ + Level: subChalTyp.Uint8(), + EndHistoryRoot: endCommit.Merkle, + EndHeight: big.NewInt(endCommitInt64), + ClaimId: challengedEdge.Id().Hash, + PrefixProof: startEndPrefixProof, + Proof: subchallengeEdgeProof, + }, + ) + }) + if err != nil { + if strings.Contains(err.Error(), InvalidInclusionProofError) { + invalidInclusionProofCounter.Inc(1) + } + return nil, err + } + + e, err = cm.GetEdge(ctx, edgeId) + if err != nil { + return nil, err + } + if e.IsNone() { + return nil, errors.New("got empty, newly created level zero edge") + } + someSpecEdge, ok := e.Unwrap().(*specEdge) + if !ok { + return nil, errors.New("not a *specEdge") + } + return &honestEdge{someSpecEdge}, nil +} diff --git a/bold/chain-abstraction/sol-implementation/edge_challenge_manager_test.go b/bold/chain-abstraction/sol-implementation/edge_challenge_manager_test.go new file mode 100644 index 0000000000..865040eb80 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/edge_challenge_manager_test.go @@ -0,0 +1,1032 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl_test + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/state-commitments/history" + challenge_testing "github.com/offchainlabs/bold/testing" + stateprovider "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func simpleAssertionMetadata() *l2stateprovider.AssociatedAssertionMetadata { + return &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: common.Hash{}, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 0, + }, + BatchLimit: 1, + } +} + +func TestEdgeChallengeManager_IsUnrivaled(t *testing.T) { + ctx := context.Background() + + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + challengeManager := createdData.Chains[0].SpecChallengeManager() + + // Honest assertion being added. + leafAdder := func(stateManager l2stateprovider.Provider, leaf protocol.Assertion) protocol.VerifiedRoyalEdge { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startCommit, startErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, startErr) + + req.UpToHeight = option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)) + endCommit, endErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endErr) + + prefixProof, proofErr := stateManager.PrefixProof(ctx, req, 0) + + require.NoError(t, proofErr) + + edge, edgeErr := challengeManager.AddBlockChallengeLevelZeroEdge( + ctx, + leaf, + startCommit, + endCommit, + prefixProof, + ) + require.NoError(t, edgeErr) + return edge + } + + honestEdge := leafAdder(createdData.HonestStateManager, createdData.Leaf1) + challengeLevel := honestEdge.GetChallengeLevel() + require.Equal(t, protocol.NewBlockChallengeLevel(), challengeLevel) + + t.Run("first leaf is presumptive", func(t *testing.T) { + hasRival, rivalErr := honestEdge.HasRival(ctx) + require.NoError(t, rivalErr) + require.Equal(t, true, !hasRival) + }) + + evilEdge := leafAdder(createdData.EvilStateManager, createdData.Leaf2) + challengeLevel = evilEdge.GetChallengeLevel() + require.Equal(t, protocol.NewBlockChallengeLevel(), challengeLevel) + + t.Run("neither is presumptive if rivals", func(t *testing.T) { + hasRival, err := honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + hasRival, err = evilEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + }) + + t.Run("bisected children are presumptive", func(t *testing.T) { + var bisectHeight uint64 = challenge_testing.LevelZeroBlockEdgeHeight / 2 + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(bisectHeight)), + } + honestBisectCommit, err := createdData.HonestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)), + } + honestProof, err := createdData.HonestStateManager.PrefixProof(ctx, req, l2stateprovider.Height(bisectHeight)) + require.NoError(t, err) + + lower, upper, err := honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + hasRival, err := lower.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, true, !hasRival) + hasRival, err = upper.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, true, !hasRival) + + hasRival, err = honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + hasRival, err = evilEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + }) +} + +func TestEdgeChallengeManager_HasLengthOneRival(t *testing.T) { + ctx := context.Background() + bisectionScenario := setupBisectionScenario(t) + honestStateManager := bisectionScenario.honestStateManager + evilStateManager := bisectionScenario.evilStateManager + honestEdge := bisectionScenario.honestLevelZeroEdge + evilEdge := bisectionScenario.evilLevelZeroEdge + + t.Run("level zero edge with rivals is not one step fork source", func(t *testing.T) { + isOSF, err := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, false, isOSF) + isOSF, err = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, false, isOSF) + }) + t.Run("post bisection, mutual edge is one step fork source", func(t *testing.T) { + var height uint64 = challenge_testing.LevelZeroBlockEdgeHeight + for height > 1 { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(height / 2)), + } + honestBisectCommit, err := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + prefixCommitReq := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(height)), + } + honestProof, err := honestStateManager.PrefixProof( + ctx, + prefixCommitReq, + l2stateprovider.Height(height/2), + ) + require.NoError(t, err) + honestEdge, _, err = honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + evilBisectCommit, err := evilStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + evilProof, err := evilStateManager.PrefixProof(ctx, prefixCommitReq, l2stateprovider.Height(height/2)) + require.NoError(t, err) + evilEdge, _, err = evilEdge.Bisect(ctx, evilBisectCommit.Merkle, evilProof) + require.NoError(t, err) + + height /= 2 + + isOSF, err := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, height == 1, isOSF) + isOSF, err = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, height == 1, isOSF) + } + }) +} + +func TestEdgeChallengeManager_BlockChallengeAddLevelZeroEdge(t *testing.T) { + ctx := context.Background() + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + chain1 := createdData.Chains[0] + challengeManager := chain1.SpecChallengeManager() + + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + start, err := createdData.HonestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + req.UpToHeight = option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)) + end, err := createdData.HonestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + prefixProof, err := createdData.HonestStateManager.PrefixProof(ctx, req, l2stateprovider.Height(0)) + require.NoError(t, err) + + t.Run("OK", func(t *testing.T) { + created, err := challengeManager.AddBlockChallengeLevelZeroEdge(ctx, createdData.Leaf1, start, end, prefixProof) + require.NoError(t, err) + existing, err := challengeManager.AddBlockChallengeLevelZeroEdge(ctx, createdData.Leaf1, start, end, prefixProof) + require.NoError(t, err) + require.Equal(t, created, existing) + }) +} + +func TestEdgeChallengeManager_Bisect(t *testing.T) { + ctx := context.Background() + bisectionScenario := setupBisectionScenario(t) + honestStateManager := bisectionScenario.honestStateManager + honestEdge := bisectionScenario.honestLevelZeroEdge + + t.Run("OK", func(t *testing.T) { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight / 2)), + } + honestBisectCommit, err := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + req.UpToHeight = option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)) + honestProof, err := honestStateManager.PrefixProof(ctx, req, challenge_testing.LevelZeroBlockEdgeHeight/2) + require.NoError(t, err) + lower, upper, err := honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + gotLower, gotUpper, err := honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + require.Equal(t, lower.Id(), gotLower.Id()) + require.Equal(t, upper.Id(), gotUpper.Id()) + }) +} + +func TestEdgeChallengeManager_AddSubchallengeLeaf(t *testing.T) { + // Set up a scenario we can bisect. + ctx := context.Background() + + heights := &protocol.LayerZeroHeights{ + BlockChallengeHeight: 1 << 5, + BigStepChallengeHeight: 1 << 5, + SmallStepChallengeHeight: 1 << 5, + } + numBigSteps := uint8(1) + bisectionScenario := setupBisectionScenario( + t, + setup.WithChallengeTestingOpts( + challenge_testing.WithLayerZeroHeights(heights), + challenge_testing.WithNumBigStepLevels(numBigSteps), + ), + setup.WithStateManagerOpts( + stateprovider.WithLayerZeroHeights(heights, numBigSteps), + ), + ) + honestStateManager := bisectionScenario.honestStateManager + evilStateManager := bisectionScenario.evilStateManager + honestEdge := bisectionScenario.honestLevelZeroEdge + evilEdge := bisectionScenario.evilLevelZeroEdge + + challengeManager := bisectionScenario.topLevelFork.Chains[1].SpecChallengeManager() + + // Perform bisections all the way down to a one step fork. + var blockHeight uint64 = challenge_testing.LevelZeroBlockEdgeHeight + for blockHeight > 1 { + bisectTo := l2stateprovider.Height(blockHeight / 2) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(bisectTo), + } + var err error + honestBisectCommit, honestErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, honestErr) + req.UpToHeight = option.Some(l2stateprovider.Height(blockHeight)) + honestProof, honestProofErr := honestStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, honestProofErr) + honestEdge, _, err = honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + req.UpToHeight = option.Some(bisectTo) + evilBisectCommit, bisectErr := evilStateManager.HistoryCommitment(ctx, req) + require.NoError(t, bisectErr) + req.UpToHeight = option.Some(l2stateprovider.Height(blockHeight)) + evilProof, evilErr := evilStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, evilErr) + evilEdge, _, err = evilEdge.Bisect(ctx, evilBisectCommit.Merkle, evilProof) + require.NoError(t, err) + + blockHeight /= 2 + + isOSF, osfErr := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, blockHeight == 1, isOSF) + isOSF, osfErr = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, blockHeight == 1, isOSF) + } + + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startCommit, startErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, startErr) + req.UpToHeight = option.None[l2stateprovider.Height]() + endCommit, endErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, endErr) + require.Equal(t, startCommit.LastLeaf, endCommit.FirstLeaf) + + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startParentCommitment, parentErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, parentErr) + req.UpToHeight = option.Some(l2stateprovider.Height(1)) + endParentCommitment, endParentErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, endParentErr) + + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.Some(l2stateprovider.Height(endCommit.Height)), + } + startEndPrefixProof, proofErr := honestStateManager.PrefixProof(ctx, req, 0) + require.NoError(t, proofErr) + + leaf, err := challengeManager.AddSubChallengeLevelZeroEdge( + ctx, + honestEdge, + startCommit, + endCommit, + startParentCommitment.LastLeafProof, + endParentCommitment.LastLeafProof, + startEndPrefixProof, + ) + require.NoError(t, err) + + lvl := leaf.GetChallengeLevel() + require.Equal(t, protocol.ChallengeLevel(1), lvl) +} + +func TestEdgeChallengeManager_ConfirmByOneStepProof(t *testing.T) { + ctx := context.Background() + t.Run("edge does not exist", func(t *testing.T) { + bisectionScenario := setupBisectionScenario(t) + challengeManager := bisectionScenario.topLevelFork.Chains[1].SpecChallengeManager() + err := challengeManager.ConfirmEdgeByOneStepProof( + ctx, + protocol.EdgeId{Hash: common.BytesToHash([]byte("foo"))}, + &protocol.OneStepData{ + BeforeHash: common.Hash{}, + Proof: make([]byte, 0), + }, + make([]common.Hash, 0), + make([]common.Hash, 0), + ) + require.ErrorContains(t, err, "execution reverted") + }) + t.Run("OK", func(t *testing.T) { + scenario := setupOneStepProofScenario(t) + honestEdge := scenario.smallStepHonestEdge + + chain := scenario.topLevelFork.Chains[0] + challengeManager := scenario.topLevelFork.Chains[1].SpecChallengeManager() + + honestStateManager := scenario.honestStateManager + fromBlockChallengeHeight := uint64(0) + fromBigStep := uint64(0) + smallStep := uint64(0) + + id, err := honestEdge.AssertionHash(ctx) + require.NoError(t, err) + parentAssertionCreationInfo, err := chain.ReadAssertionCreationInfo(ctx, id) + require.NoError(t, err) + assertionMetadata := simpleAssertionMetadata() + assertionMetadata.WasmModuleRoot = parentAssertionCreationInfo.WasmModuleRoot + + data, startInclusionProof, endInclusionProof, err := honestStateManager.OneStepProofData( + ctx, + assertionMetadata, + []l2stateprovider.Height{ + l2stateprovider.Height(fromBlockChallengeHeight), + l2stateprovider.Height(fromBigStep), + }, + l2stateprovider.Height(smallStep), + ) + require.NoError(t, err) + + err = challengeManager.ConfirmEdgeByOneStepProof( + ctx, + honestEdge.Id(), + data, + startInclusionProof, + endInclusionProof, + ) + require.NoError(t, err) + edgeStatus, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgeConfirmed, edgeStatus) + + require.NoError(t, challengeManager.ConfirmEdgeByOneStepProof( + ctx, + honestEdge.Id(), + data, + startInclusionProof, + endInclusionProof, + )) // already confirmed should not error. + }) +} + +func TestEdgeChallengeManager_ConfirmByTime(t *testing.T) { + ctx := context.Background() + bisectionScenario := setupBisectionScenario(t) + honestStateManager := bisectionScenario.honestStateManager + honestEdge := bisectionScenario.honestLevelZeroEdge + + bisectTo := l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight / 2) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(bisectTo), + } + honestBisectCommit, err := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, err) + req.UpToHeight = option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)) + honestProof, err := honestStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, err) + honestChildren1, honestChildren2, err := honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + s1, err := honestChildren1.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgePending, s1) + s2, err := honestChildren2.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgePending, s2) + + // Adjust well beyond a challenge period. + for i := 0; i < 200; i++ { + bisectionScenario.topLevelFork.Backend.Commit() + } + + expectedNewTimer := uint64(200) + chalManager := bisectionScenario.topLevelFork.Chains[0].SpecChallengeManager() + _, err = chalManager.MultiUpdateInheritedTimers(ctx, []protocol.ReadOnlyEdge{honestChildren1, honestChildren2, honestEdge}, expectedNewTimer) + require.NoError(t, err) + _, err = honestEdge.ConfirmByTimer(ctx, bisectionScenario.topLevelFork.Leaf1.Id()) + require.NoError(t, err) + s0, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgeConfirmed, s0) + _, err = honestEdge.ConfirmByTimer(ctx, bisectionScenario.topLevelFork.Leaf1.Id()) + require.NoError(t, err) +} + +func TestEdgeChallengeManager_ConfirmByTime_MoreComplexScenario(t *testing.T) { + ctx := context.Background() + + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + challengeManager := createdData.Chains[0].SpecChallengeManager() + + // Honest assertion being added. + leafAdder := func(stateManager l2stateprovider.Provider, leaf protocol.Assertion) protocol.VerifiedRoyalEdge { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startCommit, startErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, startErr) + req.UpToHeight = option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)) + endCommit, endErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endErr) + prefixProof, proofErr := stateManager.PrefixProof(ctx, req, 0) + require.NoError(t, proofErr) + + edge, edgeErr := challengeManager.AddBlockChallengeLevelZeroEdge( + ctx, + leaf, + startCommit, + endCommit, + prefixProof, + ) + require.NoError(t, edgeErr) + return edge + } + honestEdge := leafAdder(createdData.HonestStateManager, createdData.Leaf1) + s0, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgePending, s0) + + hasRival, err := honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, hasRival) + + // Adjust well beyond a challenge period. + for i := 0; i < 200; i++ { + createdData.Backend.Commit() + } + + t.Run("confirmed by timer", func(t *testing.T) { + chalManager := createdData.Chains[0].SpecChallengeManager() + expectedNewTimer := uint64(200) + _, err = chalManager.MultiUpdateInheritedTimers(ctx, []protocol.ReadOnlyEdge{honestEdge}, expectedNewTimer) + require.NoError(t, err) + + _, err = honestEdge.ConfirmByTimer(ctx, createdData.Leaf1.Id()) + require.NoError(t, err) + status, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgeConfirmed, status) + }) + t.Run("double confirm is a no-op", func(t *testing.T) { + status, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgeConfirmed, status) + _, err = honestEdge.ConfirmByTimer(ctx, createdData.Leaf1.Id()) + require.NoError(t, err) + }) +} + +func upgradeWasmModuleRoot( + t *testing.T, + opts *bind.TransactOpts, + executor common.Address, + backend *setup.SimulatedBackendWrapper, + rollup common.Address, + wasmModuleRoot common.Hash, +) { + execBindings, err := mocksgen.NewUpgradeExecutorMock(executor, backend) + require.NoError(t, err) + abiItem, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) + require.NoError(t, err) + data, err := abiItem.Pack( + "setWasmModuleRoot", + wasmModuleRoot, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, rollup, data) + require.NoError(t, err) + backend.Commit() +} + +func TestUpgradingConfigMidChallenge(t *testing.T) { + ctx := context.Background() + scenario := setupOneStepProofScenario(t) + + rollupAddr := scenario.topLevelFork.Addrs.Rollup + backend := scenario.topLevelFork.Backend + adminAccount := scenario.topLevelFork.Accounts[0].TxOpts + + // We upgrade the Rollup's config values. + adminLogic, err := rollupgen.NewRollupAdminLogic(rollupAddr, backend) + require.NoError(t, err) + + newWasmModuleRoot := common.BytesToHash([]byte("nyannyannyan")) + upgradeWasmModuleRoot( + t, + adminAccount, + scenario.topLevelFork.Addrs.UpgradeExecutor, + backend, + rollupAddr, + newWasmModuleRoot, + ) + + // We confirm the edge by one-step-proof. + honestEdge := scenario.smallStepHonestEdge + chain := scenario.topLevelFork.Chains[0] + challengeManager := scenario.topLevelFork.Chains[1].SpecChallengeManager() + + honestStateManager := scenario.honestStateManager + fromBlockChallengeHeight := uint64(0) + fromBigStep := uint64(0) + smallStep := uint64(0) + + id, err := honestEdge.AssertionHash(ctx) + require.NoError(t, err) + parentAssertionCreationInfo, err := chain.ReadAssertionCreationInfo(ctx, id) + require.NoError(t, err) + + // We check the config snapshot used for the one step proof is different than what + // is now onchain, as these values changed mid-challenge. + gotWasmModuleRoot, err := adminLogic.WasmModuleRoot(chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{})) + require.NoError(t, err) + require.Equal(t, newWasmModuleRoot[:], gotWasmModuleRoot[:]) + require.NotEqual(t, parentAssertionCreationInfo.WasmModuleRoot[:], gotWasmModuleRoot) + + data, startInclusionProof, endInclusionProof, err := honestStateManager.OneStepProofData( + ctx, + simpleAssertionMetadata(), + []l2stateprovider.Height{ + l2stateprovider.Height(fromBlockChallengeHeight), + l2stateprovider.Height(fromBigStep), + }, + l2stateprovider.Height(smallStep), + ) + require.NoError(t, err) + + err = challengeManager.ConfirmEdgeByOneStepProof( + ctx, + honestEdge.Id(), + data, + startInclusionProof, + endInclusionProof, + ) + require.NoError(t, err) + + // Check the edge was confirmed. + edgeStatus, err := honestEdge.Status(ctx) + require.NoError(t, err) + require.Equal(t, protocol.EdgeConfirmed, edgeStatus) +} + +// Returns a snapshot of the data for a scenario in which both honest +// and evil validator validators have created level zero edges in a top-level +// challenge and are ready to bisect. +type bisectionScenario struct { + topLevelFork *setup.CreatedValidatorFork + honestStateManager l2stateprovider.Provider + evilStateManager l2stateprovider.Provider + honestLevelZeroEdge protocol.VerifiedRoyalEdge + evilLevelZeroEdge protocol.VerifiedRoyalEdge + honestStartCommit history.History + evilStartCommit history.History +} + +func setupBisectionScenario( + t *testing.T, + opts ...setup.Opt, +) *bisectionScenario { + t.Helper() + ctx := context.Background() + + opts = append(opts, setup.WithMockOneStepProver()) + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, opts...) + require.NoError(t, err) + + challengeManager := createdData.Chains[0].SpecChallengeManager() + + // Honest assertion being added. + leafAdder := func(stateManager l2stateprovider.Provider, leaf protocol.Assertion) (history.History, protocol.VerifiedRoyalEdge) { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startCommit, startErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, startErr) + req.UpToHeight = option.Some(l2stateprovider.Height(challenge_testing.LevelZeroBlockEdgeHeight)) + endCommit, endErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endErr) + prefixProof, proofErr := stateManager.PrefixProof(ctx, req, 0) + require.NoError(t, proofErr) + edge, edgeErr := challengeManager.AddBlockChallengeLevelZeroEdge( + ctx, + leaf, + startCommit, + endCommit, + prefixProof, + ) + require.NoError(t, edgeErr) + return startCommit, edge + } + + honestStartCommit, honestEdge := leafAdder(createdData.HonestStateManager, createdData.Leaf1) + chalLevel := honestEdge.GetChallengeLevel() + require.Equal(t, true, chalLevel.IsBlockChallengeLevel()) + hasRival, err := honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, true, !hasRival) + + isOSF, err := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, false, isOSF) + + evilStartCommit, evilEdge := leafAdder(createdData.EvilStateManager, createdData.Leaf2) + chalLevel = evilEdge.GetChallengeLevel() + require.Equal(t, true, chalLevel.IsBlockChallengeLevel()) + + // Honest and evil edge are rivals, neither is presumptive. + hasRival, err = honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + hasRival, err = evilEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + return &bisectionScenario{ + topLevelFork: createdData, + honestStateManager: createdData.HonestStateManager, + evilStateManager: createdData.EvilStateManager, + honestLevelZeroEdge: honestEdge, + evilLevelZeroEdge: evilEdge, + honestStartCommit: honestStartCommit, + evilStartCommit: evilStartCommit, + } +} + +// Returns a snapshot of the data for a one-step-proof scenario in which +// an evil validator has reached a one step fork against an honest validator +// in a small step subchallenge. Their disagreement must then be resolved via +// a one-step-proof to declare a winner. +type oneStepProofScenario struct { + topLevelFork *setup.CreatedValidatorFork + honestStateManager l2stateprovider.Provider + evilStateManager l2stateprovider.Provider + smallStepHonestEdge protocol.SpecEdge + smallStepEvilEdge protocol.SpecEdge +} + +// Sets up a challenge between two validators in which they make challenge moves +// to reach a one-step-proof in a small step subchallenge. It returns the data needed +// to then confirm the winner by one-step-proof execution. +func setupOneStepProofScenario( + t *testing.T, +) *oneStepProofScenario { + ctx := context.Background() + bisectionScenario := setupBisectionScenario(t) + honestStateManager := bisectionScenario.honestStateManager + evilStateManager := bisectionScenario.evilStateManager + honestEdge := bisectionScenario.honestLevelZeroEdge + evilEdge := bisectionScenario.evilLevelZeroEdge + + challengeManager := bisectionScenario.topLevelFork.Chains[1].SpecChallengeManager() + + var blockHeight uint64 = challenge_testing.LevelZeroBlockEdgeHeight + for blockHeight > 1 { + bisectTo := l2stateprovider.Height(blockHeight / 2) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(bisectTo), + } + honestBisectCommit, honestErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, honestErr) + req.UpToHeight = option.Some(l2stateprovider.Height(blockHeight)) + honestProof, honestProofErr := honestStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, honestProofErr) + var err error + honestEdge, _, err = honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + req.UpToHeight = option.Some(bisectTo) + evilBisectCommit, bisectErr := evilStateManager.HistoryCommitment(ctx, req) + require.NoError(t, bisectErr) + req.UpToHeight = option.Some(l2stateprovider.Height(blockHeight)) + evilProof, evilErr := evilStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, evilErr) + evilEdge, _, err = evilEdge.Bisect(ctx, evilBisectCommit.Merkle, evilProof) + require.NoError(t, err) + + blockHeight /= 2 + + isOSF, osfErr := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, blockHeight == 1, isOSF) + isOSF, osfErr = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, blockHeight == 1, isOSF) + } + + // Now opening big step level zero leaves at index 0 + bigStepAdder := func(stateManager l2stateprovider.Provider, sourceEdge protocol.SpecEdge) protocol.VerifiedRoyalEdge { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startCommit, startErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, startErr) + req.UpToHeight = option.None[l2stateprovider.Height]() + endCommit, endErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endErr) + require.Equal(t, startCommit.LastLeaf, endCommit.FirstLeaf) + + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startParentCommitment, parentErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, parentErr) + req.UpToHeight = option.Some(l2stateprovider.Height(1)) + endParentCommitment, endParentErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endParentErr) + + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.Some(l2stateprovider.Height(endCommit.Height)), + } + startEndPrefixProof, proofErr := stateManager.PrefixProof(ctx, req, 0) + require.NoError(t, proofErr) + leaf, leafErr := challengeManager.AddSubChallengeLevelZeroEdge( + ctx, + sourceEdge, + startCommit, + endCommit, + startParentCommitment.LastLeafProof, + endParentCommitment.LastLeafProof, + startEndPrefixProof, + ) + require.NoError(t, leafErr) + return leaf + } + + honestEdge = bigStepAdder(honestStateManager, honestEdge) + challengeLevel := honestEdge.GetChallengeLevel() + totalChallengeLevels := honestEdge.GetTotalChallengeLevels(ctx) + require.Equal(t, true, uint8(challengeLevel) < totalChallengeLevels-1) + require.Equal(t, true, challengeLevel > 0) + hasRival, err := honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, true, !hasRival) + + evilEdge = bigStepAdder(evilStateManager, evilEdge) + challengeLevel = evilEdge.GetChallengeLevel() + totalChallengeLevels = evilEdge.GetTotalChallengeLevels(ctx) + require.Equal(t, true, uint8(challengeLevel) < totalChallengeLevels-1) + require.Equal(t, true, challengeLevel > 0) + + var bigStepHeight uint64 = challenge_testing.LevelZeroBigStepEdgeHeight + for bigStepHeight > 1 { + bisectTo := l2stateprovider.Height(bigStepHeight / 2) + + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.Some(bisectTo), + } + honestBisectCommit, bisectErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, bisectErr) + + req.UpToHeight = option.Some(l2stateprovider.Height(bigStepHeight)) + honestProof, honestErr := honestStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, honestErr) + honestEdge, _, err = honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + req.UpToHeight = option.Some(bisectTo) + evilBisectCommit, bisectErr := evilStateManager.HistoryCommitment(ctx, req) + require.NoError(t, bisectErr) + + req.UpToHeight = option.Some(l2stateprovider.Height(bigStepHeight)) + evilProof, evilErr := evilStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, evilErr) + evilEdge, _, err = evilEdge.Bisect(ctx, evilBisectCommit.Merkle, evilProof) + require.NoError(t, err) + + bigStepHeight /= 2 + + isOSF, osfErr := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, bigStepHeight == 1, isOSF) + isOSF, osfErr = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, bigStepHeight == 1, isOSF) + } + + hasRival, err = honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + hasRival, err = evilEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + isAtOneStepFork, err := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, true, isAtOneStepFork) + + // Now opening small step level zero leaves at index 0 + smallStepAdder := func(stateManager l2stateprovider.Provider, edge protocol.SpecEdge) protocol.VerifiedRoyalEdge { + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0, 0}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startCommit, startErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, startErr) + + req.UpToHeight = option.None[l2stateprovider.Height]() + endCommit, endErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endErr) + + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startParentCommitment, parentErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, parentErr) + + req.UpToHeight = option.Some(l2stateprovider.Height(1)) + endParentCommitment, endParentErr := stateManager.HistoryCommitment(ctx, req) + require.NoError(t, endParentErr) + + req = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0, 0}, + UpToHeight: option.Some(l2stateprovider.Height(endCommit.Height)), + } + startEndPrefixProof, prefixErr := stateManager.PrefixProof(ctx, req, 0) + require.NoError(t, prefixErr) + + leaf, leafErr := challengeManager.AddSubChallengeLevelZeroEdge( + ctx, + edge, + startCommit, + endCommit, + startParentCommitment.LastLeafProof, + endParentCommitment.LastLeafProof, + startEndPrefixProof, + ) + require.NoError(t, leafErr) + + _, leafErr = challengeManager.AddSubChallengeLevelZeroEdge( + ctx, + edge, + startCommit, + endCommit, + startParentCommitment.LastLeafProof, + endParentCommitment.LastLeafProof, + startEndPrefixProof, + ) + require.NoError(t, leafErr) // Already submitted, should be a no-op. + + return leaf + } + + honestEdge = smallStepAdder(honestStateManager, honestEdge) + challengeLevel = honestEdge.GetChallengeLevel() + totalChallengeLevels = honestEdge.GetTotalChallengeLevels(ctx) + require.Equal(t, true, uint8(challengeLevel) == totalChallengeLevels-1) + hasRival, err = honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, true, !hasRival) + + evilEdge = smallStepAdder(evilStateManager, evilEdge) + challengeLevel = honestEdge.GetChallengeLevel() + totalChallengeLevels = honestEdge.GetTotalChallengeLevels(ctx) + require.Equal(t, true, uint8(challengeLevel) == totalChallengeLevels-1) + + hasRival, err = honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + hasRival, err = evilEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + // Get the lower-level edge of either edge we just bisected. + challengeLevel = honestEdge.GetChallengeLevel() + totalChallengeLevels = honestEdge.GetTotalChallengeLevels(ctx) + require.Equal(t, true, uint8(challengeLevel) == totalChallengeLevels-1) + + var smallStepHeight uint64 = challenge_testing.LevelZeroBigStepEdgeHeight + for smallStepHeight > 1 { + bisectTo := l2stateprovider.Height(smallStepHeight / 2) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0, 0}, + UpToHeight: option.Some(bisectTo), + } + + honestBisectCommit, bisectErr := honestStateManager.HistoryCommitment(ctx, req) + require.NoError(t, bisectErr) + + req.UpToHeight = option.Some(l2stateprovider.Height(smallStepHeight)) + honestProof, proofErr := honestStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, proofErr) + honestEdge, _, err = honestEdge.Bisect(ctx, honestBisectCommit.Merkle, honestProof) + require.NoError(t, err) + + req.UpToHeight = option.Some(bisectTo) + evilBisectCommit, evilBisectErr := evilStateManager.HistoryCommitment(ctx, req) + require.NoError(t, evilBisectErr) + + req.UpToHeight = option.Some(l2stateprovider.Height(smallStepHeight)) + evilProof, evilProofErr := evilStateManager.PrefixProof(ctx, req, bisectTo) + require.NoError(t, evilProofErr) + evilEdge, _, err = evilEdge.Bisect(ctx, evilBisectCommit.Merkle, evilProof) + require.NoError(t, err) + + smallStepHeight /= 2 + + isOSF, osfErr := honestEdge.HasLengthOneRival(ctx) + require.NoError(t, osfErr) + require.Equal(t, smallStepHeight == 1, isOSF) + isOSF, err = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, smallStepHeight == 1, isOSF) + } + + isAtOneStepFork, err = honestEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, true, isAtOneStepFork) + isAtOneStepFork, err = evilEdge.HasLengthOneRival(ctx) + require.NoError(t, err) + require.Equal(t, true, isAtOneStepFork) + + return &oneStepProofScenario{ + topLevelFork: bisectionScenario.topLevelFork, + honestStateManager: honestStateManager, + evilStateManager: evilStateManager, + smallStepHonestEdge: honestEdge, + smallStepEvilEdge: evilEdge, + } +} diff --git a/bold/chain-abstraction/sol-implementation/fast_confirm.go b/bold/chain-abstraction/sol-implementation/fast_confirm.go new file mode 100644 index 0000000000..e31e70578c --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/fast_confirm.go @@ -0,0 +1,217 @@ +package solimpl + +import ( + "context" + "errors" + "fmt" + "math/big" + "sort" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + retry "github.com/offchainlabs/bold/runtime" + "github.com/offchainlabs/nitro/solgen/go/contractsgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +type FastConfirmSafe struct { + safe *contractsgen.Safe + owners []common.Address + threshold uint64 + fastConfirmAssertionMethod abi.Method + assertionChain *AssertionChain +} + +func NewFastConfirmSafe( + callOpts *bind.CallOpts, + fastConfirmSafeAddress common.Address, + assertionChain *AssertionChain, +) (*FastConfirmSafe, error) { + fastConfirmSafe := &FastConfirmSafe{ + assertionChain: assertionChain, + } + safe, err := retry.UntilSucceeds(callOpts.Context, func() (*contractsgen.Safe, error) { + return contractsgen.NewSafe(fastConfirmSafeAddress, assertionChain.backend) + }) + if err != nil { + return nil, err + } + fastConfirmSafe.safe = safe + owners, err := retry.UntilSucceeds(callOpts.Context, func() ([]common.Address, error) { + return safe.GetOwners(callOpts) + }) + if err != nil { + return nil, fmt.Errorf("calling getOwners: %w", err) + } + + // This is needed because safe contract needs owners to be sorted. + sort.Slice(owners, func(i, j int) bool { + return owners[i].Cmp(owners[j]) < 0 + }) + fastConfirmSafe.owners = owners + threshold, err := retry.UntilSucceeds(callOpts.Context, func() (*big.Int, error) { + return safe.GetThreshold(callOpts) + }) + if err != nil { + return nil, fmt.Errorf("calling getThreshold: %w", err) + } + fastConfirmSafe.threshold = threshold.Uint64() + rollupUserLogicAbi, err := rollupgen.RollupUserLogicMetaData.GetAbi() + if err != nil { + return nil, err + } + fastConfirmAssertionMethod, ok := rollupUserLogicAbi.Methods["fastConfirmAssertion"] + if !ok { + return nil, errors.New("RollupUserLogic ABI missing fastConfirmNextNode method") + } + fastConfirmSafe.fastConfirmAssertionMethod = fastConfirmAssertionMethod + return fastConfirmSafe, nil +} + +func (f *FastConfirmSafe) fastConfirmAssertion(ctx context.Context, assertionCreationInfo *protocol.AssertionCreatedInfo) (bool, error) { + fastConfirmCallData, err := f.createFastConfirmCalldata(assertionCreationInfo) + if err != nil { + return false, err + } + // Current nonce of the safe. + callOpts := f.assertionChain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx}) + nonce, err := retry.UntilSucceeds(callOpts.Context, func() (*big.Int, error) { + return f.safe.Nonce(callOpts) + }) + if err != nil { + return false, err + } + // Hash of the safe transaction. + safeTxHash, err := retry.UntilSucceeds(callOpts.Context, func() (common.Hash, error) { + return f.safe.GetTransactionHash( + callOpts, + f.assertionChain.rollupAddr, + big.NewInt(0), + fastConfirmCallData, + 0, + big.NewInt(0), + big.NewInt(0), + big.NewInt(0), + common.Address{}, + common.Address{}, + nonce, + ) + }) + if err != nil { + return false, err + } + alreadyApproved, err := retry.UntilSucceeds(callOpts.Context, func() (*big.Int, error) { + return f.safe.ApprovedHashes(callOpts, f.assertionChain.StakerAddress(), safeTxHash) + }) + if err != nil { + return false, err + } + if alreadyApproved.Cmp(common.Big1) == 0 { + log.Info("Already approved Safe tx hash for fast confirmation, checking if we can execute the Safe tx", "safeHash", safeTxHash, "assertionHash", assertionCreationInfo.AssertionHash) + return f.checkApprovedHashAndExecTransaction(ctx, callOpts, fastConfirmCallData, safeTxHash) + } + + log.Info("Approving Safe tx hash to fast confirm", "safeHash", safeTxHash, "assertionHash", assertionCreationInfo.AssertionHash) + receipt, err := f.assertionChain.transact(ctx, f.assertionChain.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return f.safe.ApproveHash(opts, safeTxHash) + }) + if err != nil { + return false, err + } + if len(receipt.Logs) == 0 { + return false, errors.New("no logs observed from hash approval") + } + return f.checkApprovedHashAndExecTransaction(ctx, callOpts, fastConfirmCallData, safeTxHash) + +} + +func (f *FastConfirmSafe) createFastConfirmCalldata( + assertionCreationInfo *protocol.AssertionCreatedInfo, +) ([]byte, error) { + calldata, err := f.fastConfirmAssertionMethod.Inputs.Pack( + assertionCreationInfo.AssertionHash.Hash, + assertionCreationInfo.ParentAssertionHash.Hash, + assertionCreationInfo.AfterState, + assertionCreationInfo.AfterInboxBatchAcc, + ) + if err != nil { + return nil, err + } + fullCalldata := append([]byte{}, f.fastConfirmAssertionMethod.ID...) + fullCalldata = append(fullCalldata, calldata...) + return fullCalldata, nil +} + +func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction( + ctx context.Context, + callOpts *bind.CallOpts, + fastConfirmCallData []byte, + safeTxHash [32]byte, +) (bool, error) { + var signatures []byte + approvedHashCount := uint64(0) + for _, owner := range f.owners { + var approved *big.Int + // No need check if wallet has approved the hash, + // since checkApprovedHashAndExecTransaction is called only after wallet has approved the hash. + if f.assertionChain.StakerAddress() == owner { + approved = common.Big1 + } else { + var err error + approved, err = retry.UntilSucceeds(callOpts.Context, func() (*big.Int, error) { + return f.safe.ApprovedHashes(callOpts, owner, safeTxHash) + }) + if err != nil { + return false, err + } + } + + // If the owner has approved the hash, we add the signature to the transaction. + // We add the signature in the format r, s, v. + // We set v to 1, as it is the only possible value for a approved hash. + // We set r to the owner's address. + // We set s to the empty hash. + // Refer to the Safe contract for more information. + if approved.Cmp(common.Big1) == 0 { + approvedHashCount++ + v := uint8(1) + r := common.BytesToHash(owner.Bytes()) + s := common.Hash{} + signatures = append(signatures, r.Bytes()...) + signatures = append(signatures, s.Bytes()...) + signatures = append(signatures, v) + } + } + if approvedHashCount >= f.threshold { + log.Info("Executing Safe tx to fast confirm", "safeHash", safeTxHash) + receipt, err := f.assertionChain.transact(ctx, f.assertionChain.backend, func(opts *bind.TransactOpts) (*types.Transaction, error) { + return f.safe.ExecTransaction( + opts, + f.assertionChain.RollupAddress(), + big.NewInt(0), + fastConfirmCallData, + 0, + big.NewInt(0), + big.NewInt(0), + big.NewInt(0), + common.Address{}, + common.Address{}, + signatures, + ) + }) + if err != nil { + return false, err + } + if len(receipt.Logs) == 0 { + return false, errors.New("no logs observed from hash approval") + } + return true, nil + } + log.Info("Not enough Safe tx approvals yet to fast confirm", "safeHash", common.BytesToHash(safeTxHash[:]).Hex()) + return false, nil +} diff --git a/bold/chain-abstraction/sol-implementation/fifo_lock.go b/bold/chain-abstraction/sol-implementation/fifo_lock.go new file mode 100644 index 0000000000..83a7d9c321 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/fifo_lock.go @@ -0,0 +1,66 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "sync" +) + +type FIFO struct { + lock sync.Mutex + queue chan struct{} + waitQueue chan chan struct{} +} + +func NewFIFO(capacity int) *FIFO { + return &FIFO{ + queue: make(chan struct{}, 1), + waitQueue: make(chan chan struct{}, capacity), + } +} + +func (f *FIFO) Lock() bool { + waitCh := make(chan struct{}) + f.lock.Lock() + select { + // If the queue is empty, we can lock immediately + case f.queue <- struct{}{}: + f.lock.Unlock() + // If the queue is not empty, we need to wait our turn + default: + // We add our wait channel to the wait queue + if len(f.waitQueue) == cap(f.waitQueue) { + f.lock.Unlock() + // If the wait queue is full, lock acquisition fails + return false + } + f.waitQueue <- waitCh + f.lock.Unlock() + // We wait for our turn + <-waitCh + } + // Lock acquisition succeeded + return true +} + +func (f *FIFO) Unlock() { + f.lock.Lock() + defer f.lock.Unlock() + select { + // If the queue is not empty, we unlock and signal the next waiter + case <-f.queue: + // If there are waiters, we signal the next one + if len(f.waitQueue) > 0 { + // We pop the next waiter from the queue + nextWaitCh := <-f.waitQueue + // We acquire the lock for the next waiter + f.queue <- struct{}{} + // We signal the next waiter + close(nextWaitCh) + } + default: + panic("attempt to unlock unlocked mutex") + } +} diff --git a/bold/chain-abstraction/sol-implementation/fifo_lock_test.go b/bold/chain-abstraction/sol-implementation/fifo_lock_test.go new file mode 100644 index 0000000000..37872cfc37 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/fifo_lock_test.go @@ -0,0 +1,74 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "sync" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestFIFOSequentialLocking(t *testing.T) { + f := NewFIFO(10) + var output []int + var wg sync.WaitGroup + wg.Add(10) + for i := 0; i < 10; i++ { + a := i + go func() { + f.Lock() + output = append(output, a) + wg.Done() + }() + time.Sleep(time.Millisecond) + } + for i := 0; i < 10; i++ { + f.Unlock() + time.Sleep(time.Millisecond) + } + wg.Wait() + require.Equal(t, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, output) +} + +func TestFIFOUnlockPanic(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Unlock did not panic") + } + }() + + fifo := NewFIFO(1) + + // Try to unlock when the FIFO is already unlocked + fifo.Unlock() +} + +func TestFIFOOnlyOneLockAllowed(t *testing.T) { + fifo := NewFIFO(1) + + // Acquire the lock + fifo.Lock() + + // Try to acquire the lock from another goroutine + doneCh := make(chan struct{}) + go func() { + defer close(doneCh) + fifo.Lock() + defer fifo.Unlock() + }() + + // Wait for some time to ensure that the second goroutine doesn't acquire the lock + select { + case <-doneCh: + t.Error("Second lock acquisition didn't fail within the expected time") + case <-time.After(time.Millisecond * 100): + t.Log("As expected, was not able to acquire the second lock") + } + + // Release the lock + fifo.Unlock() +} diff --git a/bold/chain-abstraction/sol-implementation/metrics_contract_backend.go b/bold/chain-abstraction/sol-implementation/metrics_contract_backend.go new file mode 100644 index 0000000000..3cdce04afc --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/metrics_contract_backend.go @@ -0,0 +1,106 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/metrics" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +var _ protocol.ChainBackend = &MetricsContractBackend{} + +type MetricsContractBackend struct { + protocol.ChainBackend +} + +func NewMetricsContractBackend(backend protocol.ChainBackend) *MetricsContractBackend { + return &MetricsContractBackend{ + ChainBackend: backend, + } +} + +func (t *MetricsContractBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + data := call.Data + if len(data) >= 4 { // if there's a method selector + methodHash := fmt.Sprintf("%#x", data[:4]) // first 4 bytes are method selector + metrics.GetOrRegisterCounter("arb/backend/call_contract/"+methodHash+"/count", nil).Inc(1) + } + return t.ChainBackend.CallContract(ctx, call, blockNumber) +} + +func (t *MetricsContractBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + metrics.GetOrRegisterCounter("arb/backend/code_at/count", nil).Inc(1) + return t.ChainBackend.CodeAt(ctx, contract, blockNumber) +} + +func (t *MetricsContractBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + metrics.GetOrRegisterCounter("arb/backend/header_by_number/count", nil).Inc(1) + return t.ChainBackend.HeaderByNumber(ctx, number) +} + +func (t *MetricsContractBackend) HeaderU64(ctx context.Context) (uint64, error) { + metrics.GetOrRegisterCounter("arb/backend/header_by_number/count", nil).Inc(1) + return t.ChainBackend.HeaderU64(ctx) +} + +func (t *MetricsContractBackend) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + metrics.GetOrRegisterCounter("arb/backend/pending_code_at/count", nil).Inc(1) + return t.ChainBackend.PendingCodeAt(ctx, account) +} + +func (t *MetricsContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + metrics.GetOrRegisterCounter("arb/backend/pending_code_at/count", nil).Inc(1) + return t.ChainBackend.PendingNonceAt(ctx, account) +} + +func (t *MetricsContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + metrics.GetOrRegisterCounter("arb/backend/suggest_gas_price/count", nil).Inc(1) + return t.ChainBackend.SuggestGasPrice(ctx) +} + +func (t *MetricsContractBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + metrics.GetOrRegisterCounter("arb/backend/suggest_gas_tip_cap/count", nil).Inc(1) + return t.ChainBackend.SuggestGasTipCap(ctx) +} + +func (t *MetricsContractBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { + data := call.Data + if len(data) >= 4 { // if there's a method selector + methodHash := fmt.Sprintf("%#x", data[:4]) // first 4 bytes are method selector + metrics.GetOrRegisterCounter("arb/backend/estimate_gas/"+methodHash+"/count", nil).Inc(1) + } + return t.ChainBackend.EstimateGas(ctx, call) +} + +func (t *MetricsContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { + if tx != nil && len(tx.Data()) >= 4 { + methodHash := fmt.Sprintf("%#x", tx.Data()[:4]) + metrics.GetOrRegisterCounter("arb/backend/send_transaction/"+methodHash+"/count", nil).Inc(1) + } + return t.ChainBackend.SendTransaction(ctx, tx) +} + +func (t *MetricsContractBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + metrics.GetOrRegisterCounter("arb/backend/filter_logs/count", nil).Inc(1) + return t.ChainBackend.FilterLogs(ctx, query) +} + +func (t *MetricsContractBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + metrics.GetOrRegisterCounter("arb/backend/subscribe_filter_logs/count", nil).Inc(1) + return t.ChainBackend.SubscribeFilterLogs(ctx, query, ch) +} + +func (t *MetricsContractBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + metrics.GetOrRegisterCounter("arb/backend/transaction_receipt/count", nil).Inc(1) + return t.ChainBackend.TransactionReceipt(ctx, txHash) +} diff --git a/bold/chain-abstraction/sol-implementation/tracked_contract_backend.go b/bold/chain-abstraction/sol-implementation/tracked_contract_backend.go new file mode 100644 index 0000000000..d8bbd7b95f --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/tracked_contract_backend.go @@ -0,0 +1,108 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "context" + "fmt" + "math/big" + "sort" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +// TrackedContractBackend implements a wrapper around a chain backend interface +// which can keep track of the number of calls and transactions made per +// method to a contract along with gas costs for transactions. These can then be +// printed out to a destination for analysis. +type TrackedContractBackend struct { + protocol.ChainBackend + metrics map[string]*MethodMetrics // method hash -> metrics + mu sync.RWMutex +} + +type MethodMetrics struct { + Calls int // Total number of calls to the method. + Txs int // Total number of transactions to the method. + GasCosts []big.Int // Gas costs for each tx. +} + +func NewTrackedContractBackend(backend protocol.ChainBackend) *TrackedContractBackend { + return &TrackedContractBackend{ + ChainBackend: backend, + metrics: make(map[string]*MethodMetrics), + } +} + +func (t *TrackedContractBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + data := call.Data + if len(data) >= 4 { // if there's a method selector + methodHash := fmt.Sprintf("%#x", data[:4]) // first 4 bytes are method selector + t.mu.Lock() + metric, ok := t.metrics[methodHash] + if !ok { + metric = &MethodMetrics{} + t.metrics[methodHash] = metric + } + metric.Calls++ + // Assuming gas cost for call can be added here if needed + t.mu.Unlock() + } + return t.ChainBackend.CallContract(ctx, call, blockNumber) +} + +func (t *TrackedContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { + if tx != nil && len(tx.Data()) >= 4 { + methodHash := fmt.Sprintf("%#x", tx.Data()[:4]) + t.mu.Lock() + metric, ok := t.metrics[methodHash] + if !ok { + metric = &MethodMetrics{} + t.metrics[methodHash] = metric + } + metric.Txs++ + gasCost := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) + metric.GasCosts = append(metric.GasCosts, *gasCost) + t.mu.Unlock() + } + return t.ChainBackend.SendTransaction(ctx, tx) +} + +// Computes a median of big integers (gas costs) +func median(gasCosts []big.Int) *big.Int { + if len(gasCosts) == 0 { + return nil + } + sorted := make([]big.Int, len(gasCosts)) + copy(sorted, gasCosts) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Cmp(&sorted[j]) < 0 + }) + mid := len(sorted) / 2 + if len(sorted)%2 == 0 { + return new(big.Int).Add(&sorted[mid-1], &sorted[mid]).Div(&sorted[mid], big.NewInt(2)) + } + return &sorted[mid] +} + +func (t *TrackedContractBackend) PrintMetrics() { + t.mu.RLock() + defer t.mu.RUnlock() + for methodHash, metrics := range t.metrics { + fmt.Printf("Method: %s\n", methodHash) + fmt.Printf("Calls: %d\n", metrics.Calls) + fmt.Printf("Transactions: %d\n", metrics.Txs) + if med := median(metrics.GasCosts); med != nil { + fmt.Printf("Median Gas Cost: %s\n", med.String()) + } else { + fmt.Println("Median Gas Cost: N/A") + } + fmt.Println("-----------") + } +} diff --git a/bold/chain-abstraction/sol-implementation/tracked_contract_backend_test.go b/bold/chain-abstraction/sol-implementation/tracked_contract_backend_test.go new file mode 100644 index 0000000000..4c3f876ed5 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/tracked_contract_backend_test.go @@ -0,0 +1,156 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" +) + +func TestTrackedContractBackend(t *testing.T) { + backend := NewTrackedContractBackend(&MockContractBackend{}) + + t.Run("CallContract", func(t *testing.T) { + data := []byte("1234mockdata1283918231923") + call := ethereum.CallMsg{Data: data} + _, err := backend.CallContract(context.Background(), call, nil) + require.NoError(t, err) + + key := fmt.Sprintf("%#x", data[:4]) + metric, ok := backend.metrics[key] + require.True(t, ok) + require.Equal(t, 1, metric.Calls) + }) + + t.Run("SendTransaction", func(t *testing.T) { + data := []byte("1234mocktx1283918231923") + tx := types.NewTransaction(1, common.HexToAddress("0x123456789"), big.NewInt(1), 21000, big.NewInt(1), data) + err := backend.SendTransaction(context.Background(), tx) + require.NoError(t, err) + + key := fmt.Sprintf("%#x", data[:4]) + metric, ok := backend.metrics[key] + require.True(t, ok) + require.Equal(t, 1, metric.Txs) + require.Equal(t, big.NewInt(21000), &metric.GasCosts[0]) + }) +} + +func Test_median(t *testing.T) { + t.Run("Empty slice", func(t *testing.T) { + var gasCosts []big.Int + med := median(gasCosts) + require.Equal(t, true, med == nil) + }) + + t.Run("Single value", func(t *testing.T) { + gasCosts := []big.Int{*big.NewInt(5)} + med := median(gasCosts) + require.Equal(t, big.NewInt(5), med) + }) + + t.Run("Two values takes mean", func(t *testing.T) { + gasCosts := []big.Int{*big.NewInt(5), *big.NewInt(15)} + med := median(gasCosts) + require.Equal(t, big.NewInt(7), med) + }) + + t.Run("Odd number of values", func(t *testing.T) { + gasCosts := []big.Int{*big.NewInt(5), *big.NewInt(15), *big.NewInt(25)} + med := median(gasCosts) + require.Equal(t, big.NewInt(15), med) + }) + + t.Run("Unsorted values", func(t *testing.T) { + gasCosts := []big.Int{*big.NewInt(25), *big.NewInt(5), *big.NewInt(15)} + med := median(gasCosts) + require.Equal(t, big.NewInt(15), med) + }) +} + +type MockContractBackend struct{} + +func (m *MockContractBackend) HeaderU64(ctx context.Context) (uint64, error) { + return 0, nil +} + +func (m *MockContractBackend) ChainID(ctx context.Context) (*big.Int, error) { + return nil, nil +} + +func (m *MockContractBackend) Close() {} + +func (m *MockContractBackend) Client() rpc.ClientInterface { + return nil +} + +func (m *MockContractBackend) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + return nil, nil +} + +func (m *MockContractBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + return nil, nil +} + +func (m *MockContractBackend) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + return nil, nil +} + +func (m *MockContractBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + return nil, nil +} + +func (m *MockContractBackend) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return nil, nil +} + +func (m *MockContractBackend) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + return 0, nil +} + +func (m *MockContractBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + return big.NewInt(1), nil +} + +func (m *MockContractBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + return big.NewInt(1), nil +} + +func (m *MockContractBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { + return 0, nil +} + +func (m *MockContractBackend) SendTransaction(ctx context.Context, tx *types.Transaction) error { + return nil +} + +func (m *MockContractBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + return nil, nil +} + +func (m *MockContractBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return nil, nil +} + +func (m *MockContractBackend) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + return nil, nil +} + +func (m *MockContractBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + return nil, nil +} + +func (m *MockContractBackend) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return nil, false, nil +} diff --git a/bold/chain-abstraction/sol-implementation/transact.go b/bold/chain-abstraction/sol-implementation/transact.go new file mode 100644 index 0000000000..f0a3020e92 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/transact.go @@ -0,0 +1,219 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "context" + "math/big" + "time" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers" +) + +// ChainCommitter defines a type of chain backend that supports +// committing changes via a direct method, such as a simulated backend +// for testing purposes. +type ChainCommitter interface { + Commit() common.Hash +} + +type transactConfig struct { + waitForDesiredBlockNum bool +} + +type transactOpt func(tc *transactConfig) + +func withoutSafeWait() transactOpt { + return func(tc *transactConfig) { + tc.waitForDesiredBlockNum = false + } +} + +// Runs a callback function meant to write to a chain backend, and if the +// chain backend supports committing directly, we call the commit function before +// returning. This function additionally waits for the transaction to complete and returns +// an optional transaction receipt. It returns an error if the +// transaction had a non-successful status on-chain, or if the execution of the callback +// errored directly. +func (a *AssertionChain) transact( + ctx context.Context, + backend protocol.ChainBackend, + fn func(opts *bind.TransactOpts) (*types.Transaction, error), + configOpts ...transactOpt, +) (*types.Receipt, error) { + config := &transactConfig{ + waitForDesiredBlockNum: true, + } + for _, o := range configOpts { + o(config) + } + // We do not send the tx, but instead estimate gas first. + opts := copyTxOpts(a.txOpts) + + // No BOLD transactions require a value. + opts.Value = big.NewInt(0) + opts.NoSend = true + tx, err := fn(opts) + if err != nil { + return nil, errors.Wrap(err, "test execution of tx errored before sending payable tx") + } + // Convert the transaction into a CallMsg. + msg := ethereum.CallMsg{ + From: opts.From, + To: tx.To(), + Gas: 0, // Set to 0 to let the node decide + GasPrice: opts.GasPrice, + Value: opts.Value, + Data: tx.Data(), + } + + // Estimate the gas required for the transaction. This will catch errors early + // without needing to pay for the transaction and waste funds. + gas, err := backend.EstimateGas(ctx, msg) + if err != nil { + return nil, errors.Wrapf(err, "gas estimation errored for tx with hash %s", containers.Trunc(tx.Hash().Bytes())) + } + + // Now, we send the tx with the estimated gas. + defaultGasUint64, err := safecast.ToUint64(defaultBaseGas) + if err != nil { + return nil, errors.Wrap(err, "could not convert default base gas to uint64") + } + opts.GasLimit = gas + defaultGasUint64 + tx, err = a.transactor.SendTransaction(ctx, fn, opts, gas) + if err != nil { + return nil, err + } + + if commiter, ok := backend.(ChainCommitter); ok { + commiter.Commit() + } + ctxWaitMined, cancelWaitMined := context.WithTimeout(ctx, time.Minute) + defer cancelWaitMined() + receipt, err := bind.WaitMined(ctxWaitMined, backend, tx) + if err != nil { + return nil, err + } + + if config.waitForDesiredBlockNum && a.rpcHeadBlockNumber != rpc.LatestBlockNumber { + ctxWaitSafe, cancelWaitSafe := context.WithTimeout(ctx, time.Minute*20) + defer cancelWaitSafe() + receipt, err = a.waitForTxToBeSafe(ctxWaitSafe, backend, tx, receipt) + if err != nil { + return nil, err + } + } + + if receipt.Status != types.ReceiptStatusSuccessful { + callMsg := ethereum.CallMsg{ + From: opts.From, + To: tx.To(), + Gas: 0, + GasPrice: nil, + Value: tx.Value(), + Data: tx.Data(), + AccessList: tx.AccessList(), + } + if _, err := backend.CallContract(ctx, callMsg, nil); err != nil { + return nil, errors.Wrap(err, "transaction errored") + } + } + return receipt, nil +} + +// waitForTxToBeSafe waits for the transaction to be mined in a block that is safe. +func (a *AssertionChain) waitForTxToBeSafe( + ctx context.Context, + backend protocol.ChainBackend, + tx *types.Transaction, + receipt *types.Receipt, +) (*types.Receipt, error) { + for { + if ctx.Err() != nil { + return nil, ctx.Err() + } + latestSafeHeader, err := backend.HeaderByNumber(ctx, big.NewInt(int64(a.rpcHeadBlockNumber))) + if err != nil { + return nil, err + } + if !latestSafeHeader.Number.IsUint64() { + return nil, errors.New("block number is not uint64") + } + latestSafeHeaderNumber := latestSafeHeader.Number.Uint64() + txSafe := latestSafeHeaderNumber >= receipt.BlockNumber.Uint64() + + // If the tx is not yet safe, we can simply wait. + if !txSafe { + var blocksLeftForTxToBeSafe int64 + if receipt.BlockNumber.Uint64() > latestSafeHeaderNumber { + blocksLeftForTxToBeSafe = 0 + } else { + blocksLeftForTxToBeSafe, err = safecast.ToInt64(latestSafeHeaderNumber - receipt.BlockNumber.Uint64()) + if err != nil { + return nil, errors.Wrap(err, "could not convert blocks left for tx to be safe to int64") + } + } + timeToWait := a.averageTimeForBlockCreation * time.Duration(blocksLeftForTxToBeSafe) + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-time.After(timeToWait): + } + } else { + break + } + } + + // This is to handle the case where the transaction is mined in a block, but then the block is reorged. + // In this case, we want to wait for the transaction to be mined again. + receiptLatest, err := bind.WaitMined(ctx, backend, tx) + if err != nil { + return nil, err + } + // If the receipt block number is different from the latest receipt block number, we wait for the transaction + // to be in the safe block again. + if receiptLatest.BlockNumber.Cmp(receipt.BlockNumber) != 0 { + return a.waitForTxToBeSafe(ctx, backend, tx, receiptLatest) + } + return receipt, nil +} + +// copyTxOpts creates a deep copy of the given transaction options. +func copyTxOpts(opts *bind.TransactOpts) *bind.TransactOpts { + copied := &bind.TransactOpts{ + From: opts.From, + Context: opts.Context, + NoSend: opts.NoSend, + Signer: opts.Signer, + GasLimit: opts.GasLimit, + } + + if opts.Nonce != nil { + copied.Nonce = new(big.Int).Set(opts.Nonce) + } + if opts.Value != nil { + copied.Value = new(big.Int).Set(opts.Value) + } + if opts.GasPrice != nil { + copied.GasPrice = new(big.Int).Set(opts.GasPrice) + } + if opts.GasFeeCap != nil { + copied.GasFeeCap = new(big.Int).Set(opts.GasFeeCap) + } + if opts.GasTipCap != nil { + copied.GasTipCap = new(big.Int).Set(opts.GasTipCap) + } + return copied +} diff --git a/bold/chain-abstraction/sol-implementation/types.go b/bold/chain-abstraction/sol-implementation/types.go new file mode 100644 index 0000000000..2600f4efd8 --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/types.go @@ -0,0 +1,169 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import ( + "context" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +// Assertion is a wrapper around the binding to the type +// of the same name in the protocol contracts. This allows us +// to have a smaller API surface area and attach useful +// methods that callers can use directly. +type Assertion struct { + chain *AssertionChain + id protocol.AssertionHash + createdAt uint64 + + // Fields that are eventually constant like status, firstChildBlock etc. + // These are set to option.None until they are in the final state, after which they are set + // to the final value and never changed again (this saves us the on-chain call) + prevId option.Option[protocol.AssertionHash] // This is set the first time prevId is called + firstChildBlock option.Option[uint64] // Once the assertion has a first child, this is set + secondChildBlock option.Option[uint64] // Once the assertion has a second child, this is set + isFirstChild bool // Once the assertion is determined to be a first child, this is set + isConfirmed bool // Once the assertion is confirmed, this is set +} + +func (a *Assertion) Id() protocol.AssertionHash { + return a.id +} + +func (a *Assertion) PrevId(ctx context.Context) (protocol.AssertionHash, error) { + if a.prevId.IsSome() { + return a.prevId.Unwrap(), nil + } + creationInfo, err := a.chain.ReadAssertionCreationInfo(ctx, a.id) + if err != nil { + return protocol.AssertionHash{}, err + } + a.prevId = option.Some(creationInfo.ParentAssertionHash) + return a.prevId.Unwrap(), nil +} + +func (a *Assertion) HasSecondChild(ctx context.Context, opts *bind.CallOpts) (bool, error) { + if a.secondChildBlock.IsSome() { + return a.secondChildBlock.Unwrap() > 0, nil + } + inner, err := a.inner(ctx, opts) + if err != nil { + return false, err + } + return inner.SecondChildBlock > 0, nil +} + +func (a *Assertion) inner(ctx context.Context, opts *bind.CallOpts) (*rollupgen.AssertionNode, error) { + var b [32]byte + copy(b[:], a.id.Bytes()) + assertionNode, err := a.chain.userLogic.GetAssertion(opts, b) + if err != nil { + return nil, err + } + if assertionNode.Status == uint8(0) { + return nil, errors.Wrapf( + ErrNotFound, + "assertion with id %#x", + a.id, + ) + } + // Update the assertion with the latest data, if they are in now in constant state. + if assertionNode.FirstChildBlock > 0 { + a.firstChildBlock = option.Some(assertionNode.FirstChildBlock) + } + if assertionNode.SecondChildBlock > 0 { + a.secondChildBlock = option.Some(assertionNode.SecondChildBlock) + } + if assertionNode.IsFirstChild { + a.isFirstChild = true + } + assertionStatus := protocol.AssertionStatus(assertionNode.Status) + if assertionStatus == protocol.AssertionConfirmed { + a.isConfirmed = true + } + return &assertionNode, nil +} +func (a *Assertion) FirstChildCreationBlock(ctx context.Context, opts *bind.CallOpts) (uint64, error) { + if a.firstChildBlock.IsSome() { + return a.firstChildBlock.Unwrap(), nil + } + inner, err := a.inner(ctx, opts) + if err != nil { + return 0, err + } + return inner.FirstChildBlock, nil +} +func (a *Assertion) SecondChildCreationBlock(ctx context.Context, opts *bind.CallOpts) (uint64, error) { + if a.secondChildBlock.IsSome() { + return a.secondChildBlock.Unwrap(), nil + } + inner, err := a.inner(ctx, opts) + if err != nil { + return 0, err + } + return inner.SecondChildBlock, nil +} +func (a *Assertion) IsFirstChild(ctx context.Context, opts *bind.CallOpts) (bool, error) { + if a.isFirstChild { + return a.isFirstChild, nil + } + inner, err := a.inner(ctx, opts) + if err != nil { + return false, err + } + return inner.IsFirstChild, nil +} +func (a *Assertion) CreatedAtBlock() uint64 { + return a.createdAt +} +func (a *Assertion) Status(ctx context.Context, opts *bind.CallOpts) (protocol.AssertionStatus, error) { + if a.isConfirmed { + return protocol.AssertionConfirmed, nil + } + inner, err := a.inner(ctx, opts) + if err != nil { + return 0, err + } + return protocol.AssertionStatus(inner.Status), nil +} + +type honestEdge struct { + *specEdge +} + +func (h *honestEdge) Honest() {} + +type specEdge struct { + id [32]byte + mutualId [32]byte + manager *specChallengeManager + miniStaker option.Option[common.Address] + inner challengeV2gen.ChallengeEdge + startHeight uint64 + endHeight uint64 + totalChallengeLevels uint8 + assertionHash protocol.AssertionHash + + // Fields that are eventually constant like status, hasRival etc. + // These are set to option.None until they are in the final state, after which they are set + // to the final value and never changed again (this saves us the on-chain call) + timeUnrivaled option.Option[uint64] // Once edge has a rival, this is set + hasRival bool // Once edge has a rival, this is set + isConfirmed bool // Once the edge is confirmed, this is set + confirmedAtBlock option.Option[uint64] // Once the edge is confirmed, this is set + lowerChild option.Option[protocol.EdgeId] // Once the edge has a lower child, this is set + upperChild option.Option[protocol.EdgeId] // Once the edge has an upper child, this is set + hasLengthOneRival bool // Once the edge has a rival of length 1, this is set + verifiedHonest bool // Whether or not the edge has been verified as honest. +} diff --git a/bold/chain-abstraction/sol-implementation/types_test.go b/bold/chain-abstraction/sol-implementation/types_test.go new file mode 100644 index 0000000000..272e3d064c --- /dev/null +++ b/bold/chain-abstraction/sol-implementation/types_test.go @@ -0,0 +1,12 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package solimpl + +import protocol "github.com/offchainlabs/bold/chain-abstraction" + +var ( + _ = protocol.SpecEdge(&specEdge{}) + _ = protocol.SpecChallengeManager(&specChallengeManager{}) +) diff --git a/bold/challenge-manager/chain-watcher/watcher.go b/bold/challenge-manager/chain-watcher/watcher.go new file mode 100644 index 0000000000..82d40fce9f --- /dev/null +++ b/bold/challenge-manager/chain-watcher/watcher.go @@ -0,0 +1,1070 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package watcher implements the main monitoring logic for protocol validators. +// The challenge watcher is a singleton service available to all spawned edge +// trackers and it tracks common information such as the edges' ancestors and an +// edge's time unrivaled. +// +// See: [github.com/offchainlabs/bold/challenge-manager/edge-tracker] +package watcher + +import ( + "context" + "fmt" + "math" + "sync/atomic" + "time" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + + "github.com/offchainlabs/bold/api" + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + challengetree "github.com/offchainlabs/bold/challenge-manager/challenge-tree" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/logs/ephemeral" + retry "github.com/offchainlabs/bold/runtime" + "github.com/offchainlabs/bold/util/stopwaiter" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" +) + +var ( + edgeAddedCounter = metrics.NewRegisteredCounter("arb/validator/watcher/edge_added", nil) + edgeConfirmedByTimeCounter = metrics.NewRegisteredCounter("arb/validator/watcher/confirmed_by_time", nil) + edgeConfirmedByOSPCounter = metrics.NewRegisteredCounter("arb/validator/watcher/confirmed_by_osp", nil) + errorConfirmingAssertionByWinnerCounter = metrics.NewRegisteredCounter("arb/validator/watcher/error_confirming_assertion_by_winner", nil) + assertionConfirmedCounter = metrics.GetOrRegisterCounter("arb/validator/scanner/assertion_confirmed", nil) +) + +// EdgeManager provides a method to track edges, via edge tracker goroutines. +type EdgeManager interface { + TrackEdge(ctx context.Context, edge protocol.VerifiedRoyalEdge) error +} + +// Represents a set of honest edges being tracked in a top-level challenge and +// all the associated subchallenge honest edges along with some more metadata +// used for computing information needed for confirmations. Each time an edge is +// created onchain, the challenge watcher service will add it to its respective +// "trackedChallenge" namespaced under the top-level assertion hash the edge +// belongs to. +type trackedChallenge struct { + honestEdgeTree *challengetree.RoyalChallengeTree + confirmedLevelZeroEdgeClaimIds *threadsafe.Map[protocol.ClaimId, protocol.EdgeId] +} + +// The Watcher implements a service in the validator runtime that is in charge +// of scanning through all edge creation events via a polling mechanism. It will +// keep track of edges the validator's state provider agrees with within +// trackedChallenge instances. The challenge watcher provides two useful +// methods: (a) the ability to compute the honest path timer of an edge, and (b) +// the ability to check if an edge with a certain claim id has been confirmed. +// Both are used during the confirmation process in edge tracker goroutines. +type Watcher struct { + stopwaiter.StopWaiter + histChecker l2stateprovider.HistoryChecker + chain protocol.AssertionChain + edgeManager EdgeManager + pollEventsInterval time.Duration + challenges *threadsafe.Map[protocol.AssertionHash, *trackedChallenge] + backend protocol.ChainBackend + validatorName string + numBigStepLevels uint8 + initialSyncCompleted atomic.Bool + apiDB db.Database + assertionConfirmingInterval time.Duration + averageTimeForBlockCreation time.Duration + evilEdgesByLevel *threadsafe.Map[protocol.ChallengeLevel, *threadsafe.Set[protocol.EdgeId]] + // Only track challenges for these parent assertion hashes. + // Track all if empty / nil. + trackChallengeParentAssertionHashes []protocol.AssertionHash + maxGetLogBlocks uint64 +} + +// New initializes a watcher service for frequently scanning the chain +// for edge creations and confirmations. +func New( + chain protocol.AssertionChain, + histChecker l2stateprovider.HistoryChecker, + validatorName string, + apiDB db.Database, + assertionConfirmingInterval time.Duration, + averageTimeForBlockCreation time.Duration, + trackChallengeParentAssertionHashes []protocol.AssertionHash, + maxGetLogBlocks uint64, +) (*Watcher, error) { + return &Watcher{ + chain: chain, + edgeManager: nil, // Must be set after construction. + pollEventsInterval: time.Millisecond * 500, + challenges: threadsafe.NewMap(threadsafe.MapWithMetric[protocol.AssertionHash, *trackedChallenge]("challenges")), + backend: chain.Backend(), + histChecker: histChecker, + numBigStepLevels: chain.SpecChallengeManager().NumBigSteps(), + validatorName: validatorName, + apiDB: apiDB, + assertionConfirmingInterval: assertionConfirmingInterval, + averageTimeForBlockCreation: averageTimeForBlockCreation, + evilEdgesByLevel: threadsafe.NewMap(threadsafe.MapWithMetric[protocol.ChallengeLevel, *threadsafe.Set[protocol.EdgeId]]("evilEdgesByLevel")), + trackChallengeParentAssertionHashes: trackChallengeParentAssertionHashes, + maxGetLogBlocks: maxGetLogBlocks, + }, nil +} + +// SetEdgeManager sets the EdgeManager that will track the royal edges. +func (w *Watcher) SetEdgeManager(em EdgeManager) { + w.edgeManager = em +} + +// AvgBlockTime returns the average time for block creation. +func (w *Watcher) AvgBlockTime() time.Duration { + return w.averageTimeForBlockCreation +} + +// HonestBlockChallengeRootEdge gets the honest block challenge root edge for a +// given challenge by challenged assertion id if it exists. +func (w *Watcher) HonestBlockChallengeRootEdge( + ctx context.Context, + assertionHash protocol.AssertionHash, +) (protocol.ReadOnlyEdge, error) { + chal, ok := w.challenges.TryGet(assertionHash) + if !ok { + return nil, fmt.Errorf("no challenge for assertion hash %#x", assertionHash) + } + return chal.honestEdgeTree.RoyalBlockChallengeRootEdge() +} + +// ConfirmedEdgeWithClaimExists checks if a confirmed, level zero edge exists +// that claims a particular edge id for a tracked challenge. This is used during +// the confirmation process of edges within edge tracker goroutines. Returns the +// claiming edge id. +func (w *Watcher) ConfirmedEdgeWithClaimExists( + topLevelAssertionHash protocol.AssertionHash, + claimId protocol.ClaimId, +) (protocol.EdgeId, bool) { + challenge, ok := w.challenges.TryGet(topLevelAssertionHash) + if !ok { + return protocol.EdgeId{}, false + } + return challenge.confirmedLevelZeroEdgeClaimIds.TryGet(claimId) +} + +func (w *Watcher) IsRoyal(assertionHash protocol.AssertionHash, edgeId protocol.EdgeId) bool { + chal, ok := w.challenges.TryGet(assertionHash) + if !ok { + return false + } + return chal.honestEdgeTree.HasRoyalEdge(edgeId) +} + +func (w *Watcher) InheritedTimerForEdge( + ctx context.Context, + edgeId protocol.EdgeId, +) (protocol.InheritedTimer, error) { + chalManager := w.chain.SpecChallengeManager() + edgeOpt, err := chalManager.GetEdge(ctx, edgeId) + if err != nil { + return 0, err + } + if edgeOpt.IsNone() { + return 0, fmt.Errorf("no edge found with id %#x", edgeId.Hash) + + } + return edgeOpt.Unwrap().LatestInheritedTimer(ctx) +} + +func (w *Watcher) IsSynced() bool { + return w.initialSyncCompleted.Load() +} + +// Start watching the chain via a polling mechanism for all edge added and +// confirmation events in order to process some of this data into internal +// representations for confirmation purposes. +func (w *Watcher) Start(ctx context.Context) { + w.StopWaiter.Start(ctx, w) + scanRange, err := retry.UntilSucceeds(ctx, func() (filterRange, error) { + return w.getStartEndBlockNum(ctx) + }) + if err != nil { + log.Error("Could not get start and end block num", "err", err) + return + } + fromBlock := scanRange.startBlockNum + toBlock := scanRange.endBlockNum + + // Get a challenge manager instance and filterer. + challengeManager := w.chain.SpecChallengeManager() + filterer, err := retry.UntilSucceeds(ctx, func() (*challengeV2gen.EdgeChallengeManagerFilterer, error) { + return challengeV2gen.NewEdgeChallengeManagerFilterer(challengeManager.Address(), w.backend) + }) + if err != nil { + log.Error("Could not initialize edge challenge manager filterer", "err", err) + return + } + for startBlock := fromBlock; startBlock <= toBlock; startBlock = startBlock + w.maxGetLogBlocks { + endBlock := startBlock + w.maxGetLogBlocks + if endBlock > toBlock { + endBlock = toBlock + } + filterOpts := &bind.FilterOpts{ + Start: startBlock, + End: &endBlock, + Context: ctx, + } + + // Checks for different events right away before we start polling. + _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + return true, w.checkForEdgeAdded(ctx, filterer, filterOpts) + }) + if err != nil { + log.Error("Could not check for edge added", "err", err) + return + } + _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + return true, w.checkForEdgeConfirmedByOneStepProof(ctx, filterer, filterOpts) + }) + if err != nil { + log.Error("Could not check for edge confirmed by osp", "err", err) + return + } + _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + return true, w.checkForEdgeConfirmedByTime(ctx, filterer, filterOpts) + }) + if err != nil { + log.Error("Could not check for edge confirmed by time", "err", err) + return + } + } + + fromBlock = toBlock + ticker := time.NewTicker(w.pollEventsInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + toBlock, err := w.chain.DesiredHeaderU64(ctx) + if err != nil { + log.Error("Could not get latest header", "err", err) + continue + } + // AssertionChain's rpcHeadBlockNumber is set to finalized and this might occur due to l1 backends of load balancer + // not being in consensus wrt finalized. In which case we ignore and continue + if fromBlock > toBlock { + continue + } + if fromBlock == toBlock { + w.initialSyncCompleted.Store(true) + continue + } + // Get a challenge manager instance and filterer. + challengeManager := w.chain.SpecChallengeManager() + filterer, err = retry.UntilSucceeds(ctx, func() (*challengeV2gen.EdgeChallengeManagerFilterer, error) { + return challengeV2gen.NewEdgeChallengeManagerFilterer(challengeManager.Address(), w.backend) + }) + if err != nil { + log.Error("Could not get challenge manager filterer", "err", err) + return + } + filterOpts := &bind.FilterOpts{ + Start: fromBlock, + End: &toBlock, + Context: ctx, + } + if err = w.checkForEdgeAdded(ctx, filterer, filterOpts); err != nil { + log.Error("Could not check for edge added", "err", err) + continue + } + if err = w.checkForEdgeConfirmedByOneStepProof(ctx, filterer, filterOpts); err != nil { + log.Error("Could not check for edge confirmed by osp", "err", err) + continue + } + if err = w.checkForEdgeConfirmedByTime(ctx, filterer, filterOpts); err != nil { + log.Error("Could not check for edge confirmed by time", "err", err) + continue + } + fromBlock = toBlock + case <-ctx.Done(): + return + } + } +} + +// GetRoyalEdges returns all royal, tracked edges in the watcher by assertion +// hash. +func (w *Watcher) GetRoyalEdges(ctx context.Context) (map[protocol.AssertionHash][]*api.JsonTrackedRoyalEdge, error) { + l1BlockNum, err := w.chain.DesiredL1HeaderU64(ctx) + if err != nil { + return nil, err + } + response := make(map[protocol.AssertionHash][]*api.JsonTrackedRoyalEdge) + if err = w.challenges.ForEach(func(assertionHash protocol.AssertionHash, t *trackedChallenge) error { + return t.honestEdgeTree.GetEdges().ForEach(func(edgeId protocol.EdgeId, edge protocol.SpecEdge) error { + start, startRoot := edge.StartCommitment() + end, endRoot := edge.EndCommitment() + createdAt, err2 := edge.CreatedAtBlock() + if err2 != nil { + return err2 + } + unrivaled, err2 := t.honestEdgeTree.IsUnrivaledAtBlockNum(edge, l1BlockNum) + if err2 != nil { + return err2 + } + hasRival := !unrivaled + timeUnrivaled, err2 := t.honestEdgeTree.TimeUnrivaled(ctx, edge, l1BlockNum) + if err2 != nil { + return err2 + } + var miniStaker common.Address + if edge.MiniStaker().IsSome() { + miniStaker = edge.MiniStaker().Unwrap() + } + var claimId common.Hash + if edge.ClaimId().IsSome() { + claimId = common.Hash(edge.ClaimId().Unwrap()) + } + response[assertionHash] = append( + response[assertionHash], + &api.JsonTrackedRoyalEdge{ + Id: edgeId.Hash, + ChallengeLevel: uint8(edge.GetChallengeLevel()), + StartHistoryRoot: startRoot, + StartHeight: uint64(start), + EndHeight: uint64(end), + EndHistoryRoot: endRoot, + CreatedAtBlock: createdAt, + MutualId: common.Hash(edge.MutualId()), + OriginId: common.Hash(edge.OriginId()), + ClaimId: claimId, + HasRival: hasRival, + TimeUnrivaled: timeUnrivaled, + MiniStaker: miniStaker, + }, + ) + return nil + }) + }); err != nil { + return nil, err + } + return response, nil +} + +func (w *Watcher) BlockChallengeRootEdge( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, +) (protocol.SpecEdge, error) { + chal, ok := w.challenges.TryGet(challengedAssertionHash) + if !ok { + return nil, fmt.Errorf( + "could not get challenge for top level assertion %#x", + challengedAssertionHash, + ) + } + return chal.honestEdgeTree.BlockChallengeRootEdge(ctx) +} + +func (w *Watcher) LowerMostRoyalEdges( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, +) ([]protocol.SpecEdge, error) { + chal, ok := w.challenges.TryGet(challengedAssertionHash) + if !ok { + return nil, fmt.Errorf( + "could not get challenge for top level assertion %#x", + challengedAssertionHash, + ) + } + return chal.honestEdgeTree.GetAllRoyalLeaves(ctx) +} + +func (w *Watcher) ComputeAncestors( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + edgeId protocol.EdgeId, +) ([]protocol.ReadOnlyEdge, error) { + chal, ok := w.challenges.TryGet(challengedAssertionHash) + if !ok { + return nil, fmt.Errorf( + "could not get challenge for top level assertion %#x", + challengedAssertionHash, + ) + } + l1BlockHeaderNumber, err := w.chain.DesiredL1HeaderU64(ctx) + if err != nil { + return nil, err + } + return chal.honestEdgeTree.ComputeAncestors(ctx, edgeId, l1BlockHeaderNumber) +} + +func (w *Watcher) ClosestEssentialAncestor( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + edge protocol.VerifiedRoyalEdge, +) (protocol.ReadOnlyEdge, error) { + chal, ok := w.challenges.TryGet(challengedAssertionHash) + if !ok { + return nil, fmt.Errorf( + "could not get challenge for top level assertion %#x", + challengedAssertionHash, + ) + } + return chal.honestEdgeTree.ClosestEssentialAncestor(ctx, edge) +} + +func (w *Watcher) IsEssentialAncestorConfirmable( + ctx context.Context, + edge protocol.SpecEdge, + challengedAssertionHash protocol.AssertionHash, + confirmationThreshold uint64, +) (bool, error) { + chal, ok := w.challenges.TryGet(challengedAssertionHash) + if !ok { + return false, fmt.Errorf( + "could not get challenge for top level assertion %#x", + challengedAssertionHash, + ) + } + blockL1HeaderNumber, err := w.chain.DesiredL1HeaderU64(ctx) + if err != nil { + return false, err + } + if !chal.honestEdgeTree.HasRoyalEdge(edge.Id()) { + return false, fmt.Errorf("edge with id %#x is not yet tracked locally", edge.Id().Hash) + } + essentialAncestor, err := chal.honestEdgeTree.ClosestEssentialAncestor(ctx, edge) + if err != nil { + return false, err + } + pathWeight, err := chal.honestEdgeTree.ComputePathWeight(ctx, challengetree.ComputePathWeightArgs{ + Child: edge.Id(), + Ancestor: essentialAncestor.Id(), + BlockNum: blockL1HeaderNumber, + }) + if err != nil { + return false, err + } + return pathWeight >= confirmationThreshold, nil +} + +func (w *Watcher) IsConfirmableEssentialEdge( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + essentialEdgeId protocol.EdgeId, + confirmationThreshold uint64, +) (bool, []challengetree.EssentialPath, uint64, error) { + chal, ok := w.challenges.TryGet(challengedAssertionHash) + if !ok { + return false, nil, 0, fmt.Errorf("could not get challenge for top level assertion %#x", challengedAssertionHash) + } + blockL1HeaderNumber, err := w.chain.DesiredL1HeaderU64(ctx) + if err != nil { + return false, nil, 0, err + } + confirmable, essentialPaths, timer, err := chal.honestEdgeTree.IsConfirmableEssentialEdge( + ctx, + challengetree.IsConfirmableArgs{ + EssentialEdge: essentialEdgeId, + BlockNum: blockL1HeaderNumber, + ConfirmationThreshold: confirmationThreshold, + }, + ) + return confirmable, essentialPaths, timer, err +} + +func (w *Watcher) AllowTrackingEdgeWithParentHash(parentHash protocol.AssertionHash) bool { + if len(w.trackChallengeParentAssertionHashes) == 0 { + return true + } + for _, hash := range w.trackChallengeParentAssertionHashes { + if hash == parentHash { + return true + } + } + return false +} + +// AddVerifiedHonestEdge adds an edge known to be honest to the chain watcher's +// internally tracked challenge trees and spawns an edge tracker for it. Should +// be called after the challenge manager creates a new edge, or bisects an edge +// and produces two children from that move. +func (w *Watcher) AddVerifiedHonestEdge(ctx context.Context, edge protocol.VerifiedRoyalEdge) error { + assertionHash, err := edge.AssertionHash(ctx) + if err != nil { + return err + } + // If a challenge is not yet being tracked locally by the watcher for the + // edge's assertion hash, it adds an entry to the map. + chal, ok := w.challenges.TryGet(assertionHash) + if !ok { + tree := challengetree.New( + assertionHash, + w.chain, + w.histChecker, + w.numBigStepLevels, + w.validatorName, + ) + chal = &trackedChallenge{ + honestEdgeTree: tree, + confirmedLevelZeroEdgeClaimIds: threadsafe.NewMap(threadsafe.MapWithMetric[protocol.ClaimId, protocol.EdgeId]("confirmedLevelZeroEdgeClaimIds")), + } + w.challenges.Put(assertionHash, chal) + } + // Add the edge to a local challenge tree of honest edges and, if needed, we + // also spawn a tracker for the edge. + start, startRoot := edge.StartCommitment() + end, endRoot := edge.EndCommitment() + fields := []any{ + "edgeId", fmt.Sprintf("%#x", edge.Id().Bytes()[:4]), + "challengeLevel", edge.GetChallengeLevel(), + "challengedAssertionHash", fmt.Sprintf("%#x", assertionHash.Bytes()[:4]), + "startHeight", start, + "endHeight", end, + "startCommit", fmt.Sprintf("%#x", startRoot[:4]), + "endCommit", fmt.Sprintf("%#x", endRoot[:4]), + "validatorName", w.validatorName, + "isHonestEdge", true, + } + log.Info("Observed honest edge", fields...) + if err = chal.honestEdgeTree.AddRoyalEdge(edge); err != nil { + log.Error("Could not add verified honest edge to local cache", "err", err) + return errors.Wrap(err, "could not add honest edge to challenge tree") + } + go func() { + if _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + if innerErr := w.saveEdgeToDB(ctx, edge, true /* is royal */); innerErr != nil { + log.Error("Could not save edge to db", "err", innerErr) + return false, innerErr + } + return false, nil + }); err != nil { + log.Error("Could not save edge to db", "err", err) + } + }() + return nil +} + +// Filters for all edge added events within a range and processes them. +func (w *Watcher) checkForEdgeAdded( + ctx context.Context, + filterer *challengeV2gen.EdgeChallengeManagerFilterer, + filterOpts *bind.FilterOpts, +) error { + it, err := filterer.FilterEdgeAdded(filterOpts, nil, nil, nil) + if err != nil { + return err + } + defer func() { + if err = it.Close(); err != nil { + log.Error("Could not close filter iterator", "err", err) + } + }() + for it.Next() { + if it.Error() != nil { + return errors.Wrapf( + err, + "got iterator error when scanning edge creations from block %d to %d", + filterOpts.Start, + *filterOpts.End, + ) + } + edgeAdded, processErr := retry.UntilSucceeds(ctx, func() (bool, error) { + return w.processEdgeAddedEvent(ctx, it.Event) + }) + if processErr != nil { + return processErr + } + if edgeAdded { + edgeAddedCounter.Inc(1) + } + } + return nil +} + +// AddEdge to watcher. If it is honest, it will be tracked. +func (w *Watcher) AddEdge(ctx context.Context, edge protocol.SpecEdge) (bool, error) { + challengeParentAssertionHash, err := edge.AssertionHash(ctx) + if err != nil { + return false, err + } + start, startRoot := edge.StartCommitment() + end, endRoot := edge.EndCommitment() + chal, ok := w.challenges.TryGet(challengeParentAssertionHash) + if !ok { + tree := challengetree.New( + challengeParentAssertionHash, + w.chain, + w.histChecker, + w.numBigStepLevels, + w.validatorName, + ) + chal = &trackedChallenge{ + honestEdgeTree: tree, + confirmedLevelZeroEdgeClaimIds: threadsafe.NewMap(threadsafe.MapWithMetric[protocol.ClaimId, protocol.EdgeId]("confirmedLevelZeroEdgeClaimIds")), + } + w.challenges.Put(challengeParentAssertionHash, chal) + } + // Add the edge to a local challenge tree of tracked edges. If it is honest, + // we also spawn a tracker for the edge. + if err = chal.honestEdgeTree.AddEdge(ctx, edge); err != nil { + if !errors.Is(err, challengetree.ErrAlreadyBeingTracked) { + return false, errors.Wrap(err, "could not add edge to challenge tree") + } + // If the error is that we are already tracking the edge, we exit early. + return false, nil + } + royalEdge, isRoyal := edge.AsVerifiedHonest() + if isRoyal { + err = w.edgeManager.TrackEdge(ctx, royalEdge) + if err != nil { + return false, err + } + } + fields := []any{ + "edgeId", fmt.Sprintf("%#x", edge.Id().Bytes()[:4]), + "challengeLevel", edge.GetChallengeLevel(), + "challengedAssertionHash", fmt.Sprintf("%#x", challengeParentAssertionHash.Bytes()[:4]), + "startHeight", start, + "endHeight", end, + "startCommit", fmt.Sprintf("%#x", startRoot[:4]), + "endCommit", fmt.Sprintf("%#x", endRoot[:4]), + "isHonestEdge", isRoyal, + "validatorName", w.validatorName, + } + if isRoyal { + log.Info("Observed honest edge", fields...) + } else { + if edge.ClaimId().IsSome() { + evilEdges, ok := w.evilEdgesByLevel.TryGet(edge.GetChallengeLevel()) + if !ok { + evilEdges = threadsafe.NewSet(threadsafe.SetWithMetric[protocol.EdgeId]("evilEdges")) + w.evilEdgesByLevel.Put(edge.GetChallengeLevel(), evilEdges) + } + if evilEdges.NumItems() < 5 { + evilEdges.Insert(edge.Id()) + } + if evilEdges.NumItems() >= 5 { + log.Warn("High number of evil edges observed", "numEvilEdges", evilEdges.NumItems(), "challengeLevel", edge.GetChallengeLevel()) + metrics.GetOrRegisterCounter("arb/validator/watcher/high_num_evil_edges_at_level_"+fmt.Sprint(edge.GetChallengeLevel()), nil).Inc(1) + } + } + log.Info("Observed evil edge", fields...) + } + go func() { + if _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + if innerErr := w.saveEdgeToDB(ctx, edge, isRoyal); innerErr != nil { + log.Error("Could not save edge to db", "err", innerErr) + return false, innerErr + } + return false, nil + }); err != nil { + log.Error("Could not save edge to db", "err", err) + } + }() + return true, nil +} + +// Processes an edge added event by adding it to the honest challenge tree if it +// is honest. +func (w *Watcher) processEdgeAddedEvent( + ctx context.Context, + event *challengeV2gen.EdgeChallengeManagerEdgeAdded, +) (bool, error) { + challengeManager := w.chain.SpecChallengeManager() + edgeOpt, err := challengeManager.GetEdge(ctx, protocol.EdgeId{Hash: event.EdgeId}) + if err != nil { + return false, err + } + if edgeOpt.IsNone() { + return false, fmt.Errorf("no edge found with id %#x", event.EdgeId) + } + edge := edgeOpt.Unwrap() + challengeParentAssertionHash, err := edge.AssertionHash(ctx) + if err != nil { + return false, err + } + if !w.allowTrackingEdgeWithChallengeParentAssertionHash(challengeParentAssertionHash) { + return false, nil + } + return w.AddEdge(ctx, edgeOpt.Unwrap()) +} + +func (w *Watcher) allowTrackingEdgeWithChallengeParentAssertionHash(challengeParentAssertionHash protocol.AssertionHash) bool { + if len(w.trackChallengeParentAssertionHashes) == 0 { + return true + } + for _, hash := range w.trackChallengeParentAssertionHashes { + if hash == challengeParentAssertionHash { + return true + } + } + return false +} + +// Filters for edge confirmed by one step proof events within a range and +// processes any events found. +func (w *Watcher) checkForEdgeConfirmedByOneStepProof( + ctx context.Context, + filterer *challengeV2gen.EdgeChallengeManagerFilterer, + filterOpts *bind.FilterOpts, +) error { + it, err := filterer.FilterEdgeConfirmedByOneStepProof(filterOpts, nil, nil) + if err != nil { + return err + } + defer func() { + if err = it.Close(); err != nil { + log.Error("Could not close filter iterator", "err", err) + } + }() + for it.Next() { + if it.Error() != nil { + return errors.Wrapf( + err, + "got iterator error when scanning edge creations from block %d to %d", + filterOpts.Start, + *filterOpts.End, + ) + } + _, processErr := retry.UntilSucceeds(ctx, func() (bool, error) { + return true, w.processEdgeConfirmation(ctx, protocol.EdgeId{ + Hash: it.Event.EdgeId, + }) + }) + if processErr != nil { + return processErr + } + edgeConfirmedByOSPCounter.Inc(1) + } + return nil +} + +// Filters for edge confirmed by time within a range and processes any events +// found. +func (w *Watcher) checkForEdgeConfirmedByTime( + ctx context.Context, + filterer *challengeV2gen.EdgeChallengeManagerFilterer, + filterOpts *bind.FilterOpts, +) error { + it, err := filterer.FilterEdgeConfirmedByTime(filterOpts, nil, nil) + if err != nil { + return err + } + defer func() { + if err = it.Close(); err != nil { + log.Error("Could not close filter iterator", "err", err) + } + }() + for it.Next() { + if it.Error() != nil { + return errors.Wrapf( + err, + "got iterator error when scanning edge creations from block %d to %d", + filterOpts.Start, + *filterOpts.End, + ) + } + _, processErr := retry.UntilSucceeds(ctx, func() (bool, error) { + return true, w.processEdgeConfirmation(ctx, protocol.EdgeId{ + Hash: it.Event.EdgeId, + }) + }) + if processErr != nil { + return processErr + } + edgeConfirmedByTimeCounter.Inc(1) + } + return nil +} + +// Processes an edge confirmation event by checking if it claims an edge. If so, +// we add the claim id to the confirmed, level zero edge claim ids map for the +// associated assertion-level challenge the edge is a part of. +func (w *Watcher) processEdgeConfirmation( + ctx context.Context, + edgeId protocol.EdgeId, +) error { + challengeManager := w.chain.SpecChallengeManager() + edgeOpt, err := challengeManager.GetEdge(ctx, edgeId) + if err != nil { + return err + } + if edgeOpt.IsNone() { + return errors.New("no edge found") + } + edge := edgeOpt.Unwrap() + challengeParentAssertionHash, err := edge.AssertionHash(ctx) + if err != nil { + return err + } + + if !w.allowTrackingEdgeWithChallengeParentAssertionHash(challengeParentAssertionHash) { + return nil + } + + // If an edge does not have a claim ID, it is not a level zero edge, and thus + // we can return early, as the following operations only operate on level zero + // edges. + if edge.ClaimId().IsNone() { + return nil + } + + claimId := edge.ClaimId().Unwrap() + chal, ok := w.challenges.TryGet(challengeParentAssertionHash) + if !ok { + return nil + } + + challengeComplete, err := w.chain.IsChallengeComplete(ctx, challengeParentAssertionHash) + if err != nil { + return errors.Wrapf( + err, + "could not check if edge with parent assertion hash %#x is part of a completed challenge", + challengeParentAssertionHash.Hash, + ) + } + if challengeComplete { + return nil + } + + // Check if we should confirm the assertion by challenge winner. + challengeLevel := edge.GetChallengeLevel() + if challengeLevel == protocol.NewBlockChallengeLevel() { + claimedAssertion := protocol.AssertionHash{Hash: common.Hash(claimId)} + w.LaunchThread(func(ctx context.Context) { + w.confirmAssertionByChallengeWinner(ctx, edge, claimedAssertion, challengeParentAssertionHash) + }) + } + + chal.confirmedLevelZeroEdgeClaimIds.Put(claimId, edge.Id()) + w.challenges.Put(challengeParentAssertionHash, chal) + return nil +} + +func (w *Watcher) confirmAssertionByChallengeWinner(ctx context.Context, edge protocol.SpecEdge, claimedAssertion protocol.AssertionHash, challengeParentAssertionHash protocol.AssertionHash) { + edgeConfirmedAtBlock, err := retry.UntilSucceeds(ctx, func() (uint64, error) { + return edge.ConfirmedAtBlock(ctx) + }) + if err != nil { + log.Error("Could not get edge confirmed at block", "err", err) + return + } + challengeGracePeriodBlocks, err := retry.UntilSucceeds(ctx, func() (uint64, error) { + return w.chain.RollupUserLogic().ChallengeGracePeriodBlocks(w.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + }) + if err != nil { + log.Error("Could not get challenge grace period blocks", "err", err) + return + } + assertionCreationInfo, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return w.chain.ReadAssertionCreationInfo(ctx, claimedAssertion) + }) + if err != nil { + log.Error("Could not get assertion creation info", "err", err) + return + } + parentCreationInfo, err := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return w.chain.ReadAssertionCreationInfo( + ctx, assertionCreationInfo.ParentAssertionHash, + ) + }) + if err != nil { + log.Error("Could not get parent assertion creation info", "err", err) + return + } + confirmableAtBlock := challengedAssertionConfirmableBlock( + parentCreationInfo, + edgeConfirmedAtBlock, + assertionCreationInfo, + challengeGracePeriodBlocks, + ) + + exceedsMaxMempoolSizeEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "posting this transaction will exceed max mempool size", 0) + gasEstimationEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(10*time.Minute, "gas estimation errored for tx with hash", 0) + + // Compute the number of blocks until we reach the assertion's + // deadline for confirmation. + ticker := time.NewTicker(w.assertionConfirmingInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + confirmed, err := solimpl.TryConfirmingAssertion( + ctx, + claimedAssertion, + confirmableAtBlock, + w.chain, + w.averageTimeForBlockCreation, + option.Some(edge.Id()), + ) + if err != nil { + logLevel := log.Error + logLevel = exceedsMaxMempoolSizeEphemeralErrorHandler.LogLevel(err, logLevel) + logLevel = gasEstimationEphemeralErrorHandler.LogLevel(err, logLevel) + + logLevel("Could not confirm assertion", "err", err, "assertionHash", claimedAssertion) + errorConfirmingAssertionByWinnerCounter.Inc(1) + continue + } + + exceedsMaxMempoolSizeEphemeralErrorHandler.Reset() + gasEstimationEphemeralErrorHandler.Reset() + + if confirmed { + assertionConfirmedCounter.Inc(1) + log.Info("Confirmed assertion by challenge win", "assertionHash", claimedAssertion) + return + } + } + } +} + +func challengedAssertionConfirmableBlock( + parentInfo *protocol.AssertionCreatedInfo, + winningEdgeConfirmationBlock uint64, + info *protocol.AssertionCreatedInfo, + challengeGracePeriodBlocks uint64, +) uint64 { + confirmableAtBlock := info.CreationL1Block + parentInfo.ConfirmPeriodBlocks + if winningEdgeConfirmationBlock+challengeGracePeriodBlocks > confirmableAtBlock { + confirmableAtBlock = winningEdgeConfirmationBlock + challengeGracePeriodBlocks + } + return confirmableAtBlock +} + +type filterRange struct { + startBlockNum uint64 + endBlockNum uint64 +} + +// Gets the start and end block numbers for our filter queries, starting from +// the latest confirmed assertion's block number up to the latest block number. +func (w *Watcher) getStartEndBlockNum(ctx context.Context) (filterRange, error) { + latestConfirmedAssertion, err := retry.UntilSucceeds(ctx, func() (protocol.Assertion, error) { + return w.chain.LatestConfirmed(ctx, w.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + }) + if err != nil { + return filterRange{}, err + } + latestDesiredBlockNum, err := retry.UntilSucceeds(ctx, func() (uint64, error) { + return w.chain.DesiredHeaderU64(ctx) + }) + if err != nil { + return filterRange{}, err + } + latestConfirmedAssertionCreationBlock, err := w.chain.GetAssertionCreationParentBlock(ctx, latestConfirmedAssertion.Id().Hash) + if err != nil { + return filterRange{}, err + } + return filterRange{ + startBlockNum: latestConfirmedAssertionCreationBlock, + endBlockNum: latestDesiredBlockNum, + }, nil +} + +func (w *Watcher) saveEdgeToDB( + ctx context.Context, + edge protocol.SpecEdge, + isRoyal bool, +) error { + if api.IsNil(w.apiDB) { + return nil + } + start, startCommit := edge.StartCommitment() + end, endCommit := edge.EndCommitment() + creation, err := edge.CreatedAtBlock() + if err != nil { + return err + } + var miniStaker common.Address + if edge.MiniStaker().IsSome() { + miniStaker = edge.MiniStaker().Unwrap() + } + assertionHash, err := edge.AssertionHash(ctx) + if err != nil { + return err + } + var claimId common.Hash + if edge.ClaimId().IsSome() { + claimId = common.Hash(edge.ClaimId().Unwrap()) + } + inheritedTimer, err := w.InheritedTimerForEdge(ctx, edge.Id()) + if err != nil { + return err + } + lowerChild, err := edge.LowerChild(ctx) + if err != nil { + return err + } + upperChild, err := edge.UpperChild(ctx) + if err != nil { + return err + } + var lowerChildId, upperChildId common.Hash + var hasChildren bool + if lowerChild.IsSome() { + hasChildren = true + lowerChildId = lowerChild.Unwrap().Hash + } + if upperChild.IsSome() { + hasChildren = true + upperChildId = upperChild.Unwrap().Hash + } + status, err := edge.Status(ctx) + if err != nil { + return err + } + timeUnrivaled, err := edge.TimeUnrivaled(ctx) + if err != nil { + return err + } + hasRival, err := edge.HasRival(ctx) + if err != nil { + return err + } + hasLengthOneRival, err := edge.HasLengthOneRival(ctx) + if err != nil { + return err + } + inherited := inheritedTimer + if inherited == math.MaxUint64 { + inherited = (1 << 63) - 1 + } + cumulative := inheritedTimer + if cumulative == math.MaxUint64 { + cumulative = (1 << 63) - 1 + } + return w.apiDB.InsertEdge(&api.JsonEdge{ + Id: edge.Id().Hash, + ChallengeLevel: uint8(edge.GetChallengeLevel()), + StartHistoryRoot: startCommit, + StartHeight: uint64(start), + EndHistoryRoot: endCommit, + EndHeight: uint64(end), + CreatedAtBlock: creation, + MutualId: common.Hash(edge.MutualId()), + OriginId: common.Hash(edge.OriginId()), + ClaimId: claimId, + MiniStaker: miniStaker, + AssertionHash: assertionHash.Hash, + Status: status.String(), + LowerChildId: lowerChildId, + UpperChildId: upperChildId, + HasChildren: hasChildren, + IsRoyal: isRoyal, + InheritedTimer: uint64(inherited), + CumulativePathTimer: uint64(cumulative), + TimeUnrivaled: timeUnrivaled, + HasRival: hasRival, + HasLengthOneRival: hasLengthOneRival, + }) +} diff --git a/bold/challenge-manager/chain-watcher/watcher_test.go b/bold/challenge-manager/chain-watcher/watcher_test.go new file mode 100644 index 0000000000..6689501452 --- /dev/null +++ b/bold/challenge-manager/chain-watcher/watcher_test.go @@ -0,0 +1,313 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package watcher + +import ( + "context" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/testing/mocks" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" +) + +func simpleAssertionMetadata() *l2stateprovider.AssociatedAssertionMetadata { + return &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: common.Hash{}, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 0, + }, + BatchLimit: 1, + } +} + +func Test_challengedAssertionConfirmableBlock(t *testing.T) { + t.Run("assertion confirm period has not yet passed", func(t *testing.T) { + parentInfo := &protocol.AssertionCreatedInfo{ + ConfirmPeriodBlocks: 50, + } + info := &protocol.AssertionCreatedInfo{ + CreationParentBlock: 100, + CreationL1Block: 100, // in case of l2 chain CreationL1Block is equal to CreationParentBlock + } + edgeConfirmationBlock := uint64(200) + gracePeriodBlocks := uint64(10) + want := edgeConfirmationBlock + gracePeriodBlocks + got := challengedAssertionConfirmableBlock(parentInfo, edgeConfirmationBlock, info, gracePeriodBlocks) + require.Equal(t, want, got) + }) + t.Run("assertion confirm period has passed", func(t *testing.T) { + parentInfo := &protocol.AssertionCreatedInfo{ + ConfirmPeriodBlocks: 50, + } + info := &protocol.AssertionCreatedInfo{ + CreationParentBlock: 100, + CreationL1Block: 100, // in case of l2 chain CreationL1Block is equal to CreationParentBlock + } + edgeConfirmationBlock := uint64(105) + gracePeriodBlocks := uint64(10) + want := parentInfo.ConfirmPeriodBlocks + info.CreationL1Block + got := challengedAssertionConfirmableBlock(parentInfo, edgeConfirmationBlock, info, gracePeriodBlocks) + require.Equal(t, want, got) + }) +} + +func TestWatcher_processEdgeConfirmation(t *testing.T) { + ctx := context.Background() + mockChain := &mocks.MockProtocol{} + mockChallengeManager := &mocks.MockSpecChallengeManager{} + mockChain.On( + "SpecChallengeManager", + ).Return(mockChallengeManager, nil) + + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash([]byte("foo"))} + mockChain.On( + "IsChallengeComplete", + ctx, + assertionHash, + ).Return(false, nil) + edgeId := protocol.EdgeId{Hash: common.BytesToHash([]byte("bar"))} + edge := &mocks.MockSpecEdge{} + + mockChallengeManager.On( + "GetEdge", ctx, edgeId, + ).Return(option.Some(protocol.SpecEdge(edge)), nil) + + edge.On("ClaimId").Return(option.Some(protocol.ClaimId(assertionHash.Hash))) + edge.On("Id").Return(edgeId) + edge.On("GetChallengeLevel").Return(protocol.ChallengeLevel(1), nil) + edge.On( + "AssertionHash", + ctx, + ).Return(assertionHash, nil) + + watcher := &Watcher{ + challenges: threadsafe.NewMap[protocol.AssertionHash, *trackedChallenge](), + chain: mockChain, + } + watcher.challenges.Put(assertionHash, &trackedChallenge{ + confirmedLevelZeroEdgeClaimIds: threadsafe.NewMap[protocol.ClaimId, protocol.EdgeId](), + }) + + err := watcher.processEdgeConfirmation(ctx, edgeId) + require.NoError(t, err) + + chal, ok := watcher.challenges.TryGet(assertionHash) + require.Equal(t, true, ok) + ok = chal.confirmedLevelZeroEdgeClaimIds.Has(protocol.ClaimId(assertionHash.Hash)) + require.Equal(t, true, ok) +} + +func TestWatcher_processEdgeAddedEvent(t *testing.T) { + ctx := context.Background() + mockChain := &mocks.MockProtocol{} + mockChallengeManager := &mocks.MockSpecChallengeManager{} + mockChain.On( + "SpecChallengeManager", + ).Return(mockChallengeManager, nil) + + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash([]byte("foo"))} + parentAssertionHash := protocol.AssertionHash{Hash: common.BytesToHash([]byte("parent foo"))} + edgeId := protocol.EdgeId{Hash: common.BytesToHash([]byte("bar"))} + originId := protocol.OriginId(common.BytesToHash([]byte("origin bar"))) + edge := &mocks.MockSpecEdge{} + edge.On("Status", ctx).Return(protocol.EdgePending, nil) + edge.On("GetTotalChallengeLevels", ctx).Return(uint8(3), nil) + edge.On("HasChildren", ctx).Return(false, nil) + edge.On("MarkAsHonest").Return() + mockHonest := &mocks.MockHonestEdge{MockSpecEdge: edge} + edge.On("AsVerifiedHonest").Return(mockHonest, true) + + mockChain.On( + "IsChallengeComplete", + ctx, + assertionHash, + ).Return(false, nil) + mockChain.On( + "TopLevelAssertion", + ctx, + edgeId, + ).Return(assertionHash, nil) + + info := &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(1), + ParentAssertionHash: parentAssertionHash, + } + mockChain.On( + "ReadAssertionCreationInfo", + ctx, + assertionHash, + ).Return(info, nil) + parentInfo := &protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(1), + } + mockChain.On( + "ReadAssertionCreationInfo", + ctx, + parentAssertionHash, + ).Return(parentInfo, nil) + heights := protocol.OriginHeights{} + mockChain.On( + "TopLevelClaimHeights", + ctx, + edgeId, + ).Return(heights, nil) + + assertionUnrivaledBlocks := uint64(5) + mockChain.On( + "AssertionUnrivaledBlocks", + ctx, + assertionHash, + ).Return(assertionUnrivaledBlocks, nil) + + mockChallengeManager.On( + "GetEdge", ctx, edgeId, + ).Return(option.Some(protocol.SpecEdge(edge)), nil) + + edge.On("Id").Return(edgeId) + edge.On("OriginId").Return(originId) + edge.On("CreatedAtBlock").Return(uint64(0), nil) + edge.On("ClaimId").Return(option.Some(protocol.ClaimId(assertionHash.Hash))) + edge.On("MutualId").Return(protocol.MutualId{}) + edge.On("GetChallengeLevel").Return(protocol.NewBlockChallengeLevel(), nil) + edge.On("GetReversedChallengeLevel").Return(protocol.ChallengeLevel(2), nil) + startCommit := common.BytesToHash([]byte("nyan")) + endCommit := common.BytesToHash([]byte("nyan2")) + edge.On("StartCommitment").Return(protocol.Height(0), startCommit) + edge.On("EndCommitment").Return(protocol.Height(4), endCommit) + edge.On( + "AssertionHash", + ctx, + ).Return(assertionHash, nil) + + mockStateManager := &mocks.MockStateManager{} + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some[l2stateprovider.Height](4), + }, + l2stateprovider.History{ + Height: uint64(0), + MerkleRoot: startCommit, + }, + ).Return(true, nil) + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some[l2stateprovider.Height](4), + }, + l2stateprovider.History{ + Height: uint64(4), + MerkleRoot: endCommit, + }, + ).Return(true, nil) + + mockManager := &mocks.MockEdgeTracker{} + mockManager.On("TrackEdge", ctx, mockHonest).Return(nil) + + watcher := &Watcher{ + challenges: threadsafe.NewMap[protocol.AssertionHash, *trackedChallenge](), + histChecker: mockStateManager, + chain: mockChain, + edgeManager: mockManager, + numBigStepLevels: 1, + } + _, err := watcher.processEdgeAddedEvent(ctx, &challengeV2gen.EdgeChallengeManagerEdgeAdded{ + EdgeId: edgeId.Hash, + OriginId: assertionHash.Hash, + }) + require.NoError(t, err) + + _, ok := watcher.challenges.TryGet(assertionHash) + require.Equal(t, true, ok) +} + +type mockHonestEdge struct { + *mocks.MockSpecEdge +} + +func (m *mockHonestEdge) Honest() {} + +func (m *mockHonestEdge) Bisect( + ctx context.Context, + prefixHistoryRoot common.Hash, + prefixProof []byte, +) (protocol.VerifiedRoyalEdge, protocol.VerifiedRoyalEdge, error) { + return m.MockSpecEdge.Bisect(ctx, prefixHistoryRoot, prefixProof) +} + +func (m *mockHonestEdge) ConfirmByTimer(ctx context.Context, claimedAssertion protocol.AssertionHash) (*types.Transaction, error) { + return m.MockSpecEdge.ConfirmByTimer(ctx, claimedAssertion) +} + +func TestWatcher_AddVerifiedHonestEdge(t *testing.T) { + ctx := context.Background() + mockChain := &mocks.MockProtocol{} + + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash([]byte("foo"))} + edgeId := protocol.EdgeId{Hash: common.BytesToHash([]byte("bar"))} + originId := protocol.OriginId(common.BytesToHash([]byte("origin bar"))) + edge := &mocks.MockSpecEdge{} + + edge.On( + "AssertionHash", + ctx, + ).Return(assertionHash, nil) + edge.On("Status", ctx).Return(protocol.EdgePending, nil) + edge.On("GetTotalChallengeLevels", ctx).Return(uint8(3), nil) + edge.On("HasChildren", ctx).Return(false, nil) + assertionUnrivaledBlocks := uint64(1) + mockChain.On("AssertionUnrivaledBlocks", ctx, assertionHash).Return(assertionUnrivaledBlocks, nil) + + edge.On("Id").Return(edgeId) + edge.On("OriginId").Return(originId) + createdAt := uint64(5) + edge.On("CreatedAtBlock").Return(createdAt, nil) + edge.On("ClaimId").Return(option.Some(protocol.ClaimId(assertionHash.Hash))) + edge.On("OriginId").Return(protocol.OriginId{}) + edge.On("MutualId").Return(protocol.MutualId{}) + edge.On("GetChallengeLevel").Return(protocol.NewBlockChallengeLevel(), nil) + edge.On("GetReversedChallengeLevel").Return(protocol.ChallengeLevel(2), nil) + startCommit := common.BytesToHash([]byte("start")) + endCommit := common.BytesToHash([]byte("start")) + edge.On("StartCommitment").Return(protocol.Height(0), startCommit) + edge.On("EndCommitment").Return(protocol.Height(32), endCommit) + + mockStateManager := &mocks.MockStateManager{} + mockManager := &mocks.MockEdgeTracker{} + honest := &mockHonestEdge{edge} + mockManager.On("TrackEdge", ctx, honest).Return(nil) + + watcher := &Watcher{ + challenges: threadsafe.NewMap[protocol.AssertionHash, *trackedChallenge](), + histChecker: mockStateManager, + chain: mockChain, + edgeManager: mockManager, + numBigStepLevels: 1, + } + + err := watcher.AddVerifiedHonestEdge(ctx, honest) + require.NoError(t, err) + _, ok := watcher.challenges.TryGet(assertionHash) + require.Equal(t, true, ok) +} diff --git a/bold/challenge-manager/challenge-tree/add_edge.go b/bold/challenge-manager/challenge-tree/add_edge.go new file mode 100644 index 0000000000..8f033c7c5e --- /dev/null +++ b/bold/challenge-manager/challenge-tree/add_edge.go @@ -0,0 +1,222 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "fmt" + "strings" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" +) + +// AddRoyalEdge known to be honest, such as those created by the local validator. +func (ht *RoyalChallengeTree) AddRoyalEdge(eg protocol.VerifiedRoyalEdge) error { + id := eg.Id() + if _, ok := ht.edges.TryGet(id); ok { + // Already being tracked. + return nil + } + if err := ht.keepTrackOfCreationTime(eg); err != nil { + return err + } + ht.keepTrackOfHonestEdge(eg) + return nil +} + +// AddEdge to the honest challenge tree. Only honest edges are tracked, but we also keep track +// of rival ids in a mutual ids mapping internally for extra book-keeping. +func (ht *RoyalChallengeTree) AddEdge(ctx context.Context, eg protocol.SpecEdge) error { + edgeId := eg.Id() + + // Check if edge is already being tracked. + if _, ok := ht.edges.TryGet(edgeId); ok { + return ErrAlreadyBeingTracked + } + // Check if assertion hash is correct. + if err := ht.checkAssertionHash(ctx, edgeId); err != nil { + return errors.Wrapf(err, "could not check if the edge's assertion hash is correct %#x", edgeId) + } + if err := ht.keepTrackOfCreationTime(eg); err != nil { + return errors.Wrapf(err, "could not track mutual id: %#x", edgeId) + } + hasHonestAncestry, err := ht.hasHonestAncestry(ctx, eg) + if err != nil { + return errors.Wrapf(err, "could not check if edge has honest ancestors: %#x", edgeId) + } + if !hasHonestAncestry { + return nil + } + claimedAssertionHash, err := ht.claimedAssertionHash(ctx, eg) + if err != nil { + return errors.Wrapf(err, "could not fetch claimed assertion hash for edge: %#x", edgeId) + } + historyCommitRequest, err := ht.prepareHistoryCommitmentRequest(ctx, eg, claimedAssertionHash) + if err != nil { + return errors.Wrapf(err, "could not prepare history commitment request for edge: %#x", edgeId) + } + endHeight, endCommit := eg.EndCommitment() + challengeLevel := eg.GetChallengeLevel() + isHonestEdge, err := ht.histChecker.AgreesWithHistoryCommitment( + ctx, + challengeLevel, + historyCommitRequest, + l2stateprovider.History{ + Height: uint64(endHeight), + MerkleRoot: endCommit, + }, + ) + if err != nil { + if strings.Contains(err.Error(), "accumulator not found") { + return errors.New("validator is still syncing the chain, will retry later") + } + return errors.Wrapf(err, "could not check history commitment agreement for edge: %#x", edgeId) + } + // Edges are royal if they have an honest ancestry and are also honest from our perspective. + isRoyal := hasHonestAncestry && isHonestEdge + if isRoyal { + eg.MarkAsHonest() + verifiedHonest, _ := eg.AsVerifiedHonest() + ht.keepTrackOfHonestEdge(verifiedHonest) + } + return nil +} + +func (ht *RoyalChallengeTree) checkAssertionHash(ctx context.Context, id protocol.EdgeId) error { + assertionHash, err := ht.metadataReader.TopLevelAssertion(ctx, id) + if err != nil { + return errors.Wrapf(err, "could not get top level assertion for edge %#x", id) + } + if ht.topLevelAssertionHash != assertionHash { + // This edge should not be part of this challenge tree. + return ErrMismatchedChallengeAssertionHash + } + return nil +} + +func (ht *RoyalChallengeTree) claimedAssertionHash(_ context.Context, eg protocol.SpecEdge) (protocol.AssertionHash, error) { + challengeLevel := eg.GetChallengeLevel() + // If this is a root challenge level zero edge. + if challengeLevel == protocol.NewBlockChallengeLevel() && !eg.ClaimId().IsNone() { + return protocol.AssertionHash{Hash: common.Hash(eg.ClaimId().Unwrap())}, nil + } + honestLevelZeroEdge, honestErr := ht.RoyalBlockChallengeRootEdge() + if honestErr != nil { + return protocol.AssertionHash{}, honestErr + } + if honestLevelZeroEdge.ClaimId().IsNone() { + return protocol.AssertionHash{}, errors.New("honest level zero edge has no claim id") + } + return protocol.AssertionHash{Hash: common.Hash(honestLevelZeroEdge.ClaimId().Unwrap())}, nil +} + +func (ht *RoyalChallengeTree) prepareHistoryCommitmentRequest( + ctx context.Context, + eg protocol.SpecEdge, + claimedAssertionHash protocol.AssertionHash, +) (*l2stateprovider.HistoryCommitmentRequest, error) { + // We get the batch range for the claimed assertion of the edge which is needed to compute + // history commitments. We need to figure out from what batch to what batch the assertion + // is claiming its data for. + creationInfo, err := ht.metadataReader.ReadAssertionCreationInfo(ctx, claimedAssertionHash) + if err != nil { + return nil, err + } + parentCreationInfo, err := ht.metadataReader.ReadAssertionCreationInfo(ctx, creationInfo.ParentAssertionHash) + if err != nil { + return nil, err + } + if parentCreationInfo.InboxMaxCount == nil { + return nil, errors.New("parentCreationInfo.InboxMaxCount is nil") + } + if !parentCreationInfo.InboxMaxCount.IsUint64() { + return nil, fmt.Errorf("inbox max count is not a uint64: %v", parentCreationInfo.InboxMaxCount) + } + challengeLevel := eg.GetChallengeLevel() + fromState := protocol.GoGlobalStateFromSolidity(creationInfo.BeforeState.GlobalState) + assertionMetadata := &l2stateprovider.AssociatedAssertionMetadata{ + FromState: fromState, + BatchLimit: l2stateprovider.Batch(parentCreationInfo.InboxMaxCount.Uint64()), + WasmModuleRoot: parentCreationInfo.WasmModuleRoot, + ClaimedAssertionHash: creationInfo.AssertionHash, + } + endHeight, _ := eg.EndCommitment() + heights, err := ht.metadataReader.TopLevelClaimHeights(ctx, eg.Id()) + if err != nil { + return nil, errors.Wrapf(err, "could not get claim heights for edge %#x", eg.Id()) + } + startHeights := make([]l2stateprovider.Height, len(heights.ChallengeOriginHeights)) + for i, h := range heights.ChallengeOriginHeights { + startHeights[i] = l2stateprovider.Height(h) + } + if challengeLevel == protocol.NewBlockChallengeLevel() { + return &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: make([]l2stateprovider.Height, 0), + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, nil + } + + if len(startHeights) == 0 { + return nil, errors.New("start height cannot be zero") + } + return &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: startHeights, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, nil +} + +// Check if the edge id should be added to the rivaled edges set. +// Here we only care about edges here that are either honest or those whose start +// history commitments we agree with. +func (ht *RoyalChallengeTree) keepTrackOfCreationTime(eg protocol.SpecEdge) error { + key := buildEdgeCreationTimeKey(eg.OriginId(), eg.MutualId()) + mutuals := ht.edgeCreationTimes.Get(key) + if mutuals == nil { + ht.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = ht.edgeCreationTimes.Get(key) + } + createdAtBlock, err := eg.CreatedAtBlock() + if err != nil { + return err + } + mutuals.Put(eg.Id(), creationTime(createdAtBlock)) + ht.edgeCreationTimes.Put(key, mutuals) + return nil +} + +// If we agree with the edge, we add it to our edges mapping and if it is level zero, +// we keep track of it specifically in our struct. +func (ht *RoyalChallengeTree) keepTrackOfHonestEdge(eg protocol.VerifiedRoyalEdge) { + id := eg.Id() + ht.edges.Put(id, eg) + if eg.ClaimId().IsNone() { + return + } + reversedChallengeLevel := eg.GetReversedChallengeLevel() + rootEdgesAtLevel, ok := ht.royalRootEdgesByLevel.TryGet(reversedChallengeLevel) + if !ok || rootEdgesAtLevel == nil { + honestRootEdges := threadsafe.NewSlice[protocol.SpecEdge]() + honestRootEdges.Push(eg) + ht.royalRootEdgesByLevel.Put(reversedChallengeLevel, honestRootEdges) + } else { + // If the edge is already being tracked, we do not add it again. + if rootEdgesAtLevel.Find(func(_ int, e protocol.SpecEdge) bool { + return e.Id() == id + }) { + return + } + rootEdgesAtLevel.Push(eg) + ht.royalRootEdgesByLevel.Put(reversedChallengeLevel, rootEdgesAtLevel) + } +} diff --git a/bold/challenge-manager/challenge-tree/ancestors.go b/bold/challenge-manager/challenge-tree/ancestors.go new file mode 100644 index 0000000000..58afb85329 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/ancestors.go @@ -0,0 +1,283 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/threadsafe" + bisection "github.com/offchainlabs/bold/math" +) + +var ( + ErrNoHonestTopLevelEdge = errors.New("no honest block challenge edge being tracked") + ErrNotFound = errors.New("not found in honest challenge tree") + ErrNoLevelZero = errors.New("no level zero edge with origin id found") + ErrNoLowerChildYet = errors.New("edge does not yet have a lower child") +) + +// PathTimer for an honest edge defined as the cumulative unrivaled time +// of it and its honest ancestors all the way up to the assertion chain level. +// This also includes the time the assertion, which the challenge corresponds to, +// has been unrivaled. +type PathTimer uint64 + +// HonestAncestors of an edge id all the way up to and including the +// block challenge level zero edge. +type HonestAncestors []protocol.ReadOnlyEdge + +// EdgeLocalTimer is the local, unrivaled timer of a specific edge. +type EdgeLocalTimer uint64 + +// ComputeAncestors gathers all royal ancestors of a given edge across challenge levels. +// Ancestor lists are linked through challenge levels via claimed edges. It is generalized +// to any number of challenge levels in the protocol. +func (ht *RoyalChallengeTree) ComputeAncestors( + ctx context.Context, + edgeId protocol.EdgeId, + blockNumber uint64, +) (HonestAncestors, error) { + // Checks if the edge exists before performing any computation. + startEdge, ok := ht.edges.TryGet(edgeId) + if !ok { + return nil, errNotFound(edgeId) + } + currentChallengeLevel := startEdge.GetReversedChallengeLevel() + + // Set a cursor at the edge we start from. We will update this cursor + // as we advance in this function. + currentEdge := startEdge + + ancestry := make([]protocol.ReadOnlyEdge, 0) + + // Challenge levels go from lowest to highest, where lowest is the smallest challenge level + // (where challenges are over individual, WASM opcodes). If we have 3 challenge levels, + // we will go from 0, 1, 2. We want the ancestors for an edge across an entire challenge + // tree, even across levels. + for currentChallengeLevel < protocol.ChallengeLevel(ht.totalChallengeLevels) { + // Compute the root edge for the current challenge level. + rootEdge, err := ht.honestRootAncestorAtChallengeLevel(currentEdge, currentChallengeLevel) + if err != nil { + return nil, err + } + + // Compute the ancestors for the current edge in the current challenge level. + ancestorLocalTimers, ancestorsAtLevel, err := ht.findHonestAncestorsWithinChallengeLevel( + ctx, rootEdge, currentEdge, blockNumber, + ) + if err != nil { + return nil, err + } + + // Expand the total ancestry and timers slices. We want ancestors from + // the bottom-up, so we must reverse the output slice from the find function. + containers.Reverse(ancestorLocalTimers) + containers.Reverse(ancestorsAtLevel) + ancestry = append(ancestry, ancestorsAtLevel...) + + // Advance the challenge level. + currentChallengeLevel += 1 + + if currentChallengeLevel == protocol.ChallengeLevel(ht.totalChallengeLevels) { + break + } + + // Update the current edge to the one the root edge at this challenge claims + // at the next challenge level to link between levels. + nextLevelClaimedEdge, err := ht.getClaimedEdge(rootEdge) + if err != nil { + return nil, err + } + // Update the cursor to be the claimed edge at the next challenge level. + currentEdge = nextLevelClaimedEdge + + // Include the next level claimed edge in the ancestry list. + ancestry = append(ancestry, nextLevelClaimedEdge) + } + return ancestry, nil +} + +// ClosestEssentialAncestor of a child edge. If the edge is a block challenge edge, this is the root of +// the block challenge. Otherwise, it is the root of the subchallenge the edge is in. +func (ht *RoyalChallengeTree) ClosestEssentialAncestor( + ctx context.Context, child protocol.ReadOnlyEdge, +) (protocol.ReadOnlyEdge, error) { + // If the edge is already essential, return itself. + if child.ClaimId().IsSome() { + return child, nil + } + if child.GetChallengeLevel().IsBlockChallengeLevel() { + return ht.RoyalBlockChallengeRootEdge() + } + childOrigin := child.OriginId() + var claimedEdge protocol.SpecEdge + var ok bool + _ = ht.edges.ForEach(func(_ protocol.EdgeId, edge protocol.SpecEdge) error { + if edge.MutualId() == protocol.MutualId(childOrigin) { + claimedEdge = edge + ok = true + } + return nil + }) + if !ok { + return nil, errors.New("no edge corresponding to origin id found") + } + isClaimedEdge, claimingEdge := ht.isClaimedEdge(ctx, claimedEdge) + if !isClaimedEdge { + return nil, errors.New("edge is not claimed") + } + return claimingEdge, nil +} + +// Computes the list of ancestors in a challenge level from a root edge down +// to a specified child edge within the same level. The edge we are querying must be +// a child of this start edge for this function to succeed without error. +func (ht *RoyalChallengeTree) findHonestAncestorsWithinChallengeLevel( + ctx context.Context, + rootEdge protocol.ReadOnlyEdge, + queryingFor protocol.ReadOnlyEdge, + blockNumber uint64, +) ([]EdgeLocalTimer, []protocol.ReadOnlyEdge, error) { + found := false + cursor := rootEdge + ancestry := make([]protocol.ReadOnlyEdge, 0) + localTimers := make([]EdgeLocalTimer, 0) + wantedEdgeStart, _ := queryingFor.StartCommitment() + + for { + if ctx.Err() != nil { + return nil, nil, ctx.Err() + } + if cursor.Id() == queryingFor.Id() { + found = true + break + } + // We expand the ancestry and timers' slices using the cursor edge. + ancestry = append(ancestry, cursor) + timer, err := ht.LocalTimer(ctx, cursor, blockNumber) + if err != nil { + return nil, nil, err + } + localTimers = append(localTimers, EdgeLocalTimer(timer)) + + currStart, _ := cursor.StartCommitment() + currEnd, _ := cursor.EndCommitment() + bisectTo, err := bisection.Bisect(uint64(currStart), uint64(currEnd)) + if err != nil { + return nil, nil, errors.Wrapf(err, "could not bisect start=%d, end=%d", currStart, currEnd) + } + // If the wanted edge's start commitment is less than the bisection height of the current + // edge in the loop, it means it is part of its lower children. + if uint64(wantedEdgeStart) < bisectTo { + lowerChild, lowerErr := cursor.LowerChild(ctx) + if lowerErr != nil { + return nil, nil, errors.Wrapf(lowerErr, "could not get lower child for edge %#x", cursor.Id()) + } + if lowerChild.IsNone() { + return nil, nil, errNotFound(queryingFor.Id()) + } + cursor = ht.edges.Get(lowerChild.Unwrap()) + } else { + // Else, it is part of the upper children. + upperChild, upperErr := cursor.UpperChild(ctx) + if upperErr != nil { + return nil, nil, errors.Wrapf(upperErr, "could not get upper child for edge %#x", cursor.Id()) + } + if upperChild.IsNone() { + return nil, nil, errNotFound(queryingFor.Id()) + } + cursor = ht.edges.Get(upperChild.Unwrap()) + } + } + if !found { + return nil, nil, errNotFound(queryingFor.Id()) + } + return localTimers, ancestry, nil +} + +func (ht *RoyalChallengeTree) hasHonestAncestry(ctx context.Context, eg protocol.SpecEdge) (bool, error) { + chalLevel := eg.GetChallengeLevel() + claimId := eg.ClaimId() + + // If the edge is a root edge at the block challenge level, then we return true early. + if chalLevel == protocol.NewBlockChallengeLevel() && claimId.IsSome() { + return true, nil + } + ancestry, err := ht.ComputeAncestors( + ctx, + eg.Id(), + 0, /* block num (unimportant here) */ + ) + if err != nil { + // If the edge we were looking for had no direct ancestry links, we return false. + if errors.Is(err, ErrNotFound) { + return false, nil + } + // Otherwise, we received a real error in the computation. + return false, err + } + return len(ancestry) > 0, nil +} + +// Computes the root edge for a given child edge at a challenge level. +// In a challenge that looks like this: +// +// /--5---6-----8-----------16A = Alice +// 0-----4 +// \--5'--6'----8'----------16B = Bob +// +// where Alice is the honest party, edge 0-16A is the honest root edge. +func (ht *RoyalChallengeTree) honestRootAncestorAtChallengeLevel( + childEdge protocol.ReadOnlyEdge, + challengeLevel protocol.ChallengeLevel, +) (protocol.ReadOnlyEdge, error) { + originId := childEdge.OriginId() + // // Otherwise, finds the honest root edge at the appropriate challenge level. + rootEdgesAtLevel, ok := ht.royalRootEdgesByLevel.TryGet(challengeLevel) + if !ok || rootEdgesAtLevel == nil { + return nil, fmt.Errorf("no honest edges found at challenge level %d", challengeLevel) + } + rootAncestor, found := findOriginEdge(originId, rootEdgesAtLevel) + if !found { + return nil, fmt.Errorf("no honest root edge with origin id %#x found at challenge level %d", originId, challengeLevel) + } + return rootAncestor, nil +} + +// Gets the edge a specified edge claims, if any. +func (ht *RoyalChallengeTree) getClaimedEdge(edge protocol.ReadOnlyEdge) (protocol.SpecEdge, error) { + if edge.ClaimId().IsNone() { + return nil, errors.New("does not claim any edge") + } + claimId := edge.ClaimId().Unwrap() + claimIdHash := [32]byte(claimId) + claimedBlockEdge, ok := ht.edges.TryGet(protocol.EdgeId{Hash: claimIdHash}) + if !ok { + return nil, errors.New("claimed edge not found") + } + return claimedBlockEdge, nil +} + +// Finds an edge in a list with a specified origin id. +func findOriginEdge(originId protocol.OriginId, edges *threadsafe.Slice[protocol.SpecEdge]) (protocol.SpecEdge, bool) { + var originEdge protocol.SpecEdge + found := edges.Find(func(_ int, e protocol.SpecEdge) bool { + if e.OriginId() == originId { + originEdge = e + return true + } + return false + }) + return originEdge, found +} + +func errNotFound(id protocol.EdgeId) error { + return errors.Wrapf(ErrNotFound, "id=%#x", id) +} diff --git a/bold/challenge-manager/challenge-tree/ancestors_test.go b/bold/challenge-manager/challenge-tree/ancestors_test.go new file mode 100644 index 0000000000..62bb441013 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/ancestors_test.go @@ -0,0 +1,498 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/challenge-manager/challenge-tree/mock" + "github.com/offchainlabs/bold/containers/threadsafe" +) + +func TestClosestEssentialAncestor(t *testing.T) { + ctx := context.Background() + tree := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + metadataReader: &mockMetadataReader{}, + totalChallengeLevels: 3, + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](), + } + tree.royalRootEdgesByLevel.Put(2, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(1, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(0, threadsafe.NewSlice[protocol.SpecEdge]()) + + // Edge ids that belong to block challenges are prefixed with "blk". + // For big step, prefixed with "big", and small step, prefixed with "smol". + setupBlockChallengeTreeSnapshot(t, tree, "ass.a") + blockRootEdges := tree.royalRootEdgesByLevel.Get(2 /* big step level */) + blockRootEdges.Push(tree.edges.Get(id("blk-0.a-16.a"))) + claimId := "blk-4.a-5.a" + setupBigStepChallengeSnapshot(t, tree, claimId) + bigStepRootEdges := tree.royalRootEdgesByLevel.Get(1 /* big step level */) + bigStepRootEdges.Push(tree.edges.Get(id("big-0.a-16.a"))) + claimId = "big-4.a-5.a" + setupSmallStepChallengeSnapshot(t, tree, claimId) + smallStepRootEdges := tree.royalRootEdgesByLevel.Get(0 /* small step level */) + smallStepRootEdges.Push(tree.edges.Get(id("smol-0.a-16.a"))) + + t.Run("essential edge is its own closest essential ancestor", func(t *testing.T) { + edge := tree.edges.Get(id("big-0.a-16.a")) + ancestor, err := tree.ClosestEssentialAncestor(ctx, edge) + require.NoError(t, err) + require.Equal(t, id("big-0.a-16.a"), ancestor.Id()) + }) + t.Run("block challenge level", func(t *testing.T) { + edge := tree.edges.Get(id("blk-5.a-6.a")) + ancestor, err := tree.ClosestEssentialAncestor(ctx, edge) + require.NoError(t, err) + require.Equal(t, id("blk-0.a-16.a"), ancestor.Id()) + }) + t.Run("big step subchallenge edge", func(t *testing.T) { + edge := tree.edges.Get(id("big-6.a-8.a")) + ancestor, err := tree.ClosestEssentialAncestor(ctx, edge) + require.NoError(t, err) + require.Equal(t, id("big-0.a-16.a"), ancestor.Id()) + }) + t.Run("small step subchallenge edge", func(t *testing.T) { + edge := tree.edges.Get(id("smol-6.a-8.a")) + ancestor, err := tree.ClosestEssentialAncestor(ctx, edge) + require.NoError(t, err) + require.Equal(t, id("smol-0.a-16.a"), ancestor.Id()) + }) +} + +func Test_findOriginEdge(t *testing.T) { + edges := threadsafe.NewSlice[protocol.SpecEdge]() + origin := protocol.OriginId(common.BytesToHash([]byte("foo"))) + _, ok := findOriginEdge(origin, edges) + require.Equal(t, false, ok) + edges.Push(newEdge(&newCfg{ + t: t, + originId: "bar", + edgeId: "blk-0.a-4.a", + claimId: "", + createdAt: 2, + })) + + _, ok = findOriginEdge(origin, edges) + require.Equal(t, false, ok) + + origin = protocol.OriginId(common.BytesToHash([]byte("bar"))) + got, ok := findOriginEdge(origin, edges) + require.Equal(t, true, ok) + require.Equal(t, got.Id(), protocol.EdgeId{Hash: common.BytesToHash([]byte("blk-0.a-4.a"))}) + + edges.Push(newEdge(&newCfg{ + t: t, + originId: "baz", + edgeId: "blk-0.b-4.b", + claimId: "", + createdAt: 2, + })) + + origin = protocol.OriginId(common.BytesToHash([]byte("baz"))) + got, ok = findOriginEdge(origin, edges) + require.Equal(t, true, ok) + require.Equal(t, got.Id(), protocol.EdgeId{Hash: common.BytesToHash([]byte("blk-0.b-4.b"))}) +} + +func buildEdges(allEdges ...*mock.Edge) map[mock.EdgeId]*mock.Edge { + m := make(map[mock.EdgeId]*mock.Edge) + for _, e := range allEdges { + m[e.ID] = e + } + return m +} + +// Sets up the following block challenge snapshot: +// +// /--5---6-----8-----------16 = Alice +// 0-----4 +// \--5'--6'----8'----------16' = Bob +// +// and then inserts the respective edges into a challenge tree. +func setupBlockChallengeTreeSnapshot(t *testing.T, tree *RoyalChallengeTree, claimId string) { + t.Helper() + aliceEdges := buildEdges( + // Alice. + newEdge(&newCfg{t: t, edgeId: "blk-0.a-16.a", claimId: claimId, createdAt: 1}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-8.a", createdAt: 3}), + newEdge(&newCfg{t: t, edgeId: "blk-8.a-16.a", createdAt: 3}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-4.a", createdAt: 5}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-8.a", createdAt: 5}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-6.a", createdAt: 7}), + newEdge(&newCfg{t: t, edgeId: "blk-6.a-8.a", createdAt: 7}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-5.a", createdAt: 9}), + newEdge(&newCfg{t: t, edgeId: "blk-5.a-6.a", createdAt: 9}), + ) + bobEdges := buildEdges( + // Bob. + newEdge(&newCfg{t: t, edgeId: "blk-0.a-16.b", createdAt: 2}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-8.b", createdAt: 4}), + newEdge(&newCfg{t: t, edgeId: "blk-8.b-16.b", createdAt: 4}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-8.b", createdAt: 6}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-6.b", createdAt: 6}), + newEdge(&newCfg{t: t, edgeId: "blk-6.b-8.b", createdAt: 8}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-5.b", createdAt: 10}), + newEdge(&newCfg{t: t, edgeId: "blk-5.b-6.b", createdAt: 10}), + ) + // Child-relationship linking. + // Alice. + aliceEdges["blk-0.a-16.a"].LowerChildID = "blk-0.a-8.a" + aliceEdges["blk-0.a-16.a"].UpperChildID = "blk-8.a-16.a" + aliceEdges["blk-0.a-8.a"].LowerChildID = "blk-0.a-4.a" + aliceEdges["blk-0.a-8.a"].UpperChildID = "blk-4.a-8.a" + aliceEdges["blk-4.a-8.a"].LowerChildID = "blk-4.a-6.a" + aliceEdges["blk-4.a-8.a"].UpperChildID = "blk-6.a-8.a" + aliceEdges["blk-4.a-6.a"].LowerChildID = "blk-4.a-5.a" + aliceEdges["blk-4.a-6.a"].UpperChildID = "blk-5.a-6.a" + // Bob. + bobEdges["blk-0.a-16.b"].LowerChildID = "blk-0.a-8.b" + bobEdges["blk-0.a-16.b"].UpperChildID = "blk-8.b-16.b" + bobEdges["blk-0.a-8.b"].LowerChildID = "blk-0.a-4.a" + bobEdges["blk-0.a-8.b"].UpperChildID = "blk-4.a-8.b" + bobEdges["blk-4.a-8.b"].LowerChildID = "blk-4.a-6.b" + bobEdges["blk-4.a-8.b"].UpperChildID = "blk-6.b-6.8" + bobEdges["blk-4.a-6.b"].LowerChildID = "blk-4.a-5.b" + bobEdges["blk-4.a-6.b"].UpperChildID = "blk-5.b-6.b" + + transformedEdges := make(map[protocol.EdgeId]protocol.SpecEdge) + for _, v := range aliceEdges { + transformedEdges[v.Id()] = v + } + allEdges := threadsafe.NewMapFromItems(transformedEdges) + tree.edges = allEdges + + // Set up rivaled edges. + mutual := aliceEdges["blk-0.a-16.a"].MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := tree.edgeCreationTimes.Get(key) + a := aliceEdges["blk-0.a-16.a"] + b := bobEdges["blk-0.a-16.b"] + aCreation, err := a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err := b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["blk-0.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["blk-0.a-8.a"] + b = bobEdges["blk-0.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["blk-4.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["blk-4.a-8.a"] + b = bobEdges["blk-4.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["blk-4.a-6.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["blk-4.a-5.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["blk-4.a-5.a"] + b = bobEdges["blk-4.a-5.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) +} + +func id(eId mock.EdgeId) protocol.EdgeId { + return protocol.EdgeId{Hash: common.BytesToHash([]byte(eId))} +} + +// Sets up the following big step challenge snapshot: +// +// /--5---6-----8-----------16 = Alice +// 0-----4 +// \--5'--6'----8'----------16' = Bob +// +// and then inserts the respective edges into a challenge tree. +func setupBigStepChallengeSnapshot(t *testing.T, tree *RoyalChallengeTree, claimId string) { + t.Helper() + originEdge, ok := tree.edges.Get(id(mock.EdgeId(claimId))).(*mock.Edge) + require.Equal(t, true, ok) + origin := mock.OriginId(originEdge.ComputeMutualId()) + originId := protocol.OriginId(common.BytesToHash([]byte(originEdge.ComputeMutualId()))) + aliceEdges := buildEdges( + // Alice. + newEdge(&newCfg{t: t, edgeId: "big-0.a-16.a", originId: origin, claimId: claimId, createdAt: 11}), + newEdge(&newCfg{t: t, edgeId: "big-0.a-8.a", originId: origin, createdAt: 13}), + newEdge(&newCfg{t: t, edgeId: "big-8.a-16.a", originId: origin, createdAt: 13}), + newEdge(&newCfg{t: t, edgeId: "big-0.a-4.a", originId: origin, createdAt: 15}), + newEdge(&newCfg{t: t, edgeId: "big-4.a-8.a", originId: origin, createdAt: 15}), + newEdge(&newCfg{t: t, edgeId: "big-4.a-6.a", originId: origin, createdAt: 17}), + newEdge(&newCfg{t: t, edgeId: "big-6.a-8.a", originId: origin, createdAt: 17}), + newEdge(&newCfg{t: t, edgeId: "big-4.a-5.a", originId: origin, createdAt: 19}), + newEdge(&newCfg{t: t, edgeId: "big-5.a-6.a", originId: origin, createdAt: 19}), + ) + bobEdges := buildEdges( + // Bob. + newEdge(&newCfg{t: t, edgeId: "big-0.a-16.b", originId: origin, createdAt: 12}), + newEdge(&newCfg{t: t, edgeId: "big-0.a-8.b", originId: origin, createdAt: 14}), + newEdge(&newCfg{t: t, edgeId: "big-8.b-16.b", originId: origin, createdAt: 14}), + newEdge(&newCfg{t: t, edgeId: "big-4.a-8.b", originId: origin, createdAt: 16}), + newEdge(&newCfg{t: t, edgeId: "big-4.a-6.b", originId: origin, createdAt: 18}), + newEdge(&newCfg{t: t, edgeId: "big-6.b-8.b", originId: origin, createdAt: 18}), + newEdge(&newCfg{t: t, edgeId: "big-4.a-5.b", originId: origin, createdAt: 20}), + newEdge(&newCfg{t: t, edgeId: "big-5.b-6.b", originId: origin, createdAt: 20}), + ) + // Child-relationship linking. + // Alice. + aliceEdges["big-0.a-16.a"].LowerChildID = "big-0.a-8.a" + aliceEdges["big-0.a-16.a"].UpperChildID = "big-8.a-16.a" + aliceEdges["big-0.a-8.a"].LowerChildID = "big-0.a-4.a" + aliceEdges["big-0.a-8.a"].UpperChildID = "big-4.a-8.a" + aliceEdges["big-4.a-8.a"].LowerChildID = "big-4.a-6.a" + aliceEdges["big-4.a-8.a"].UpperChildID = "big-6.a-8.a" + aliceEdges["big-4.a-6.a"].LowerChildID = "big-4.a-5.a" + aliceEdges["big-4.a-6.a"].UpperChildID = "big-5.a-6.a" + // Bob. + bobEdges["big-0.a-16.b"].LowerChildID = "big-0.a-8.b" + bobEdges["big-0.a-16.b"].UpperChildID = "big-8.b-16.b" + bobEdges["big-0.a-8.b"].LowerChildID = "big-0.a-4.a" + bobEdges["big-0.a-8.b"].UpperChildID = "big-4.a-8.b" + bobEdges["big-4.a-8.b"].LowerChildID = "big-4.a-6.b" + bobEdges["big-4.a-8.b"].UpperChildID = "big-6.b-6.8" + bobEdges["big-4.a-6.b"].LowerChildID = "big-4.a-5.b" + bobEdges["big-4.a-6.b"].UpperChildID = "big-5.b-6.b" + + for _, v := range aliceEdges { + tree.edges.Put(v.Id(), v) + } + + // Set up rivaled edges. + mutual := aliceEdges["big-0.a-16.a"].MutualId() + key := buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := tree.edgeCreationTimes.Get(key) + a := aliceEdges["big-0.a-16.a"] + b := bobEdges["big-0.a-16.b"] + aCreation, err := a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err := b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["big-0.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["big-0.a-8.a"] + b = bobEdges["big-0.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["big-4.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["big-4.a-8.a"] + b = bobEdges["big-4.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["big-4.a-6.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["big-4.a-6.a"] + b = bobEdges["big-4.a-6.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["big-4.a-5.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["big-4.a-5.a"] + b = bobEdges["big-4.a-5.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) +} + +// Sets up the following small step challenge snapshot: +// +// /--5---6-----8-----------16 = Alice +// 0-----4 +// \--5'--6'----8'----------16' = Bob +// +// and then inserts the respective edges into a challenge tree. +// +// and then inserts the respective edges into a challenge tree. +func setupSmallStepChallengeSnapshot(t *testing.T, tree *RoyalChallengeTree, claimId string) { + t.Helper() + originEdge, ok := tree.edges.Get(id(mock.EdgeId(claimId))).(*mock.Edge) + require.Equal(t, true, ok) + origin := mock.OriginId(originEdge.ComputeMutualId()) + originId := protocol.OriginId(common.BytesToHash([]byte(originEdge.ComputeMutualId()))) + aliceEdges := buildEdges( + // Alice. + newEdge(&newCfg{t: t, edgeId: "smol-0.a-16.a", originId: origin, claimId: claimId, createdAt: 21}), + newEdge(&newCfg{t: t, edgeId: "smol-0.a-8.a", originId: origin, createdAt: 23}), + newEdge(&newCfg{t: t, edgeId: "smol-8.a-16.a", originId: origin, createdAt: 23}), + newEdge(&newCfg{t: t, edgeId: "smol-0.a-4.a", originId: origin, createdAt: 25}), + newEdge(&newCfg{t: t, edgeId: "smol-4.a-8.a", originId: origin, createdAt: 25}), + newEdge(&newCfg{t: t, edgeId: "smol-4.a-6.a", originId: origin, createdAt: 27}), + newEdge(&newCfg{t: t, edgeId: "smol-6.a-8.a", originId: origin, createdAt: 27}), + newEdge(&newCfg{t: t, edgeId: "smol-4.a-5.a", originId: origin, createdAt: 29}), + newEdge(&newCfg{t: t, edgeId: "smol-5.a-6.a", originId: origin, createdAt: 29}), + ) + bobEdges := buildEdges( + // Bob. + newEdge(&newCfg{t: t, edgeId: "smol-0.a-16.b", originId: origin, createdAt: 22}), + newEdge(&newCfg{t: t, edgeId: "smol-0.a-8.b", originId: origin, createdAt: 24}), + newEdge(&newCfg{t: t, edgeId: "smol-8.b-16.b", originId: origin, createdAt: 24}), + newEdge(&newCfg{t: t, edgeId: "smol-4.a-8.b", originId: origin, createdAt: 26}), + newEdge(&newCfg{t: t, edgeId: "smol-4.a-6.b", originId: origin, createdAt: 28}), + newEdge(&newCfg{t: t, edgeId: "smol-6.b-8.b", originId: origin, createdAt: 28}), + newEdge(&newCfg{t: t, edgeId: "smol-4.a-5.b", originId: origin, createdAt: 30}), + newEdge(&newCfg{t: t, edgeId: "smol-5.b-6.b", originId: origin, createdAt: 30}), + ) + // Child-relationship linking. + // Alice. + aliceEdges["smol-0.a-16.a"].LowerChildID = "smol-0.a-8.a" + aliceEdges["smol-0.a-16.a"].UpperChildID = "smol-8.a-16.a" + aliceEdges["smol-0.a-8.a"].LowerChildID = "smol-0.a-4.a" + aliceEdges["smol-0.a-8.a"].UpperChildID = "smol-4.a-8.a" + aliceEdges["smol-4.a-8.a"].LowerChildID = "smol-4.a-6.a" + aliceEdges["smol-4.a-8.a"].UpperChildID = "smol-6.a-8.a" + aliceEdges["smol-4.a-6.a"].LowerChildID = "smol-4.a-5.a" + aliceEdges["smol-4.a-6.a"].UpperChildID = "smol-5.a-6.a" + // Bob. + bobEdges["smol-0.a-16.b"].LowerChildID = "smol-0.a-8.b" + bobEdges["smol-0.a-16.b"].UpperChildID = "smol-8.b-16.b" + bobEdges["smol-0.a-8.b"].LowerChildID = "smol-0.a-4.a" + bobEdges["smol-0.a-8.b"].UpperChildID = "smol-4.a-8.b" + bobEdges["smol-4.a-8.b"].LowerChildID = "smol-4.a-6.b" + bobEdges["smol-4.a-8.b"].UpperChildID = "smol-6.b-6.8" + bobEdges["smol-4.a-6.b"].LowerChildID = "smol-4.a-5.b" + bobEdges["smol-4.a-6.b"].UpperChildID = "smol-5.b-6.b" + + for _, v := range aliceEdges { + tree.edges.Put(v.Id(), v) + } + + // Set up rivaled edges. + mutual := aliceEdges["smol-0.a-16.a"].MutualId() + key := buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := tree.edgeCreationTimes.Get(key) + a := aliceEdges["smol-0.a-16.a"] + b := bobEdges["smol-0.a-16.b"] + aCreation, err := a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err := b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["smol-0.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["smol-0.a-8.a"] + b = bobEdges["smol-0.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["smol-4.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["smol-4.a-8.a"] + b = bobEdges["smol-4.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["smol-4.a-6.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["smol-4.a-6.a"] + b = bobEdges["smol-4.a-6.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = aliceEdges["smol-4.a-5.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = aliceEdges["smol-4.a-5.a"] + b = bobEdges["smol-4.a-5.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) +} diff --git a/bold/challenge-manager/challenge-tree/compute_ancestors_test.go b/bold/challenge-manager/challenge-tree/compute_ancestors_test.go new file mode 100644 index 0000000000..57ee8fce08 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/compute_ancestors_test.go @@ -0,0 +1,184 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/threadsafe" +) + +// Tests the following tree, all the way down to the small +// step subchallenge level. +// +// Block challenge: +// +// /--5---6-----8-----------16 = Alice +// 0-----4 +// \--5'--6'----8'----------16' = Bob +// +// Big step challenge: +// +// /--5---6-----8-----------16 = Alice (claim_id = id(5, 6) in the level above) +// 0-----4 +// \--5'--6'----8'----------16' = Bob +// +// Small step challenge: +// +// /--5---6-----8-----------16 = Alice (claim_id = id(5, 6) in the level above) +// 0-----4 +// \--5'--6'----8'----------16' = Bob +// +// From here, the list of ancestors can be determined all the way to the top across +// challenge levels successfully, linked by claimed edges. +func TestComputeAncestors(t *testing.T) { + ctx := context.Background() + tree := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + metadataReader: &mockMetadataReader{}, + totalChallengeLevels: 3, + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](), + } + tree.royalRootEdgesByLevel.Put(2, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(1, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(0, threadsafe.NewSlice[protocol.SpecEdge]()) + + // Edge ids that belong to block challenges are prefixed with "blk". + // For big step, prefixed with "big", and small step, prefixed with "smol". + setupBlockChallengeTreeSnapshot(t, tree, "ass.a") + blockRootEdges := tree.royalRootEdgesByLevel.Get(2 /* big step level */) + blockRootEdges.Push(tree.edges.Get(id("blk-0.a-16.a"))) + claimId := "blk-4.a-5.a" + setupBigStepChallengeSnapshot(t, tree, claimId) + bigStepRootEdges := tree.royalRootEdgesByLevel.Get(1 /* big step level */) + bigStepRootEdges.Push(tree.edges.Get(id("big-0.a-16.a"))) + claimId = "big-4.a-5.a" + setupSmallStepChallengeSnapshot(t, tree, claimId) + smallStepRootEdges := tree.royalRootEdgesByLevel.Get(0 /* small step level */) + smallStepRootEdges.Push(tree.edges.Get(id("smol-0.a-16.a"))) + blockNum := uint64(30) + + t.Run("junk edge errored", func(t *testing.T) { + // We start by querying for ancestors for a block edge id. + _, err := tree.ComputeAncestors(ctx, id("foo"), blockNum) + require.ErrorContains(t, err, "not found in honest challenge tree") + }) + t.Run("dishonest edge lookup errored", func(t *testing.T) { + _, err := tree.ComputeAncestors(ctx, id("blk-0.a-16.b"), blockNum) + require.ErrorContains(t, err, "not found in honest challenge tree") + }) + t.Run("block challenge: level zero edge has no ancestors", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("blk-0.a-16.a"), blockNum) + require.NoError(t, err) + require.Equal(t, 0, len(resp)) + }) + t.Run("block challenge: single ancestor", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("blk-0.a-8.a"), blockNum) + require.NoError(t, err) + require.Equal(t, id("blk-0.a-16.a"), resp[0].Id()) + resp, err = tree.ComputeAncestors(ctx, id("blk-8.a-16.a"), blockNum) + require.NoError(t, err) + require.Equal(t, id("blk-0.a-16.a"), resp[0].Id()) + }) + t.Run("block challenge: many ancestors", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("blk-4.a-5.a"), blockNum) + require.NoError(t, err) + wanted := []protocol.EdgeId{ + id("blk-4.a-6.a"), + id("blk-4.a-8.a"), + id("blk-0.a-8.a"), + id("blk-0.a-16.a"), + } + for i := 0; i < len(wanted); i++ { + require.Equal(t, wanted[i], resp[i].Id()) + } + }) + t.Run("big step challenge: level zero edge has ancestors from block challenge", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("big-0.a-16.a"), blockNum) + require.NoError(t, err) + wanted := []protocol.EdgeId{ + id("blk-4.a-5.a"), + id("blk-4.a-6.a"), + id("blk-4.a-8.a"), + id("blk-0.a-8.a"), + id("blk-0.a-16.a"), + } + for i := 0; i < len(wanted); i++ { + require.Equal(t, wanted[i], resp[i].Id()) + } + }) + t.Run("big step challenge: many ancestors plus block challenge ancestors", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("big-5.a-6.a"), blockNum) + require.NoError(t, err) + wanted := []protocol.EdgeId{ + // Big step chal. + id("big-4.a-6.a"), + id("big-4.a-8.a"), + id("big-0.a-8.a"), + id("big-0.a-16.a"), + // Block chal. + id("blk-4.a-5.a"), + id("blk-4.a-6.a"), + id("blk-4.a-8.a"), + id("blk-0.a-8.a"), + id("blk-0.a-16.a"), + } + for i := 0; i < len(wanted); i++ { + require.Equal(t, wanted[i], resp[i].Id()) + } + }) + t.Run("small step challenge: level zero edge has ancestors from big and block challenge", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("smol-0.a-16.a"), blockNum) + require.NoError(t, err) + wanted := []protocol.EdgeId{ + // Big step chal. + id("big-4.a-5.a"), + id("big-4.a-6.a"), + id("big-4.a-8.a"), + id("big-0.a-8.a"), + id("big-0.a-16.a"), + // Block chal. + id("blk-4.a-5.a"), + id("blk-4.a-6.a"), + id("blk-4.a-8.a"), + id("blk-0.a-8.a"), + id("blk-0.a-16.a"), + } + for i := 0; i < len(wanted); i++ { + require.Equal(t, wanted[i], resp[i].Id()) + } + }) + t.Run("small step challenge: lowest level edge has full ancestry", func(t *testing.T) { + resp, err := tree.ComputeAncestors(ctx, id("smol-5.a-6.a"), blockNum) + require.NoError(t, err) + wanted := []protocol.EdgeId{ + // Small step chal. + id("smol-4.a-6.a"), + id("smol-4.a-8.a"), + id("smol-0.a-8.a"), + id("smol-0.a-16.a"), + // Big step chal. + id("big-4.a-5.a"), + id("big-4.a-6.a"), + id("big-4.a-8.a"), + id("big-0.a-8.a"), + id("big-0.a-16.a"), + // Block chal. + id("blk-4.a-5.a"), + id("blk-4.a-6.a"), + id("blk-4.a-8.a"), + id("blk-0.a-8.a"), + id("blk-0.a-16.a"), + } + for i := 0; i < len(wanted); i++ { + require.Equal(t, wanted[i], resp[i].Id()) + } + }) +} diff --git a/bold/challenge-manager/challenge-tree/local_timer.go b/bold/challenge-manager/challenge-tree/local_timer.go new file mode 100644 index 0000000000..d05f59f6d1 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/local_timer.go @@ -0,0 +1,127 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "fmt" + "math" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" +) + +// Gets the local timer of an edge at a block number, T. If T is earlier than the edge's creation, +// this function will return 0. +func (ht *RoyalChallengeTree) LocalTimer(ctx context.Context, e protocol.ReadOnlyEdge, blockNum uint64) (uint64, error) { + createdAtBlock, err := e.CreatedAtBlock() + if err != nil { + return 0, err + } + if blockNum <= createdAtBlock { + return 0, nil + } + status, err := e.Status(ctx) + if err != nil { + return 0, err + } + if status == protocol.EdgeConfirmed { + return math.MaxUint64, nil + } + // If no rival at a block num, then the local timer is defined + // as t - t_creation(e). + unrivaled, err := ht.UnrivaledAtBlockNum(e, blockNum) + if err != nil { + return 0, err + } + if unrivaled { + return blockNum - createdAtBlock, nil + } + // Else we return the earliest created rival's block number: t_rival - t_creation(e). + // This unwrap is safe because the edge has rivals at this point due to the check above. + earliest := ht.EarliestCreatedRivalBlockNumber(e) + tRival := earliest.Unwrap() + if createdAtBlock >= tRival { + return 0, nil + } + return tRival - createdAtBlock, nil +} + +// Gets the minimum creation block number across all of an edge's rivals. If an edge +// has no rivals, this minimum is undefined. +func (ht *RoyalChallengeTree) EarliestCreatedRivalBlockNumber(e protocol.ReadOnlyEdge) option.Option[uint64] { + rivals := ht.rivalsWithCreationTimes(e) + creationBlocks := make([]uint64, len(rivals)) + earliestCreatedRivalBlock := option.None[uint64]() + for i, r := range rivals { + creationBlocks[i] = uint64(r.createdAtBlock) + if earliestCreatedRivalBlock.IsNone() { + earliestCreatedRivalBlock = option.Some(uint64(r.createdAtBlock)) + } else if uint64(r.createdAtBlock) < earliestCreatedRivalBlock.Unwrap() { + earliestCreatedRivalBlock = option.Some(uint64(r.createdAtBlock)) + } + } + return earliestCreatedRivalBlock +} + +// Determines if an edge was unrivaled at a block num T. If any rival existed +// for the edge at T, this function will return false. +func (ht *RoyalChallengeTree) UnrivaledAtBlockNum(e protocol.ReadOnlyEdge, blockNum uint64) (bool, error) { + createdAtBlock, err := e.CreatedAtBlock() + if err != nil { + return false, err + } + if blockNum < createdAtBlock { + return false, fmt.Errorf( + "edge creation block %d less than specified %d", + createdAtBlock, + blockNum, + ) + } + rivals := ht.rivalsWithCreationTimes(e) + if len(rivals) == 0 { + return true, nil + } + for _, r := range rivals { + // If a rival existed before or at the time of the edge's + // creation, we then return false. + if uint64(r.createdAtBlock) <= blockNum { + return false, nil + } + } + return true, nil +} + +// Contains a rival edge's id and its creation block number. +type rival struct { + id protocol.EdgeId + createdAtBlock creationTime +} + +// Computes the set of rivals with their creation block number for an edge being tracked +// by the challenge tree. We do this by computing the mutual id of the edge and fetching +// all edge ids that share the same one from a set the challenge tree keeps track of. +// We exclude the specified edge from the returned list of rivals. +func (ht *RoyalChallengeTree) rivalsWithCreationTimes(eg protocol.ReadOnlyEdge) []*rival { + rivals := make([]*rival, 0) + key := buildEdgeCreationTimeKey(eg.OriginId(), eg.MutualId()) + mutuals := ht.edgeCreationTimes.Get(key) + if mutuals == nil { + ht.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + return rivals + } + _ = mutuals.ForEach(func(rivalId protocol.EdgeId, t creationTime) error { + if rivalId == eg.Id() { + return nil + } + rivals = append(rivals, &rival{ + id: rivalId, + createdAtBlock: t, + }) + return nil + }) + return rivals +} diff --git a/bold/challenge-manager/challenge-tree/local_timer_test.go b/bold/challenge-manager/challenge-tree/local_timer_test.go new file mode 100644 index 0000000000..45709f65a2 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/local_timer_test.go @@ -0,0 +1,223 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/challenge-manager/challenge-tree/mock" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" +) + +func Test_localTimer(t *testing.T) { + ctx := context.Background() + ct := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + } + edgeA := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.a", createdAt: 3}) + ct.edges.Put(edgeA.Id(), edgeA) + + t.Run("zero if earlier than creation time", func(t *testing.T) { + timer, err := ct.LocalTimer(ctx, edgeA, edgeA.CreationBlock-1) + require.NoError(t, err) + require.Equal(t, uint64(0), timer) + }) + t.Run("no rival is simply difference between T and creation time", func(t *testing.T) { + timer, err := ct.LocalTimer(ctx, edgeA, edgeA.CreationBlock) + require.NoError(t, err) + require.Equal(t, uint64(0), timer) + timer, err = ct.LocalTimer(ctx, edgeA, edgeA.CreationBlock+3) + require.NoError(t, err) + require.Equal(t, uint64(3), timer) + timer, err = ct.LocalTimer(ctx, edgeA, edgeA.CreationBlock+1000) + require.NoError(t, err) + require.Equal(t, uint64(1000), timer) + }) + t.Run("if rivaled timer is difference between earliest rival and edge creation", func(t *testing.T) { + edgeB := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.b", createdAt: 5}) + edgeC := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.c", createdAt: 10}) + ct.edges.Put(edgeB.Id(), edgeB) + ct.edges.Put(edgeC.Id(), edgeC) + mutual := edgeA.MutualId() + + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + ct.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := ct.edgeCreationTimes.Get(key) + mutuals.Put(edgeA.Id(), creationTime(edgeA.CreationBlock)) + mutuals.Put(edgeB.Id(), creationTime(edgeB.CreationBlock)) + mutuals.Put(edgeC.Id(), creationTime(edgeC.CreationBlock)) + + // Should get same result regardless of specified time. + timer, err := ct.LocalTimer(ctx, edgeA, 100) + require.NoError(t, err) + require.Equal(t, edgeB.CreationBlock-edgeA.CreationBlock, timer) + timer, err = ct.LocalTimer(ctx, edgeA, 10000) + require.NoError(t, err) + require.Equal(t, edgeB.CreationBlock-edgeA.CreationBlock, timer) + timer, err = ct.LocalTimer(ctx, edgeA, 1000000) + require.NoError(t, err) + require.Equal(t, edgeB.CreationBlock-edgeA.CreationBlock, timer) + + // EdgeB and EdgeC were already rivaled at creation, so they should have + // a local timer of 0 regardless of specified time. + timer, err = ct.LocalTimer(ctx, edgeB, 100) + require.NoError(t, err) + require.Equal(t, uint64(0), timer) + timer, err = ct.LocalTimer(ctx, edgeC, 100) + require.NoError(t, err) + require.Equal(t, uint64(0), timer) + timer, err = ct.LocalTimer(ctx, edgeB, 10000) + require.NoError(t, err) + require.Equal(t, uint64(0), timer) + timer, err = ct.LocalTimer(ctx, edgeC, 10000) + require.NoError(t, err) + require.Equal(t, uint64(0), timer) + }) +} + +func Test_earliestCreatedRivalBlockNumber(t *testing.T) { + ct := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + } + edgeA := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.a", createdAt: 3}) + edgeB := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.b", createdAt: 5}) + edgeC := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.c", createdAt: 10}) + ct.edges.Put(edgeA.Id(), edgeA) + t.Run("no rivals", func(t *testing.T) { + res := ct.EarliestCreatedRivalBlockNumber(edgeA) + + require.Equal(t, option.None[uint64](), res) + }) + t.Run("one rival", func(t *testing.T) { + mutual := edgeA.MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + ct.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := ct.edgeCreationTimes.Get(key) + mutuals.Put(edgeA.Id(), creationTime(edgeA.CreationBlock)) + mutuals.Put(edgeB.Id(), creationTime(edgeB.CreationBlock)) + ct.edges.Put(edgeB.Id(), edgeB) + + res := ct.EarliestCreatedRivalBlockNumber(edgeA) + + require.Equal(t, uint64(5), res.Unwrap()) + }) + t.Run("multiple rivals", func(t *testing.T) { + ct.edges.Put(edgeC.Id(), edgeC) + mutual := edgeC.MutualId() + + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + mutuals := ct.edgeCreationTimes.Get(key) + mutuals.Put(edgeC.Id(), creationTime(edgeC.CreationBlock)) + + res := ct.EarliestCreatedRivalBlockNumber(edgeA) + + require.Equal(t, uint64(5), res.Unwrap()) + }) +} + +func Test_unrivaledAtBlockNum(t *testing.T) { + ct := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + } + edgeA := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.a", createdAt: 3}) + edgeB := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.b", createdAt: 5}) + ct.edges.Put(edgeA.Id(), edgeA) + t.Run("less than specified time", func(t *testing.T) { + _, err := ct.UnrivaledAtBlockNum(edgeA, 0) + require.ErrorContains(t, err, "less than specified") + }) + t.Run("no rivals", func(t *testing.T) { + unrivaled, err := ct.UnrivaledAtBlockNum(edgeA, 3) + require.NoError(t, err) + require.Equal(t, true, unrivaled) + unrivaled, err = ct.UnrivaledAtBlockNum(edgeA, 1000) + require.NoError(t, err) + require.Equal(t, true, unrivaled) + }) + t.Run("with rivals but unrivaled at creation time", func(t *testing.T) { + mutual := edgeA.MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + ct.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := ct.edgeCreationTimes.Get(key) + mutuals.Put(edgeA.Id(), creationTime(edgeA.CreationBlock)) + mutuals.Put(edgeB.Id(), creationTime(edgeB.CreationBlock)) + ct.edges.Put(edgeB.Id(), edgeB) + + unrivaled, err := ct.UnrivaledAtBlockNum(edgeA, 3) + require.NoError(t, err) + require.Equal(t, true, unrivaled) + }) + t.Run("rivaled at first rival creation time", func(t *testing.T) { + unrivaled, err := ct.UnrivaledAtBlockNum(edgeA, 5) + require.NoError(t, err) + require.Equal(t, false, unrivaled) + unrivaled, err = ct.UnrivaledAtBlockNum(edgeB, 5) + require.NoError(t, err) + require.Equal(t, false, unrivaled) + }) +} + +func Test_rivalsWithCreationTimes(t *testing.T) { + ct := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + } + edgeA := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.a", createdAt: 5}) + edgeB := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.b", createdAt: 5}) + edgeC := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.c", createdAt: 10}) + ct.edges.Put(edgeA.Id(), edgeA) + t.Run("no rivals", func(t *testing.T) { + rivals := ct.rivalsWithCreationTimes(edgeA) + + require.Equal(t, 0, len(rivals)) + }) + t.Run("single rival", func(t *testing.T) { + mutual := edgeA.MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + ct.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := ct.edgeCreationTimes.Get(key) + mutuals.Put(edgeB.Id(), creationTime(edgeB.CreationBlock)) + mutuals.Put(edgeA.Id(), creationTime(edgeA.CreationBlock)) + ct.edges.Put(edgeB.Id(), edgeB) + rivals := ct.rivalsWithCreationTimes(edgeA) + + want := []*rival{ + {id: edgeB.Id(), createdAtBlock: creationTime(edgeB.CreationBlock)}, + } + require.Equal(t, want, rivals) + rivals = ct.rivalsWithCreationTimes(edgeB) + + want = []*rival{ + {id: edgeA.Id(), createdAtBlock: creationTime(edgeA.CreationBlock)}, + } + require.Equal(t, want, rivals) + }) + t.Run("multiple rivals", func(t *testing.T) { + ct.edges.Put(edgeC.Id(), edgeC) + mutual := edgeC.MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + mutuals := ct.edgeCreationTimes.Get(key) + mutuals.Put(edgeC.Id(), creationTime(edgeC.CreationBlock)) + want := []mock.EdgeId{edgeA.ID, edgeB.ID} + rivals := ct.rivalsWithCreationTimes(edgeC) + + require.Equal(t, true, len(rivals) > 0) + got := make(map[protocol.EdgeId]bool) + for _, r := range rivals { + got[r.id] = true + } + for _, w := range want { + require.Equal(t, true, got[id(w)]) + } + }) +} diff --git a/bold/challenge-manager/challenge-tree/mock/edge.go b/bold/challenge-manager/challenge-tree/mock/edge.go new file mode 100644 index 0000000000..811aabdf3a --- /dev/null +++ b/bold/challenge-manager/challenge-tree/mock/edge.go @@ -0,0 +1,206 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package mock includes specific mock setups for edge types used in internal tests. +package mock + +import ( + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" +) + +var _ = protocol.ReadOnlyEdge(&Edge{}) + +type EdgeId string +type Commit string +type OriginId string + +// Edge for challenge tree specific tests, making it easier for test ergonomics. +type Edge struct { + ID EdgeId + EdgeType protocol.ChallengeLevel + InnerStatus protocol.EdgeStatus + InnerInheritedTimer protocol.InheritedTimer + StartHeight uint64 + StartCommit Commit + EndHeight uint64 + EndCommit Commit + OriginID OriginId + ClaimID string + LowerChildID EdgeId + UpperChildID EdgeId + CreationBlock uint64 + TotalChallengeLevels uint8 + IsHonest bool +} + +func (e *Edge) Id() protocol.EdgeId { + return protocol.EdgeId{Hash: common.BytesToHash([]byte(e.ID))} +} + +func (e *Edge) GetChallengeLevel() protocol.ChallengeLevel { + return e.EdgeType +} + +func (e *Edge) GetReversedChallengeLevel() protocol.ChallengeLevel { + return protocol.ChallengeLevel(e.TotalChallengeLevels) - 1 - e.EdgeType +} + +func (e *Edge) GetTotalChallengeLevels(ctx context.Context) uint8 { + return e.TotalChallengeLevels +} + +func (e *Edge) StartCommitment() (protocol.Height, common.Hash) { + return protocol.Height(e.StartHeight), common.BytesToHash([]byte(e.StartCommit)) +} + +func (e *Edge) EndCommitment() (protocol.Height, common.Hash) { + return protocol.Height(e.EndHeight), common.BytesToHash([]byte(e.EndCommit)) +} + +func (e *Edge) CreatedAtBlock() (uint64, error) { + return e.CreationBlock, nil +} + +func (e *Edge) OriginId() protocol.OriginId { + return protocol.OriginId(common.BytesToHash([]byte(e.OriginID))) +} + +func (e *Edge) MutualId() protocol.MutualId { + return protocol.MutualId(common.BytesToHash([]byte(e.ComputeMutualId()))) +} + +func (e *Edge) ComputeMutualId() string { + return fmt.Sprintf( + "%d-%s-%d-%s-%d", + e.EdgeType, + e.OriginID, + e.StartHeight, + e.StartCommit, + e.EndHeight, + ) +} + +// ClaimId of the edge, if any +func (e *Edge) ClaimId() option.Option[protocol.ClaimId] { + if e.ClaimID == "" { + return option.None[protocol.ClaimId]() + } + return option.Some(protocol.ClaimId(common.BytesToHash([]byte(e.ClaimID)))) +} + +// LowerChild of the edge, if any. +func (e *Edge) LowerChild(_ context.Context) (option.Option[protocol.EdgeId], error) { + if e.LowerChildID == "" { + return option.None[protocol.EdgeId](), nil + } + return option.Some(protocol.EdgeId{Hash: common.BytesToHash([]byte(e.LowerChildID))}), nil +} + +// UpperChild of the edge, if any. +func (e *Edge) UpperChild(_ context.Context) (option.Option[protocol.EdgeId], error) { + if e.UpperChildID == "" { + return option.None[protocol.EdgeId](), nil + } + return option.Some(protocol.EdgeId{Hash: common.BytesToHash([]byte(e.UpperChildID))}), nil +} + +func (e *Edge) HasChildren(ctx context.Context) (bool, error) { + return e.LowerChildID != "" && e.UpperChildID != "", nil +} + +// MiniStaker of an edge. Only existing for level zero edges. +func (*Edge) MiniStaker() option.Option[common.Address] { + return option.None[common.Address]() +} + +// AssertionHash of the parent assertion that originated the challenge +// at the top-level. +func (*Edge) AssertionHash(_ context.Context) (protocol.AssertionHash, error) { + return protocol.AssertionHash{}, nil +} + +// TimeUnrivaled in seconds an edge has been unrivaled. +func (*Edge) TimeUnrivaled(_ context.Context) (uint64, error) { + return 0, nil +} + +// LatestInheritedTimer in seconds an edge has been unrivaled. +func (e *Edge) LatestInheritedTimer(_ context.Context) (protocol.InheritedTimer, error) { + return e.InnerInheritedTimer, nil +} + +// Status of an edge. +func (e *Edge) Status(_ context.Context) (protocol.EdgeStatus, error) { + return e.InnerStatus, nil +} + +// HasRival if an edge has rivals. +func (*Edge) HasRival(_ context.Context) (bool, error) { + return false, nil +} + +// HasLengthOneRival checks if an edge has a length one rival. +func (*Edge) HasLengthOneRival(_ context.Context) (bool, error) { + return false, nil +} + +// TopLevelClaimHeight for the top-level edge the current edge's challenge is made upon. +// This is used at subchallenge creation boundaries. +func (*Edge) TopLevelClaimHeight(_ context.Context) (protocol.OriginHeights, error) { + return protocol.OriginHeights{}, nil +} + +// HasLengthOneRival checks if an edge has a length one rival. +func (e *Edge) MarkAsHonest() { + e.IsHonest = true +} + +func (e *Edge) AsVerifiedHonest() (protocol.VerifiedRoyalEdge, bool) { + if e.IsHonest { + return &MockHonestEdge{e}, true + } + return nil, false +} + +func (*Edge) Bisect( + _ context.Context, + _ common.Hash, + _ []byte, +) (protocol.VerifiedRoyalEdge, protocol.VerifiedRoyalEdge, error) { + return nil, nil, errors.New("unimplemented") +} + +func (*Edge) ConfirmByTimer(_ context.Context, _ protocol.AssertionHash) (*types.Transaction, error) { + return nil, errors.New("unimplemented") +} + +func (*Edge) ConfirmedAtBlock(ctx context.Context) (uint64, error) { + return 0, nil +} + +type MockHonestEdge struct { + *Edge +} + +func (m *MockHonestEdge) Honest() {} + +func (m *MockHonestEdge) Bisect( + ctx context.Context, + prefixHistoryRoot common.Hash, + prefixProof []byte, +) (protocol.VerifiedRoyalEdge, protocol.VerifiedRoyalEdge, error) { + return m.Edge.Bisect(ctx, prefixHistoryRoot, prefixProof) +} + +func (m *MockHonestEdge) ConfirmByTimer(ctx context.Context, claimedAssertion protocol.AssertionHash) (*types.Transaction, error) { + return m.Edge.ConfirmByTimer(ctx, claimedAssertion) +} diff --git a/bold/challenge-manager/challenge-tree/paths.go b/bold/challenge-manager/challenge-tree/paths.go new file mode 100644 index 0000000000..afe4913415 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/paths.go @@ -0,0 +1,320 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "container/list" + "context" + "fmt" + "math" + "slices" + + "github.com/pkg/errors" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/option" +) + +type ComputePathWeightArgs struct { + Child protocol.EdgeId + Ancestor protocol.EdgeId + BlockNum uint64 +} + +var ErrChildrenNotYetSeen = errors.New("child not yet tracked") + +// ComputePathWeight from a child edge to a specified ancestor edge. A weight is the sum of the local timers +// of all edges along the path. +// +// Invariant: assumes ComputeAncestors returns a list of ancestors ordered from child to parent, +// not including the edge id we are querying ancestors for. +func (ht *RoyalChallengeTree) ComputePathWeight( + ctx context.Context, + args ComputePathWeightArgs, +) (uint64, error) { + child, ok := ht.edges.TryGet(args.Child) + if !ok { + return 0, fmt.Errorf("child edge not yet tracked %#x", args.Child.Hash) + } + if !ht.edges.Has(args.Ancestor) { + return 0, fmt.Errorf("ancestor not yet tracked %#x", args.Ancestor.Hash) + } + localTimer, err := ht.LocalTimer(ctx, child, args.BlockNum) + if err != nil { + return 0, err + } + if args.Child == args.Ancestor { + return localTimer, nil + } + ancestors, err := ht.ComputeAncestors(ctx, args.Child, args.BlockNum) + if err != nil { + return 0, err + } + pathWeight := localTimer + found := false + for _, an := range ancestors { + localTimer, err := ht.LocalTimer(ctx, an, args.BlockNum) + if err != nil { + return 0, err + } + pathWeight += localTimer + if an.Id() == args.Ancestor { + found = true + break + } + } + if !found { + return 0, errors.New("expected ancestor not found in computed ancestors list") + } + return pathWeight, nil +} + +type EssentialPath []protocol.EdgeId + +type IsConfirmableArgs struct { + EssentialEdge protocol.EdgeId + ConfirmationThreshold uint64 + BlockNum uint64 +} + +// Find all the paths down from an essential edge, and +// compute the local timer of each edge along the path. This is +// a recursive computation that goes down the tree rooted at the essential +// edge and ends once it finds edges that either do not have children, +// or are terminal edges that end in children that are incorrectly constructed +// or non-essential. +// +// After the paths are computed, we then compute the path weight of each +// and if the min element of this list has a weight >= the confirmation threshold, +// the essential edge is then confirmable. +// +// Note: the specified argument essential edge must indeed be essential, otherwise, +// this function will error. +func (ht *RoyalChallengeTree) IsConfirmableEssentialEdge( + ctx context.Context, + args IsConfirmableArgs, +) (bool, []EssentialPath, uint64, error) { + essentialEdge, ok := ht.edges.TryGet(args.EssentialEdge) + if !ok { + return false, nil, 0, fmt.Errorf("essential edge not found") + } + if essentialEdge.ClaimId().IsNone() { + return false, nil, 0, fmt.Errorf("specified input argument %#x is not essential", args.EssentialEdge.Hash) + } + essentialPaths, essentialTimers, err := ht.findEssentialPaths( + ctx, + essentialEdge, + args.BlockNum, + ) + if err != nil { + return false, nil, 0, err + } + if len(essentialPaths) == 0 || len(essentialTimers) == 0 { + return false, nil, 0, fmt.Errorf("no essential paths found") + } + // An essential edge is confirmable if all of its essential paths + // down the tree have a path weight >= the confirmation threshold. + // To do this, we compute the path weight of each path + // and find the minimum. + // Then, it is sufficient to check that the minimum is + // greater than or equal to the confirmation threshold. + minWeight := uint64(math.MaxUint64) + for _, timers := range essentialTimers { + pathWeight := uint64(0) + for _, timer := range timers { + pathWeight = saturatingUAdd(pathWeight, timer) + } + if pathWeight < minWeight { + minWeight = pathWeight + } + } + allEssentialPathsConfirmable := minWeight >= args.ConfirmationThreshold + return allEssentialPathsConfirmable, essentialPaths, minWeight, nil +} + +type essentialLocalTimers []uint64 + +// Use a depth-first-search approach (DFS) to gather the +// essential branches of the protocol graph. We manage our own +// visitor stack to avoid recursion. +// +// Invariant: the input edge must be essential. +func (ht *RoyalChallengeTree) findEssentialPaths( + ctx context.Context, + essentialEdge protocol.ReadOnlyEdge, + blockNum uint64, +) ([]EssentialPath, []essentialLocalTimers, error) { + allPaths := make([]EssentialPath, 0) + allTimers := make([]essentialLocalTimers, 0) + + type visited struct { + essentialEdge protocol.ReadOnlyEdge + path EssentialPath + localTimers essentialLocalTimers + } + stack := newStack[*visited]() + + localTimer, err := ht.LocalTimer(ctx, essentialEdge, blockNum) + if err != nil { + return nil, nil, err + } + + stack.push(&visited{ + essentialEdge: essentialEdge, + path: EssentialPath{essentialEdge.Id()}, + localTimers: essentialLocalTimers{localTimer}, + }) + + for stack.len() > 0 { + curr := stack.pop().Unwrap() + currentEdge, currentTimers, path := curr.essentialEdge, curr.localTimers, curr.path + isClaimedEdge, claimingEdge := ht.isClaimedEdge(ctx, currentEdge) + + hasChildren, err := currentEdge.HasChildren(ctx) + if err != nil { + return nil, nil, err + } + if hasChildren { + lowerChildIdOpt, err := currentEdge.LowerChild(ctx) + if err != nil { + return nil, nil, err + } + upperChildIdOpt, err := currentEdge.UpperChild(ctx) + if err != nil { + return nil, nil, err + } + lowerChildId, upperChildId := lowerChildIdOpt.Unwrap(), upperChildIdOpt.Unwrap() + lowerChild, ok := ht.edges.TryGet(lowerChildId) + if !ok { + return nil, nil, errors.Wrap(ErrChildrenNotYetSeen, "lower child") + } + upperChild, ok := ht.edges.TryGet(upperChildId) + if !ok { + return nil, nil, errors.Wrap(ErrChildrenNotYetSeen, "upper child") + } + lowerTimer, err := ht.LocalTimer(ctx, lowerChild, blockNum) + if err != nil { + return nil, nil, err + } + upperTimer, err := ht.LocalTimer(ctx, upperChild, blockNum) + if err != nil { + return nil, nil, err + } + lowerPath := append(slices.Clone(path), lowerChildId) + upperPath := append(slices.Clone(path), upperChildId) + lowerTimers := append(slices.Clone(currentTimers), lowerTimer) + upperTimers := append(slices.Clone(currentTimers), upperTimer) + stack.push(&visited{ + essentialEdge: lowerChild, + path: lowerPath, + localTimers: lowerTimers, + }) + stack.push(&visited{ + essentialEdge: upperChild, + path: upperPath, + localTimers: upperTimers, + }) + continue + } else if isClaimedEdge { + // Figure out if the edge is a terminal edge that has a refinement, in which + // case we need to continue the search down the next challenge level, + claimingEdgeTimer, err := ht.LocalTimer(ctx, claimingEdge, blockNum) + if err != nil { + return nil, nil, err + } + claimingPath := append(slices.Clone(path), claimingEdge.Id()) + claimingTimers := append(slices.Clone(currentTimers), claimingEdgeTimer) + stack.push(&visited{ + essentialEdge: claimingEdge, + path: claimingPath, + localTimers: claimingTimers, + }) + continue + } + + // Otherwise, the edge is a qualified leaf and we can push to the list of paths + // and all the timers of the path. + // Onchain actions expect ordered paths from leaf to root, so we + // preserve that ordering to make it easier for callers to use this data. + containers.Reverse(path) + containers.Reverse(currentTimers) + allPaths = append(allPaths, path) + allTimers = append(allTimers, currentTimers) + } + return allPaths, allTimers, nil +} + +func (ht *RoyalChallengeTree) isClaimedEdge(ctx context.Context, edge protocol.ReadOnlyEdge) (bool, protocol.ReadOnlyEdge) { + if isProofEdge(ctx, edge) { + return false, nil + } + if !hasLengthOne(edge) { + return false, nil + } + // Note: the specification requires that the claiming edge is correctly constructed. + // This is not checked here, because the honest validator only tracks + // essential edges as an invariant. + claimingEdge, ok := ht.findClaimingEdge(edge.Id()) + if !ok { + return false, nil + } + return true, claimingEdge +} + +func IsClaimingAnEdge(edge protocol.ReadOnlyEdge) bool { + return edge.ClaimId().IsSome() && edge.GetChallengeLevel() != protocol.NewBlockChallengeLevel() +} + +func hasLengthOne(edge protocol.ReadOnlyEdge) bool { + startHeight, _ := edge.StartCommitment() + endHeight, _ := edge.EndCommitment() + return endHeight-startHeight == 1 +} + +// Proof edges are edges that have length one at the lowest challenge level. +func isProofEdge(ctx context.Context, edge protocol.ReadOnlyEdge) bool { + isSmallStep := edge.GetChallengeLevel() == protocol.ChallengeLevel(edge.GetTotalChallengeLevels(ctx)-1) + return isSmallStep && hasLengthOne(edge) +} + +type stack[T any] struct { + dll *list.List +} + +func newStack[T any]() *stack[T] { + return &stack[T]{dll: list.New()} +} + +func (s *stack[T]) len() int { + return s.dll.Len() +} + +func (s *stack[T]) push(x T) { + s.dll.PushBack(x) +} + +func (s *stack[T]) pop() option.Option[T] { + if s.dll.Len() == 0 { + return option.None[T]() + } + tail := s.dll.Back() + val := tail.Value + s.dll.Remove(tail) + // nolint:errcheck + return option.Some(val.(T)) +} + +type unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +func saturatingUAdd[T unsigned](a, b T) T { + sum := a + b + if sum < a || sum < b { + sum = ^T(0) + } + return sum +} diff --git a/bold/challenge-manager/challenge-tree/paths_edge_cases_test.go b/bold/challenge-manager/challenge-tree/paths_edge_cases_test.go new file mode 100644 index 0000000000..7e33959fd5 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/paths_edge_cases_test.go @@ -0,0 +1,191 @@ +package challengetree + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/challenge-manager/challenge-tree/mock" + "github.com/offchainlabs/bold/containers/threadsafe" +) + +func Test_findEssentialPaths_edgeCases(t *testing.T) { + ctx := context.Background() + tree, honestEdges := setupEdgeCaseTest(t) + + // Calculate the essential paths starting at the honest root. + essentialHonestRoot := protocol.SpecEdge(honestEdges["blk-0.a-32.a"]) + blockNum := uint64(10) + paths, pathLocalTimers, err := tree.findEssentialPaths( + ctx, + essentialHonestRoot, + blockNum, + ) + require.NoError(t, err) + t.Log(paths) + t.Log(pathLocalTimers) +} + +func setupEdgeCaseTest(t *testing.T) (*RoyalChallengeTree, map[mock.EdgeId]*mock.Edge) { + t.Helper() + tree := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + metadataReader: &mockMetadataReader{}, + totalChallengeLevels: 3, + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](), + } + tree.royalRootEdgesByLevel.Put(2, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(1, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(0, threadsafe.NewSlice[protocol.SpecEdge]()) + honestAssertion := "assertion.a" + evilAssertion := "assertion.b" + evilEdges := buildEdges( + newEdge(&newCfg{t: t, edgeId: "blk-0.a-32.b", claimId: evilAssertion, createdAt: 1}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-16.b", createdAt: 3}), + newEdge(&newCfg{t: t, edgeId: "blk-16.b-32.b", createdAt: 3}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-8.b", createdAt: 5}), + newEdge(&newCfg{t: t, edgeId: "blk-8.b-16.b", createdAt: 5}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-4.a", createdAt: 7}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-8.b", createdAt: 7}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-6.a", createdAt: 9}), + newEdge(&newCfg{t: t, edgeId: "blk-6.a-8.b", createdAt: 9}), + newEdge(&newCfg{t: t, edgeId: "blk-6.a-7.b", createdAt: 11}), + newEdge(&newCfg{t: t, edgeId: "blk-7.b-8.b", createdAt: 11}), + ) + honestEdges := buildEdges( + newEdge(&newCfg{t: t, edgeId: "blk-0.a-32.a", claimId: honestAssertion, createdAt: 2}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-16.a", createdAt: 4}), + newEdge(&newCfg{t: t, edgeId: "blk-16.a-32.a", createdAt: 4}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-8.a", createdAt: 6}), + newEdge(&newCfg{t: t, edgeId: "blk-8.a-16.a", createdAt: 6}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-4.a", createdAt: 7}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-8.a", createdAt: 8}), + newEdge(&newCfg{t: t, edgeId: "blk-4.a-6.a", createdAt: 9}), + newEdge(&newCfg{t: t, edgeId: "blk-6.a-8.a", createdAt: 10}), + newEdge(&newCfg{t: t, edgeId: "blk-6.a-7.a", createdAt: 12}), + newEdge(&newCfg{t: t, edgeId: "blk-7.a-8.a", createdAt: 12}), + ) + + // Child-relationship linking. + // Honest. + honestEdges["blk-0.a-32.a"].LowerChildID = "blk-0.a-16.a" + honestEdges["blk-0.a-32.a"].UpperChildID = "blk-16.a-32.a" + + honestEdges["blk-0.a-16.a"].LowerChildID = "blk-0.a-8.a" + honestEdges["blk-0.a-16.a"].UpperChildID = "blk-8.a-16.a" + + honestEdges["blk-0.a-8.a"].LowerChildID = "blk-0.a-4.a" + honestEdges["blk-0.a-8.a"].UpperChildID = "blk-4.a-8.a" + + honestEdges["blk-4.a-8.a"].LowerChildID = "blk-4.a-6.a" + honestEdges["blk-4.a-8.a"].UpperChildID = "blk-6.a-8.a" + + honestEdges["blk-6.a-8.a"].LowerChildID = "blk-6.a-7.a" + honestEdges["blk-6.a-8.a"].UpperChildID = "blk-7.a-8.a" + + // Evil. + evilEdges["blk-0.a-32.b"].LowerChildID = "blk-0.a-16.b" + evilEdges["blk-0.a-32.b"].UpperChildID = "blk-16.b-32.b" + + evilEdges["blk-0.a-16.b"].LowerChildID = "blk-0.a-8.b" + evilEdges["blk-0.a-16.b"].UpperChildID = "blk-8.b-16.b" + + evilEdges["blk-0.a-8.b"].LowerChildID = "blk-0.a-4.a" + evilEdges["blk-0.a-8.b"].UpperChildID = "blk-4.a-8.b" + + evilEdges["blk-4.a-8.b"].LowerChildID = "blk-4.a-6.a" + evilEdges["blk-4.a-8.b"].UpperChildID = "blk-6.a-8.b" + + evilEdges["blk-6.a-8.b"].LowerChildID = "blk-6.a-7.b" + evilEdges["blk-6.a-8.b"].UpperChildID = "blk-7.b-8.b" + + transformedEdges := make(map[protocol.EdgeId]protocol.SpecEdge) + for _, v := range honestEdges { + transformedEdges[v.Id()] = v + } + allEdges := threadsafe.NewMapFromItems(transformedEdges) + tree.edges = allEdges + + // Set up rivaled edges. + mutual := honestEdges["blk-0.a-32.a"].MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := tree.edgeCreationTimes.Get(key) + a := honestEdges["blk-0.a-32.a"] + b := evilEdges["blk-0.a-32.b"] + aCreation, err := a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err := b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-0.a-16.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-0.a-16.a"] + b = evilEdges["blk-0.a-16.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-0.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-0.a-8.a"] + b = evilEdges["blk-0.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-4.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-4.a-8.a"] + b = evilEdges["blk-4.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-6.a-8.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-6.a-8.a"] + b = evilEdges["blk-6.a-8.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-6.a-7.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-6.a-7.a"] + b = evilEdges["blk-6.a-7.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + return tree, honestEdges +} diff --git a/bold/challenge-manager/challenge-tree/paths_test.go b/bold/challenge-manager/challenge-tree/paths_test.go new file mode 100644 index 0000000000..613e3ffb57 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/paths_test.go @@ -0,0 +1,487 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/challenge-manager/challenge-tree/mock" + "github.com/offchainlabs/bold/containers/threadsafe" +) + +func TestIsConfirmableEssentialEdge(t *testing.T) { + ctx := context.Background() + tree, honestEdges := setupEssentialPathsTest(t) + + // Calculate the essential paths starting at the honest root. + // See setupEssentialPaths and Test_findEssentialpaths below to + // understand the setup of the challenge tree. + _, _, _, err := tree.IsConfirmableEssentialEdge( + ctx, + IsConfirmableArgs{ + EssentialEdge: protocol.EdgeId{}, + }, + ) + require.ErrorContains(t, err, "essential edge not found") + + // Based on the setup, we expect the minimum path weight at block number 10 + // to be 6, which at a confirmation threshold of 10 is not enough to confirm + // the essential edge. + essentialHonestRoot := protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + blockNum := uint64(10) + isConfirmable, _, minPathWeight, err := tree.IsConfirmableEssentialEdge( + ctx, + IsConfirmableArgs{ + ConfirmationThreshold: 10, + EssentialEdge: essentialHonestRoot.Id(), + BlockNum: blockNum, + }, + ) + require.NoError(t, err) + require.False(t, isConfirmable) + require.Equal(t, uint64(6), minPathWeight) + + // At block number 14, we should exactly meet the confirmation threshold. + essentialHonestRoot = protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + blockNum = uint64(14) + isConfirmable, _, minPathWeight, err = tree.IsConfirmableEssentialEdge( + ctx, + IsConfirmableArgs{ + ConfirmationThreshold: 10, + EssentialEdge: essentialHonestRoot.Id(), + BlockNum: blockNum, + }, + ) + require.NoError(t, err) + require.True(t, isConfirmable) + require.Equal(t, uint64(10), minPathWeight) +} + +func Test_findEssentialPaths(t *testing.T) { + ctx := context.Background() + tree, honestEdges := setupEssentialPathsTest(t) + + // Calculate the essential paths starting at the honest root. + essentialHonestRoot := protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + blockNum := uint64(10) + paths, pathLocalTimers, err := tree.findEssentialPaths( + ctx, + essentialHonestRoot, + blockNum, + ) + require.NoError(t, err) + + // There should be three total essential paths from honest root down + // to terminal edges in this test, as there are three terminal edges, + // namely, path A ending in blk-3.a-4.a, path B ending in big-0.a-4.a, and path C ending in blk-0.a-2.a. + // + // Path A, at block number 10, should have a total weight of 7 as + // - blk-0.a-4.a has 1 block unrivaled + // - blk-2.a-4.a has 1 block unrivaled + // - blk-3.a-4.a has 5 blocks unrivaled + // + // Path B, at block number 10, should have a total weight of 6 as + // - blk-0.a-4.a has 1 block unrivaled + // - blk-2.a-4.a has 1 block unrivaled + // - blk-2.a-3.a has 1 block unrivaled + // - big-0.a-4.a has 3 block unrivaled + // + // Path C, at block number 10, should have a total weight of 8 as + // - blk-0.a-4.a has 1 block unrivaled + // - blk-0.a-2.a has 7 blocks unrivaled + require.Equal(t, 3, len(paths)) + require.Equal(t, 3, len(pathLocalTimers)) + + wantPathA := EssentialPath{ + honestEdges["blk-3.a-4.a"].Id(), + honestEdges["blk-2.a-4.a"].Id(), + honestEdges["blk-0.a-4.a"].Id(), + } + wantATimers := essentialLocalTimers{5, 1, 1} + require.Equal(t, wantPathA, paths[0]) + require.Equal(t, wantATimers, pathLocalTimers[0]) + + wantPathB := EssentialPath{ + honestEdges["big-0.a-4.a"].Id(), + honestEdges["blk-2.a-3.a"].Id(), + honestEdges["blk-2.a-4.a"].Id(), + honestEdges["blk-0.a-4.a"].Id(), + } + wantBTimers := essentialLocalTimers{3, 1, 1, 1} + require.Equal(t, wantPathB, paths[1]) + require.Equal(t, wantBTimers, pathLocalTimers[1]) + + wantPathC := EssentialPath{ + honestEdges["blk-0.a-2.a"].Id(), + honestEdges["blk-0.a-4.a"].Id(), + } + wantCTimers := essentialLocalTimers{7, 1} + require.Equal(t, wantPathC, paths[2]) + require.Equal(t, wantCTimers, pathLocalTimers[2]) +} + +func Test_stack(t *testing.T) { + s := newStack[int]() + require.Equal(t, 0, s.len()) + s.push(10) + require.Equal(t, 1, s.len()) + + result := s.pop() + require.False(t, result.IsNone()) + require.Equal(t, 10, result.Unwrap()) + + result = s.pop() + require.True(t, result.IsNone()) + + s.push(10) + s.push(20) + s.push(30) + require.Equal(t, 3, s.len()) + s.pop() + require.Equal(t, 2, s.len()) + s.pop() + require.Equal(t, 1, s.len()) + s.pop() + require.Equal(t, 0, s.len()) +} + +func TestComputePathWeight(t *testing.T) { + ctx := context.Background() + ht := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + } + t.Run("edges not found", func(t *testing.T) { + unseenEdge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-4.a", createdAt: 4}) + unseenAncestor := newEdge(&newCfg{t: t, edgeId: "blk-0.a-8.a", createdAt: 2}) + _, err := ht.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: unseenEdge.Id(), + Ancestor: unseenAncestor.Id(), + BlockNum: 10, + }, + ) + require.ErrorContains(t, err, "child edge not yet tracked") + ht.edges.Put(unseenEdge.Id(), unseenEdge) + _, err = ht.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: unseenEdge.Id(), + Ancestor: unseenAncestor.Id(), + BlockNum: 10, + }, + ) + require.ErrorContains(t, err, "ancestor not yet tracked") + }) + // To see the relationship between the edges, their creation times, + // and the time they became rivaled, see the setupEssentialPathsTest function. + tree, honestEdges := setupEssentialPathsTest(t) + tree.royalRootEdgesByLevel.Put(2, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(1, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(0, threadsafe.NewSlice[protocol.SpecEdge]()) + + blockRootEdges := tree.royalRootEdgesByLevel.Get(2 /* big step level */) + blockRootEdges.Push(tree.edges.Get(id("blk-0.a-4.a"))) + bigStepRootEdges := tree.royalRootEdgesByLevel.Get(1 /* big step level */) + bigStepRootEdges.Push(tree.edges.Get(id("big-0.a-4.a"))) + + t.Run("length 0 path", func(t *testing.T) { + child := protocol.SpecEdge(honestEdges["blk-2.a-4.a"]) + ancestor := child + weight, err := tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 10, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(1), weight) + + // Querying at a future block number should not change the result, + // as the terminal edge is rivaled. + weight, err = tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 20, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(1), weight) + }) + + t.Run("length 1 path with rivaled terminal", func(t *testing.T) { + child := protocol.SpecEdge(honestEdges["blk-2.a-4.a"]) + ancestor := protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + weight, err := tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 10, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(2), weight) + + // Querying at a future block number should not change the result, + // as the terminal edge is rivaled. + weight, err = tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 20, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(2), weight) + }) + t.Run("length 2 path with unrivaled terminal", func(t *testing.T) { + child := protocol.SpecEdge(honestEdges["blk-0.a-2.a"]) + ancestor := protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + weight, err := tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 10, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(8), weight) + + isUnrivaled, err := tree.IsUnrivaledAtBlockNum(child, 20) + require.NoError(t, err) + require.True(t, isUnrivaled) + + // Should increase if we query at a future block number, + // as in the tree setup, blk-3.a-4.a is unrivaled. + weight, err = tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 20, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(18), weight) + }) + t.Run("length 3 path with unrivaled terminal", func(t *testing.T) { + child := protocol.SpecEdge(honestEdges["blk-3.a-4.a"]) + ancestor := protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + weight, err := tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 10, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(7), weight) + + isUnrivaled, err := tree.IsUnrivaledAtBlockNum(child, 20) + require.NoError(t, err) + require.True(t, isUnrivaled) + + // Should increase if we query at a future block number, + // as in the tree setup, blk-3.a-4.a is unrivaled. + weight, err = tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 20, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(17), weight) + }) + t.Run("path ending in refinement edge across challenge level", func(t *testing.T) { + child := protocol.SpecEdge(honestEdges["big-0.a-4.a"]) + ancestor := protocol.SpecEdge(honestEdges["blk-0.a-4.a"]) + weight, err := tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 10, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(6), weight) + + isUnrivaled, err := tree.IsUnrivaledAtBlockNum(child, 20) + require.NoError(t, err) + require.True(t, isUnrivaled) + + // Should increase if we query at a future block number, + // as in the tree setup, blk-3.a-4.a is unrivaled. + weight, err = tree.ComputePathWeight( + ctx, + ComputePathWeightArgs{ + Child: child.Id(), + Ancestor: ancestor.Id(), + BlockNum: 20, + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(16), weight) + }) +} + +// Set up a challenge tree, down to two challenge levels. +func setupEssentialPathsTest(t *testing.T) (*RoyalChallengeTree, map[mock.EdgeId]*mock.Edge) { + t.Helper() + tree := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + metadataReader: &mockMetadataReader{}, + totalChallengeLevels: 3, + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](), + } + tree.royalRootEdgesByLevel.Put(2, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(1, threadsafe.NewSlice[protocol.SpecEdge]()) + tree.royalRootEdgesByLevel.Put(0, threadsafe.NewSlice[protocol.SpecEdge]()) + honestAssertion := "assertion.a" + evilAssertion := "assertion.b" + honestEdges := buildEdges( + newEdge(&newCfg{t: t, edgeId: "blk-0.a-4.a", claimId: honestAssertion, createdAt: 1}), + newEdge(&newCfg{t: t, edgeId: "blk-0.a-2.a", createdAt: 3}), + newEdge(&newCfg{t: t, edgeId: "blk-2.a-4.a", createdAt: 3}), + newEdge(&newCfg{t: t, edgeId: "blk-2.a-3.a", createdAt: 5}), + newEdge(&newCfg{t: t, edgeId: "blk-3.a-4.a", createdAt: 5}), + newEdge(&newCfg{t: t, edgeId: "big-0.a-4.a", claimId: "blk-2.a-3.a", createdAt: 7}), + ) + evilEdges := buildEdges( + newEdge(&newCfg{t: t, edgeId: "blk-0.a-4.b", claimId: evilAssertion, createdAt: 2}), + newEdge(&newCfg{t: t, edgeId: "blk-2.a-4.b", createdAt: 4}), + newEdge(&newCfg{t: t, edgeId: "blk-2.a-3.b", createdAt: 6}), + newEdge(&newCfg{t: t, edgeId: "blk-3.b-4.b", createdAt: 6}), + newEdge(&newCfg{t: t, edgeId: "big-0.a-4.b", claimId: "blk-2.a-3.b", createdAt: 8}), + ) + + // Child-relationship linking. + // Honest. + honestEdges["blk-0.a-4.a"].LowerChildID = "blk-0.a-2.a" + honestEdges["blk-0.a-4.a"].UpperChildID = "blk-2.a-4.a" + honestEdges["blk-2.a-4.a"].LowerChildID = "blk-2.a-3.a" + honestEdges["blk-2.a-4.a"].UpperChildID = "blk-3.a-4.a" + // Evil. + evilEdges["blk-0.a-4.b"].LowerChildID = "blk-0.a-2.a" + evilEdges["blk-0.a-4.b"].UpperChildID = "blk-2.a-4.b" + evilEdges["blk-2.a-4.b"].LowerChildID = "blk-2.a-3.b" + evilEdges["blk-2.a-4.b"].UpperChildID = "blk-3.b-4.b" + + transformedEdges := make(map[protocol.EdgeId]protocol.SpecEdge) + for _, v := range honestEdges { + transformedEdges[v.Id()] = v + } + allEdges := threadsafe.NewMapFromItems(transformedEdges) + tree.edges = allEdges + + // Set up rivaled edges. + mutual := honestEdges["blk-0.a-4.a"].MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals := tree.edgeCreationTimes.Get(key) + a := honestEdges["blk-0.a-4.a"] + b := evilEdges["blk-0.a-4.b"] + aCreation, err := a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err := b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-2.a-4.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-2.a-4.a"] + b = evilEdges["blk-2.a-4.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + mutual = honestEdges["blk-2.a-3.a"].MutualId() + key = buildEdgeCreationTimeKey(protocol.OriginId{}, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["blk-2.a-3.a"] + b = evilEdges["blk-2.a-3.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + + originId := protocol.OriginId(honestEdges["blk-2.a-3.a"].MutualId()) + mutual = honestEdges["big-0.a-4.a"].MutualId() + key = buildEdgeCreationTimeKey(originId, mutual) + tree.edgeCreationTimes.Put(key, threadsafe.NewMap[protocol.EdgeId, creationTime]()) + mutuals = tree.edgeCreationTimes.Get(key) + a = honestEdges["big-0.a-4.a"] + b = evilEdges["big-0.a-4.b"] + aCreation, err = a.CreatedAtBlock() + require.NoError(t, err) + bCreation, err = b.CreatedAtBlock() + require.NoError(t, err) + mutuals.Put(a.Id(), creationTime(aCreation)) + mutuals.Put(b.Id(), creationTime(bCreation)) + return tree, honestEdges +} + +func Test_isProofEdge(t *testing.T) { + ctx := context.Background() + edge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-32.a"}) + require.Equal(t, false, isProofEdge(ctx, edge)) + edge = newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.a"}) + require.Equal(t, false, isProofEdge(ctx, edge)) + edge = newEdge(&newCfg{t: t, edgeId: "smol-0.a-2.a"}) + require.Equal(t, false, isProofEdge(ctx, edge)) + edge = newEdge(&newCfg{t: t, edgeId: "smol-0.a-1.a"}) + require.Equal(t, true, isProofEdge(ctx, edge)) +} + +func Test_isClaimedEdge(t *testing.T) { + ctx := context.Background() + ht := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + } + edge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-32.a"}) + ok, _ := ht.isClaimedEdge(ctx, edge) + require.False(t, ok) + + edge = newEdge(&newCfg{t: t, edgeId: "smol-0.a-1.a"}) + ok, _ = ht.isClaimedEdge(ctx, edge) + require.False(t, ok) + + edge = newEdge(&newCfg{t: t, edgeId: "smol-0.a-1.a"}) + ok, _ = ht.isClaimedEdge(ctx, edge) + require.False(t, ok) + + claimedEdge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-1.a"}) + claimingEdge := newEdge(&newCfg{t: t, edgeId: "big-0.a-32.a", claimId: "blk-0.a-1.a"}) + ht.edges.Put(claimedEdge.Id(), claimedEdge) + ht.edges.Put(claimingEdge.Id(), claimingEdge) + + ok, expectedClaiming := ht.isClaimedEdge(ctx, claimedEdge) + require.True(t, ok) + require.Equal(t, expectedClaiming.Id(), claimingEdge.Id()) +} diff --git a/bold/challenge-manager/challenge-tree/tree.go b/bold/challenge-manager/challenge-tree/tree.go new file mode 100644 index 0000000000..0899223acc --- /dev/null +++ b/bold/challenge-manager/challenge-tree/tree.go @@ -0,0 +1,167 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package challengetree includes logic for keeping track of royal edges within a challenge +// with utilities for computing cumulative path timers for said edges. This is helpful during +// the confirmation process needed by edge trackers. +package challengetree + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" +) + +// MetadataReader can read certain information about edges from the backend. +type MetadataReader interface { + AssertionUnrivaledBlocks(ctx context.Context, assertionHash protocol.AssertionHash) (uint64, error) + TopLevelAssertion(ctx context.Context, edgeId protocol.EdgeId) (protocol.AssertionHash, error) + TopLevelClaimHeights(ctx context.Context, edgeId protocol.EdgeId) (protocol.OriginHeights, error) + SpecChallengeManager() protocol.SpecChallengeManager + ReadAssertionCreationInfo( + ctx context.Context, id protocol.AssertionHash, + ) (*protocol.AssertionCreatedInfo, error) +} + +type creationTime uint64 + +// OriginPlusMutualId combines a mutual id and origin id as a key for a mapping. +// This is used for computing the rivals of an edge, as all rivals share a mutual id. +// However, we also add the origin id as that allows us to namespace to lookup +// to a specific challenge. +type OriginPlusMutualId [64]byte + +func buildEdgeCreationTimeKey(originId protocol.OriginId, mutualId protocol.MutualId) OriginPlusMutualId { + var key OriginPlusMutualId + copy(key[0:32], originId[:]) + copy(key[32:64], mutualId[:]) + return key +} + +// RoyalChallengeTree keeps track of royal edges the honest validator agrees with in a particular challenge. +// All edges tracked in this data structure are part of the same, top-level assertion challenge. +type RoyalChallengeTree struct { + edges *threadsafe.Map[protocol.EdgeId, protocol.SpecEdge] + edgeCreationTimes *threadsafe.Map[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]] + topLevelAssertionHash protocol.AssertionHash + metadataReader MetadataReader + histChecker l2stateprovider.HistoryChecker + validatorName string + totalChallengeLevels uint8 + royalRootEdgesByLevel *threadsafe.Map[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]] +} + +func New( + assertionHash protocol.AssertionHash, + metadataReader MetadataReader, + histChecker l2stateprovider.HistoryChecker, + numBigStepLevels uint8, + validatorName string, +) *RoyalChallengeTree { + return &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](threadsafe.MapWithMetric[protocol.EdgeId, protocol.SpecEdge]("edges")), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](threadsafe.MapWithMetric[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]]("edgeCreationTimes")), + topLevelAssertionHash: assertionHash, + metadataReader: metadataReader, + histChecker: histChecker, + validatorName: validatorName, + // The total number of challenge levels include block challenges, small step challenges, and N big step challenges. + totalChallengeLevels: numBigStepLevels + 2, + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](threadsafe.MapWithMetric[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]]("royalRootEdgesByLevel")), + } +} + +// RoyalBlockChallengeRootEdge gets the royal, root challenge block edge for the top level assertion +// being challenged. +func (ht *RoyalChallengeTree) RoyalBlockChallengeRootEdge() (protocol.ReadOnlyEdge, error) { + // In our locally tracked challenge tree implementation, the + // block challenge level is equal to the total challenge levels - 1. + blockChallengeLevel := protocol.ChallengeLevel(ht.totalChallengeLevels) - 1 + if rootEdges, ok := ht.royalRootEdgesByLevel.TryGet(blockChallengeLevel); ok { + if rootEdges.Len() != 1 { + return nil, fmt.Errorf( + "expected one royal root block challenge edge for challenged assertion %#x", + ht.topLevelAssertionHash, + ) + } + return rootEdges.Get(0).Unwrap(), nil + } + return nil, fmt.Errorf("no royal root edges for block challenge level for assertion %#x", ht.topLevelAssertionHash) +} + +var ( + ErrAlreadyBeingTracked = errors.New("edge already being tracked") + ErrMismatchedChallengeAssertionHash = errors.New("edge challenged assertion hash is not the expected one for the challenge") +) + +func (ht *RoyalChallengeTree) GetEdges() *threadsafe.Map[protocol.EdgeId, protocol.SpecEdge] { + return ht.edges +} + +func (ht *RoyalChallengeTree) GetEdge(edgeId protocol.EdgeId) (protocol.SpecEdge, bool) { + return ht.edges.TryGet(edgeId) +} + +func (ht *RoyalChallengeTree) HasRoyalEdge(edgeId protocol.EdgeId) bool { + return ht.edges.Has(edgeId) +} + +func (ht *RoyalChallengeTree) IsUnrivaledAtBlockNum(edge protocol.ReadOnlyEdge, blockNum uint64) (bool, error) { + return ht.UnrivaledAtBlockNum(edge, blockNum) +} + +func (ht *RoyalChallengeTree) TimeUnrivaled(ctx context.Context, edge protocol.ReadOnlyEdge, blockNum uint64) (uint64, error) { + return ht.LocalTimer(ctx, edge, blockNum) +} + +// Obtains the lowermost edges across all subchallenges that are royal. +// To do this, we fetch all royal, tracked edges that do not have children. +func (ht *RoyalChallengeTree) GetAllRoyalLeaves(ctx context.Context) ([]protocol.SpecEdge, error) { + royalLeaves := make([]protocol.SpecEdge, 0) + if err := ht.edges.ForEach(func(_ protocol.EdgeId, edge protocol.SpecEdge) error { + hasChildren, err := edge.HasChildren(ctx) + if err != nil { + return err + } + if !hasChildren { + royalLeaves = append(royalLeaves, edge) + } + return nil + }); err != nil { + return nil, err + } + return royalLeaves, nil +} + +func (ht *RoyalChallengeTree) BlockChallengeRootEdge(ctx context.Context) (protocol.SpecEdge, error) { + blockChalEdges, ok := ht.royalRootEdgesByLevel.TryGet(protocol.ChallengeLevel(ht.totalChallengeLevels) - 1) + if !ok { + return nil, errors.New("no block challenge root edge found") + } + if blockChalEdges.Len() != 1 { + return nil, errors.New("expected exactly one block challenge root edge") + } + return blockChalEdges.Get(0).Unwrap(), nil +} + +func (ht *RoyalChallengeTree) findClaimingEdge(claimedEdge protocol.EdgeId) (protocol.SpecEdge, bool) { + var foundEdge protocol.SpecEdge + var ok bool + _ = ht.edges.ForEach(func(_ protocol.EdgeId, edge protocol.SpecEdge) error { + if edge.ClaimId().IsNone() { + return nil + } + if edge.ClaimId().Unwrap() == protocol.ClaimId(claimedEdge.Hash) { + foundEdge = edge + ok = true + } + return nil + }) + return foundEdge, ok +} diff --git a/bold/challenge-manager/challenge-tree/tree_test.go b/bold/challenge-manager/challenge-tree/tree_test.go new file mode 100644 index 0000000000..06b8778964 --- /dev/null +++ b/bold/challenge-manager/challenge-tree/tree_test.go @@ -0,0 +1,369 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengetree + +import ( + "context" + "errors" + "math/big" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/challenge-manager/challenge-tree/mock" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/testing/mocks" +) + +func simpleAssertionMetadata() *l2stateprovider.AssociatedAssertionMetadata { + return &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: common.Hash{}, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 0, + }, + BatchLimit: 1, + } +} + +func TestAddEdge(t *testing.T) { + ht := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](), + totalChallengeLevels: 3, + } + ht.topLevelAssertionHash = protocol.AssertionHash{Hash: common.BytesToHash([]byte("foo"))} + ctx := context.Background() + edge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-16.a", createdAt: 1, claimId: "foo"}) + + t.Run("getting top level assertion errored", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: errors.New("bad request"), + } + err := ht.AddEdge(ctx, edge) + require.ErrorContains(t, err, "could not get top level assertion for edge") + }) + t.Run("ignores if disagrees with top level assertion hash of edge", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: nil, + assertionHash: protocol.AssertionHash{Hash: common.BytesToHash([]byte("bar"))}, + } + err := ht.AddEdge(ctx, edge) + require.ErrorIs(t, err, ErrMismatchedChallengeAssertionHash) + }) + t.Run("getting claim heights errored", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: nil, + assertionHash: ht.topLevelAssertionHash, + claimHeightsErr: errors.New("bad request"), + } + ht.royalRootEdgesByLevel.Put(protocol.ChallengeLevel(2), threadsafe.NewSlice[protocol.SpecEdge]()) + honestBlockEdges := ht.royalRootEdgesByLevel.Get(protocol.ChallengeLevel(2)) + honestBlockEdges.Push(edge) + err := ht.AddEdge(ctx, edge) + require.ErrorContains(t, err, "could not get claim heights for edge") + }) + t.Run("checking if agrees with commit errored", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: nil, + assertionHash: ht.topLevelAssertionHash, + } + start, startCommit := edge.StartCommitment() + end, endCommit := edge.EndCommitment() + mockStateManager := &mocks.MockStateManager{} + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(end)), + }, + l2stateprovider.History{ + Height: uint64(start), + MerkleRoot: startCommit, + }, + ).Return(false, errors.New("something went wrong")) + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(end)), + }, + l2stateprovider.History{ + Height: uint64(end), + MerkleRoot: endCommit, + }, + ).Return(false, errors.New("something went wrong")) + ht.histChecker = mockStateManager + err := ht.AddEdge(ctx, edge) + require.ErrorContains(t, err, "could not check history commitment agreement") + }) + t.Run("fully disagrees with edge", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: nil, + assertionHash: ht.topLevelAssertionHash, + } + badEdge := newEdge(&newCfg{t: t, edgeId: "blk-0.f-16.a", createdAt: 1, claimId: "foo"}) + endHeight, endCommit := badEdge.EndCommitment() + mockStateManager := &mocks.MockStateManager{} + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, + l2stateprovider.History{ + Height: uint64(endHeight), + MerkleRoot: endCommit, + }, + ).Return(false, nil) + ht.histChecker = mockStateManager + err := ht.AddEdge(ctx, badEdge) + require.NoError(t, err) + _, ok := badEdge.AsVerifiedHonest() + require.Equal(t, false, ok) + + // Check the edge is not kept track of in the honest edge, but we do track its mutual id. + _, ok = ht.edges.TryGet(badEdge.Id()) + require.Equal(t, false, ok) + key := buildEdgeCreationTimeKey(protocol.OriginId{}, badEdge.MutualId()) + _, ok = ht.edgeCreationTimes.TryGet(key) + require.Equal(t, true, ok) + }) + t.Run("agrees with edge but is not royal", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: nil, + assertionHash: ht.topLevelAssertionHash, + } + rootEdge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-32.a", createdAt: 1, claimId: "foo"}) + ht.royalRootEdgesByLevel.Put(protocol.ChallengeLevel(2), threadsafe.NewSlice[protocol.SpecEdge]()) + honestBlockEdges := ht.royalRootEdgesByLevel.Get(protocol.ChallengeLevel(2)) + honestBlockEdges.Push(rootEdge) + + edge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-16.a", createdAt: 2}) + startHeight, startCommit := edge.StartCommitment() + endHeight, endCommit := edge.EndCommitment() + mockStateManager := &mocks.MockStateManager{} + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, + l2stateprovider.History{ + Height: uint64(startHeight), + MerkleRoot: startCommit, + }, + ).Return(true, nil) + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, + l2stateprovider.History{ + Height: uint64(endHeight), + MerkleRoot: endCommit, + }, + ).Return(true, nil) + ht.histChecker = mockStateManager + err := ht.AddEdge(ctx, edge) + require.NoError(t, err) + _, ok := edge.AsVerifiedHonest() + require.Equal(t, false, ok) + + // Not tracked. + _, ok = ht.edges.TryGet(edge.Id()) + require.Equal(t, false, ok) + // However, exists in the mutual ids mapping. + key := buildEdgeCreationTimeKey(protocol.OriginId{}, edge.MutualId()) + _, ok = ht.edgeCreationTimes.TryGet(key) + require.Equal(t, true, ok) + + // However, we should not have a level zero edge being tracked yet. + blockChallengeEdges := ht.royalRootEdgesByLevel.Get(protocol.ChallengeLevel(2)) + found := blockChallengeEdges.Find(func(_ int, e protocol.SpecEdge) bool { + return e.Id() == edge.Id() + }) + require.Equal(t, false, found) + }) + t.Run("agrees with edge and is a level zero edge", func(t *testing.T) { + ht.metadataReader = &mockMetadataReader{ + assertionErr: nil, + assertionHash: ht.topLevelAssertionHash, + } + edge := newEdge(&newCfg{t: t, edgeId: "blk-0.a-32.a", createdAt: 1, claimId: "foo"}) + endHeight, endCommit := edge.EndCommitment() + mockStateManager := &mocks.MockStateManager{} + mockStateManager.On( + "AgreesWithHistoryCommitment", + ctx, + protocol.NewBlockChallengeLevel(), + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, + l2stateprovider.History{ + Height: uint64(endHeight), + MerkleRoot: endCommit, + }, + ).Return(true, nil) + ht.histChecker = mockStateManager + err := ht.AddEdge(ctx, edge) + require.NoError(t, err) + + // Exists. + _, ok := ht.edges.TryGet(edge.Id()) + require.Equal(t, true, ok) + // Exists in the mutual ids mapping. + key := buildEdgeCreationTimeKey(protocol.OriginId{}, edge.MutualId()) + _, ok = ht.edgeCreationTimes.TryGet(key) + require.Equal(t, true, ok) + + // We should have a level zero edge being tracked. + require.Equal(t, false, ht.royalRootEdgesByLevel.IsEmpty()) + _, ok = ht.royalRootEdgesByLevel.TryGet(protocol.ChallengeLevel(2)) + require.Equal(t, true, ok) + }) +} + +func TestAddHonestEdge(t *testing.T) { + createdAt := uint64(1) + edge := newEdge(&newCfg{t: t, edgeId: "big-0.a-32.a", createdAt: createdAt, claimId: "bar"}) + ht := &RoyalChallengeTree{ + edges: threadsafe.NewMap[protocol.EdgeId, protocol.SpecEdge](), + edgeCreationTimes: threadsafe.NewMap[OriginPlusMutualId, *threadsafe.Map[protocol.EdgeId, creationTime]](), + royalRootEdgesByLevel: threadsafe.NewMap[protocol.ChallengeLevel, *threadsafe.Slice[protocol.SpecEdge]](), + } + ht.topLevelAssertionHash = protocol.AssertionHash{Hash: common.BytesToHash([]byte("foo"))} + edge.MarkAsHonest() + verifiedHonest, _ := edge.AsVerifiedHonest() + err := ht.AddRoyalEdge(verifiedHonest) + require.NoError(t, err) + + // We now check if the challenge tree has a populated + // block challenge level zero edge. + require.Equal(t, 1, ht.royalRootEdgesByLevel.Get(protocol.ChallengeLevel(1)).Len()) + + // Check if it exists in the mutual ids mapping. + mutualId := edge.MutualId() + key := buildEdgeCreationTimeKey(protocol.OriginId{}, mutualId) + mutuals, ok := ht.edgeCreationTimes.TryGet(key) + require.Equal(t, true, ok) + gotCreatedAt, ok := mutuals.TryGet(edge.Id()) + require.Equal(t, true, ok) + require.Equal(t, createdAt, uint64(gotCreatedAt)) + + // Does not add it again. + err = ht.AddRoyalEdge(verifiedHonest) + require.NoError(t, err) + + require.Equal(t, 1, ht.royalRootEdgesByLevel.Get(protocol.ChallengeLevel(1)).Len()) +} + +type mockMetadataReader struct { + assertionHash protocol.AssertionHash + assertionErr error + claimHeights protocol.OriginHeights + claimHeightsErr error + unrivaledAssertionBlocks uint64 + mockManager *mocks.MockSpecChallengeManager +} + +func (m *mockMetadataReader) TopLevelAssertion( + _ context.Context, _ protocol.EdgeId, +) (protocol.AssertionHash, error) { + return m.assertionHash, m.assertionErr +} + +func (m *mockMetadataReader) AssertionUnrivaledBlocks( + _ context.Context, _ protocol.AssertionHash, +) (uint64, error) { + return m.unrivaledAssertionBlocks, nil +} + +func (m *mockMetadataReader) TopLevelClaimHeights( + _ context.Context, _ protocol.EdgeId, +) (protocol.OriginHeights, error) { + return m.claimHeights, m.claimHeightsErr +} + +func (m *mockMetadataReader) SpecChallengeManager() protocol.SpecChallengeManager { + return m.mockManager +} +func (m *mockMetadataReader) ReadAssertionCreationInfo( + _ context.Context, _ protocol.AssertionHash, +) (*protocol.AssertionCreatedInfo, error) { + return &protocol.AssertionCreatedInfo{InboxMaxCount: big.NewInt(1)}, nil +} + +type newCfg struct { + t *testing.T + originId mock.OriginId + edgeId mock.EdgeId + claimId string + createdAt uint64 +} + +func newEdge(cfg *newCfg) *mock.Edge { + cfg.t.Helper() + items := strings.Split(string(cfg.edgeId), "-") + var typ protocol.ChallengeLevel + switch items[0] { + case "blk": + typ = 0 + case "big": + typ = 1 + case "smol": + typ = 2 + } + startData := strings.Split(items[1], ".") + startHeight, err := strconv.ParseUint(startData[0], 10, 64) + require.NoError(cfg.t, err) + startCommit := startData[1] + + endData := strings.Split(items[2], ".") + endHeight, err := strconv.ParseUint(endData[0], 10, 64) + require.NoError(cfg.t, err) + endCommit := endData[1] + + return &mock.Edge{ + EdgeType: typ, + OriginID: cfg.originId, + ID: cfg.edgeId, + StartHeight: startHeight, + ClaimID: cfg.claimId, + StartCommit: mock.Commit(startCommit), + EndHeight: endHeight, + EndCommit: mock.Commit(endCommit), + LowerChildID: "", + UpperChildID: "", + CreationBlock: cfg.createdAt, + TotalChallengeLevels: 3, + } +} diff --git a/bold/challenge-manager/challenges.go b/bold/challenge-manager/challenges.go new file mode 100644 index 0000000000..9e660c92b2 --- /dev/null +++ b/bold/challenge-manager/challenges.go @@ -0,0 +1,200 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengemanager + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/log" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + edgetracker "github.com/offchainlabs/bold/challenge-manager/edge-tracker" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" +) + +// HandleCorrectRival is called when the assertion manager has posted a correct +// rival assertion on the chain and the chllenge manager needs to create a +// challenge committing to the correct assertion to rival one or more incorrect +// assertions. +func (m *Manager) HandleCorrectRival(ctx context.Context, riv protocol.AssertionHash) error { + challengeSubmitted, err := m.ChallengeAssertion(ctx, riv) + if err != nil { + return err + } + if challengeSubmitted { + challengeSubmittedCounter.Inc(1) + } + m.logChallengeConfigs() + return nil +} + +// ChallengeAssertion initiates a challenge committing to an assertion added to +// the protocol by finding its parent assertion and starting a challenge +// transaction. If the challenge creation is successful, the challenge manager +// adds a leaf with an associated history commitment to it and spawns a +// challenge tracker in the background. +// +// id is the id of the assertion that this validator agrees with. +func (m *Manager) ChallengeAssertion(ctx context.Context, id protocol.AssertionHash) (bool, error) { + assertion, err := m.chain.GetAssertion(ctx, &bind.CallOpts{Context: ctx}, id) + if err != nil { + return false, errors.Wrapf(err, "could not get assertion to challenge with id %#x", id) + } + if m.claimedAssertionsInChallenge.Has(id) { + log.Debug(fmt.Sprintf("Already challenged assertion with id %#x, skipping", id.Hash)) + return false, nil + } + assertionStatus, err := m.chain.AssertionStatus(ctx, assertion.Id()) + if err != nil { + return false, errors.Wrapf(err, "could not get assertion status with id %#x", id) + } + if assertionStatus == protocol.AssertionConfirmed { + log.Info("Skipping challenge submission on already confirmed assertion", "assertionHash", id.Hash) + return false, nil + } + // We then add a level zero edge to initiate a challenge. + levelZeroEdge, shouldTrack, edgeTrackerAssertionInfo, alreadyExists, err := m.addBlockChallengeLevelZeroEdge(ctx, assertion) + if err != nil { + return false, fmt.Errorf("could not add block challenge level zero edge %v: %w", m.name, err) + } + if !shouldTrack { + log.Info("Challenge not in list of specified challenges to track, skipping", "assertionHash", id.Hash) + return false, nil + } + log.Info("Opening a challenge on an observed assertion", + "assertionHash", id.Hash, + "validatorName", m.name, + ) + if alreadyExists { + log.Info("Challenge on assertion already exists, now tracking it locally", "assertionHash", id.Hash) + m.claimedAssertionsInChallenge.Insert(id) + return false, nil + } + if verifiedErr := m.watcher.AddVerifiedHonestEdge(ctx, levelZeroEdge); verifiedErr != nil { + fields := []any{ + "edgeId", levelZeroEdge.Id(), + "err", verifiedErr, + } + log.Error("could not add verified honest edge to chain watcher", fields...) + } + // Start tracking the challenge. + tracker, err := edgetracker.New( + ctx, + levelZeroEdge, + m.chain, + m.stateManager, + m.watcher, + m, + edgeTrackerAssertionInfo, + edgetracker.WithTimeReference(m.timeRef), + edgetracker.WithValidatorName(m.name), + ) + if err != nil { + return false, err + } + m.LaunchThread(tracker.Spawn) + + log.Info("Successfully opened a challenge on an invalid assertion", + "name", m.name, + "assertionHash", containers.Trunc(id.Bytes()), + "fromBatch", edgeTrackerAssertionInfo.FromState.Batch, + "fromPosInBatch", edgeTrackerAssertionInfo.FromState.PosInBatch, + "batchLimit", edgeTrackerAssertionInfo.BatchLimit, + ) + return true, nil +} + +func (m *Manager) addBlockChallengeLevelZeroEdge( + ctx context.Context, + assertion protocol.Assertion, +) (protocol.VerifiedRoyalEdge, bool, *l2stateprovider.AssociatedAssertionMetadata, bool, error) { + creationInfo, err := m.chain.ReadAssertionCreationInfo(ctx, assertion.Id()) + if err != nil { + return nil, false, nil, false, errors.Wrap(err, "could not get assertion creation info") + } + if !m.watcher.AllowTrackingEdgeWithParentHash(creationInfo.ParentAssertionHash) { + return nil, false, nil, false, nil + } + prevCreationInfo, err := m.chain.ReadAssertionCreationInfo(ctx, creationInfo.ParentAssertionHash) + if err != nil { + return nil, false, nil, false, errors.Wrap(err, "could not get assertion creation info") + } + if prevCreationInfo.InboxMaxCount == nil { + return nil, false, nil, false, errors.New("prevCreationInfo.InboxMaxCount is nil") + } + if !prevCreationInfo.InboxMaxCount.IsUint64() { + return nil, false, nil, false, fmt.Errorf("inbox max count is not a uint64: %v", prevCreationInfo.InboxMaxCount) + } + fromState := protocol.GoGlobalStateFromSolidity(creationInfo.BeforeState.GlobalState) + assertionMetadata := &l2stateprovider.AssociatedAssertionMetadata{ + FromState: fromState, + BatchLimit: l2stateprovider.Batch(prevCreationInfo.InboxMaxCount.Uint64()), + WasmModuleRoot: prevCreationInfo.WasmModuleRoot, + ClaimedAssertionHash: creationInfo.AssertionHash, + } + + startCommit, err := m.stateManager.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + }, + ) + if err != nil { + return nil, false, nil, false, err + } + manager := m.chain.SpecChallengeManager() + layerZeroHeights := manager.LayerZeroHeights() + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(layerZeroHeights.BlockChallengeHeight)), + } + endCommit, err := m.stateManager.HistoryCommitment( + ctx, + req, + ) + if err != nil { + return nil, false, nil, false, err + } + precomputedEdgeId, err := manager.CalculateEdgeId( + ctx, + protocol.NewBlockChallengeLevel(), + protocol.OriginId(creationInfo.ParentAssertionHash.Hash), + protocol.Height(startCommit.Height), + startCommit.Merkle, + protocol.Height(endCommit.Height), + endCommit.Merkle, + ) + if err != nil { + return nil, false, nil, false, errors.Wrap(err, "could not calculate edge id") + } + someLevelZeroEdge, err := manager.GetEdge(ctx, precomputedEdgeId) + + // If the edge already exists, we return true and everything else nil. + if err == nil && !someLevelZeroEdge.IsNone() { + return nil, true, nil, true, nil + } + startEndPrefixProof, err := m.stateManager.PrefixProof( + ctx, + req, + l2stateprovider.Height(0), + ) + if err != nil { + return nil, false, nil, false, err + } + edge, err := manager.AddBlockChallengeLevelZeroEdge(ctx, assertion, startCommit, endCommit, startEndPrefixProof) + if err != nil { + return nil, false, nil, false, errors.Wrap(err, "could not post block challenge root edge") + } + return edge, true, assertionMetadata, false, nil +} diff --git a/bold/challenge-manager/edge-tracker/challenge_confirmation.go b/bold/challenge-manager/edge-tracker/challenge_confirmation.go new file mode 100644 index 0000000000..982ffa039a --- /dev/null +++ b/bold/challenge-manager/edge-tracker/challenge_confirmation.go @@ -0,0 +1,352 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package edgetracker + +import ( + "context" + "fmt" + "math/big" + "strings" + "time" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + retry "github.com/offchainlabs/bold/runtime" +) + +var onchainTimerDifferAfterConfirmationJobCounter = metrics.NewRegisteredCounter("arb/validator/tracker/onchain_timer_differed_after_confirmation_job", nil) + +// Defines a struct which can handle confirming of an entire challenge tree +// in the BOLD protocol. It does so by updating the inherited timers of royal edges +// onchain until the root of the tree has a timer >= a challenge period. At that point, +// it ensures to confirm that edge. If this is not the case, it will return an error +// and write data to disk to help with debugging the issue. +type challengeConfirmer struct { + reader HonestChallengeTreeReader + writer ChainWriter + backend protocol.ChainBackend + validatorName string + averageTimeForBlockCreation time.Duration + chain protocol.Protocol +} + +// Defines a chain writer interface that is +// used to update the cached inherited timers of edges +// onchain. +type ChainWriter interface { + MultiUpdateInheritedTimers( + ctx context.Context, + challengeBranch []protocol.ReadOnlyEdge, + desiredNewTimerForLastEdge uint64, + ) (*types.Transaction, error) +} + +func newChallengeConfirmer( + challengeReader HonestChallengeTreeReader, + chainWriter ChainWriter, + backend protocol.ChainBackend, + validatorName string, + chain protocol.Protocol, +) *challengeConfirmer { + return &challengeConfirmer{ + reader: challengeReader, + writer: chainWriter, + validatorName: validatorName, + backend: backend, + chain: chain, + } +} + +// A challenge confirmation job will attempt to confirm a challenge all the way up to the top, +// block challenge root edge by updating all the inherited timers of royal edges along the way, +// across all open subchallenges, until the onchain timer of the block challenge root edge +// is greater than or equal to a challenge period. +// +// It works by updating royal branches of the challenge tree, starting from the bottom-most, +// deepest level royal edges. For each branch, update the onchain inherited timers +// of the ancestors along the way. +// +// This function must only be called once the locally computed value of the block challenge, royal root +// edge has an inherited timer that is confirmable. This function MUST complete, and it will retry +// any external call if it errors during its execution. +func (cc *challengeConfirmer) beginConfirmationJob( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + computedTimer uint64, + royalRootEdge protocol.VerifiedRoyalEdge, + claimedAssertionHash protocol.AssertionHash, + challengePeriodBlocks uint64, +) error { + fields := []any{ + "validatorName", cc.validatorName, + "challengedAssertion", fmt.Sprintf("%#x", challengedAssertionHash.Hash[:4]), + "essentialEdgeId", fmt.Sprintf("%#x", royalRootEdge.Id().Bytes()[:4]), + "challengeLevel", royalRootEdge.GetChallengeLevel(), + } + log.Info("Starting challenge confirmation job", fields...) + // Find the bottom-most royal edges that exist in our local challenge tree, each one + // will be the base of a branch we will update. + royalTreeLeaves, err := retry.UntilSucceeds(ctx, func() ([]protocol.SpecEdge, error) { + edges, innerErr := cc.reader.LowerMostRoyalEdges(ctx, challengedAssertionHash) + if innerErr != nil { + fields = append(fields, "err", innerErr) + log.Error("Could not fetch lower-most royal edges", fields) + return nil, innerErr + } + return edges, nil + }) + if err != nil { + return err + } + + log.Info(fmt.Sprintf("Obtained all %d royal tree leaves for confirmation job", len(royalTreeLeaves)), fields...) + // For each branch, compute the royal ancestor branch up to the root of the tree. + // The branch should contain royal ancestors ordered from a bottom-most leaf edge to the root edge + // of the block level challenge, meaning it should also include claim id links. + royalBranches := make([][]protocol.ReadOnlyEdge, 0) + for _, edge := range royalTreeLeaves { + branch := []protocol.ReadOnlyEdge{edge} + ancestors, err2 := retry.UntilSucceeds(ctx, func() ([]protocol.ReadOnlyEdge, error) { + resp, innerErr := cc.reader.ComputeAncestors( + ctx, challengedAssertionHash, edge.Id(), + ) + if innerErr != nil { + fields = append(fields, "err", innerErr) + log.Error("Could not compute ancestors for edge", fields) + return nil, innerErr + } + return resp, nil + }) + if err2 != nil { + return err2 + } + branch = append(branch, ancestors...) + royalBranches = append(royalBranches, branch) + } + log.Info("Computed all the royal branches to update onchain", fields...) + + // For each branch, update the inherited timers onchain via transactions and don't + // wait for them to reach safe head. + var lastPropagationTx *types.Transaction + for i, branch := range royalBranches { + tx, innerErr := cc.propageTimerUpdateToBranch( + ctx, + royalRootEdge, + computedTimer, + challengedAssertionHash, + i, + len(royalBranches), + branch, + challengePeriodBlocks, + claimedAssertionHash, + ) + if innerErr != nil { + return innerErr + } + lastPropagationTx = tx + } + + // Instead, we wait for the last transaction we made to reach `safe` head if it is not nil + // so that we can avoid unnecessary delays per tx. + if lastPropagationTx != nil { + receipt, innerErr := cc.backend.TransactionReceipt(ctx, lastPropagationTx.Hash()) + if innerErr != nil { + return innerErr + } + if err = cc.waitForTxToBeSafe(ctx, cc.backend, lastPropagationTx, receipt); err != nil { + return err + } + } + + onchainInheritedTimer, err := retry.UntilSucceeds(ctx, func() (protocol.InheritedTimer, error) { + timer, innerErr := royalRootEdge.LatestInheritedTimer(ctx) + if innerErr != nil { + fields = append(fields, "err", innerErr) + log.Error("Could not get inherited timer for edge", fields) + return 0, innerErr + } + return timer, nil + }) + if err != nil { + return err + } + + // If the onchain timer is not >= a challenge period by the end of this job, + // it means the challenge has yet to complete and our local computation was incorrect. + // In this scenario, we can dump the confirmation job of royal edges for manual + // inspection and debugging + if onchainInheritedTimer < protocol.InheritedTimer(challengePeriodBlocks) { + onchainTimerDifferAfterConfirmationJobCounter.Inc(1) + log.Error( + fmt.Sprintf("Onchain timer %d was not >= %d after confirmation job", onchainInheritedTimer, challengePeriodBlocks), + fields..., + ) + return fmt.Errorf( + "onchain timer %d after confirmation job was executed < challenge period %d", + onchainInheritedTimer, + challengePeriodBlocks, + ) + } + log.Info("Confirming essential root edge by timer", fields...) + if _, err = retry.UntilSucceeds(ctx, func() (bool, error) { + if _, innerErr := royalRootEdge.ConfirmByTimer(ctx, claimedAssertionHash); innerErr != nil { + fields = append(fields, "err", innerErr) + log.Error("Could not confirm edge by timer", fields) + return false, innerErr + } + return false, nil + }); err != nil { + return err + } + log.Info("Essential root edge confirmed by timer", fields...) + return nil +} + +func (cc *challengeConfirmer) propageTimerUpdateToBranch( + ctx context.Context, + royalRootEdge protocol.VerifiedRoyalEdge, + computedLocalTimer uint64, + challengedAssertionHash protocol.AssertionHash, + branchIdx, + totalBranches int, + branch []protocol.ReadOnlyEdge, + challengePeriodBlocks uint64, + claimedAssertionHash protocol.AssertionHash, +) (*types.Transaction, error) { + if len(branch) == 0 { + return nil, nil + } + fields := []any{ + "validatorName", cc.validatorName, + "challengedAssertionHash", fmt.Sprintf("%#x", challengedAssertionHash.Hash[:4]), + "claimedAssertionHash", fmt.Sprintf("%#x", claimedAssertionHash.Hash[:4]), + "royalRootBlockChallengeEdge", fmt.Sprintf("%#x", royalRootEdge.Id().Bytes()[:4]), + "branch", fmt.Sprintf("%d/%d", branchIdx, totalBranches-1), + "challengeLevel", fmt.Sprintf("%d", royalRootEdge.GetChallengeLevel()), + } + tx, err := retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + tx, innerErr := cc.writer.MultiUpdateInheritedTimers(ctx, branch, computedLocalTimer) + if innerErr != nil { + // If are trying to update the inherited timers of a branch to a value that + // is less than what already exists onchain, we will receive the below error. + // This means we can finish early as the onchain timer is already sufficient, + // and our transaction reverted. We can gracefully continue if so. + if strings.Contains(innerErr.Error(), protocol.ErrCachedTimeSufficient) { + log.Info("Onchain, cached timer for branch is already sufficient, so no need to transact", fields...) + return nil, nil + } + fields = append(fields, "err", innerErr) + log.Error("Could not transact multi-update inherited timers", fields...) + return nil, innerErr + } + return tx, nil + }) + if err != nil { + return nil, err + } + + // In each iteration, check if the root edge has a timer >= a challenge period + rootTimer, err := retry.UntilSucceeds(ctx, func() (protocol.InheritedTimer, error) { + timer, innerErr := royalRootEdge.LatestInheritedTimer(ctx) + if innerErr != nil { + fields = append(fields, "err", innerErr) + log.Error("Could not get inherited timer for edge", fields...) + return 0, innerErr + } + return timer, nil + }) + if err != nil { + return nil, err + } + + fields = append(fields, "onchainTimer", rootTimer) + log.Info("Updated the onchain inherited timer for royal branch", fields...) + + if uint64(rootTimer) < challengePeriodBlocks { + return tx, nil + } + + // If yes, we confirm the root edge and finish early, we do so. + log.Info("Branch was confirmable by timer", fields...) + tx, err = retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + innerTx, innerErr := royalRootEdge.ConfirmByTimer(ctx, claimedAssertionHash) + if innerErr != nil { + fields = append(fields, "err", innerErr) + log.Error("Could not confirm edge by timer early with confirmable branch", fields...) + return nil, innerErr + } + return innerTx, nil + }) + if err != nil { + return nil, err + } + log.Info("Essential root edge confirmed by timer", fields...) + return tx, nil +} + +// waitForTxToBeSafe waits for the transaction to be mined in a block that is safe. +func (cc *challengeConfirmer) waitForTxToBeSafe( + ctx context.Context, + backend protocol.ChainBackend, + tx *types.Transaction, + receipt *types.Receipt, +) error { + for { + if ctx.Err() != nil { + return ctx.Err() + } + safeBlockNum := cc.chain.GetDesiredRpcHeadBlockNumber() + latestSafeHeader, err := backend.HeaderByNumber(ctx, big.NewInt(int64(safeBlockNum))) + if err != nil { + return err + } + if !latestSafeHeader.Number.IsUint64() { + return errors.New("block number is not uint64") + } + latestSafeHeaderNumber := latestSafeHeader.Number.Uint64() + txSafe := latestSafeHeaderNumber >= receipt.BlockNumber.Uint64() + + // If the tx is not yet safe, we can simply wait. + if !txSafe { + var blocksLeftForTxToBeSafe int64 + if receipt.BlockNumber.Uint64() > latestSafeHeaderNumber { + blocksLeftForTxToBeSafe = 0 + } else { + blocksLeftForTxToBeSafe, err = safecast.ToInt64(latestSafeHeaderNumber - receipt.BlockNumber.Uint64()) + if err != nil { + return errors.Wrap(err, "could not convert blocks left for tx to be safe to int64") + } + } + timeToWait := cc.averageTimeForBlockCreation * time.Duration(blocksLeftForTxToBeSafe) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(timeToWait): + } + } else { + break + } + } + + // This is to handle the case where the transaction is mined in a block, but then the block is reorged. + // In this case, we want to wait for the transaction to be mined again. + receiptLatest, err := bind.WaitMined(ctx, backend, tx) + if err != nil { + return err + } + // If the receipt block number is different from the latest receipt block number, we wait for the transaction + // to be in the safe block again. + if receiptLatest.BlockNumber.Cmp(receipt.BlockNumber) != 0 { + return cc.waitForTxToBeSafe(ctx, backend, tx, receiptLatest) + } + return nil +} diff --git a/bold/challenge-manager/edge-tracker/fsm_states.go b/bold/challenge-manager/edge-tracker/fsm_states.go new file mode 100644 index 0000000000..c65b3bd4e4 --- /dev/null +++ b/bold/challenge-manager/edge-tracker/fsm_states.go @@ -0,0 +1,100 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package edgetracker + +import ( + "fmt" +) + +// State defines a finite state machine that aids +// in deciding a challenge edge tracker's actions. +type State uint8 + +const ( + // Start state of 0 can never happen to avoid silly mistakes with default Go values. + _ State = iota + // The start state of the tracker. + EdgeStarted + // The edge being tracked is at a one step proof. + EdgeAtOneStepProof + // The tracker is adding a subchallenge leaf on the edge's subchallenge. + EdgeAddingSubchallengeLeaf + // The tracker is attempting a bisection move. + EdgeBisecting + // Intermediary state in which an edge is doing nothing else but awaiting confirmation + // whenever it is possible. + EdgeAwaitingChallengeCompletion +) + +// String turns an edge tracker state into a readable string. +func (s State) String() string { + switch s { + case EdgeStarted: + return "started" + case EdgeAtOneStepProof: + return "one_step_proof" + case EdgeAddingSubchallengeLeaf: + return "adding_subchallenge_leaf" + case EdgeBisecting: + return "bisecting" + case EdgeAwaitingChallengeCompletion: + return "awaiting_challenge_completion" + default: + return "invalid" + } +} + +// Defines structs that characterize actions an edge tracker +// can take to transition between states in its finite state machine. +type edgeTrackerAction interface { + fmt.Stringer + isEdgeTrackerAction() bool +} + +// Transitions the edge tracker back to a start state. +type edgeBackToStart struct{} + +// Tracker will act if the edge is at a one step proof. +type edgeHandleOneStepProof struct{} + +// Tracker will add a subchallenge on its edge's subchallenge. +type edgeOpenSubchallengeLeaf struct{} + +// Tracker will attempt to bisect its edge. +type edgeBisect struct{} + +type edgeAwaitChallengeCompletion struct{} + +func (edgeBackToStart) String() string { + return "back_to_start" +} +func (edgeHandleOneStepProof) String() string { + return "check_one_step_proof" +} +func (edgeOpenSubchallengeLeaf) String() string { + return "open_subchallenge_leaf" +} +func (edgeBisect) String() string { + return "bisect" +} +func (edgeAwaitChallengeCompletion) String() string { + return "await_challenge_completion" +} + +func (edgeBackToStart) isEdgeTrackerAction() bool { + return true +} +func (edgeHandleOneStepProof) isEdgeTrackerAction() bool { + return true +} +func (edgeOpenSubchallengeLeaf) isEdgeTrackerAction() bool { + return true +} +func (edgeBisect) isEdgeTrackerAction() bool { + return true +} +func (edgeAwaitChallengeCompletion) isEdgeTrackerAction() bool { + return true +} diff --git a/bold/challenge-manager/edge-tracker/tracker.go b/bold/challenge-manager/edge-tracker/tracker.go new file mode 100644 index 0000000000..69051f9726 --- /dev/null +++ b/bold/challenge-manager/edge-tracker/tracker.go @@ -0,0 +1,885 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package edgetracker contains the logic for tracking an edge in the challenge manager. It keeps +// track of edges created and their own state transitions until an eventual confirmation. +package edgetracker + +import ( + "context" + "fmt" + "time" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + challengetree "github.com/offchainlabs/bold/challenge-manager/challenge-tree" + "github.com/offchainlabs/bold/containers" + "github.com/offchainlabs/bold/containers/events" + "github.com/offchainlabs/bold/containers/fsm" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/math" + "github.com/offchainlabs/bold/state-commitments/history" + utilTime "github.com/offchainlabs/bold/time" +) + +var ( + errBadOneStepProof = errors.New("bad one step proof data") + spawnedCounter = metrics.NewRegisteredCounter("arb/validator/tracker/spawned", nil) + bisectedCounter = metrics.NewRegisteredCounter("arb/validator/tracker/bisected", nil) + layerZeroLeafCounter = metrics.NewRegisteredCounter("arb/validator/tracker/layer_zero_leaves", nil) +) + +// HonestChallengeTreeReader defines a type which can retrieve information about +// an edge to determine if it can be confirmed via different means. For example, +// checking if a confirmed edge exists that claims a specified edge id as its claim id, +// or retrieving the cumulative, honest path timer for an edge and its honest ancestors. +// This information is used in order to confirm edges onchain. +type HonestChallengeTreeReader interface { + LowerMostRoyalEdges( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + ) ([]protocol.SpecEdge, error) + ComputeAncestors( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + edgeId protocol.EdgeId, + ) ([]protocol.ReadOnlyEdge, error) + ClosestEssentialAncestor( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + edge protocol.VerifiedRoyalEdge, + ) (protocol.ReadOnlyEdge, error) + IsEssentialAncestorConfirmable( + ctx context.Context, + edge protocol.SpecEdge, + challengedAssertionHash protocol.AssertionHash, + confirmationThreshold uint64, + ) (bool, error) + IsConfirmableEssentialEdge( + ctx context.Context, + challengedAssertionHash protocol.AssertionHash, + essentialEdgeId protocol.EdgeId, + confirmationThreshold uint64, + ) (confirmable bool, essentialPaths []challengetree.EssentialPath, timer uint64, err error) +} + +// HonestChallengeTreeWriter defines a type which can not only read information +// about the honest challenge tree, but also add a verified honest edge to the tree. +type HonestChallengeTreeWriter interface { + HonestChallengeTreeReader + AddVerifiedHonestEdge( + ctx context.Context, verifiedHonest protocol.VerifiedRoyalEdge, + ) error +} + +// ChallengeTracker defines a type which can keep track of edge spawner goroutines +// and remove them as needed upon confirmation. +type ChallengeTracker interface { + IsTrackingEdge(protocol.EdgeId) bool + MarkTrackedEdge(protocol.EdgeId, *Tracker) + RemovedTrackedEdge(protocol.EdgeId) + NewBlockSubscriber() *events.Producer[*types.Header] +} + +type Opt func(et *Tracker) + +// WithTimeReference allows setting the timer used by the tracker to determine that time +// passed in accordance with the act interval set with [WithActInterval]. The default is +// to use [github.com/offchainlabs/bold/time.NewRealTimeReference]. +// This is useful for testing with a fake time reference to avoid waiting for real time. +func WithTimeReference(ref utilTime.Reference) Opt { + return func(et *Tracker) { + et.timeRef = ref + } +} + +// WithValidatorName associates a name to the running validator. This name is used only for logging +// and is not exposed externally. This is particularly useful for debugging purposes. +func WithValidatorName(name string) Opt { + return func(et *Tracker) { + et.validatorName = name + } +} + +// WithFSMOpts sets any FSM options to be used when creating the tracker's FSM. +func WithFSMOpts(opts ...fsm.Opt[edgeTrackerAction, State]) Opt { + return func(et *Tracker) { + et.fsmOpts = opts + } +} + +type Tracker struct { + edge protocol.VerifiedRoyalEdge + fsm *fsm.Fsm[edgeTrackerAction, State] + fsmOpts []fsm.Opt[edgeTrackerAction, State] + timeRef utilTime.Reference + validatorName string + chain protocol.Protocol + stateProvider l2stateprovider.Provider + chainWatcher HonestChallengeTreeWriter + challengeManager ChallengeTracker + associatedAssertionMetadata *l2stateprovider.AssociatedAssertionMetadata + challengeConfirmer *challengeConfirmer +} + +func New( + ctx context.Context, + edge protocol.VerifiedRoyalEdge, + chain protocol.Protocol, + stateProvider l2stateprovider.Provider, + chainWatcher HonestChallengeTreeWriter, + challengeManager ChallengeTracker, + assertionCreationInfo *l2stateprovider.AssociatedAssertionMetadata, + opts ...Opt, +) (*Tracker, error) { + tr := &Tracker{ + edge: edge, + chain: chain, + stateProvider: stateProvider, + chainWatcher: chainWatcher, + challengeManager: challengeManager, + associatedAssertionMetadata: assertionCreationInfo, + timeRef: utilTime.NewRealTimeReference(), + } + for _, o := range opts { + o(tr) + } + chalManager := chain.SpecChallengeManager() + tr.challengeConfirmer = newChallengeConfirmer(chainWatcher, chalManager, chain.Backend(), tr.validatorName, chain) + fsm, err := newEdgeTrackerFsm( + EdgeStarted, + tr.fsmOpts..., + ) + if err != nil { + return nil, err + } + tr.fsm = fsm + return tr, nil +} + +func (et *Tracker) AssertionInfo() *l2stateprovider.AssociatedAssertionMetadata { + return et.associatedAssertionMetadata +} + +func (et *Tracker) EdgeId() protocol.EdgeId { + return et.edge.Id() +} + +func (et *Tracker) ChallengeManager() ChallengeTracker { + return et.challengeManager +} + +type FSMStateSummary struct { + CurrentState string + Error error +} + +func (et *Tracker) FSMSummary() *FSMStateSummary { + curr := et.fsm.Current() + return &FSMStateSummary{ + CurrentState: curr.State.String(), + Error: curr.Error, + } +} + +func (et *Tracker) Spawn(ctx context.Context) { + // No-op if we are already tracking this edge in our challenge manager. + if et.challengeManager.IsTrackingEdge(et.edge.Id()) { + return + } + fields := et.uniqueTrackerLogFields() + log.Info("Now tracking challenge edge locally and making moves", fields...) + spawnedCounter.Inc(1) + et.challengeManager.MarkTrackedEdge(et.edge.Id(), et) + + subscription := et.challengeManager.NewBlockSubscriber().Subscribe() + for { + _, shouldExit := subscription.Next(ctx) + if ctx.Err() != nil || shouldExit { + log.Debug("Edge tracker goroutine exiting", fields...) + spawnedCounter.Dec(1) + return + } + if et.ShouldDespawn(ctx) { + log.Info("Tracked edge received notice it should exit - now despawning", fields...) + spawnedCounter.Dec(1) + et.challengeManager.RemovedTrackedEdge(et.edge.Id()) + return + } + if err := et.Act(ctx); err != nil { + log.Error("Could not act with edge tracker", append(fields, "err", err)...) + } + } +} + +func (et *Tracker) CurrentState() State { + return et.fsm.Current().State +} + +func (et *Tracker) Act(ctx context.Context) error { + fields := et.uniqueTrackerLogFields() + current := et.fsm.Current() + switch current.State { + // Start state. + case EdgeStarted: + canOsp, err := canOneStepProve(ctx, et.edge) + if err != nil { + log.Error("Could not check if edge can be one step proven", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + wasConfirmed, err := et.tryToConfirmEdge(ctx) + if err != nil { + log.Error("Could not check if edge can be confirmed from start state", append(fields, "err", err)...) + et.fsm.MarkError(err) + } + if wasConfirmed { + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + } + hasRival, err := et.edge.HasRival(ctx) + if err != nil { + log.Error("Could not check if edge has rival", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + if !hasRival { + return et.fsm.Do(edgeBackToStart{}) + } + if canOsp { // Implicitly, the edge has a rival. + return et.fsm.Do(edgeHandleOneStepProof{}) + } + atOneStepFork, err := et.edge.HasLengthOneRival(ctx) + if err != nil { + log.Error("Could not check if edge has length one rival", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + if atOneStepFork { + return et.fsm.Do(edgeOpenSubchallengeLeaf{}) + } + return et.fsm.Do(edgeBisect{}) + // Edge is at a one-step-proof in a small-step challenge. + case EdgeAtOneStepProof: + ok, err := et.isEssentialAncestorConfirmable(ctx) + if err != nil { + log.Error("Could not check if closest essential ancestor is confirmable", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + if ok { + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + } + if err := et.submitOneStepProof(ctx); err != nil { + log.Error("Could not submit one step proof", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + // Edge tracker should add a subchallenge. + case EdgeAddingSubchallengeLeaf: + ok, err := et.isEssentialAncestorConfirmable(ctx) + if err != nil { + log.Error("Could not check if closest essential ancestor is confirmable", fields, "err", err) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + if ok { + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + } + if err := et.openSubchallenge(ctx); err != nil { + log.Error("Could not open subchallenge leaf", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + layerZeroLeafCounter.Inc(1) + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + // Edge should bisect. + case EdgeBisecting: + ok, err := et.isEssentialAncestorConfirmable(ctx) + if err != nil { + log.Error("Could not check if closest essential ancestor is confirmable", fields, "err", err) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + if ok { + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + } + lowerChild, upperChild, err := et.bisect(ctx) + if err != nil { + log.Error("Could not bisect", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + bisectedCounter.Inc(1) + + firstTracker, err := New( + ctx, + lowerChild, + et.chain, + et.stateProvider, + et.chainWatcher, + et.challengeManager, + et.associatedAssertionMetadata, + WithTimeReference(et.timeRef), + WithValidatorName(et.validatorName), + WithFSMOpts(et.fsmOpts...), + ) + if err != nil { + log.Error("Could not create new edge tracker", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + secondTracker, err := New( + ctx, + upperChild, + et.chain, + et.stateProvider, + et.chainWatcher, + et.challengeManager, + et.associatedAssertionMetadata, + WithTimeReference(et.timeRef), + WithValidatorName(et.validatorName), + WithFSMOpts(et.fsmOpts...), + ) + if err != nil { + log.Error("Could not create new edge tracker", append(fields, "err", err)...) + et.fsm.MarkError(err) + return et.fsm.Do(edgeBackToStart{}) + } + go firstTracker.Spawn(ctx) + go secondTracker.Spawn(ctx) + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + case EdgeAwaitingChallengeCompletion: + _, err := et.tryToConfirmEdge(ctx) + if err != nil { + log.Error("Could not check if edge can be confirmed", append(fields, "err", err)...) + et.fsm.MarkError(err) + } + return et.fsm.Do(edgeAwaitChallengeCompletion{}) + default: + return fmt.Errorf("invalid state: %s", current.State) + } +} + +// ShouldDespawn checks if an edge tracker should despawn and no longer act. +// Every edge tracker needs to have a despawn condition +// to ensure goroutines are cleaned up. +func (et *Tracker) ShouldDespawn(ctx context.Context) bool { + // If the edge is an essential root, it should despawn once it is confirmed. + fields := et.uniqueTrackerLogFields() + if et.edge.ClaimId().IsSome() { + status, err := et.edge.Status(ctx) + if err != nil { + log.Error("Could not get edge status", append(fields, "err", err)...) + return false + } + return status == protocol.EdgeConfirmed + } + // Else if the edge is a NON-essential root: + canOsp, err := canOneStepProve(ctx, et.edge) + if err != nil { + log.Error("Could not check if edge can be one step proven", append(fields, "err", err)...) + return false + } + // If the edge is a small step edge of length one, exit once it is confirmed by OSP. + if canOsp { + status, err2 := et.edge.Status(ctx) + if err2 != nil { + log.Error("Could not get edge status", append(fields, "err", err2)...) + return false + } + return status == protocol.EdgeConfirmed + } + assertionHash, err := et.edge.AssertionHash(ctx) + if err != nil { + log.Error("Could not get edge assertion hash", append(fields, "err", err)...) + return false + } + closestEssential, err := et.chainWatcher.ClosestEssentialAncestor(ctx, assertionHash, et.edge) + if err != nil { + log.Error("Could not get edge closest essential ancestor", append(fields, "err", err)...) + return false + } + status, err := closestEssential.Status(ctx) + if err != nil { + log.Error("Could not get closest essential ancestor status", append(fields, "err", err)...) + return false + } + return status == protocol.EdgeConfirmed +} + +func (et *Tracker) uniqueTrackerLogFields() []any { + startHeight, startCommit := et.edge.StartCommitment() + endHeight, endCommit := et.edge.EndCommitment() + chalLevel := et.edge.GetChallengeLevel() + return []any{ + "id", fmt.Sprintf("%#x", et.edge.Id().Bytes()[:4]), + "fromBatch", et.associatedAssertionMetadata.FromState.Batch, + "fromPosInBatch", et.associatedAssertionMetadata.FromState.PosInBatch, + "batchLimit", et.associatedAssertionMetadata.BatchLimit, + "claimedAssertionHash", fmt.Sprintf("%#x", et.associatedAssertionMetadata.ClaimedAssertionHash.Hash[:4]), + "startHeight", startHeight, + "startCommit", fmt.Sprintf("%#x", startCommit[:4]), + "endHeight", endHeight, + "endCommit", fmt.Sprintf("%#x", endCommit[:4]), + "validatorName", et.validatorName, + "challengeType", chalLevel.String(), + "originId", fmt.Sprintf("%#x", common.Hash(et.edge.OriginId()).Bytes()[:4]), + "mutualId", fmt.Sprintf("%#x", common.Hash(et.edge.MutualId()).Bytes()[:8]), + } +} + +func (et *Tracker) tryToConfirmEdge(ctx context.Context) (bool, error) { + fields := et.uniqueTrackerLogFields() + // If the edge is not a root of a challenge or subchallenge, we have nothing to do here. + if et.edge.ClaimId().IsNone() { + return false, nil + } + status, err := et.edge.Status(ctx) + if err != nil { + return false, errors.Wrap(err, "could not get edge status") + } + if status == protocol.EdgeConfirmed { + return true, nil + } + challengedAssertionHash, err := et.edge.AssertionHash(ctx) + if err != nil { + return false, err + } + manager := et.chain.SpecChallengeManager() + chalPeriod := manager.ChallengePeriodBlocks() + start := time.Now() + isConfirmable, _, computedTimer, err := et.chainWatcher.IsConfirmableEssentialEdge( + ctx, + challengedAssertionHash, + et.edge.Id(), + chalPeriod, + ) + if err != nil { + // If the error is that the child edges have not yet been observed by our chain watcher, + // we can simply return false and nil as they will eventually seen. This may occur when the validator + // is relying on safe or finalized data from the chain watcher. + if errors.Is(err, challengetree.ErrChildrenNotYetSeen) { + return false, nil + } + return false, errors.Wrap(err, "not check if essential edge is confirmable") + } + end := time.Since(start) + localFields := []any{ + "localTimer", computedTimer, + "confirmableAfter", chalPeriod, + "edgeId", fmt.Sprintf("%#x", et.edge.Id().Bytes()[:4]), + "took", end, + "batchLimit", et.associatedAssertionMetadata.BatchLimit, + "claimedAssertion", fmt.Sprintf("%#x", et.associatedAssertionMetadata.ClaimedAssertionHash.Hash[:4]), + } + if isConfirmable { + log.Info("Local computed timer big enough to confirm edge", append(fields, localFields...)...) + if err := et.challengeConfirmer.beginConfirmationJob( + ctx, + challengedAssertionHash, + computedTimer, + et.edge, + et.associatedAssertionMetadata.ClaimedAssertionHash, + chalPeriod, + ); err != nil { + log.Error("Could not begin confirmation job", fields...) + return false, errors.Wrapf( + err, + "could not complete confirmation job for essential root edge at level %d", + et.edge.GetChallengeLevel(), + ) + } + // The edge is now confirmed. + return true, nil + } + log.Info("Local computed timer not big enough to confirm edge", append(fields, localFields...)...) + return false, nil +} + +// Checks if the closest essential ancestor of an edge is confirmable. This method is used by the edge +// tracker to determine if it needs to open a subchallenge or bisect. The honest strategy +// aims to avoid unnecessary moves if it can determine they are unnecessary. +func (et *Tracker) isEssentialAncestorConfirmable(ctx context.Context) (bool, error) { + assertionHash, err := et.edge.AssertionHash(ctx) + if err != nil { + return false, err + } + manager := et.chain.SpecChallengeManager() + chalPeriod := manager.ChallengePeriodBlocks() + return et.chainWatcher.IsEssentialAncestorConfirmable( + ctx, + et.edge, + assertionHash, + chalPeriod, + ) +} + +// Determines the bisection point from parentHeight to toHeight and returns a history +// commitment with a prefix proof for the action based on the challenge type. +func (et *Tracker) DetermineBisectionHistoryWithProof( + ctx context.Context, +) (history.History, []byte, error) { + startHeight, _ := et.edge.StartCommitment() + endHeight, _ := et.edge.EndCommitment() + bisectTo, err := math.Bisect(uint64(startHeight), uint64(endHeight)) + if err != nil { + return history.History{}, nil, errors.Wrapf(err, "determining bisection point errored for %d and %d", startHeight, endHeight) + } + challengeLevel := et.edge.GetChallengeLevel() + if challengeLevel == protocol.NewBlockChallengeLevel() { + historyCommit, commitErr := et.stateProvider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(bisectTo)), + }, + ) + if commitErr != nil { + return history.History{}, nil, commitErr + } + proof, proofErr := et.stateProvider.PrefixProof( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, + l2stateprovider.Height(bisectTo), + ) + if proofErr != nil { + return history.History{}, nil, proofErr + } + return historyCommit, proof, nil + } + var historyCommit history.History + var commitErr error + var proof []byte + var proofErr error + + originHeights, err := et.edge.TopLevelClaimHeight(ctx) + if err != nil { + return history.History{}, nil, err + } + challengeOriginHeights := make([]l2stateprovider.Height, len(originHeights.ChallengeOriginHeights)) + for index, height := range originHeights.ChallengeOriginHeights { + challengeOriginHeights[index] = l2stateprovider.Height(height) + } + // The first challenge origin height must account for the start block height of the assertion. + historyCommit, commitErr = et.stateProvider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: challengeOriginHeights, + UpToHeight: option.Some(l2stateprovider.Height(bisectTo)), + }, + ) + if commitErr != nil { + return history.History{}, nil, errors.Wrap(commitErr, "could not produce history commitment") + } + proof, proofErr = et.stateProvider.PrefixProof( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: challengeOriginHeights, + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + }, + l2stateprovider.Height(bisectTo), + ) + if proofErr != nil { + return history.History{}, nil, errors.Wrap(proofErr, "could not produce prefix proof") + } + return historyCommit, proof, nil +} + +func (et *Tracker) bisect(ctx context.Context) (protocol.VerifiedRoyalEdge, protocol.VerifiedRoyalEdge, error) { + historyCommit, proof, err := et.DetermineBisectionHistoryWithProof(ctx) + if err != nil { + return nil, nil, err + } + endHeight, endCommit := et.edge.EndCommitment() + bisectTo := historyCommit.Height + firstChild, secondChild, err := et.edge.Bisect(ctx, historyCommit.Merkle, proof) + if err != nil { + return nil, nil, errors.Wrapf( + err, + "%s could not bisect to height=%d,commit=%s from height=%d,commit=%s", + et.validatorName, + bisectTo, + containers.Trunc(historyCommit.Merkle.Bytes()), + endHeight, + containers.Trunc(endCommit.Bytes()), + ) + } + log.Info("Bisecting honest edge", et.uniqueTrackerLogFields()...) + if addVerifiedErr := et.chainWatcher.AddVerifiedHonestEdge(ctx, firstChild); addVerifiedErr != nil { + // We simply log an error, as if this errored, it will be added later on by the chain watcher + // scraping events from the chain, but this is a helpful optimization. + log.Error("Could not add verified honest edge to chain watcher", "err", addVerifiedErr) + } + if addVerifiedErr := et.chainWatcher.AddVerifiedHonestEdge(ctx, secondChild); addVerifiedErr != nil { + log.Error("Could not add verified honest edge to chain watcher", "err", addVerifiedErr) + } + return firstChild, secondChild, nil +} + +func (et *Tracker) openSubchallenge(ctx context.Context) error { + originHeights, err := et.edge.TopLevelClaimHeight(ctx) + if err != nil { + return errors.Wrap(err, "could not get top level claim height") + } + + fromBlockChallengeHeight := l2stateprovider.Height(originHeights.ChallengeOriginHeights[0]) + + startHeight, _ := et.edge.StartCommitment() + endHeight, _ := et.edge.EndCommitment() + + fields := et.uniqueTrackerLogFields() + + var startHistory history.History + var endHistory history.History + var startParentCommitment history.History + var endParentCommitment history.History + var startEndPrefixProof []byte + challengeLevel := et.edge.GetChallengeLevel() + switch challengeLevel { + case protocol.NewBlockChallengeLevel(): + endHistory, err = et.stateProvider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{fromBlockChallengeHeight}, + UpToHeight: option.None[l2stateprovider.Height](), + }, + ) + if err != nil { + return errors.Wrap(err, "could not compute end history commitment") + } + startEndPrefixProof, err = et.stateProvider.PrefixProof( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{fromBlockChallengeHeight}, + UpToHeight: option.Some(l2stateprovider.Height(endHistory.Height)), + }, + l2stateprovider.Height(0), + ) + if err != nil { + return errors.Wrap(err, "could not compute prefix proof") + } + startHistory, err = et.stateProvider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{fromBlockChallengeHeight}, + UpToHeight: option.Some(l2stateprovider.Height(0)), + }, + ) + if err != nil { + return err + } + endParentCommitment, err = et.stateProvider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(fromBlockChallengeHeight + 1), + }, + ) + if err != nil { + return err + } + startParentCommitment, err = et.stateProvider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(fromBlockChallengeHeight), + }, + ) + if err != nil { + return err + } + default: + heights := make([]l2stateprovider.Height, 0) + for _, h := range originHeights.ChallengeOriginHeights { + heights = append(heights, l2stateprovider.Height(h)) + } + heights = append(heights, l2stateprovider.Height(startHeight)) + request := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: heights, + UpToHeight: option.None[l2stateprovider.Height](), + } + endHistory, err = et.stateProvider.HistoryCommitment( + ctx, + request, + ) + if err != nil { + return errors.Wrapf(err, "could not compute child commitment with request %+v", request) + } + request = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: heights, + UpToHeight: option.Some(l2stateprovider.Height(endHistory.Height)), + } + startEndPrefixProof, err = et.stateProvider.PrefixProof( + ctx, + request, + l2stateprovider.Height(0), + ) + if err != nil { + return errors.Wrapf(err, "could not compute prefix proof for child with request %+v, up to height %d", request, endHistory.Height) + } + request = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: heights, + UpToHeight: option.Some(l2stateprovider.Height(0)), + } + startHistory, err = et.stateProvider.HistoryCommitment( + ctx, + request, + ) + if err != nil { + return errors.Wrapf(err, "could not compute start history commitment with request %+v", request) + } + request = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: heights[:len(heights)-1], + UpToHeight: option.Some(l2stateprovider.Height(endHeight)), + } + endParentCommitment, err = et.stateProvider.HistoryCommitment( + ctx, + request, + ) + if err != nil { + return errors.Wrapf(err, "could not compute end parent commitment with request %+v, end height %d", request, endHeight) + } + request = &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: et.associatedAssertionMetadata, + UpperChallengeOriginHeights: heights[:len(heights)-1], + UpToHeight: option.Some(l2stateprovider.Height(startHeight)), + } + startParentCommitment, err = et.stateProvider.HistoryCommitment( + ctx, + request, + ) + if err != nil { + return errors.Wrapf(err, "could not compute start parent commitment with request %+v, start height %d", request, startHeight) + } + } + fields = append( + fields, + "firstLeaf", containers.Trunc(startHistory.FirstLeaf.Bytes()), + "lastLeaf", containers.Trunc(endHistory.LastLeaf.Bytes()), + "parentFirstLeaf", containers.Trunc(startParentCommitment.LastLeaf.Bytes()), + "parentLastLeaf", containers.Trunc(endParentCommitment.LastLeaf.Bytes()), + "parentStartHeight", startParentCommitment.Height, + "parentEndHeight", endParentCommitment.Height, + ) + log.Info("Identified single point of disagreement within a challenge level, now opening subchallenge", fields...) + log.Info("Making subchallenge creation move on edge", fields...) + + manager := et.chain.SpecChallengeManager() + addedLeaf, err := manager.AddSubChallengeLevelZeroEdge( + ctx, + et.edge, + startHistory, + endHistory, + startParentCommitment.LastLeafProof, + endParentCommitment.LastLeafProof, + startEndPrefixProof, + ) + if err != nil { + return err + } + addedLeafChallengeLevel := addedLeaf.GetChallengeLevel() + fields = append(fields, "subchallengeType", addedLeafChallengeLevel) + log.Info("Successfully created a subchallenge edge", fields...) + + if addVerifiedErr := et.chainWatcher.AddVerifiedHonestEdge(ctx, addedLeaf); addVerifiedErr != nil { + // We simply log an error, as if this errored, it will be added later on by the chain watcher + // scraping events from the chain, but this is a helpful optimization. + log.Error("Could not add verified honest edge to chain watcher", "err", addVerifiedErr) + } + + tracker, err := New( + ctx, + addedLeaf, + et.chain, + et.stateProvider, + et.chainWatcher, + et.challengeManager, + et.associatedAssertionMetadata, + WithTimeReference(et.timeRef), + WithValidatorName(et.validatorName), + WithFSMOpts(et.fsmOpts...), + ) + if err != nil { + return err + } + go tracker.Spawn(ctx) + return nil +} + +func (et *Tracker) submitOneStepProof(ctx context.Context) error { + fields := et.uniqueTrackerLogFields() + log.Info("Identified single step of disagreement at the execution of a block, ready for one-step fraud proof", fields...) + log.Info("Submitting one-step-proof to protocol", fields...) + originHeights, err := et.edge.TopLevelClaimHeight(ctx) + if err != nil { + return errors.Wrap(err, "could not get top level claim height") + } + pc, _ := et.edge.StartCommitment() + + challengeOriginHeights := make([]l2stateprovider.Height, len(originHeights.ChallengeOriginHeights)) + for index, height := range originHeights.ChallengeOriginHeights { + challengeOriginHeights[index] = l2stateprovider.Height(height) + } + data, beforeStateInclusionProof, afterStateInclusionProof, err := et.stateProvider.OneStepProofData( + ctx, + et.associatedAssertionMetadata, + challengeOriginHeights, + l2stateprovider.Height(pc), + ) + if err != nil { + return errors.Wrapf(errBadOneStepProof, "could not get one step data: %v", err) + } + manager := et.chain.SpecChallengeManager() + if err = manager.ConfirmEdgeByOneStepProof( + ctx, + et.edge.Id(), + data, + beforeStateInclusionProof, + afterStateInclusionProof, + ); err != nil { + return errors.Wrap(err, "could not confirm one step proof against protocol") + } + log.Info("Succeeded one-step-proof for edge and confirmed it as winner", fields...) + return nil +} + +func canOneStepProve(ctx context.Context, edge protocol.SpecEdge) (bool, error) { + start, _ := edge.StartCommitment() + end, _ := edge.EndCommitment() + // Can never happen in the protocol, but added as an additional defensive check. + if start >= end { + return false, fmt.Errorf("start height %d cannot be >= end height %d", start, end) + } + challengeLevel := edge.GetChallengeLevel() + totalChallengeLevels := edge.GetTotalChallengeLevels(ctx) + return end-start == 1 && challengeLevel.Uint8() == totalChallengeLevels-1, nil +} + +func IsRootBlockChallengeEdge(edge protocol.ReadOnlyEdge) bool { + return edge.ClaimId().IsSome() && edge.GetChallengeLevel() == protocol.NewBlockChallengeLevel() +} diff --git a/bold/challenge-manager/edge-tracker/transition_table.go b/bold/challenge-manager/edge-tracker/transition_table.go new file mode 100644 index 0000000000..61b269c4eb --- /dev/null +++ b/bold/challenge-manager/edge-tracker/transition_table.go @@ -0,0 +1,55 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package edgetracker + +import ( + "github.com/offchainlabs/bold/containers/fsm" +) + +func newEdgeTrackerFsm( + startState State, + fsmOpts ...fsm.Opt[edgeTrackerAction, State], +) (*fsm.Fsm[edgeTrackerAction, State], error) { + transitions := []*fsm.Event[edgeTrackerAction, State]{ + { + // Returns the tracker to the very beginning. Several states can cause + // this, including challenge moves. + Typ: edgeBackToStart{}, + From: []State{ + EdgeBisecting, + EdgeStarted, + EdgeAtOneStepProof, + EdgeAddingSubchallengeLeaf, + }, + To: EdgeStarted, + }, + { + // The tracker will take some action if it has reached a one-step-proof + // in a small step challenge. + Typ: edgeHandleOneStepProof{}, + From: []State{EdgeStarted, EdgeAtOneStepProof}, + To: EdgeAtOneStepProof, + }, + { + // The tracker will add a subchallenge leaf to its edge's subchallenge. + Typ: edgeOpenSubchallengeLeaf{}, + From: []State{EdgeStarted, EdgeAddingSubchallengeLeaf}, + To: EdgeAddingSubchallengeLeaf, + }, + // Challenge moves. + { + Typ: edgeBisect{}, + From: []State{EdgeStarted, EdgeBisecting}, + To: EdgeBisecting, + }, + // Terminal state, awaiting confirmation. + { + Typ: edgeAwaitChallengeCompletion{}, + From: []State{EdgeStarted, EdgeBisecting, EdgeAddingSubchallengeLeaf, EdgeAwaitingChallengeCompletion, EdgeAtOneStepProof}, + To: EdgeAwaitingChallengeCompletion, + }, + } + return fsm.New(startState, transitions, fsmOpts...) +} diff --git a/bold/challenge-manager/manager.go b/bold/challenge-manager/manager.go new file mode 100644 index 0000000000..8da93a375f --- /dev/null +++ b/bold/challenge-manager/manager.go @@ -0,0 +1,418 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package challengemanager includes the main entrypoint for setting up a BoLD +// challenge manager instance and challenging assertions onchain. +package challengemanager + +import ( + "context" + "fmt" + "time" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + + "github.com/offchainlabs/bold/api/server" + protocol "github.com/offchainlabs/bold/chain-abstraction" + watcher "github.com/offchainlabs/bold/challenge-manager/chain-watcher" + edgetracker "github.com/offchainlabs/bold/challenge-manager/edge-tracker" + "github.com/offchainlabs/bold/challenge-manager/types" + "github.com/offchainlabs/bold/containers/events" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/containers/threadsafe" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + retry "github.com/offchainlabs/bold/runtime" + utilTime "github.com/offchainlabs/bold/time" + "github.com/offchainlabs/bold/util/stopwaiter" +) + +var ( + challengeSubmittedCounter = metrics.NewRegisteredCounter("arb/validator/scanner/challenge_submitted", nil) +) + +type Opt = func(val *Manager) + +// AssertionManager works with the challenge manager suppplying information +// about assertions. +type AssertionManager interface { + Start(context.Context) + StopAndWait() + LatestAgreedAssertion() protocol.AssertionHash + SetRivalHandler(types.RivalHandler) +} + +// HeaderProvider is a producer of new block headers. +type HeaderProvider interface { + Subscribe(requireBlockNrUpdates bool) (<-chan *gethtypes.Header, func()) +} + +// Manager defines an offchain, challenge manager, which will be +// an active participant in interacting with the on-chain contracts. +type Manager struct { + stopwaiter.StopWaiter + chain protocol.Protocol + assertionManager AssertionManager + watcher *watcher.Watcher + stateManager l2stateprovider.Provider + name string + headerProvider HeaderProvider + timeRef utilTime.Reference + trackedEdgeIds *threadsafe.Map[protocol.EdgeId, *edgetracker.Tracker] + assertionMetadataCache *threadsafe.LruMap[protocol.AssertionHash, l2stateprovider.AssociatedAssertionMetadata] + newBlockNotifier *events.Producer[*gethtypes.Header] + notifyOnNumberOfBlocks uint64 + mode types.Mode + claimedAssertionsInChallenge *threadsafe.LruSet[protocol.AssertionHash] + // API + api *server.Server +} + +// WithName is a human-readable identifier for this challenge manager for +// logging purposes. +func WithName(name string) Opt { + return func(val *Manager) { + val.name = name + } +} + +// Edges tick on every block received from the parent chain of the rollup, by +// default. Alternatively, they can be configured to tick every N blocks. +func WithTickEdgesOnNumberOfBlocks(n uint64) Opt { + return func(val *Manager) { + val.notifyOnNumberOfBlocks = n + } +} + +// WithMode specifies the mode of the challenge manager. +func WithMode(m types.Mode) Opt { + return func(val *Manager) { + val.mode = m + } +} + +// WithAPIServer sets the API server for the challenge manager. +func WithAPIServer(api *server.Server) Opt { + return func(val *Manager) { + val.api = api + } +} + +// WithHeaderProvider sets the header provider for the challenge manager. +func WithHeaderProvider(provider HeaderProvider) Opt { + return func(val *Manager) { + val.headerProvider = provider + } +} + +// New sets up a challenge manager instance provided a protocol, state manager, +// chain watcher, assertion manager, and additional options. +func New( + chain protocol.Protocol, + stateManager l2stateprovider.Provider, + watcher *watcher.Watcher, + assertionManager AssertionManager, + opts ...Opt, +) (*Manager, error) { + maxAssertions, err := safecast.ToInt(chain.MaxAssertionsPerChallengePeriod()) + if err != nil { + return nil, errors.Wrap(err, "could not convert max assertions to int") + } + m := &Manager{ + chain: chain, + stateManager: stateManager, + assertionManager: assertionManager, + watcher: watcher, + timeRef: utilTime.NewRealTimeReference(), + trackedEdgeIds: threadsafe.NewMap(threadsafe.MapWithMetric[protocol.EdgeId, *edgetracker.Tracker]("trackedEdgeIds")), + assertionMetadataCache: threadsafe.NewLruMap(maxAssertions, threadsafe.LruMapWithMetric[protocol.AssertionHash, l2stateprovider.AssociatedAssertionMetadata]("batchIndexForAssertionCache")), + notifyOnNumberOfBlocks: 1, + newBlockNotifier: events.NewProducer[*gethtypes.Header](), + claimedAssertionsInChallenge: threadsafe.NewLruSet(maxAssertions, threadsafe.LruSetWithMetric[protocol.AssertionHash]("claimedAssertionsInChallenge")), + api: nil, + } + for _, o := range opts { + o(m) + } + m.watcher.SetEdgeManager(m) + m.assertionManager.SetRivalHandler(m) + log.Info("Setting up challenge manager", + "name", m.name, + "addreess", m.chain.StakerAddress(), + "rollup", m.chain.RollupAddress()) + return m, nil +} + +func (m *Manager) GetEdgeTracker(edgeId protocol.EdgeId) option.Option[*edgetracker.Tracker] { + if m.IsTrackingEdge(edgeId) { + return option.Some(m.trackedEdgeIds.Get(edgeId)) + } + return option.None[*edgetracker.Tracker]() +} + +// IsTrackingEdge returns true if we are currently tracking a specified edge id +// as an edge tracker goroutine. +func (m *Manager) IsTrackingEdge(edgeId protocol.EdgeId) bool { + return m.trackedEdgeIds.Has(edgeId) +} + +// MarkTrackedEdge marks an edge id as being tracked by our challenge manager. +func (m *Manager) MarkTrackedEdge(edgeId protocol.EdgeId, tracker *edgetracker.Tracker) { + m.trackedEdgeIds.Put(edgeId, tracker) +} + +func (m *Manager) RemovedTrackedEdge(edgeId protocol.EdgeId) { + m.trackedEdgeIds.Delete(edgeId) +} + +// Mode returns the mode of the challenge manager. +func (m *Manager) Mode() types.Mode { + return m.mode +} + +// IsChallengedAssertion checks if an assertion with a given hash has a +// challenge. +func (m *Manager) IsClaimedByChallenge(assertionHash protocol.AssertionHash) bool { + return m.claimedAssertionsInChallenge.Has(assertionHash) +} + +// TrackEdge spawns an edge tracker for an edge if it is not currently being tracked. +func (m *Manager) TrackEdge(ctx context.Context, edge protocol.VerifiedRoyalEdge) error { + if m.trackedEdgeIds.Has(edge.Id()) { + return nil + } + trk, err := m.getTrackerForEdge(ctx, edge) + if err != nil { + return err + } + m.LaunchThread(trk.Spawn) + return nil +} + +// Gets an edge tracker for an edge by retrieving its associated assertion creation info. +func (m *Manager) getTrackerForEdge(ctx context.Context, edge protocol.VerifiedRoyalEdge) (*edgetracker.Tracker, error) { + // Retry until you get the previous assertion Hash. + assertionHash, err := retry.UntilSucceeds(ctx, func() (protocol.AssertionHash, error) { + return edge.AssertionHash(ctx) + }) + if err != nil { + return nil, err + } + blockChallengeRootEdge, err := m.watcher.HonestBlockChallengeRootEdge(ctx, assertionHash) + if err != nil { + return nil, err + } + if blockChallengeRootEdge.ClaimId().IsNone() { + return nil, fmt.Errorf( + "block challenge root edge %#x did not have a claim id for challenged assertion %#x", + blockChallengeRootEdge.Id(), + assertionHash, + ) + } + claimedAssertionId := blockChallengeRootEdge.ClaimId().Unwrap() + claimedHash := protocol.AssertionHash{Hash: common.Hash(claimedAssertionId)} + + // Smart caching to avoid querying the same assertion number and creation info + // multiple times. Edges in the same challenge should have the same creation + // info. + cachedHeightAndInboxMsgCount, ok := m.assertionMetadataCache.TryGet(claimedHash) + var edgeTrackerAssertionInfo l2stateprovider.AssociatedAssertionMetadata + if !ok { + assertionCreationInfo, creationErr := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return m.chain.ReadAssertionCreationInfo(ctx, claimedHash) + }) + if creationErr != nil { + return nil, creationErr + } + prevCreationInfo, prevCreationErr := retry.UntilSucceeds(ctx, func() (*protocol.AssertionCreatedInfo, error) { + return m.chain.ReadAssertionCreationInfo(ctx, assertionCreationInfo.ParentAssertionHash) + }) + if prevCreationErr != nil { + return nil, prevCreationErr + } + if prevCreationInfo.InboxMaxCount == nil { + return nil, errors.New("prevCreationInfo.InboxMaxCount is nil") + } + if !prevCreationInfo.InboxMaxCount.IsUint64() { + return nil, fmt.Errorf("inbox max count is not a uint64: %v", prevCreationInfo.InboxMaxCount) + } + fromState := protocol.GoGlobalStateFromSolidity(assertionCreationInfo.BeforeState.GlobalState) + edgeTrackerAssertionInfo = l2stateprovider.AssociatedAssertionMetadata{ + FromState: fromState, + BatchLimit: l2stateprovider.Batch(prevCreationInfo.InboxMaxCount.Uint64()), + WasmModuleRoot: prevCreationInfo.WasmModuleRoot, + ClaimedAssertionHash: claimedHash, + } + m.assertionMetadataCache.Put(claimedHash, edgeTrackerAssertionInfo) + } else { + edgeTrackerAssertionInfo = cachedHeightAndInboxMsgCount + } + return retry.UntilSucceeds(ctx, func() (*edgetracker.Tracker, error) { + return edgetracker.New( + ctx, + edge, + m.chain, + m.stateManager, + m.watcher, + m, + &edgeTrackerAssertionInfo, + edgetracker.WithTimeReference(m.timeRef), + edgetracker.WithValidatorName(m.name), + ) + }) +} + +func (m *Manager) Watcher() *watcher.Watcher { + return m.watcher +} + +func (m *Manager) NewBlockSubscriber() *events.Producer[*gethtypes.Header] { + return m.newBlockNotifier +} + +func (m *Manager) Start(ctx context.Context) { + m.StopWaiter.Start(ctx, m) + log.Info("Started challenge manager", "stakerAddress", m.chain.StakerAddress().Hex()) + + // Start the assertion manager. + m.LaunchThread(m.assertionManager.Start) + + // Watcher tower and resolve modes don't monitor challenges. + if m.mode == types.WatchTowerMode || m.mode == types.ResolveMode { + return + } + + // Start watching for parent chain block events in the background. + m.LaunchThread(m.listenForBlockEvents) + + // Start watching for ongoing chain events in the background. + m.LaunchThread(m.watcher.Start) + + if m.api != nil { + m.LaunchThread(func(ctx context.Context) { + if err := m.api.Start(ctx); err != nil { + log.Error("Could not start API server", + "address", m.api.Addr(), + "err", err, + ) + } + }) + } +} + +func (m *Manager) StopAndWait() { + m.StopWaiter.StopAndWait() + m.assertionManager.StopAndWait() + m.watcher.StopAndWait() + if m.api != nil { + m.api.StopAndWait() + } +} + +func (m *Manager) listenForBlockEvents(ctx context.Context) { + // If the chain watcher has not yet scraped and caught up all BoLD + // events up to the latest head, then we fire "block notification" events + // every second. This will help the tracked edges act fast if we are + // just starting up the validator or catching up to a challenge. + m.fastTickWhileCatchingUp(ctx) + + // Then, once the watcher has reached the latest head, we + // fire off a block notifications events normally. + if m.headerProvider != nil { + m.tickOnHeadBlockSubscriptions(ctx) + } else { + m.tickAtInterval(ctx) + } +} + +func (m *Manager) tickOnHeadBlockSubscriptions(ctx context.Context) { + doesNotNeedEveryBlockNumberUpdate := false + ch, unsub := m.headerProvider.Subscribe(doesNotNeedEveryBlockNumberUpdate) + defer unsub() + numBlocksReceived := uint64(0) + for { + select { + case header := <-ch: + numBlocksReceived += 1 + // Only broadcast every N blocks received. This is important for Orbit + // chains that have parent chains with very fast block times, such as + // Arbitrum One, as broadcasting every 250ms would otherwise be too + // frequent. + if numBlocksReceived%m.notifyOnNumberOfBlocks == 0 { + m.newBlockNotifier.Broadcast(ctx, header) + } + case <-ctx.Done(): + return + } + } +} + +func (m *Manager) tickAtInterval(ctx context.Context) { + ticker := time.NewTicker(time.Second * 12) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + m.newBlockNotifier.Broadcast(ctx, nil) + } + } +} + +func (m *Manager) fastTickWhileCatchingUp(ctx context.Context) { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-time.After(time.Second): + m.newBlockNotifier.Broadcast(ctx, nil) + if m.watcher.IsSynced() { + return + } + } + } +} + +func (m *Manager) LatestConfirmedState(ctx context.Context) (protocol.GoGlobalState, error) { + latestConfirmed, err := m.chain.LatestConfirmed(ctx, m.chain.GetCallOptsWithDesiredRpcHeadBlockNumber(&bind.CallOpts{Context: ctx})) + if err != nil { + return protocol.GoGlobalState{}, err + } + info, err := m.chain.ReadAssertionCreationInfo(ctx, latestConfirmed.Id()) + if err != nil { + return protocol.GoGlobalState{}, err + } + return protocol.GoExecutionStateFromSolidity(info.AfterState).GlobalState, nil +} + +func (m *Manager) LatestAgreedState(ctx context.Context) (protocol.GoGlobalState, error) { + latestAgreedAssertion := m.assertionManager.LatestAgreedAssertion() + info, err := m.chain.ReadAssertionCreationInfo(ctx, latestAgreedAssertion) + if err != nil { + return protocol.GoGlobalState{}, err + } + return protocol.GoExecutionStateFromSolidity(info.AfterState).GlobalState, nil +} + +func (m *Manager) logChallengeConfigs() { + cm := m.chain.SpecChallengeManager() + bigStepNum := cm.NumBigSteps() + challengePeriodBlocks := cm.ChallengePeriodBlocks() + layerZeroHeights := cm.LayerZeroHeights() + log.Info("Opening challenge with the following configuration", + "address", cm.Address(), + "bigStepNumber", bigStepNum, + "challengePeriodBlocks", challengePeriodBlocks, + "layerZeroHeights", layerZeroHeights, + ) +} diff --git a/bold/challenge-manager/manager_test.go b/bold/challenge-manager/manager_test.go new file mode 100644 index 0000000000..b07d5d13fc --- /dev/null +++ b/bold/challenge-manager/manager_test.go @@ -0,0 +1,278 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengemanager + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + watcher "github.com/offchainlabs/bold/challenge-manager/chain-watcher" + edgetracker "github.com/offchainlabs/bold/challenge-manager/edge-tracker" + "github.com/offchainlabs/bold/challenge-manager/types" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/testing/mocks" + "github.com/offchainlabs/bold/testing/setup" + customTime "github.com/offchainlabs/bold/time" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +var _ = types.RivalHandler(&Manager{}) + +func TestEdgeTracker_Act(t *testing.T) { + ctx := context.Background() + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + tkr, _ := setupEdgeTrackersForBisection(t, ctx, createdData, option.None[uint64]()) + err = tkr.Act(ctx) + require.NoError(t, err) + require.Equal(t, edgetracker.EdgeBisecting, tkr.CurrentState()) + + err = tkr.Act(ctx) + require.NoError(t, err) + require.Equal(t, edgetracker.EdgeAwaitingChallengeCompletion, tkr.CurrentState()) + + err = tkr.Act(ctx) + require.NoError(t, err) + require.Equal(t, edgetracker.EdgeAwaitingChallengeCompletion, tkr.CurrentState()) +} + +func TestEdgeTracker_Act_ConfirmedByTime(t *testing.T) { + ctx := context.Background() + createdData, err := setup.CreateTwoValidatorFork(ctx, t, &setup.CreateForkConfig{}, setup.WithMockOneStepProver()) + require.NoError(t, err) + + chalManager := createdData.Chains[0].SpecChallengeManager() + chalPeriodBlocks := chalManager.ChallengePeriodBlocks() + + // Delay the evil root edge creation by a challenge period. + delayEvilRootEdgeCreation := option.Some(chalPeriodBlocks) + honestTracker, evilTracker := setupEdgeTrackersForBisection(t, ctx, createdData, delayEvilRootEdgeCreation) + + honestEdgeOpt, err := chalManager.GetEdge(ctx, honestTracker.EdgeId()) + require.NoError(t, err) + require.Equal(t, false, honestEdgeOpt.IsNone()) + + evilEdgeOpt, err := chalManager.GetEdge(ctx, evilTracker.EdgeId()) + require.NoError(t, err) + require.Equal(t, false, evilEdgeOpt.IsNone()) + + // Expect our edge to be confirmed right away. + err = honestTracker.Act(ctx) + require.NoError(t, err) + require.Equal(t, edgetracker.EdgeAwaitingChallengeCompletion, honestTracker.CurrentState()) + require.Equal(t, true, honestTracker.ShouldDespawn(ctx)) +} + +type verifiedHonestMock struct { + *mocks.MockSpecEdge +} + +func (verifiedHonestMock) Honest() {} + +func Test_getEdgeTrackers(t *testing.T) { + ctx := context.Background() + + v, m, s := setupValidator(ctx, t) + edge := &mocks.MockSpecEdge{} + honest := &mocks.MockHonestEdge{MockSpecEdge: edge} + edge.On("Id").Return(protocol.EdgeId{Hash: common.BytesToHash([]byte("foo"))}) + edge.On("GetReversedChallengeLevel").Return(protocol.ChallengeLevel(2)) + edge.On("MutualId").Return(protocol.MutualId{}) + edge.On("OriginId").Return(protocol.OriginId{}) + edge.On("CreatedAtBlock").Return(uint64(1), nil) + parentAssertionHash := protocol.AssertionHash{Hash: common.BytesToHash([]byte("par"))} + assertionHash := protocol.AssertionHash{Hash: common.BytesToHash([]byte("bar"))} + edge.On("ClaimId").Return(option.Some(protocol.ClaimId(assertionHash.Hash))) + edge.On("AssertionHash", ctx).Return(assertionHash, nil) + edge.On("StartCommitment").Return(protocol.Height(0), common.Hash{}) + edge.On("EndCommitment").Return(protocol.Height(0), common.Hash{}) + edge.On("GetChallengeLevel").Return(protocol.ChallengeLevel(0)) + edge.On("MarkAsHonest").Return() + edge.On("AsVerifiedHonest").Return(honest, true) + m.On("ReadAssertionCreationInfo", ctx, assertionHash).Return(&protocol.AssertionCreatedInfo{ + BeforeState: rollupgen.AssertionState{ + GlobalState: rollupgen.GlobalState{ + U64Vals: [2]uint64{1, 0}, + }, + }, + AfterState: rollupgen.AssertionState{ + GlobalState: rollupgen.GlobalState{ + U64Vals: [2]uint64{100, 0}, + }, + }, + ParentAssertionHash: parentAssertionHash, + }, nil) + m.On("ReadAssertionCreationInfo", ctx, parentAssertionHash).Return(&protocol.AssertionCreatedInfo{ + InboxMaxCount: big.NewInt(100), + }, nil) + s.On("ExecutionStateMsgCount", ctx, &protocol.ExecutionState{}).Return(uint64(1), nil) + + require.NoError(t, v.watcher.AddVerifiedHonestEdge(ctx, verifiedHonestMock{edge})) + edge.MarkAsHonest() + verifiedRoyal, _ := edge.AsVerifiedHonest() + trk, err := v.getTrackerForEdge(ctx, verifiedRoyal) + require.NoError(t, err) + + require.Equal(t, l2stateprovider.Batch(1), l2stateprovider.Batch(trk.AssertionInfo().FromState.Batch)) + require.Equal(t, l2stateprovider.Batch(100), trk.AssertionInfo().BatchLimit) +} + +func setupEdgeTrackersForBisection( + t *testing.T, + ctx context.Context, + createdData *setup.CreatedValidatorFork, + delayEvilRootEdgeCreationByBlocks option.Option[uint64], +) (*edgetracker.Tracker, *edgetracker.Tracker) { + t.Helper() + confInterval := time.Second * 10 + avgBlockTime := time.Second * 12 + honestOpts := []StackOpt{ + StackWithMode(types.MakeMode), + StackWithName("alice"), + StackWithConfirmationInterval(confInterval), + StackWithAverageBlockCreationTime(avgBlockTime), + } + honestValidator, err := NewChallengeStack( + createdData.Chains[0], + createdData.HonestStateManager, + honestOpts..., + ) + require.NoError(t, err) + + evilOpts := honestOpts + evilOpts = append(evilOpts, StackWithName("bob")) + evilValidator, err := NewChallengeStack( + createdData.Chains[1], + createdData.EvilStateManager, + evilOpts..., + ) + require.NoError(t, err) + + honestEdge, _, _, _, err := honestValidator.addBlockChallengeLevelZeroEdge(ctx, createdData.Leaf1) + require.NoError(t, err) + + // If we specify an optional amount of blocks to delay the evil root edge creation by, do so + // by committing blocks to the simulated backend. + if !delayEvilRootEdgeCreationByBlocks.IsNone() { + delay := delayEvilRootEdgeCreationByBlocks.Unwrap() + for i := uint64(0); i < delay; i++ { + createdData.Backend.Commit() + } + } + + evilEdge, _, _, _, err := evilValidator.addBlockChallengeLevelZeroEdge(ctx, createdData.Leaf2) + require.NoError(t, err) + + // Check unrivaled statuses. + hasRival, err := honestEdge.HasRival(ctx) + require.NoError(t, err) + require.Equal(t, false, !hasRival) + + honestWatcher, err := watcher.New( + honestValidator.chain, + honestValidator.stateManager, + "alice", + nil, + confInterval, + avgBlockTime, + nil, + 10, + ) + require.NoError(t, err) + honestWatcher.SetEdgeManager(honestValidator) + honestValidator.watcher = honestWatcher + assertionInfo := &l2stateprovider.AssociatedAssertionMetadata{ + FromState: protocol.GoGlobalState{Batch: 0, PosInBatch: 0}, + BatchLimit: 1, + WasmModuleRoot: common.Hash{}, + ClaimedAssertionHash: createdData.Leaf1.Id(), + } + tracker1, err := edgetracker.New( + ctx, + honestEdge, + honestValidator.chain, + createdData.HonestStateManager, + honestWatcher, + honestValidator, + assertionInfo, + edgetracker.WithTimeReference(customTime.NewArtificialTimeReference()), + edgetracker.WithValidatorName(honestValidator.name), + ) + require.NoError(t, err) + + evilWatcher, err := watcher.New( + evilValidator.chain, + evilValidator.stateManager, + "bob", + nil, + confInterval, + avgBlockTime, + nil, + 10, + ) + require.NoError(t, err) + evilWatcher.SetEdgeManager(evilValidator) + evilValidator.watcher = evilWatcher + assertionInfo = &l2stateprovider.AssociatedAssertionMetadata{ + FromState: protocol.GoGlobalState{Batch: 0, PosInBatch: 0}, + BatchLimit: 1, + WasmModuleRoot: common.Hash{}, + ClaimedAssertionHash: createdData.Leaf2.Id(), + } + tracker2, err := edgetracker.New( + ctx, + evilEdge, + evilValidator.chain, + createdData.EvilStateManager, + evilWatcher, + evilValidator, + assertionInfo, + edgetracker.WithTimeReference(customTime.NewArtificialTimeReference()), + edgetracker.WithValidatorName(evilValidator.name), + ) + require.NoError(t, err) + + require.NoError(t, honestWatcher.AddVerifiedHonestEdge(ctx, honestEdge)) + _, err = honestWatcher.AddEdge(ctx, evilEdge) + require.NoError(t, err) + require.NoError(t, evilWatcher.AddVerifiedHonestEdge(ctx, evilEdge)) + _, err = evilWatcher.AddEdge(ctx, honestEdge) + require.NoError(t, err) + + return tracker1, tracker2 +} + +func setupValidator(ctx context.Context, t *testing.T) (*Manager, *mocks.MockProtocol, *mocks.MockStateManager) { + t.Helper() + p := &mocks.MockProtocol{} + cm := &mocks.MockSpecChallengeManager{} + p.On("CurrentChallengeManager", ctx).Return(cm, nil) + p.On("SpecChallengeManager").Return(cm) + p.On("MaxAssertionsPerChallengePeriod").Return(uint64(100)) + cm.On("NumBigSteps").Return(uint8(1)) + s := &mocks.MockStateManager{} + cfg, err := setup.ChainsWithEdgeChallengeManager(setup.WithMockOneStepProver()) + require.NoError(t, err) + p.On("Backend").Return(cfg.Backend, nil) + p.On("RollupAddress").Return(cfg.Addrs.Rollup) + p.On("StakerAddress").Return(cfg.Chains[0].StakerAddress()) + v, err := NewChallengeStack( + p, + s, + StackWithMode(types.MakeMode), + StackWithName("alice"), + ) + require.NoError(t, err) + return v, p, s +} diff --git a/bold/challenge-manager/stack.go b/bold/challenge-manager/stack.go new file mode 100644 index 0000000000..50e2ab6b69 --- /dev/null +++ b/bold/challenge-manager/stack.go @@ -0,0 +1,300 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challengemanager + +import ( + "time" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/api/backend" + "github.com/offchainlabs/bold/api/db" + "github.com/offchainlabs/bold/api/server" + "github.com/offchainlabs/bold/assertions" + protocol "github.com/offchainlabs/bold/chain-abstraction" + watcher "github.com/offchainlabs/bold/challenge-manager/chain-watcher" + "github.com/offchainlabs/bold/challenge-manager/types" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" +) + +type stackParams struct { + mode types.Mode + name string + pollInterval time.Duration + postInterval time.Duration + confInterval time.Duration + avgBlockTime time.Duration + minGapToParent time.Duration + trackChallengeParentAssertionHashes []protocol.AssertionHash + apiAddr string + apiDBPath string + headerProvider HeaderProvider + enableFastConfirmation bool + assertionManagerOverride *assertions.Manager + maxGetLogBlocks int64 + delegatedStaking bool + autoDeposit bool + autoAllowanceApproval bool +} + +var defaultStackParams = stackParams{ + mode: types.MakeMode, + name: "unnamed-challenge-manager", + pollInterval: time.Minute, + postInterval: time.Hour, + confInterval: time.Second * 10, + avgBlockTime: time.Second * 12, + minGapToParent: time.Minute * 10, + trackChallengeParentAssertionHashes: nil, + apiAddr: "", + apiDBPath: "", + headerProvider: nil, + enableFastConfirmation: false, + assertionManagerOverride: nil, + maxGetLogBlocks: 1000, + delegatedStaking: false, + autoDeposit: true, + autoAllowanceApproval: true, +} + +// StackOpt is a functional option to configure the stack. +type StackOpt func(*stackParams) + +// WithMode sets the mode of the challenge manager. +func StackWithMode(mode types.Mode) StackOpt { + return func(p *stackParams) { + p.mode = mode + } +} + +// WithName sets the name of the challenge manager. +func StackWithName(name string) StackOpt { + return func(p *stackParams) { + p.name = name + } +} + +// WithPollingInterval sets the polling interval of the challenge manager. +func StackWithPollingInterval(interval time.Duration) StackOpt { + return func(p *stackParams) { + p.pollInterval = interval + } +} + +// WithPostingInterval sets the posting interval of the challenge manager. +func StackWithPostingInterval(interval time.Duration) StackOpt { + return func(p *stackParams) { + p.postInterval = interval + } +} + +// WithConfirmationInterval sets the confirmation interval of the challenge +// manager. +func StackWithConfirmationInterval(interval time.Duration) StackOpt { + return func(p *stackParams) { + p.confInterval = interval + } +} + +// WithAverageBlockCreationTime sets the average block creation time of the +// challenge manager. +func StackWithAverageBlockCreationTime(interval time.Duration) StackOpt { + return func(p *stackParams) { + p.avgBlockTime = interval + } +} + +// StackWithMinimumGapToParentAssertion sets the minimum gap to parent assertion creation time +// of the challenge manager. +func StackWithMinimumGapToParentAssertion(interval time.Duration) StackOpt { + return func(p *stackParams) { + p.minGapToParent = interval + } +} + +// WithTrackChallengeParentAssertionHashes sets the track challenge parent +// assertion hashes of the challenge manager. +func StackWithTrackChallengeParentAssertionHashes(hashes []string) StackOpt { + return func(p *stackParams) { + p.trackChallengeParentAssertionHashes = make([]protocol.AssertionHash, len(hashes)) + for i, h := range hashes { + p.trackChallengeParentAssertionHashes[i] = protocol.AssertionHash{Hash: common.HexToHash(h)} + } + } +} + +// WithAPIEnabled sets the API address and database path of the challenge +// manager. +func StackWithAPIEnabled(apiAddr, apiDBPath string) StackOpt { + return func(p *stackParams) { + p.apiAddr = apiAddr + p.apiDBPath = apiDBPath + } +} + +// StackWithHeaderProvider sets the header provider of the challenge manager. +func StackWithHeaderProvider(hp HeaderProvider) StackOpt { + return func(p *stackParams) { + p.headerProvider = hp + } +} + +// WithFastConfirmationEnabled +func StackWithFastConfirmationEnabled() StackOpt { + return func(p *stackParams) { + p.enableFastConfirmation = true + } +} + +// StackWithSyncMaxGetLogBlocks specifies the max size chunks of blocks to use when using get logs rpc for +// when syncing the chain watcher. +func StackWithSyncMaxGetLogBlocks(maxGetLog int64) StackOpt { + return func(p *stackParams) { + p.maxGetLogBlocks = maxGetLog + } +} + +// StackWithDelegatedStaking specifies that the challenge manager will call +// the `newStake` function in the rollup contract on startup to await funding from another account +// such that it becomes a delegated staker. +func StackWithDelegatedStaking() StackOpt { + return func(p *stackParams) { + p.delegatedStaking = true + } +} + +// StackWithoutAutoDeposit specifies that the software will not call +// the stake token's `deposit` function on startup to fund the account. +func StackWithoutAutoDeposit() StackOpt { + return func(p *stackParams) { + p.autoDeposit = false + } +} + +// StackWithoutAutoAllowanceApproval specifies that the software will not call +// the stake token's `increaseAllowance` function on startup to approve allowance spending for +// the rollup and challenge manager contracts. +func StackWithoutAutoAllowanceApproval() StackOpt { + return func(p *stackParams) { + p.autoAllowanceApproval = false + } +} + +// OverrideAssertionManger can be used in tests to override the assertion +// manager. +func OverrideAssertionManager(asm *assertions.Manager) StackOpt { + return func(p *stackParams) { + p.assertionManagerOverride = asm + } +} + +// NewChallengeStack creates a new ChallengeManager and all of the dependencies +// wiring them together. +func NewChallengeStack( + chain protocol.AssertionChain, + provider l2stateprovider.Provider, + opts ...StackOpt, +) (*Manager, error) { + params := defaultStackParams + for _, o := range opts { + o(¶ms) + } + + var err error + // Create the api database. + var apiDB db.Database + if params.apiDBPath != "" { + apiDB, err = db.NewDatabase(params.apiDBPath) + if err != nil { + return nil, err + } + provider.UpdateAPIDatabase(apiDB) + } + maxGetLogBlocks, err := safecast.ToUint64(params.maxGetLogBlocks) + if err != nil { + return nil, err + } + + // Create the chain watcher. + watcher, err := watcher.New( + chain, + provider, + params.name, + apiDB, + params.confInterval, + params.avgBlockTime, + params.trackChallengeParentAssertionHashes, + maxGetLogBlocks, + ) + if err != nil { + return nil, err + } + + // Create the api backend server. + var api *server.Server + if params.apiAddr != "" { + bknd := backend.NewBackend(apiDB, chain, watcher) + api, err = server.New(params.apiAddr, bknd) + if err != nil { + return nil, err + } + } + + // Create the assertions manager. + var asm *assertions.Manager + if params.assertionManagerOverride == nil { + // Create the assertions manager. + amOpts := []assertions.Opt{ + assertions.WithAverageBlockCreationTime(params.avgBlockTime), + assertions.WithConfirmationInterval(params.confInterval), + assertions.WithPollingInterval(params.pollInterval), + assertions.WithPostingInterval(params.postInterval), + assertions.WithMinimumGapToParentAssertion(params.minGapToParent), + assertions.WithMaxGetLogBlocks(maxGetLogBlocks), + } + if apiDB != nil { + amOpts = append(amOpts, assertions.WithAPIDB(apiDB)) + } + if params.enableFastConfirmation { + amOpts = append(amOpts, assertions.WithFastConfirmation()) + } + if params.delegatedStaking { + amOpts = append(amOpts, assertions.WithDelegatedStaking()) + } + if !params.autoDeposit { + amOpts = append(amOpts, assertions.WithoutAutoDeposit()) + } + if !params.autoAllowanceApproval { + amOpts = append(amOpts, assertions.WithoutAutoAllowanceApproval()) + } + asm, err = assertions.NewManager( + chain, + provider, + params.name, + params.mode, + amOpts..., + ) + if err != nil { + return nil, err + } + } else { + asm = params.assertionManagerOverride + } + + // Create the challenge manager. + cmOpts := []Opt{ + WithMode(params.mode), + WithName(params.name), + } + if params.headerProvider != nil { + cmOpts = append(cmOpts, WithHeaderProvider(params.headerProvider)) + } + if params.apiAddr != "" { + cmOpts = append(cmOpts, WithAPIServer(api)) + } + return New(chain, provider, watcher, asm, cmOpts...) +} diff --git a/bold/challenge-manager/types/interfaces.go b/bold/challenge-manager/types/interfaces.go new file mode 100644 index 0000000000..fe30282dfe --- /dev/null +++ b/bold/challenge-manager/types/interfaces.go @@ -0,0 +1,25 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package types includes types and interfaces specific to the challenge manager instance. +package types + +import ( + "context" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +// RivalHandler is the interface between the challenge manager and the assertion +// manager. +// +// The challenge manager implements the interface promising to handle opening +// challenges on correct rival assertions, and the assertion manager is +// responsible for posting correct rival assertions, and notifying the rival +// handler of the existence of the correct rival assertion. +type RivalHandler interface { + // HandleCorrectRival is called when the assertion manager has posted a correct + // rival assertion on the chain. + HandleCorrectRival(context.Context, protocol.AssertionHash) error +} diff --git a/bold/challenge-manager/types/mode.go b/bold/challenge-manager/types/mode.go new file mode 100644 index 0000000000..54aa941402 --- /dev/null +++ b/bold/challenge-manager/types/mode.go @@ -0,0 +1,36 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package types + +type Mode uint8 + +const ( + // Watchtower: don't do anything on L1, but log if there's a bad assertion + WatchTowerMode Mode = iota + // Defensive: stake if there's a bad assertion + DefensiveMode + // Resolve nodes: stay staked on the latest node and resolve any unconfirmed + // nodes, challenging bad assertions + ResolveMode + // Make nodes: continually create new nodes, challenging bad assertions + MakeMode +) + +// SupportsStaking returns true if the mode supports staking +func (m Mode) SupportsStaking() bool { + return m >= MakeMode +} + +// SupportsPostingRivals returns true if the mode supports posting rival +// assertions. +func (m Mode) SupportsPostingRivals() bool { + return m >= DefensiveMode +} + +// SupportsPostingChallenges returns true if the mode supports posting +// challenging edges. +func (m Mode) SupportsPostingChallenges() bool { + return m > DefensiveMode +} diff --git a/bold/codecov.yml b/bold/codecov.yml new file mode 100644 index 0000000000..be07a097ff --- /dev/null +++ b/bold/codecov.yml @@ -0,0 +1,11 @@ +coverage: + status: + project: + default: false + patch: false +github_checks: + annotations: false +comment: + layout: "diff" + behavior: once + after_n_builds: 2 \ No newline at end of file diff --git a/bold/containers/events/producer.go b/bold/containers/events/producer.go new file mode 100644 index 0000000000..2df0605773 --- /dev/null +++ b/bold/containers/events/producer.go @@ -0,0 +1,134 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package events + +import ( + "context" + "sync" + "time" +) + +const ( + defaultBroadcastTimeout = time.Millisecond * 500 + defaultSubscriptionBufferSize = 10 +) + +// Producer manages event subscriptions and broadcasts events to them. +type Producer[T any] struct { + sync.RWMutex + subscriptionBufferSize int + subs []*Subscription[T] + doneListener chan subId // channel to listen for IDs of subscriptions to be remove. + broadcastTimeout time.Duration // maximum duration to wait for an event to be sent. +} + +type ProducerOpt[T any] func(*Producer[T]) + +// WithBroadcastTimeout enables the amount of time the broadcaster will wait to send +// to each subscriber before dropping the send. +func WithBroadcastTimeout[T any](timeout time.Duration) ProducerOpt[T] { + return func(ep *Producer[T]) { + ep.broadcastTimeout = timeout + } +} + +// WithSubscriptionBuffer customizes the size of the subscription buffer channel. +func WithSubscriptionBuffer[T any](size int) ProducerOpt[T] { + return func(ep *Producer[T]) { + ep.subscriptionBufferSize = size + } +} + +func NewProducer[T any](opts ...ProducerOpt[T]) *Producer[T] { + producer := &Producer[T]{ + subs: make([]*Subscription[T], 0), + subscriptionBufferSize: defaultSubscriptionBufferSize, + doneListener: make(chan subId, 100), + broadcastTimeout: defaultBroadcastTimeout, + } + for _, opt := range opts { + opt(producer) + } + return producer +} + +// Start begins listening for subscription cancelation requests or context cancelation. +func (ep *Producer[T]) Start(ctx context.Context) { + for { + select { + case id := <-ep.doneListener: + ep.Lock() + // Check if id overflows the length of the slice. + if int(id) >= len(ep.subs) { + ep.Unlock() + continue + } + // Otherwise, clear the subscription from the list. + ep.subs = append(ep.subs[:id], ep.subs[id+1:]...) + ep.Unlock() + case <-ctx.Done(): + close(ep.doneListener) + ep.subs = nil + return + } + } +} + +// Subscribe returns a handle to a new event subscription, +// adding it to the list of active subscriptions. +func (ep *Producer[T]) Subscribe() *Subscription[T] { + ep.Lock() + defer ep.Unlock() + sub := &Subscription[T]{ + id: subId(len(ep.subs)), // Assign a unique ID based on the current count of subscriptions + events: make(chan T), + done: ep.doneListener, + } + ep.subs = append(ep.subs, sub) + return sub +} + +// Broadcast sends an event to all active subscriptions, respecting a configured timeout or context. +// It spawns goroutines to send events to each subscription so as to not block the producer to submitting +// to all consumers. Broadcast should be used if not all consumers are expected to consume the event, +// within a reasonable time, or if the configured broadcast timeout is short enough. +func (ep *Producer[T]) Broadcast(ctx context.Context, event T) { + ep.RLock() + defer ep.RUnlock() + for _, sub := range ep.subs { + go func(listener *Subscription[T]) { + select { + case listener.events <- event: + case <-time.After(ep.broadcastTimeout): + case <-ctx.Done(): + } + }(sub) + } +} + +type subId int + +// Subscription defines a generic handle to a subscription of +// events from a producer. +type Subscription[T any] struct { + id subId + events chan T + done chan subId +} + +// Next waits for the next event or context cancelation, returning the event or an error. +func (es *Subscription[T]) Next(ctx context.Context) (T, bool) { + var zeroVal T + for { + select { + case ev := <-es.events: + return ev, false + case <-ctx.Done(): + es.done <- es.id + close(es.events) + return zeroVal, true + } + } +} diff --git a/bold/containers/events/producer_test.go b/bold/containers/events/producer_test.go new file mode 100644 index 0000000000..1b09a1f9b4 --- /dev/null +++ b/bold/containers/events/producer_test.go @@ -0,0 +1,70 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package events + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestSubscribe(t *testing.T) { + producer := NewProducer[int]() + sub := producer.Subscribe() + require.Equal(t, 1, len(producer.subs)) + require.NotNil(t, sub) +} + +func TestBroadcast(t *testing.T) { + producer := NewProducer[int]() + sub := producer.Subscribe() + done := make(chan bool) + go func() { + event, shouldEnd := sub.Next(context.Background()) + require.False(t, shouldEnd) + require.Equal(t, 42, event) + done <- true + }() + ctx := context.Background() + producer.Broadcast(ctx, 42) + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("Test timed out waiting for event") + } +} + +func TestBroadcastTimeout(t *testing.T) { + timeout := 50 * time.Millisecond + producer := NewProducer(WithBroadcastTimeout[int](timeout)) + sub := producer.Subscribe() + + go func() { + // Delay sending to simulate timeout scenario + time.Sleep(100 * time.Millisecond) + sub.events <- 42 + }() + + event, shouldEnd := sub.Next(context.Background()) + require.False(t, shouldEnd) + require.Equal(t, 42, event) +} + +func TestEventProducer_Start(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + producer := NewProducer[int]() + go producer.Start(ctx) + + sub := producer.Subscribe() + + // Simulate removing the subscription. + cancel() + _, shouldEnd := sub.Next(ctx) + if !shouldEnd { + t.Error("Expected to end after context cancellation") + } +} diff --git a/bold/containers/fsm/fsm.go b/bold/containers/fsm/fsm.go new file mode 100644 index 0000000000..fd5be5a204 --- /dev/null +++ b/bold/containers/fsm/fsm.go @@ -0,0 +1,187 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package fsm defines a generic, finite state machine in Go that is extremely +// simple and type-safe. It is used by edge tracker goroutines to keep track of +// the edge states and transition to confirmation. +package fsm + +import ( + "fmt" + "sync" + + "github.com/pkg/errors" +) + +var ( + ErrFsmInvalidTransition = errors.New("invalid state transition") + ErrFsmEventNotFound = errors.New("event not found") +) + +// Event defines an event in the finite state machine, which includes +// a type, a set of source states, and a destination state +type Event[E, T fmt.Stringer] struct { + Typ E + From []T + To T +} + +// CurrentState of the finite state machine, including a source event (if any) +// and the actual state value. +type CurrentState[E, T fmt.Stringer] struct { + SourceEvent E + State T + Error error +} + +// Fsm defines a generic, finite state machine which can transition from a series +// of predefined states and events. It can optionally track the executed state +// transitions for debugging or exploratory purposes. +type Fsm[E, T fmt.Stringer] struct { + lock sync.RWMutex + trackingTransitions bool + transitionsExecuted []*transitionMade[E, T] + curr *CurrentState[E, T] + validEvents map[string]bool + validStates map[string]bool + validTransitions map[internalKey]T +} + +// Opt defines a configuration option for the fsm. +type Opt[E, T fmt.Stringer] func(f *Fsm[E, T]) + +// WithTrackedTransitions configures the fsm to track all executed state +// transitions in an slice. +// NOTE: The growth of this slice is unbounded so this method is NOT +// recommended in production. +func WithTrackedTransitions[E, T fmt.Stringer]() Opt[E, T] { + return func(f *Fsm[E, T]) { + f.trackingTransitions = true + } +} + +// New initializes an FSM from a list of valid events / states type +// in a transition table. +// +// var startState doorState +// startState = doorStateClosed +// transitions := []*FsmEvent[doorEvent, doorState]{ +// {Typ: Open{}, From: []doorState{doorStateClosed}, To: doorStateOpened}, +// {Typ: Close{}, From: []doorState{doorStateOpened}, To: doorStateClosed}, +// } +// fsm, err := New(startState, transitions) +// +// the example above showcases how to define an fsm for a simple door +// that can be opened and closed as long. +func New[E, T fmt.Stringer]( + startState T, + transitionTable []*Event[E, T], + opts ...Opt[E, T], +) (*Fsm[E, T], error) { + f := &Fsm[E, T]{ + curr: &CurrentState[E, T]{ + State: startState, + }, + transitionsExecuted: make([]*transitionMade[E, T], 0), + } + for _, opt := range opts { + opt(f) + } + f.validTransitions = make(map[internalKey]T) + f.validEvents = make(map[string]bool) + f.validStates = make(map[string]bool) + for _, ev := range transitionTable { + for _, from := range ev.From { + f.validTransitions[internalKey{ + eventType: ev.Typ.String(), + src: from.String(), + }] = ev.To + f.validStates[from.String()] = true + } + f.validEvents[ev.Typ.String()] = true + } + return f, nil +} + +// CanTransition checks if the fsm can transition from +// its current state using a specified event. +func (f *Fsm[E, T]) CanTransition(event E) bool { + f.lock.RLock() + defer f.lock.RUnlock() + _, ok := f.validTransitions[internalKey{ + eventType: event.String(), + src: f.curr.State.String(), + }] + return ok +} + +// Do executes an event based on the current state of the fsm +// and updates the current state to the destination. +// If the transition is not allowed based on the transition table +// from the fsm initialization, we return ErrFsmInvalidTransition. +// If the event is not found in the transition table, we return ErrFsmEventNotFound. +func (f *Fsm[E, T]) Do(event E) error { + f.lock.Lock() + defer f.lock.Unlock() + src := f.curr.State + key := internalKey{ + eventType: event.String(), + src: src.String(), + } + to, ok := f.validTransitions[key] + if !ok { + for key := range f.validTransitions { + if key.eventType == event.String() { + return errors.Wrapf(ErrFsmInvalidTransition, "source: %s, event %s", src, event) + } + } + return errors.Wrapf(ErrFsmEventNotFound, "event: %s", event) + } + // If we are transition from one state to a different state, clear the FSM error. + if src.String() != to.String() { + f.curr.Error = nil + } + newState := &CurrentState[E, T]{ + State: to, + SourceEvent: event, + Error: f.curr.Error, + } + f.curr = newState + if f.trackingTransitions { + f.transitionsExecuted = append( + f.transitionsExecuted, + &transitionMade[E, T]{From: src, To: to, Event: event}, + ) + } + return nil +} + +// Current returns the current state of the FSM, containing the state value +// and source event it used to get there, if any. +func (f *Fsm[E, T]) Current() *CurrentState[E, T] { + f.lock.RLock() + defer f.lock.RUnlock() + return f.curr +} + +// Current returns the current state of the FSM, containing the state value +// and source event it used to get there, if any. +func (f *Fsm[E, T]) MarkError(e error) { + f.lock.Lock() + defer f.lock.Unlock() + f.curr.Error = e +} + +// An internal key the fsm uses to store data in maps. +type internalKey struct { + eventType string + src string +} + +// A struct keeping track of a state transition in the fsm. +type transitionMade[E, T fmt.Stringer] struct { + From T + To T + Event E +} diff --git a/bold/containers/fsm/fsm_test.go b/bold/containers/fsm/fsm_test.go new file mode 100644 index 0000000000..bad75841de --- /dev/null +++ b/bold/containers/fsm/fsm_test.go @@ -0,0 +1,285 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package fsm + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +type doorEvent interface { + isDoorEvent() bool + fmt.Stringer +} + +type Open struct { + intruderName string +} + +func (o Open) String() string { + return "open" +} + +func (o Open) isDoorEvent() bool { + return true +} + +type Close struct{} + +func (c Close) String() string { + return "close" +} + +func (c Close) isDoorEvent() bool { + return true +} + +type SchrodingersDoorOpenedAndClosed struct{} + +func (c SchrodingersDoorOpenedAndClosed) String() string { + return "open_and_closed" +} + +func (c SchrodingersDoorOpenedAndClosed) isDoorEvent() bool { + return true +} + +type doorState uint8 + +const ( + doorStateInvalid = iota + doorStateOpened + doorStateClosed +) + +func (d doorState) String() string { + switch d { + case doorStateOpened: + return "opened" + case doorStateClosed: + return "closed" + default: + return "invalid" + } +} + +// Creates a simple test where we have a door open/closed state machine. +func TestFSM_OpenClose(t *testing.T) { + var startState doorState = doorStateClosed + transitions := []*Event[doorEvent, doorState]{ + {Typ: Open{}, From: []doorState{doorStateClosed}, To: doorStateOpened}, + {Typ: Close{}, From: []doorState{doorStateOpened}, To: doorStateClosed}, + } + fsm, err := New(startState, transitions) + require.NoError(t, err) + + t.Run("assert state", func(t *testing.T) { + curr := fsm.Current() + require.Equal(t, uint8(doorStateClosed), uint8(curr.State)) + }) + t.Run("invalid transition", func(t *testing.T) { + err = fsm.Do(Close{}) + require.ErrorIs(t, err, ErrFsmInvalidTransition) + }) + t.Run("valid transitions", func(t *testing.T) { + err = fsm.Do(Open{intruderName: "vitalik"}) + require.NoError(t, err) + + curr := fsm.Current() + require.Equal(t, uint8(doorStateOpened), uint8(curr.State)) + openedEv, ok := curr.SourceEvent.(Open) + require.Equal(t, true, ok) + require.Equal(t, "vitalik", openedEv.intruderName) + + err = fsm.Do(Close{}) + require.NoError(t, err) + + curr = fsm.Current() + require.Equal(t, uint8(doorStateClosed), uint8(curr.State)) + + err = fsm.Do(Open{intruderName: "vitalik"}) + require.NoError(t, err) + + curr = fsm.Current() + require.Equal(t, uint8(doorStateOpened), uint8(curr.State)) + }) + t.Run("unknown event", func(t *testing.T) { + err = fsm.Do(SchrodingersDoorOpenedAndClosed{}) + require.ErrorIs(t, err, ErrFsmEventNotFound) + }) +} + +// Checks if our FSM can correctly track state transitions when configured to do so. +func TestFSM_TrackTransitions(t *testing.T) { + var startState doorState = doorStateClosed + transitions := []*Event[doorEvent, doorState]{ + {Typ: Open{}, From: []doorState{doorStateClosed}, To: doorStateOpened}, + {Typ: Close{}, From: []doorState{doorStateOpened}, To: doorStateClosed}, + } + fsm, err := New( + startState, + transitions, + WithTrackedTransitions[doorEvent, doorState](), + ) + require.NoError(t, err) + + err = fsm.Do(Open{intruderName: "vitalik"}) + require.NoError(t, err) + + err = fsm.Do(Close{}) + require.NoError(t, err) + + err = fsm.Do(Open{intruderName: "vitalik"}) + require.NoError(t, err) + + err = fsm.Do(Open{}) + require.ErrorIs(t, err, ErrFsmInvalidTransition) + + require.Equal(t, 3, len(fsm.transitionsExecuted)) + require.Equal(t, uint8(doorStateClosed), uint8(fsm.transitionsExecuted[0].From)) + require.Equal(t, uint8(doorStateOpened), uint8(fsm.transitionsExecuted[0].To)) + _, ok := fsm.transitionsExecuted[0].Event.(Open) + require.Equal(t, true, ok) + + require.Equal(t, uint8(doorStateOpened), uint8(fsm.transitionsExecuted[1].From)) + require.Equal(t, uint8(doorStateClosed), uint8(fsm.transitionsExecuted[1].To)) + _, ok = fsm.transitionsExecuted[1].Event.(Close) + require.Equal(t, true, ok) + + require.Equal(t, uint8(doorStateClosed), uint8(fsm.transitionsExecuted[2].From)) + require.Equal(t, uint8(doorStateOpened), uint8(fsm.transitionsExecuted[2].To)) + _, ok = fsm.transitionsExecuted[2].Event.(Open) + require.Equal(t, true, ok) +} + +type hvacState uint8 + +const ( + hvacStateInvalid = iota + hvacOn + hvacOff + hvacHeating + hvacCooling +) + +func (h hvacState) String() string { + switch h { + case hvacOn: + return "on" + case hvacOff: + return "off" + case hvacHeating: + return "heating" + case hvacCooling: + return "heating" + default: + return "invalid" + } +} + +type hvacEvent interface { + isHvacEvent() bool + fmt.Stringer +} + +type On struct{} +type Off struct{} +type Heat struct { + Temp float64 +} +type Cool struct { + Temp float64 +} + +func (On) isHvacEvent() bool { + return true +} +func (Off) isHvacEvent() bool { + return true +} +func (Heat) isHvacEvent() bool { + return true +} +func (Cool) isHvacEvent() bool { + return true +} +func (On) String() string { + return "turn_on" +} +func (Off) String() string { + return "turn_off" +} +func (Heat) String() string { + return "heat" +} +func (Cool) String() string { + return "cool" +} + +// Tests a more complex fsm that describes an HVAC system which includes cycles. +func TestFSM_ComplexWithCycles(t *testing.T) { + var startState hvacState = hvacOff + transitions := []*Event[hvacEvent, hvacState]{ + {Typ: On{}, From: []hvacState{hvacOff}, To: hvacOn}, + {Typ: Off{}, From: []hvacState{hvacOn, hvacHeating, hvacCooling}, To: hvacOff}, + {Typ: Heat{}, From: []hvacState{hvacOn, hvacHeating, hvacCooling}, To: hvacHeating}, + {Typ: Cool{}, From: []hvacState{hvacOn, hvacHeating, hvacCooling}, To: hvacCooling}, + } + fsm, err := New(startState, transitions) + require.NoError(t, err) + + err = fsm.Do(On{}) + require.NoError(t, err) + + curr := fsm.Current() + require.Equal(t, uint8(hvacOn), uint8(curr.State)) + + err = fsm.Do(Off{}) + require.NoError(t, err) + + curr = fsm.Current() + require.Equal(t, uint8(hvacOff), uint8(curr.State)) + + err = fsm.Do(On{}) + require.NoError(t, err) + + curr = fsm.Current() + require.Equal(t, uint8(hvacOn), uint8(curr.State)) + + err = fsm.Do(Cool{Temp: 18.5}) + require.NoError(t, err) + + curr = fsm.Current() + ev, ok := curr.SourceEvent.(Cool) + require.Equal(t, true, ok) + require.Equal(t, 18.5, ev.Temp) + + err = fsm.Do(Cool{Temp: 17.0}) + require.NoError(t, err) + + curr = fsm.Current() + ev, ok = curr.SourceEvent.(Cool) + require.Equal(t, true, ok) + require.Equal(t, 17.0, ev.Temp) + + err = fsm.Do(Heat{Temp: 23.5}) + require.NoError(t, err) + + curr = fsm.Current() + heatEv, ok := curr.SourceEvent.(Heat) + require.Equal(t, true, ok) + require.Equal(t, 23.5, heatEv.Temp) + + err = fsm.Do(On{}) + require.ErrorIs(t, err, ErrFsmInvalidTransition) + + err = fsm.Do(Off{}) + require.NoError(t, err) + + curr = fsm.Current() + require.Equal(t, uint8(hvacOff), uint8(curr.State)) +} diff --git a/bold/containers/option/option_type.go b/bold/containers/option/option_type.go new file mode 100644 index 0000000000..6b4b77d1d1 --- /dev/null +++ b/bold/containers/option/option_type.go @@ -0,0 +1,32 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package option defines a generic option type as a way of representing +// "nothingness" or "something" in a type-safe way. This is useful for +// representing optional values without the need for nil checks or pointers. +package option + +type Option[T any] struct { + value *T +} + +func None[T any]() Option[T] { + return Option[T]{nil} +} + +func Some[T any](x T) Option[T] { + return Option[T]{&x} +} + +func (x Option[T]) IsNone() bool { + return x.value == nil +} + +func (x Option[T]) IsSome() bool { + return x.value != nil +} + +func (x Option[T]) Unwrap() T { + return *x.value +} diff --git a/bold/containers/slice.go b/bold/containers/slice.go new file mode 100644 index 0000000000..8fbe0dd3be --- /dev/null +++ b/bold/containers/slice.go @@ -0,0 +1,24 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package containers defines generic data structures to be used in the BoLD +// repository. +package containers + +import "fmt" + +// Trunc truncates a byte slice to 4 bytes and pretty-prints as a hex string. +func Trunc(b []byte) string { + if len(b) < 4 { + return fmt.Sprintf("%#x", b) + } + return fmt.Sprintf("%#x", b[:4]) +} + +// Reverse a generic slice. +func Reverse[T any](s []T) { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } +} diff --git a/bold/containers/slice_test.go b/bold/containers/slice_test.go new file mode 100644 index 0000000000..552dfc855e --- /dev/null +++ b/bold/containers/slice_test.go @@ -0,0 +1,37 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package containers + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestReverse(t *testing.T) { + type testCase[T any] struct { + items []T + wanted []T + } + testCases := []testCase[uint64]{ + { + items: []uint64{}, + wanted: []uint64{}, + }, + { + items: []uint64{1}, + wanted: []uint64{1}, + }, + { + items: []uint64{1, 2, 3}, + wanted: []uint64{3, 2, 1}, + }, + } + for _, tt := range testCases { + items := tt.items + Reverse(items) + require.Equal(t, tt.wanted, items) + } +} diff --git a/bold/containers/threadsafe/collections_test.go b/bold/containers/threadsafe/collections_test.go new file mode 100644 index 0000000000..9b87724f97 --- /dev/null +++ b/bold/containers/threadsafe/collections_test.go @@ -0,0 +1,65 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMap(t *testing.T) { + m := NewMap[string, uint64]() + t.Run("Get", func(t *testing.T) { + _, ok := m.TryGet("foo") + require.Equal(t, false, ok) + m.Put("foo", 5) + got, ok := m.TryGet("foo") + require.Equal(t, true, ok) + require.Equal(t, uint64(5), got) + require.Equal(t, uint64(5), m.Get("foo")) + }) + t.Run("Delete", func(t *testing.T) { + m.Delete("foo") + _, ok := m.TryGet("foo") + require.Equal(t, false, ok) + }) + t.Run("ForEach", func(t *testing.T) { + m.Put("foo", 5) + m.Put("bar", 10) + var total uint64 + err := m.ForEach(func(_ string, v uint64) error { + total += v + return nil + }) + require.NoError(t, err) + require.Equal(t, uint64(15), total) + }) +} + +func TestSet(t *testing.T) { + m := NewSet[uint64]() + t.Run("Has", func(t *testing.T) { + ok := m.Has(5) + require.Equal(t, false, ok) + m.Insert(5) + ok = m.Has(5) + require.Equal(t, true, ok) + }) + t.Run("Delete", func(t *testing.T) { + m.Delete(5) + ok := m.Has(5) + require.Equal(t, false, ok) + }) + t.Run("ForEach", func(t *testing.T) { + m.Insert(5) + m.Insert(10) + var total uint64 + m.ForEach(func(elem uint64) { + total += elem + }) + require.Equal(t, uint64(15), total) + }) +} diff --git a/bold/containers/threadsafe/lru_map.go b/bold/containers/threadsafe/lru_map.go new file mode 100644 index 0000000000..0d9214dab5 --- /dev/null +++ b/bold/containers/threadsafe/lru_map.go @@ -0,0 +1,89 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/metrics" +) + +type LruMap[K comparable, V any] struct { + sync.RWMutex + items lru.BasicLRU[K, V] + gauge *metrics.Gauge +} + +type LruMapOpt[K comparable, V any] func(*LruMap[K, V]) + +func LruMapWithMetric[K comparable, V any](name string) LruMapOpt[K, V] { + return func(m *LruMap[K, V]) { + gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_lru_map/"+name, nil) + m.gauge = gauge + } +} + +func NewLruMap[K comparable, V any](capacity int, opts ...LruMapOpt[K, V]) *LruMap[K, V] { + m := &LruMap[K, V]{items: lru.NewBasicLRU[K, V](capacity)} + for _, opt := range opts { + opt(m) + } + return m +} + +func (s *LruMap[K, V]) IsEmpty() bool { + s.RLock() + defer s.RUnlock() + return s.items.Len() == 0 +} + +func (s *LruMap[K, V]) Put(k K, v V) { + s.Lock() + defer s.Unlock() + s.items.Add(k, v) + if s.gauge != nil { + (*s.gauge).Inc(1) + } +} + +func (s *LruMap[K, V]) Has(k K) bool { + s.RLock() + defer s.RUnlock() + return s.items.Contains(k) +} + +func (s *LruMap[K, V]) NumItems() int { + s.RLock() + defer s.RUnlock() + return s.items.Len() +} + +func (s *LruMap[K, V]) TryGet(k K) (V, bool) { + s.RLock() + defer s.RUnlock() + return s.items.Get(k) +} + +func (s *LruMap[K, V]) Delete(k K) { + s.Lock() + defer s.Unlock() + s.items.Remove(k) + if s.gauge != nil { + (*s.gauge).Dec(1) + } +} + +func (s *LruMap[K, V]) ForEach(fn func(k K, v V) error) error { + s.RLock() + defer s.RUnlock() + for _, k := range s.items.Keys() { + v, _ := s.items.Get(k) + if err := fn(k, v); err != nil { + return err + } + } + return nil +} diff --git a/bold/containers/threadsafe/lru_set.go b/bold/containers/threadsafe/lru_set.go new file mode 100644 index 0000000000..0451deebcc --- /dev/null +++ b/bold/containers/threadsafe/lru_set.go @@ -0,0 +1,75 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/metrics" +) + +type LruSet[T comparable] struct { + sync.RWMutex + items lru.BasicLRU[T, bool] + gauge *metrics.Gauge +} + +type LruSetOpt[T comparable] func(*LruSet[T]) + +func LruSetWithMetric[T comparable](name string) LruSetOpt[T] { + return func(s *LruSet[T]) { + gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_lru_set/"+name, nil) + s.gauge = gauge + } +} + +func NewLruSet[T comparable](capacity int, opts ...LruSetOpt[T]) *LruSet[T] { + s := &LruSet[T]{ + items: lru.NewBasicLRU[T, bool](capacity), + } + for _, opt := range opts { + opt(s) + } + return s +} + +func (s *LruSet[T]) Insert(t T) { + s.Lock() + defer s.Unlock() + s.items.Add(t, true) + if s.gauge != nil { + (*s.gauge).Inc(1) + } +} + +func (s *LruSet[T]) NumItems() int { + s.RLock() + defer s.RUnlock() + return s.items.Len() +} + +func (s *LruSet[T]) Has(t T) bool { + s.RLock() + defer s.RUnlock() + return s.items.Contains(t) +} + +func (s *LruSet[T]) Delete(t T) { + s.Lock() + defer s.Unlock() + s.items.Remove(t) + if s.gauge != nil { + (*s.gauge).Dec(1) + } +} + +func (s *LruSet[T]) ForEach(fn func(elem T)) { + s.RLock() + defer s.RUnlock() + for _, elem := range s.items.Keys() { + fn(elem) + } +} diff --git a/bold/containers/threadsafe/map.go b/bold/containers/threadsafe/map.go new file mode 100644 index 0000000000..4a59160691 --- /dev/null +++ b/bold/containers/threadsafe/map.go @@ -0,0 +1,99 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "sync" + + "github.com/ethereum/go-ethereum/metrics" +) + +type Map[K comparable, V any] struct { + sync.RWMutex + items map[K]V + gauge *metrics.Gauge +} + +type MapOpt[K comparable, V any] func(*Map[K, V]) + +func MapWithMetric[K comparable, V any](name string) MapOpt[K, V] { + return func(m *Map[K, V]) { + gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_map/"+name, nil) + m.gauge = gauge + } +} + +func NewMap[K comparable, V any](opts ...MapOpt[K, V]) *Map[K, V] { + m := &Map[K, V]{items: make(map[K]V)} + for _, opt := range opts { + opt(m) + } + return m +} + +func NewMapFromItems[K comparable, V any](m map[K]V) *Map[K, V] { + return &Map[K, V]{items: m} +} + +func (s *Map[K, V]) IsEmpty() bool { + s.RLock() + defer s.RUnlock() + return len(s.items) == 0 +} + +func (s *Map[K, V]) Put(k K, v V) { + s.Lock() + defer s.Unlock() + s.items[k] = v + if s.gauge != nil { + (*s.gauge).Inc(1) + } +} + +func (s *Map[K, V]) Has(k K) bool { + s.RLock() + defer s.RUnlock() + _, ok := s.items[k] + return ok +} + +func (s *Map[K, V]) NumItems() uint64 { + s.RLock() + defer s.RUnlock() + return uint64(len(s.items)) +} + +func (s *Map[K, V]) TryGet(k K) (V, bool) { + s.RLock() + defer s.RUnlock() + item, ok := s.items[k] + return item, ok +} + +func (s *Map[K, V]) Get(k K) V { + s.RLock() + defer s.RUnlock() + return s.items[k] +} + +func (s *Map[K, V]) Delete(k K) { + s.Lock() + defer s.Unlock() + delete(s.items, k) + if s.gauge != nil { + (*s.gauge).Dec(1) + } +} + +func (s *Map[K, V]) ForEach(fn func(k K, v V) error) error { + s.RLock() + defer s.RUnlock() + for k, v := range s.items { + if err := fn(k, v); err != nil { + return err + } + } + return nil +} diff --git a/bold/containers/threadsafe/map_test.go b/bold/containers/threadsafe/map_test.go new file mode 100644 index 0000000000..521f068ebb --- /dev/null +++ b/bold/containers/threadsafe/map_test.go @@ -0,0 +1,92 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "errors" + "testing" +) + +func TestNewMap(t *testing.T) { + m := NewMap[int, string]() + if !m.IsEmpty() { + t.Errorf("Expected map to be empty") + } +} + +func TestNewMapFromItems(t *testing.T) { + initialItems := map[int]string{1: "one", 2: "two"} + m := NewMapFromItems(initialItems) + + if m.NumItems() != 2 { + t.Errorf("Expected 2 items, got %d", m.NumItems()) + } + + if val, ok := m.TryGet(1); !ok || val != "one" { + t.Errorf("Expected 'one', got %s", val) + } + + if val, ok := m.TryGet(2); !ok || val != "two" { + t.Errorf("Expected 'two', got %s", val) + } +} + +func TestPutAndGet(t *testing.T) { + m := NewMap[int, string]() + m.Put(1, "one") + if val := m.Get(1); val != "one" { + t.Errorf("Expected 'one', got %s", val) + } +} + +func TestHas(t *testing.T) { + m := NewMap[int, string]() + m.Put(1, "one") + if !m.Has(1) { + t.Errorf("Expected key to exist") + } +} + +func TestNumItems(t *testing.T) { + m := NewMap[int, string]() + m.Put(1, "one") + m.Put(2, "two") + if m.NumItems() != 2 { + t.Errorf("Expected 2 items, got %d", m.NumItems()) + } +} + +func TestTryGet(t *testing.T) { + m := NewMap[int, string]() + m.Put(1, "one") + val, ok := m.TryGet(1) + if !ok || val != "one" { + t.Errorf("Expected 'one', got %s", val) + } +} + +func TestDelete(t *testing.T) { + m := NewMap[int, string]() + m.Put(1, "one") + m.Delete(1) + if m.Has(1) { + t.Errorf("Expected key to be deleted") + } +} + +func TestForEach(t *testing.T) { + m := NewMap[int, string]() + m.Put(1, "one") + m.Put(2, "two") + err := m.ForEach(func(k int, v string) error { + if v == "three" { + return errors.New("should not have 'three'") + } + return nil + }) + if err != nil { + t.Errorf("ForEach errored: %v", err) + } +} diff --git a/bold/containers/threadsafe/set.go b/bold/containers/threadsafe/set.go new file mode 100644 index 0000000000..e3d24b3cc7 --- /dev/null +++ b/bold/containers/threadsafe/set.go @@ -0,0 +1,74 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "sync" + + "github.com/ethereum/go-ethereum/metrics" +) + +type Set[T comparable] struct { + sync.RWMutex + items map[T]bool + gauge *metrics.Gauge +} + +type SetOpt[T comparable] func(*Set[T]) + +func SetWithMetric[T comparable](name string) SetOpt[T] { + return func(s *Set[T]) { + gauge := metrics.NewRegisteredGauge("arb/validator/threadsafe_set/"+name, nil) + s.gauge = gauge + } +} + +func NewSet[T comparable](opts ...SetOpt[T]) *Set[T] { + s := &Set[T]{ + items: make(map[T]bool), + } + for _, opt := range opts { + opt(s) + } + return s +} + +func (s *Set[T]) Insert(t T) { + s.Lock() + defer s.Unlock() + s.items[t] = true + if s.gauge != nil { + (*s.gauge).Inc(1) + } +} + +func (s *Set[T]) NumItems() uint64 { + s.RLock() + defer s.RUnlock() + return uint64(len(s.items)) +} + +func (s *Set[T]) Has(t T) bool { + s.RLock() + defer s.RUnlock() + return s.items[t] +} + +func (s *Set[T]) Delete(t T) { + s.Lock() + defer s.Unlock() + delete(s.items, t) + if s.gauge != nil { + (*s.gauge).Dec(1) + } +} + +func (s *Set[T]) ForEach(fn func(elem T)) { + s.RLock() + defer s.RUnlock() + for elem := range s.items { + fn(elem) + } +} diff --git a/bold/containers/threadsafe/set_test.go b/bold/containers/threadsafe/set_test.go new file mode 100644 index 0000000000..0948aa8c68 --- /dev/null +++ b/bold/containers/threadsafe/set_test.go @@ -0,0 +1,63 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "testing" +) + +func TestNewSet(t *testing.T) { + s := NewSet[int]() + if s.NumItems() != 0 { + t.Errorf("Expected 0 items, got %d", s.NumItems()) + } +} + +func TestInsert(t *testing.T) { + s := NewSet[int]() + s.Insert(1) + if s.NumItems() != 1 { + t.Errorf("Expected 1 item, got %d", s.NumItems()) + } +} + +func TestHasSet(t *testing.T) { + s := NewSet[int]() + s.Insert(1) + if !s.Has(1) { + t.Errorf("Expected item to exist") + } +} + +func TestDeleteSet(t *testing.T) { + s := NewSet[int]() + s.Insert(1) + s.Delete(1) + if s.Has(1) { + t.Errorf("Expected item to be deleted") + } +} + +func TestNumItemsSet(t *testing.T) { + s := NewSet[int]() + s.Insert(1) + s.Insert(2) + if s.NumItems() != 2 { + t.Errorf("Expected 2 items, got %d", s.NumItems()) + } +} + +func TestForEachSet(t *testing.T) { + s := NewSet[int]() + s.Insert(1) + s.Insert(2) + count := 0 + s.ForEach(func(elem int) { + count++ + }) + if count != 2 { + t.Errorf("Expected to iterate 2 times, iterated %d times", count) + } +} diff --git a/bold/containers/threadsafe/slice.go b/bold/containers/threadsafe/slice.go new file mode 100644 index 0000000000..38a9c4846c --- /dev/null +++ b/bold/containers/threadsafe/slice.go @@ -0,0 +1,54 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package threadsafe defines generic, threadsafe analogues of common data structures +// in Go such as maps, slices, and sets for use in BoLD with an intuitive API. +package threadsafe + +import ( + "sync" + + "github.com/offchainlabs/bold/containers/option" +) + +type Slice[V any] struct { + sync.RWMutex + items []V +} + +func NewSlice[V any]() *Slice[V] { + return &Slice[V]{items: make([]V, 0)} +} + +func (s *Slice[V]) Push(v V) { + s.Lock() + defer s.Unlock() + s.items = append(s.items, v) +} + +func (s *Slice[V]) Get(i int) option.Option[V] { + s.RLock() + defer s.RUnlock() + if i >= len(s.items) { + return option.None[V]() + } + return option.Some(s.items[i]) +} + +func (s *Slice[V]) Len() int { + s.RLock() + defer s.RUnlock() + return len(s.items) +} + +func (s *Slice[V]) Find(fn func(idx int, elem V) bool) bool { + s.RLock() + defer s.RUnlock() + for ii, vv := range s.items { + if fn(ii, vv) { + return true + } + } + return false +} diff --git a/bold/containers/threadsafe/slice_test.go b/bold/containers/threadsafe/slice_test.go new file mode 100644 index 0000000000..9b74de1a02 --- /dev/null +++ b/bold/containers/threadsafe/slice_test.go @@ -0,0 +1,56 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package threadsafe + +import ( + "testing" +) + +func TestNewSlice(t *testing.T) { + s := NewSlice[int]() + if s.Len() != 0 { + t.Errorf("Expected length to be 0, got %d", s.Len()) + } +} + +func TestPush(t *testing.T) { + s := NewSlice[int]() + s.Push(1) + if s.Len() != 1 { + t.Errorf("Expected length to be 1, got %d", s.Len()) + } +} + +func TestLen(t *testing.T) { + s := NewSlice[int]() + s.Push(1) + s.Push(2) + if s.Len() != 2 { + t.Errorf("Expected length to be 2, got %d", s.Len()) + } +} + +func TestFind(t *testing.T) { + s := NewSlice[int]() + s.Push(1) + s.Push(2) + s.Push(3) + + found := s.Find(func(idx int, elem int) bool { + return elem == 2 + }) + + if !found { + t.Errorf("Expected to find the element") + } + + notFound := s.Find(func(idx int, elem int) bool { + return elem == 4 + }) + + if notFound { + t.Errorf("Did not expect to find the element") + } +} diff --git a/bold/docs/ARCHITECTURE.md b/bold/docs/ARCHITECTURE.md new file mode 100644 index 0000000000..352dacbd41 --- /dev/null +++ b/bold/docs/ARCHITECTURE.md @@ -0,0 +1,37 @@ +# BOLD Architecture + +When explaining how all components of BOLD fit together, it’s helpful to consider the software that runs an actual validator for Arbitrum: Arbitrum Nitro. Some of its responsibilities are: + +- To **execute incoming transactions** from an Inbox contract and produce post-states according to the L2 state transition +- To **submit transaction batches** to Ethereum L1 to a contract known as the SequencerInbox +- To validate transactions and submit claims known as **assertions** about the L2 state at frequent intervals to Ethereum, to a contract known as `RollupCore.sol` +- To **challenge** incorrect assertions posted by dishonest validators to `RollupCore.sol` + +![Image](diagrams/nitro-integration.drawio.png) + +Most importantly, Arbitrum technology is moving into a more modular architecture in which specific components can be run as standalone binaries, allowing for greater resilience and easier changes in the future. BOLD implements a component of Nitro responsible for posting assertions and challenging invalid ones, and therefore depends on other parts of the Nitro node to provide it with the state data it needs. The BOLD repository is a standalone Github project separate from Nitro, as it can be plugged into Nitro chains as a dependency to perform its roles. + +## How it Works + +The **BOLD Challenge Manager** above interacts with a few items from an Arbitrum node, allowing it to make challenge moves and submit history commitments to the contracts on Ethereum. In our code, abstract away those L2 node components via a small interface called the **L2 State Provider**. + +Recall the core primitive of the protocol are **challenge edges**, where an edge represents a start and end history commitment to an Arbitrum chain. Participants in the protocol can make **moves** on challenges, and validators can **participate in many challenges concurrently.** That is, our software needs to track edges within a challenge and their lifecycle in order to win against malicious entities. + +Our detailed architecture looks like this: + +![Image](diagrams/bold-internals.drawio.png) + +Here's how it works: + +1. An **Assertion Poster** frequently takes validated messages from the L2 validator and posts them onchain +2. An **Assertion Scanner** checks smart contracts on Ethereum for newly posted assertions and compares them against the local Nitro node’s DB. This is done via an abstraction known as a **L2 State Provider**. This is within `assertions/scanner.go` +3. If we disagree with an assertion, our **Challenge Manager** submits an onchain challenge by creating a level zero edge, and tracks that edge as a go routine +4. Our **Chain Watcher**, in the background, scans for other created edges onchain and spawns edge tracker goroutines for honest edges we are not yet tracking + +Each edge is tracked as a goroutine called an **Edge Tracker**. Edge trackers are self-contained goroutines that wake up at specified tick intervals and use a finite state machine to decide what challenge move to take. For example, after an edge is created, it will try to bisect, confirm via one step proof, or create a subchallenge depending on its current state. + +Here's what the finite state machine of an edge tracker looks like, with its final state being `Confirmed`: + +![Image](diagrams/edge-tracker-fsm.drawio.png) + +Once a level zero edge for a challenge on an assertion is confirmed, the assertion can then be confirmed and the honest party will emerge successful. \ No newline at end of file diff --git a/bold/docs/audits/TrailOfBitsAudit.pdf b/bold/docs/audits/TrailOfBitsAudit.pdf new file mode 100644 index 0000000000..30a4f61576 Binary files /dev/null and b/bold/docs/audits/TrailOfBitsAudit.pdf differ diff --git a/bold/docs/diagrams/api.drawio.png b/bold/docs/diagrams/api.drawio.png new file mode 100644 index 0000000000..6dc3388103 Binary files /dev/null and b/bold/docs/diagrams/api.drawio.png differ diff --git a/bold/docs/diagrams/bold-internals.drawio.png b/bold/docs/diagrams/bold-internals.drawio.png new file mode 100644 index 0000000000..de43d6821a Binary files /dev/null and b/bold/docs/diagrams/bold-internals.drawio.png differ diff --git a/bold/docs/diagrams/edge-tracker-fsm.drawio.png b/bold/docs/diagrams/edge-tracker-fsm.drawio.png new file mode 100644 index 0000000000..43334fae58 Binary files /dev/null and b/bold/docs/diagrams/edge-tracker-fsm.drawio.png differ diff --git a/bold/docs/diagrams/nitro-integration.drawio.png b/bold/docs/diagrams/nitro-integration.drawio.png new file mode 100644 index 0000000000..7c01c8a3c8 Binary files /dev/null and b/bold/docs/diagrams/nitro-integration.drawio.png differ diff --git a/bold/docs/research-specs/BOLDChallengeProtocol.pdf b/bold/docs/research-specs/BOLDChallengeProtocol.pdf new file mode 100644 index 0000000000..db92758daa Binary files /dev/null and b/bold/docs/research-specs/BOLDChallengeProtocol.pdf differ diff --git a/bold/docs/research-specs/Economics.pdf b/bold/docs/research-specs/Economics.pdf new file mode 100644 index 0000000000..a9125a0b53 Binary files /dev/null and b/bold/docs/research-specs/Economics.pdf differ diff --git a/bold/docs/research-specs/TechnicalDeepDive.pdf b/bold/docs/research-specs/TechnicalDeepDive.pdf new file mode 100644 index 0000000000..6ccba4df5c Binary files /dev/null and b/bold/docs/research-specs/TechnicalDeepDive.pdf differ diff --git a/bold/go.mod b/bold/go.mod new file mode 100644 index 0000000000..6051083902 --- /dev/null +++ b/bold/go.mod @@ -0,0 +1,104 @@ +module github.com/offchainlabs/bold + +go 1.24.5 + +require ( + github.com/ccoveille/go-safecast v1.1.0 + github.com/ethereum/go-ethereum v1.15.5 + github.com/gorilla/mux v1.8.0 + github.com/jmoiron/sqlx v1.4.0 + github.com/mattn/go-sqlite3 v1.14.22 + github.com/offchainlabs/nitro v0.0.0-00010101000000-000000000000 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.10.0 + golang.org/x/sync v0.12.0 +) + +require ( + github.com/DataDog/zstd v1.5.2 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.20.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cockroachdb/errors v1.11.3 // indirect + github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v1.1.2 // indirect + github.com/cockroachdb/redact v1.1.5 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.27 // indirect + github.com/consensys/gnark-crypto v0.16.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/deckarep/golang-set/v2 v2.6.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect + github.com/ethereum/go-verkle v0.2.2 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/gammazero/deque v0.2.1 // indirect + github.com/getsentry/sentry-go v0.27.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/go-bexpr v0.1.10 // indirect + github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect + github.com/holiman/uint256 v1.3.2 // indirect + github.com/huin/goupnp v1.3.0 // indirect + github.com/jackpal/go-nat-pmp v1.0.2 // indirect + github.com/klauspost/compress v1.17.2 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/mitchellh/mapstructure v1.4.1 // indirect + github.com/mitchellh/pointerstructure v1.2.0 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pion/dtls/v2 v2.2.7 // indirect + github.com/pion/logging v0.2.2 // indirect + github.com/pion/stun/v2 v2.0.0 // indirect + github.com/pion/transport/v2 v2.2.1 // indirect + github.com/pion/transport/v3 v3.0.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.4.0 // indirect + github.com/prometheus/common v0.39.0 // indirect + github.com/prometheus/procfs v0.9.0 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rs/cors v1.7.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/supranational/blst v0.3.14 // indirect + github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/urfave/cli/v2 v2.27.5 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.9.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) + +replace github.com/offchainlabs/nitro => ../ + +replace github.com/ethereum/go-ethereum => ../go-ethereum diff --git a/bold/go.sum b/bold/go.sum new file mode 100644 index 0000000000..5ffe109f20 --- /dev/null +++ b/bold/go.sum @@ -0,0 +1,376 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= +github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= +github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/ccoveille/go-safecast v1.1.0 h1:iHKNWaZm+OznO7Eh6EljXPjGfGQsSfa6/sxPlIEKO+g= +github.com/ccoveille/go-safecast v1.1.0/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8= +github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= +github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= +github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= +github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= +github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= +github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= +github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs= +github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw4KoTAawo= +github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= +github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= +github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= +github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= +github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= +github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= +github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/deepmap/oapi-codegen v1.6.0 h1:w/d1ntwh91XI0b/8ja7+u5SvA4IFfM0UNNLmiDR1gg0= +github.com/deepmap/oapi-codegen v1.6.0/go.mod h1:ryDa9AgbELGeB+YEXE1dR53yAjHwFvE9iAUlWl9Al3M= +github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= +github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= +github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= +github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= +github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= +github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= +github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= +github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/graph-gophers/graphql-go v1.3.0 h1:Eb9x/q6MFpCLz7jBCiP/WTxjSDrYLR1QY41SORZyNJ0= +github.com/graph-gophers/graphql-go v1.3.0/go.mod h1:9CQHMSxwO4MprSdzoIEobiHpoLtHm77vfxsvsIN5Vuc= +github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= +github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= +github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= +github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= +github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= +github.com/influxdata/influxdb-client-go/v2 v2.4.0 h1:HGBfZYStlx3Kqvsv1h2pJixbCl/jhnFtxpKFAv9Tu5k= +github.com/influxdata/influxdb-client-go/v2 v2.4.0/go.mod h1:vLNHdxTJkIf2mSLvGrpj8TCcISApPoXkaxP8g9uRlW8= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= +github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= +github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pion/dtls/v2 v2.2.7 h1:cSUBsETxepsCSFSxC3mc/aDo14qQLMSL+O6IjG28yV8= +github.com/pion/dtls/v2 v2.2.7/go.mod h1:8WiMkebSHFD0T+dIU+UeBaoV7kDhOW5oDCzZ7WZ/F9s= +github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY= +github.com/pion/logging v0.2.2/go.mod h1:k0/tDVsRCX2Mb2ZEmTqNa7CWsQPc+YYCB7Q+5pahoms= +github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= +github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= +github.com/pion/transport/v2 v2.2.1 h1:7qYnCBlpgSJNYMbLCKuSY9KbQdBFoETvPNETv0y4N7c= +github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= +github.com/pion/transport/v3 v3.0.1 h1:gDTlPJwROfSfz6QfSi0ZmeCSkFcnWWiiR9ES0ouANiM= +github.com/pion/transport/v3 v3.0.1/go.mod h1:UY7kiITrlMv7/IKgd5eTUcaahZx5oUN3l9SzK5f5xE0= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw= +github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y= +github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= +github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.39.0 h1:oOyhkDq05hPZKItWVBkJ6g6AtGxi+fy7F4JvUV8uhsI= +github.com/prometheus/common v0.39.0/go.mod h1:6XBZ7lYdLCbkAVhwRsWTZn+IN5AB9F/NXd5w0BbEX0Y= +github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= +github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= +github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= +github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= +github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/bold/layer2-state-provider/history_commitment_provider.go b/bold/layer2-state-provider/history_commitment_provider.go new file mode 100644 index 0000000000..8e2e17e062 --- /dev/null +++ b/bold/layer2-state-provider/history_commitment_provider.go @@ -0,0 +1,698 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package l2stateprovider + +import ( + "context" + "fmt" + "math/big" + "slices" + "strconv" + "time" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/metrics" + + "github.com/offchainlabs/bold/api" + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/state-commitments/history" + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" +) + +// MachineHashCollector defines an interface which collects hashes of the state +// of Arbitrator machines according to a provided `cfg` argument. +// See the documentation of the HashCollectorConfig for the details of how the +// configuration affects the collection of machine hashes. +type MachineHashCollector interface { + CollectMachineHashes(ctx context.Context, cfg *HashCollectorConfig) ([]common.Hash, error) +} + +// ProofCollector defines an interface which can collect a one-step proof from +// an Arbitrator machine at a block height offset from some global state, and +// at a specific opcode index. +type ProofCollector interface { + CollectProof( + ctx context.Context, + assertionMetadata *AssociatedAssertionMetadata, + blockChallengeHeight Height, + machineIndex OpcodeIndex, + ) ([]byte, error) +} + +// HashCollectorConfig configures the behavior the CollectMachineHashes method. +// +// The goal of CollectMachineHashes is to gather Arbitrator machine hashes for +// a specific arbitrum block in the context of a BoLD challenge which has been +// created to deterimine which assertion is correct. +// +// Depending on the challenge level, the set of machine hashes the collector +// needs to collect can vary. But, it will always be some set of machine hashes +// which represent states of the Arbitrator machine when executing a specific +// "challenged" block. The "challenged" block is the block within the range +// of an assertion where the rival assertion and this staker's assertions +// diverge. +// +// To determine the exact block from which to collect the machine hashes, the +// collector needs to know the `FromState` which contains the batch and position +// within that batch of the first messsage to which the rival assertions are +// committing. In addiiton, the collector needs to know the +// `BlockChallengeHeight` (which is a relative index within the range of blocks +// to which the rival assertions are committing where they first diverge.) +// +// The collector also needs to know the `BatchLimit` to deal with scenarios +// where the `BlockChallengeHeight` is greater than the number of blocks in the +// assertion. +// +// Most of this information is configured using an `AssociatedAssertionMetadata` +// instance. +// +// The collector then starts collecting hashes at a specific `MachineStartIndex` +// which is an opcode index within the execution the block which corresponds to +// the first machine state hash to be returned. It then steps through the +// Arbitrator machine in increments of `StepSize` until it has collected the +// `NumDesiredHashes` machine hashes. +type HashCollectorConfig struct { + // Miscellaneous metadata for assertion the commitment is being made for. + // Includes the WasmModuleRoot and the start and end states. + AssertionMetadata *AssociatedAssertionMetadata + // The block challenge height is the height of the block at which the rival + // assertions diverge. + BlockChallengeHeight Height + // Defines the heights at which the collector collects machine hashes for + // each challenge level. + // An index in this slice represents a challenge level, and a value + // represents a height within that challenge level. + StepHeights []Height + // The number of desired hashes to be collected. + NumDesiredHashes uint64 + // The opcode index at which to start stepping through the machine. + MachineStartIndex OpcodeIndex + // The step size for stepping through the machine to collect its hashes. + StepSize StepSize +} + +func (h *HashCollectorConfig) String() string { + str := "" + str += h.AssertionMetadata.WasmModuleRoot.String() + str += "/" + str += fmt.Sprintf("%d", h.AssertionMetadata.FromState.Batch) + str += "/" + str += fmt.Sprintf("%d", h.AssertionMetadata.FromState.PosInBatch) + str += "/" + str += fmt.Sprintf("%d", h.BlockChallengeHeight) + str += "/" + for _, height := range h.StepHeights { + str += fmt.Sprintf("%d", height) + str += "/" + } + str += fmt.Sprintf("%d", h.NumDesiredHashes) + str += "/" + str += fmt.Sprintf("%d", h.MachineStartIndex) + str += "/" + str += fmt.Sprintf("%d", h.StepSize) + return str +} + +// L2MessageStateCollector defines an interface which can obtain the machine +// hashes at each L2 message from fromState to batchLimit, ending at +// batch=batchLimit posInBatch=0 unless toHeight+1 states are produced first, +// in which case it ends there. +type L2MessageStateCollector interface { + L2MessageStatesUpTo( + ctx context.Context, + fromState protocol.GoGlobalState, + batchLimit Batch, + toHeight option.Option[Height], + ) ([]common.Hash, error) +} + +// HistoryCommitmentProvider computes history commitments from input parameters +// by loading Arbitrator machines for L2 state transitions. It can compute +// history commitments over ranges of opcodes at specified increments used for +// the BoLD protocol. +type HistoryCommitmentProvider struct { + l2MessageStateCollector L2MessageStateCollector + machineHashCollector MachineHashCollector + proofCollector ProofCollector + challengeLeafHeights []Height + apiDB db.Database + ExecutionProvider +} + +// NewHistoryCommitmentProvider creates an instance of a struct which can +// compute history commitments over any number of challenge levels for BoLD. +func NewHistoryCommitmentProvider( + l2MessageStateCollector L2MessageStateCollector, + machineHashCollector MachineHashCollector, + proofCollector ProofCollector, + challengeLeafHeights []Height, + executionProvider ExecutionProvider, + apiDB db.Database, +) *HistoryCommitmentProvider { + return &HistoryCommitmentProvider{ + l2MessageStateCollector: l2MessageStateCollector, + machineHashCollector: machineHashCollector, + proofCollector: proofCollector, + challengeLeafHeights: challengeLeafHeights, + ExecutionProvider: executionProvider, + apiDB: apiDB, + } +} + +// A list of heights that have been validated to be non-empty +// and to be less than the total number of challenge levels in the protocol. +type validatedStartHeights []Height + +func (p *HistoryCommitmentProvider) UpdateAPIDatabase(apiDB db.Database) { + p.apiDB = apiDB +} + +// virtualFrom computes the virtual value for a history commitment +// +// I the optional h value is None, then based on the challenge level, and given +// slice of challenge origin heights (coh) determine the maximum number of +// leaves for that level and return it as virtual. +func (p *HistoryCommitmentProvider) virtualFrom(h option.Option[Height], coh []Height) (uint64, error) { + var virtual uint64 + if h.IsNone() { + validatedHeights, err := p.validateOriginHeights(coh) + if err != nil { + return 0, err + } + if len(validatedHeights) == 0 { + virtual = uint64(p.challengeLeafHeights[0]) + 1 + } else { + lvl := deepestRequestedChallengeLevel(validatedHeights) + virtual = uint64(p.challengeLeafHeights[lvl]) + 1 + } + } else { + virtual = uint64(h.Unwrap()) + 1 + } + return virtual, nil +} + +// HistoryCommitment computes a Merklelized commitment over a set of hashes +// at specified challenge levels. For block challenges, for example, this is a +// set of machine hashes corresponding each message in a range N to M. +func (p *HistoryCommitmentProvider) HistoryCommitment( + ctx context.Context, + req *HistoryCommitmentRequest, +) (history.History, error) { + hashes, err := p.historyCommitmentImpl(ctx, req) + if err != nil { + return history.History{}, err + } + virtual, err := p.virtualFrom(req.UpToHeight, req.UpperChallengeOriginHeights) + if err != nil { + return history.History{}, err + } + return history.NewCommitment(hashes, virtual) +} + +func (p *HistoryCommitmentProvider) historyCommitmentImpl( + ctx context.Context, + req *HistoryCommitmentRequest, +) ([]common.Hash, error) { + // Validate the input heights for correctness. + validatedHeights, err := p.validateOriginHeights(req.UpperChallengeOriginHeights) + if err != nil { + return nil, err + } + // If the call is for message number ranges only, we get the hashes for + // those states and return a commitment for them. + var fromBlockChallengeHeight Height + if len(validatedHeights) == 0 { + hashes, hashesErr := p.l2MessageStateCollector.L2MessageStatesUpTo( + ctx, + req.AssertionMetadata.FromState, + req.AssertionMetadata.BatchLimit, + req.UpToHeight, + ) + if hashesErr != nil { + return nil, hashesErr + } + return hashes, nil + } else { + fromBlockChallengeHeight = validatedHeights[0] + } + + // Computes the desired challenge level this history commitment is for. + desiredChallengeLevel := deepestRequestedChallengeLevel(validatedHeights) + + // At each challenge level, the history commitment always starts from the + // state just before the first opcode within the range of opcodes to which + // the challenge has been narrowed. + startIdx := Height(0) + + // Compute the exact start point of where we need to execute + // the machine from the inputs, and figure out, in what increments, we need + // to do so. + machineStartIndex, err := p.computeMachineStartIndex(validatedHeights, startIdx) + if err != nil { + return nil, err + } + + // We compute the stepwise increments we need for stepping through the + // machine. + stepSize, err := p.computeStepSize(desiredChallengeLevel) + if err != nil { + return nil, err + } + + // Compute the maximum number of machine hashes we need to collect at the + // desired challenge level. + maxHashes, err := p.computeRequiredNumberOfHashes(desiredChallengeLevel, req.UpToHeight) + if err != nil { + return nil, err + } + + // Collect the machine hashes at the specified challenge level based on the + // values we computed. + cfg := &HashCollectorConfig{ + AssertionMetadata: req.AssertionMetadata, + BlockChallengeHeight: fromBlockChallengeHeight, + // We drop the first index of the validated heights, because the first + // index is for the block challenge level, which is over blocks and not + // over individual machine WASM opcodes. Starting from the second index, + // we are now dealing with challenges over ranges of opcodes which are + // what we care about for our implementation of machine hash collection. + StepHeights: validatedHeights[1:], + NumDesiredHashes: maxHashes, + MachineStartIndex: machineStartIndex, + StepSize: stepSize, + } + // Requests collecting machine hashes for the specified config. + if !api.IsNil(p.apiDB) { + var rawStepHeights string + for i, stepHeight := range cfg.StepHeights { + hInt, err := safecast.ToInt(stepHeight) + if err != nil { + return nil, err + } + rawStepHeights += strconv.Itoa(hInt) + if i != len(rawStepHeights)-1 { + rawStepHeights += "," + } + } + collectMachineHashes := api.JsonCollectMachineHashes{ + WasmModuleRoot: cfg.AssertionMetadata.WasmModuleRoot, + FromBatch: cfg.AssertionMetadata.FromState.Batch, + PositionInBatch: cfg.AssertionMetadata.FromState.PosInBatch, + BatchLimit: uint64(cfg.AssertionMetadata.BatchLimit), + BlockChallengeHeight: uint64(cfg.BlockChallengeHeight), + RawStepHeights: rawStepHeights, + NumDesiredHashes: cfg.NumDesiredHashes, + MachineStartIndex: uint64(cfg.MachineStartIndex), + StepSize: uint64(cfg.StepSize), + StartTime: time.Now().UTC(), + } + err := p.apiDB.InsertCollectMachineHash(&collectMachineHashes) + if err != nil { + return nil, err + } + defer func() { + finishTime := time.Now().UTC() + collectMachineHashes.FinishTime = &finishTime + err := p.apiDB.UpdateCollectMachineHash(&collectMachineHashes) + if err != nil { + return + } + }() + } + startTime := time.Now() + defer func() { + // TODO: Replace NewUniformSample(100) with + // NewBoundedHistogramSample(), once offchainlabs geth is merged in + // bold. + // Eg https://github.com/offchainlabs/nitro/blob/ab6790a9e33884c3b4e81de2a97dae5bf904266e/das/restful_server.go#L30 + sizeInt, err := safecast.ToInt(stepSize) + if err != nil { + return + } + metrics.GetOrRegisterHistogram("arb/state_provider/collect_machine_hashes/step_size_"+strconv.Itoa(sizeInt)+"/duration", nil, metrics.NewUniformSample(100)).Update(time.Since(startTime).Nanoseconds()) + }() + return p.machineHashCollector.CollectMachineHashes(ctx, cfg) +} + +// AgreesWithHistoryCommitment checks if the l2 state provider agrees with a +// specified start and end history commitment for a type of edge under a +// specified assertion challenge. It returns an agreement struct which informs +// the caller whether (a) we agree with the start commitment, and whether (b) +// the edge is honest, meaning that we also agree with the end commitment. +func (p *HistoryCommitmentProvider) AgreesWithHistoryCommitment( + ctx context.Context, + challengeLevel protocol.ChallengeLevel, + historyCommitMetadata *HistoryCommitmentRequest, + commit History, +) (bool, error) { + var localCommit history.History + var err error + switch challengeLevel { + case protocol.NewBlockChallengeLevel(): + localCommit, err = p.HistoryCommitment( + ctx, + &HistoryCommitmentRequest{ + AssertionMetadata: historyCommitMetadata.AssertionMetadata, + UpperChallengeOriginHeights: []Height{}, + UpToHeight: option.Some(Height(commit.Height)), + }, + ) + if err != nil { + return false, err + } + default: + localCommit, err = p.HistoryCommitment( + ctx, + &HistoryCommitmentRequest{ + AssertionMetadata: historyCommitMetadata.AssertionMetadata, + UpperChallengeOriginHeights: historyCommitMetadata.UpperChallengeOriginHeights, + UpToHeight: option.Some(Height(commit.Height)), + }, + ) + if err != nil { + return false, err + } + } + return localCommit.Height == commit.Height && localCommit.Merkle == commit.MerkleRoot, nil +} + +var ( + b32Arr, _ = abi.NewType("bytes32[]", "", nil) + // ProofArgs for submission to the protocol. + ProofArgs = abi.Arguments{ + {Type: b32Arr, Name: "prefixExpansion"}, + {Type: b32Arr, Name: "prefixProof"}, + } +) + +// PrefixProof allows a caller to provide a proof that, given heights N < M, +// that the history commitment for height N is a Merkle prefix of the +// commitment at height M. +// +// Here's how one would use it: +// +// fromMessageNumber := 1000 +// +// PrefixProof( +// wasmModuleRoot, +// batch, +// []Height{16}, +// fromMessageNumber, +// upToHeight(Height(24)), +// ) +// +// This means that we want a proof that the history commitment at height 16 +// is a prefix of the history commitment at height 24. Each index in the +// []Height{} slice represents a challenge level. For example, this call wants +// us to use the history commitment at the very first challenge level, over +// blocks. +func (p *HistoryCommitmentProvider) PrefixProof( + ctx context.Context, + req *HistoryCommitmentRequest, + prefixHeight Height, +) ([]byte, error) { + // Obtain the leaves we need to produce our Merkle expansion. + leaves, err := p.historyCommitmentImpl( + ctx, + req, + ) + if err != nil { + return nil, err + } + virtual, err := p.virtualFrom(req.UpToHeight, req.UpperChallengeOriginHeights) + if err != nil { + return nil, err + } + // If no upToHeight is provided, we want to use the max number of leaves in + // our computation. + lowCommitmentNumLeaves := uint64(prefixHeight + 1) + // The prefix proof may be over a range of leaves that include virtual ones. + prefixLen := min(lowCommitmentNumLeaves, uint64(len(leaves))) + prefixHashes := slices.Clone(leaves[:prefixLen]) + prefixRoot, err := history.ComputeRoot(prefixHashes, lowCommitmentNumLeaves) + if err != nil { + return nil, err + } + fullTreeHashes := slices.Clone(leaves) + fullTreeRoot, err := history.ComputeRoot(fullTreeHashes, virtual) + if err != nil { + return nil, err + } + hashesForProof := make([]common.Hash, len(leaves)) + for i := uint64(0); i < uint64(len(leaves)); i++ { + hashesForProof[i] = leaves[i] + } + prefixExp, proof, err := history.GeneratePrefixProof(uint64(prefixHeight), hashesForProof, virtual) + if err != nil { + return nil, err + } + // We verify our prefix proof before an onchain submission as an extra + // safety-check. + if err = prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: prefixRoot, + PreSize: lowCommitmentNumLeaves, + PostRoot: fullTreeRoot, + PostSize: virtual, + PreExpansion: prefixExp, + PrefixProof: proof, + }); err != nil { + return nil, fmt.Errorf("could not verify prefix proof locally: %w", err) + } + return ProofArgs.Pack(&prefixExp, &proof) +} + +func (p *HistoryCommitmentProvider) OneStepProofData( + ctx context.Context, + assertionMetadata *AssociatedAssertionMetadata, + startHeights []Height, + upToHeight Height, +) (*protocol.OneStepData, []common.Hash, []common.Hash, error) { + // Start heights must reflect at least one challenge level to produce one + // step proofs. + if len(startHeights) < 1 { + return nil, nil, nil, fmt.Errorf("upper challenge origin heights must have at least length 1, got %d", len(startHeights)) + } + endCommit, err := p.HistoryCommitment( + ctx, + &HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: startHeights, + UpToHeight: option.Some(upToHeight + 1), + }, + ) + if err != nil { + return nil, nil, nil, err + } + startCommit, err := p.HistoryCommitment( + ctx, + &HistoryCommitmentRequest{ + AssertionMetadata: assertionMetadata, + UpperChallengeOriginHeights: startHeights, + UpToHeight: option.Some(upToHeight), + }, + ) + if err != nil { + return nil, nil, nil, err + } + + // Compute the exact start point of where we need to execute the machine + // from the inputs, and figure out, in what increments, we need to do so. + machineIndex, err := p.computeMachineStartIndex(startHeights, upToHeight) + if err != nil { + return nil, nil, nil, err + } + + osp, err := p.proofCollector.CollectProof(ctx, assertionMetadata, startHeights[0], machineIndex) + if err != nil { + return nil, nil, nil, err + } + + data := &protocol.OneStepData{ + BeforeHash: startCommit.LastLeaf, + AfterHash: endCommit.LastLeaf, + Proof: osp, + } + return data, startCommit.LastLeafProof, endCommit.LastLeafProof, nil +} + +// Computes the required number of hashes for a history commitment +// based on the requested challenge level. The required number of hashes +// for a leaf commitment at each challenge level is a constant, so we can +// determine the desired challenge level from the input params and compute the +// total from there. +func (p *HistoryCommitmentProvider) computeRequiredNumberOfHashes( + challengeLevel uint64, + upToHeight option.Option[Height], +) (uint64, error) { + maxHeightForLevel, err := p.leafHeightAtChallengeLevel(challengeLevel) + if err != nil { + return 0, err + } + + // Get the requested history commitment height we need at our desired + // challenge level. + var end Height + if upToHeight.IsNone() { + end = maxHeightForLevel + } else { + end = upToHeight.Unwrap() + // If the end height is more than the allowed max, we return an error. + // This scenario should not happen, and instead of silently truncating, + // surfacing an error is the safest way of warning the operator + // they are committing something invalid. + if end > maxHeightForLevel { + return 0, fmt.Errorf( + "end %d was greater than max height for level %d", + end, + maxHeightForLevel, + ) + } + } + // The number of hashes is the difference between the start and end + // requested heights, plus 1. But, since we always start at 0, it's just + // the end height + 1. + return uint64(end) + 1, nil +} + +// Figures out the actual opcode index we should move the machine to +// when we compute the history commitment. As there are different levels of +// challenge granularity, we have to do some math to figure out the correct +// index. +// +// Take, for example, that we have 4 challenge kinds: +// +// block_challenge => over a range of L2 message hashes +// megastep_challenge => over ranges of 1048576 (2^20) opcodes at a time. +// kilostep_challenge => over ranges of 1024 (2^10) opcodes at a time +// step_challenge => over a range of individual WASM opcodes +// +// We only directly step through WASM machines when in a subchallenge (starting +// at megastep), so we can ignore block challenges for this calculation. +// +// Let's say we want to figure out the machine start opcode index for the +// following inputs: +// +// megastep=4, kilostep=5, step=10 +// +// We can compute the opcode index using the following algorithm for the example +// above. +// +// 4 * (1048576) +// + 5 * (1024) +// + 10 +// = 4,199,434 +// +// This generalizes for any number of subchallenge levels into the algorithm +// below. +// It works by taking the sum of (each input * product of all challenge level +// height constants beneath its level). +// This means we start executing our machine exactly at opcode index 4,199,434. +func (p *HistoryCommitmentProvider) computeMachineStartIndex( + upperChallengeOriginHeights validatedStartHeights, + fromHeight Height, +) (OpcodeIndex, error) { + // For the block challenge level, the machine start opcode index is 0. + if len(upperChallengeOriginHeights) == 0 { + return 0, nil + } + // The first position in the start heights slice is the block challenge + // level, which is over ranges of L2 messages and not over individual + // opcodes. We ignore this level and start at the next level when it comes + // to dealing with machines. + heights := upperChallengeOriginHeights[1:] + heights = append(heights, fromHeight) + leafHeights := p.challengeLeafHeights[1:] + + // Next, we compute the opcode index. We use big ints to make sure we do not + // overflow uint64 as this computation depends on external user inputs. + opcodeIndex := new(big.Int).SetUint64(0) + idx := 1 + for _, height := range heights { + total := new(big.Int).SetUint64(1) + for i := idx; i < len(leafHeights); i++ { + total = new(big.Int).Mul(total, new(big.Int).SetUint64(uint64(leafHeights[i]))) + } + increase := new(big.Int).Mul(total, new(big.Int).SetUint64(uint64(height))) + opcodeIndex = new(big.Int).Add(opcodeIndex, increase) + idx += 1 + } + if !opcodeIndex.IsUint64() { + return 0, fmt.Errorf("computed machine start index overflows uint64: %s", opcodeIndex.String()) + } + return OpcodeIndex(opcodeIndex.Uint64()), nil +} + +// Computes the number of individual opcodes we need to step through a machine +// at a time. +// Each challenge level has a different amount of ranges of opcodes, so the +// overall step size can be computed as a multiplication of all the next +// challenge levels needed. +// +// As an example, this function helps answer questions such as: "How many +// individual opcodes are there in a single step of a Megastep challenge?" +func (p *HistoryCommitmentProvider) computeStepSize(challengeLevel uint64) (StepSize, error) { + // The last challenge level is over individual opcodes, so the step size is + // always 1 opcode at a time. + if challengeLevel+1 == p.numberOfChallengeLevels() { + return 1, nil + } + // Otherwise, it is the multiplication of all the challenge leaf heights at + // the next challenge levels. + levels := p.challengeLeafHeights[challengeLevel+1:] + total := uint64(1) + for _, h := range levels { + total *= uint64(h) + } + return StepSize(total), nil +} + +func (p *HistoryCommitmentProvider) validateOriginHeights( + upperChallengeOriginHeights []Height, +) (validatedStartHeights, error) { + // Length cannot be greater than the total number of challenge levels in + // the protocol - 1. + if len(upperChallengeOriginHeights) > len(p.challengeLeafHeights)-1 { + return nil, fmt.Errorf( + "challenge level %d is out of range for challenge leaf heights %v", + len(upperChallengeOriginHeights), + p.challengeLeafHeights, + ) + } + return upperChallengeOriginHeights, nil +} + +// A caller specifies a request for a history commitment at challenge level N. +// It specifies a list of heights at which to compute the history commitment at +// each challenge level on the way to level N as a list of heights, where each +// position represents a challenge level. +// The length of this list cannot be greater than the total number of challenge +// levels in the protocol. +// Takes in an input type that has already been validated for correctness. +func deepestRequestedChallengeLevel(requestedHeights validatedStartHeights) uint64 { + return uint64(len(requestedHeights)) +} + +// Gets the required leaf height at a specified challenge level. This is a +// protocol constant. +func (p *HistoryCommitmentProvider) leafHeightAtChallengeLevel(challengeLevel uint64) (Height, error) { + if challengeLevel >= uint64(len(p.challengeLeafHeights)) { + return 0, fmt.Errorf( + "challenge level %d is out of range for challenge leaf heights %v", + challengeLevel, + p.challengeLeafHeights, + ) + } + return p.challengeLeafHeights[challengeLevel], nil +} + +// The total number of challenge levels in the protocol. +func (p *HistoryCommitmentProvider) numberOfChallengeLevels() uint64 { + return uint64(len(p.challengeLeafHeights)) +} diff --git a/bold/layer2-state-provider/history_commitment_provider_test.go b/bold/layer2-state-provider/history_commitment_provider_test.go new file mode 100644 index 0000000000..1c0887dad0 --- /dev/null +++ b/bold/layer2-state-provider/history_commitment_provider_test.go @@ -0,0 +1,212 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package l2stateprovider + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/offchainlabs/bold/containers/option" +) + +func Test_computeRequiredNumberOfHashes(t *testing.T) { + provider := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{ + 4, + 8, + 16, + }, + } + + challengeLevel := uint64(0) + _, err := provider.computeRequiredNumberOfHashes( + challengeLevel, + option.Some(Height(5)), + ) + require.ErrorContains(t, err, "end 5 was greater than max height for level 4") + + got, err := provider.computeRequiredNumberOfHashes( + challengeLevel, + option.Some(Height(4)), + ) + require.NoError(t, err) + require.Equal(t, uint64(5), got) + + challengeLevel = uint64(1) + got, err = provider.computeRequiredNumberOfHashes( + challengeLevel, + option.Some(Height(4)), + ) + require.NoError(t, err) + require.Equal(t, uint64(5), got) + + got, err = provider.computeRequiredNumberOfHashes( + challengeLevel, + option.None[Height](), + ) + require.NoError(t, err) + require.Equal(t, uint64(9), got) + + challengeLevel = uint64(2) + got, err = provider.computeRequiredNumberOfHashes( + challengeLevel, + option.None[Height](), + ) + require.NoError(t, err) + require.Equal(t, uint64(17), got) + + challengeLevel = uint64(1) + got, err = provider.computeRequiredNumberOfHashes( + challengeLevel, + option.Some(Height(8)), + ) + require.NoError(t, err) + require.Equal(t, uint64(9), got) +} + +func Test_computeMachineStartIndex(t *testing.T) { + t.Run("block challenge level", func(t *testing.T) { + provider := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{ + 32, + 1 << 10, + 1 << 10, + }, + } + machineStartIdx, err := provider.computeMachineStartIndex(validatedStartHeights{}, 1) + require.NoError(t, err) + require.Equal(t, OpcodeIndex(0), machineStartIdx) + }) + t.Run("three subchallenge levels", func(t *testing.T) { + provider := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{ + 32, // block challenge level = 0 + 32, // challenge level = 1 + 32, // challenge level = 2 + 32, // challenge level = 3 + }, + } + heights := []Height{ + 0, + 3, + 4, + } + // The first height is ignored, as it is for block challenges and not over machine opcodes. + // + // 3 * (32 * 32) + // + 4 * (32) + // + 5 + // = 3205 + got, err := provider.computeMachineStartIndex(validatedStartHeights(heights), 5) + require.NoError(t, err) + require.Equal(t, OpcodeIndex(3205), got) + }) + t.Run("four challenge levels", func(t *testing.T) { + // Take, for example, that we have 4 challenge kinds: + // + // block_challenge => over a range of L2 message hashes + // megastep_challenge => over ranges of 1048576 (2^20) opcodes at a time. + // kilostep_challenge => over ranges of 1024 (2^10) opcodes at a time + // step_challenge => over a range of individual WASM opcodes + // + // We only directly step through WASM machines when in a subchallenge (starting at megastep), + // so we can ignore block challenges for this calculation. + // + // Let's say we want to figure out the machine start opcode index for the following inputs: + // + // megastep=4, kilostep=5, step=10 + // + // We can compute the opcode index using the following algorithm for the example above. + // + // 4 * (1048576) + // + 5 * (1024) + // + 10 + // = 4,199,434 + provider := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{ + 32, // Block challenge level. + 1 << 10, + 1 << 10, + 1 << 10, + }, + } + heights := []Height{ + 0, + 4, + 5, + } + got, err := provider.computeMachineStartIndex(validatedStartHeights(heights), 10) + require.NoError(t, err) + require.Equal(t, OpcodeIndex(4199434), got) + }) +} + +func Test_computeStepSize(t *testing.T) { + provider := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{ + 1, + 2, + 4, + 8, + }, + } + t.Run("small step size", func(t *testing.T) { + challengeLevel := uint64(3) + stepSize, err := provider.computeStepSize(challengeLevel) + require.NoError(t, err) + // The step size for the last challenge level is always 1 opcode at a time. + require.Equal(t, StepSize(1), stepSize) + }) + t.Run("product of height constants for next challenge levels", func(t *testing.T) { + challengeLevel := uint64(0) + stepSize, err := provider.computeStepSize(challengeLevel) + require.NoError(t, err) + // Product of height constants for challenge levels 1, 2, 3. + require.Equal(t, StepSize(2*4*8), stepSize) + }) + +} + +func TestValidateOriginHeights(t *testing.T) { + p := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{1, 2, 3, 4, 5}, + } + + // Test case: valid upperChallengeOriginHeights + _, err := p.validateOriginHeights([]Height{1, 2}) + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + + // Test case: too many upperChallengeOriginHeights + _, err = p.validateOriginHeights([]Height{1, 2, 3, 4, 5, 6}) + if err == nil { + t.Errorf("Expected an error but got none") + } else if fmt.Sprintf("%v", err) != "challenge level 6 is out of range for challenge leaf heights [1 2 3 4 5]" { + t.Errorf("Unexpected error: %v", err) + } +} + +func TestLeafHeightAtChallengeLevel(t *testing.T) { + p := &HistoryCommitmentProvider{ + challengeLeafHeights: []Height{1, 2, 3, 4, 5}, + } + + // Test case: valid challengeLevel + height, err := p.leafHeightAtChallengeLevel(2) + if err != nil || height != 3 { + t.Errorf("Expected height 3, got %d with error %v", height, err) + } + + // Test case: invalid challengeLevel + _, err = p.leafHeightAtChallengeLevel(10) + if err == nil { + t.Errorf("Expected an error but got none") + } else if fmt.Sprintf("%v", err) != "challenge level 10 is out of range for challenge leaf heights [1 2 3 4 5]" { + t.Errorf("Unexpected error: %v", err) + } +} diff --git a/bold/layer2-state-provider/provider.go b/bold/layer2-state-provider/provider.go new file mode 100644 index 0000000000..f3d9a0f650 --- /dev/null +++ b/bold/layer2-state-provider/provider.go @@ -0,0 +1,148 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package l2stateprovider defines a dependency which provides L2 states and +// proofs needed for the challenge manager to interact with an Arbitrum chain's +// rollup and challenge contracts. +package l2stateprovider + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + "github.com/offchainlabs/bold/state-commitments/history" +) + +var ErrChainCatchingUp = errors.New("chain is catching up to the execution state") + +// Batch index for an Arbitrum L2 state. +type Batch uint64 + +// Height for a BoLD history commitment. +type Height uint64 + +// OpcodeIndex within an Arbitrator machine for an L2 message. +type OpcodeIndex uint64 + +// StepSize is the number of opcode increments used for stepping through +// machines for BoLD challenges. +type StepSize uint64 + +// ConfigSnapshot for an assertion on Arbitrum. +type ConfigSnapshot struct { + RequiredStake *big.Int + ChallengeManagerAddress common.Address + ConfirmPeriodBlocks uint64 + WasmModuleRoot [32]byte + InboxMaxCount *big.Int +} + +type History struct { + Height uint64 + MerkleRoot common.Hash +} + +// Provider defines an L2 state backend that can provide history commitments, +// execution states, prefix proofs, and more for the BoLD protocol. +type Provider interface { + ExecutionProvider + GeneralHistoryCommitter + GeneralPrefixProver + OneStepProofProvider + HistoryChecker +} + +type ExecutionProvider interface { + // Produces the L2 execution state to assert to after the previous assertion + // state. + // Returns either the state at the batch count maxInboxCount (PosInBatch=0) or + // the state LayerZeroHeights.BlockChallengeHeight blokcs after + // previousGlobalState, whichever is an earlier state. + ExecutionStateAfterPreviousState(ctx context.Context, maxInboxCount uint64, previousGlobalState protocol.GoGlobalState) (*protocol.ExecutionState, error) +} + +// AssociatedAssertionMetadata for the tracked edge. +type AssociatedAssertionMetadata struct { + FromState protocol.GoGlobalState + // This assertion may not read this batch. + // Unless it hits the block limit, its last state in position 0 of this batch. + BatchLimit Batch + WasmModuleRoot common.Hash + ClaimedAssertionHash protocol.AssertionHash +} + +// HistoryCommitmentRequest for a BoLD history commitment. +// +// The request specifies the metadata for the assertion which is being +// challenged in the block level challenge, and the heights at which the +// challenges at higher challenge levels originated. +// +// HistoryCommitment requestors can also specify an optional height at which to +// end the history commitment. If none, the request will commit to all the +// leaves at the current challenge level. +// +// NOTE: It is NOT possible to request a history commitment which starts at +// some height other than 0 for the current challenge level. This is because +// the edge tracker only needs to be able to provide history commitments for +// all machine state hases at the current challenge level, or sets of leaves +// which are prefixes to that full set of leaves. In all cases, the first leaf +// is the one in relative position 0 for the challenge level. +type HistoryCommitmentRequest struct { + // Miscellaneous metadata for assertion the commitment is being made for. + // Includes the WasmModuleRoot and the start and end states. + AssertionMetadata *AssociatedAssertionMetadata + // A slice of heights that tells the backend where the subchallenges for the + // requested history commitment originated from. + // Each index corresponds to a challenge level. For example, + // if we have three levels, where lvl 0 is the block challenge level, an + // input of []Height{12, 3} tells us that that the top-level subchallenge + // originated at height 12 then the next subchallenge originated at height + // 3 below that. + UpperChallengeOriginHeights []Height + // An optional height at which to end the history commitment. If none, the + // request will commit to all the leaves at the specified challenge level. + UpToHeight option.Option[Height] +} + +type GeneralHistoryCommitter interface { + // Request a history commitment for the machine state hashes at the current + // challenge level. See the HistoryCommitmentRequest struct for details. + HistoryCommitment( + ctx context.Context, + req *HistoryCommitmentRequest, + ) (history.History, error) + UpdateAPIDatabase(db.Database) +} + +type GeneralPrefixProver interface { + PrefixProof( + ctx context.Context, + req *HistoryCommitmentRequest, + prefixHeight Height, + ) ([]byte, error) +} + +type OneStepProofProvider interface { + OneStepProofData( + ctx context.Context, + assertionMetadata *AssociatedAssertionMetadata, + upperChallengeOriginHeights []Height, + upToHeight Height, + ) (data *protocol.OneStepData, startLeafInclusionProof, endLeafInclusionProof []common.Hash, err error) +} + +type HistoryChecker interface { + AgreesWithHistoryCommitment( + ctx context.Context, + challengeLevel protocol.ChallengeLevel, + historyCommitMetadata *HistoryCommitmentRequest, + commit History, + ) (bool, error) +} diff --git a/bold/logs/ephemeral/log.go b/bold/logs/ephemeral/log.go new file mode 100644 index 0000000000..5d3497f678 --- /dev/null +++ b/bold/logs/ephemeral/log.go @@ -0,0 +1,71 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package ephemeral + +import ( + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +// EphemeralErrorHandler handles errors that are ephemeral in nature i.e. these are errors +// that we would like to log as a warning unless they repeat for more than a certain duration of time. +type EphemeralErrorHandler struct { + Duration time.Duration + ErrorString string + FirstOccurrence *time.Time + + IgnoreDuration time.Duration + IgnoredErrLogLevel func(string, ...interface{}) // Default IgnoredErrLogLevel is log.Debug +} + +func NewEphemeralErrorHandler(duration time.Duration, errorString string, ignoreDuration time.Duration) *EphemeralErrorHandler { + return &EphemeralErrorHandler{ + Duration: duration, + ErrorString: errorString, + FirstOccurrence: &time.Time{}, + IgnoreDuration: ignoreDuration, + IgnoredErrLogLevel: log.Debug, + } +} + +// LogLevel method defaults to returning the input currentLogLevel if the given error doesnt contain the errorSubstring, +// but if it does, then returns one of the corresponding loglevels as follows +// - IgnoredErrLogLevel - if the error has been repeating for less than the IgnoreDuration of time. Defaults to log.Debug +// - log.Warn - if the error has been repeating for less than the given duration of time +// - log.Error - Otherwise +// +// # Usage Examples +// +// ephemeralErrorHandler.Loglevel(err, log.Error)("msg") +// ephemeralErrorHandler.Loglevel(err, log.Error)("msg", "key1", val1, "key2", val2) +// ephemeralErrorHandler.Loglevel(err, log.Error)("msg", "key1", val1) +func (h *EphemeralErrorHandler) LogLevel(err error, currentLogLevel func(msg string, ctx ...interface{})) func(string, ...interface{}) { + if h.ErrorString != "" && !strings.Contains(err.Error(), h.ErrorString) { + h.Reset() + return currentLogLevel + } + + if h.FirstOccurrence.Equal((time.Time{})) { + *h.FirstOccurrence = time.Now() + } + + if h.IgnoreDuration != 0 && time.Since(*h.FirstOccurrence) < h.IgnoreDuration { + if h.IgnoredErrLogLevel != nil { + return h.IgnoredErrLogLevel + } + return log.Debug + } + + if time.Since(*h.FirstOccurrence) < h.Duration { + return log.Warn + } + return log.Error +} + +func (h *EphemeralErrorHandler) Reset() { + *h.FirstOccurrence = time.Time{} +} diff --git a/bold/logs/ephemeral/log_test.go b/bold/logs/ephemeral/log_test.go new file mode 100644 index 0000000000..bcce5548e8 --- /dev/null +++ b/bold/logs/ephemeral/log_test.go @@ -0,0 +1,74 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package ephemeral + +import ( + "errors" + "reflect" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +func compareFunctions(f1, f2 func(msg string, ctx ...interface{})) bool { + return reflect.ValueOf(f1).Pointer() == reflect.ValueOf(f2).Pointer() +} +func TestSimple(t *testing.T) { + allErrHandler := NewEphemeralErrorHandler(2500*time.Millisecond, "", time.Second) + err := errors.New("sample error") + logLevel := allErrHandler.LogLevel(err, log.Error) + if !compareFunctions(log.Debug, logLevel) { + t.Fatalf("incorrect loglevel output. Want: Debug") + } + + time.Sleep(1 * time.Second) + logLevel = allErrHandler.LogLevel(err, log.Error) + if !compareFunctions(log.Warn, logLevel) { + t.Fatalf("incorrect loglevel output. Want: Warn") + } + + time.Sleep(2 * time.Second) + logLevel = allErrHandler.LogLevel(err, log.Error) + if !compareFunctions(log.Error, logLevel) { + t.Fatalf("incorrect loglevel output. Want: Error") + } +} + +func TestComplex(t *testing.T) { + // Simulation: errorA happens continuously for 2 seconds and then errorB happens + errorAHandler := NewEphemeralErrorHandler(time.Second, "errorA", 0) + errorBHandler := NewEphemeralErrorHandler(1500*time.Millisecond, "errorB", 0) + + // Computes result of chaining two ephemeral error handlers for a given recurring error + chainingErrHandlers := func(err error) func(string, ...interface{}) { + logLevel := log.Error + logLevel = errorAHandler.LogLevel(err, logLevel) + logLevel = errorBHandler.LogLevel(err, logLevel) + return logLevel + } + + errA := errors.New("this is a sample errorA") + if !compareFunctions(log.Warn, chainingErrHandlers(errA)) { + t.Fatalf("incorrect loglevel output. Want: Warn") + } + time.Sleep(2 * time.Second) + if !compareFunctions(log.Error, chainingErrHandlers(errA)) { + t.Fatalf("incorrect loglevel output. Want: Error") + } + + errB := errors.New("this is a sample errorB") + if !compareFunctions(log.Warn, chainingErrHandlers(errB)) { + t.Fatalf("incorrect loglevel output. Want: Warn") + } + if !compareFunctions(log.Warn, chainingErrHandlers(errA)) { + t.Fatalf("incorrect loglevel output. Want: Warn") + } + + errC := errors.New("random error") + if !compareFunctions(log.Error, chainingErrHandlers(errC)) { + t.Fatalf("incorrect loglevel output. Want: Error") + } +} diff --git a/bold/math/intlog2.go b/bold/math/intlog2.go new file mode 100644 index 0000000000..4c0e2d42b5 --- /dev/null +++ b/bold/math/intlog2.go @@ -0,0 +1,28 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package math + +import "math/bits" + +// Log2Floor returns the integer logarithm base 2 of u (rounded down). +func Log2Floor(u uint64) int { + if u == 0 { + panic("log2 undefined for non-positive values") + } + return bits.Len64(u) - 1 +} + +// Log2Ceil returns the integer logarithm base 2 of u (rounded up). +func Log2Ceil(u uint64) int { + r := Log2Floor(u) + if isPowerOfTwo(u) { + return r + } + return r + 1 +} + +func isPowerOfTwo(u uint64) bool { + return u&(u-1) == 0 +} diff --git a/bold/math/intlog2_test.go b/bold/math/intlog2_test.go new file mode 100644 index 0000000000..cea6a6a9ad --- /dev/null +++ b/bold/math/intlog2_test.go @@ -0,0 +1,141 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package math + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/offchainlabs/bold/testing/casttest" +) + +var benchResult int + +func TestUnsingedIntegerLog2Floor(t *testing.T) { + type log2TestCase struct { + input uint64 + expected int + } + + testCases := []log2TestCase{ + {input: 1, expected: 0}, + {input: 2, expected: 1}, + {input: 4, expected: 2}, + {input: 6, expected: 2}, + {input: 8, expected: 3}, + {input: 24601, expected: 14}, + } + for _, tc := range testCases { + t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { + res := Log2Floor(tc.input) + require.Equal(t, tc.expected, res) + }) + } +} + +func TestUnsingedIntegerLog2FloorPanicsOnZero(t *testing.T) { + require.Panics(t, func() { + Log2Floor(0) + }) +} + +func FuzzUnsingedIntegerLog2Floor(f *testing.F) { + testcases := []uint64{0, 2, 4, 6, 8} + for _, tc := range testcases { + f.Add(tc) + } + f.Fuzz(func(t *testing.T, input uint64) { + if input == 0 { + require.Panics(t, func() { + Log2Floor(input) + }) + t.Skip() + } + r := Log2Floor(input) + fr := math.Log2(float64(input)) + require.Equal(t, int(math.Floor(fr)), r) + }) +} + +func BenchmarkUnsingedIntegerLog2Floor(b *testing.B) { + var r int + for i := 1; i < b.N; i++ { + r = Log2Floor(casttest.ToUint64(b, i)) + } + benchResult = r +} + +func BenchmarkMathLog2Floor(b *testing.B) { + var r int + for i := 1; i < b.N; i++ { + r = int(math.Log2(float64(i))) + } + benchResult = r +} + +func TestUnsingedIntegerLog2Ceil(t *testing.T) { + type log2TestCase struct { + input uint64 + expected int + } + + testCases := []log2TestCase{ + {input: 1, expected: 0}, + {input: 2, expected: 1}, + {input: 4, expected: 2}, + {input: 6, expected: 3}, + {input: 8, expected: 3}, + {input: 24601, expected: 15}, + } + for _, tc := range testCases { + t.Run(fmt.Sprintf("%d", tc.input), func(t *testing.T) { + res := Log2Ceil(tc.input) + require.Equal(t, tc.expected, res) + }) + } +} + +func TestUnsingedIntegerLog2CeilPanicsOnZero(t *testing.T) { + require.Panics(t, func() { + Log2Ceil(0) + }) +} + +func FuzzUnsingedIntegerLog2Ceil(f *testing.F) { + testcases := []uint64{0, 2, 4, 6, 8} + for _, tc := range testcases { + f.Add(tc) + } + f.Fuzz(func(t *testing.T, input uint64) { + if input == 0 { + require.Panics(t, func() { + Log2Ceil(input) + }) + t.Skip() + } + r := Log2Ceil(input) + fr := math.Log2(float64(input)) + require.Equal(t, int(math.Ceil(fr)), r) + }) +} + +func BenchmarkUnsingedIntegerLog2Ceil(b *testing.B) { + var r int + for i := 1; i < b.N; i++ { + r = Log2Ceil(casttest.ToUint64(b, i)) + } + benchResult = r +} + +func BenchmarkMathLog2Ceil(b *testing.B) { + var r int + for i := 1; i < b.N; i++ { + r = int(math.Ceil(math.Log2(float64(i)))) + } + benchResult = r +} diff --git a/bold/math/math.go b/bold/math/math.go new file mode 100644 index 0000000000..feafae8eff --- /dev/null +++ b/bold/math/math.go @@ -0,0 +1,32 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package math defines utilities for performing operations critical to the +// computations performed during a challenge in BOLD. +package math + +import ( + "errors" + "math" + "math/bits" +) + +var ErrUnableToBisect = errors.New("unable to bisect") + +// Unsigned is a generic constraint for all unsigned numeric primitives. +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} + +func Bisect(pre, post uint64) (uint64, error) { + if pre+2 > post { + return 0, ErrUnableToBisect + } + if pre+2 == post { + return pre + 1, nil + } + matchingBits := bits.LeadingZeros64((post - 1) ^ pre) + mask := uint64(math.MaxUint64) << (63 - matchingBits) + return (post - 1) & mask, nil +} diff --git a/bold/math/math_test.go b/bold/math/math_test.go new file mode 100644 index 0000000000..05fc3325c7 --- /dev/null +++ b/bold/math/math_test.go @@ -0,0 +1,46 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package math + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestBisectionPoint(t *testing.T) { + type bpTestCase struct { + pre uint64 + post uint64 + expected uint64 + } + + errorTestCases := []bpTestCase{ + {12, 13, 0}, + {13, 9, 0}, + } + for _, testCase := range errorTestCases { + _, err := Bisect(testCase.pre, testCase.post) + require.ErrorIs(t, err, ErrUnableToBisect, testCase) + } + testCases := []bpTestCase{ + {0, 2, 1}, + {1, 3, 2}, + {31, 33, 32}, + {32, 34, 33}, + {13, 15, 14}, + {0, 9, 8}, + {0, 13, 8}, + {0, 15, 8}, + {13, 17, 16}, + {13, 31, 16}, + {15, 31, 16}, + } + for _, testCase := range testCases { + res, err := Bisect(testCase.pre, testCase.post) + require.NoError(t, err, testCase) + require.Equal(t, testCase.expected, res) + } +} diff --git a/bold/nogo.json b/bold/nogo.json new file mode 100644 index 0000000000..99b28a7917 --- /dev/null +++ b/bold/nogo.json @@ -0,0 +1,161 @@ +{ + "asmdecl": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "assign": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "atomicassign": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "bools": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "buildssa": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "buildtag": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "composites": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "ctrlflow": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "copylocks": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "deepequalerrors": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "errorsas": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "httpresponse": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "ifaceassert": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "loopclosure": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "lostcancel": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "nilfunc": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "nilness": { + "exclude_files": { + "external/.*": "Third party code", + "cgo/.*": "Third party code" + } + }, + "pkgfact": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "printf": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "shadow": { + "exclude_files": { + "external/.*": "Third party code", + "cgo/.*": "Third party code", + "validator/.*test\\.go": "disabling for tests", + "protocol/.*test\\.go": "disabling for tests" + } + }, + "shift": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "sortslice": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "stdmethods": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "stringintconv": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "structtag": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "testinggoroutine": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "tests": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "unmarshal": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "unreachable": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "unsafeptr": { + "exclude_files": { + "external/.*": "Third party code" + } + }, + "unusedresult": { + "exclude_files": { + "external/.*": "Third party code" + } + } +} diff --git a/bold/runtime/retry.go b/bold/runtime/retry.go new file mode 100644 index 0000000000..8b1d726524 --- /dev/null +++ b/bold/runtime/retry.go @@ -0,0 +1,95 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package runtime defines utilities that deal with managing lifecycles of +// functions and important behaviors at the application runtime, such as +// retrying errored functions until they succeed. +package retry + +import ( + "context" + "strings" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + + "github.com/offchainlabs/bold/logs/ephemeral" +) + +const defaultSleepTime = time.Second * 30 + +var ( + retryCounter = metrics.NewRegisteredCounter("arb/validator/runtime/retry", nil) +) + +type RetryConfig struct { + sleepTime time.Duration + LevelWarningError string // can be extended to a list or regex if demanded in future, currently supporting for one error +} + +type Opt func(*RetryConfig) + +// WithInterval specifies how often to retry an errored function. +func WithInterval(d time.Duration) Opt { + return func(rc *RetryConfig) { + rc.sleepTime = d + } +} + +func UntilSucceedsMultipleReturnValue[T, U any](ctx context.Context, fn func() (T, U, error), opts ...Opt) (T, U, error) { + cfg := &RetryConfig{ + sleepTime: defaultSleepTime, + } + for _, o := range opts { + o(cfg) + } + count := 0 + // Retry until succeeds is usually used for cases where its believed that retrying a function will most likely succeed + // or the function has a some chance of failing even if it's not expected to fail, based on this assumption, + // we use a commonEphemeralErrorHandler to log the errors at warn level for the first 10 minutes + // and only after that we log the errors at error level. + commonEphemeralErrorHandler := ephemeral.NewEphemeralErrorHandler(time.Minute*10, "", 0) + for { + if ctx.Err() != nil { + return zeroVal[T](), zeroVal[U](), ctx.Err() + } + got, got2, err := fn() + if err != nil { + count++ + logLevel := log.Error + logLevel = commonEphemeralErrorHandler.LogLevel(err, logLevel) + if cfg.LevelWarningError != "" && strings.Contains(err.Error(), cfg.LevelWarningError) { + logLevel = log.Warn + } + logLevel("Could not succeed function after retries", + "retryCount", count, + "err", err, + ) + retryCounter.Inc(1) + select { + case <-ctx.Done(): + return zeroVal[T](), zeroVal[U](), ctx.Err() + case <-time.After(cfg.sleepTime): + } + continue + } + commonEphemeralErrorHandler.Reset() + return got, got2, nil + } +} + +// UntilSucceeds retries the given function until it succeeds or the context is cancelled. +func UntilSucceeds[T any](ctx context.Context, fn func() (T, error), opts ...Opt) (T, error) { + result, _, err := UntilSucceedsMultipleReturnValue(ctx, func() (T, struct{}, error) { + got, err := fn() + return got, struct{}{}, err + }, opts...) + return result, err +} + +func zeroVal[T any]() T { + var result T + return result +} diff --git a/bold/runtime/retry_test.go b/bold/runtime/retry_test.go new file mode 100644 index 0000000000..19f03b65a7 --- /dev/null +++ b/bold/runtime/retry_test.go @@ -0,0 +1,28 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package retry + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRetryUntilSucceeds(t *testing.T) { + hello := func() (string, error) { + return "hello", nil + } + + ctx := context.Background() + got, err := UntilSucceeds(ctx, hello) + require.NoError(t, err) + require.Equal(t, "hello", got) + + newCtx, cancel := context.WithCancel(ctx) + cancel() + _, err = UntilSucceeds(newCtx, hello) + require.ErrorContains(t, err, "context canceled") +} diff --git a/bold/state-commitments/history/history_commitment.go b/bold/state-commitments/history/history_commitment.go new file mode 100644 index 0000000000..0df1ba8fde --- /dev/null +++ b/bold/state-commitments/history/history_commitment.go @@ -0,0 +1,635 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package history provides functions for computing merkle tree roots +// and proofs needed for the BoLD protocol's history commitments. +// +// Throughout this package, the following terms are used: +// +// - leaf: a leaf node in a merkle tree, which is a hash of some data. +// - virtual: the length of the desired number of leaf nodes. In the BoLD +// protocol, it is important that all history commitments which for a given +// challenge edge have the same length, even if the participants disagree +// about the number of blocks or steps to which they are committing. To +// solve this, history commitments must have fixed lengths at different +// challenge levels. Callers only need to provide the leaves they to which +// they commit, and the virtual length. The last leaf in the list is used +// to pad the tree to the virtual length. +// - limit: the length of the leaves that would be in a complete subtree +// of the depth required to hold the virtual leaves in a tree (or subtree) +// - pure tree: a tree where len(leaves) == virtual +// - complete tree: a tree where the number of leaves is a power of 2 +// - complete virtual tree: a tree where the number of leaves including the +// virtual padding is a power of 2 +// - partial tree: a tree where the number of leaves is not a power of 2 +// - partial virtual tree: a tree where the number of leaves including the +// virtual padding is not a power of 2 +// - empty hash: common.Hash{} +// Any time the root of a partial tree (either virtual or pure) is computed, +// the sibling node of the last node in a layer may be missing. In this case +// an empty hash (common.Hash{}) is used as the sibling node. +// Note: This is not the same as padding the leaves of the tree with +// common.Hash{} values. If that approach were taken, then the higher-level +// layers would contain the hash of the empty hash, or the hash of multiple +// empty hashes. This would be less efficient to calculate, and would not +// change expressiveness or security of the data structure, but it would +// produce a different root hash. +// - virtual node: a node in a virtual tree which is not one of the real +// leaves and not computed from the data in the real leaves. +package history + +import ( + "errors" + "fmt" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/bold/math" +) + +var ( + emptyHash = common.Hash{} + emptyHistory = History{} +) + +// History represents a history commitment in the protocol. +type History struct { + Height uint64 + Merkle common.Hash + FirstLeaf common.Hash + LastLeaf common.Hash + LastLeafProof []common.Hash +} + +// treePosition tracks the current position in the merkle tree. +type treePosition struct { + // layer is the layer of the tree. + layer uint64 + // index is the index of the leaf in this layer of the tree. + index uint64 +} + +type historyCommitter struct { + fillers []common.Hash + keccak crypto.KeccakState + cursor treePosition + lastLeafProver *lastLeafProver +} + +func newCommitter() *historyCommitter { + return &historyCommitter{ + fillers: make([]common.Hash, 0), + keccak: crypto.NewKeccakState(), + } +} + +// soughtHash holds a pointer to the hash and whether it has been found. +// +// Without this type, it would be impossible to distinguish between a hash which +// has not been found and a hash which is the value of common.Hash{}. +// That's because the lastLeafProver's postions map is initialized with pointers +// to common.Hash{} values in a pre-allocated slice. +type soughtHash struct { + found bool + hash *common.Hash +} + +// lastLeafProver finds the siblings needed to produce a merkle inclusion +// proof for the last leaf in a virtual merkle tree. +// +// The prover maintains a map of treePositions where sibling nodes live +// and fills them in as the historyCommitter calculates them. +type lastLeafProver struct { + positions map[treePosition]*soughtHash + proof []common.Hash +} + +func newLastLeafProver(virtual uint64) (*lastLeafProver, error) { + positions, err := lastLeafProofPositions(virtual) + if err != nil { + return nil, err + } + posMap := make(map[treePosition]*soughtHash, len(positions)) + proof := make([]common.Hash, len(positions)) + for i, pos := range positions { + posMap[pos] = &soughtHash{false, &proof[i]} + } + return &lastLeafProver{ + positions: posMap, + proof: proof, + }, nil +} + +// handle filters the hashes found while computing the merkle root looking for +// the sibling nodes needed to produce the merkle inclusion proof, and fills +// them in the proof slice. +func (p *lastLeafProver) handle(hash common.Hash, pos treePosition) { + if sibling, ok := p.positions[pos]; ok { + sibling.found = true + *sibling.hash = hash + } +} + +// handle is called each time a hash is computed in the merkle tree. +// +// The cursor is kept in sync with tree traversal. The implementation of +// handle can therefore assume that the cursor is pointing to the node which +// has the value of the hash. +func (h *historyCommitter) handle(hash common.Hash) { + if h.lastLeafProver != nil { + h.lastLeafProver.handle(hash, h.cursor) + } +} + +// hash hashes the passed item into a common.Hash. +func (h *historyCommitter) hash(item ...*common.Hash) common.Hash { + var result common.Hash + h.hashInto(&result, item...) + return result +} + +// proof returns the merkle inclusion proof for the last leaf in a virtual tree. +// +// If the proof is not complete (i.e. some sibling nodes are missing), the +// sibling nodes are filled in with the fillers. +// +// The reason this works, is that the only nodes which are not visited when +// computing the merkle root are those which are in some complete virtual +// subtree. +func (h *historyCommitter) lastLeafProof() []common.Hash { + for pos, sibling := range h.lastLeafProver.positions { + if !sibling.found { + *h.lastLeafProver.positions[pos].hash = h.fillers[pos.layer] + } + } + if len(h.lastLeafProver.proof) == 0 { + return nil + } + return h.lastLeafProver.proof +} + +// hashInto hashes the concatenation of the passed items into the result. +// nolint:errcheck +func (h *historyCommitter) hashInto(result *common.Hash, items ...*common.Hash) { + defer h.keccak.Reset() + for _, item := range items { + h.keccak.Write(item[:]) // #nosec G104 - KeccakState.Write never errors + } + h.keccak.Read(result[:]) // #nosec G104 - KeccakState.Read never errors +} + +// NewCommitment produces a history commitment from a list of real leaves that +// are virtually padded using the last leaf in the list to some virtual length. +// +// Virtual must be >= len(leaves). +func NewCommitment(leaves []common.Hash, virtual uint64) (History, error) { + if len(leaves) == 0 { + return emptyHistory, errors.New("must commit to at least one leaf") + } + if virtual < uint64(len(leaves)) { + return emptyHistory, errors.New("virtual size must be >= len(leaves)") + } + comm := newCommitter() + firstLeaf := leaves[0] + lastLeaf := leaves[len(leaves)-1] + prover, err := newLastLeafProver(virtual) + if err != nil { + return emptyHistory, err + } + comm.lastLeafProver = prover + root, err := comm.computeRoot(leaves, virtual) + if err != nil { + return emptyHistory, err + } + lastLeafProof := comm.lastLeafProof() + return History{ + // Height is the relative height of the history commitment. + // It's the index of the last leaf in the tree. + Height: virtual - 1, + Merkle: root, + FirstLeaf: firstLeaf, + LastLeaf: lastLeaf, + LastLeafProof: lastLeafProof, + }, nil +} + +// ComputeRoot computes the merkle root of a virtual merkle tree. +func ComputeRoot(leaves []common.Hash, virtual uint64) (common.Hash, error) { + comm := newCommitter() + return comm.computeRoot(leaves, virtual) +} + +// GeneratePrefixProof generates a prefix proof for a given prefix index. +func GeneratePrefixProof(prefixIndex uint64, leaves []common.Hash, virtual uint64) ([]common.Hash, []common.Hash, error) { + comm := newCommitter() + return comm.generatePrefixProof(prefixIndex, leaves, virtual) +} + +// computeRoot computes the merkle root of a virtual merkle tree. +func (h *historyCommitter) computeRoot(leaves []common.Hash, virtual uint64) (common.Hash, error) { + lvLen := uint64(len(leaves)) + if lvLen == 0 { + return emptyHash, nil + } + hashed := h.hashLeaves(leaves) + limit := nextPowerOf2(virtual) + depth, err := safecast.ToUint(math.Log2Floor(limit)) + if err != nil { + return emptyHash, err + } + n, err := safecast.ToUint(math.Log2Ceil(virtual)) + if err != nil { + return emptyHash, err + } + n = max(n, 1) + if err := h.populateFillers(&hashed[lvLen-1], n); err != nil { + return emptyHash, err + } + h.cursor = treePosition{layer: uint64(depth), index: 0} + return h.partialRoot(hashed, virtual, limit) +} + +// generatePrefixProof generates a prefix proof for a given prefix index. +// +// A prefix proof consists of the data needed to prove that a merkle root +// created from the leaves upto the prefix index represents a merkle tree which +// spans a specific prefix of the virtual merkle tree. +func (h *historyCommitter) generatePrefixProof(prefixIndex uint64, leaves []common.Hash, virtual uint64) ([]common.Hash, []common.Hash, error) { + hashed := h.hashLeaves(leaves) + prefixExpansion, proof, err := h.prefixAndProof(prefixIndex, hashed, virtual) + if err != nil { + return nil, nil, err + } + prefixExpansion = trimTrailingEmptyHashes(prefixExpansion) + proof = filterEmptyHashes(proof) + return prefixExpansion, proof, nil +} + +// hashLeaves returns a slice of hashes of the leaves +func (h *historyCommitter) hashLeaves(leaves []common.Hash) []common.Hash { + hashedLeaves := make([]common.Hash, len(leaves)) + for i := range leaves { + hashedLeaves[i] = h.hash(&leaves[i]) + } + return hashedLeaves +} + +// partialRoot returns the merkle root of a possibly partial hashtree where the +// first layer is passed as leaves, then padded by repeating the last leaf +// until it reaches virtual and terminated with a single common.Hash{}. +// +// limit is a power of 2 which is greater or equal to virtual, and defines how +// deep the complete tree analogous to this partial one would be. +// +// Implementation note: The historyCommitter's fillers member must be populated +// correctly before calling this method. There must be at least +// Log2FCeil(virtual) filler nodes to properly pad each layer of the tree if it +// is a partial virtual tree. +// +// The algorithm is split in three different logical cases: +// +// 1. If the virtual length is less than or equal to half the limit (this can +// never happen in the first iteration of the algorithm), the left half of +// the tree is computed by recursion and the right half is an empty hash. +// 2. If the leaves all fit in the left half, then both halves of the tree are +// computed by recursion. This is the most common starting scenario. +// There is a special case when the virtual length is equal to the limit, +// and the right half is a complete virtual tree. In this case, the right +// subtree is just a lookup in the precomputed fillers. +// 3. If the leaves do not fit in the left half, then both halves are computed +// by recursion. +func (h *historyCommitter) partialRoot(leaves []common.Hash, virtual, limit uint64) (common.Hash, error) { + lvLen := uint64(len(leaves)) + if lvLen == 0 { + return emptyHash, errors.New("nil leaves") + } + if uint64(virtual) < lvLen { + return emptyHash, fmt.Errorf("virtual %d should be >= num leaves %d", virtual, lvLen) + } + if limit < virtual { + return emptyHash, fmt.Errorf("limit %d should be >= virtual %d", limit, virtual) + } + minFillers := math.Log2Ceil(uint64(virtual)) + if len(h.fillers) < minFillers { + return emptyHash, fmt.Errorf("insufficient fillers, want %d, got %d", minFillers, len(h.fillers)) + } + if limit == 1 { + h.handle(leaves[0]) + return leaves[0], nil + } + + h.cursor.layer-- + var left, right common.Hash + var err error + mid := limit / 2 + + // Deal with the left child first + h.cursor.index *= 2 + var lLeaves []common.Hash + var lVirtual uint64 + if virtual > mid { + // Case 2 or 3: A complete subtree can be computed + lVirtual = mid + if lvLen > uint64(mid) { + // Case 3: A complete pure subtree can be computed + lLeaves = leaves[:mid] + } else { + // Case 2: A complete virtual subtree can be computed + lLeaves = leaves + } + } else { + // Case 1: A partial virtual tree can be computed + lLeaves = leaves + lVirtual = virtual + } + left, err = h.partialRoot(lLeaves, lVirtual, mid) + if err != nil { + return emptyHash, err + } + + // Deal with the right child + h.cursor.index++ + if virtual > mid { + // Case 2 or 3: The virtual size is greater than half the limit + if lvLen <= uint64(mid) && virtual == limit { + // This is a special case of 2 where the entire right subtree is + // made purely of virtual nodes, and it is a complete tree. + // So, the root of the subtree will be the precomputed filler + // at the current layer. + right = h.fillers[math.Log2Floor(uint64(mid))] + h.handle(right) + } else { + var rLeaves []common.Hash + if lvLen > uint64(mid) { + // Case 3: The leaves do not fit in the first half + rLeaves = leaves[mid:] + } else { + // Case 2: The leaves fit in the first half + rLeaves = []common.Hash{h.fillers[0]} + } + right, err = h.partialRoot(rLeaves, virtual-mid, mid) + if err != nil { + return emptyHash, err + } + } + } else { + // Case 1: The virtual size is less than half the limit + right = emptyHash + h.handle(right) + } + + h.hashInto(&leaves[0], &left, &right) + + // Restore the cursor layer to the state for this level of recursion + h.cursor.index /= 2 + h.cursor.layer++ + h.handle(leaves[0]) + + return leaves[0], nil +} + +func (h *historyCommitter) subtreeExpansion(leaves []common.Hash, virtual, limit uint64, stripped bool) (proof []common.Hash, err error) { + lvLen := uint64(len(leaves)) + if lvLen == 0 { + return make([]common.Hash, 0), nil + } + if virtual == 0 { + for i := limit; i > 1; i /= 2 { + proof = append(proof, emptyHash) + } + return + } + if limit == 0 { + limit = nextPowerOf2(virtual) + } + if limit == virtual { + left, err2 := h.partialRoot(leaves, limit, limit) + if err2 != nil { + return nil, err2 + } + if !stripped { + for i := limit; i > 1; i /= 2 { + proof = append(proof, emptyHash) + } + } + return append(proof, left), nil + } + mid := limit / 2 + if lvLen > mid { + left, err2 := h.partialRoot(leaves[:mid], mid, mid) + if err2 != nil { + return nil, err2 + } + proof, err = h.subtreeExpansion(leaves[mid:], virtual-mid, mid, stripped) + if err != nil { + return nil, err + } + return append(proof, left), nil + } + if virtual >= mid { + left, err2 := h.partialRoot(leaves, mid, mid) + if err2 != nil { + return nil, err2 + } + if len(h.fillers) == 0 { + return nil, errors.New("fillers is empty") + } + proof, err = h.subtreeExpansion([]common.Hash{h.fillers[0]}, virtual-mid, mid, stripped) + if err != nil { + return nil, err + } + return append(proof, left), nil + } + if stripped { + return h.subtreeExpansion(leaves, virtual, mid, stripped) + } + expac, err := h.subtreeExpansion(leaves, virtual, mid, stripped) + if err != nil { + return nil, err + } + return append(expac, emptyHash), nil +} + +func (h *historyCommitter) proof(index uint64, leaves []common.Hash, virtual, limit uint64) (tail []common.Hash, err error) { + lvLen := uint64(len(leaves)) + if lvLen == 0 { + return nil, errors.New("empty leaves slice") + } + if limit == 0 { + limit = nextPowerOf2(virtual) + } + if limit == 1 { + // Can only reach this with index == 0 + return + } + mid := limit / 2 + if index >= mid { + if lvLen > mid { + return h.proof(index-mid, leaves[mid:], virtual-mid, mid) + } + if len(h.fillers) == 0 { + return nil, errors.New("fillers is empty") + } + return h.proof(index-mid, []common.Hash{h.fillers[0]}, virtual-mid, mid) + } + if lvLen > mid { + tail, err = h.proof(index, leaves[:mid], mid, mid) + if err != nil { + return nil, err + } + right, err2 := h.subtreeExpansion(leaves[mid:], virtual-mid, mid, true) + if err2 != nil { + return nil, err2 + } + for i := len(right) - 1; i >= 0; i-- { + tail = append(tail, right[i]) + } + return tail, nil + } + if virtual > mid { + tail, err = h.proof(index, leaves, mid, mid) + if err != nil { + return nil, err + } + if len(h.fillers) == 0 { + return nil, errors.New("fillers is empty") + } + right, err := h.subtreeExpansion([]common.Hash{h.fillers[0]}, virtual-mid, mid, true) + if err != nil { + return nil, err + } + for i := len(right) - 1; i >= 0; i-- { + tail = append(tail, right[i]) + } + return tail, nil + } + return h.proof(index, leaves, virtual, mid) +} + +func (h *historyCommitter) prefixAndProof(index uint64, leaves []common.Hash, virtual uint64) (prefix []common.Hash, tail []common.Hash, err error) { + lvLen := uint64(len(leaves)) + if lvLen == 0 { + return nil, nil, errors.New("nil leaves") + } + if virtual == 0 { + return nil, nil, errors.New("virtual size cannot be zero") + } + if lvLen > virtual { + return nil, nil, fmt.Errorf("num leaves %d should be <= virtual %d", lvLen, virtual) + } + if index+1 > virtual { + return nil, nil, fmt.Errorf("index %d + 1 should be <= virtual %d", index, virtual) + } + logVirtual, err := safecast.ToUint(math.Log2Floor(virtual) + 1) + if err != nil { + return nil, nil, err + } + if err = h.populateFillers(&leaves[lvLen-1], logVirtual); err != nil { + return nil, nil, err + } + + if index+1 > lvLen { + prefix, err = h.subtreeExpansion(leaves, index+1, 0, false) + } else { + prefix, err = h.subtreeExpansion(leaves[:index+1], index+1, 0, false) + } + if err != nil { + return nil, nil, err + } + tail, err = h.proof(index, leaves, virtual, 0) + return +} + +// populateFillers returns a slice built recursively as +// ret[0] = the passed in leaf +// ret[i+1] = Hash(ret[i] + ret[i]) +// +// Allocates n hashes +// Computes n-1 hashes +// Copies 1 hash +func (h *historyCommitter) populateFillers(leaf *common.Hash, n uint) error { + if leaf == nil { + return errors.New("nil leaf pointer") + } + h.fillers = make([]common.Hash, n) + copy(h.fillers[0][:], (*leaf)[:]) + for i := uint(1); i < n; i++ { + h.hashInto(&h.fillers[i], &h.fillers[i-1], &h.fillers[i-1]) + } + return nil +} + +// lastLeafProofPositions returns the positions in a virtual merkle tree +// of the sibling nodes that need to be hashed with the last leaf at each +// layer to compute the root of the tree. +func lastLeafProofPositions(virtual uint64) ([]treePosition, error) { + if virtual == 0 { + return nil, errors.New("virtual size cannot be zero") + } + if virtual == 1 { + return []treePosition{}, nil + } + limit := nextPowerOf2(uint64(virtual)) + depth := math.Log2Floor(limit) + positions := make([]treePosition, depth) + idx := uint64(virtual) - 1 + for l := range positions { + lU64, err := safecast.ToUint64(l) + if err != nil { + return nil, err + } + positions[l] = sibling(idx, lU64) + idx = parent(idx) + } + return positions, nil +} + +// sibling returns the position of the sibling of the node at the given layer +func sibling(index, layer uint64) treePosition { + return treePosition{layer: layer, index: index ^ 1} +} + +// parent returns the index of the parent of the node in the next higher layer +func parent(index uint64) uint64 { + return index >> 1 +} + +func nextPowerOf2(n uint64) uint64 { + if n == 0 { + return 1 + } + n-- // Decrement n to handle the case where n is a power of 2 + n |= n >> 1 // Propagate the highest bit set + n |= n >> 2 + n |= n >> 4 + n |= n >> 8 + n |= n >> 16 + n |= n >> 32 + return n + 1 // Increment n to get the next power of 2 +} + +func trimTrailingEmptyHashes(hashes []common.Hash) []common.Hash { + // Start from the end of the slice + for i := len(hashes) - 1; i >= 0; i-- { + if hashes[i] != emptyHash { + return hashes[:i+1] + } + } + // If all elements are zero, return an empty slice + return []common.Hash{} +} + +func filterEmptyHashes(hashes []common.Hash) []common.Hash { + newHashes := make([]common.Hash, 0, len(hashes)) + for _, h := range hashes { + if h == emptyHash { + continue + } + newHashes = append(newHashes, h) + } + return newHashes +} diff --git a/bold/state-commitments/history/history_commitment_test.go b/bold/state-commitments/history/history_commitment_test.go new file mode 100644 index 0000000000..e7af4d9837 --- /dev/null +++ b/bold/state-commitments/history/history_commitment_test.go @@ -0,0 +1,376 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package history + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/offchainlabs/bold/state-commitments/legacy" + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" + "github.com/offchainlabs/bold/testing/casttest" +) + +func FuzzHistoryCommitter(f *testing.F) { + simpleHash := crypto.Keccak256Hash([]byte("foo")) + f.Fuzz(func(t *testing.T, numReal uint64, virtual uint64, limit uint64) { + // Set some bounds. + numReal = numReal % (1 << 10) + virtual = virtual % (1 << 20) + hashedLeaves := make([]common.Hash, numReal) + for i := range hashedLeaves { + hashedLeaves[i] = simpleHash + } + _, err := NewCommitment(hashedLeaves, virtual) + if err != nil { + if len(hashedLeaves) == 0 || virtual < uint64(len(hashedLeaves)) { + t.Skip() + } + t.Errorf("NewCommitment(%v, %d): err %v\n", hashedLeaves, virtual, err) + } + _ = err + }) +} + +func BenchmarkPrefixProofGeneration_Legacy(b *testing.B) { + for i := 0; i < b.N; i++ { + prefixIndex := 13384 + simpleHash := crypto.Keccak256Hash([]byte("foo")) + hashes := make([]common.Hash, 1<<14) + for i := 0; i < len(hashes); i++ { + hashes[i] = simpleHash + } + + lowCommitmentNumLeaves := prefixIndex + 1 + hiCommitmentNumLeaves := (1 << 14) + prefixExpansion, err := prefixproofs.ExpansionFromLeaves(hashes[:lowCommitmentNumLeaves]) + require.NoError(b, err) + _, err = prefixproofs.GeneratePrefixProof( + casttest.ToUint64(b, lowCommitmentNumLeaves), + prefixExpansion, + hashes[lowCommitmentNumLeaves:hiCommitmentNumLeaves], + prefixproofs.RootFetcherFromExpansion, + ) + require.NoError(b, err) + } +} + +func BenchmarkPrefixProofGeneration_Optimized(b *testing.B) { + b.StopTimer() + simpleHash := crypto.Keccak256Hash([]byte("foo")) + hashes := []common.Hash{crypto.Keccak256Hash(simpleHash[:])} + prefixIndex := uint64(13384) + virtual := uint64(1 << 14) + committer := newCommitter() + b.StartTimer() + for i := 0; i < b.N; i++ { + _, _, err := committer.generatePrefixProof(prefixIndex, hashes, virtual) + require.NoError(b, err) + } +} + +func TestSimpleHistoryCommitment(t *testing.T) { + aLeaf := common.HexToHash("0xA") + bLeaf := common.HexToHash("0xB") + // Level 0 + aHash := crypto.Keccak256Hash(aLeaf[:]) + bHash := crypto.Keccak256Hash(bLeaf[:]) + // Level 1 + abHash := crypto.Keccak256Hash(append(aHash[:], bHash[:]...)) + bzHash := crypto.Keccak256Hash(append(bHash[:], emptyHash[:]...)) + bbHash := crypto.Keccak256Hash(append(bHash[:], bHash[:]...)) + // Level 2 + abbzHash := crypto.Keccak256Hash(append(abHash[:], bzHash[:]...)) + abbbHash := crypto.Keccak256Hash(append(abHash[:], bbHash[:]...)) + ababHash := crypto.Keccak256Hash(append(abHash[:], abHash[:]...)) + bbbbHash := crypto.Keccak256Hash(append(bbHash[:], bbHash[:]...)) + // Level 3 + ababbbbbHash := crypto.Keccak256Hash(append(ababHash[:], bbbbHash[:]...)) + abababbbHash := crypto.Keccak256Hash(append(ababHash[:], abbbHash[:]...)) + + tests := []struct { + name string + lvs []common.Hash + virt uint64 + want common.Hash + }{ + { + name: "empty leaves", + lvs: []common.Hash{}, + virt: 0, + want: emptyHash, + }, + { + name: "single leaf", + lvs: []common.Hash{aLeaf}, + virt: 1, + want: aHash, + }, + { + name: "two leaves", + lvs: []common.Hash{aLeaf, bLeaf}, + virt: 2, + want: abHash, + }, + { + name: "two leaves - virtual 3", + lvs: []common.Hash{aLeaf, bLeaf}, + virt: 3, + want: abbzHash, + }, + { + name: "two leaves - virtual 4", + lvs: []common.Hash{aLeaf, bLeaf}, + virt: 4, + want: abbbHash, + }, + { + name: "four leaves", + lvs: []common.Hash{aLeaf, bLeaf, aLeaf, bLeaf}, + virt: 4, + want: ababHash, + }, + { + name: "four leaves - virtual 8", + lvs: []common.Hash{aLeaf, bLeaf, aLeaf, bLeaf}, + virt: 8, + want: ababbbbbHash, + }, + { + name: "six leaves - virtual 8", + lvs: []common.Hash{aLeaf, bLeaf, aLeaf, bLeaf, aLeaf, bLeaf}, + virt: 8, + want: abababbbHash, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + hc := newCommitter() + got, err := hc.computeRoot(tc.lvs, tc.virt) + if err != nil { + t.Errorf("ComputeRoot(%v, %d): err %v\n", tc.lvs, tc.virt, err) + } + if got != tc.want { + t.Errorf("ComputeRoot(%v, %d): got %s, want %s\n", tc.lvs, tc.virt, got.Hex(), tc.want.Hex()) + } + }) + } +} + +func TestLegacyVsOptimized(t *testing.T) { + t.Parallel() + end := uint64(1 << 6) + simpleHash := crypto.Keccak256Hash([]byte("foo")) + for i := uint64(1); i < end; i++ { + limit := nextPowerOf2(i) + for j := i; j < limit; j++ { + inputLeaves := make([]common.Hash, i) + for i := range inputLeaves { + inputLeaves[i] = simpleHash + } + committer := newCommitter() + computedRoot, err := committer.computeRoot(inputLeaves, uint64(j)) + require.NoError(t, err) + + legacyInputLeaves := make([]common.Hash, j) + for i := range legacyInputLeaves { + legacyInputLeaves[i] = simpleHash + } + histCommit, err := legacy.NewLegacy(legacyInputLeaves) + require.NoError(t, err) + require.Equal(t, computedRoot, histCommit.Merkle) + } + } +} + +func TestLegacyVsOptimizedEdgeCases(t *testing.T) { + t.Parallel() + simpleHash := crypto.Keccak256Hash([]byte("foo")) + + tests := []struct { + realLength int + virtualLength int + }{ + {12, 14}, + {8, 10}, + {6, 6}, + {10, 16}, + {4, 8}, + {1, 5}, + {3, 5}, + {5, 5}, + {1023, 1024}, + {(1 << 14) - 7, (1 << 14) - 7}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("real length %d, virtual %d", tt.realLength, tt.virtualLength), func(t *testing.T) { + inputLeaves := make([]common.Hash, tt.realLength) + for i := range inputLeaves { + inputLeaves[i] = simpleHash + } + committer := newCommitter() + computedRoot, err := committer.computeRoot(inputLeaves, casttest.ToUint64(t, tt.virtualLength)) + require.NoError(t, err) + + leaves := make([]common.Hash, tt.virtualLength) + for i := range leaves { + leaves[i] = simpleHash + } + histCommit, err := legacy.NewLegacy(leaves) + require.NoError(t, err) + require.Equal(t, computedRoot, histCommit.Merkle) + }) + } +} + +func TestVirtualSparse(t *testing.T) { + t.Parallel() + simpleHash := crypto.Keccak256Hash([]byte("foo")) + makeLeaves := func(n int) []common.Hash { + leaves := make([]common.Hash, n) + for i := range leaves { + leaves[i] = simpleHash + } + return leaves + } + tests := []struct { + name string + real []common.Hash + virt uint64 + full []common.Hash + }{ + { + name: "real 1, virtual 3", + real: makeLeaves(1), + virt: 3, + full: makeLeaves(3), + }, + { + name: "real 2, virtual 3", + real: makeLeaves(2), + virt: 3, + full: makeLeaves(3), + }, + { + name: "real 3, virtual 3", + real: makeLeaves(3), + virt: 3, + full: makeLeaves(3), + }, + { + name: "real 4, virtual 4", + real: makeLeaves(4), + virt: 4, + full: makeLeaves(4), + }, + { + name: "real 1, virtual 5", + real: makeLeaves(1), + virt: 5, + full: makeLeaves(5), + }, + { + name: "real 12, virtual 14", + real: makeLeaves(12), + virt: 14, + full: makeLeaves(14), + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + optCommit, err := NewCommitment(tc.real, tc.virt) + require.NoError(t, err) + + histCommit, err := legacy.NewLegacy(tc.full) + require.NoError(t, err) + require.Equal(t, histCommit.Merkle, optCommit.Merkle) + }) + } +} + +func TestMaximumDepthHistoryCommitment(t *testing.T) { + t.Parallel() + simpleHash := crypto.Keccak256Hash([]byte("foo")) + hashedLeaves := []common.Hash{ + simpleHash, + } + _, err := NewCommitment(hashedLeaves, 1<<26) + require.NoError(t, err) +} + +func BenchmarkMaximumDepthHistoryCommitment(b *testing.B) { + b.StopTimer() + simpleHash := crypto.Keccak256Hash([]byte("foo")) + hashedLeaves := []common.Hash{ + simpleHash, + } + b.StartTimer() + for i := 0; i < b.N; i++ { + _, err := ComputeRoot(hashedLeaves, 1<<26) + _ = err + } +} + +func TestInclusionProofEquivalence(t *testing.T) { + simpleHash := crypto.Keccak256Hash([]byte("foo")) + leaves := []common.Hash{ + simpleHash, + simpleHash, + simpleHash, + simpleHash, + } + commit, err := NewCommitment(leaves, 4) + require.NoError(t, err) + oldCommit, err := legacy.NewLegacy(leaves) + require.NoError(t, err) + require.Equal(t, commit.Merkle, oldCommit.Merkle) +} + +func TestHashInto(t *testing.T) { + simpleHash := crypto.Keccak256Hash([]byte("foo")) + leaves := []common.Hash{ + simpleHash, + simpleHash, + simpleHash, + simpleHash, + } + comm := newCommitter() + want := crypto.Keccak256Hash(simpleHash[:], simpleHash[:], simpleHash[:], simpleHash[:]) + var got common.Hash + comm.hashInto(&got, &leaves[0], &leaves[1], &leaves[2], &leaves[3]) + if got != want { + t.Errorf("got %s, want %s", got.Hex(), want.Hex()) + } +} + +func TestLastLeafProofPositions(t *testing.T) { + tests := []struct { + name string + virtual uint64 + want []treePosition + }{ + {"virtual 1", 1, []treePosition{}}, + {"virtual 2", 2, []treePosition{{0, 0}}}, + {"virtual 9", 9, []treePosition{ + {0, 9}, + {1, 5}, + {2, 3}, + {3, 0}, + }}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got, err := lastLeafProofPositions(tc.virtual) + require.NoError(t, err) + require.Equal(t, tc.want, got) + }) + } +} diff --git a/bold/state-commitments/inclusion-proofs/inclusion_proofs.go b/bold/state-commitments/inclusion-proofs/inclusion_proofs.go new file mode 100644 index 0000000000..0f0550c819 --- /dev/null +++ b/bold/state-commitments/inclusion-proofs/inclusion_proofs.go @@ -0,0 +1,136 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package inclusionproofs defines a series of utilities for generating and +// verifying traditional Merkle proofs of data. +package inclusionproofs + +import ( + "runtime" + "sync" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" +) + +var ( + ErrProofTooLong = errors.New("merkle proof too long") + ErrInvalidLeaves = errors.New("invalid number of leaves for merkle tree") +) + +// FullTree generates a Merkle tree from a list of leaves. +func FullTree(leaves []common.Hash) ([][]common.Hash, error) { + msb, err := prefixproofs.MostSignificantBit(uint64(len(leaves))) + if err != nil { + return nil, err + } + lsb, err := prefixproofs.LeastSignificantBit(uint64(len(leaves))) + if err != nil { + return nil, err + } + maxLevel := msb + 1 + if msb == lsb { + maxLevel = msb + } + + layers := make([][]common.Hash, maxLevel+1) + layers[0] = leaves + l := uint64(1) + + prevLayer := leaves + for len(prevLayer) > 1 { + nextLayer := make([]common.Hash, (len(prevLayer)+1)/2) + for i := 0; i < len(nextLayer); i++ { + if 2*i+1 < len(prevLayer) { + nextLayer[i] = crypto.Keccak256Hash(prevLayer[2*i].Bytes(), prevLayer[2*i+1].Bytes()) + } else { + nextLayer[i] = crypto.Keccak256Hash(prevLayer[2*i].Bytes(), (common.Hash{}).Bytes()) + } + } + layers[l] = nextLayer + prevLayer = nextLayer + l++ + } + return layers, nil +} + +// GenerateInclusionProof from a list of Merkle leaves at a specified index. +func GenerateInclusionProof(leaves []common.Hash, idx uint64) ([]common.Hash, error) { + if len(leaves) == 0 { + return nil, ErrInvalidLeaves + } + if idx >= uint64(len(leaves)) { + return nil, ErrInvalidLeaves + } + if len(leaves) == 1 { + return make([]common.Hash, 0), nil + } + rehashed := make([]common.Hash, len(leaves)) + var waitGroup sync.WaitGroup + gomaxprocs := runtime.GOMAXPROCS(-1) + waitGroup.Add(gomaxprocs) + batchSize := len(leaves) / gomaxprocs + batchRemainder := len(leaves) % gomaxprocs + for i := 0; i < gomaxprocs-1; i++ { + start := i * batchSize + go func() { + defer waitGroup.Done() + for j := start; j < start+batchSize; j++ { + rehashed[j] = crypto.Keccak256Hash(leaves[j].Bytes()) + } + }() + } + start := (gomaxprocs - 1) * batchSize + go func() { + defer waitGroup.Done() + for j := start; j < start+batchSize+batchRemainder; j++ { + rehashed[j] = crypto.Keccak256Hash(leaves[j].Bytes()) + } + }() + waitGroup.Wait() + + fullT, err := FullTree(rehashed) + if err != nil { + return nil, err + } + maxLevel, err := prefixproofs.MostSignificantBit(uint64(len(rehashed)) - 1) + if err != nil { + return nil, err + } + proof := make([]common.Hash, maxLevel+1) + + for level := uint64(0); level <= maxLevel; level++ { + levelIndex := idx >> level + counterpartIndex := levelIndex ^ 1 + layer := fullT[level] + counterpart := common.Hash{} + if counterpartIndex <= uint64(len(layer))-1 { + counterpart = layer[counterpartIndex] + } + proof[level] = counterpart + } + + return proof, nil +} + +// CalculateRootFromProof calculates a Merkle root from a Merkle proof, index, and leaf. +func CalculateRootFromProof(proof []common.Hash, index uint64, leaf common.Hash) (common.Hash, error) { + if len(proof) > 256 { + return common.Hash{}, ErrProofTooLong + } + h := crypto.Keccak256Hash(leaf[:]) + for i := 0; i < len(proof); i++ { + node := proof[i] + if index&(1< 0) + + computedRoot, err := CalculateRootFromProof(proof, index, leaves[index]) + require.NoError(t, err) + + exp := prefixproofs.NewEmptyMerkleExpansion() + for _, r := range leaves { + exp, err = prefixproofs.AppendLeaf(exp, r) + require.NoError(t, err) + } + + root, err := prefixproofs.Root(exp) + require.NoError(t, err) + + t.Run("proof verifies", func(t *testing.T) { + require.Equal(t, root, computedRoot) + }) + t.Run("first leaf proof", func(t *testing.T) { + index = uint64(0) + proof, err = GenerateInclusionProof(leaves, index) + require.NoError(t, err) + require.Equal(t, true, len(proof) > 0) + computedRoot, err = CalculateRootFromProof(proof, index, leaves[index]) + require.NoError(t, err) + require.Equal(t, root, computedRoot) + }) + t.Run("last leaf proof", func(t *testing.T) { + index = casttest.ToUint64(t, len(leaves)-1) + proof, err = GenerateInclusionProof(leaves, index) + require.NoError(t, err) + require.Equal(t, true, len(proof) > 0) + computedRoot, err = CalculateRootFromProof(proof, index, leaves[index]) + require.NoError(t, err) + require.Equal(t, root, computedRoot) + }) + t.Run("Invalid inputs", func(t *testing.T) { + // Empty tree should not generate a proof. + _, err := GenerateInclusionProof([]common.Hash{}, 0) + require.Equal(t, ErrInvalidLeaves, err) + + // Index greater than the number of leaves should not generate a proof. + _, err = GenerateInclusionProof(leaves, uint64(len(leaves))) + require.Equal(t, ErrInvalidLeaves, err) + + // Proof with more than 256 elements should not calculate a root... + _, err = CalculateRootFromProof(make([]common.Hash, 257), 0, common.Hash{}) + require.Equal(t, ErrProofTooLong, err) + + // ... but proof with exactly 256 elements should be OK. + _, err = CalculateRootFromProof(make([]common.Hash, 256), 0, common.Hash{}) + require.NotEqual(t, ErrProofTooLong, err) + }) +} diff --git a/bold/state-commitments/legacy/legacy.go b/bold/state-commitments/legacy/legacy.go new file mode 100644 index 0000000000..eb65fdfa1a --- /dev/null +++ b/bold/state-commitments/legacy/legacy.go @@ -0,0 +1,99 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package history defines the primitive HistoryCommitment type in the BoLD +// protocol. +package legacy + +import ( + "errors" + "sync" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/common" + + inclusionproofs "github.com/offchainlabs/bold/state-commitments/inclusion-proofs" + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" +) + +var ( + emptyCommit = LegacyHistory{} +) + +// LegacyHistory defines a Merkle accumulator over a list of leaves, which +// are understood to be state roots in the goimpl. A history commitment contains +// a "height" value, which can refer to a height of an assertion in the assertions +// tree, or a "step" of WAVM states in a big step or small step subchallenge. +// A commitment contains a Merkle root over the list of leaves, and can optionally +// provide a proof that the last leaf in the accumulator Merkleizes into the +// specified root hash, which is required when verifying challenge creation invariants. +type LegacyHistory struct { + Height uint64 + Merkle common.Hash + FirstLeaf common.Hash + LastLeafProof []common.Hash + FirstLeafProof []common.Hash + LastLeaf common.Hash +} + +func NewLegacy(leaves []common.Hash) (LegacyHistory, error) { + if len(leaves) == 0 { + return emptyCommit, errors.New("must commit to at least one leaf") + } + var waitGroup sync.WaitGroup + waitGroup.Add(3) + + var firstLeafProof []common.Hash + var err1 error + go func() { + defer waitGroup.Done() + firstLeafProof, err1 = inclusionproofs.GenerateInclusionProof(leaves, 0) + }() + + var lastLeafProof []common.Hash + var err2 error + go func() { + defer waitGroup.Done() + lastLeafProof, err2 = inclusionproofs.GenerateInclusionProof(leaves, uint64(len(leaves))-1) + }() + + var root common.Hash + var err3 error + go func() { + defer waitGroup.Done() + exp := prefixproofs.NewEmptyMerkleExpansion() + for _, r := range leaves { + exp, err3 = prefixproofs.AppendLeaf(exp, r) + if err3 != nil { + return + } + } + root, err3 = prefixproofs.Root(exp) + }() + waitGroup.Wait() + + if err1 != nil { + return emptyCommit, err1 + } + if err2 != nil { + return emptyCommit, err2 + } + if err3 != nil { + return emptyCommit, err3 + } + hU64, err := safecast.ToUint64(len(leaves) - 1) + if err != nil { + return emptyCommit, err + } + + return LegacyHistory{ + Merkle: root, + Height: hU64, + FirstLeaf: leaves[0], + LastLeaf: leaves[len(leaves)-1], + FirstLeafProof: firstLeafProof, + LastLeafProof: lastLeafProof, + }, nil +} diff --git a/bold/state-commitments/legacy/legacy_test.go b/bold/state-commitments/legacy/legacy_test.go new file mode 100644 index 0000000000..e79a844f02 --- /dev/null +++ b/bold/state-commitments/legacy/legacy_test.go @@ -0,0 +1,34 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package legacy + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + inclusionproofs "github.com/offchainlabs/bold/state-commitments/inclusion-proofs" +) + +func TestHistoryCommitment_LeafProofs(t *testing.T) { + leaves := make([]common.Hash, 8) + for i := 0; i < len(leaves); i++ { + leaves[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + } + history, err := NewLegacy(leaves) + require.NoError(t, err) + require.Equal(t, history.FirstLeaf, leaves[0]) + require.Equal(t, history.LastLeaf, leaves[len(leaves)-1]) + + computed, err := inclusionproofs.CalculateRootFromProof(history.LastLeafProof, history.Height, history.LastLeaf) + require.NoError(t, err) + require.Equal(t, history.Merkle, computed) + computed, err = inclusionproofs.CalculateRootFromProof(history.FirstLeafProof, 0, history.FirstLeaf) + require.NoError(t, err) + require.Equal(t, history.Merkle, computed) +} diff --git a/bold/state-commitments/prefix-proofs/merkle_expansions.go b/bold/state-commitments/prefix-proofs/merkle_expansions.go new file mode 100644 index 0000000000..9a07125437 --- /dev/null +++ b/bold/state-commitments/prefix-proofs/merkle_expansions.go @@ -0,0 +1,70 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package prefixproofs + +import ( + "github.com/ethereum/go-ethereum/common" +) + +type MerkleExpansion []common.Hash + +func NewEmptyMerkleExpansion() MerkleExpansion { + return []common.Hash{} +} + +func (me MerkleExpansion) Clone() MerkleExpansion { + return append([]common.Hash{}, me...) +} + +func (me MerkleExpansion) Compact() ([]common.Hash, uint64) { + var comp []common.Hash + size := uint64(0) + for level, h := range me { + if h != (common.Hash{}) { + comp = append(comp, h) + size += 1 << level + } + } + return comp, size +} + +func MerkleExpansionFromCompact(comp []common.Hash, size uint64) (MerkleExpansion, uint64) { + var me []common.Hash + numRead := uint64(0) + i := uint64(1) + for i <= size { + if i&size != 0 { + numRead++ + me = append(me, comp[0]) + comp = comp[1:] + } else { + me = append(me, common.Hash{}) + } + i <<= 1 + } + return me, numRead +} + +func ExpansionFromLeaves(leaves []common.Hash) (MerkleExpansion, error) { + ret := NewEmptyMerkleExpansion() + for _, leaf := range leaves { + appended, err := AppendLeaf(ret, leaf) + if err != nil { + return nil, err + } + ret = appended + } + return ret, nil +} + +type MerkleExpansionRootFetcherFunc = func(leaves []common.Hash, upTo uint64) (common.Hash, error) + +func RootFetcherFromExpansion(leaves []common.Hash, upTo uint64) (common.Hash, error) { + exp, err := ExpansionFromLeaves(leaves[:upTo]) + if err != nil { + return common.Hash{}, err + } + return Root(exp) +} diff --git a/bold/state-commitments/prefix-proofs/merkle_expansions_test.go b/bold/state-commitments/prefix-proofs/merkle_expansions_test.go new file mode 100644 index 0000000000..25162547fd --- /dev/null +++ b/bold/state-commitments/prefix-proofs/merkle_expansions_test.go @@ -0,0 +1,69 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package prefixproofs + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/crypto" +) + +func TestMerkleExpansion(t *testing.T) { + me := NewEmptyMerkleExpansion() + + h0 := crypto.Keccak256Hash([]byte{0}) + me, err := AppendCompleteSubTree(me, 0, h0) + require.NoError(t, err) + root, err := Root(me) + require.NoError(t, err) + require.Equal(t, h0, root) + compUncompTest(t, me) + + h1 := crypto.Keccak256Hash([]byte{1}) + me, err = AppendCompleteSubTree(me, 0, h1) + require.NoError(t, err) + root, err = Root(me) + require.NoError(t, err) + require.Equal(t, crypto.Keccak256Hash(h0.Bytes(), h1.Bytes()), root) + compUncompTest(t, me) + + me2 := me.Clone() + h2 := crypto.Keccak256Hash([]byte{2}) + h3 := crypto.Keccak256Hash([]byte{2}) + h23 := crypto.Keccak256Hash(h2.Bytes(), h3.Bytes()) + me, err = AppendCompleteSubTree(me, 1, h23) + require.NoError(t, err) + root, err = Root(me) + require.NoError(t, err) + root2, err := Root(me2) + require.NoError(t, err) + require.Equal(t, crypto.Keccak256Hash(root2.Bytes(), h23.Bytes()), root) + compUncompTest(t, me) + + me4 := me.Clone() + me, err = AppendCompleteSubTree(me2, 0, h2) + require.NoError(t, err) + me, err = AppendCompleteSubTree(me, 0, h3) + require.NoError(t, err) + root, err = Root(me) + require.NoError(t, err) + root4, err := Root(me4) + require.NoError(t, err) + require.Equal(t, root, root4) + compUncompTest(t, me) +} + +func compUncompTest(t *testing.T, me MerkleExpansion) { + t.Helper() + comp, compSz := me.Compact() + me2, _ := MerkleExpansionFromCompact(comp, compSz) + root, err := Root(me) + require.NoError(t, err) + root2, err := Root(me2) + require.NoError(t, err) + require.Equal(t, root, root2) +} diff --git a/bold/state-commitments/prefix-proofs/prefix_proofs.go b/bold/state-commitments/prefix-proofs/prefix_proofs.go new file mode 100644 index 0000000000..2e0d3742c2 --- /dev/null +++ b/bold/state-commitments/prefix-proofs/prefix_proofs.go @@ -0,0 +1,510 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package prefixproofs defines utilities for creating Merkle prefix proofs for binary +// trees. It is used extensively in the challenge protocol for making challenge moves on-chain. +// These utilities also have equivalent counterparts written in Solidity under +// MerkleTreeLib.sol, which must be thoroughly tested against to ensure safety. +// +// Binary trees +// -------------------------------------------------------------------------------------------- +// A complete tree is a balanced binary tree - each node has two children except the leaf +// Leaves have no children, they are a complete tree of size one +// Any tree (can be incomplete) can be represented as a collection of complete sub trees. +// Since the tree is binary only one or zero complete tree at each level is enough to define any size of tree. +// The root of a tree (incomplete or otherwise) is defined as the cumulative hashing of all of the +// roots of each of it's complete and empty subtrees. +// --------- +// eg. Below a tree of size 3 is represented as the composition of 2 complete subtrees, one of size +// 2 (AB) and one of size one (C). +// +// AB +// / \ +// A B C +// +// Merkle expansions and roots +// -------------------------------------------------------------------------------------------- +// The minimal amount of information we need to keep in order to compute the root of a tree +// is the roots of each of its sub trees, and the levels of each of those trees +// A "merkle expansion" (ME) is this information - it is a vector of roots of each complete subtree, +// the level of the tree being the index in the vector, the subtree root being the value. +// The root is calculated by hashing each of the levels of the subtree together, adding zero hashes +// where relevant to make a balanced tree. +// --------- +// +// # ME Example 1 +// +// C => (C) +// +// ME of the C tree = (C), root=(C) +// The merkle expansion of a tree consisting of a single leaf is vector of size one with the +// zeroth index being the leaf C. The zeroth index of the vector represents the presence of a size +// one complete subtree in the overall tree. So if a tree has a size one complete subtree as part +// of its composition, the root of that size one tree will be present in the zeroth index. +// +// ME Example 2 +// +// AB +// / \ +// A B +// +// ME of the AB tree = (0, AB), root=AB +// The merkle expansion of a tree consisting of a single size 2 complete subtree is a vector +// of size 2, with the zeroth index value being 0, and the 1st index value being the root of the size +// 2 subtree. The zero in the zeroth index indicated that there is not a size 1 subtree in the tree's +// composition. If a tree has a size 2 subtree in its composition its root will be present in the +// 1st index. +// +// ME Example 3 +// +// AB +// / \ +// A B C +// +// ME of the composed ABC tree = (C, AB), root=hash(AB, hash(C, 0)). +// When a tree is not itself a complete subtree, but rather a composition, zeros are added when +// calculating the root. To do this hash the first complete sub tree with zero, and from there +// cumulatively hash the merkle expansion. +// The merkle expansion of this composed tree is a vector of size two. Since it has a size one tree in +// its composition the root of that goes in the zeroth index of the expansion - C, and since it has a +// size two tree in its composition the root of that goes in the 1st index, to give (C, AB). +// +// Tree operations +// -------------------------------------------------------------------------------------------- +// Binary trees are modified by adding or subtracting complete subtrees, however this library +// supports additive only trees since we dont have a specific use for subtraction at the moment. +// We call adding a complete subtree to an existing tree "appending", appending has the following +// rules: +// 1. Only a complete sub trees can be appended +// 2. Complete sub trees can only be appended at the level of the lowest complete subtree in the tree, or below +// 3. If the existing tree is empty a sub tree can be appended at any level +// When appending a sub tree we may increase the size of the merkle expansion vector, in the same +// that adding 1 to a binary number may increase the index of its most significant bit +// --------- +// eg. A complete subtree can only be appended to the ABC tree at level 0, since the its lowest complete +// subtree (C) is at level 0. Doing so would create a complete sub tree at level 1, which would in turn +// cause the creation of new size 4 sub tree +// +// ABCD +// / \ +// AB AB CD +// / \ + = / \ / \ +// A B C D A B C D +// +// ME of ABCD = (0, AB) + (C) + (D) +// +// = (C, AB) + (D) +// = (0, 0, ABCD) +// +// root of ABCD =hash(AB, CD) +// -------------------------------------------------------------------------------------------- +// +//nolint:dupword +package prefixproofs + +import ( + "math" + "math/bits" + + "github.com/ccoveille/go-safecast" + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +const ( + // MAX_LEVEL for our binary trees. + MAX_LEVEL = uint64(64) +) + +var ( + ErrRootForEmpty = errors.New("cannot calculate root for empty") + ErrExpansionTooLarge = errors.New("merkle expansion to large") + ErrLevelTooHigh = errors.New("level too high") + ErrTreeSize = errors.New("tree size incorrect") + ErrCannotAppendEmpty = errors.New("cannot append empty") + ErrCannotAppendAboveLeastSignificant = errors.New("cannot append above least significant") + ErrStartNotLessThanEnd = errors.New("start not less than end") + ErrCannotBeZero = errors.New("cannot be zero") + ErrRootMismatch = errors.New("root mismatch") + ErrIncompleteProof = errors.New("incomplete proof usage") + ErrSizeNotLeqPostSize = errors.New("size not <= post size") + ErrIndexOutOfRange = errors.New("index out of range") +) + +// LeastSignificantBit of a 64bit unsigned integer. +func LeastSignificantBit(x uint64) (uint64, error) { + if x == 0 { + return 0, ErrCannotBeZero + } + lsbU64, err := safecast.ToUint64(bits.TrailingZeros64(x)) + if err != nil { + return 0, err + } + return lsbU64, nil +} + +// MostSignificantBit of a 64bit unsigned integer. +func MostSignificantBit(x uint64) (uint64, error) { + if x == 0 { + return 0, ErrCannotBeZero + } + msb64, err := safecast.ToUint64(63 - bits.LeadingZeros64(x)) + if err != nil { + return 0, err + } + return msb64, nil +} + +// Root of the subtree. A collision free commitment to the contents of the tree. +// The root of a tree is defined as the cumulative hashing of the roots of +// all its subtrees. Returns error for empty tree. +func Root(me []common.Hash) (common.Hash, error) { + if uint64(len(me)) > MAX_LEVEL { + return common.Hash{}, ErrExpansionTooLarge + } + if uint64(len(me)) == 0 { + return common.Hash{}, ErrRootForEmpty + } + + var accum common.Hash + for i := 0; i < len(me); i++ { + val := me[i] + if accum == (common.Hash{}) { + if val != (common.Hash{}) { + accum = val + + // the tree is balanced if the only non zero entry in the merkle extension + // us the last entry + // otherwise the lowest level entry needs to be combined with a zero to balance the bottom + // level, after which zeros in the merkle extension above that will balance the rest + if i != len(me)-1 { + accum = crypto.Keccak256Hash(accum.Bytes(), (common.Hash{}).Bytes()) + } + } + } else if (val != common.Hash{}) { + // accum represents the smaller sub trees, since it is earlier in the expansion we put + // the larger subtrees on the left + accum = crypto.Keccak256Hash(val.Bytes(), accum.Bytes()) + } else { + // by definition we always complete trees by appending zeros to the right + accum = crypto.Keccak256Hash(accum.Bytes(), (common.Hash{}).Bytes()) + } + } + return accum, nil +} + +// TreeSize calculates the full tree size represented by a merkle expansion +func TreeSize(me []common.Hash) uint64 { + sum := uint64(0) + for i := 0; i < len(me); i++ { + if me[i] != (common.Hash{}) { + sum += uint64(math.Pow(2, float64(i))) + } + } + return sum +} + +// AppendCompleteSubTree appends a complete subtree to an existing tree +// See above description of trees for rules on how appending can occur. +// Briefly, appending works like binary addition only that the value being added be an +// exact power of two (complete), and must equal to or less than the least significant bit +// in the existing tree. +// If the me is empty, will just append directly. +func AppendCompleteSubTree( + me []common.Hash, level uint64, subtreeRoot common.Hash, +) ([]common.Hash, error) { + // we use number representations of the levels elsewhere, so we need to ensure we're appending a leve + // that's too high to use in uint + if level >= MAX_LEVEL { + return nil, ErrLevelTooHigh + } + if subtreeRoot == (common.Hash{}) { + return nil, ErrCannotAppendEmpty + } + if uint64(len(me)) > MAX_LEVEL { + return nil, ErrExpansionTooLarge + } + + if len(me) == 0 { + empty := make([]common.Hash, level+1) + empty[level] = subtreeRoot + return empty, nil + } + + if level >= uint64(len(me)) { + // This technically isn't necessary since it would be caught by the i < level check + // on the last loop of the for-loop below, but we add it for a clearer error message + return nil, errors.Wrap(ErrLevelTooHigh, "errored before for loop") + } + + accumHash := subtreeRoot + next := make([]common.Hash, len(me)) + + // loop through all the levels in self and try to append the new subtree + // since each node has two children by appending a subtree we may complete another one + // in the level above. So we move through the levels updating the result at each level + for i := uint64(0); i < uint64(len(me)); i++ { + // we can only append at the level of the smallest complete sub tree or below + // appending above this level would mean create "holes" in the tree + // we can find the smallest complete sub tree by looking for the first entry in the merkle expansion + if i < level { + // we're below the level we want to append - no complete sub trees allowed down here + // if the level is 0 there are no complete subtrees, and we therefore cannot be too low + if me[i] != (common.Hash{}) { + return nil, ErrCannotAppendAboveLeastSignificant + } + } else { + // we're at or above the level + if accumHash == (common.Hash{}) { + // no more changes to propagate upwards - just fill the tree + next[i] = me[i] + } else { + // we have a change to propagate + if me[i] == (common.Hash{}) { + // if the level is currently empty we can just add the change + next[i] = accumHash + // and then there's nothing more to propagate + accumHash = common.Hash{} + } else { + // if the level is not currently empty then we combine it with propagation + // change, and propagate that to the level above. This level is now part of a complete subtree + // so we zero it out + next[i] = common.Hash{} + accumHash = crypto.Keccak256Hash(me[i].Bytes(), accumHash.Bytes()) + } + } + } + } + + // we had a final change to propagate above the existing highest complete sub tree + // so we have a new highest complete sub tree in the level above + if accumHash != (common.Hash{}) { + next = append(next, accumHash) + } + + if uint64(len(next)) >= MAX_LEVEL+1 { + return nil, ErrLevelTooHigh + } + return next, nil +} + +// AppendLeaf appends a leaf to a subtree +// Leaves are just complete subtrees at level 0, however we hash the leaf before putting it +// into the tree to avoid root collisions. +func AppendLeaf( + me []common.Hash, leaf [32]byte, +) ([]common.Hash, error) { + // it's important that we hash the leaf, this ensures that this leaf cannot be a collision with any other non leaf + // or root node, since these are always the hash of 64 bytes of data, and we're hashing 32 bytes + return AppendCompleteSubTree(me, 0, crypto.Keccak256Hash(leaf[:])) +} + +// MaximumAppendBetween finds the highest level which can be appended to tree of size startSize without +// creating a tree with size greater than end size (inclusive) +// Subtrees can only be appended according to certain rules, see tree description at top of file +// for details. A subtree can only be appended if it is at the same level, or below, the current lowest +// subtree in the expansion +func MaximumAppendBetween(startSize, endSize uint64) (uint64, error) { + // Since the tree is binary we can represent it using the binary representation of a number + // We use size here instead of height since height is zero indexed + // As described above, subtrees can only be appended to a tree if they are at the same level, or below, + // the current lowest subtree. + // In this function we want to find the level of the highest tree that can be appended to the current + // tree, without the resulting tree surpassing the end point. We do this by looking at the difference + // between the start and end size, and iteratively reducing it in the maximal way. + + // The start and end size will share some higher order bits, below that they differ, and it is this + // difference that we need to fill in the minimum number of appends + // startSize looks like: xxxxxxyyyy + // endSize looks like: xxxxxxzzzz + // where x are the complete sub trees they share, and y and z are the subtrees they dont + if startSize >= endSize { + return 0, errors.Wrapf(ErrStartNotLessThanEnd, "start %d, end %d", startSize, endSize) + } + + // remove the high order bits that are shared + msb, err := MostSignificantBit(startSize ^ endSize) + if err != nil { + return 0, err + } + + mask := uint64((1 << (msb + 1)) - 1) + y := startSize & mask + z := endSize & mask + + // Since in the verification we will be appending at start size, the highest level at which we + // can append is the lowest complete subtree - the least significant bit + if y != 0 { + return LeastSignificantBit(y) + } + // y == 0, therefore we can append at any of levels where start and end differ + // The highest level that we can append at without surpassing the end, is the most significant + // bit of the end + if z != 0 { + return MostSignificantBit(z) + } + // since we enforce that start < end, we know that y and z cannot both be 0 + return 0, errors.Wrap(ErrCannotBeZero, "y and z cannot both be 0") +} + +func GeneratePrefixProof( + prefixHeight uint64, + prefixExpansion MerkleExpansion, + leaves []common.Hash, + rootFetcher MerkleExpansionRootFetcherFunc, +) ([]common.Hash, error) { + if prefixHeight == 0 { + return nil, errors.Wrap(ErrCannotBeZero, "prefixHeight was 0") + } + if len(leaves) == 0 { + return nil, errors.Wrap(ErrCannotBeZero, "length of leaves was 0") + } + height := prefixHeight + postHeight := height + uint64(len(leaves)) + + // Impossible to generate a prefix proof if the prefix height is greater than the post height given conditions above + if prefixHeight >= postHeight { + return nil, errors.Wrapf( + ErrStartNotLessThanEnd, + "preheight %d >= postheight %d", + prefixHeight, + postHeight, + ) + } + + proof, _ := prefixExpansion.Compact() + for height < postHeight { + // extHeight looks like xxxxxxxyyy + // post.height looks like xxxxxxxzzz + firstDiffBit, err := MostSignificantBit(height ^ postHeight) + if err != nil { + return nil, err + } + mask := (uint64(1) << (firstDiffBit + 1)) - 1 + yyy := height & mask + zzz := postHeight & mask + if yyy != 0 { + lowBit, err := LeastSignificantBit(yyy) + if err != nil { + return nil, err + } + numLeaves := uint64(1) << lowBit + root, err := rootFetcher(leaves, numLeaves) + if err != nil { + return nil, err + } + proof = append(proof, root) + leaves = leaves[numLeaves:] + height += numLeaves + } else if zzz != 0 { + highBit, err := MostSignificantBit(zzz) + if err != nil { + return nil, err + } + numLeaves := uint64(1) << highBit + root, err := rootFetcher(leaves, numLeaves) + if err != nil { + return nil, err + } + proof = append(proof, root) + leaves = leaves[numLeaves:] + height += numLeaves + } + } + return proof, nil +} + +type VerifyPrefixProofConfig struct { + PreRoot common.Hash + PreSize uint64 + PostRoot common.Hash + PostSize uint64 + PreExpansion []common.Hash + PrefixProof []common.Hash +} + +// VerifyPrefixProof verifies that a pre-root commits to a prefix of the leaves committed by a post-root +// Verifies by appending sub trees to the pre tree until we get to the size of the post tree +// and then checking that the root of the calculated post tree is equal to the supplied one +func VerifyPrefixProof(cfg *VerifyPrefixProofConfig) error { + if cfg.PreSize == 0 { + return errors.Wrap(ErrCannotBeZero, "presize was 0") + } + root, rootErr := Root(cfg.PreExpansion) + if rootErr != nil { + return errors.Wrap(rootErr, "pre expansion root error") + } + if root != cfg.PreRoot { + return errors.Wrap(ErrRootMismatch, "pre expansion root mismatch") + } + if cfg.PreSize != TreeSize(cfg.PreExpansion) { + return errors.Wrap(ErrTreeSize, "pre expansion tree size") + } + if cfg.PreSize >= cfg.PostSize { + return errors.Wrapf( + ErrStartNotLessThanEnd, + "presize %d >= postsize %d", + cfg.PreSize, + cfg.PostSize, + ) + } + + exp := make([]common.Hash, len(cfg.PreExpansion)) + copy(exp, cfg.PreExpansion) + size := cfg.PreSize + proofIndex := uint64(0) + + for size < cfg.PostSize { + level, err := MaximumAppendBetween(size, cfg.PostSize) + if err != nil { + return err + } + if proofIndex >= uint64(len(cfg.PrefixProof)) { + return ErrIndexOutOfRange + } + exp, err = AppendCompleteSubTree( + exp, level, cfg.PrefixProof[proofIndex], + ) + if err != nil { + return err + } + numLeaves, err := safecast.ToUint64(1 << level) + if err != nil { + return err + } + size += numLeaves + if size > cfg.PostSize { + return errors.Wrapf( + ErrSizeNotLeqPostSize, + "size %d > postsize %d", + size, + cfg.PostSize, + ) + } + proofIndex++ + } + gotRoot, gotRootErr := Root(exp) + if gotRootErr != nil { + return errors.Wrap(gotRootErr, "post root error") + } + if gotRoot != cfg.PostRoot { + return errors.Wrapf( + ErrRootMismatch, + "post expansion root mismatch got %#x, wanted %#x", + gotRoot, + cfg.PostRoot, + ) + } + if proofIndex != uint64(len(cfg.PrefixProof)) { + return errors.Wrapf( + ErrIncompleteProof, + "proof index %d, proof length %d", + proofIndex, + len(cfg.PrefixProof), + ) + } + return nil +} diff --git a/bold/state-commitments/prefix-proofs/prefix_proofs_test.go b/bold/state-commitments/prefix-proofs/prefix_proofs_test.go new file mode 100644 index 0000000000..e6f563dfd5 --- /dev/null +++ b/bold/state-commitments/prefix-proofs/prefix_proofs_test.go @@ -0,0 +1,637 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package prefixproofs_test + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" + "github.com/offchainlabs/bold/testing/casttest" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" +) + +func TestAppendCompleteSubTree(t *testing.T) { + // Test case: Level >= MAX_LEVEL + _, err := prefixproofs.AppendCompleteSubTree([]common.Hash{{1}}, prefixproofs.MAX_LEVEL, common.Hash{2}) + require.ErrorContains(t, err, "level too high") + + // Test case: Empty Subtree Root + _, err = prefixproofs.AppendCompleteSubTree([]common.Hash{{1}}, 1, common.Hash{}) + require.ErrorContains(t, err, "cannot append empty") + + // Test case: Expansion Too Large + _, err = prefixproofs.AppendCompleteSubTree(make([]common.Hash, prefixproofs.MAX_LEVEL+1), 1, common.Hash{2}) + require.ErrorContains(t, err, "merkle expansion to large") + + // Test case: Empty 'me' Array + _, err = prefixproofs.AppendCompleteSubTree([]common.Hash{}, 1, common.Hash{2}) + require.NoError(t, err) + + // Test case: Level >= len(me) + _, err = prefixproofs.AppendCompleteSubTree([]common.Hash{{1}}, 2, common.Hash{2}) + require.ErrorContains(t, err, "errored before for loop: level too high") +} + +func TestGeneratePrefixProof(t *testing.T) { + defaultLeaves := []common.Hash{{1}, {2}} + + // Test case: Zero PrefixHeight + _, err := prefixproofs.GeneratePrefixProof(0, nil, defaultLeaves, nil) + require.ErrorContains(t, err, "prefixHeight was 0") + + // Test case: Zero Length of Leaves + _, err = prefixproofs.GeneratePrefixProof(1, nil, []common.Hash{}, nil) + require.ErrorContains(t, err, "length of leaves was 0") +} + +func TestRoot(t *testing.T) { + t.Run("tree with exactly size MAX_LEVEL should pass validation", func(t *testing.T) { + tree := make([]common.Hash, prefixproofs.MAX_LEVEL) + _, err := prefixproofs.Root(tree) + require.NotEqual(t, prefixproofs.ErrLevelTooHigh, err) + }) + t.Run("tree too large", func(t *testing.T) { + tree := make([]common.Hash, prefixproofs.MAX_LEVEL+1) + _, err := prefixproofs.Root(tree) + require.Equal(t, prefixproofs.ErrExpansionTooLarge, err) + }) + t.Run("empty tree", func(t *testing.T) { + tree := make([]common.Hash, 0) + _, err := prefixproofs.Root(tree) + require.Equal(t, prefixproofs.ErrRootForEmpty, err) + }) + t.Run("single element returns itself", func(t *testing.T) { + tree := make([]common.Hash, 1) + tree[0] = common.HexToHash("0x1234") + root, err := prefixproofs.Root(tree) + require.NoError(t, err) + require.Equal(t, tree[0], root) + }) +} + +func TestVerifyPrefixProof_GoSolidityEquivalence(t *testing.T) { + ctx := context.Background() + hashes := make([]common.Hash, 10) + for i := 0; i < len(hashes); i++ { + hashes[i] = crypto.Keccak256Hash([]byte(fmt.Sprintf("%d", i))) + } + manager, err := statemanager.NewWithMockedStateRoots(hashes) + require.NoError(t, err) + + wasmModuleRoot := common.Hash{} + startMessageNumber := l2stateprovider.Height(0) + fromMessageNumber := l2stateprovider.Height(3) + toMessageNumber := l2stateprovider.Height(7) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: wasmModuleRoot, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: uint64(startMessageNumber), + }, + BatchLimit: 10, + }, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(fromMessageNumber)), + } + loCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(t, err) + + req.UpToHeight = option.Some(l2stateprovider.Height(toMessageNumber)) + hiCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(t, err) + + packedProof, err := manager.PrefixProof(ctx, req, fromMessageNumber) + require.NoError(t, err) + + data, err := statemanager.ProofArgs.Unpack(packedProof) + require.NoError(t, err) + preExpansion, ok := data[0].([][32]byte) + require.Equal(t, true, ok) + proof, ok := data[1].([][32]byte) + require.Equal(t, true, ok) + + preExpansionHashes := make([]common.Hash, len(preExpansion)) + for i := 0; i < len(preExpansion); i++ { + preExpansionHashes[i] = preExpansion[i] + } + prefixProof := make([]common.Hash, len(proof)) + for i := 0; i < len(proof); i++ { + prefixProof[i] = proof[i] + } + + merkleTreeContract, _ := setupMerkleTreeContract(t) + err = merkleTreeContract.VerifyPrefixProof( + &bind.CallOpts{}, + loCommit.Merkle, + big.NewInt(4), + hiCommit.Merkle, + big.NewInt(8), + preExpansion, + proof, + ) + require.NoError(t, err) + + err = prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: loCommit.Merkle, + PreSize: 4, + PostRoot: hiCommit.Merkle, + PostSize: 8, + PreExpansion: preExpansionHashes, + PrefixProof: prefixProof, + }) + require.NoError(t, err) +} + +func TestVerifyPrefixProofWithHeight7_GoSolidityEquivalence1(t *testing.T) { + ctx := context.Background() + hashes := make([]common.Hash, 10) + for i := 0; i < len(hashes); i++ { + hashes[i] = crypto.Keccak256Hash([]byte(fmt.Sprintf("%d", i))) + } + manager, err := statemanager.NewWithMockedStateRoots(hashes) + require.NoError(t, err) + + wasmModuleRoot := common.Hash{} + startMessageNumber := l2stateprovider.Height(0) + fromMessageNumber := l2stateprovider.Height(3) + toMessageNumber := l2stateprovider.Height(6) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: wasmModuleRoot, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: uint64(startMessageNumber), + }, + BatchLimit: 10, + }, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(fromMessageNumber)), + } + loCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(t, err) + + req.UpToHeight = option.Some(l2stateprovider.Height(toMessageNumber)) + hiCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(t, err) + + packedProof, err := manager.PrefixProof(ctx, req, fromMessageNumber) + require.NoError(t, err) + + data, err := statemanager.ProofArgs.Unpack(packedProof) + require.NoError(t, err) + preExpansion, ok := data[0].([][32]byte) + require.Equal(t, true, ok) + proof, ok := data[1].([][32]byte) + require.Equal(t, true, ok) + + preExpansionHashes := make([]common.Hash, len(preExpansion)) + for i := 0; i < len(preExpansion); i++ { + preExpansionHashes[i] = preExpansion[i] + } + prefixProof := make([]common.Hash, len(proof)) + for i := 0; i < len(proof); i++ { + prefixProof[i] = proof[i] + } + + merkleTreeContract, _ := setupMerkleTreeContract(t) + err = merkleTreeContract.VerifyPrefixProof( + &bind.CallOpts{}, + loCommit.Merkle, + big.NewInt(4), + hiCommit.Merkle, + big.NewInt(7), + preExpansion, + proof, + ) + require.NoError(t, err) + + err = prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: loCommit.Merkle, + PreSize: 4, + PostRoot: hiCommit.Merkle, + PostSize: 7, + PreExpansion: preExpansionHashes, + PrefixProof: prefixProof, + }) + require.NoError(t, err) +} + +func TestLeastSignificantBit_GoSolidityEquivalence(t *testing.T) { + merkleTreeContract, _ := setupMerkleTreeContract(t) + runBitEquivalenceTest(t, merkleTreeContract.LeastSignificantBit, prefixproofs.LeastSignificantBit) +} + +func TestMostSignificantBit_GoSolidityEquivalence(t *testing.T) { + merkleTreeContract, _ := setupMerkleTreeContract(t) + runBitEquivalenceTest(t, merkleTreeContract.MostSignificantBit, prefixproofs.MostSignificantBit) +} + +func FuzzPrefixProof_Verify(f *testing.F) { + ctx := context.Background() + hashes := make([]common.Hash, 10) + for i := 0; i < len(hashes); i++ { + hashes[i] = crypto.Keccak256Hash([]byte(fmt.Sprintf("%d", i))) + } + manager, err := statemanager.NewWithMockedStateRoots(hashes) + require.NoError(f, err) + + wasmModuleRoot := common.Hash{} + batch := l2stateprovider.Batch(1) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: wasmModuleRoot, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 3, + }, + BatchLimit: batch, + }, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.None[l2stateprovider.Height](), + } + loCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(f, err) + req.AssertionMetadata.FromState.PosInBatch = 7 + hiCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(f, err) + + fromMessageNumber := l2stateprovider.Height(3) + toMessageNumber := l2stateprovider.Height(7) + + req.AssertionMetadata.FromState.PosInBatch = 0 + req.UpToHeight = option.Some(toMessageNumber) + packedProof, err := manager.PrefixProof(ctx, req, fromMessageNumber) + require.NoError(f, err) + + data, err := statemanager.ProofArgs.Unpack(packedProof) + require.NoError(f, err) + preExpansion, ok := data[0].([][32]byte) + require.Equal(f, true, ok) + proof, ok := data[1].([][32]byte) + require.Equal(f, true, ok) + preExp := make([]byte, 0) + for _, item := range preExpansion { + preExp = append(preExp, item[:]...) + } + prefixProof := make([]byte, 0) + for _, item := range proof { + prefixProof = append(prefixProof, item[:]...) + } + + testcases := []prefixproofs.VerifyPrefixProofConfig{ + { + PreRoot: loCommit.Merkle, + PreSize: 4, + PostRoot: hiCommit.Merkle, + PostSize: 8, + }, + { + PreRoot: loCommit.Merkle, + PreSize: 0, + PostRoot: hiCommit.Merkle, + PostSize: 0, + }, + { + PreRoot: loCommit.Merkle, + PreSize: 0, + PostRoot: hiCommit.Merkle, + PostSize: 100, + }, + } + for _, tc := range testcases { + f.Add(tc.PreRoot.String(), tc.PreSize, tc.PostRoot.String(), tc.PostSize, hexutil.Encode(preExp), hexutil.Encode(prefixProof)) + } + merkleTreeContract, _ := setupMerkleTreeContract(f) + opts := &bind.CallOpts{} + f.Fuzz(func( + t *testing.T, + preRootF string, + preSizeF uint64, + postRootF string, + postSizeF uint64, + preExpansionF string, + prefixProofF string, + ) { + preExpF := make([]common.Hash, 0) + preArray := make([][32]byte, 0) + expansionRaw, err := hexutil.Decode(preExpansionF) + if err != nil { + return + } + proofRaw, err := hexutil.Decode(prefixProofF) + if err != nil { + return + } + preExpansionArray := make([][32]byte, 0) + for i := 0; i < len(expansionRaw); i += 32 { + var r [32]byte + if i+32 <= len(expansionRaw) { + copy(r[:], expansionRaw[i:i+32]) + } else { + copy(r[:], expansionRaw[i:]) + } + preExpansionArray = append(preExpansionArray, r) + } + + preExpansionHash := make([]common.Hash, len(preExpansionArray)) + for i := range preExpansionArray { + preExpansionHash[i] = preExpansionArray[i] + } + + proofArray := make([][32]byte, 0) + for i := 0; i < len(proofRaw); i += 32 { + var r [32]byte + if i+32 <= len(proofRaw) { + copy(r[:], proofRaw[i:i+32]) + } else { + copy(r[:], proofRaw[i:]) + } + proofArray = append(proofArray, r) + } + + proofHash := make([]common.Hash, len(proofArray)) + for i := range proofArray { + proofHash[i] = proofArray[i] + } + preRoot, err := hexutil.Decode(preRootF) + if err != nil { + return + } + postRoot, err := hexutil.Decode(postRootF) + if err != nil { + return + } + cfg := &prefixproofs.VerifyPrefixProofConfig{ + PreRoot: common.BytesToHash(preRoot), + PreSize: preSizeF, + PostRoot: common.BytesToHash(postRoot), + PostSize: postSizeF, + PreExpansion: preExpF, + PrefixProof: proofHash, + } + goErr := prefixproofs.VerifyPrefixProof(cfg) + solErr := merkleTreeContract.VerifyPrefixProof( + opts, + cfg.PreRoot, + big.NewInt(casttest.ToInt64(t, cfg.PreSize)), + cfg.PostRoot, + big.NewInt(casttest.ToInt64(t, cfg.PostSize)), + preArray, + proofArray, + ) + + if goErr == nil && solErr != nil { + t.Errorf("Go verified, but solidity did not verify: %+v", cfg) + } + if goErr != nil && solErr == nil { + t.Errorf("Solidity verified, but go did not verify: %+v", cfg) + } + }) +} + +func FuzzPrefixProof_MaximumAppendBetween_GoSolidityEquivalence(f *testing.F) { + type prePost struct { + pre uint64 + post uint64 + } + testcases := []prePost{ + {4, 8}, + {10, 0}, + {0, 0}, + {0, 1}, + {3, 3}, + {3, 4}, + {0, 15}, + {128, 512}, + {128, 200}, + {128, 1 << 20}, + {1 << 20, 1<<20 + 1}, + } + for _, tc := range testcases { + f.Add(tc.pre, tc.post) + } + merkleTreeContract, _ := setupMerkleTreeContract(f) + opts := &bind.CallOpts{} + f.Fuzz(func(t *testing.T, pre, post uint64) { + gotGo, err1 := prefixproofs.MaximumAppendBetween(pre, post) + preBig := big.NewInt(casttest.ToInt64(t, pre)) + postBig := big.NewInt(casttest.ToInt64(t, post)) + gotSol, err2 := merkleTreeContract.MaximumAppendBetween(opts, preBig, postBig) + if err1 == nil && err2 == nil { + if !gotSol.IsUint64() { + t.Fatal("sol result was not a uint64") + } + if gotSol.Uint64() != gotGo { + t.Errorf("sol %d != go %d", gotSol.Uint64(), gotGo) + } + } + }) +} + +func FuzzPrefixProof_BitUtils_GoSolidityEquivalence(f *testing.F) { + testcases := []uint64{ + 0, + 2, + 3, + 4, + 7, + 8, + 100, + 1 << 32, + 1<<32 - 1, + 1<<32 + 1, + 1 << 40, + } + for _, tc := range testcases { + f.Add(tc) + } + merkleTreeContract, _ := setupMerkleTreeContract(f) + opts := &bind.CallOpts{} + f.Fuzz(func(t *testing.T, x uint64) { + lsbSol, _ := merkleTreeContract.LeastSignificantBit(opts, big.NewInt(casttest.ToInt64(t, x))) + lsbGo, _ := prefixproofs.LeastSignificantBit(x) + if lsbSol != nil { + if !lsbSol.IsUint64() { + t.Fatal("lsb sol not a uint64") + } + if lsbSol.Uint64() != lsbGo { + t.Errorf("Mismatch lsb sol=%d, go=%d", lsbSol, lsbGo) + } + } + msbSol, _ := merkleTreeContract.MostSignificantBit(opts, big.NewInt(casttest.ToInt64(t, x))) + msbGo, _ := prefixproofs.MostSignificantBit(x) + if msbSol != nil { + if !msbSol.IsUint64() { + t.Fatal("msb sol not a uint64") + } + if msbSol.Uint64() != msbGo { + t.Errorf("Mismatch msb sol=%d, go=%d", msbSol, msbGo) + } + } + }) +} + +func runBitEquivalenceTest( + t testing.TB, + solFunc func(opts *bind.CallOpts, x *big.Int) (*big.Int, error), + goFunc func(x uint64) (uint64, error), +) { + opts := &bind.CallOpts{} + for _, tt := range []struct { + num uint64 + wantSolErr bool + solErr string + wantGoErr bool + goErr error + }{ + { + num: 0, + wantSolErr: true, + solErr: "has no significant bits", + wantGoErr: true, + goErr: prefixproofs.ErrCannotBeZero, + }, + {num: 2}, + {num: 3}, + {num: 4}, + {num: 7}, + {num: 8}, + {num: 10}, + {num: 100}, + {num: 256}, + {num: 1 << 32}, + {num: 1<<32 + 1}, + {num: 1<<32 - 1}, + {num: 10231920391293}, + } { + lsbSol, err := solFunc(opts, big.NewInt(casttest.ToInt64(t, tt.num))) + if tt.wantSolErr { + require.NotNil(t, err) + require.ErrorContains(t, err, tt.solErr) + } else { + require.NoError(t, err) + } + + lsbGo, err := goFunc(tt.num) + if tt.wantGoErr { + require.NotNil(t, err) + require.ErrorIs(t, err, tt.goErr) + } else { + require.NoError(t, err) + require.Equal(t, lsbSol.Uint64(), lsbGo) + } + } +} + +func setupMerkleTreeContract(t testing.TB) (*mocksgen.MerkleTreeAccess, *simulated.Backend) { + numChains := uint64(1) + accs, backend := setupAccounts(t, numChains) + _, _, merkleTreeContract, err := mocksgen.DeployMerkleTreeAccess(accs[0].txOpts, backend.Client()) + if err != nil { + t.Fatal(err) + } + backend.Commit() + return merkleTreeContract, backend +} + +// Represents a test EOA account in the simulated backend, +type testAccount struct { + accountAddr common.Address + txOpts *bind.TransactOpts +} + +func setupAccounts(t testing.TB, numAccounts uint64) ([]*testAccount, *simulated.Backend) { + genesis := make(types.GenesisAlloc) + gasLimit := uint64(100000000) + + accs := make([]*testAccount, numAccounts) + for i := uint64(0); i < numAccounts; i++ { + privKey, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + pubKeyECDSA, ok := privKey.Public().(*ecdsa.PublicKey) + if !ok { + t.Fatal("not ok") + } + + // Strip off the 0x and the first 2 characters 04 which is always the + // EC prefix and is not required. + publicKeyBytes := crypto.FromECDSAPub(pubKeyECDSA)[4:] + var pubKey = make([]byte, 48) + copy(pubKey, publicKeyBytes) + + addr := crypto.PubkeyToAddress(privKey.PublicKey) + chainID := big.NewInt(1337) + txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, chainID) + if err != nil { + t.Fatal(err) + } + startingBalance, _ := new(big.Int).SetString( + "100000000000000000000000000000000000000", + 10, + ) + genesis[addr] = types.Account{Balance: startingBalance} + accs[i] = &testAccount{ + accountAddr: addr, + txOpts: txOpts, + } + } + backend := simulated.NewBackend(genesis, simulated.WithBlockGasLimit(gasLimit)) + return accs, backend +} + +func TestVerifyPrefixProof(t *testing.T) { + // Test when PreSize is 0 + t.Run("TestPreSizeZero", func(t *testing.T) { + cfg := &prefixproofs.VerifyPrefixProofConfig{ + PreSize: 0, + } + err := prefixproofs.VerifyPrefixProof(cfg) + require.ErrorContains(t, err, "presize was 0: cannot be zero") + }) + + // Test when PreExpansion root does not match PreRoot + t.Run("TestPreRootMismatch", func(t *testing.T) { + cfg := &prefixproofs.VerifyPrefixProofConfig{ + PreRoot: common.Hash{1}, + PreExpansion: []common.Hash{{2}}, + PreSize: 1, + } + err := prefixproofs.VerifyPrefixProof(cfg) + require.ErrorContains(t, err, "pre expansion root mismatch: root mismatch") + }) + + // Test when PreSize does not match TreeSize + t.Run("TestTreeSizeMismatch", func(t *testing.T) { + r, err := prefixproofs.Root([]common.Hash{{1}, {2}}) + require.NoError(t, err) + cfg := &prefixproofs.VerifyPrefixProofConfig{ + PreRoot: r, + PreSize: 1, + PreExpansion: []common.Hash{{1}, {2}}, + } + err = prefixproofs.VerifyPrefixProof(cfg) + require.ErrorContains(t, err, "pre expansion tree size: tree size") + }) +} diff --git a/bold/state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/75d9022918aebc68769044c9043bd11a47046e9a443da304857347818e0bb256 b/bold/state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/75d9022918aebc68769044c9043bd11a47046e9a443da304857347818e0bb256 new file mode 100644 index 0000000000..69726c3f36 --- /dev/null +++ b/bold/state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/75d9022918aebc68769044c9043bd11a47046e9a443da304857347818e0bb256 @@ -0,0 +1,7 @@ +go test fuzz v1 +string("0X8d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") +uint64(4) +string("0X0A67518C3889d1662998592B444f649ed6518ff579C361f7ff588ABe702B435C") +uint64(8) +string("0X000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") +string("0Xf33B5757f8CC18013A35072A9d7015CeB97f82Af403C903B2e1d118d135831") diff --git a/bold/state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/94136d3efec8e976 b/bold/state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/94136d3efec8e976 new file mode 100644 index 0000000000..449c7258ea --- /dev/null +++ b/bold/state-commitments/prefix-proofs/testdata/fuzz/FuzzVerifyPrefixProof_Go/94136d3efec8e976 @@ -0,0 +1,7 @@ +go test fuzz v1 +string("0X8d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") +uint64(4) +string("0X0A67518C3889d1662998592B444f649ed6518ff579C361f7ff588ABe702B435C") +uint64(8) +string("0X000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008d68097Af7f27A85fefB268e5B1e9B5eef40e63fd0CC59e2B4C077fd79ABddeC") +string("0Xf33B5757f8CC18013A35072A9d7015CeB97f82Af403C903B2e1d118D135831") diff --git a/bold/testing/casttest/safe.go b/bold/testing/casttest/safe.go new file mode 100644 index 0000000000..ffef736815 --- /dev/null +++ b/bold/testing/casttest/safe.go @@ -0,0 +1,53 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package casttest exposes test helper functions to wrap safecast calls. +package casttest + +import ( + "testing" + + "github.com/ccoveille/go-safecast" + "github.com/stretchr/testify/require" +) + +// ToUint wraps safecast.ToUint with a test assertion. +func ToUint[T safecast.Type](t testing.TB, i T) uint { + t.Helper() + u, err := safecast.ToUint(i) + require.NoError(t, err) + return u +} + +// ToUint8 wraps safecast.ToUint8 with a test assertion. +func ToUint8[T safecast.Type](t testing.TB, i T) uint8 { + t.Helper() + u, err := safecast.ToUint8(i) + require.NoError(t, err) + return u +} + +// ToUint64 wraps safecast.ToUint64 with a test assertion. +func ToUint64[T safecast.Type](t testing.TB, i T) uint64 { + t.Helper() + u, err := safecast.ToUint64(i) + require.NoError(t, err) + return u +} + +// ToInt wraps safecast.ToInt with a test assertion. +func ToInt[T safecast.Type](t testing.TB, i T) int { + t.Helper() + u, err := safecast.ToInt(i) + require.NoError(t, err) + return u +} + +// ToInt64 wraps safecast.ToInt64 with a test assertion. +func ToInt64[T safecast.Type](t testing.TB, i T) int64 { + t.Helper() + u, err := safecast.ToInt64(i) + require.NoError(t, err) + return u +} diff --git a/bold/testing/endtoend/README.md b/bold/testing/endtoend/README.md new file mode 100644 index 0000000000..98a60cbb55 --- /dev/null +++ b/bold/testing/endtoend/README.md @@ -0,0 +1,27 @@ +# End to End Testing + +## Host Requirements + +The following tools are required to be on the host system and are not provided by bazel. + +- [foundry](https://github.com/foundry-rs/foundry) -- Specifically the `anvil` tool. + +## Running Tests + +- Running with bazel is recommended, but not required. +- The ANVIL environment variable is required or the test will attempt to guess where anvil is installed. + +Note: The test which depend on running anvil should be run exclusively. Bazel targets that depend +on anvil should include "exclusive-if-local" or "exclusive" tags. + +**Running with Bazel** + +``` +bazel test //testing/endtoend:endtoend_suite --test_env=ANVIL=$(which anvil) --test_output=all +``` + +**Running with Go** + +``` +ANVIL=$(which anvil) go test ./testing/endtoend/... +``` diff --git a/bold/testing/endtoend/backend/anvil_local.go b/bold/testing/endtoend/backend/anvil_local.go new file mode 100644 index 0000000000..e8f4de4e37 --- /dev/null +++ b/bold/testing/endtoend/backend/anvil_local.go @@ -0,0 +1,324 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package backend + +import ( + "context" + "fmt" + "math/big" + "os" + "os/exec" + "path" + "time" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/bold/util" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +var _ Backend = &AnvilLocal{} + +type AnvilLocal struct { + client protocol.ChainBackend + cmd *exec.Cmd + addresses *setup.RollupAddresses + accounts []*bind.TransactOpts +} + +var anvilLocalChainID = big.NewInt(1002) + +// NewAnvilLocal creates an anvil local backend with the following configuration: +// +// anvil --block-time=1 --chain-id=1002 +// +// You must call Start() on the returned backend to start the backend. +func NewAnvilLocal(ctx context.Context) (*AnvilLocal, error) { + a := &AnvilLocal{} + if err := a.loadAccounts(); err != nil { + return nil, err + } + c, err := rpc.DialContext(ctx, "http://localhost:8686") + if err != nil { + return nil, err + } + a.client = util.NewBackendWrapper(ethclient.NewClient(c), rpc.LatestBlockNumber) + return a, nil +} + +// Load accounts from test mnemonic. These are not real accounts. Don't even try to use them. +func (a *AnvilLocal) loadAccounts() error { + accounts := make([]*bind.TransactOpts, 0) + for i := 0; i < len(anvilPrivKeyHexStrings); i++ { + privKeyHex := hexutil.MustDecode(anvilPrivKeyHexStrings[i]) + privKey, err := crypto.ToECDSA(privKeyHex) + if err != nil { + return err + } + txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, anvilLocalChainID) + if err != nil { + return err + } + accounts = append(accounts, txOpts) + } + a.accounts = accounts + return nil +} + +// Start the actual backend and wait for it to be ready to serve requests. +// This process also initializes the anvil blockchain by mining 100 blocks. +func (a *AnvilLocal) Start(ctx context.Context) error { + // If the user has told us where the anvil binary is, we will use that. + // When using bazel, the user can provide --test_env=ANVIL=$(which anvil). + binaryPath, ok := os.LookupEnv("ANVIL") + if !ok { + // Otherwise, we assume it is installed at $HOME/.foundry/bin/anvil + home, err := os.UserHomeDir() + if err != nil { + return errors.Wrap(err, "unable to determine user home directory") + } + binaryPath = path.Join(home, ".foundry/bin/anvil") + } + + args := []string{ + "--block-time=1", + "--chain-id=1002", + "--gas-limit=50000000000", + "--port=8686", + } + + cmd := exec.CommandContext(ctx, binaryPath, args...) // #nosec G204 -- Test only code. + + // Pipe stdout and stderr to test logs directory, if known. + if outputsDir, ok := os.LookupEnv("TEST_UNDECLARED_OUTPUTS_DIR"); ok { + stdoutFileName := path.Join(outputsDir, "anvil_out.log") + stderrFileName := path.Join(outputsDir, "anvil_err.log") + stdout, err := os.Create(stdoutFileName) // #nosec G304 -- Test only code. + if err != nil { + return err + } + stderr, err := os.Create(stderrFileName) // #nosec G304 -- Test only code. + if err != nil { + return err + } + + cmd.Stdout = stdout + cmd.Stderr = stderr + + fmt.Printf("Writing anvil stdout to %s\n", stdoutFileName) + fmt.Printf("Writing anvil stderr to %s\n", stderrFileName) + } else { + fmt.Println("Warning: No environment variable found for TEST_UNDECLARED_OUTPUTS_DIR. Anvil output will not be captured.") + } + + if err := cmd.Start(); err != nil { + return errors.Wrap(err, "could not start anvil") + } + + // Wait until ready to serve a request. + // It should be very fast. + waitCtx, cancel := context.WithTimeout(ctx, 1*time.Second) + defer cancel() + for waitCtx.Err() == nil { + cID, _ := a.client.ChainID(waitCtx) + if cID != nil && cID.Cmp(anvilLocalChainID) == 0 { + break + } + } + + a.cmd = cmd + + go func() { + <-ctx.Done() + a.client.Close() + if err := a.cmd.Process.Kill(); err != nil { + fmt.Printf("Could not kill anvil process: %v\n", err) + } + }() + + return nil +} + +// Client returns the ethclient associated with the backend. +func (a *AnvilLocal) Client() protocol.ChainBackend { + return a.client +} + +func (a *AnvilLocal) Accounts() []*bind.TransactOpts { + return a.accounts +} + +func (a *AnvilLocal) Commit() common.Hash { + return common.Hash{} +} + +func (a *AnvilLocal) DeployRollup(ctx context.Context, opts ...challenge_testing.Opt) (*setup.RollupAddresses, error) { + prod := false + wasmModuleRoot := common.Hash{} + rollupOwner := a.accounts[0].From + loserStakeEscrow := rollupOwner + anyTrustFastConfirmer := common.Address{} + genesisExecutionState := rollupgen.AssertionState{ + GlobalState: rollupgen.GlobalState{}, + MachineStatus: 1, + } + genesisInboxCount := big.NewInt(0) + + stakeToken, tx, tokenBindings, err := mocksgen.DeployTestWETH9( + a.accounts[0], + a.client, + "Weth", + "WETH", + ) + if err != nil { + return nil, errors.Wrap(err, "could not deploy test weth") + } + if waitErr := challenge_testing.WaitForTx(ctx, a.client, tx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transaction") + } + receipt, err := a.client.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx hash") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + + miniStakeValues := []*big.Int{ + big.NewInt(1), + big.NewInt(2), + big.NewInt(3), + } + result, err := setup.DeployFullRollupStack( + ctx, + a.client, + a.accounts[0], + common.Address{}, // Sequencer + challenge_testing.GenerateRollupConfig( + prod, + wasmModuleRoot, + rollupOwner, + anvilLocalChainID, + loserStakeEscrow, + miniStakeValues, + stakeToken, + genesisExecutionState, + genesisInboxCount, + anyTrustFastConfirmer, + opts..., + ), + setup.RollupStackConfig{ + UseMockBridge: false, + UseMockOneStepProver: true, + UseBlobs: true, + MinimumAssertionPeriod: 0, + }, + ) + if err != nil { + return nil, errors.Wrap(err, "could not deploy rollup stack") + } + + value, ok := new(big.Int).SetString("100000", 10) + if !ok { + return nil, errors.New("could not set value") + } + a.accounts[0].Value = value + mintTx, err := tokenBindings.Deposit(a.accounts[0]) + if err != nil { + return nil, errors.Wrap(err, "could not mint test weth") + } + if waitErr := challenge_testing.WaitForTx(ctx, a.client, mintTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transaction") + } + receipt, err = a.client.TransactionReceipt(ctx, mintTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx hash") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt errored") + } + a.accounts[0].Value = big.NewInt(0) + rollupCaller, err := rollupgen.NewRollupUserLogicCaller(result.Rollup, a.client) + if err != nil { + return nil, err + } + chalManagerAddr, err := rollupCaller.ChallengeManager(&bind.CallOpts{}) + if err != nil { + return nil, err + } + seed, ok := new(big.Int).SetString("1000", 10) + if !ok { + return nil, errors.New("could not set big int") + } + for _, acc := range a.accounts[1:] { + transferTx, err := tokenBindings.Transfer(a.accounts[0], acc.From, seed) + if err != nil { + return nil, errors.Wrap(err, "could not approve account") + } + if waitErr := challenge_testing.WaitForTx(ctx, a.client, transferTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transfer transaction") + } + receipt, err := a.client.TransactionReceipt(ctx, transferTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx receipt") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + approveTx, err := tokenBindings.Approve(acc, result.Rollup, value) + if err != nil { + return nil, errors.Wrap(err, "could not approve account") + } + if waitErr := challenge_testing.WaitForTx(ctx, a.client, approveTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for approval transaction") + } + receipt, err = a.client.TransactionReceipt(ctx, approveTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx receipt") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + approveTx, err = tokenBindings.Approve(acc, chalManagerAddr, value) + if err != nil { + return nil, errors.Wrap(err, "could not approve account") + } + if waitErr := challenge_testing.WaitForTx(ctx, a.client, approveTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for approval transaction") + } + receipt, err = a.client.TransactionReceipt(ctx, approveTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx receipt") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + } + + a.addresses = result + + return result, a.MineBlocks(ctx, 100) // At least 100 blocks should be mined for a challenge to be possible. +} + +// MineBlocks will call anvil to instantly mine n blocks. +func (a *AnvilLocal) MineBlocks(ctx context.Context, n uint64) error { + return a.client.Client().CallContext(ctx, nil, "anvil_mine", hexutil.EncodeUint64(n)) +} + +func (a *AnvilLocal) ContractAddresses() *setup.RollupAddresses { + return a.addresses +} diff --git a/bold/testing/endtoend/backend/anvil_local_test.go b/bold/testing/endtoend/backend/anvil_local_test.go new file mode 100644 index 0000000000..a6c892b519 --- /dev/null +++ b/bold/testing/endtoend/backend/anvil_local_test.go @@ -0,0 +1,23 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package backend + +import ( + "context" + "testing" +) + +func TestLocalAnvilLoadAccounts(t *testing.T) { + a, err := NewAnvilLocal(context.Background()) + if err != nil { + t.Fatal(err) + } + if err := a.loadAccounts(); err != nil { + t.Fatal(err) + } + if len(a.accounts) == 0 { + t.Error("No accounts generated") + } +} diff --git a/bold/testing/endtoend/backend/anvil_priv_keys.go b/bold/testing/endtoend/backend/anvil_priv_keys.go new file mode 100644 index 0000000000..7e564f3db2 --- /dev/null +++ b/bold/testing/endtoend/backend/anvil_priv_keys.go @@ -0,0 +1,49 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package backend + +// From: https://github.com/foundry-rs/foundry/tree/master/crates/anvil +var anvilPrivKeyHexStrings = []string{ + "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", + "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d", + "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", + "0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6", + "0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a", + "0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba", + "0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e", + "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356", + "0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97", + "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6", + "0xf214f2b2cd398c806f84e317254e0f0b801d0643303237d97a22a48e01628897", + "0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82", + "0xa267530f49f8280200edf313ee7af6b827f2a8bce2897751d06a843f644967b1", + "0x47c99abed3324a2707c28affff1267e45918ec8c3f20b8aa892e8b065d2942dd", + "0xc526ee95bf44d8fc405a158bb884d9d1238d99f0612e9f33d006bb0789009aaa", + "0x8166f546bab6da521a8369cab06c5d2b9e46670292d85c875ee9ec20e84ffb61", + "0xea6c44ac03bff858b476bba40716402b03e41b8e97e276d1baec7c37d42484a0", + "0x689af8efa8c651a91ad287602527f3af2fe9f6501a7ac4b061667b5a93e037fd", + "0xde9be858da4a475276426320d5e9262ecfc3ba460bfac56360bfa6c4c28b4ee0", + "0xdf57089febbacf7ba0bc227dafbffa9fc08a93fdc68e1e42411a14efcf23656e", + "0xeaa861a9a01391ed3d587d8a5a84ca56ee277629a8b02c22093a419bf240e65d", + "0xc511b2aa70776d4ff1d376e8537903dae36896132c90b91d52c1dfbae267cd8b", + "0x224b7eb7449992aac96d631d9677f7bf5888245eef6d6eeda31e62d2f29a83e4", + "0x4624e0802698b9769f5bdb260a3777fbd4941ad2901f5966b854f953497eec1b", + "0x375ad145df13ed97f8ca8e27bb21ebf2a3819e9e0a06509a812db377e533def7", + "0x18743e59419b01d1d846d97ea070b5a3368a3e7f6f0242cf497e1baac6972427", + "0xe383b226df7c8282489889170b0f68f66af6459261f4833a781acd0804fafe7a", + "0xf3a6b71b94f5cd909fb2dbb287da47badaa6d8bcdc45d595e2884835d8749001", + "0x4e249d317253b9641e477aba8dd5d8f1f7cf5250a5acadd1229693e262720a19", + "0x233c86e887ac435d7f7dc64979d7758d69320906a0d340d2b6518b0fd20aa998", + "0x85a74ca11529e215137ccffd9c95b2c72c5fb0295c973eb21032e823329b3d2d", + "0xac8698a440d33b866b6ffe8775621ce1a4e6ebd04ab7980deb97b3d997fc64fb", + "0xf076539fbce50f0513c488f32bf81524d30ca7a29f400d68378cc5b1b17bc8f2", + "0x5544b8b2010dbdbef382d254802d856629156aba578f453a76af01b81a80104e", + "0x47003709a0a9a4431899d4e014c1fd01c5aad19e873172538a02370a119bae11", + "0x9644b39377553a920edc79a275f45fa5399cbcf030972f771d0bca8097f9aad3", + "0xcaa7b4a2d30d1d565716199f068f69ba5df586cf32ce396744858924fdf827f0", + "0xfc5a028670e1b6381ea876dd444d3faaee96cffae6db8d93ca6141130259247c", + "0x5b92c5fe82d4fabee0bc6d95b4b8a3f9680a0ed7801f631035528f32c9eb2ad5", + "0xb68ac4aa2137dd31fd0732436d8e59e959bb62b4db2e6107b15f594caf0f405f", +} diff --git a/bold/testing/endtoend/backend/backend.go b/bold/testing/endtoend/backend/backend.go new file mode 100644 index 0000000000..d5a1c1a5f9 --- /dev/null +++ b/bold/testing/endtoend/backend/backend.go @@ -0,0 +1,31 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package backend + +import ( + "context" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/setup" +) + +type Backend interface { + // Start sets up the backend and waits until the process is in a ready state. + Start(ctx context.Context) error + // Client returns the backend's client. + Client() protocol.ChainBackend + // Accounts managed by the backend. + Accounts() []*bind.TransactOpts + // DeployRollup contract, if not already deployed. + DeployRollup(ctx context.Context, opts ...challenge_testing.Opt) (*setup.RollupAddresses, error) + // Contract addresses relevant to the challenge protocol. + ContractAddresses() *setup.RollupAddresses + // Commit a tx to the backend, if possible (simulated backend requires this) + Commit() common.Hash +} diff --git a/bold/testing/endtoend/backend/simulated.go b/bold/testing/endtoend/backend/simulated.go new file mode 100644 index 0000000000..5319a3ce49 --- /dev/null +++ b/bold/testing/endtoend/backend/simulated.go @@ -0,0 +1,78 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package backend + +import ( + "context" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/setup" +) + +var _ Backend = &LocalSimulatedBackend{} + +type LocalSimulatedBackend struct { + blockTime time.Duration + setup *setup.ChainSetup +} + +func (l *LocalSimulatedBackend) Start(ctx context.Context) error { + // Advance blocks in the background. + go func() { + ticker := time.NewTicker(l.blockTime) + defer ticker.Stop() + for { + select { + case <-ticker.C: + l.setup.Backend.Commit() + case <-ctx.Done(): + return + } + } + }() + return nil +} + +func (l *LocalSimulatedBackend) Stop(ctx context.Context) error { + return nil +} + +func (l *LocalSimulatedBackend) Client() protocol.ChainBackend { + return l.setup.Backend +} + +func (l *LocalSimulatedBackend) Commit() common.Hash { + return l.setup.Backend.Commit() +} + +func (l *LocalSimulatedBackend) Accounts() []*bind.TransactOpts { + accs := make([]*bind.TransactOpts, len(l.setup.Accounts)) + for i := 0; i < len(l.setup.Accounts); i++ { + accs[i] = l.setup.Accounts[i].TxOpts + } + return accs +} + +func (l *LocalSimulatedBackend) ContractAddresses() *setup.RollupAddresses { + return l.setup.Addrs +} + +func (l *LocalSimulatedBackend) DeployRollup(_ context.Context, _ ...challenge_testing.Opt) (*setup.RollupAddresses, error) { + // No-op, as the sim backend deploys the rollup on initialization. + return l.setup.Addrs, nil +} + +func NewSimulated(blockTime time.Duration, opts ...setup.Opt) (*LocalSimulatedBackend, error) { + setup, err := setup.ChainsWithEdgeChallengeManager(opts...) + if err != nil { + return nil, err + } + return &LocalSimulatedBackend{blockTime: blockTime, setup: setup}, nil +} diff --git a/bold/testing/endtoend/e2e_crash_test.go b/bold/testing/endtoend/e2e_crash_test.go new file mode 100644 index 0000000000..6625be8f0a --- /dev/null +++ b/bold/testing/endtoend/e2e_crash_test.go @@ -0,0 +1,314 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package endtoend + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + cm "github.com/offchainlabs/bold/challenge-manager" + "github.com/offchainlabs/bold/challenge-manager/types" + retry "github.com/offchainlabs/bold/runtime" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/endtoend/backend" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +// This test ensures a challenge can complete even if the honest validator crashes mid-challenge. +// We cancel the honest validator's context after it opens the first subchallenge and prove that it +// can restart and carry things out to confirm the honest, claimed assertion in the challenge. +func TestEndToEnd_HonestValidatorCrashes(t *testing.T) { + t.Skip("Flakey in CI, needs investigation") + neutralCtx, neutralCancel := context.WithCancel(context.Background()) + defer neutralCancel() + evilCtx, evilCancel := context.WithCancel(context.Background()) + defer evilCancel() + honestCtx, honestCancel := context.WithCancel(context.Background()) + defer honestCancel() + + protocolCfg := defaultProtocolParams() + protocolCfg.challengePeriodBlocks = 40 + timeCfg := defaultTimeParams() + timeCfg.blockTime = time.Second + inboxCfg := defaultInboxParams() + + challengeTestingOpts := []challenge_testing.Opt{ + challenge_testing.WithConfirmPeriodBlocks(protocolCfg.challengePeriodBlocks), + challenge_testing.WithLayerZeroHeights(&protocolCfg.layerZeroHeights), + challenge_testing.WithNumBigStepLevels(protocolCfg.numBigStepLevels), + } + deployOpts := []setup.Opt{ + setup.WithMockBridge(), + setup.WithMockOneStepProver(), + setup.WithNumAccounts(5), + setup.WithChallengeTestingOpts(challengeTestingOpts...), + } + + simBackend, err := backend.NewSimulated(timeCfg.blockTime, deployOpts...) + require.NoError(t, err) + bk := simBackend + + rollupAddr, err := bk.DeployRollup(neutralCtx, challengeTestingOpts...) + require.NoError(t, err) + + require.NoError(t, bk.Start(neutralCtx)) + + accounts := bk.Accounts() + bk.Commit() + + rollupUserBindings, err := rollupgen.NewRollupUserLogic(rollupAddr.Rollup, bk.Client()) + require.NoError(t, err) + bridgeAddr, err := rollupUserBindings.Bridge(&bind.CallOpts{}) + require.NoError(t, err) + dataHash := common.Hash{1} + enqueueSequencerMessageAsExecutor( + t, accounts[0], rollupAddr.UpgradeExecutor, bk.Client(), bridgeAddr, seqMessage{ + dataHash: dataHash, + afterDelayedMessagesRead: big.NewInt(1), + prevMessageCount: big.NewInt(1), + newMessageCount: big.NewInt(2), + }, + ) + + baseStateManagerOpts := []statemanager.Opt{ + statemanager.WithNumBatchesRead(inboxCfg.numBatchesPosted), + statemanager.WithLayerZeroHeights(&protocolCfg.layerZeroHeights, protocolCfg.numBigStepLevels), + } + honestStateManager, err := statemanager.NewForSimpleMachine(t, baseStateManagerOpts...) + require.NoError(t, err) + + shp := &simpleHeaderProvider{b: bk, chs: make([]chan<- *gethtypes.Header, 0)} + shp.Start(neutralCtx) + + baseStackOpts := []cm.StackOpt{ + cm.StackWithMode(types.MakeMode), + cm.StackWithPollingInterval(timeCfg.assertionScanningInterval), + cm.StackWithPostingInterval(timeCfg.assertionPostingInterval), + cm.StackWithAverageBlockCreationTime(timeCfg.blockTime), + cm.StackWithConfirmationInterval(timeCfg.assertionConfirmationAttemptInterval), + cm.StackWithMinimumGapToParentAssertion(0), + cm.StackWithHeaderProvider(shp), + } + + name := "honest" + txOpts := accounts[1] + //nolint:gocritic + honestOpts := append( + baseStackOpts, + cm.StackWithName(name), + ) + honestChain := setupAssertionChain(t, honestCtx, bk.Client(), rollupAddr.Rollup, txOpts) + honestManager, err := cm.NewChallengeStack(honestChain, honestStateManager, honestOpts...) + require.NoError(t, err) + + totalOpcodes := totalWasmOpcodes(&protocolCfg.layerZeroHeights, protocolCfg.numBigStepLevels) + t.Logf("Total wasm opcodes in test: %d", totalOpcodes) + + assertionDivergenceHeight := uint64(1) + assertionBlockHeightDifference := int64(1) + + machineDivergenceStep := uint64(1) + //nolint:gocritic + evilStateManagerOpts := append( + baseStateManagerOpts, + statemanager.WithMachineDivergenceStep(machineDivergenceStep), + statemanager.WithBlockDivergenceHeight(assertionDivergenceHeight), + statemanager.WithDivergentBlockHeightOffset(assertionBlockHeightDifference), + ) + evilStateManager, err := statemanager.NewForSimpleMachine(t, evilStateManagerOpts...) + require.NoError(t, err) + + // Honest validator has index 1 in the accounts slice, as 0 is admin, so + // evil ones should start at 2. + evilTxOpts := accounts[2] + //nolint:gocritic + evilOpts := append( + baseStackOpts, + cm.StackWithName("evil"), + ) + evilChain := setupAssertionChain(t, evilCtx, bk.Client(), rollupAddr.Rollup, evilTxOpts) + evilManager, err := cm.NewChallengeStack(evilChain, evilStateManager, evilOpts...) + require.NoError(t, err) + + chalManagerAddr := honestChain.SpecChallengeManager().Address() + cmBindings, err := challengeV2gen.NewEdgeChallengeManager(chalManagerAddr, bk.Client()) + require.NoError(t, err) + + honestManager.Start(honestCtx) + evilManager.Start(evilCtx) + + t.Run("crashes mid-challenge and recovers to complete it", func(t *testing.T) { + // We will listen for the first subchallenge edge created by the honest validator to appear, and then + // we will cancel the honest validator context. We will then wait for a bit, then restart the honest + // validator and we should expect the honest assertion is still confirmed by time. + // No more edges will be added here, so we then scrape all the edges added to the challenge. + // We await until all the essential root edges are also confirmed by time. + chainId, err2 := bk.Client().ChainID(neutralCtx) + require.NoError(t, err2) + var foundSubchalEdge bool + for neutralCtx.Err() == nil && !foundSubchalEdge { + it, err3 := cmBindings.FilterEdgeAdded(nil, nil, nil, nil) + require.NoError(t, err3) + for it.Next() { + txHash := it.Event.Raw.TxHash + tx, _, err3 := bk.Client().TransactionByHash(neutralCtx, txHash) + require.NoError(t, err3) + sender, err3 := gethtypes.Sender(gethtypes.NewCancunSigner(chainId), tx) + require.NoError(t, err3) + if sender != txOpts.From { + continue + } + if it.Event.Level > 0 { + foundSubchalEdge = true + t.Log("Honest validator made a subchallenge") + break // The honest validator made a subchallenge. + } + } + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + } + // Cancel the honest context. + honestCancel() + t.Log("Honest context has been canceled") + + // We then restart the honest validator after a few seconds of wait time. + time.Sleep(time.Second * 3) + + honestCtx, honestCancel = context.WithCancel(context.Background()) + honestChain := setupAssertionChain(t, honestCtx, bk.Client(), rollupAddr.Rollup, txOpts) + honestManager, err := cm.NewChallengeStack(honestChain, honestStateManager, honestOpts...) + require.NoError(t, err) + + honestManager.Start(honestCtx) + + rc, err2 := rollupgen.NewRollupCore(rollupAddr.Rollup, bk.Client()) + require.NoError(t, err2) + + // Wait until a challenged assertion is confirmed by time. + var confirmed bool + for neutralCtx.Err() == nil && !confirmed { + var i *rollupgen.RollupCoreAssertionConfirmedIterator + i, err = retry.UntilSucceeds(neutralCtx, func() (*rollupgen.RollupCoreAssertionConfirmedIterator, error) { + return rc.FilterAssertionConfirmed(nil, nil) + }) + require.NoError(t, err) + for i.Next() { + creationInfo, err2 := evilChain.ReadAssertionCreationInfo(evilCtx, protocol.AssertionHash{Hash: i.Event.AssertionHash}) + require.NoError(t, err2) + + var parent rollupgen.AssertionNode + parent, err = retry.UntilSucceeds(neutralCtx, func() (rollupgen.AssertionNode, error) { + return rc.GetAssertion(&bind.CallOpts{Context: neutralCtx}, creationInfo.ParentAssertionHash.Hash) + }) + require.NoError(t, err) + + tx, _, err2 := bk.Client().TransactionByHash(neutralCtx, creationInfo.TransactionHash) + require.NoError(t, err2) + sender, err2 := gethtypes.Sender(gethtypes.NewCancunSigner(chainId), tx) + require.NoError(t, err2) + honestConfirmed := sender == txOpts.From + + isChallengeChild := parent.FirstChildBlock > 0 && parent.SecondChildBlock > 0 + if !isChallengeChild { + // Assertion must be a challenge child. + continue + } + // We expect the honest party to have confirmed it. + if !honestConfirmed { + t.Fatal("Evil party confirmed the assertion by challenge win") + } + confirmed = true + break + } + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + } + // Once the honest, claimed assertion in the challenge is confirmed by time, we + // then continue the test. + t.Log("Assertion was confirmed by time") + honestCancel() + }) + // This test ensures that an honest validator can crash after a challenge has completed, can resync + // the completed challenge and continue playing the game until all essential edges are confirmed. + // This is to ensure that even if a challenge is completed, we can still resync it and continue + // playing for the sake of refunding honest stakes. + t.Run( + "crashes once challenged assertion is confirmed and restarts to confirm essential edges", + func(t *testing.T) { + // We restart the honest validator after a few seconds of wait time. + time.Sleep(time.Second * 5) + + ctx := context.Background() + honestChain := setupAssertionChain(t, ctx, bk.Client(), rollupAddr.Rollup, txOpts) + honestManager, err := cm.NewChallengeStack(honestChain, honestStateManager, honestOpts...) + require.NoError(t, err) + + honestManager.Start(ctx) + + t.Log("Restarted honest validator to continue playing game after challenge has finished") + + // We then expect that all essential root edges created by the honest validator are confirmed by time. + // Scrape all the honest edges onchain (the ones made by the honest address). + // Check if the edges that have claim id != None are confirmed (those are essential root edges) + // and also check one step edges from honest party are confirmed. + honestEssentialRootIds := make(map[common.Hash]bool, 0) + chainId, err := bk.Client().ChainID(neutralCtx) + require.NoError(t, err) + it, err := cmBindings.FilterEdgeAdded(nil, nil, nil, nil) + require.NoError(t, err) + for it.Next() { + txHash := it.Event.Raw.TxHash + tx, _, err2 := bk.Client().TransactionByHash(neutralCtx, txHash) + require.NoError(t, err2) + sender, err2 := gethtypes.Sender(gethtypes.NewCancunSigner(chainId), tx) + require.NoError(t, err2) + if sender != txOpts.From { + continue + } + // Skip edges that are not essential roots (skip the top-level edge). + if it.Event.ClaimId == (common.Hash{}) || it.Event.Level == 0 { + continue + } + honestEssentialRootIds[it.Event.EdgeId] = false + } + // Wait until all of the honest essential root ids are confirmed. + startBlk, err := bk.Client().HeaderU64(neutralCtx) + require.NoError(t, err) + chalPeriodBlocks, err := cmBindings.ChallengePeriodBlocks(&bind.CallOpts{}) + require.NoError(t, err) + totalPeriod := chalPeriodBlocks * uint64(len(honestEssentialRootIds)) + confirmedCount := 0 + _ = totalPeriod + _ = startBlk + for confirmedCount < len(honestEssentialRootIds) { + latestBlk, err2 := bk.Client().HeaderU64(neutralCtx) + require.NoError(t, err2) + numBlocksElapsed := latestBlk - startBlk + if numBlocksElapsed > totalPeriod { + t.Fatalf("%d blocks have passed without essential edges being confirmed", numBlocksElapsed) + } + for k, markedConfirmed := range honestEssentialRootIds { + edge, err2 := cmBindings.GetEdge(&bind.CallOpts{}, k) + require.NoError(t, err2) + if edge.Status == 1 && !markedConfirmed { + confirmedCount += 1 + honestEssentialRootIds[k] = true + t.Logf("Confirmed %d honest essential edges, got edge at level %d", confirmedCount, edge.Level) + } + } + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + } + }) +} diff --git a/bold/testing/endtoend/e2e_delegated_staking_test.go b/bold/testing/endtoend/e2e_delegated_staking_test.go new file mode 100644 index 0000000000..31dbe5bda1 --- /dev/null +++ b/bold/testing/endtoend/e2e_delegated_staking_test.go @@ -0,0 +1,274 @@ +package endtoend + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + cm "github.com/offchainlabs/bold/challenge-manager" + "github.com/offchainlabs/bold/challenge-manager/types" + retry "github.com/offchainlabs/bold/runtime" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/endtoend/backend" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func TestEndToEnd_DelegatedStaking(t *testing.T) { + neutralCtx, neutralCancel := context.WithCancel(context.Background()) + defer neutralCancel() + evilCtx, evilCancel := context.WithCancel(context.Background()) + defer evilCancel() + honestCtx, honestCancel := context.WithCancel(context.Background()) + defer honestCancel() + + protocolCfg := defaultProtocolParams() + protocolCfg.challengePeriodBlocks = 25 + timeCfg := defaultTimeParams() + timeCfg.blockTime = time.Second + inboxCfg := defaultInboxParams() + + challengeTestingOpts := []challenge_testing.Opt{ + challenge_testing.WithConfirmPeriodBlocks(protocolCfg.challengePeriodBlocks), + challenge_testing.WithLayerZeroHeights(&protocolCfg.layerZeroHeights), + challenge_testing.WithNumBigStepLevels(protocolCfg.numBigStepLevels), + } + deployOpts := []setup.Opt{ + setup.WithMockBridge(), + setup.WithMockOneStepProver(), + setup.WithNumAccounts(10), + setup.WithChallengeTestingOpts(challengeTestingOpts...), + } + + simBackend, err := backend.NewSimulated(timeCfg.blockTime, deployOpts...) + require.NoError(t, err) + bk := simBackend + + rollupAddr, err := bk.DeployRollup(neutralCtx, challengeTestingOpts...) + require.NoError(t, err) + + require.NoError(t, bk.Start(neutralCtx)) + + accounts := bk.Accounts() + bk.Commit() + + rollupUserBindings, err := rollupgen.NewRollupUserLogic(rollupAddr.Rollup, bk.Client()) + require.NoError(t, err) + bridgeAddr, err := rollupUserBindings.Bridge(&bind.CallOpts{}) + require.NoError(t, err) + dataHash := common.Hash{1} + enqueueSequencerMessageAsExecutor( + t, accounts[0], rollupAddr.UpgradeExecutor, bk.Client(), bridgeAddr, seqMessage{ + dataHash: dataHash, + afterDelayedMessagesRead: big.NewInt(1), + prevMessageCount: big.NewInt(1), + newMessageCount: big.NewInt(2), + }, + ) + + baseStateManagerOpts := []statemanager.Opt{ + statemanager.WithNumBatchesRead(inboxCfg.numBatchesPosted), + statemanager.WithLayerZeroHeights(&protocolCfg.layerZeroHeights, protocolCfg.numBigStepLevels), + } + honestStateManager, err := statemanager.NewForSimpleMachine(t, baseStateManagerOpts...) + require.NoError(t, err) + + shp := &simpleHeaderProvider{b: bk, chs: make([]chan<- *gethtypes.Header, 0)} + shp.Start(neutralCtx) + + baseStackOpts := []cm.StackOpt{ + cm.StackWithMode(types.MakeMode), + cm.StackWithPollingInterval(timeCfg.assertionScanningInterval), + cm.StackWithPostingInterval(timeCfg.assertionPostingInterval), + cm.StackWithAverageBlockCreationTime(timeCfg.blockTime), + cm.StackWithConfirmationInterval(timeCfg.assertionConfirmationAttemptInterval), + cm.StackWithMinimumGapToParentAssertion(0), + cm.StackWithHeaderProvider(shp), + cm.StackWithDelegatedStaking(), // Enable delegated staking. + cm.StackWithoutAutoDeposit(), + } + + name := "honest" + + // Ensure the honest validator is a generated account that has no erc20 token balance, + // but has some ETH to pay for gas costs of BoLD. We ensure that the honest validator + // is not initially staked, and that the actual address that will be funding the honest + // validator has enough funds. + fundsCustodianOpts := accounts[1] // The 1st and 2nd accounts should be the funds' custodians. + evilFundsCustodianOpts := accounts[2] + honestTxOpts := accounts[len(accounts)-1] + evilTxOpts := accounts[len(accounts)-2] + + //nolint:gocritic + honestOpts := append( + baseStackOpts, + cm.StackWithName(name), + ) + // Ensure the funds custodian is the withdrawal address for the honest validator. + honestChain := setupAssertionChain( + t, + honestCtx, + bk.Client(), + rollupAddr.Rollup, + honestTxOpts, + solimpl.WithCustomWithdrawalAddress(fundsCustodianOpts.From), + ) + + machineDivergenceStep := uint64(1) + assertionDivergenceHeight := uint64(1) + assertionBlockHeightDifference := int64(1) + + //nolint:gocritic + evilStateManagerOpts := append( + baseStateManagerOpts, + statemanager.WithMachineDivergenceStep(machineDivergenceStep), + statemanager.WithBlockDivergenceHeight(assertionDivergenceHeight), + statemanager.WithDivergentBlockHeightOffset(assertionBlockHeightDifference), + ) + evilStateManager, err := statemanager.NewForSimpleMachine(t, evilStateManagerOpts...) + require.NoError(t, err) + + //nolint:gocritic + evilOpts := append( + baseStackOpts, + cm.StackWithName("evil"), + ) + evilChain := setupAssertionChain( + t, + evilCtx, + bk.Client(), + rollupAddr.Rollup, + evilTxOpts, + solimpl.WithCustomWithdrawalAddress(evilFundsCustodianOpts.From), + ) + + // Ensure that both validators are not yet staked. + isStaked, err := honestChain.IsStaked(honestCtx) + require.NoError(t, err) + require.False(t, isStaked) + isStaked, err = evilChain.IsStaked(evilCtx) + require.NoError(t, err) + require.False(t, isStaked) + + chalManagerAddr := honestChain.SpecChallengeManager().Address() + cmBindings, err := challengeV2gen.NewEdgeChallengeManager(chalManagerAddr, bk.Client()) + require.NoError(t, err) + stakeToken, err := cmBindings.StakeToken(&bind.CallOpts{}) + require.NoError(t, err) + requiredStake, err := honestChain.RollupCore().BaseStake(&bind.CallOpts{}) + require.NoError(t, err) + + tokenBindings, err := mocksgen.NewTestWETH9(stakeToken, bk.Client()) + require.NoError(t, err) + + balCustodian, err := tokenBindings.BalanceOf(&bind.CallOpts{}, fundsCustodianOpts.From) + require.NoError(t, err) + require.True(t, balCustodian.Cmp(requiredStake) >= 0) // Ensure funds custodian DOES have enough stake token balance. + balEvilCustodian, err := tokenBindings.BalanceOf(&bind.CallOpts{}, evilFundsCustodianOpts.From) + require.NoError(t, err) + require.True(t, balEvilCustodian.Cmp(requiredStake) >= 0) // Ensure funds custodian DOES have enough stake token balance. + + honestManager, err := cm.NewChallengeStack(honestChain, honestStateManager, honestOpts...) + require.NoError(t, err) + _ = honestManager + + evilManager, err := cm.NewChallengeStack(evilChain, evilStateManager, evilOpts...) + require.NoError(t, err) + _ = evilManager + + honestManager.Start(honestCtx) + evilManager.Start(evilCtx) + + // Next, the custodians add deposits. + // Waits until the validators are staked with a value of 0 before adding the deposit. + var isStakedWithZero bool + for honestCtx.Err() == nil && !isStakedWithZero { + isStaked, err = honestChain.IsStaked(honestCtx) + require.NoError(t, err) + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + if isStaked { + isStakedWithZero = true + } + } + isStakedWithZero = false + for evilCtx.Err() == nil && !isStakedWithZero { + isStaked, err = evilChain.IsStaked(evilCtx) + require.NoError(t, err) + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + if isStaked { + isStakedWithZero = true + } + } + + // Now, adds the deposit. + rollupUserLogic, err := rollupgen.NewRollupUserLogic(rollupAddr.Rollup, bk.Client()) + require.NoError(t, err) + tx, err := rollupUserLogic.AddToDeposit(fundsCustodianOpts, honestTxOpts.From, fundsCustodianOpts.From, balCustodian) + require.NoError(t, err) + _, err = bind.WaitMined(honestCtx, bk.Client(), tx) + require.NoError(t, err) + + tx, err = rollupUserLogic.AddToDeposit(evilFundsCustodianOpts, evilTxOpts.From, evilFundsCustodianOpts.From, balEvilCustodian) + require.NoError(t, err) + _, err = bind.WaitMined(evilCtx, bk.Client(), tx) + require.NoError(t, err) + + t.Log("Delegated validators now have a deposit balance") + + t.Run("expects honest validator to win challenge", func(t *testing.T) { + chainId, err := bk.Client().ChainID(honestCtx) + require.NoError(t, err) + // Wait until a challenged assertion is confirmed by time. + var confirmed bool + for neutralCtx.Err() == nil && !confirmed { + var i *rollupgen.RollupCoreAssertionConfirmedIterator + i, err = retry.UntilSucceeds(neutralCtx, func() (*rollupgen.RollupCoreAssertionConfirmedIterator, error) { + return honestChain.RollupCore().FilterAssertionConfirmed(nil, nil) + }) + require.NoError(t, err) + for i.Next() { + creationInfo, err2 := evilChain.ReadAssertionCreationInfo(evilCtx, protocol.AssertionHash{Hash: i.Event.AssertionHash}) + require.NoError(t, err2) + + var parent rollupgen.AssertionNode + parent, err = retry.UntilSucceeds(neutralCtx, func() (rollupgen.AssertionNode, error) { + return honestChain.RollupCore().GetAssertion(&bind.CallOpts{Context: neutralCtx}, creationInfo.ParentAssertionHash.Hash) + }) + require.NoError(t, err) + + tx, _, err2 := bk.Client().TransactionByHash(neutralCtx, creationInfo.TransactionHash) + require.NoError(t, err2) + sender, err2 := gethtypes.Sender(gethtypes.NewCancunSigner(chainId), tx) + require.NoError(t, err2) + honestConfirmed := sender == honestTxOpts.From + + isChallengeChild := parent.FirstChildBlock > 0 && parent.SecondChildBlock > 0 + if !isChallengeChild { + // Assertion must be a challenge child. + continue + } + // We expect the honest party to have confirmed it. + if !honestConfirmed { + t.Fatal("Evil party confirmed the assertion by challenge win") + } + confirmed = true + break + } + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + } + // Once the honest, claimed assertion in the challenge is confirmed by time, we win the test. + t.Log("Assertion was confirmed by time") + }) +} diff --git a/bold/testing/endtoend/e2e_test.go b/bold/testing/endtoend/e2e_test.go new file mode 100644 index 0000000000..6c75826d25 --- /dev/null +++ b/bold/testing/endtoend/e2e_test.go @@ -0,0 +1,360 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package endtoend + +import ( + "context" + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + cm "github.com/offchainlabs/bold/challenge-manager" + "github.com/offchainlabs/bold/challenge-manager/types" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/endtoend/backend" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +type backendKind uint8 + +const ( + simulated backendKind = iota + anvil +) + +func (b backendKind) String() string { + switch b { + case simulated: + return "simulated" + case anvil: + return "anvil" + default: + return "unknown" + } +} + +// Defines the configuration for an end-to-end test, with different +// parameters for the various parts of the system. +type e2eConfig struct { + backend backendKind + protocol protocolParams + timings timeParams + inbox inboxParams + actors actorParams + expectations []expect +} + +// Defines parameters related to the actors participating in the test. +type actorParams struct { + numEvilValidators uint64 +} + +// Configures intervals related to timings in the system. +type timeParams struct { + blockTime time.Duration + assertionPostingInterval time.Duration + assertionScanningInterval time.Duration + assertionConfirmationAttemptInterval time.Duration +} + +func defaultTimeParams() timeParams { + return timeParams{ + // Fast block time. + blockTime: time.Second, + // Go very fast. + assertionPostingInterval: time.Hour, + assertionScanningInterval: time.Second, + assertionConfirmationAttemptInterval: time.Second, + } +} + +// Configures info about the state of the Arbitrum Inbox when a test runs, +// useful to set up things such as the number of batches posted. +type inboxParams struct { + numBatchesPosted uint64 +} + +func defaultInboxParams() inboxParams { + return inboxParams{ + numBatchesPosted: 5, + } +} + +// Defines constants and other parameters related to the protocol itself, +// such as the number of challenge levels or the confirmation period. +type protocolParams struct { + numBigStepLevels uint8 + challengePeriodBlocks uint64 + layerZeroHeights protocol.LayerZeroHeights +} + +func defaultProtocolParams() protocolParams { + return protocolParams{ + numBigStepLevels: 1, + challengePeriodBlocks: 25, + layerZeroHeights: protocol.LayerZeroHeights{ + BlockChallengeHeight: 1 << 4, + BigStepChallengeHeight: 1 << 4, + SmallStepChallengeHeight: 1 << 4, + }, + } +} + +func TestEndToEnd_SmokeTest(t *testing.T) { + timeCfg := defaultTimeParams() + timeCfg.blockTime = time.Second + runEndToEndTest(t, &e2eConfig{ + backend: simulated, + protocol: defaultProtocolParams(), + inbox: defaultInboxParams(), + actors: actorParams{ + numEvilValidators: 1, + }, + timings: timeCfg, + expectations: []expect{ + expectChallengeWinWithAllHonestEssentialEdgesConfirmed, + }, + }) +} + +func TestEndToEnd_TwoEvilValidators(t *testing.T) { + protocolCfg := defaultProtocolParams() + timeCfg := defaultTimeParams() + timeCfg.assertionPostingInterval = time.Hour + runEndToEndTest(t, &e2eConfig{ + backend: simulated, + protocol: protocolCfg, + inbox: defaultInboxParams(), + actors: actorParams{ + numEvilValidators: 2, + }, + timings: timeCfg, + expectations: []expect{ + expectChallengeWinWithAllHonestEssentialEdgesConfirmed, + }, + }) +} + +func TestEndToEnd_ManyEvilValidators(t *testing.T) { + protocolCfg := defaultProtocolParams() + timeCfg := defaultTimeParams() + timeCfg.assertionPostingInterval = time.Hour + protocolCfg.challengePeriodBlocks = 50 + runEndToEndTest(t, &e2eConfig{ + backend: simulated, + protocol: protocolCfg, + inbox: defaultInboxParams(), + actors: actorParams{ + numEvilValidators: 5, + }, + timings: timeCfg, + expectations: []expect{ + expectChallengeWinWithAllHonestEssentialEdgesConfirmed, + }, + }) +} + +func runEndToEndTest(t *testing.T, cfg *e2eConfig) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Validators include a chain admin, a single honest validator, and any + // number of evil entities. + totalValidators := cfg.actors.numEvilValidators + 2 + + challengeTestingOpts := []challenge_testing.Opt{ + challenge_testing.WithConfirmPeriodBlocks(cfg.protocol.challengePeriodBlocks), + challenge_testing.WithLayerZeroHeights(&cfg.protocol.layerZeroHeights), + challenge_testing.WithNumBigStepLevels(cfg.protocol.numBigStepLevels), + } + deployOpts := []setup.Opt{ + setup.WithMockBridge(), + setup.WithMockOneStepProver(), + setup.WithNumAccounts(totalValidators), + setup.WithChallengeTestingOpts(challengeTestingOpts...), + } + + var bk backend.Backend + switch cfg.backend { + case simulated: + simBackend, err := backend.NewSimulated(cfg.timings.blockTime, deployOpts...) + require.NoError(t, err) + bk = simBackend + case anvil: + anvilBackend, err := backend.NewAnvilLocal(ctx) + require.NoError(t, err) + bk = anvilBackend + default: + t.Fatalf("Backend kind for e2e test not supported: %s", cfg.backend) + } + + rollupAddr, err := bk.DeployRollup(ctx, challengeTestingOpts...) + require.NoError(t, err) + + require.NoError(t, bk.Start(ctx)) + + accounts := bk.Accounts() + bk.Commit() + + rollupUserBindings, err := rollupgen.NewRollupUserLogic(rollupAddr.Rollup, bk.Client()) + require.NoError(t, err) + bridgeAddr, err := rollupUserBindings.Bridge(&bind.CallOpts{}) + require.NoError(t, err) + dataHash := common.Hash{1} + enqueueSequencerMessageAsExecutor( + t, accounts[0], rollupAddr.UpgradeExecutor, bk.Client(), bridgeAddr, seqMessage{ + dataHash: dataHash, + afterDelayedMessagesRead: big.NewInt(1), + prevMessageCount: big.NewInt(1), + newMessageCount: big.NewInt(2), + }, + ) + + baseStateManagerOpts := []statemanager.Opt{ + statemanager.WithNumBatchesRead(cfg.inbox.numBatchesPosted), + statemanager.WithLayerZeroHeights(&cfg.protocol.layerZeroHeights, cfg.protocol.numBigStepLevels), + } + honestStateManager, err := statemanager.NewForSimpleMachine(t, baseStateManagerOpts...) + require.NoError(t, err) + + shp := &simpleHeaderProvider{b: bk, chs: make([]chan<- *gethtypes.Header, 0)} + shp.Start(ctx) + + baseStackOpts := []cm.StackOpt{ + cm.StackWithMode(types.MakeMode), + cm.StackWithPollingInterval(cfg.timings.assertionScanningInterval), + cm.StackWithPostingInterval(cfg.timings.assertionPostingInterval), + cm.StackWithAverageBlockCreationTime(cfg.timings.blockTime), + cm.StackWithConfirmationInterval(cfg.timings.assertionConfirmationAttemptInterval), + cm.StackWithMinimumGapToParentAssertion(0), + cm.StackWithHeaderProvider(shp), + } + + name := "honest" + txOpts := accounts[1] + //nolint:gocritic + honestOpts := append( + baseStackOpts, + cm.StackWithName(name), + ) + honestChain := setupAssertionChain(t, ctx, bk.Client(), rollupAddr.Rollup, txOpts) + honestManager, err := cm.NewChallengeStack(honestChain, honestStateManager, honestOpts...) + require.NoError(t, err) + + totalOpcodes := totalWasmOpcodes(&cfg.protocol.layerZeroHeights, cfg.protocol.numBigStepLevels) + t.Logf("Total wasm opcodes in test: %d", totalOpcodes) + + assertionDivergenceHeight := uint64(1) + assertionBlockHeightDifference := int64(1) + + evilChallengeManagers := make([]*cm.Manager, cfg.actors.numEvilValidators) + for i := uint64(0); i < cfg.actors.numEvilValidators; i++ { + // Diverge at a random opcode within the block. + machineDivergenceStep := randUint64(i) + if machineDivergenceStep == 0 { + machineDivergenceStep = 1 + } + //nolint:gocritic + evilStateManagerOpts := append( + baseStateManagerOpts, + statemanager.WithMachineDivergenceStep(machineDivergenceStep), + statemanager.WithBlockDivergenceHeight(assertionDivergenceHeight), + statemanager.WithDivergentBlockHeightOffset(assertionBlockHeightDifference), + ) + evilStateManager, err := statemanager.NewForSimpleMachine(t, evilStateManagerOpts...) + require.NoError(t, err) + + // Honest validator has index 1 in the accounts slice, as 0 is admin, so + // evil ones should start at 2. + evilTxOpts := accounts[2+i] + name = fmt.Sprintf("evil-%d", i) + //nolint:gocritic + evilOpts := append( + baseStackOpts, + cm.StackWithName(name), + ) + evilChain := setupAssertionChain(t, ctx, bk.Client(), rollupAddr.Rollup, evilTxOpts) + evilManager, err := cm.NewChallengeStack(evilChain, evilStateManager, evilOpts...) + require.NoError(t, err) + evilChallengeManagers[i] = evilManager + } + + honestManager.Start(ctx) + + for _, evilManager := range evilChallengeManagers { + evilManager.Start(ctx) + } + + g, ctx := errgroup.WithContext(ctx) + for _, e := range cfg.expectations { + fn := e // loop closure + g.Go(func() error { + return fn(t, ctx, bk.ContractAddresses(), bk.Client(), txOpts.From) + }) + } + require.NoError(t, g.Wait()) +} + +type seqMessage struct { + dataHash common.Hash + afterDelayedMessagesRead *big.Int + prevMessageCount *big.Int + newMessageCount *big.Int +} + +type committer interface { + Commit() common.Hash +} + +func enqueueSequencerMessageAsExecutor( + t *testing.T, + opts *bind.TransactOpts, + executor common.Address, + backend protocol.ChainBackend, + bridge common.Address, + msg seqMessage, +) { + execBindings, err := mocksgen.NewUpgradeExecutorMock(executor, backend) + require.NoError(t, err) + seqInboxABI, err := abi.JSON(strings.NewReader(bridgegen.AbsBridgeABI)) + require.NoError(t, err) + + data, err := seqInboxABI.Pack( + "setSequencerInbox", + executor, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, bridge, data) + require.NoError(t, err) + if comm, ok := backend.(committer); ok { + comm.Commit() + } + + seqQueueMsg, err := seqInboxABI.Pack( + "enqueueSequencerMessage", + msg.dataHash, msg.afterDelayedMessagesRead, msg.prevMessageCount, msg.newMessageCount, + ) + require.NoError(t, err) + _, err = execBindings.ExecuteCall(opts, bridge, seqQueueMsg) + require.NoError(t, err) + if comm, ok := backend.(committer); ok { + comm.Commit() + } +} diff --git a/bold/testing/endtoend/expectations.go b/bold/testing/endtoend/expectations.go new file mode 100644 index 0000000000..d5842c064e --- /dev/null +++ b/bold/testing/endtoend/expectations.go @@ -0,0 +1,115 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package endtoend + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + retry "github.com/offchainlabs/bold/runtime" + "github.com/offchainlabs/bold/testing/setup" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +// expect is a function that will be called asynchronously to verify some success criteria +// for the given scenario. +type expect func(t *testing.T, ctx context.Context, addresses *setup.RollupAddresses, be protocol.ChainBackend, honestValidatorAddress common.Address) error + +// Expects that an assertion is confirmed by challenge win. +func expectChallengeWinWithAllHonestEssentialEdgesConfirmed( + t *testing.T, + ctx context.Context, + addresses *setup.RollupAddresses, + backend protocol.ChainBackend, + honestValidatorAddress common.Address, +) error { + t.Run("honest essential edges confirmed by challenge win", func(t *testing.T) { + rc, err := rollupgen.NewRollupCore(addresses.Rollup, backend) + require.NoError(t, err) + cmAddr, err := rc.ChallengeManager(&bind.CallOpts{}) + require.NoError(t, err) + + // Wait until a challenged assertion is confirmed by time. + var confirmed bool + for ctx.Err() == nil && !confirmed { + var i *rollupgen.RollupCoreAssertionConfirmedIterator + i, err = retry.UntilSucceeds(ctx, func() (*rollupgen.RollupCoreAssertionConfirmedIterator, error) { + return rc.FilterAssertionConfirmed(nil, nil) + }) + require.NoError(t, err) + for i.Next() { + var assertionNode rollupgen.AssertionNode + assertionNode, err = retry.UntilSucceeds(ctx, func() (rollupgen.AssertionNode, error) { + return rc.GetAssertion(&bind.CallOpts{Context: ctx}, i.Event.AssertionHash) + }) + require.NoError(t, err) + isChallengeParent := assertionNode.FirstChildBlock > 0 && assertionNode.SecondChildBlock > 0 + if isChallengeParent && assertionNode.Status != uint8(protocol.AssertionConfirmed) { + t.Fatal("Confirmed assertion with unfinished state") + } + confirmed = true + break + } + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + } + + if !confirmed { + t.Fatal("assertion was not confirmed") + } + + // No more edges will be added here, so we then scrape all the edges added to the challenge. + // We await until all the essential root edges are also confirmed by time. + cm, err := challengeV2gen.NewEdgeChallengeManager(cmAddr, backend) + require.NoError(t, err) + + // Scrape all the honest edges onchain (the ones made by the honest address). + // Check if the edges that have claim id != None are confirmed (those are essential root edges) + // and also check one step edges from honest party are confirmed. + honestEssentialRootIds := make(map[common.Hash]bool, 0) + chainId, err := backend.ChainID(ctx) + require.NoError(t, err) + it, err := cm.FilterEdgeAdded(nil, nil, nil, nil) + require.NoError(t, err) + for it.Next() { + txHash := it.Event.Raw.TxHash + tx, _, err := backend.TransactionByHash(ctx, txHash) + require.NoError(t, err) + sender, err := types.Sender(types.NewCancunSigner(chainId), tx) + require.NoError(t, err) + if sender != honestValidatorAddress { + continue + } + // Skip edges that are not essential roots or the top-level challenge root. + if it.Event.ClaimId == (common.Hash{}) || it.Event.Level == 0 { + continue + } + honestEssentialRootIds[it.Event.EdgeId] = false + } + // Wait until all of the honest essential root ids are confirmed. + confirmedCount := 0 + for confirmedCount < len(honestEssentialRootIds) { + for k, markedConfirmed := range honestEssentialRootIds { + edge, err := cm.GetEdge(&bind.CallOpts{}, k) + require.NoError(t, err) + if edge.Status == 1 && !markedConfirmed { + confirmedCount += 1 + honestEssentialRootIds[k] = true + t.Logf("Confirmed %d honest essential edges, got edge at level %d", confirmedCount, edge.Level) + } + } + time.Sleep(500 * time.Millisecond) // Don't spam the backend. + } + }) + return nil +} diff --git a/bold/testing/endtoend/headers.go b/bold/testing/endtoend/headers.go new file mode 100644 index 0000000000..e349f17dc8 --- /dev/null +++ b/bold/testing/endtoend/headers.go @@ -0,0 +1,63 @@ +package endtoend + +import ( + "context" + + "github.com/ethereum/go-ethereum/core/types" + + "github.com/offchainlabs/bold/testing/endtoend/backend" + "github.com/offchainlabs/bold/util/stopwaiter" +) + +type simpleHeaderProvider struct { + stopwaiter.StopWaiter + b backend.Backend + chs []chan<- *types.Header +} + +func (s *simpleHeaderProvider) Start(ctx context.Context) { + s.StopWaiter.Start(ctx, s) + s.LaunchThread(s.listenToHeaders) +} + +func (s *simpleHeaderProvider) listenToHeaders(ctx context.Context) { + ch := make(chan *types.Header, 100) + sub, err := s.b.Client().SubscribeNewHead(ctx, ch) + if err != nil { + panic(err) + } + defer sub.Unsubscribe() + for { + select { + case header := <-ch: + for _, sch := range s.chs { + sch <- header + } + case <-sub.Err(): + case <-ctx.Done(): + return + } + } +} + +func (s *simpleHeaderProvider) StopAndWait() { + s.StopWaiter.StopAndWait() +} + +func (s *simpleHeaderProvider) Subscribe(requireBlockNrUpdates bool) (<-chan *types.Header, func()) { + ch := make(chan *types.Header, 100) + s.chs = append(s.chs, ch) + return ch, func() { + s.removeChannel(ch) + close(ch) + } +} + +func (s *simpleHeaderProvider) removeChannel(ch chan<- *types.Header) { + for i, sch := range s.chs { + if sch == ch { + s.chs = append(s.chs[:i], s.chs[i+1:]...) + return + } + } +} diff --git a/bold/testing/endtoend/helpers_test.go b/bold/testing/endtoend/helpers_test.go new file mode 100644 index 0000000000..71392fcf3a --- /dev/null +++ b/bold/testing/endtoend/helpers_test.go @@ -0,0 +1,199 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package endtoend + +import ( + "context" + "errors" + "math/big" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +func setupAssertionChain( + t *testing.T, + ctx context.Context, + backend protocol.ChainBackend, + rollup common.Address, + txOpts *bind.TransactOpts, + opts ...solimpl.Opt, +) *solimpl.AssertionChain { + t.Helper() + assertionChainBinding, err := rollupgen.NewRollupUserLogic( + rollup, backend, + ) + require.NoError(t, err) + challengeManagerAddr, err := assertionChainBinding.RollupUserLogicCaller.ChallengeManager( + &bind.CallOpts{Context: ctx}, + ) + require.NoError(t, err) + chain, err := solimpl.NewAssertionChain( + ctx, + rollup, + challengeManagerAddr, + txOpts, + backend, + solimpl.NewChainBackendTransactor(backend), + opts..., + ) + require.NoError(t, err) + return chain +} + +func totalWasmOpcodes(heights *protocol.LayerZeroHeights, numBigSteps uint8) uint64 { + totalWasmOpcodes := uint64(1) + for i := uint8(0); i < numBigSteps; i++ { + totalWasmOpcodes *= heights.BigStepChallengeHeight.Uint64() + } + totalWasmOpcodes *= heights.SmallStepChallengeHeight.Uint64() + return totalWasmOpcodes +} + +// rand.Uint64() returns a random uint64 value. +// To get a value in the range [0, n), take the modulo n. +func randUint64(n uint64) uint64 { + if n == 0 { + return 0 + } + return rand.Uint64() % n +} + +func TestTotalWasmOpcodes(t *testing.T) { + t.Run("2^43 production value", func(t *testing.T) { + layerZeroHeights := &protocol.LayerZeroHeights{ + BlockChallengeHeight: 1 << 10, + BigStepChallengeHeight: 1 << 10, + SmallStepChallengeHeight: 1 << 13, + } + numBigSteps := uint8(3) + require.Equal(t, uint64(1<<43), totalWasmOpcodes(layerZeroHeights, numBigSteps)) + }) + t.Run("minimal configuration", func(t *testing.T) { + layerZeroHeights := &protocol.LayerZeroHeights{ + BlockChallengeHeight: 1 << 5, + BigStepChallengeHeight: 1 << 5, + SmallStepChallengeHeight: 1 << 5, + } + numBigSteps := uint8(1) + require.Equal(t, uint64(1<<10), totalWasmOpcodes(layerZeroHeights, numBigSteps)) + }) +} + +var ( + _ protocol.ChainBackend = &FlakyEthClient{} +) + +type FlakyEthClient struct { + protocol.ChainBackend +} + +func (f *FlakyEthClient) flaky() error { + // 10% chance of error + if rand.Intn(10) > 8 { + return errors.New("flaky error") + } + return nil +} + +func (f *FlakyEthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.TransactionReceipt(ctx, txHash) +} + +func (f *FlakyEthClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.CodeAt(ctx, contract, blockNumber) +} + +func (f *FlakyEthClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.CallContract(ctx, call, blockNumber) +} + +func (f *FlakyEthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.HeaderByNumber(ctx, number) +} + +func (f *FlakyEthClient) HeaderU64(ctx context.Context) (uint64, error) { + if err := f.flaky(); err != nil { + return 0, err + } + return f.ChainBackend.HeaderU64(ctx) +} + +func (f *FlakyEthClient) PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.PendingCodeAt(ctx, account) +} + +func (f *FlakyEthClient) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + if err := f.flaky(); err != nil { + return 0, err + } + return f.ChainBackend.PendingNonceAt(ctx, account) +} + +func (f *FlakyEthClient) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.SuggestGasPrice(ctx) +} + +func (f *FlakyEthClient) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.SuggestGasTipCap(ctx) +} + +func (f *FlakyEthClient) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { + if err := f.flaky(); err != nil { + return 0, err + } + return f.ChainBackend.EstimateGas(ctx, call) +} + +func (f *FlakyEthClient) SendTransaction(ctx context.Context, tx *types.Transaction) error { + if err := f.flaky(); err != nil { + return err + } + return f.ChainBackend.SendTransaction(ctx, tx) +} + +func (f *FlakyEthClient) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.FilterLogs(ctx, query) +} +func (f *FlakyEthClient) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + if err := f.flaky(); err != nil { + return nil, err + } + return f.ChainBackend.SubscribeFilterLogs(ctx, query, ch) +} diff --git a/bold/testing/integration/prefixproofs_test.go b/bold/testing/integration/prefixproofs_test.go new file mode 100644 index 0000000000..26937c5a30 --- /dev/null +++ b/bold/testing/integration/prefixproofs_test.go @@ -0,0 +1,262 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package prefixproofs + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/state-commitments/history" + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" +) + +func TestPrefixProofGeneration(t *testing.T) { + t.Parallel() + ctx := context.Background() + merkleTreeContract, _ := setupMerkleTreeContract(t) + verify := func(t *testing.T, computed *prefixProofComputation) { + prefixExpRaw := make([][32]byte, len(computed.prefixExpansion)) + for i := 0; i < len(computed.prefixExpansion); i++ { + var r [32]byte + copy(r[:], computed.prefixExpansion[i][:]) + prefixExpRaw[i] = r + } + proofRaw := make([][32]byte, len(computed.proof)) + for i := 0; i < len(computed.proof); i++ { + var r [32]byte + copy(r[:], computed.proof[i][:]) + proofRaw[i] = r + } + err := prefixproofs.VerifyPrefixProof(&prefixproofs.VerifyPrefixProofConfig{ + PreRoot: computed.prefixRoot, + PreSize: computed.prefixTotalLeaves, + PostRoot: computed.fullRoot, + PostSize: computed.fullTreeTotalLeaves, + PreExpansion: computed.prefixExpansion, + PrefixProof: computed.proof, + }) + require.NoError(t, err) + err = merkleTreeContract.VerifyPrefixProof( + &bind.CallOpts{}, + computed.prefixRoot, + new(big.Int).SetUint64(computed.prefixTotalLeaves), + computed.fullRoot, + new(big.Int).SetUint64(computed.fullTreeTotalLeaves), + prefixExpRaw, + proofRaw, + ) + require.NoError(t, err) + } + tests := []struct { + realLength uint64 + virtualLength uint64 + }{ + {1, 4}, + {2, 4}, + {3, 4}, + {4, 4}, + {1, 8}, + {2, 8}, + {3, 8}, + {4, 8}, + {5, 8}, + {6, 8}, + {7, 8}, + {8, 8}, + {1, 16}, + } + + for _, tt := range tests { + for virtual := tt.realLength; virtual < tt.virtualLength; virtual++ { + for prefixIndex := uint64(0); prefixIndex < virtual-1; prefixIndex++ { + t.Run(fmt.Sprintf("real length %d, virtual %d, prefix index %d", tt.realLength, virtual, prefixIndex), func(t *testing.T) { + legacy := computeLegacyPrefixProof(t, ctx, virtual, prefixIndex) + optimized := computeOptimizedPrefixProof(t, tt.realLength, virtual, prefixIndex) + verify(t, legacy) + verify(t, optimized) + }) + } + } + } +} + +type prefixProofComputation struct { + prefixRoot common.Hash + fullRoot common.Hash + prefixTotalLeaves uint64 + fullTreeTotalLeaves uint64 + prefixExpansion []common.Hash + proof []common.Hash +} + +func computeOptimizedPrefixProof(t *testing.T, numRealHashes uint64, virtual uint64, prefixIndex uint64) *prefixProofComputation { + // Computes the prefix proof and expansion. + simpleHash := crypto.Keccak256Hash([]byte("foo")) + hashes := make([]common.Hash, prefixIndex+1) + for i := 0; i < len(hashes); i++ { + hashes[i] = simpleHash + } + + // Computes the prefix root. + prefixRoot, err := history.ComputeRoot(hashes, prefixIndex+1) + require.NoError(t, err) + + // Computes the full tree root. + hashes = make([]common.Hash, numRealHashes) + for i := 0; i < len(hashes); i++ { + hashes[i] = simpleHash + } + fullTreeRoot, err := history.ComputeRoot(hashes, virtual) + require.NoError(t, err) + + // Computes the prefix proof. + hashes = make([]common.Hash, numRealHashes) + for i := 0; i < len(hashes); i++ { + hashes[i] = simpleHash + } + prefixExp, proof, err := history.GeneratePrefixProof(uint64(prefixIndex), hashes, virtual) + require.NoError(t, err) + return &prefixProofComputation{ + prefixRoot: prefixRoot, + fullRoot: fullTreeRoot, + prefixTotalLeaves: uint64(prefixIndex) + 1, + fullTreeTotalLeaves: uint64(virtual), + prefixExpansion: prefixExp, + proof: proof, + } +} + +func computeLegacyPrefixProof(t *testing.T, ctx context.Context, numHashes uint64, prefixIndex uint64) *prefixProofComputation { + simpleHash := crypto.Keccak256Hash([]byte("foo")) + hashes := make([]common.Hash, numHashes) + for i := 0; i < len(hashes); i++ { + hashes[i] = simpleHash + } + manager, err := statemanager.NewWithMockedStateRoots(hashes) + require.NoError(t, err) + + wasmModuleRoot := common.Hash{} + startMessageNumber := l2stateprovider.Height(0) + fromMessageNumber := l2stateprovider.Height(prefixIndex) + req := &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: wasmModuleRoot, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: uint64(startMessageNumber), + }, + BatchLimit: 10, + }, + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(fromMessageNumber)), + } + loCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(t, err) + + req.UpToHeight = option.Some(l2stateprovider.Height(numHashes - 1)) + hiCommit, err := manager.HistoryCommitment(ctx, req) + require.NoError(t, err) + + packedProof, err := manager.PrefixProof(ctx, req, fromMessageNumber) + require.NoError(t, err) + + data, err := statemanager.ProofArgs.Unpack(packedProof) + require.NoError(t, err) + preExpansion, ok := data[0].([][32]byte) + require.Equal(t, true, ok) + proof, ok := data[1].([][32]byte) + require.Equal(t, true, ok) + + preExpansionHashes := make([]common.Hash, len(preExpansion)) + for i := 0; i < len(preExpansion); i++ { + preExpansionHashes[i] = preExpansion[i] + } + prefixProof := make([]common.Hash, len(proof)) + for i := 0; i < len(proof); i++ { + prefixProof[i] = proof[i] + } + return &prefixProofComputation{ + prefixRoot: loCommit.Merkle, + fullRoot: hiCommit.Merkle, + prefixTotalLeaves: uint64(prefixIndex) + 1, + fullTreeTotalLeaves: uint64(numHashes), + prefixExpansion: preExpansionHashes, + proof: prefixProof, + } +} + +func setupMerkleTreeContract(t testing.TB) (*mocksgen.MerkleTreeAccess, *simulated.Backend) { + numChains := uint64(1) + accs, backend := setupAccounts(t, numChains) + _, _, merkleTreeContract, err := mocksgen.DeployMerkleTreeAccess(accs[0].txOpts, backend.Client()) + if err != nil { + t.Fatal(err) + } + backend.Commit() + return merkleTreeContract, backend +} + +// Represents a test EOA account in the simulated backend, +type testAccount struct { + accountAddr common.Address + txOpts *bind.TransactOpts +} + +func setupAccounts(t testing.TB, numAccounts uint64) ([]*testAccount, *simulated.Backend) { + genesis := make(types.GenesisAlloc) + gasLimit := uint64(100000000) + + accs := make([]*testAccount, numAccounts) + for i := uint64(0); i < numAccounts; i++ { + privKey, err := crypto.GenerateKey() + if err != nil { + t.Fatal(err) + } + pubKeyECDSA, ok := privKey.Public().(*ecdsa.PublicKey) + if !ok { + t.Fatal("not ok") + } + + // Strip off the 0x and the first 2 characters 04 which is always the + // EC prefix and is not required. + publicKeyBytes := crypto.FromECDSAPub(pubKeyECDSA)[4:] + var pubKey = make([]byte, 48) + copy(pubKey, publicKeyBytes) + + addr := crypto.PubkeyToAddress(privKey.PublicKey) + chainID := big.NewInt(1337) + txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, chainID) + if err != nil { + t.Fatal(err) + } + startingBalance, _ := new(big.Int).SetString( + "100000000000000000000000000000000000000", + 10, + ) + genesis[addr] = types.Account{Balance: startingBalance} + accs[i] = &testAccount{ + accountAddr: addr, + txOpts: txOpts, + } + } + backend := simulated.NewBackend(genesis, simulated.WithBlockGasLimit(gasLimit)) + return accs, backend +} diff --git a/bold/testing/mocks/mocks.go b/bold/testing/mocks/mocks.go new file mode 100644 index 0000000000..30026ae466 --- /dev/null +++ b/bold/testing/mocks/mocks.go @@ -0,0 +1,619 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package mocks includes simple mocks for unit testing BOLD. +// nolint:errcheck +package mocks + +import ( + "context" + "math/big" + + "github.com/stretchr/testify/mock" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/state-commitments/history" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +var ( + _ = protocol.SpecChallengeManager(&MockSpecChallengeManager{}) + _ = protocol.SpecEdge(&MockSpecEdge{}) + _ = protocol.AssertionChain(&MockProtocol{}) + _ = l2stateprovider.Provider(&MockStateManager{}) +) + +type MockAssertion struct { + MockId protocol.AssertionHash + MockPrevId protocol.AssertionHash + Prev option.Option[*MockAssertion] + MockHeight uint64 + MockStateHash common.Hash + MockInboxMsgCountSeen uint64 + MockCreatedAtBlock uint64 + MockHasSecondChild bool + CreatedAt uint64 +} + +func (m *MockAssertion) Id() protocol.AssertionHash { + return m.MockId +} + +func (m *MockAssertion) PrevId(ctx context.Context) (protocol.AssertionHash, error) { + return m.MockPrevId, nil +} + +func (m *MockAssertion) StateHash() (common.Hash, error) { + return m.MockStateHash, nil +} + +func (m *MockAssertion) HasSecondChild() (bool, error) { + return m.MockHasSecondChild, nil +} + +func (m *MockAssertion) InboxMsgCountSeen() (uint64, error) { + return m.MockInboxMsgCountSeen, nil +} + +func (m *MockAssertion) CreatedAtBlock() uint64 { + return m.CreatedAt +} + +func (m *MockAssertion) FirstChildCreationBlock() (uint64, error) { + return 0, nil +} + +func (m *MockAssertion) SecondChildCreationBlock() (uint64, error) { + return 0, nil +} + +func (m *MockAssertion) IsFirstChild() (bool, error) { + return false, nil +} + +func (m *MockAssertion) Status(ctx context.Context) (protocol.AssertionStatus, error) { + return protocol.AssertionPending, nil +} + +type MockStateManager struct { + mock.Mock + Agrees bool + AgreeErr bool +} + +func (m *MockStateManager) HistoryCommitment( + ctx context.Context, + req *l2stateprovider.HistoryCommitmentRequest, +) (history.History, error) { + args := m.Called(ctx, req) + return args.Get(0).(history.History), args.Error(1) +} + +func (m *MockStateManager) UpdateAPIDatabase(apiDB db.Database) { + m.Called(apiDB) +} + +func (m *MockStateManager) PrefixProof( + ctx context.Context, + req *l2stateprovider.HistoryCommitmentRequest, + prefixHeight l2stateprovider.Height, +) ([]byte, error) { + args := m.Called(ctx, req, prefixHeight) + return args.Get(0).([]byte), args.Error(1) +} + +func (m *MockStateManager) AgreesWithHistoryCommitment( + ctx context.Context, + challengeLevel protocol.ChallengeLevel, + historyCommitMetadata *l2stateprovider.HistoryCommitmentRequest, + commit l2stateprovider.History, +) (bool, error) { + args := m.Called(ctx, challengeLevel, historyCommitMetadata, commit) + return args.Get(0).(bool), args.Error(1) +} + +func (m *MockStateManager) ExecutionStateAfterPreviousState(ctx context.Context, maxInboxCount uint64, previousGlobalState protocol.GoGlobalState) (*protocol.ExecutionState, error) { + args := m.Called(ctx, maxInboxCount, previousGlobalState) + return args.Get(0).(*protocol.ExecutionState), args.Error(1) +} + +func (m *MockStateManager) OneStepProofData( + ctx context.Context, + assertionMetadata *l2stateprovider.AssociatedAssertionMetadata, + startHeights []l2stateprovider.Height, + upToHeight l2stateprovider.Height, +) (data *protocol.OneStepData, startLeafInclusionProof, endLeafInclusionProof []common.Hash, err error) { + args := m.Called(ctx, assertionMetadata.WasmModuleRoot, startHeights, assertionMetadata.FromState.PosInBatch, upToHeight) + return args.Get(0).(*protocol.OneStepData), args.Get(1).([]common.Hash), args.Get(2).([]common.Hash), args.Error(3) +} + +type MockChallengeManager struct { + mock.Mock + MockAddr common.Address +} + +func (m *MockChallengeManager) ChallengePeriodBlocks(ctx context.Context) (uint64, error) { + args := m.Called(ctx) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockChallengeManager) Address() common.Address { + return m.MockAddr +} + +func (m *MockChallengeManager) LevelZeroBlockEdgeHeight(ctx context.Context) (uint64, error) { + args := m.Called(ctx) + return args.Get(0).(uint64), args.Error(1) +} + +// MockSpecChallengeManager is a mock implementation of the SpecChallengeManager interface. +type MockSpecChallengeManager struct { + mock.Mock + MockAddr common.Address +} + +func (m *MockSpecChallengeManager) Address() common.Address { + return m.MockAddr +} + +func (m *MockSpecChallengeManager) NumBigSteps() uint8 { + args := m.Called() + return args.Get(0).(uint8) +} + +func (m *MockSpecChallengeManager) LayerZeroHeights() protocol.LayerZeroHeights { + args := m.Called() + return args.Get(0).(protocol.LayerZeroHeights) +} + +func (m *MockSpecChallengeManager) ChallengePeriodBlocks() uint64 { + args := m.Called() + return args.Get(0).(uint64) +} + +func (m *MockSpecChallengeManager) MultiUpdateInheritedTimers(ctx context.Context, branch []protocol.ReadOnlyEdge, desiredTimerForLastEdge uint64) (*types.Transaction, error) { + args := m.Called(ctx, branch, desiredTimerForLastEdge) + return args.Get(0).(*types.Transaction), args.Error(1) +} + +func (m *MockSpecChallengeManager) GetEdge( + ctx context.Context, + edgeId protocol.EdgeId, +) (option.Option[protocol.SpecEdge], error) { + args := m.Called(ctx, edgeId) + return args.Get(0).(option.Option[protocol.SpecEdge]), args.Error(1) +} + +func (m *MockSpecChallengeManager) CalculateMutualId( + ctx context.Context, + edgeType protocol.ChallengeLevel, + originId protocol.OriginId, + startHeight protocol.Height, + startHistoryRoot common.Hash, + endHeight protocol.Height, +) (protocol.MutualId, error) { + args := m.Called(ctx, edgeType, originId, startHeight, startHistoryRoot, endHeight) + return args.Get(0).(protocol.MutualId), args.Error(1) +} + +func (m *MockSpecChallengeManager) CalculateEdgeId( + ctx context.Context, + edgeType protocol.ChallengeLevel, + originId protocol.OriginId, + startHeight protocol.Height, + startHistoryRoot common.Hash, + endHeight protocol.Height, + endHistoryRoot common.Hash, +) (protocol.EdgeId, error) { + args := m.Called(ctx, edgeType, originId, startHeight, startHistoryRoot, endHeight, endHistoryRoot) + return args.Get(0).(protocol.EdgeId), args.Error(1) +} + +func (m *MockSpecChallengeManager) AddBlockChallengeLevelZeroEdge( + ctx context.Context, + assertion protocol.Assertion, + startCommit, + endCommit history.History, + startEndPrefixProof []byte, +) (protocol.VerifiedRoyalEdge, error) { + args := m.Called(ctx, assertion, startCommit, endCommit, startEndPrefixProof) + return args.Get(0).(protocol.VerifiedRoyalEdge), args.Error(1) +} + +func (m *MockSpecChallengeManager) AddSubChallengeLevelZeroEdge( + ctx context.Context, + challengedEdge protocol.SpecEdge, + startCommit, + endCommit history.History, + startParentInclusionProof []common.Hash, + endParentInclusionProof []common.Hash, + startEndPrefixProof []byte, +) (protocol.VerifiedRoyalEdge, error) { + args := m.Called(ctx, challengedEdge, startCommit, endCommit, startParentInclusionProof, endParentInclusionProof, startEndPrefixProof) + return args.Get(0).(protocol.VerifiedRoyalEdge), args.Error(1) +} + +func (m *MockSpecChallengeManager) ConfirmEdgeByOneStepProof( + ctx context.Context, + tentativeWinnerId protocol.EdgeId, + oneStepData *protocol.OneStepData, + preHistoryInclusionProof []common.Hash, + postHistoryInclusionProof []common.Hash, +) error { + args := m.Called(ctx, tentativeWinnerId, oneStepData, preHistoryInclusionProof, postHistoryInclusionProof) + return args.Error(0) +} + +// MockSpecEdge is a mock implementation of the SpecEdge interface. +type MockSpecEdge struct { + mock.Mock +} + +func (m *MockSpecEdge) Id() protocol.EdgeId { + args := m.Called() + return args.Get(0).(protocol.EdgeId) +} + +func (m *MockSpecEdge) GetChallengeLevel() protocol.ChallengeLevel { + args := m.Called() + return args.Get(0).(protocol.ChallengeLevel) +} + +func (m *MockSpecEdge) GetReversedChallengeLevel() protocol.ChallengeLevel { + args := m.Called() + return args.Get(0).(protocol.ChallengeLevel) +} + +func (m *MockSpecEdge) GetTotalChallengeLevels(ctx context.Context) uint8 { + args := m.Called(ctx) + return args.Get(0).(uint8) +} + +func (m *MockSpecEdge) MiniStaker() option.Option[common.Address] { + args := m.Called() + return args.Get(0).(option.Option[common.Address]) +} + +func (m *MockSpecEdge) StartCommitment() (protocol.Height, common.Hash) { + args := m.Called() + return args.Get(0).(protocol.Height), args.Get(1).(common.Hash) +} + +func (m *MockSpecEdge) EndCommitment() (protocol.Height, common.Hash) { + args := m.Called() + return args.Get(0).(protocol.Height), args.Get(1).(common.Hash) +} + +func (m *MockSpecEdge) TopLevelClaimHeight(ctx context.Context) (protocol.OriginHeights, error) { + args := m.Called(ctx) + return args.Get(0).(protocol.OriginHeights), args.Error(1) +} + +func (m *MockSpecEdge) AssertionHash(ctx context.Context) (protocol.AssertionHash, error) { + args := m.Called(ctx) + return args.Get(0).(protocol.AssertionHash), args.Error(1) +} + +func (m *MockSpecEdge) TimeUnrivaled(ctx context.Context) (uint64, error) { + args := m.Called(ctx) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockSpecEdge) LatestInheritedTimer(ctx context.Context) (protocol.InheritedTimer, error) { + args := m.Called(ctx) + return args.Get(0).(protocol.InheritedTimer), args.Error(1) +} + +func (m *MockSpecEdge) HasRival(ctx context.Context) (bool, error) { + args := m.Called(ctx) + return args.Get(0).(bool), args.Error(1) +} + +func (m *MockSpecEdge) Status(ctx context.Context) (protocol.EdgeStatus, error) { + args := m.Called(ctx) + return args.Get(0).(protocol.EdgeStatus), args.Error(1) +} + +func (m *MockSpecEdge) ConfirmedAtBlock(ctx context.Context) (uint64, error) { + args := m.Called(ctx) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockSpecEdge) CreatedAtBlock() (uint64, error) { + args := m.Called() + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockSpecEdge) MutualId() protocol.MutualId { + args := m.Called() + return args.Get(0).(protocol.MutualId) +} + +func (m *MockSpecEdge) OriginId() protocol.OriginId { + args := m.Called() + return args.Get(0).(protocol.OriginId) +} + +func (m *MockSpecEdge) ClaimId() option.Option[protocol.ClaimId] { + args := m.Called() + return args.Get(0).(option.Option[protocol.ClaimId]) +} + +func (m *MockSpecEdge) LowerChild(ctx context.Context) (option.Option[protocol.EdgeId], error) { + args := m.Called(ctx) + return args.Get(0).(option.Option[protocol.EdgeId]), args.Error(1) +} + +func (m *MockSpecEdge) UpperChild(ctx context.Context) (option.Option[protocol.EdgeId], error) { + args := m.Called(ctx) + return args.Get(0).(option.Option[protocol.EdgeId]), args.Error(1) +} + +func (m *MockSpecEdge) HasChildren(ctx context.Context) (bool, error) { + args := m.Called(ctx) + return args.Get(0).(bool), args.Error(1) +} + +func (m *MockSpecEdge) Bisect( + ctx context.Context, + prefixHistoryRoot common.Hash, + prefixProof []byte, +) (protocol.VerifiedRoyalEdge, protocol.VerifiedRoyalEdge, error) { + args := m.Called(ctx, prefixHistoryRoot, prefixProof) + return args.Get(0).(protocol.VerifiedRoyalEdge), args.Get(1).(protocol.VerifiedRoyalEdge), args.Error(2) +} +func (m *MockSpecEdge) ConfirmByTimer(ctx context.Context, assertionHash protocol.AssertionHash) (*types.Transaction, error) { + args := m.Called(ctx, assertionHash) + return args.Get(0).(*types.Transaction), args.Error(1) +} + +func (m *MockSpecEdge) ConfirmByClaim(ctx context.Context, claimId protocol.ClaimId) error { + args := m.Called(ctx, claimId) + return args.Error(0) +} + +func (m *MockSpecEdge) ConfirmByOneStepProof(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockSpecEdge) ConfirmByChildren(ctx context.Context) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockSpecEdge) HasLengthOneRival(ctx context.Context) (bool, error) { + args := m.Called(ctx) + return args.Get(0).(bool), args.Error(1) +} +func (m *MockSpecEdge) MarkAsHonest() { + m.Called() +} +func (m *MockSpecEdge) AsVerifiedHonest() (protocol.VerifiedRoyalEdge, bool) { + args := m.Called() + return args.Get(0).(protocol.VerifiedRoyalEdge), args.Get(1).(bool) +} + +type MockHonestEdge struct { + *MockSpecEdge +} + +func (m *MockHonestEdge) Honest() {} + +type MockEdgeTracker struct { + mock.Mock +} + +func (m *MockEdgeTracker) TrackEdge(ctx context.Context, edge protocol.VerifiedRoyalEdge) error { + args := m.Called(ctx, edge) + return args.Error(0) +} + +type MockProtocol struct { + mock.Mock +} + +func (m *MockProtocol) GetCallOptsWithDesiredRpcHeadBlockNumber(opts *bind.CallOpts) *bind.CallOpts { + if opts == nil { + opts = &bind.CallOpts{} + } + return opts +} + +func (m *MockProtocol) GetAssertionCreationParentBlock(ctx context.Context, assertionHash common.Hash) (uint64, error) { + args := m.Called(ctx, assertionHash) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockProtocol) GetDesiredRpcHeadBlockNumber() rpc.BlockNumber { + return rpc.LatestBlockNumber +} + +// Read-only methods. +func (m *MockProtocol) DesiredHeaderU64(ctx context.Context) (uint64, error) { + args := m.Called() + return args.Get(0).(uint64), args.Error(1) +} + +// Read-only methods. +func (m *MockProtocol) DesiredL1HeaderU64(ctx context.Context) (uint64, error) { + args := m.Called() + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockProtocol) Backend() protocol.ChainBackend { + args := m.Called() + return args.Get(0).(protocol.ChainBackend) +} + +func (m *MockProtocol) RollupAddress() common.Address { + args := m.Called() + return args.Get(0).(common.Address) +} + +func (m *MockProtocol) StakerAddress() common.Address { + args := m.Called() + return args.Get(0).(common.Address) +} + +func (m *MockProtocol) RollupUserLogic() *rollupgen.RollupUserLogic { + args := m.Called() + return args.Get(0).(*rollupgen.RollupUserLogic) +} + +func (m *MockProtocol) IsChallengeComplete(ctx context.Context, challengeParentAssertionHash protocol.AssertionHash) (bool, error) { + args := m.Called(ctx, challengeParentAssertionHash) + return args.Get(0).(bool), args.Error(1) +} + +func (m *MockProtocol) NumAssertions(ctx context.Context) (uint64, error) { + args := m.Called(ctx) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockProtocol) MinAssertionPeriodBlocks() uint64 { + args := m.Called() + return args.Get(0).(uint64) +} + +func (m *MockProtocol) MaxAssertionsPerChallengePeriod() uint64 { + args := m.Called() + return args.Get(0).(uint64) +} + +func (m *MockProtocol) GetAssertion(ctx context.Context, opts *bind.CallOpts, id protocol.AssertionHash) (protocol.Assertion, error) { + args := m.Called(ctx, opts, id) + return args.Get(0).(protocol.Assertion), args.Error(1) +} + +func (m *MockProtocol) AssertionStatus(ctx context.Context, id protocol.AssertionHash) (protocol.AssertionStatus, error) { + args := m.Called(ctx, id) + return args.Get(0).(protocol.AssertionStatus), args.Error(1) +} + +func (m *MockProtocol) AssertionUnrivaledBlocks(ctx context.Context, assertionHash protocol.AssertionHash) (uint64, error) { + args := m.Called(ctx, assertionHash) + return args.Get(0).(uint64), args.Error(1) +} + +func (m *MockProtocol) TopLevelAssertion(ctx context.Context, edgeId protocol.EdgeId) (protocol.AssertionHash, error) { + args := m.Called(ctx, edgeId) + return args.Get(0).(protocol.AssertionHash), args.Error(1) +} + +func (m *MockProtocol) TopLevelClaimHeights(ctx context.Context, edgeId protocol.EdgeId) (protocol.OriginHeights, error) { + args := m.Called(ctx, edgeId) + return args.Get(0).(protocol.OriginHeights), args.Error(1) +} + +func (m *MockProtocol) LatestCreatedAssertion(ctx context.Context) (protocol.Assertion, error) { + args := m.Called(ctx) + return args.Get(0).(protocol.Assertion), args.Error(1) +} + +func (m *MockProtocol) LatestConfirmed(ctx context.Context, opts *bind.CallOpts) (protocol.Assertion, error) { + args := m.Called(ctx, opts) + return args.Get(0).(protocol.Assertion), args.Error(1) +} + +func (m *MockProtocol) ReadAssertionCreationInfo( + ctx context.Context, id protocol.AssertionHash, +) (*protocol.AssertionCreatedInfo, error) { + args := m.Called(ctx, id) + return args.Get(0).(*protocol.AssertionCreatedInfo), args.Error(1) +} + +func (m *MockProtocol) LatestCreatedAssertionHashes(ctx context.Context) ([]protocol.AssertionHash, error) { + args := m.Called(ctx) + return args.Get(0).([]protocol.AssertionHash), args.Error(1) +} + +// Mutating methods. +func (m *MockProtocol) ConfirmAssertionByTime( + ctx context.Context, + assertionHash protocol.AssertionHash, +) error { + args := m.Called(ctx, assertionHash) + return args.Error(0) +} + +func (m *MockProtocol) ConfirmAssertionByChallengeWinner( + ctx context.Context, + assertionHash protocol.AssertionHash, + winningEdgeId protocol.EdgeId, +) error { + args := m.Called(ctx, assertionHash, winningEdgeId) + return args.Error(0) +} + +func (m *MockProtocol) FastConfirmAssertion( + ctx context.Context, + assertionCreationInfo *protocol.AssertionCreatedInfo, +) (bool, error) { + args := m.Called(ctx, assertionCreationInfo) + return args.Get(0).(bool), args.Error(1) +} + +func (m *MockProtocol) IsStaked(ctx context.Context) (bool, error) { + args := m.Called(ctx) + return args.Get(0).(bool), args.Error(1) +} + +func (m *MockProtocol) AutoDepositTokenForStaking( + ctx context.Context, + amount *big.Int, +) error { + args := m.Called(ctx, amount) + return args.Error(0) +} + +func (m *MockProtocol) ApproveAllowances( + ctx context.Context, +) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockProtocol) NewStake( + ctx context.Context, +) error { + args := m.Called(ctx) + return args.Error(0) +} + +func (m *MockProtocol) NewStakeOnNewAssertion( + ctx context.Context, + assertionCreationInfo *protocol.AssertionCreatedInfo, + postState *protocol.ExecutionState, +) (protocol.Assertion, error) { + args := m.Called(ctx, assertionCreationInfo, postState) + return args.Get(0).(protocol.Assertion), args.Error(1) +} + +func (m *MockProtocol) StakeOnNewAssertion( + ctx context.Context, + assertionCreationInfo *protocol.AssertionCreatedInfo, + postState *protocol.ExecutionState, +) (protocol.Assertion, error) { + args := m.Called(ctx, assertionCreationInfo, postState) + return args.Get(0).(protocol.Assertion), args.Error(1) +} + +func (m *MockProtocol) SpecChallengeManager() protocol.SpecChallengeManager { + args := m.Called() + return args.Get(0).(protocol.SpecChallengeManager) +} + +func (m *MockProtocol) Confirm(ctx context.Context, blockHash, sendRoot common.Hash) error { + args := m.Called(ctx, blockHash, sendRoot) + return args.Error(0) +} diff --git a/bold/testing/mocks/state-provider/execution_engine.go b/bold/testing/mocks/state-provider/execution_engine.go new file mode 100644 index 0000000000..65a3450972 --- /dev/null +++ b/bold/testing/mocks/state-provider/execution_engine.go @@ -0,0 +1,133 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md +package stateprovider + +import ( + "encoding/binary" + "math/big" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +type Machine interface { + CurrentStepNum() uint64 + GetExecutionState() *protocol.ExecutionState + Hash() common.Hash + IsStopped() bool + Clone() Machine + Step(steps uint64) error + OneStepProof() ([]byte, error) +} + +type SimpleMachine struct { + step uint64 + state *protocol.ExecutionState + maxBatchesRead *big.Int +} + +func NewSimpleMachine(startingState *protocol.ExecutionState, maxBatchesRead *big.Int) *SimpleMachine { + stateCopy := *startingState + if maxBatchesRead != nil { + maxBatchesRead = new(big.Int).Set(maxBatchesRead) + } + return &SimpleMachine{ + step: 0, + state: &stateCopy, + maxBatchesRead: maxBatchesRead, + } +} + +func (m *SimpleMachine) CurrentStepNum() uint64 { + return m.step +} + +func (m *SimpleMachine) GetExecutionState() *protocol.ExecutionState { + stateCopy := *m.state + return &stateCopy +} + +func (m *SimpleMachine) Hash() common.Hash { + return m.GetExecutionState().GlobalState.Hash() +} + +func (m *SimpleMachine) IsStopped() bool { + if m.step == 0 && m.state.MachineStatus == protocol.MachineStatusFinished { + if m.maxBatchesRead == nil || new(big.Int).SetUint64(m.state.GlobalState.Batch).Cmp(m.maxBatchesRead) < 0 { + // Kickstart the machine at step 0 + return false + } + } + return m.state.MachineStatus != protocol.MachineStatusRunning +} + +func (m *SimpleMachine) Clone() Machine { + newMachine := *m + stateCopy := *m.state + newMachine.state = &stateCopy + return &newMachine +} + +// End the batch after 2000 steps. This results in 11 blocks for an honest validator. +// This constant must be synchronized with the one in execution/engine.go +const stepsPerBatch = 2000 + +func (m *SimpleMachine) Step(steps uint64) error { + for ; steps > 0; steps-- { + if m.IsStopped() { + m.step += steps + return nil + } + m.state.MachineStatus = protocol.MachineStatusRunning + m.step++ + m.state.GlobalState.PosInBatch++ + if m.state.GlobalState.PosInBatch%stepsPerBatch == 0 { + m.state.GlobalState.Batch++ + m.state.GlobalState.PosInBatch = 0 + m.state.MachineStatus = protocol.MachineStatusFinished + } + if m.Hash()[0] == 0 { + m.state.MachineStatus = protocol.MachineStatusFinished + } + } + return nil +} + +func (m *SimpleMachine) OneStepProof() ([]byte, error) { + proof := make([]byte, 16) + binary.BigEndian.PutUint64(proof[:8], m.state.GlobalState.Batch) + binary.BigEndian.PutUint64(proof[8:], m.state.GlobalState.PosInBatch) + return proof, nil +} + +// VerifySimpleMachineOneStepProof checks the claimed post-state root results from executing +// a specified pre-state hash. +func VerifySimpleMachineOneStepProof(beforeStateRoot common.Hash, claimedAfterStateRoot common.Hash, step uint64, maxBatchesRead *big.Int, proof []byte) bool { + if len(proof) != 16 { + return false + } + batch := binary.BigEndian.Uint64(proof[:8]) + posInBatch := binary.BigEndian.Uint64(proof[8:]) + state := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + Batch: batch, + PosInBatch: posInBatch, + }, + MachineStatus: protocol.MachineStatusRunning, + } + mach := NewSimpleMachine(state, maxBatchesRead) + mach.step = step + if step == 0 || mach.Hash()[0] == 0 { + mach.state.MachineStatus = protocol.MachineStatusFinished + } + if mach.Hash() != beforeStateRoot { + return false + } + err := mach.Step(1) + if err != nil { + return false + } + return mach.Hash() == claimedAfterStateRoot +} diff --git a/bold/testing/mocks/state-provider/execution_engine_test.go b/bold/testing/mocks/state-provider/execution_engine_test.go new file mode 100644 index 0000000000..ad1512db08 --- /dev/null +++ b/bold/testing/mocks/state-provider/execution_engine_test.go @@ -0,0 +1,65 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md +package stateprovider + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +func TestExecutionEngine(t *testing.T) { + startState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{}, + MachineStatus: protocol.MachineStatusFinished, + } + machine := NewSimpleMachine(startState, nil) + for i := uint64(0); i < 100; i++ { + require.Equal(t, i, machine.CurrentStepNum()) + + thisHash := machine.Hash() + stopped := machine.IsStopped() + osp, err := machine.OneStepProof() + require.NoError(t, err) + err = machine.Step(1) + require.NoError(t, err) + nextHash := machine.Hash() + + require.Equal(t, thisHash == nextHash, stopped) + + if !VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, osp) { + t.Fatal(i) + } + + // verify that bad proofs get rejected + fakeProof := append([]byte{}, osp...) + fakeProof = append(fakeProof, 0) + if VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, fakeProof) { + t.Fatal(i) + } + + fakeProof = append([]byte{}, osp...) + fakeProof[0] ^= 1 + if VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, fakeProof) { + t.Fatal(i) + } + + fakeProof = append([]byte{}, osp...) + fakeProof = fakeProof[len(fakeProof)-1:] + if VerifySimpleMachineOneStepProof(thisHash, nextHash, i, nil, fakeProof) { + t.Fatal(i) + } + + if thisHash != nextHash && VerifySimpleMachineOneStepProof(thisHash, thisHash, i, nil, osp) { + t.Fatal(i) + } + if VerifySimpleMachineOneStepProof(thisHash, common.Hash{}, i, nil, osp) { + t.Fatal(i) + } + } +} diff --git a/bold/testing/mocks/state-provider/history_provider.go b/bold/testing/mocks/state-provider/history_provider.go new file mode 100644 index 0000000000..6b95444af7 --- /dev/null +++ b/bold/testing/mocks/state-provider/history_provider.go @@ -0,0 +1,77 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package stateprovider + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" +) + +// Collects a list of machine hashes at a message number based on some configuration parameters. +func (s *L2StateBackend) CollectMachineHashes( + ctx context.Context, cfg *l2stateprovider.HashCollectorConfig, +) ([]common.Hash, error) { + // We step through the machine in our desired increments, and gather the + // machine hashes along the way for the history commitment. + machine, err := s.machineAtBlock(ctx, uint64(cfg.BlockChallengeHeight)) + if err != nil { + return nil, err + } + // Advance the machine to the start index. + if machErr := machine.Step(uint64(cfg.MachineStartIndex)); machErr != nil { + return nil, machErr + } + hashes := make([]common.Hash, 0, cfg.NumDesiredHashes) + hashes = append(hashes, s.getMachineHash(machine, uint64(cfg.BlockChallengeHeight))) + for i := uint64(1); i < cfg.NumDesiredHashes; i++ { + if stepErr := machine.Step(uint64(cfg.StepSize)); stepErr != nil { + return nil, stepErr + } + hashes = append(hashes, s.getMachineHash(machine, uint64(cfg.BlockChallengeHeight))) + } + return hashes, nil +} + +// CollectProof Collects osp of at a message number and OpcodeIndex . +func (s *L2StateBackend) CollectProof( + ctx context.Context, + assertionMetadata *l2stateprovider.AssociatedAssertionMetadata, + blockChallengeHeight l2stateprovider.Height, + machineIndex l2stateprovider.OpcodeIndex, +) ([]byte, error) { + machine, err := s.machineAtBlock(ctx, uint64(blockChallengeHeight)) + if err != nil { + return nil, err + } + err = machine.Step(uint64(machineIndex)) + if err != nil { + return nil, err + } + return machine.OneStepProof() +} + +// Computes a block history commitment from a start L2 message to an end L2 message index +// and up to a required batch index. The hashes used for this commitment are the machine hashes +// at each message number. +func (s *L2StateBackend) L2MessageStatesUpTo( + ctx context.Context, + fromState protocol.GoGlobalState, + batchLimit l2stateprovider.Batch, + toHeight option.Option[l2stateprovider.Height], +) ([]common.Hash, error) { + var to l2stateprovider.Height + if !toHeight.IsNone() { + to = toHeight.Unwrap() + } else { + blockChallengeLeafHeight := s.challengeLeafHeights[0] + to = l2stateprovider.Height(blockChallengeLeafHeight) + } + return s.statesUpTo(uint64(fromState.PosInBatch), uint64(to), uint64(fromState.Batch), uint64(batchLimit)) +} diff --git a/bold/testing/mocks/state-provider/history_provider_test.go b/bold/testing/mocks/state-provider/history_provider_test.go new file mode 100644 index 0000000000..a6df0bcbe7 --- /dev/null +++ b/bold/testing/mocks/state-provider/history_provider_test.go @@ -0,0 +1,135 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package stateprovider + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" +) + +var ( + _ l2stateprovider.L2MessageStateCollector = (*L2StateBackend)(nil) + _ l2stateprovider.MachineHashCollector = (*L2StateBackend)(nil) +) + +func simpleAssertionMetadata() *l2stateprovider.AssociatedAssertionMetadata { + return &l2stateprovider.AssociatedAssertionMetadata{ + WasmModuleRoot: common.Hash{}, + FromState: protocol.GoGlobalState{ + Batch: 0, + PosInBatch: 0, + }, + BatchLimit: 1, + } +} + +func TestHistoryCommitment(t *testing.T) { + ctx := context.Background() + challengeLeafHeights := []l2stateprovider.Height{ + 1 << 2, + 1 << 3, + 1 << 4, + } + numStates := uint64(10) + states, _ := setupStates(t, numStates, 0 /* honest */) + stateBackend, err := newTestingMachine( + states, + WithMaxWavmOpcodesPerBlock(uint64(challengeLeafHeights[1]*challengeLeafHeights[2])), + WithMachineAtBlockProvider(mockMachineAtBlock), + WithForceMachineBlockCompat(), + ) + require.NoError(t, err) + stateBackend.challengeLeafHeights = challengeLeafHeights + + provider := l2stateprovider.NewHistoryCommitmentProvider( + stateBackend, + stateBackend, + stateBackend, + challengeLeafHeights, + stateBackend, + nil, + ) + t.Run("produces a block challenge commitment with height equal to leaf height const", func(t *testing.T) { + got, err := provider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.None[l2stateprovider.Height](), + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(challengeLeafHeights[0]), got.Height) + }) + t.Run("produces a block challenge commitment with height up to", func(t *testing.T) { + got, err := provider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(2)), + }, + ) + require.NoError(t, err) + require.Equal(t, uint64(2), got.Height) + }) + t.Run("produces a subchallenge history commitment with claims matching higher level start end leaves", func(t *testing.T) { + blockChallengeCommit, err := provider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(1)), + }, + ) + require.NoError(t, err) + + subChallengeCommit, err := provider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0}, + UpToHeight: option.None[l2stateprovider.Height](), + }, + ) + require.NoError(t, err) + + require.Equal(t, uint64(challengeLeafHeights[1]), subChallengeCommit.Height) + require.Equal(t, blockChallengeCommit.FirstLeaf, subChallengeCommit.FirstLeaf) + require.Equal(t, blockChallengeCommit.LastLeaf, subChallengeCommit.LastLeaf) + }) + t.Run("produces a small step challenge commit", func(t *testing.T) { + blockChallengeCommit, err := provider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{}, + UpToHeight: option.Some(l2stateprovider.Height(1)), + }, + ) + require.NoError(t, err) + + smallStepSubchallengeCommit, err := provider.HistoryCommitment( + ctx, + &l2stateprovider.HistoryCommitmentRequest{ + AssertionMetadata: simpleAssertionMetadata(), + UpperChallengeOriginHeights: []l2stateprovider.Height{0, 0}, + UpToHeight: option.None[l2stateprovider.Height](), + }, + ) + require.NoError(t, err) + + require.Equal(t, uint64(challengeLeafHeights[2]), smallStepSubchallengeCommit.Height) + require.Equal(t, blockChallengeCommit.FirstLeaf, smallStepSubchallengeCommit.FirstLeaf) + }) +} diff --git a/bold/testing/mocks/state-provider/layer2_state_provider.go b/bold/testing/mocks/state-provider/layer2_state_provider.go new file mode 100644 index 0000000000..e1da78897c --- /dev/null +++ b/bold/testing/mocks/state-provider/layer2_state_provider.go @@ -0,0 +1,329 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package stateprovider defines smarter mocks for testing purposes that can +// simulate a layer 2 state provider and layer 2 state execution. +package stateprovider + +import ( + "context" + "errors" + "fmt" + "math/big" + "testing" + + "github.com/ccoveille/go-safecast" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/bold/api/db" + protocol "github.com/offchainlabs/bold/chain-abstraction" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + "github.com/offchainlabs/bold/state-commitments/history" + challenge_testing "github.com/offchainlabs/bold/testing" + "github.com/offchainlabs/bold/testing/casttest" +) + +// Defines the ABI encoding structure for submission of prefix proofs to the protocol contracts +var ( + b32Arr, _ = abi.NewType("bytes32[]", "", nil) + // ProofArgs for submission to the protocol. + ProofArgs = abi.Arguments{ + {Type: b32Arr, Name: "prefixExpansion"}, + {Type: b32Arr, Name: "prefixProof"}, + } +) + +// L2StateBackend defines a very naive state manager that is initialized from a list of predetermined +// state roots. It can produce state and history commitments from those roots. +type L2StateBackend struct { + l2stateprovider.HistoryCommitmentProvider + stateRoots []common.Hash + executionStates []*protocol.ExecutionState + machineAtBlock func(context.Context, uint64) (Machine, error) + maxWavmOpcodes uint64 + blockDivergenceHeight uint64 + posInBatchDivergence int64 + machineDivergenceStep uint64 + forceMachineBlockCompat bool + maliciousMachineIndex uint64 + numBigSteps uint64 + numBatches uint64 + challengeLeafHeights []l2stateprovider.Height +} + +// NewWithMockedStateRoots initialize with a list of predefined state roots, useful for tests and simulations. +func NewWithMockedStateRoots(stateRoots []common.Hash, opts ...Opt) (*L2StateBackend, error) { + if len(stateRoots) == 0 { + return nil, errors.New("no state roots provided") + } + s := &L2StateBackend{ + stateRoots: stateRoots, + machineAtBlock: func(context.Context, uint64) (Machine, error) { + return nil, errors.New("state manager created with New() cannot provide machines") + }, + numBigSteps: 1, + challengeLeafHeights: []l2stateprovider.Height{ + challenge_testing.LevelZeroBlockEdgeHeight, + challenge_testing.LevelZeroBigStepEdgeHeight, + challenge_testing.LevelZeroSmallStepEdgeHeight, + }, + } + for _, o := range opts { + o(s) + } + commitmentProvider := l2stateprovider.NewHistoryCommitmentProvider(s, s, s, s.challengeLeafHeights, s, nil) + s.HistoryCommitmentProvider = *commitmentProvider + return s, nil +} + +type Opt func(*L2StateBackend) + +func WithMaxWavmOpcodesPerBlock(maxOpcodes uint64) Opt { + return func(s *L2StateBackend) { + s.maxWavmOpcodes = maxOpcodes + } +} + +func WithMachineDivergenceStep(divergenceStep uint64) Opt { + return func(s *L2StateBackend) { + s.machineDivergenceStep = divergenceStep + } +} + +func WithBlockDivergenceHeight(divergenceHeight uint64) Opt { + return func(s *L2StateBackend) { + s.blockDivergenceHeight = divergenceHeight + } +} + +func WithDivergentBlockHeightOffset(blockHeightOffset int64) Opt { + return func(s *L2StateBackend) { + s.posInBatchDivergence = blockHeightOffset * 150 + } +} + +func WithMachineAtBlockProvider(machineAtBlock func(ctx context.Context, blockNum uint64) (Machine, error)) Opt { + return func(s *L2StateBackend) { + s.machineAtBlock = machineAtBlock + } +} + +// WithForceMachineBlockCompat if enabled, forces the machine hash at block boundaries to be the block hash +func WithForceMachineBlockCompat() Opt { + return func(s *L2StateBackend) { + s.forceMachineBlockCompat = true + } +} + +func WithLayerZeroHeights(heights *protocol.LayerZeroHeights, numBigSteps uint8) Opt { + return func(s *L2StateBackend) { + challengeLeafHeights := make([]l2stateprovider.Height, 0) + challengeLeafHeights = append(challengeLeafHeights, l2stateprovider.Height(heights.BlockChallengeHeight)) + for i := uint8(0); i < numBigSteps; i++ { + challengeLeafHeights = append(challengeLeafHeights, l2stateprovider.Height(heights.BigStepChallengeHeight)) + } + challengeLeafHeights = append(challengeLeafHeights, l2stateprovider.Height(heights.SmallStepChallengeHeight)) + s.challengeLeafHeights = challengeLeafHeights + } +} + +func WithMaliciousMachineIndex(index uint64) Opt { + return func(s *L2StateBackend) { + s.maliciousMachineIndex = index + } +} + +func WithNumBatchesRead(n uint64) Opt { + return func(s *L2StateBackend) { + s.numBatches = n + } +} + +func NewForSimpleMachine( + t testing.TB, + opts ...Opt, +) (*L2StateBackend, error) { + s := &L2StateBackend{ + maliciousMachineIndex: 0, + challengeLeafHeights: []l2stateprovider.Height{ + challenge_testing.LevelZeroBlockEdgeHeight, + challenge_testing.LevelZeroBigStepEdgeHeight, + challenge_testing.LevelZeroSmallStepEdgeHeight, + }, + numBatches: 1, + } + for _, o := range opts { + o(s) + } + commitmentProvider := l2stateprovider.NewHistoryCommitmentProvider(s, s, s, s.challengeLeafHeights, s, nil) + s.HistoryCommitmentProvider = *commitmentProvider + totalWavmOpcodes := uint64(1) + for _, h := range s.challengeLeafHeights[1:] { + totalWavmOpcodes *= uint64(h) + } + s.maxWavmOpcodes = totalWavmOpcodes + if s.maxWavmOpcodes == 0 { + return nil, errors.New("maxWavmOpcodes cannot be zero") + } + if s.blockDivergenceHeight > 0 && s.machineDivergenceStep == 0 { + return nil, errors.New("machineDivergenceStep cannot be zero if blockDivergenceHeight is non-zero") + } + nextMachineState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{}, + MachineStatus: protocol.MachineStatusFinished, + } + maxBatchesRead := big.NewInt(casttest.ToInt64(t, s.numBatches)) + for block := uint64(0); ; block++ { + machine := NewSimpleMachine(nextMachineState, maxBatchesRead) + state := machine.GetExecutionState() + machHash := machine.Hash() + if machHash != state.GlobalState.Hash() { + return nil, fmt.Errorf("machine at block %v has hash %v but we expected hash %v", block, machine.Hash(), state.GlobalState.Hash()) + } + if s.blockDivergenceHeight > 0 { + if block == s.blockDivergenceHeight { + // Note: blockHeightOffset might be negative, but two's complement subtraction works regardless + state.GlobalState.PosInBatch -= casttest.ToUint64(t, s.posInBatchDivergence) + } + if block >= s.blockDivergenceHeight { + state.GlobalState.BlockHash[s.maliciousMachineIndex] = 1 + } + machHash = protocol.ComputeSimpleMachineChallengeHash(state) + } + s.executionStates = append(s.executionStates, state) + s.stateRoots = append(s.stateRoots, machHash) + + if machine.IsStopped() || state.GlobalState.Batch >= s.numBatches { + break + } + err := machine.Step(s.maxWavmOpcodes) + if err != nil { + return nil, err + } + nextMachineState = machine.GetExecutionState() + } + s.machineAtBlock = func(_ context.Context, block uint64) (Machine, error) { + if block >= uint64(len(s.executionStates)) { + block = casttest.ToUint64(t, len(s.executionStates)-1) + } + return NewSimpleMachine(s.executionStates[block], maxBatchesRead), nil + } + return s, nil +} + +func (s *L2StateBackend) UpdateAPIDatabase(database db.Database) { + commitmentProvider := l2stateprovider.NewHistoryCommitmentProvider(s, s, s, s.challengeLeafHeights, s, database) + s.HistoryCommitmentProvider = *commitmentProvider +} + +// ExecutionStateAfterPreviousState produces the l2 state to assert at the message number specified. +func (s *L2StateBackend) ExecutionStateAfterPreviousState(ctx context.Context, maxInboxCount uint64, previousGlobalState protocol.GoGlobalState) (*protocol.ExecutionState, error) { + if len(s.executionStates) == 0 { + return nil, errors.New("no execution states") + } + if maxInboxCount >= uint64(len(s.executionStates)) { + return nil, fmt.Errorf("message number %v is greater than number of execution states %v", maxInboxCount, len(s.executionStates)) + } + blocksSincePrevious := -1 + for _, st := range s.executionStates { + if st.GlobalState.Equals(previousGlobalState) { + blocksSincePrevious = 0 + } + bsp64, err := safecast.ToUint64(blocksSincePrevious + 1) + if err != nil { + return nil, fmt.Errorf("could not convert blocksSincePrevious to uint64: %w", err) + } + if st.GlobalState.Batch == maxInboxCount || (blocksSincePrevious >= 0 && bsp64 >= uint64(s.challengeLeafHeights[0])) { + if blocksSincePrevious < 0 { + return nil, fmt.Errorf("missing previous global state %+v", previousGlobalState) + } + // Compute the history commitment for the assertion state. + fromBatch := previousGlobalState.Batch + historyCommit, err := s.statesUpTo(0, uint64(s.challengeLeafHeights[0]), fromBatch, st.GlobalState.Batch) + if err != nil { + return nil, err + } + commit, err := history.NewCommitment(historyCommit, uint64(s.challengeLeafHeights[0])+1) + if err != nil { + return nil, err + } + st.EndHistoryRoot = commit.Merkle + return st, nil + } + if blocksSincePrevious >= 0 { + blocksSincePrevious++ + } + } + return nil, fmt.Errorf("no execution state at message number %d found", maxInboxCount) +} + +func (s *L2StateBackend) statesUpTo(blockStart, blockEnd, fromBatch, toBatch uint64) ([]common.Hash, error) { + if blockEnd < blockStart { + return nil, fmt.Errorf("end block %v is less than start block %v", blockEnd, blockStart) + } + var err error + var startIndex uint64 + for i, st := range s.executionStates { + if st.GlobalState.Batch == fromBatch { + startIndex, err = safecast.ToUint64(i) + if err != nil { + return nil, fmt.Errorf("could not convert start index to uint64: %w", err) + } + break + } + } + start := startIndex + blockStart + end := start + blockEnd + + var states []common.Hash + for i := start; i <= end; i++ { + if i >= uint64(len(s.stateRoots)) { + break + } + state := s.stateRoots[i] + states = append(states, state) + if len(s.executionStates) == 0 { + // should only happen in tests + continue + } + gs := s.executionStates[i].GlobalState + if gs.Batch >= toBatch { + if gs.Batch > toBatch || gs.PosInBatch > 0 { + return nil, fmt.Errorf("overran next batch count %v with global state batch %v position %v", toBatch, gs.Batch, gs.PosInBatch) + } + break + } + } + return states, nil +} + +func (s *L2StateBackend) maybeDivergeState(state *protocol.ExecutionState, block uint64, step uint64) { + if block+1 == s.blockDivergenceHeight && step == s.maxWavmOpcodes { + *state = *s.executionStates[block+1] + } + if block+1 > s.blockDivergenceHeight || step >= s.machineDivergenceStep { + state.GlobalState.BlockHash[s.maliciousMachineIndex] = 1 + } +} + +// May modify the machine hash if divergence is enabled +func (s *L2StateBackend) getMachineHash(machine Machine, block uint64) common.Hash { + if s.forceMachineBlockCompat { + step := machine.CurrentStepNum() + if step == 0 { + return s.stateRoots[block] + } + if step == s.maxWavmOpcodes { + return s.stateRoots[block+1] + } + } + if s.blockDivergenceHeight == 0 || block+1 < s.blockDivergenceHeight { + return machine.Hash() + } + state := machine.GetExecutionState() + s.maybeDivergeState(state, block, machine.CurrentStepNum()) + return protocol.ComputeSimpleMachineChallengeHash(state) +} diff --git a/bold/testing/mocks/state-provider/layer2_state_provider_test.go b/bold/testing/mocks/state-provider/layer2_state_provider_test.go new file mode 100644 index 0000000000..cc308f9b13 --- /dev/null +++ b/bold/testing/mocks/state-provider/layer2_state_provider_test.go @@ -0,0 +1,104 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md +package stateprovider + +import ( + "context" + "crypto/rand" + "encoding/binary" + "errors" + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + challenge_testing "github.com/offchainlabs/bold/testing" +) + +func mockMachineAtBlock(_ context.Context, block uint64) (Machine, error) { + blockBytes := make([]uint8, 8) + binary.BigEndian.PutUint64(blockBytes, block) + startState := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: crypto.Keccak256Hash(blockBytes), + }, + MachineStatus: protocol.MachineStatusFinished, + } + return NewSimpleMachine(startState, nil), nil +} +func setupStates(t *testing.T, numStates, divergenceHeight uint64) ([]*protocol.ExecutionState, []common.Hash) { + t.Helper() + states := make([]*protocol.ExecutionState, numStates) + roots := make([]common.Hash, numStates) + for i := uint64(0); i < numStates; i++ { + var blockHash common.Hash + if divergenceHeight == 0 || i < divergenceHeight { + blockHash = crypto.Keccak256Hash([]byte(fmt.Sprintf("%d", i))) + } else { + junkRoot := make([]byte, 32) + _, err := rand.Read(junkRoot) + require.NoError(t, err) + blockHash = crypto.Keccak256Hash(junkRoot) + } + state := &protocol.ExecutionState{ + GlobalState: protocol.GoGlobalState{ + BlockHash: blockHash, + Batch: 0, + PosInBatch: i, + }, + MachineStatus: protocol.MachineStatusFinished, + } + if i+1 == numStates { + state.GlobalState.Batch = 1 + state.GlobalState.PosInBatch = 0 + } + states[i] = state + roots[i] = protocol.ComputeSimpleMachineChallengeHash(state) + } + return states, roots +} + +func newTestingMachine( + assertionChainExecutionStates []*protocol.ExecutionState, + opts ...Opt, +) (*L2StateBackend, error) { + if len(assertionChainExecutionStates) == 0 { + return nil, errors.New("must have execution states") + } + stateRoots := make([]common.Hash, len(assertionChainExecutionStates)) + var lastBatch uint64 = math.MaxUint64 + var lastPosInBatch uint64 = math.MaxUint64 + for i := 0; i < len(stateRoots); i++ { + state := assertionChainExecutionStates[i] + if state.GlobalState.Batch == lastBatch && state.GlobalState.PosInBatch == lastPosInBatch { + return nil, fmt.Errorf("execution states %v and %v have the same batch %v and position in batch %v", i-1, i, lastBatch, lastPosInBatch) + } + lastBatch = state.GlobalState.Batch + lastPosInBatch = state.GlobalState.PosInBatch + stateRoots[i] = protocol.ComputeSimpleMachineChallengeHash(state) + } + s := &L2StateBackend{ + stateRoots: stateRoots, + executionStates: assertionChainExecutionStates, + machineAtBlock: func(context.Context, uint64) (Machine, error) { + return nil, errors.New("state manager created with NewWithAssertionStates() cannot provide machines") + }, + numBigSteps: 1, + challengeLeafHeights: []l2stateprovider.Height{ + challenge_testing.LevelZeroBlockEdgeHeight, + challenge_testing.LevelZeroBigStepEdgeHeight, + challenge_testing.LevelZeroSmallStepEdgeHeight, + }, + } + for _, o := range opts { + o(s) + } + return s, nil +} diff --git a/bold/testing/rollup_config.go b/bold/testing/rollup_config.go new file mode 100644 index 0000000000..32ab0bb8f8 --- /dev/null +++ b/bold/testing/rollup_config.go @@ -0,0 +1,121 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package challenge_testing + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" +) + +const ( + LevelZeroBlockEdgeHeight = 1 << 5 + LevelZeroBigStepEdgeHeight = 1 << 5 + LevelZeroSmallStepEdgeHeight = 1 << 5 + MaxDataSize = 117964 +) + +type Opt func(c *rollupgen.Config) + +func WithNumBigStepLevels(num uint8) Opt { + return func(c *rollupgen.Config) { + c.NumBigStepLevel = num + } +} + +func WithLayerZeroHeights(h *protocol.LayerZeroHeights) Opt { + return func(c *rollupgen.Config) { + c.LayerZeroBlockEdgeHeight = new(big.Int).SetUint64(h.BlockChallengeHeight.Uint64()) + c.LayerZeroBigStepEdgeHeight = new(big.Int).SetUint64(h.BigStepChallengeHeight.Uint64()) + c.LayerZeroSmallStepEdgeHeight = new(big.Int).SetUint64(h.SmallStepChallengeHeight.Uint64()) + } +} + +func WithConfirmPeriodBlocks(num uint64) Opt { + return func(c *rollupgen.Config) { + c.ConfirmPeriodBlocks = num + } +} + +func WithChallengeGracePeriodBlocks(num uint64) Opt { + return func(c *rollupgen.Config) { + c.ChallengeGracePeriodBlocks = num + } +} + +func WithBaseStakeValue(num *big.Int) Opt { + return func(c *rollupgen.Config) { + c.BaseStake = num + } +} + +func WithChainConfig(cfg string) Opt { + return func(c *rollupgen.Config) { + c.ChainConfig = cfg + } +} + +func GenerateRollupConfig( + prod bool, + wasmModuleRoot common.Hash, + rollupOwner common.Address, + chainId *big.Int, + loserStakeEscrow common.Address, + miniStakeValues []*big.Int, + stakeToken common.Address, + genesisExecutionState rollupgen.AssertionState, + genesisInboxCount *big.Int, + anyTrustFastConfirmer common.Address, + opts ...Opt, +) rollupgen.Config { + var confirmPeriod uint64 + if prod { + confirmPeriod = 45818 + } else { + confirmPeriod = 25 + } + + var gracePeriod uint64 + if prod { + gracePeriod = 14400 + } else { + gracePeriod = 3 + } + + cfg := rollupgen.Config{ + MiniStakeValues: miniStakeValues, + ConfirmPeriodBlocks: confirmPeriod, + StakeToken: stakeToken, + BaseStake: big.NewInt(1), + WasmModuleRoot: wasmModuleRoot, + Owner: rollupOwner, + LoserStakeEscrow: loserStakeEscrow, + ChainId: chainId, + ChainConfig: "{ 'config': 'Test config'}", + MinimumAssertionPeriod: big.NewInt(75), + ValidatorAfkBlocks: 201600, + SequencerInboxMaxTimeVariation: rollupgen.ISequencerInboxMaxTimeVariation{ + DelayBlocks: big.NewInt(60 * 60 * 24 / 15), + FutureBlocks: big.NewInt(12), + DelaySeconds: big.NewInt(60 * 60 * 24), + FutureSeconds: big.NewInt(60 * 60), + }, + LayerZeroBlockEdgeHeight: big.NewInt(LevelZeroBlockEdgeHeight), + LayerZeroBigStepEdgeHeight: big.NewInt(LevelZeroBigStepEdgeHeight), + LayerZeroSmallStepEdgeHeight: big.NewInt(LevelZeroSmallStepEdgeHeight), + GenesisAssertionState: genesisExecutionState, + GenesisInboxCount: genesisInboxCount, + AnyTrustFastConfirmer: anyTrustFastConfirmer, + NumBigStepLevel: 1, + ChallengeGracePeriodBlocks: gracePeriod, + } + for _, o := range opts { + o(&cfg) + } + return cfg +} diff --git a/bold/testing/setup/rollup_stack.go b/bold/testing/setup/rollup_stack.go new file mode 100644 index 0000000000..f16236ef23 --- /dev/null +++ b/bold/testing/setup/rollup_stack.go @@ -0,0 +1,1160 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +// Package setup prepares a simulated backend for testing. +package setup + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + "strings" + "testing" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rpc" + + protocol "github.com/offchainlabs/bold/chain-abstraction" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + retry "github.com/offchainlabs/bold/runtime" + challenge_testing "github.com/offchainlabs/bold/testing" + statemanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/contractsgen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/ospgen" + "github.com/offchainlabs/nitro/solgen/go/proxiesgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" + "github.com/offchainlabs/nitro/solgen/go/yulgen" +) + +type Committer interface { + Commit() common.Hash +} + +type CreatedValidatorFork struct { + Leaf1 protocol.Assertion + Leaf2 protocol.Assertion + Chains []*solimpl.AssertionChain + Accounts []*TestAccount + Backend *SimulatedBackendWrapper + HonestStateManager l2stateprovider.Provider + EvilStateManager l2stateprovider.Provider + Addrs *RollupAddresses +} + +type CreateForkConfig struct { + DivergeBlockHeight uint64 + BlockHeightDifference int64 + DivergeMachineHeight uint64 + BlockChallengeHeight uint64 +} + +func CreateTwoValidatorFork( + ctx context.Context, + t testing.TB, + cfg *CreateForkConfig, + opts ...Opt, +) (*CreatedValidatorFork, error) { + t.Helper() + setup, err := ChainsWithEdgeChallengeManager(opts...) + if err != nil { + return nil, err + } + + // Advance the backend by some blocks to get over time delta errors when + // using the assertion chain. + for i := 0; i < 100; i++ { + setup.Backend.Commit() + } + + genesisHash, err := setup.Chains[1].GenesisAssertionHash(ctx) + if err != nil { + return nil, err + } + genesisCreationInfo, err := setup.Chains[1].ReadAssertionCreationInfo(ctx, protocol.AssertionHash{Hash: genesisHash}) + if err != nil { + return nil, err + } + + honestStateManager, err := statemanager.NewForSimpleMachine(t, setup.StateManagerOpts...) + if err != nil { + return nil, err + } + + // Set defaults (zeroes are not valid here) + if cfg.DivergeBlockHeight == 0 { + cfg.DivergeBlockHeight = 1 + } + if cfg.DivergeMachineHeight == 0 { + cfg.DivergeMachineHeight = 1 + } + if cfg.BlockChallengeHeight == 0 { + cfg.BlockChallengeHeight = 1 << 5 + } + + stateManagerOpts := setup.StateManagerOpts + stateManagerOpts = append( + stateManagerOpts, + statemanager.WithBlockDivergenceHeight(cfg.DivergeBlockHeight), + statemanager.WithDivergentBlockHeightOffset(cfg.BlockHeightDifference), + statemanager.WithMachineDivergenceStep(cfg.DivergeMachineHeight), + ) + evilStateManager, err := statemanager.NewForSimpleMachine(t, stateManagerOpts...) + if err != nil { + return nil, err + } + genesis, err := honestStateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + if err != nil { + return nil, err + } + honestPostState, err := honestStateManager.ExecutionStateAfterPreviousState(ctx, 1, genesis.GlobalState) + if err != nil { + return nil, err + } + assertion, err := setup.Chains[0].NewStakeOnNewAssertion( + ctx, + genesisCreationInfo, + honestPostState, + ) + if err != nil { + return nil, err + } + + genesis, err = evilStateManager.ExecutionStateAfterPreviousState(ctx, 0, protocol.GoGlobalState{}) + if err != nil { + return nil, err + } + evilPostState, err := evilStateManager.ExecutionStateAfterPreviousState(ctx, 1, genesis.GlobalState) + if err != nil { + return nil, err + } + forkedAssertion, err := setup.Chains[1].NewStakeOnNewAssertion( + ctx, + genesisCreationInfo, + evilPostState, + ) + if err != nil { + return nil, err + } + + return &CreatedValidatorFork{ + Leaf1: assertion, + Leaf2: forkedAssertion, + Chains: setup.Chains, + Accounts: setup.Accounts, + Backend: setup.Backend, + Addrs: setup.Addrs, + HonestStateManager: honestStateManager, + EvilStateManager: evilStateManager, + }, nil +} + +type ChainSetup struct { + Chains []*solimpl.AssertionChain + Accounts []*TestAccount + Addrs *RollupAddresses + Backend *SimulatedBackendWrapper + RollupConfig rollupgen.Config + useMockBridge bool + useMockOneStepProver bool + numAccountsToGen uint64 + numFundedAccounts uint64 + minimumAssertionPeriod int64 + autoDeposit bool + challengeTestingOpts []challenge_testing.Opt + StateManagerOpts []statemanager.Opt + StakeTokenAddress common.Address + EnableFastConfirmation bool + EnableSafeFastConfirmation bool +} + +type Opt func(setup *ChainSetup) + +func WithMockOneStepProver() Opt { + return func(setup *ChainSetup) { + setup.useMockOneStepProver = true + } +} + +func WithFastConfirmation() Opt { + return func(setup *ChainSetup) { + setup.EnableFastConfirmation = true + } +} + +func WithSafeFastConfirmation() Opt { + return func(setup *ChainSetup) { + setup.EnableSafeFastConfirmation = true + } +} + +func WithMockBridge() Opt { + return func(setup *ChainSetup) { + setup.useMockBridge = false + } +} + +func WithMinimumAssertionPeriod(period int64) Opt { + return func(setup *ChainSetup) { + setup.minimumAssertionPeriod = period + } +} + +func WithChallengeTestingOpts(opts ...challenge_testing.Opt) Opt { + return func(setup *ChainSetup) { + setup.challengeTestingOpts = opts + } +} + +func WithStateManagerOpts(opts ...statemanager.Opt) Opt { + return func(setup *ChainSetup) { + setup.StateManagerOpts = opts + } +} + +func WithNumAccounts(n uint64) Opt { + return func(setup *ChainSetup) { + setup.numAccountsToGen = n + } +} + +func WithNumFundedAccounts(n uint64) Opt { + return func(setup *ChainSetup) { + setup.numFundedAccounts = n + } +} + +func WithAutoDeposit() Opt { + return func(setup *ChainSetup) { + setup.autoDeposit = true + } +} + +func ChainsWithEdgeChallengeManager(opts ...Opt) (*ChainSetup, error) { + ctx := context.Background() + setp := &ChainSetup{ + numAccountsToGen: 4, + autoDeposit: false, + } + for _, o := range opts { + o(setp) + } + if setp.numAccountsToGen < 3 { + setp.numAccountsToGen = 3 + } + accs, backend, err := Accounts(setp.numAccountsToGen) + if err != nil { + return nil, err + } + stakeToken, tx, tokenBindings, err := mocksgen.DeployTestWETH9( + accs[0].TxOpts, + backend, + "Weth", + "WETH", + ) + if err != nil { + return nil, err + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, tx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transaction") + } + receipt, err := backend.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + return nil, err + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + value, ok := new(big.Int).SetString("10000000000000000000000", 10) + if !ok { + return nil, errors.New("could not set value") + } + if !setp.autoDeposit { + accs[0].TxOpts.Value = value + mintTx, err3 := tokenBindings.Deposit(accs[0].TxOpts) + if err3 != nil { + return nil, err3 + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, mintTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transaction") + } + receipt, err = backend.TransactionReceipt(ctx, mintTx.Hash()) + if err != nil { + return nil, err + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + accs[0].TxOpts.Value = big.NewInt(0) + } + + prod := false + wasmModuleRoot := common.Hash{} + rollupOwner := accs[0].AccountAddr + chainId := big.NewInt(1337) + loserStakeEscrow := rollupOwner + cfgOpts := &rollupgen.Config{} + for _, o := range setp.challengeTestingOpts { + o(cfgOpts) + } + if setp.EnableFastConfirmation { + cfgOpts.AnyTrustFastConfirmer = accs[1].AccountAddr + } + var safeProxyAddress common.Address + if setp.EnableSafeFastConfirmation { + var safeAddress common.Address + safeAddress, err = retry.UntilSucceeds(ctx, func() (common.Address, error) { + safeAddress, tx, _, err = contractsgen.DeploySafeL2(accs[0].TxOpts, backend) + if err != nil { + return common.Address{}, err + } + err = challenge_testing.TxSucceeded(ctx, tx, safeAddress, backend, err) + if err != nil { + return common.Address{}, err + } + return safeAddress, nil + }) + if err != nil { + return nil, err + } + + safeProxyAddress, err = retry.UntilSucceeds(ctx, func() (common.Address, error) { + safeProxyAddress, tx, _, err = proxiesgen.DeploySafeProxy(accs[0].TxOpts, backend, safeAddress) + if err != nil { + return common.Address{}, err + } + err = challenge_testing.TxSucceeded(ctx, tx, safeProxyAddress, backend, err) + if err != nil { + return common.Address{}, err + } + return safeProxyAddress, nil + }) + if err != nil { + return nil, err + } + var safe *contractsgen.Safe + safe, err = contractsgen.NewSafe(safeProxyAddress, backend) + if err != nil { + return nil, err + } + tx, err = safe.Setup( + accs[0].TxOpts, + []common.Address{accs[1].AccountAddr, accs[2].AccountAddr, accs[3].AccountAddr}, + big.NewInt(2), + common.Address{}, + nil, + common.Address{}, + common.Address{}, + big.NewInt(0), + common.Address{}, + ) + if err != nil { + return nil, err + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, tx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transaction") + } + cfgOpts.AnyTrustFastConfirmer = safeProxyAddress + } + numLevels := cfgOpts.NumBigStepLevel + 2 + if numLevels == 2 { + numLevels = 3 + } + miniStakeValues := make([]*big.Int, numLevels) + for i := 1; i <= int(numLevels); i++ { + miniStakeValues[i-1] = big.NewInt(int64(i)) + } + genesisExecutionState := rollupgen.AssertionState{ + GlobalState: rollupgen.GlobalState{}, + MachineStatus: 1, + } + genesisInboxCount := big.NewInt(0) + anyTrustFastConfirmer := cfgOpts.AnyTrustFastConfirmer + cfg := challenge_testing.GenerateRollupConfig( + prod, + wasmModuleRoot, + rollupOwner, + chainId, + loserStakeEscrow, + miniStakeValues, + stakeToken, + genesisExecutionState, + genesisInboxCount, + anyTrustFastConfirmer, + setp.challengeTestingOpts..., + ) + addresses, err := DeployFullRollupStack( + ctx, + backend, + accs[0].TxOpts, + accs[0].TxOpts.From, // Sequencer addr. + cfg, + RollupStackConfig{ + UseMockBridge: setp.useMockBridge, + UseMockOneStepProver: setp.useMockOneStepProver, + UseBlobs: true, + MinimumAssertionPeriod: setp.minimumAssertionPeriod, + }, + ) + if err != nil { + return nil, err + } + + chains := make([]*solimpl.AssertionChain, 0) + for _, acc := range accs[1:] { + var assertionChainBinding *rollupgen.RollupUserLogic + assertionChainBinding, err = rollupgen.NewRollupUserLogic( + addresses.Rollup, backend, + ) + if err != nil { + return nil, err + } + var challengeManagerAddr common.Address + challengeManagerAddr, err = assertionChainBinding.ChallengeManager( + &bind.CallOpts{Context: ctx}, + ) + if err != nil { + return nil, err + } + assertionChainOpts := []solimpl.Opt{ + solimpl.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), + } + if setp.EnableSafeFastConfirmation || (setp.EnableFastConfirmation && acc.AccountAddr == cfgOpts.AnyTrustFastConfirmer) { + assertionChainOpts = append(assertionChainOpts, solimpl.WithFastConfirmation()) + } + chain, chainErr := solimpl.NewAssertionChain( + ctx, + addresses.Rollup, + challengeManagerAddr, + acc.TxOpts, + backend, + solimpl.NewChainBackendTransactor(backend), + assertionChainOpts..., + ) + if chainErr != nil { + return nil, chainErr + } + chains = append(chains, chain) + } + chalManager := chains[1].SpecChallengeManager() + chalManagerAddr := chalManager.Address() + seed, ok := new(big.Int).SetString("10000", 10) + if !ok { + return nil, errors.New("could not set big int") + } + if !setp.autoDeposit { + for i := 0; i < len(accs); i++ { + acc := accs[i] + transferTx, err := tokenBindings.Transfer(accs[0].TxOpts, acc.TxOpts.From, seed) + if err != nil { + return nil, errors.Wrap(err, "could not approve account") + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, transferTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for transfer transaction") + } + receipt, err := backend.TransactionReceipt(ctx, transferTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx receipt") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + approveTx, err := tokenBindings.Approve(acc.TxOpts, addresses.Rollup, value) + if err != nil { + return nil, errors.Wrap(err, "could not approve account") + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, approveTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for approval transaction") + } + receipt, err = backend.TransactionReceipt(ctx, approveTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx receipt") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + approveTx, err = tokenBindings.Approve(acc.TxOpts, chalManagerAddr, value) + if err != nil { + return nil, errors.Wrap(err, "could not approve account") + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, approveTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for approval transaction") + } + receipt, err = backend.TransactionReceipt(ctx, approveTx.Hash()) + if err != nil { + return nil, errors.Wrap(err, "could not get tx receipt") + } + if receipt.Status != types.ReceiptStatusSuccessful { + return nil, errors.New("receipt not successful") + } + } + } + + setp.Chains = chains + setp.Accounts = accs + setp.Addrs = addresses + setp.Backend = backend + setp.RollupConfig = cfg + setp.StakeTokenAddress = stakeToken + return setp, nil +} + +type RollupAddresses struct { + Bridge common.Address `json:"bridge"` + Inbox common.Address `json:"inbox"` + SequencerInbox common.Address `json:"sequencer-inbox"` + Rollup common.Address `json:"rollup"` + RollupUserLogic common.Address `json:"rollup-user-logic"` + ValidatorUtils common.Address `json:"validator-utils"` + ValidatorWalletCreator common.Address `json:"validator-wallet-creator"` + UpgradeExecutor common.Address `json:"upgrade-executor"` + DeployedAt uint64 `json:"deployed-at"` +} + +type RollupStackConfig struct { + UseMockBridge bool + UseMockOneStepProver bool + UseBlobs bool + MinimumAssertionPeriod int64 +} + +func DeployFullRollupStack( + ctx context.Context, + backend protocol.ChainBackend, + deployAuth *bind.TransactOpts, + sequencer common.Address, + config rollupgen.Config, + stackConf RollupStackConfig, +) (*RollupAddresses, error) { + log.Info("Deploying rollup creator") + rollupCreator, rollupUserAddr, rollupCreatorAddress, validatorUtils, validatorWalletCreator, err := deployRollupCreator(ctx, backend, deployAuth, stackConf.UseBlobs, stackConf.UseMockBridge, stackConf.UseMockOneStepProver) + if err != nil { + return nil, err + } + + log.Info("Creating rollup") + tx, err := retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + creationTx, creationErr := rollupCreator.CreateRollup( + deployAuth, + rollupgen.RollupCreatorRollupDeploymentParams{ + Config: config, + Validators: []common.Address{}, + MaxDataSize: big.NewInt(challenge_testing.MaxDataSize), + NativeToken: common.Address{}, + DeployFactoriesToL2: false, + MaxFeePerGasForRetryables: big.NewInt(0), + BatchPosters: []common.Address{}, + BatchPosterManager: common.Address{}, + }, + ) + if creationErr != nil { + fmt.Println(creationErr) + return nil, creationErr + } + err = challenge_testing.TxSucceeded(ctx, creationTx, rollupCreatorAddress, backend, err) + if err != nil { + return nil, err + } + return creationTx, nil + }) + if err != nil { + return nil, err + } + + creationReceipt, err := backend.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + return nil, err + } + info, err := rollupCreator.ParseRollupCreated(*creationReceipt.Logs[len(creationReceipt.Logs)-1]) + if err != nil { + return nil, err + } + + upgradeExecBindings, err := mocksgen.NewUpgradeExecutorMock(info.UpgradeExecutor, backend) + if err != nil { + return nil, err + } + + rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) + if err != nil { + return nil, err + } + setWhitelistDisabled, err := rollupABI.Pack("setValidatorWhitelistDisabled", true) + if err != nil { + return nil, err + } + setMinimumAssertionPeriod, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(stackConf.MinimumAssertionPeriod)) + if err != nil { + return nil, err + } + seqInboxABI, err := abi.JSON(strings.NewReader(bridgegen.SequencerInboxABI)) + if err != nil { + return nil, err + } + setBatchPosterManager, err := seqInboxABI.Pack("setBatchPosterManager", deployAuth.From) + if err != nil { + return nil, err + } + txs := map[common.Address][][]byte{ + info.RollupAddress: {setWhitelistDisabled, setMinimumAssertionPeriod}, + info.SequencerInbox: {setBatchPosterManager}, + } + // if a zero sequencer address is specified, don't authorize any sequencers + if sequencer != (common.Address{}) { + setIsBatchPoster, err2 := seqInboxABI.Pack("setIsBatchPoster", sequencer, true) + if err2 != nil { + return nil, err2 + } + txs[info.SequencerInbox] = append(txs[info.SequencerInbox], setIsBatchPoster) + } + for addr, items := range txs { + for _, item := range items { + _, err = retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + innerTx, err2 := upgradeExecBindings.ExecuteCall(deployAuth, addr, item) + if err2 != nil { + return nil, err2 + } + if waitErr := challenge_testing.WaitForTx(ctx, backend, innerTx); waitErr != nil { + return nil, errors.Wrap(waitErr, "errored waiting for UpgradeExecutor transaction") + } + return innerTx, nil + }) + if err != nil { + return nil, err + } + } + } + if committer, ok := backend.(Committer); ok { + committer.Commit() + } + if !creationReceipt.BlockNumber.IsUint64() { + return nil, errors.New("block number was not a uint64") + } + log.Info("Done deploying") + + return &RollupAddresses{ + Bridge: info.Bridge, + Inbox: info.InboxAddress, + SequencerInbox: info.SequencerInbox, + DeployedAt: creationReceipt.BlockNumber.Uint64(), + Rollup: info.RollupAddress, + RollupUserLogic: rollupUserAddr, + ValidatorUtils: validatorUtils, + ValidatorWalletCreator: validatorWalletCreator, + UpgradeExecutor: info.UpgradeExecutor, + }, nil +} + +func deployBridgeCreator( + ctx context.Context, + auth *bind.TransactOpts, + useBlobs bool, + backend protocol.ChainBackend, + useMockBridge bool, +) (common.Address, error) { + var bridgeTemplate common.Address + var err error + if useMockBridge { + bridgeTemplate, _, _, err = mocksgen.DeployBridgeStub(auth, backend) + if err != nil { + return common.Address{}, err + } + } else { + log.Info("Deploying bridge template") + bridgeTemplate, err = retry.UntilSucceeds(ctx, func() (common.Address, error) { + bridgeTemplateAddr, tx, _, err2 := bridgegen.DeployBridge(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, bridgeTemplateAddr, backend, err2) + if err2 != nil { + return common.Address{}, errors.Wrap(err2, "bridgegen.DeployBridge") + } + return bridgeTemplateAddr, nil + }) + if err != nil { + return common.Address{}, err + } + } + + var dataHashesReader common.Address + if useBlobs { + reader, err2 := retry.UntilSucceeds(ctx, func() (common.Address, error) { + readerAddr, tx, _, err3 := yulgen.DeployReader4844(auth, backend) + if err3 != nil { + return common.Address{}, err3 + } + err3 = challenge_testing.TxSucceeded(ctx, tx, readerAddr, backend, err3) + if err3 != nil { + return common.Address{}, errors.Wrap(err3, "yulgen.DeployReader4844") + } + return readerAddr, nil + }) + if err2 != nil { + return common.Address{}, err2 + } + dataHashesReader = reader + } + + maxDataSize := big.NewInt(challenge_testing.MaxDataSize) + log.Info("Deploying seq inbox") + seqInboxTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + seqInboxTemplateAddr, tx, _, err2 := bridgegen.DeploySequencerInbox(auth, backend, maxDataSize, dataHashesReader, false /* no fee token */, false /* disable delay buffer */) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, seqInboxTemplateAddr, backend, err2) + if err2 != nil { + return common.Address{}, errors.Wrap(err2, "bridgegen.DeploySequencerInbox") + } + return seqInboxTemplateAddr, nil + }) + if err != nil { + return common.Address{}, err + } + + log.Info("Deploying seq inbox bufferable") + seqInboxBufferableTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + seqInboxTemplateAddr, tx, _, err2 := bridgegen.DeploySequencerInbox(auth, backend, maxDataSize, dataHashesReader, false /* no fee token */, true /* enable delay buffer */) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, seqInboxTemplateAddr, backend, err2) + if err2 != nil { + return common.Address{}, errors.Wrap(err2, "bridgegen.DeploySequencerInbox") + } + return seqInboxTemplateAddr, nil + }) + if err != nil { + return common.Address{}, err + } + + log.Info("Deploying inbox") + inboxTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + inboxTemplateAddr, tx, _, err2 := bridgegen.DeployInbox(auth, backend, maxDataSize) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, inboxTemplateAddr, backend, err2) + if err2 != nil { + return common.Address{}, errors.Wrap(err2, "bridgegen.DeployInbox") + } + return inboxTemplateAddr, nil + }) + if err != nil { + return common.Address{}, err + } + + log.Info("Deploying event bridge") + rollupEventBridgeTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + rollupEventBridgeTemplateAddr, tx, _, err2 := mocksgen.DeployMockRollupEventInbox(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, rollupEventBridgeTemplateAddr, backend, err2) + if err2 != nil { + return common.Address{}, errors.Wrap(err2, "rollupgen.DeployRollupEventInbox") + } + return rollupEventBridgeTemplateAddr, nil + }) + if err != nil { + return common.Address{}, err + } + + log.Info("Deploying outbox") + outboxTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + outboxTemplateAddr, tx, _, err2 := bridgegen.DeployOutbox(auth, backend) + if err2 != nil { + return common.Address{}, err + } + err2 = challenge_testing.TxSucceeded(ctx, tx, outboxTemplateAddr, backend, err2) + if err2 != nil { + return common.Address{}, errors.Wrap(err2, "bridgegen.DeployOutbox") + } + return outboxTemplateAddr, nil + }) + if err != nil { + return common.Address{}, err + } + + ethTemplates := rollupgen.BridgeCreatorBridgeTemplates{ + SequencerInbox: seqInboxTemplate, + Bridge: bridgeTemplate, + Inbox: inboxTemplate, + RollupEventInbox: rollupEventBridgeTemplate, + Outbox: outboxTemplate, + DelayBufferableSequencerInbox: seqInboxBufferableTemplate, + } + + /// deploy ERC20 based templates + erc20BridgeTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + addr, _, _, innerErr := bridgegen.DeployERC20Bridge(auth, backend) + return addr, innerErr + }) + if err != nil { + return common.Address{}, err + } + + erc20InboxTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + addr, _, _, innerErr := bridgegen.DeployERC20Inbox(auth, backend, maxDataSize) + return addr, innerErr + }) + if err != nil { + return common.Address{}, err + } + + erc20RollupEventBridgeTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + addr, _, _, innerErr := rollupgen.DeployERC20RollupEventInbox(auth, backend) + return addr, innerErr + }) + if err != nil { + return common.Address{}, err + } + + erc20OutboxTemplate, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + addr, _, _, innerErr := bridgegen.DeployERC20Outbox(auth, backend) + return addr, innerErr + }) + if err != nil { + return common.Address{}, err + } + + erc20Templates := rollupgen.BridgeCreatorBridgeTemplates{ + Bridge: erc20BridgeTemplate, + SequencerInbox: seqInboxTemplate, + Inbox: erc20InboxTemplate, + RollupEventInbox: erc20RollupEventBridgeTemplate, + Outbox: erc20OutboxTemplate, + DelayBufferableSequencerInbox: seqInboxBufferableTemplate, + } + + type bridgeCreationResult struct { + bridgeCreatorAddr common.Address + bridgeCreator *rollupgen.BridgeCreator + } + log.Info("Deploying bridge creator itself") + result, err := retry.UntilSucceeds(ctx, func() (*bridgeCreationResult, error) { + bridgeCreatorAddr, tx, bridgeCreator, err2 := rollupgen.DeployBridgeCreator(auth, backend, ethTemplates, erc20Templates) + if err2 != nil { + return nil, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, bridgeCreatorAddr, backend, err2) + if err2 != nil { + return nil, err2 + } + return &bridgeCreationResult{ + bridgeCreatorAddr: bridgeCreatorAddr, + bridgeCreator: bridgeCreator, + }, nil + }) + if err != nil { + return common.Address{}, err + } + + log.Info("Updating bridge creator templates") + _, err = retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + tx, err2 := result.bridgeCreator.UpdateTemplates(auth, ethTemplates) + if err2 != nil { + return nil, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, result.bridgeCreatorAddr, backend, err2) + if err2 != nil { + return nil, err2 + } + return tx, nil + }) + if err != nil { + return common.Address{}, err + } + _, err = retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + tx, err2 := result.bridgeCreator.UpdateERC20Templates(auth, erc20Templates) + if err2 != nil { + return nil, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, result.bridgeCreatorAddr, backend, err2) + if err2 != nil { + return nil, err2 + } + return tx, nil + }) + if err != nil { + return common.Address{}, err + } + return result.bridgeCreatorAddr, nil +} + +func deployChallengeFactory( + ctx context.Context, + auth *bind.TransactOpts, + backend protocol.ChainBackend, + useMockOneStepProver bool, +) (common.Address, common.Address, error) { + var ospEntryAddr common.Address + if useMockOneStepProver { + ospEntry, tx, _, err := mocksgen.DeploySimpleOneStepProofEntry(auth, backend) + if waitErr := challenge_testing.WaitForTx(ctx, backend, tx); waitErr != nil { + return common.Address{}, common.Address{}, errors.Wrap(err, "mocksgen.DeployMockOneStepProofEntry") + } + ospEntryAddr = ospEntry + } else { + log.Info("Deploying osp0") + osp0, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + osp0Addr, _, _, err2 := ospgen.DeployOneStepProver0(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + return osp0Addr, nil + }) + if err != nil { + return common.Address{}, common.Address{}, err + } + log.Info("Deploying ospMem") + ospMem, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + ospMemAddr, _, _, err2 := ospgen.DeployOneStepProverMemory(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + return ospMemAddr, nil + }) + if err != nil { + return common.Address{}, common.Address{}, err + } + log.Info("Deploying ospMath") + ospMath, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + ospMathAddr, _, _, err2 := ospgen.DeployOneStepProverMath(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + return ospMathAddr, nil + }) + if err != nil { + return common.Address{}, common.Address{}, err + } + log.Info("Deploying ospHostIo") + ospHostIo, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + ospHostIoAddr, _, _, err2 := ospgen.DeployOneStepProverHostIo(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + return ospHostIoAddr, nil + }) + if err != nil { + return common.Address{}, common.Address{}, err + } + log.Info("Deploying ospEntry") + ospEntry, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + ospEntryAddr2, _, _, err2 := ospgen.DeployOneStepProofEntry(auth, backend, osp0, ospMem, ospMath, ospHostIo) + if err2 != nil { + return common.Address{}, err2 + } + return ospEntryAddr2, nil + }) + if err != nil { + return common.Address{}, common.Address{}, err + } + ospEntryAddr = ospEntry + } + log.Info("Deploying edge challenge manager") + edgeChallengeManagerAddr, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + edgeChallengeManagerAddr2, _, _, err2 := challengeV2gen.DeployEdgeChallengeManager( + auth, + backend, + ) + if err2 != nil { + return common.Address{}, err2 + } + return edgeChallengeManagerAddr2, nil + }) + if err != nil { + return common.Address{}, common.Address{}, err + } + return ospEntryAddr, edgeChallengeManagerAddr, nil +} + +func deployRollupCreator( + ctx context.Context, + backend protocol.ChainBackend, + auth *bind.TransactOpts, + useBlobs bool, + useMockBridge bool, + useMockOneStepProver bool, +) (*rollupgen.RollupCreator, common.Address, common.Address, common.Address, common.Address, error) { + log.Info("Deploying bridge creator contracts") + bridgeCreator, err := deployBridgeCreator(ctx, auth, useBlobs, backend, useMockBridge) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + log.Info("Deploying challenge factory contracts") + ospEntryAddr, challengeManagerAddr, err := deployChallengeFactory(ctx, auth, backend, useMockOneStepProver) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + + log.Info("Deploying admin logic contracts") + rollupAdminLogic, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + rollupAdminLogicAddr, tx, _, err2 := rollupgen.DeployRollupAdminLogic(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, rollupAdminLogicAddr, backend, err2) + if err2 != nil { + return common.Address{}, err2 + } + return rollupAdminLogicAddr, nil + }) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + + log.Info("Deploying user logic contracts") + rollupUserLogic, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + rollupUserLogicAddr, tx, _, err2 := rollupgen.DeployRollupUserLogic(auth, backend) + err2 = challenge_testing.TxSucceeded(ctx, tx, rollupUserLogicAddr, backend, err2) + if err2 != nil { + return common.Address{}, err2 + } + return rollupUserLogicAddr, nil + }) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + + type creatorResult struct { + rollupCreatorAddress common.Address + rollupCreator *rollupgen.RollupCreator + } + + log.Info("Deploying rollup creator contract") + result, err := retry.UntilSucceeds(ctx, func() (*creatorResult, error) { + rollupCreatorAddress, tx, rollupCreator, err2 := rollupgen.DeployRollupCreator(auth, backend) + if err2 != nil { + return nil, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, rollupCreatorAddress, backend, err2) + if err2 != nil { + return nil, err2 + } + return &creatorResult{rollupCreatorAddress: rollupCreatorAddress, rollupCreator: rollupCreator}, nil + }) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + + log.Info("Deploying validator wallet creator contract") + validatorWalletCreator, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + validatorWalletCreatorAddr, tx, _, err2 := rollupgen.DeployValidatorWalletCreator(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, validatorWalletCreatorAddr, backend, err2) + if err2 != nil { + return common.Address{}, err2 + } + return validatorWalletCreatorAddr, nil + }) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + + log.Info("Deploying upgrade executor mock") + upgradeExecutor, err := retry.UntilSucceeds(ctx, func() (common.Address, error) { + upgradeExecutorAddr, tx, _, err2 := mocksgen.DeployUpgradeExecutorMock(auth, backend) + if err2 != nil { + return common.Address{}, err2 + } + err2 = challenge_testing.TxSucceeded(ctx, tx, upgradeExecutorAddr, backend, err2) + if err2 != nil { + return common.Address{}, err2 + } + return upgradeExecutorAddr, nil + }) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + + log.Info("Setting rollup templates") + _, err = retry.UntilSucceeds(ctx, func() (*types.Transaction, error) { + tx, err2 := result.rollupCreator.SetTemplates( + auth, + bridgeCreator, + ospEntryAddr, + challengeManagerAddr, + rollupAdminLogic, + rollupUserLogic, + upgradeExecutor, + validatorWalletCreator, + common.Address{}, + ) + if err2 != nil { + return nil, err2 + } + if err2 := challenge_testing.WaitForTx(ctx, backend, tx); err2 != nil { + return nil, err2 + } + return tx, nil + }) + if err != nil { + return nil, common.Address{}, common.Address{}, common.Address{}, common.Address{}, err + } + return result.rollupCreator, rollupUserLogic, result.rollupCreatorAddress, common.Address{}, validatorWalletCreator, nil +} + +// TestAccount represents a test EOA account in the simulated backend, +type TestAccount struct { + AccountAddr common.Address + TxOpts *bind.TransactOpts +} + +func Accounts(numAccounts uint64) ([]*TestAccount, *SimulatedBackendWrapper, error) { + genesis := make(types.GenesisAlloc) + gasLimit := uint64(100000000) + + accs := make([]*TestAccount, numAccounts) + for i := uint64(0); i < numAccounts; i++ { + privKey, err := crypto.GenerateKey() + if err != nil { + return nil, nil, err + } + pubKeyECDSA, ok := privKey.Public().(*ecdsa.PublicKey) + if !ok { + return nil, nil, errors.New("not ecdsa") + } + + // Strip off the 0x and the first 2 characters 04 which is always the + // EC prefix and is not required. + publicKeyBytes := crypto.FromECDSAPub(pubKeyECDSA)[4:] + var pubKey = make([]byte, 48) + copy(pubKey, publicKeyBytes) + + addr := crypto.PubkeyToAddress(privKey.PublicKey) + chainID := big.NewInt(1337) + txOpts, err := bind.NewKeyedTransactorWithChainID(privKey, chainID) + if err != nil { + return nil, nil, err + } + startingBalance, _ := new(big.Int).SetString( + "100000000000000000000000000000000000000", + 10, + ) + genesis[addr] = types.Account{Balance: startingBalance} + accs[i] = &TestAccount{ + AccountAddr: addr, + TxOpts: txOpts, + } + } + backend := NewSimulatedBackendWrapper(simulated.NewBackend(genesis, simulated.WithBlockGasLimit(gasLimit))) + return accs, backend, nil +} diff --git a/bold/testing/setup/simulated_backend_wrapper.go b/bold/testing/setup/simulated_backend_wrapper.go new file mode 100644 index 0000000000..1ea2ce6780 --- /dev/null +++ b/bold/testing/setup/simulated_backend_wrapper.go @@ -0,0 +1,129 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +package setup + +import ( + "context" + "errors" + "math/big" + "sync" + + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient/simulated" + "github.com/ethereum/go-ethereum/rpc" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +var ( + _ protocol.ChainBackend = &SimulatedBackendWrapper{ + desiredBlockNum: rpc.LatestBlockNumber, + } +) + +type SimulatedBackendWrapper struct { + lock sync.Mutex + *simulated.Backend + desiredBlockNum rpc.BlockNumber +} + +func (s *SimulatedBackendWrapper) HeaderU64(ctx context.Context) (uint64, error) { + header, err := s.Backend.Client().HeaderByNumber(ctx, big.NewInt(int64(s.desiredBlockNum))) + if err != nil { + return 0, err + } + if !header.Number.IsUint64() { + return 0, errors.New("block number is not uint64") + } + return header.Number.Uint64(), nil +} + +func (s *SimulatedBackendWrapper) ChainID(ctx context.Context) (*big.Int, error) { + return s.Backend.Client().ChainID(ctx) +} + +func (s *SimulatedBackendWrapper) Close() { + s.Backend.Close() // #nosec G104 +} + +func (s *SimulatedBackendWrapper) Client() rpc.ClientInterface { + iface, ok := s.Backend.Client().(rpc.ClientInterface) + if !ok { + panic("simulated backend client does not implement rpc.ClientInterface") + } + return iface +} + +func (s *SimulatedBackendWrapper) Commit() common.Hash { + s.lock.Lock() + defer s.lock.Unlock() + return s.Backend.Commit() +} + +func NewSimulatedBackendWrapper(bk *simulated.Backend) *SimulatedBackendWrapper { + return &SimulatedBackendWrapper{Backend: bk, desiredBlockNum: rpc.LatestBlockNumber} +} + +func (s *SimulatedBackendWrapper) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + return s.Backend.Client().CodeAt(ctx, contract, blockNumber) +} + +func (s *SimulatedBackendWrapper) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + return s.Backend.Client().CallContract(ctx, call, blockNumber) +} + +func (s *SimulatedBackendWrapper) PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error) { + return s.Backend.Client().PendingCodeAt(ctx, contract) +} + +func (s *SimulatedBackendWrapper) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) { + return s.Backend.Client().PendingCallContract(ctx, call) +} + +func (s *SimulatedBackendWrapper) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { + return s.Backend.Client().HeaderByNumber(ctx, number) +} + +func (s *SimulatedBackendWrapper) PendingNonceAt(ctx context.Context, account common.Address) (uint64, error) { + return s.Backend.Client().PendingNonceAt(ctx, account) +} + +func (s *SimulatedBackendWrapper) SuggestGasPrice(ctx context.Context) (*big.Int, error) { + return s.Backend.Client().SuggestGasPrice(ctx) +} + +func (s *SimulatedBackendWrapper) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { + return s.Backend.Client().SuggestGasTipCap(ctx) +} + +func (s *SimulatedBackendWrapper) EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error) { + return s.Backend.Client().EstimateGas(ctx, call) +} + +func (s *SimulatedBackendWrapper) SendTransaction(ctx context.Context, tx *types.Transaction) error { + return s.Backend.Client().SendTransaction(ctx, tx) +} + +func (s *SimulatedBackendWrapper) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) { + return s.Backend.Client().FilterLogs(ctx, query) +} + +func (s *SimulatedBackendWrapper) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) { + return s.Backend.Client().SubscribeFilterLogs(ctx, query, ch) +} + +func (s *SimulatedBackendWrapper) SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) { + return s.Backend.Client().SubscribeNewHead(ctx, ch) +} + +func (s *SimulatedBackendWrapper) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) { + return s.Backend.Client().TransactionReceipt(ctx, txHash) +} + +func (s *SimulatedBackendWrapper) TransactionByHash(ctx context.Context, txHash common.Hash) (*types.Transaction, bool, error) { + return s.Backend.Client().TransactionByHash(ctx, txHash) +} diff --git a/bold/testing/tx.go b/bold/testing/tx.go new file mode 100644 index 0000000000..4d6e1812d0 --- /dev/null +++ b/bold/testing/tx.go @@ -0,0 +1,66 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package challenge_testing includes all non-production code used in BoLD. +package challenge_testing + +import ( + "context" + "fmt" + "time" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TxSucceeded( + ctx context.Context, + tx *types.Transaction, + addr common.Address, + backend bind.DeployBackend, + err error, +) error { + if err != nil { + return fmt.Errorf("error submitting tx: %w", err) + } + if waitErr := WaitForTx(ctx, backend, tx); waitErr != nil { + return errors.Wrap(waitErr, "error waiting for tx to be mined") + } + receipt, err := backend.TransactionReceipt(ctx, tx.Hash()) + if err != nil { + return err + } + if receipt.Status != types.ReceiptStatusSuccessful { + return errors.New("tx receipt not successful") + } + code, err := backend.CodeAt(ctx, addr, nil) + if err != nil { + return err + } + if len(code) == 0 { + return errors.New("contract not deployed") + } + return nil +} + +type committer interface { + Commit() common.Hash +} + +// WaitForTx to be mined. This method will trigger .Commit() on a simulated backend. +func WaitForTx(ctx context.Context, be bind.DeployBackend, tx *types.Transaction) error { + if simulated, ok := be.(committer); ok { + simulated.Commit() + } + + ctx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + + _, err := bind.WaitMined(ctx, be, tx) + + return err +} diff --git a/bold/third_party/com_github_datadog_zstd.patch b/bold/third_party/com_github_datadog_zstd.patch new file mode 100644 index 0000000000..7a2727a470 --- /dev/null +++ b/bold/third_party/com_github_datadog_zstd.patch @@ -0,0 +1,147 @@ +diff --git a/BUILD.bazel b/BUILD.bazel +new file mode 100644 +index 0000000..f15d38e +--- /dev/null ++++ b/BUILD.bazel +@@ -0,0 +1,131 @@ ++load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") ++load("@rules_cc//cc:defs.bzl", "cc_library") ++ ++cc_library( ++ name = "zstd_cc", ++ srcs = [ ++ "bitstream.h", ++ "clevels.h", ++ "compiler.h", ++ "cover.c", ++ "cover.h", ++ "cpu.h", ++ "debug.c", ++ "debug.h", ++ "divsufsort.c", ++ "divsufsort.h", ++ "entropy_common.c", ++ "error_private.c", ++ "error_private.h", ++ "fastcover.c", ++ "fse.h", ++ "fse_compress.c", ++ "fse_decompress.c", ++ "hist.c", ++ "hist.h", ++ "huf.h", ++ "huf_compress.c", ++ "huf_decompress.c", ++ "huf_decompress_amd64.S", ++ "mem.h", ++ "pool.c", ++ "pool.h", ++ "portability_macros.h", ++ "threading.c", ++ "threading.h", ++ "xxhash.c", ++ "xxhash.h", ++ "zbuff.h", ++ "zbuff_common.c", ++ "zbuff_compress.c", ++ "zbuff_decompress.c", ++ "zdict.c", ++ "zdict.h", ++ "zstd.h", ++ "zstd_common.c", ++ "zstd_compress.c", ++ "zstd_compress_internal.h", ++ "zstd_compress_literals.c", ++ "zstd_compress_literals.h", ++ "zstd_compress_sequences.c", ++ "zstd_compress_sequences.h", ++ "zstd_compress_superblock.c", ++ "zstd_compress_superblock.h", ++ "zstd_cwksp.h", ++ "zstd_ddict.c", ++ "zstd_ddict.h", ++ "zstd_decompress.c", ++ "zstd_decompress_block.c", ++ "zstd_decompress_block.h", ++ "zstd_decompress_internal.h", ++ "zstd_deps.h", ++ "zstd_double_fast.c", ++ "zstd_double_fast.h", ++ "zstd_errors.h", ++ "zstd_fast.c", ++ "zstd_fast.h", ++ "zstd_internal.h", ++ "zstd_lazy.c", ++ "zstd_lazy.h", ++ "zstd_ldm.c", ++ "zstd_ldm.h", ++ "zstd_ldm_geartab.h", ++ "zstd_legacy.h", ++ "zstd_opt.c", ++ "zstd_opt.h", ++ "zstd_trace.h", ++ "zstd_v01.c", ++ "zstd_v01.h", ++ "zstd_v02.c", ++ "zstd_v02.h", ++ "zstd_v03.c", ++ "zstd_v03.h", ++ "zstd_v04.c", ++ "zstd_v04.h", ++ "zstd_v05.c", ++ "zstd_v05.h", ++ "zstd_v06.c", ++ "zstd_v06.h", ++ "zstd_v07.c", ++ "zstd_v07.h", ++ "zstdmt_compress.c", ++ "zstdmt_compress.h", ++ ], ++ copts = ["-DZSTD_LEGACY_SUPPORT=4"], ++ ++) ++go_library( ++ name = "zstd", ++ srcs = [ ++ "errors.go", ++ "zstd.go", ++ "zstd_bulk.go", ++ "zstd_ctx.go", ++ "zstd_stream.go", ++ "zstd.h", ++ ], ++ cgo = True, ++ cdeps = [":zstd_cc"], ++ copts = ["-DZSTD_LEGACY_SUPPORT=4"], ++ importpath = "github.com/DataDog/zstd", ++ visibility = ["//visibility:public"], ++) ++ ++alias( ++ name = "go_default_library", ++ actual = ":zstd", ++ visibility = ["//visibility:public"], ++) ++ ++go_test( ++ name = "zstd_test", ++ srcs = [ ++ "errors_test.go", ++ "helpers_test.go", ++ "zstd_bullk_test.go", ++ "zstd_ctx_test.go", ++ "zstd_stream_test.go", ++ "zstd_test.go", ++ ], ++ embed = [":zstd"], ++) +diff --git a/WORKSPACE b/WORKSPACE +new file mode 100644 +index 0000000..dea5b27 +--- /dev/null ++++ b/WORKSPACE +@@ -0,0 +1,2 @@ ++# DO NOT EDIT: automatically generated WORKSPACE file for go_repository rule ++workspace(name = "com_github_datadog_zstd") +-- +2.34.1 diff --git a/bold/third_party/com_github_ethereum_go_ethereum_secp256k1.patch b/bold/third_party/com_github_ethereum_go_ethereum_secp256k1.patch new file mode 100644 index 0000000000..a113d89047 --- /dev/null +++ b/bold/third_party/com_github_ethereum_go_ethereum_secp256k1.patch @@ -0,0 +1,58 @@ +diff --color -ruN a/crypto/secp256k1/BUILD.bazel b/crypto/secp256k1/BUILD.bazel +--- a/crypto/secp256k1/BUILD.bazel 2021-10-14 20:32:30.202922024 -0500 ++++ b/crypto/secp256k1/BUILD.bazel 2021-10-14 20:30:17.921027939 -0500 +@@ -11,10 +11,11 @@ + "scalar_mult_nocgo.go", + "secp256.go", + ], ++ cdeps = ["//crypto/secp256k1/libsecp256k1:hdrs"], + cgo = True, + copts = [ +- "-Icrypto/secp256k1/libsecp256k1", +- "-Icrypto/secp256k1/libsecp256k1/src", ++ "-Iexternal/gazelle~~go_deps~com_github_ethereum_go_ethereum/crypto/secp256k1/libsecp256k1", ++ "-Iexternal/gazelle~~go_deps~com_github_ethereum_go_ethereum/crypto/secp256k1/libsecp256k1/src", + ], + importpath = "github.com/ethereum/go-ethereum/crypto/secp256k1", + visibility = ["//visibility:public"], +diff --color -ruN a/crypto/secp256k1/libsecp256k1/BUILD.bazel b/crypto/secp256k1/libsecp256k1/BUILD.bazel +--- a/crypto/secp256k1/libsecp256k1/BUILD.bazel 1969-12-31 18:00:00.000000000 -0600 ++++ b/crypto/secp256k1/libsecp256k1/BUILD.bazel 2021-10-14 12:54:27.704265206 -0500 +@@ -0,0 +1,37 @@ ++cc_library( ++ name = "hdrs", ++ hdrs = [ ++ "include/secp256k1.h", ++ "include/secp256k1_recovery.h", ++ "src/ecdsa.h", ++ "src/ecdsa_impl.h", ++ "src/eckey.h", ++ "src/eckey_impl.h", ++ "src/ecmult.h", ++ "src/ecmult_const.h", ++ "src/ecmult_const_impl.h", ++ "src/ecmult_gen.h", ++ "src/ecmult_gen_impl.h", ++ "src/ecmult_impl.h", ++ "src/field.h", ++ "src/field_5x52.h", ++ "src/field_5x52_impl.h", ++ "src/field_5x52_int128_impl.h", ++ "src/field_impl.h", ++ "src/group.h", ++ "src/group_impl.h", ++ "src/hash.h", ++ "src/hash_impl.h", ++ "src/modules/recovery/main_impl.h", ++ "src/num.h", ++ "src/num_impl.h", ++ "src/scalar.h", ++ "src/scalar_4x64.h", ++ "src/scalar_4x64_impl.h", ++ "src/scalar_impl.h", ++ "src/secp256k1.c", ++ "src/util.h", ++ ], ++ visibility = ["//visibility:public"], ++) ++ diff --git a/bold/time/time_reference.go b/bold/time/time_reference.go new file mode 100644 index 0000000000..412ad8fd34 --- /dev/null +++ b/bold/time/time_reference.go @@ -0,0 +1,180 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md + +// Package time defines abstractions for time-related operations in BoLD. +package time + +import ( + "sync" + "time" +) + +type Reference interface { + Get() time.Time + Sleep(duration time.Duration) + SleepUntil(wakeTime time.Time) + NewTicker(duration time.Duration) GenericTimeTicker +} + +type GenericTimeTicker interface { + C() <-chan time.Time + Stop() +} + +type realTimeReference struct{} + +func NewRealTimeReference() Reference { + return realTimeReference{} +} + +func (realTimeReference) Get() time.Time { + return time.Now() +} + +func (realTimeReference) Sleep(duration time.Duration) { + time.Sleep(duration) +} + +func (realTimeReference) SleepUntil(wakeTime time.Time) { + time.Sleep(time.Until(wakeTime)) +} + +func (realTimeReference) NewTicker(duration time.Duration) GenericTimeTicker { + return newRealTimeTicker(duration) +} + +type realTimeTicker struct { + ticker *time.Ticker +} + +func newRealTimeTicker(duration time.Duration) *realTimeTicker { + return &realTimeTicker{time.NewTicker(duration)} +} + +func (ticker *realTimeTicker) C() <-chan time.Time { + return ticker.ticker.C +} + +func (ticker *realTimeTicker) Stop() { + ticker.ticker.Stop() +} + +type ArtificialTimeReference struct { + mutex sync.RWMutex + current time.Time + changedChan chan struct{} // every time the time changes, this is closed and re-generated +} + +func NewArtificialTimeReference() *ArtificialTimeReference { + return &ArtificialTimeReference{ + mutex: sync.RWMutex{}, + current: time.Unix(0, 0), + changedChan: make(chan struct{}), + } +} + +func (atr *ArtificialTimeReference) Get() time.Time { + atr.mutex.RLock() + defer atr.mutex.RUnlock() + return atr.current +} + +func (atr *ArtificialTimeReference) Sleep(duration time.Duration) { + atr.SleepUntil(atr.Get().Add(duration)) +} + +func (atr *ArtificialTimeReference) SleepUntil(wakeTime time.Time) { + for { + atr.mutex.RLock() + current := atr.current + changedChan := atr.changedChan + atr.mutex.RUnlock() + if !current.Before(wakeTime) { + return + } + <-changedChan + } +} + +func (atr *ArtificialTimeReference) Set(newVal time.Time) { + atr.mutex.Lock() + defer atr.mutex.Unlock() + atr.setLocked(newVal) +} + +func (atr *ArtificialTimeReference) setLocked(newVal time.Time) { + if newVal.Before(atr.current) { + return + } + changed := newVal.After(atr.current) + atr.current = newVal + if changed { + close(atr.changedChan) + atr.changedChan = make(chan struct{}) + } +} + +func (atr *ArtificialTimeReference) Add(delta time.Duration) { + atr.mutex.Lock() + defer atr.mutex.Unlock() + atr.setLocked(atr.current.Add(delta)) +} + +func (atr *ArtificialTimeReference) NewTicker(interval time.Duration) GenericTimeTicker { + ticker := &artificialTicker{ + timeRef: atr, + c: make(chan time.Time), + interval: interval, + next: atr.Get().Add(interval), + stoppedChan: make(chan struct{}), + stopped: false, + } + go func() { + defer close(ticker.c) + for { + ticker.timeRef.mutex.RLock() + current := atr.current + changedChan := atr.changedChan + ticker.timeRef.mutex.RUnlock() + if current.Before(ticker.next) { + select { + case <-changedChan: + case <-ticker.stoppedChan: + return + } + } else { + select { + case ticker.c <- current: + ticker.next = ticker.timeRef.Get().Add(ticker.interval) + case <-ticker.stoppedChan: + return + } + } + } + }() + return ticker +} + +type artificialTicker struct { + timeRef *ArtificialTimeReference + c chan time.Time + interval time.Duration + next time.Time + closeMutex sync.Mutex + stoppedChan chan struct{} + stopped bool +} + +func (ticker *artificialTicker) C() <-chan time.Time { + return ticker.c +} + +func (ticker *artificialTicker) Stop() { + ticker.closeMutex.Lock() + defer ticker.closeMutex.Unlock() + if !ticker.stopped { + ticker.stopped = true + close(ticker.stoppedChan) + } +} diff --git a/bold/time/time_reference_test.go b/bold/time/time_reference_test.go new file mode 100644 index 0000000000..6514b0f80b --- /dev/null +++ b/bold/time/time_reference_test.go @@ -0,0 +1,113 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see: +// https://github.com/offchainlabs/nitro/blob/master/LICENSE.md +package time + +import ( + "testing" + "time" +) + +var ( + _ = Reference(&realTimeReference{}) + _ = Reference(&ArtificialTimeReference{}) +) + +func TestRealTimeReference(t *testing.T) { + rtRef := NewRealTimeReference() + now := rtRef.Get() + time.Sleep(time.Millisecond) + newTime := rtRef.Get() + if newTime.Before(now) || newTime.Equal(now) { + t.Errorf("Time did not advance as expected") + } +} + +func TestRealTimeReference_SleepUntil(t *testing.T) { + rtRef := NewRealTimeReference() + wakeTime := rtRef.Get().Add(time.Millisecond * 10) + rtRef.SleepUntil(wakeTime) + now := rtRef.Get() + if now.Before(wakeTime) { + t.Errorf("SleepUntil did not sleep until the correct time") + } +} + +func TestRealTimeReference_NewTicker(t *testing.T) { + rtRef := NewRealTimeReference() + ticker := rtRef.NewTicker(time.Millisecond * 10) + time.Sleep(time.Millisecond * 15) + select { + case <-ticker.C(): + // successfully ticked + default: + t.Errorf("Ticker did not tick as expected") + } + ticker.Stop() +} + +func TestArtificialTimeReference_GetSet(t *testing.T) { + atRef := NewArtificialTimeReference() + time1 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC) + atRef.Set(time1) + time2 := atRef.Get() + if !time1.Equal(time2) { + t.Errorf("Did not get/set time as expected") + } +} + +func TestArtificialTimeReference_SleepUntil(t *testing.T) { + atRef := NewArtificialTimeReference() + sleepUntil := time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC) + go func() { + time.Sleep(time.Millisecond * 10) + atRef.Set(sleepUntil) + }() + atRef.SleepUntil(sleepUntil) + if atRef.Get().Before(sleepUntil) { + t.Errorf("SleepUntil did not sleep as expected") + } +} + +func TestRealTimeReference_Sleep(t *testing.T) { + rtRef := NewRealTimeReference() + start := rtRef.Get() + sleepDuration := time.Millisecond * 10 + rtRef.Sleep(sleepDuration) + end := rtRef.Get() + if end.Sub(start) < sleepDuration { + t.Errorf("Sleep did not last for the expected duration") + } +} + +func TestArtificialTimeReference_Sleep(t *testing.T) { + atRef := NewArtificialTimeReference() + start := atRef.Get() + sleepDuration := time.Minute + go func() { + time.Sleep(time.Millisecond * 10) + atRef.Add(sleepDuration) + }() + atRef.Sleep(sleepDuration) + end := atRef.Get() + if end.Sub(start) < sleepDuration { + t.Errorf("Sleep did not last for the expected duration") + } +} + +func TestArtificialTicker(t *testing.T) { + atRef := NewArtificialTimeReference() + interval := time.Minute + ticker := atRef.NewTicker(interval) + go func() { + time.Sleep(time.Millisecond * 10) + atRef.Add(interval) + }() + select { + case <-ticker.C(): + // Ticker ticked + case <-time.After(time.Second): + t.Errorf("Ticker did not tick as expected") + } + ticker.Stop() +} diff --git a/bold/util/backend.go b/bold/util/backend.go new file mode 100644 index 0000000000..84426dabf5 --- /dev/null +++ b/bold/util/backend.go @@ -0,0 +1,39 @@ +package util + +import ( + "context" + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + + protocol "github.com/offchainlabs/bold/chain-abstraction" +) + +var ( + _ protocol.ChainBackend = &BackendWrapper{ + desiredBlockNum: rpc.LatestBlockNumber, + } +) + +type ethClient = ethclient.Client +type BackendWrapper struct { + *ethClient + desiredBlockNum rpc.BlockNumber +} + +func NewBackendWrapper(client *ethclient.Client, desiredBlockNum rpc.BlockNumber) *BackendWrapper { + return &BackendWrapper{client, desiredBlockNum} +} + +func (b BackendWrapper) HeaderU64(ctx context.Context) (uint64, error) { + header, err := b.HeaderByNumber(ctx, big.NewInt(int64(b.desiredBlockNum))) + if err != nil { + return 0, err + } + if !header.Number.IsUint64() { + return 0, errors.New("block number is not uint64") + } + return header.Number.Uint64(), nil +} diff --git a/bold/util/stopwaiter/stopwaiter.go b/bold/util/stopwaiter/stopwaiter.go new file mode 100644 index 0000000000..036754c009 --- /dev/null +++ b/bold/util/stopwaiter/stopwaiter.go @@ -0,0 +1,258 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package stopwaiter + +import ( + "context" + "errors" + "reflect" + "runtime" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +const stopDelayWarningTimeout = 30 * time.Second + +type StopWaiterSafe struct { + mutex sync.Mutex // protects started, stopped, ctx, parentCtx, stopFunc + started bool + stopped bool + ctx context.Context + parentCtx context.Context + stopFunc func() + name string + waitChan <-chan interface{} + + wg sync.WaitGroup +} + +func (s *StopWaiterSafe) Started() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.started +} + +func (s *StopWaiterSafe) Stopped() bool { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.stopped +} + +func (s *StopWaiterSafe) GetContextSafe() (context.Context, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.getContext() +} + +// this context is not cancelled even after someone calls Stop +func (s *StopWaiterSafe) GetParentContextSafe() (context.Context, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + return s.getParentContext() +} + +// Only call this internally with the mutex held. +func (s *StopWaiterSafe) getContext() (context.Context, error) { + if s.started { + return s.ctx, nil + } + return nil, errors.New("not started") +} + +// Only call this internally with the mutex held. +func (s *StopWaiterSafe) getParentContext() (context.Context, error) { + if s.started { + return s.parentCtx, nil + } + return nil, errors.New("not started") +} + +func getParentName(parent any) string { + // remove asterisk in case the type is a pointer + return strings.Replace(reflect.TypeOf(parent).String(), "*", "", 1) +} + +// start-after-start will error, start-after-stop will immediately cancel +func (s *StopWaiterSafe) Start(ctx context.Context, parent any) error { + s.mutex.Lock() + defer s.mutex.Unlock() + if s.started { + return errors.New("start after start") + } + s.started = true + s.name = getParentName(parent) + s.parentCtx = ctx + s.ctx, s.stopFunc = context.WithCancel(s.parentCtx) + if s.stopped { + s.stopFunc() + } + return nil +} + +func (s *StopWaiterSafe) StopOnly() { + _ = s.stopOnly() +} + +// returns true if stop function was called +func (s *StopWaiterSafe) stopOnly() bool { + stopWasCalled := false + s.mutex.Lock() + defer s.mutex.Unlock() + if s.started && !s.stopped { + s.stopFunc() + stopWasCalled = true + } + s.stopped = true + return stopWasCalled +} + +// StopAndWait may be called multiple times, even before start. +func (s *StopWaiterSafe) StopAndWait() error { + return s.stopAndWaitImpl(stopDelayWarningTimeout) +} + +func getAllStackTraces() string { + buf := make([]byte, 64*1024*1024) + size := runtime.Stack(buf, true) + builder := strings.Builder{} + builder.Write(buf[0:size]) + return builder.String() +} + +func (s *StopWaiterSafe) stopAndWaitImpl(warningTimeout time.Duration) error { + if !s.stopOnly() { + return nil + } + waitChan, err := s.GetWaitChannel() + if err != nil { + return err + } + timer := time.NewTimer(warningTimeout) + + select { + case <-timer.C: + traces := getAllStackTraces() + log.Warn("taking too long to stop", "name", s.name, "delay[s]", warningTimeout.Seconds()) + log.Warn(traces) + case <-waitChan: + timer.Stop() + return nil + } + <-waitChan + return nil +} + +func (s *StopWaiterSafe) GetWaitChannel() (<-chan interface{}, error) { + s.mutex.Lock() + defer s.mutex.Unlock() + if s.waitChan == nil { + ctx, err := s.getContext() + if err != nil { + return nil, err + } + waitChan := make(chan interface{}) + go func() { + <-ctx.Done() + s.wg.Wait() + close(waitChan) + }() + s.waitChan = waitChan + } + return s.waitChan, nil +} + +// If stop was already called, thread might silently not be launched +func (s *StopWaiterSafe) LaunchThreadSafe(foo func(context.Context)) error { + ctx, err := s.GetContextSafe() + if err != nil { + return err + } + if s.Stopped() { + return nil + } + s.wg.Add(1) + go func() { + foo(ctx) + s.wg.Done() + }() + return nil +} + +// This calls go foo() directly, with the benefit of being easily searchable. +// Callers may rely on the assumption that foo runs even if this is stopped. +func (s *StopWaiterSafe) LaunchUntrackedThread(foo func()) { + go foo() +} + +// CallIteratively calls function iteratively in a thread. +// input param return value is how long to wait before next invocation +func (s *StopWaiterSafe) CallIterativelySafe(foo func(context.Context) time.Duration) error { + return s.LaunchThreadSafe(func(ctx context.Context) { + for { + interval := foo(ctx) + if ctx.Err() != nil { + return + } + if interval == time.Duration(0) { + continue + } + timer := time.NewTimer(interval) + select { + case <-ctx.Done(): + timer.Stop() + return + case <-timer.C: + } + } + }) +} + +// StopWaiter may panic on race conditions instead of returning errors +type StopWaiter struct { + StopWaiterSafe +} + +func (s *StopWaiter) Start(ctx context.Context, parent any) { + if err := s.StopWaiterSafe.Start(ctx, parent); err != nil { + panic(err) + } +} + +func (s *StopWaiter) StopAndWait() { + if err := s.StopWaiterSafe.StopAndWait(); err != nil { + panic(err) + } +} + +// If stop was already called, thread might silently not be launched +func (s *StopWaiter) LaunchThread(foo func(context.Context)) { + if err := s.LaunchThreadSafe(foo); err != nil { + panic(err) + } +} + +func (s *StopWaiter) CallIteratively(foo func(context.Context) time.Duration) { + if err := s.CallIterativelySafe(foo); err != nil { + panic(err) + } +} + +func (s *StopWaiter) GetContext() context.Context { + ctx, err := s.GetContextSafe() + if err != nil { + panic(err) + } + return ctx +} + +func (s *StopWaiter) GetParentContext() context.Context { + ctx, err := s.GetParentContextSafe() + if err != nil { + panic(err) + } + return ctx +} diff --git a/bold/yarn.lock b/bold/yarn.lock new file mode 100644 index 0000000000..fb57ccd13a --- /dev/null +++ b/bold/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/broadcastclient/broadcastclient_test.go b/broadcastclient/broadcastclient_test.go index e06ce72c7a..f5ea76f62c 100644 --- a/broadcastclient/broadcastclient_test.go +++ b/broadcastclient/broadcastclient_test.go @@ -264,7 +264,7 @@ func startMakeBroadcastClient(ctx context.Context, t *testing.T, clientConfig Co } timer.Stop() if (!gotMsg && expectedCount > 0) || (gotMsg && expectedCount == 0) { - t.Errorf("Client %d expected %d meesages, got %d messages\n", index, expectedCount, messageCount) + t.Errorf("Client %d expected %d messages, got %d messages\n", index, expectedCount, messageCount) return } diff --git a/broadcaster/backlog/backlog.go b/broadcaster/backlog/backlog.go index 0897eedd10..2ef378275c 100644 --- a/broadcaster/backlog/backlog.go +++ b/broadcaster/backlog/backlog.go @@ -115,6 +115,12 @@ func (b *backlog) Append(bm *m.BroadcastMessage) error { } prevMsgIdx := segment.End() + if prevMsgIdx != 0 && uint64(msg.SequenceNumber) <= prevMsgIdx { + log.Info("ignoring message sequence number, already in backlog", "message sequence number", msg.SequenceNumber) + // Skip creating a new segment if we're at the segment boundary and would not + // append a message. Empty segments break the backlog's invariants. + continue + } if segment.count() >= b.config().SegmentLimit { segment.messagesLock.RLock() if len(segment.messages) > 0 { @@ -138,9 +144,6 @@ func (b *backlog) Append(bm *m.BroadcastMessage) error { b.messageCount.Store(0) backlogSizeInBytesGauge.Update(0) log.Warn(err.Error()) - } else if errors.Is(err, errSequenceNumberSeen) { - log.Info("ignoring message sequence number, already in backlog", "message sequence number", msg.SequenceNumber) - continue } else if err != nil { return err } diff --git a/broadcaster/backlog/backlog_test.go b/broadcaster/backlog/backlog_test.go index d74389f692..87aecf6a6c 100644 --- a/broadcaster/backlog/backlog_test.go +++ b/broadcaster/backlog/backlog_test.go @@ -129,6 +129,26 @@ func TestAppend(t *testing.T) { 46, []arbutil.MessageIndex{40, 41, 42, 43, 44, 45, 46}, }, + // There was a bug where if the last message was a duplicate it could insert an empty segment. + { + "MessageSeenFirstSegmentMessageDoesntCreateNewSegment", + []arbutil.MessageIndex{40, 41}, + []arbutil.MessageIndex{42, 43, 44, 45, 41}, + 6, + 40, + 45, + []arbutil.MessageIndex{40, 41, 42, 43, 44, 45}, + }, + // The above bug could also be used to insert messages out of order. + { + "MyMessageSeenFirstSegmentMessageDoesntAllowOutOfOrderInsertion", + []arbutil.MessageIndex{40, 41}, + []arbutil.MessageIndex{42, 43, 44, 45, 41, 1}, + 6, + 40, + 45, + []arbutil.MessageIndex{40, 41, 42, 43, 44, 45}, + }, } for _, tc := range testcases { diff --git a/cmd/blobtool/blobtool.go b/cmd/blobtool/blobtool.go new file mode 100644 index 0000000000..94da9eb8a4 --- /dev/null +++ b/cmd/blobtool/blobtool.go @@ -0,0 +1,234 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +// This is a command line tool for testing beacon/blobs and blob_sidecars endpoints. +package main + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + flag "github.com/spf13/pflag" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto/kzg4844" + + "github.com/offchainlabs/nitro/cmd/util/confighelpers" + "github.com/offchainlabs/nitro/util/blobs" + "github.com/offchainlabs/nitro/util/headerreader" +) + +func main() { + args := os.Args + if len(args) < 2 { + fmt.Println("Usage: blobtool [fetch] ...") + os.Exit(1) + } + + var err error + switch strings.ToLower(args[1]) { + case "fetch": + err = fetchBlobs(args[2:]) + default: + err = fmt.Errorf("unknown command '%s', valid commands are: fetch", args[1]) + } + if err != nil { + fmt.Fprintf(os.Stderr, "Error: %v\n", err) + os.Exit(1) + } +} + +type FetchConfig struct { + BeaconURL string `koanf:"beacon-url"` + Slot uint64 `koanf:"slot"` + VersionedHashes []string `koanf:"versioned-hashes"` + UseLegacyEndpoint bool `koanf:"use-legacy-endpoint"` + CompareEndpoints bool `koanf:"compare-endpoints"` +} + +func parseFetchConfig(args []string) (*FetchConfig, error) { + f := flag.NewFlagSet("blobtool fetch", flag.ContinueOnError) + f.String("beacon-url", "", "Beacon Chain RPC URL. For example with --beacon-url=http://localhost, an RPC call will be made to http://localhost/eth/v1/beacon/blobs") + f.Uint64("slot", 0, "Beacon chain slot number to fetch blobs from") + f.StringSlice("versioned-hashes", []string{}, "Comma-separated list of versioned hashes to fetch (optional - fetches all if not provided)") + f.Bool("use-legacy-endpoint", false, "Use the legacy blob_sidecars endpoint") + f.Bool("compare-endpoints", false, "Fetch using both endpoints and compare results") + + k, err := confighelpers.BeginCommonParse(f, args) + if err != nil { + return nil, err + } + + var config FetchConfig + if err := confighelpers.EndCommonParse(k, &config); err != nil { + return nil, err + } + + if config.BeaconURL == "" { + return nil, fmt.Errorf("--beacon-url is required") + } + if config.Slot == 0 { + return nil, fmt.Errorf("--slot is required") + } + + return &config, nil +} + +func fetchBlobs(args []string) error { + config, err := parseFetchConfig(args) + if err != nil { + return err + } + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + versionedHashes := make([]common.Hash, len(config.VersionedHashes)) + for i, hashStr := range config.VersionedHashes { + if !common.IsHexAddress(hashStr) && len(hashStr) != 66 { + return fmt.Errorf("invalid versioned hash at index %d: %s", i, hashStr) + } + versionedHashes[i] = common.HexToHash(hashStr) + } + + if config.UseLegacyEndpoint && len(versionedHashes) == 0 { + return fmt.Errorf("--versioned-hashes is required when using --use-legacy-endpoint") + } + + if config.CompareEndpoints { + if len(versionedHashes) == 0 { + return fmt.Errorf("--versioned-hashes is required when using --compare-endpoints") + } + return compareEndpoints(ctx, config, versionedHashes) + } + + blobClientConfig := headerreader.BlobClientConfig{ + BeaconUrl: config.BeaconURL, + UseLegacyEndpoint: config.UseLegacyEndpoint, + } + + blobClient, err := headerreader.NewBlobClient(blobClientConfig, nil) + if err != nil { + return fmt.Errorf("failed to create blob client: %w", err) + } + + if err := blobClient.Initialize(ctx); err != nil { + return fmt.Errorf("failed to initialize blob client: %w", err) + } + + endpointType := "new blobs" + if config.UseLegacyEndpoint { + endpointType = "legacy blob_sidecars" + } + + if len(versionedHashes) > 0 { + fmt.Printf("Fetching %d blobs for slot %d using %s endpoint...\n", len(versionedHashes), config.Slot, endpointType) + } else { + fmt.Printf("Fetching all blobs for slot %d using %s endpoint...\n", config.Slot, endpointType) + } + + startTime := time.Now() + fetchedBlobs, err := blobClient.GetBlobsBySlot(ctx, config.Slot, versionedHashes) + if err != nil { + return fmt.Errorf("failed to fetch blobs: %w", err) + } + duration := time.Since(startTime) + + fmt.Printf("Successfully fetched %d blobs in %v\n", len(fetchedBlobs), duration) + + for i, blob := range fetchedBlobs { + _, hashes, err := blobs.ComputeCommitmentsAndHashes([]kzg4844.Blob{blob}) + if err != nil { + return fmt.Errorf("failed to compute commitment for blob %d: %w", i, err) + } + if len(versionedHashes) > 0 { + fmt.Printf("Blob %d: versioned_hash=%s (computed=%s), size=%d bytes\n", i, versionedHashes[i].Hex(), hashes[0].Hex(), len(blob)) + } else { + fmt.Printf("Blob %d: versioned_hash=%s, size=%d bytes\n", i, hashes[0].Hex(), len(blob)) + } + } + + return nil +} + +func compareEndpoints(ctx context.Context, config *FetchConfig, versionedHashes []common.Hash) error { + fmt.Println("Comparing legacy blob_sidecars and new blobs endpoints...") + fmt.Println() + + legacyConfig := headerreader.BlobClientConfig{ + BeaconUrl: config.BeaconURL, + UseLegacyEndpoint: true, + } + legacyClient, err := headerreader.NewBlobClient(legacyConfig, nil) + if err != nil { + return fmt.Errorf("failed to create legacy blob client: %w", err) + } + if err := legacyClient.Initialize(ctx); err != nil { + return fmt.Errorf("failed to initialize legacy blob client: %w", err) + } + + fmt.Println("Fetching with legacy blob_sidecars endpoint...") + legacyStart := time.Now() + legacyBlobs, err := legacyClient.GetBlobsBySlot(ctx, config.Slot, versionedHashes) + legacyDuration := time.Since(legacyStart) + if err != nil { + return fmt.Errorf("failed to fetch blobs with legacy endpoint: %w", err) + } + fmt.Printf("✓ Legacy endpoint: fetched %d blobs in %v\n", len(legacyBlobs), legacyDuration) + fmt.Println() + + newConfig := headerreader.BlobClientConfig{ + BeaconUrl: config.BeaconURL, + UseLegacyEndpoint: false, + } + newClient, err := headerreader.NewBlobClient(newConfig, nil) + if err != nil { + return fmt.Errorf("failed to create new blob client: %w", err) + } + if err := newClient.Initialize(ctx); err != nil { + return fmt.Errorf("failed to initialize new blob client: %w", err) + } + + fmt.Println("Fetching with new blobs endpoint...") + newStart := time.Now() + newBlobs, err := newClient.GetBlobsBySlot(ctx, config.Slot, versionedHashes) + newDuration := time.Since(newStart) + if err != nil { + return fmt.Errorf("failed to fetch blobs with new endpoint: %w", err) + } + fmt.Printf("✓ New endpoint: fetched %d blobs in %v\n", len(newBlobs), newDuration) + fmt.Println() + + if len(legacyBlobs) != len(newBlobs) { + return fmt.Errorf("blob count mismatch: legacy=%d, new=%d", len(legacyBlobs), len(newBlobs)) + } + + fmt.Println("Comparing blob data...") + for i := range legacyBlobs { + if legacyBlobs[i] != newBlobs[i] { + return fmt.Errorf("blob %d data mismatch", i) + } + _, hashes, err := blobs.ComputeCommitmentsAndHashes([]kzg4844.Blob{legacyBlobs[i]}) + if err != nil { + return fmt.Errorf("failed to compute hash for blob %d: %w", i, err) + } + fmt.Printf(" Blob %d: ✓ identical (%s)\n", i, hashes[0].Hex()) + } + + fmt.Println() + fmt.Printf("Performance comparison:\n") + fmt.Printf(" Legacy endpoint: %v\n", legacyDuration) + fmt.Printf(" New endpoint: %v\n", newDuration) + if newDuration < legacyDuration { + improvement := float64(legacyDuration-newDuration) / float64(legacyDuration) * 100 + fmt.Printf(" New endpoint is %.1f%% faster\n", improvement) + } else { + slower := float64(newDuration-legacyDuration) / float64(legacyDuration) * 100 + fmt.Printf(" New endpoint is %.1f%% slower\n", slower) + } + + return nil +} diff --git a/cmd/chaininfo/arbitrum_chain_info.json b/cmd/chaininfo/arbitrum_chain_info.json index 35a6e833f2..0fdc81275f 100644 --- a/cmd/chaininfo/arbitrum_chain_info.json +++ b/cmd/chaininfo/arbitrum_chain_info.json @@ -37,18 +37,6 @@ "InitialArbOSVersion": 6, "InitialChainOwner": "0xd345e41ae2cb00311956aa7109fc801ae8c81a52", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } }, "rollup": { @@ -98,18 +86,6 @@ "InitialArbOSVersion": 1, "InitialChainOwner": "0x9c040726f2a657226ed95712245dee84b650a1b5", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } }, "rollup": { @@ -156,18 +132,6 @@ "InitialArbOSVersion": 2, "InitialChainOwner": "0x186b56023d42b2b4e7616589a5c62eef5fca21dd", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } }, "rollup": { @@ -209,18 +173,6 @@ "InitialArbOSVersion": 40, "InitialChainOwner": "0x0000000000000000000000000000000000000000", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } } }, @@ -253,18 +205,6 @@ "InitialArbOSVersion": 32, "InitialChainOwner": "0x0000000000000000000000000000000000000000", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } } }, @@ -304,18 +244,6 @@ "InitialArbOSVersion": 10, "InitialChainOwner": "0x71B61c2E250AFa05dFc36304D6c91501bE0965D8", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } }, "rollup": { @@ -365,18 +293,6 @@ "InitialArbOSVersion": 10, "InitialChainOwner": "0x35c8d15334Eaf0e4b82417Fe10e28deEa0c5C32B", "GenesisBlockNum": 0 - }, - "blobSchedule": { - "cancun": { - "target": 3, - "max": 6, - "baseFeeUpdateFraction": 3338477 - }, - "prague": { - "target": 6, - "max": 9, - "baseFeeUpdateFraction": 5007716 - } } }, "rollup": diff --git a/cmd/conf/init.go b/cmd/conf/init.go index 30153069fe..777b25de5a 100644 --- a/cmd/conf/init.go +++ b/cmd/conf/init.go @@ -41,6 +41,7 @@ type InitConfig struct { ReorgToBatch int64 `koanf:"reorg-to-batch"` ReorgToMessageBatch int64 `koanf:"reorg-to-message-batch"` ReorgToBlockBatch int64 `koanf:"reorg-to-block-batch"` + ValidateGenesisAssertion bool `koanf:"validate-genesis-assertion"` } var InitConfigDefault = InitConfig{ @@ -71,6 +72,7 @@ var InitConfigDefault = InitConfig{ ReorgToBatch: -1, ReorgToMessageBatch: -1, ReorgToBlockBatch: -1, + ValidateGenesisAssertion: true, } func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { @@ -105,6 +107,7 @@ func InitConfigAddOptions(prefix string, f *pflag.FlagSet) { "\"force\"- force rebuilding which would commence rebuilding despite the status of previous attempts,\n"+ "\"false\"- do not rebuild on startup", ) + f.Bool(prefix+".validate-genesis-assertion", InitConfigDefault.ValidateGenesisAssertion, "tests genesis assertion posted on parent chain against the genesis block created on init") } func (c *InitConfig) Validate() error { diff --git a/cmd/dataavailability/data_availability_check.go b/cmd/dataavailability/data_availability_check.go index 0e73323d1f..d7d5e2ce47 100644 --- a/cmd/dataavailability/data_availability_check.go +++ b/cmd/dataavailability/data_availability_check.go @@ -224,7 +224,7 @@ func (d *DataAvailabilityCheck) checkDataAvailabilityForOldHashInBlockRange(ctx return fmt.Errorf("no das message found between block %d and block %d", oldBlock, latestBlock) } -// Trys to find if DAS message is present in the given log and if present +// Tries to find if DAS message is present in the given log and if present // returns true and validates if the data is available in the storage service. func (d *DataAvailabilityCheck) checkDataAvailability(ctx context.Context, deliveredLog types.Log, metricBase string) (bool, error) { deliveredEvent, err := d.inboxContract.ParseSequencerBatchDelivered(deliveredLog) diff --git a/cmd/deploy/deploy.go b/cmd/deploy/deploy.go index 14d772c76a..ab8914c4fa 100644 --- a/cmd/deploy/deploy.go +++ b/cmd/deploy/deploy.go @@ -138,6 +138,7 @@ func main() { } loserEscrowAddress := common.HexToAddress(*loserEscrowAddressString) + if sequencerAddress != (common.Address{}) && ownerAddress != l1TransactionOpts.From { panic("cannot specify sequencer address if owner is not deployer") } diff --git a/cmd/el-proxy/config.go b/cmd/el-proxy/config.go new file mode 100644 index 0000000000..9216efe0cc --- /dev/null +++ b/cmd/el-proxy/config.go @@ -0,0 +1,168 @@ +package main + +import ( + "fmt" + "reflect" + "time" + + flag "github.com/spf13/pflag" + + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/offchainlabs/nitro/cmd/conf" + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/util/colors" +) + +type ExpressLaneProxyConfig struct { + ExpressLaneURL string `koanf:"express-lane-url"` + RPCURL string `koanf:"rpc-url"` + ChainId int64 `koanf:"chain-id"` + AuctionContractAddress string `koanf:"auction-contract-address"` + Wallet genericconf.WalletConfig `koanf:"wallet"` + + Persistent conf.PersistentConfig `koanf:"persistent"` + Conf genericconf.ConfConfig `koanf:"conf" reload:"hot"` + LogLevel string `koanf:"log-level" reload:"hot"` + LogType string `koanf:"log-type" reload:"hot"` + FileLogging genericconf.FileLoggingConfig `koanf:"file-logging" reload:"hot"` + HTTP genericconf.HTTPConfig `koanf:"http"` + WS genericconf.WSConfig `koanf:"ws"` + IPC genericconf.IPCConfig `koanf:"ipc"` + Metrics bool `koanf:"metrics"` + MetricsServer genericconf.MetricsServerConfig `koanf:"metrics-server"` + PProf bool `koanf:"pprof"` + PprofCfg genericconf.PProf `koanf:"pprof-cfg"` +} + +var HTTPConfigDefault = genericconf.HTTPConfig{ + Addr: "", + Port: genericconf.HTTPConfigDefault.Port, + API: []string{}, + RPCPrefix: genericconf.HTTPConfigDefault.RPCPrefix, + CORSDomain: genericconf.HTTPConfigDefault.CORSDomain, + VHosts: genericconf.HTTPConfigDefault.VHosts, + ServerTimeouts: genericconf.HTTPConfigDefault.ServerTimeouts, +} + +var WSConfigDefault = genericconf.WSConfig{ + Addr: "", + Port: genericconf.WSConfigDefault.Port, + API: []string{}, + RPCPrefix: genericconf.WSConfigDefault.RPCPrefix, + Origins: genericconf.WSConfigDefault.Origins, + ExposeAll: genericconf.WSConfigDefault.ExposeAll, +} + +var IPCConfigDefault = genericconf.IPCConfig{ + Path: "", +} + +var ExpressLaneProxyConfigDefault = ExpressLaneProxyConfig{ + ExpressLaneURL: "http://localhost:8547", + RPCURL: "http://localhost:8547", + ChainId: 412346, // nitro-testnode chainid + AuctionContractAddress: "", + + Conf: genericconf.ConfConfigDefault, + LogLevel: "INFO", + LogType: "plaintext", + HTTP: HTTPConfigDefault, + WS: WSConfigDefault, + IPC: IPCConfigDefault, + Metrics: false, + MetricsServer: genericconf.MetricsServerConfigDefault, + PProf: false, + Persistent: conf.PersistentConfigDefault, + PprofCfg: genericconf.PProfDefault, +} + +func ExpressLaneProxyConfigAddOptions(f *flag.FlagSet) { + f.String("express-lane-url", ExpressLaneProxyConfigDefault.ExpressLaneURL, "URL to send timeboost_sendExpressLaneTransaction requests to") + f.String("rpc-url", ExpressLaneProxyConfigDefault.RPCURL, "URL to proxy to all other RPC requests to") + f.Int64("chain-id", ExpressLaneProxyConfigDefault.ChainId, "Chain ID of the chain being proxied to") + f.String("auction-contract-address", ExpressLaneProxyConfigDefault.AuctionContractAddress, "Address of the proxy pointing to the ExpressLaneAuction contract") + genericconf.WalletConfigAddOptions("wallet", f, "wallet with account for proxy to use to sign txs") + + conf.PersistentConfigAddOptions("persistent", f) + genericconf.ConfConfigAddOptions("conf", f) + f.String("log-level", ExpressLaneProxyConfigDefault.LogLevel, "log level, valid values are CRIT, ERROR, WARN, INFO, DEBUG, TRACE") + f.String("log-type", ExpressLaneProxyConfigDefault.LogType, "log type (plaintext or json)") + genericconf.FileLoggingConfigAddOptions("file-logging", f) + genericconf.HTTPConfigAddOptions("http", f) + genericconf.WSConfigAddOptions("ws", f) + genericconf.IPCConfigAddOptions("ipc", f) + f.Bool("metrics", ExpressLaneProxyConfigDefault.Metrics, "enable metrics") + genericconf.MetricsServerAddOptions("metrics-server", f) + f.Bool("pprof", ExpressLaneProxyConfigDefault.PProf, "enable pprof") + genericconf.PProfAddOptions("pprof-cfg", f) +} + +func (c *ExpressLaneProxyConfig) ShallowClone() *ExpressLaneProxyConfig { + config := &ExpressLaneProxyConfig{} + *config = *c + return config +} + +func (c *ExpressLaneProxyConfig) CanReload(new *ExpressLaneProxyConfig) error { + var check func(node, other reflect.Value, path string) + var err error + + check = func(node, value reflect.Value, path string) { + if node.Kind() != reflect.Struct { + return + } + + for i := 0; i < node.NumField(); i++ { + fieldTy := node.Type().Field(i) + if !fieldTy.IsExported() { + continue + } + hot := fieldTy.Tag.Get("reload") == "hot" + dot := path + "." + fieldTy.Name + + first := node.Field(i).Interface() + other := value.Field(i).Interface() + + if !hot && !reflect.DeepEqual(first, other) { + err = fmt.Errorf("illegal change to %v%v%v", colors.Red, dot, colors.Clear) + } else { + check(node.Field(i), value.Field(i), dot) + } + } + } + + check(reflect.ValueOf(c).Elem(), reflect.ValueOf(new).Elem(), "config") + return err +} + +func (c *ExpressLaneProxyConfig) GetReloadInterval() time.Duration { + return c.Conf.ReloadInterval +} + +func (c *ExpressLaneProxyConfig) Validate() error { + return nil +} + +var DefaultExpressLaneProxyStackConfig = node.Config{ + DataDir: node.DefaultDataDir(), + HTTPPort: node.DefaultHTTPPort, + AuthAddr: node.DefaultAuthHost, + AuthPort: node.DefaultAuthPort, + AuthVirtualHosts: node.DefaultAuthVhosts, + HTTPModules: []string{"eth"}, + HTTPHost: "localhost", + HTTPVirtualHosts: []string{"localhost"}, + HTTPTimeouts: rpc.DefaultHTTPTimeouts, + WSHost: "localhost", + WSPort: node.DefaultWSPort, + WSModules: []string{"eth"}, + GraphQLVirtualHosts: []string{"localhost"}, + P2P: p2p.Config{ + ListenAddr: "", + NoDiscovery: true, + NoDial: true, + }, +} diff --git a/cmd/el-proxy/main.go b/cmd/el-proxy/main.go new file mode 100644 index 0000000000..6b16ad80e5 --- /dev/null +++ b/cmd/el-proxy/main.go @@ -0,0 +1,457 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/nitro/blob/master/LICENSE + +// el-proxy is an example implementation of a Timeboost Express Lane proxy +// and should only be used for testing purposes. It listens for +// eth_sendRawTransaction messages, wraps them, and forwards them to +// an endpoint implementing timeboost_sendExpressLaneTransaction. +// It also forwards other methods needed by tools like cast to build +// and check on the transaction to an RPC url which may be different to +// the express lane url. + +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "math/big" + "net/http" + "net/url" + "os" + "os/signal" + "path/filepath" + "syscall" + "time" + + flag "github.com/spf13/pflag" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/metrics/exp" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rpc" + + "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/cmd/util" + "github.com/offchainlabs/nitro/cmd/util/confighelpers" + "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/express_lane_auctiongen" + "github.com/offchainlabs/nitro/timeboost" + "github.com/offchainlabs/nitro/util/signature" + "github.com/offchainlabs/nitro/util/stopwaiter" +) + +func printSampleUsage(name string) { + fmt.Printf("Sample usage: %s --help \n", name) +} + +func main() { + os.Exit(mainImpl()) +} + +// Checks metrics and PProf flag, runs them if enabled. +// Note: they are separate so one can enable/disable them as they wish, the only +// requirement is that they can't run on the same address and port. +func startMetrics(cfg *ExpressLaneProxyConfig) error { + mAddr := fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port) + pAddr := fmt.Sprintf("%v:%v", cfg.PprofCfg.Addr, cfg.PprofCfg.Port) + if cfg.Metrics && !metrics.Enabled() { + return fmt.Errorf("metrics must be enabled via command line by adding --metrics, json config has no effect") + } + if cfg.Metrics && cfg.PProf && mAddr == pAddr { + return fmt.Errorf("metrics and pprof cannot be enabled on the same address:port: %s", mAddr) + } + if cfg.Metrics { + go metrics.CollectProcessMetrics(time.Second) + exp.Setup(fmt.Sprintf("%v:%v", cfg.MetricsServer.Addr, cfg.MetricsServer.Port)) + } + if cfg.PProf { + genericconf.StartPprof(pAddr) + } + return nil +} + +type ExpressLaneProxy struct { + stopwaiter.StopWaiter + config *ExpressLaneProxyConfig + roundTimingInfo timeboost.RoundTimingInfo + auctionContractAddr common.Address + expressLaneTracker *gethexec.ExpressLaneTracker + dataSignerFunc signature.DataSignerFunc +} + +type HeaderProviderAdapter struct { + *ethclient.Client +} + +func (a *HeaderProviderAdapter) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + return a.Client.HeaderByNumber(ctx, big.NewInt((int64)(number))) +} + +func NewExpressLaneProxy( + ctx context.Context, + config *ExpressLaneProxyConfig, + stack *node.Node, +) (*ExpressLaneProxy, error) { + client, err := GetClientFromURL(ctx, config.RPCURL, nil) + if err != nil { + return nil, err + } + arbClient := ethclient.NewClient(client) + + if !common.IsHexAddress(config.AuctionContractAddress) { + return nil, fmt.Errorf("invalid auction-contract-address \"%v\"", config.AuctionContractAddress) + } + auctionContractAddr := common.HexToAddress(config.AuctionContractAddress) + auctionContract, err := express_lane_auctiongen.NewExpressLaneAuction(auctionContractAddr, arbClient) + if err != nil { + return nil, err + } + + roundTimingInfo, err := gethexec.GetRoundTimingInfo(auctionContract) + if err != nil { + return nil, err + } + + expressLaneTracker := gethexec.NewExpressLaneTracker( + *roundTimingInfo, + time.Millisecond*250, + &HeaderProviderAdapter{arbClient}, + auctionContract, + auctionContractAddr, + ¶ms.ChainConfig{ChainID: big.NewInt(config.ChainId)}, + 0, + ) + + _, dataSignerFunc, err := util.OpenWallet("el-proxy", &config.Wallet, big.NewInt(config.ChainId)) + if err != nil { + return nil, fmt.Errorf("error opening wallet: %w", err) + } + + elProxy := &ExpressLaneProxy{ + config: config, + roundTimingInfo: *roundTimingInfo, + auctionContractAddr: auctionContractAddr, + expressLaneTracker: expressLaneTracker, + dataSignerFunc: dataSignerFunc, + } + + elAPIs := []rpc.API{{ + Namespace: "eth", + Version: "1.0", + Service: elProxy, + Public: true, + }} + + stack.RegisterAPIs(elAPIs) + return elProxy, nil +} + +func (p *ExpressLaneProxy) Start(ctx context.Context) { + p.StopWaiter.Start(ctx, p) + p.expressLaneTracker.Start(ctx) +} + +func (p *ExpressLaneProxy) StopAndWait() { + p.StopWaiter.StopAndWait() + p.expressLaneTracker.StopAndWait() +} + +var ErrorInternalConnectionError = errors.New("internal connection error") + +func GetClientFromURL(ctx context.Context, rawUrl string, transport *http.Transport) (*rpc.Client, error) { + u, err := url.Parse(rawUrl) + if err != nil { + log.Error("error getting client from url", "error", err) + return nil, ErrorInternalConnectionError + } + + var rpcClient *rpc.Client + switch u.Scheme { + case "http", "https": + if transport != nil { + client := &http.Client{ + Transport: transport, + } + rpcClient, err = rpc.DialOptions(ctx, rawUrl, rpc.WithHTTPClient(client)) + } else { + rpcClient, err = rpc.DialHTTP(rawUrl) + } + case "ws", "wss": + rpcClient, err = rpc.DialWebsocket(ctx, rawUrl, "") + default: + log.Error("no known transport", "scheme", u.Scheme, "url", rawUrl) + return nil, ErrorInternalConnectionError + } + if err != nil { + log.Error("error connecting to client", "error", err, "url", rawUrl) + return nil, ErrorInternalConnectionError + } + return rpcClient, nil +} + +func (p *ExpressLaneProxy) buildSignature(data []byte) ([]byte, error) { + prefixedData := crypto.Keccak256(append([]byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%d", len(data))), data...)) + signature, err := p.dataSignerFunc(prefixedData) + if err != nil { + return nil, err + } + return signature, nil +} + +func (p *ExpressLaneProxy) SendRawTransaction(ctx context.Context, input hexutil.Bytes) (common.Hash, error) { + roundNumber := p.roundTimingInfo.RoundNumber() + + wrapper := timeboost.JsonExpressLaneSubmission{ + ChainId: (*hexutil.Big)(big.NewInt(p.config.ChainId)), + Round: (hexutil.Uint64)(roundNumber), + AuctionContractAddress: p.auctionContractAddr, + Transaction: input, + SequenceNumber: (hexutil.Uint64)(gethexec.DontCareSequence), + Signature: []byte{}, // It is set below + } + goWrapper, err := timeboost.JsonSubmissionToGo(&wrapper) + if err != nil { + return common.Hash{}, fmt.Errorf("Error converting submission: %w", err) + } + signableMsg, err := goWrapper.ToMessageBytes() + if err != nil { + return common.Hash{}, fmt.Errorf("Error serializing signable msg: %w", err) + } + sig, err := p.buildSignature(signableMsg) + if err != nil { + return common.Hash{}, fmt.Errorf("Error signing msg: %w", err) + } + wrapper.Signature = sig + + client, err := GetClientFromURL(ctx, p.config.ExpressLaneURL, nil) + if err != nil { + return common.Hash{}, fmt.Errorf("Error getting client: %w", err) + } + + log.Info("Sending timeboost_sendExpressLaneTransaction", "round", roundNumber, "txHash", goWrapper.Transaction.Hash().Hex()) + err = client.CallContext(ctx, nil, "timeboost_sendExpressLaneTransaction", &wrapper) + if err != nil { + return common.Hash{}, fmt.Errorf("Error forwarding msg: %w", err) + } + + return goWrapper.Transaction.Hash(), nil +} + +// We need to proxy some other methods for tools like cast to use when building txs. + +func (p *ExpressLaneProxy) ChainId(_ context.Context) hexutil.Uint64 { + chainId := p.config.ChainId + // #nosec G115 + return (hexutil.Uint64)(chainId) +} + +func (p *ExpressLaneProxy) GetTransactionCount(ctx context.Context, address common.Address, blockNumOrHash rpc.BlockNumberOrHash) (hexutil.Uint64, error) { + client, err := GetClientFromURL(ctx, p.config.RPCURL, nil) + if err != nil { + return 0, err + } + + var result hexutil.Uint64 + err = client.CallContext(ctx, &result, "eth_getTransactionCount", address, blockNumOrHash) + return result, err +} + +func (p *ExpressLaneProxy) FeeHistory(ctx context.Context, blockCount hexutil.Uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (json.RawMessage, error) { + var result json.RawMessage + + client, err := GetClientFromURL(ctx, p.config.RPCURL, nil) + if err != nil { + return result, err + } + + err = client.CallContext(ctx, &result, "eth_feeHistory", blockCount, lastBlock, rewardPercentiles) + return result, err +} + +func (p *ExpressLaneProxy) BlockNumber(ctx context.Context) (uint64, error) { + client, err := GetClientFromURL(ctx, p.config.RPCURL, nil) + if err != nil { + return 0, err + } + + return ethclient.NewClient(client).BlockNumber(ctx) +} + +func (p *ExpressLaneProxy) GetBlockByNumber(ctx context.Context, blockNum *rpc.BlockNumber, includeTxData bool) (json.RawMessage, error) { + var result json.RawMessage + + client, err := GetClientFromURL(ctx, p.config.RPCURL, nil) + if err != nil { + return result, err + } + + err = client.CallContext(ctx, &result, "eth_getBlockByNumber", blockNum, includeTxData) + return result, err +} + +func (p *ExpressLaneProxy) GetTransactionReceipt(ctx context.Context, txHash hexutil.Bytes, opts *json.RawMessage) (json.RawMessage, error) { + log.Debug("Received eth_getTransactionReceipt", "txHash", txHash) + + var result json.RawMessage + client, err := GetClientFromURL(ctx, p.config.RPCURL, nil) + if err != nil { + return result, err + } + + err = client.CallContext(ctx, &result, "eth_getTransactionReceipt", txHash, opts) + return result, err + +} + +func mainImpl() int { + ctx, cancelFunc := context.WithCancel(context.Background()) + defer cancelFunc() + + args := os.Args[1:] + expressLaneProxyConfig, err := parseExpressLaneProxyArgs(ctx, args) + if err != nil { + confighelpers.PrintErrorAndExit(err, printSampleUsage) + panic(err) + } + stackConf := DefaultExpressLaneProxyStackConfig + stackConf.DataDir = "" // ephemeral + expressLaneProxyConfig.HTTP.Apply(&stackConf) + expressLaneProxyConfig.WS.Apply(&stackConf) + expressLaneProxyConfig.IPC.Apply(&stackConf) + stackConf.P2P.ListenAddr = "" + stackConf.P2P.NoDial = true + stackConf.P2P.NoDiscovery = true + vcsRevision, strippedRevision, vcsTime := confighelpers.GetVersion() + stackConf.Version = strippedRevision + + pathResolver := func(workdir string) func(string) string { + if workdir == "" { + workdir, err = os.Getwd() + if err != nil { + log.Warn("Failed to get workdir", "err", err) + } + } + return func(path string) string { + if filepath.IsAbs(path) { + return path + } + return filepath.Join(workdir, path) + } + } + + err = genericconf.InitLog(expressLaneProxyConfig.LogType, expressLaneProxyConfig.LogLevel, &expressLaneProxyConfig.FileLogging, pathResolver(expressLaneProxyConfig.Persistent.LogDir)) + if err != nil { + fmt.Fprintf(os.Stderr, "Error initializing logging: %v\n", err) + return 1 + } + if stackConf.JWTSecret == "" && stackConf.AuthAddr != "" { + filename := pathResolver(expressLaneProxyConfig.Persistent.GlobalConfig)("jwtsecret") + if err := genericconf.TryCreatingJWTSecret(filename); err != nil { + log.Error("Failed to prepare jwt secret file", "err", err) + return 1 + } + stackConf.JWTSecret = filename + } + + liveNodeConfig := genericconf.NewLiveConfig[*ExpressLaneProxyConfig](args, expressLaneProxyConfig, parseExpressLaneProxyArgs) + liveNodeConfig.SetOnReloadHook(func(oldCfg *ExpressLaneProxyConfig, newCfg *ExpressLaneProxyConfig) error { + + return genericconf.InitLog(newCfg.LogType, newCfg.LogLevel, &newCfg.FileLogging, pathResolver(expressLaneProxyConfig.Persistent.LogDir)) + }) + + if err := startMetrics(expressLaneProxyConfig); err != nil { + log.Error("Error starting metrics", "error", err) + return 1 + } + + fatalErrChan := make(chan error, 10) + + log.Info("Running Arbitrum Express Lane Proxy", "revision", vcsRevision, "vcs.time", vcsTime) + stack, err := node.New(&stackConf) + if err != nil { + flag.Usage() + log.Crit("failed to initialize geth stack", "err", err) + } + proxy, err := NewExpressLaneProxy(ctx, expressLaneProxyConfig, stack) + if err != nil { + log.Error("error", "err", err) + return 1 + } + + err = stack.Start() + if err != nil { + log.Error("error", "err", err) + return 1 + } + defer stack.Close() + + proxy.Start(ctx) + defer proxy.StopAndWait() + + liveNodeConfig.Start(ctx) + defer liveNodeConfig.StopAndWait() + + sigint := make(chan os.Signal, 1) + signal.Notify(sigint, os.Interrupt, syscall.SIGTERM) + + exitCode := 0 + select { + case err := <-fatalErrChan: + log.Error("shutting down due to fatal error", "err", err) + defer log.Error("shut down due to fatal error", "err", err) + exitCode = 1 + case <-sigint: + log.Info("shutting down because of sigint") + } + // cause future ctrl+c's to panic + close(sigint) + return exitCode +} + +func parseExpressLaneProxyArgs(ctx context.Context, args []string) (*ExpressLaneProxyConfig, error) { + f := flag.NewFlagSet("", flag.ContinueOnError) + + ExpressLaneProxyConfigAddOptions(f) + + k, err := confighelpers.BeginCommonParse(f, args) + if err != nil { + return nil, err + } + + err = confighelpers.ApplyOverrides(f, k) + if err != nil { + return nil, err + } + + var cfg ExpressLaneProxyConfig + if err := confighelpers.EndCommonParse(k, &cfg); err != nil { + return nil, err + } + + // Don't print wallet passwords + if cfg.Conf.Dump { + err = confighelpers.DumpConfig(k, map[string]interface{}{ + "wallet.password": "", + "wallet.private-key": "", + }) + if err != nil { + return nil, err + } + } + + // Don't pass around wallet contents with normal configuration + err = cfg.Validate() + if err != nil { + return nil, err + } + return &cfg, nil +} diff --git a/cmd/genesis-generator/genesis-generator.go b/cmd/genesis-generator/genesis-generator.go new file mode 100644 index 0000000000..5e70f8b646 --- /dev/null +++ b/cmd/genesis-generator/genesis-generator.go @@ -0,0 +1,160 @@ +package main + +import ( + "encoding/json" + "fmt" + "math/big" + "os" + + flag "github.com/spf13/pflag" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/arbos/arbosState" + "github.com/offchainlabs/nitro/arbos/arbostypes" + "github.com/offchainlabs/nitro/cmd/util/confighelpers" + "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/statetransfer" + "github.com/offchainlabs/nitro/validator" +) + +func main() { + if err := mainImpl(); err != nil { + log.Error("Error running genesis generator", "error", err) + os.Exit(1) + } +} + +func mainImpl() error { + args := os.Args[1:] + f := flag.NewFlagSet("", flag.ContinueOnError) + + ConfigAddOptions(f) + + k, err := confighelpers.BeginCommonParse(f, args) + if err != nil { + return fmt.Errorf("failed to parse flags: %w", err) + } + + err = confighelpers.ApplyOverrides(f, k) + if err != nil { + return fmt.Errorf("failed to apply overrides: %w", err) + } + + var config Config + if err := confighelpers.EndCommonParse(k, &config); err != nil { + return fmt.Errorf("failed to parse config: %w", err) + } + + if config.GenesisJsonFile == "" { + return fmt.Errorf("genesis JSON file must be specified") + } + genesisJson, err := os.ReadFile(config.GenesisJsonFile) + if err != nil { + return fmt.Errorf("failed to read genesis JSON file %s: %w", config.GenesisJsonFile, err) + } + var gen core.Genesis + if err := json.Unmarshal(genesisJson, &gen); err != nil { + return fmt.Errorf("failed to unmarshal genesis JSON: %w", err) + } + var accounts []statetransfer.AccountInitializationInfo + for address, account := range gen.Alloc { + accounts = append(accounts, statetransfer.AccountInitializationInfo{ + Addr: address, + EthBalance: account.Balance, + Nonce: account.Nonce, + ContractInfo: &statetransfer.AccountInitContractInfo{ + Code: account.Code, + ContractStorage: account.Storage, + }, + }) + } + initDataReader := statetransfer.NewMemoryInitDataReader(&statetransfer.ArbosInitializationInfo{ + Accounts: accounts, + }) + chainConfig := gen.Config + genesisArbOSInit := gen.ArbOSInit + serializedChainConfig, err := json.Marshal(chainConfig) + if err != nil { + return fmt.Errorf("failed to serialize chain config: %w", err) + } + parsedInitMessage := &arbostypes.ParsedInitMessage{ + ChainId: chainConfig.ChainID, + InitialL1BaseFee: big.NewInt(config.InitialL1BaseFee), + ChainConfig: chainConfig, + SerializedChainConfig: serializedChainConfig, + } + genesisBlock, err := generateGenesisBlock(rawdb.NewMemoryDatabase(), + gethexec.DefaultCacheConfigFor(&config.Caching), + initDataReader, + chainConfig, + genesisArbOSInit, + parsedInitMessage, + config.AccountsPerSync, + ) + if err != nil { + return fmt.Errorf("failed to generate genesis hash: %w", err) + } + globalState := validator.GoGlobalState{ + BlockHash: genesisBlock.Hash(), + SendRoot: genesisBlock.Root(), + Batch: 1, + PosInBatch: 0, + } + fmt.Print(globalState) + return nil +} + +func generateGenesisBlock(chainDb ethdb.Database, cacheConfig *core.CacheConfig, initData statetransfer.InitDataReader, chainConfig *params.ChainConfig, genesisArbOSInit *params.ArbOSInit, initMessage *arbostypes.ParsedInitMessage, accountsPerSync uint) (*types.Block, error) { + EmptyHash := common.Hash{} + prevHash := EmptyHash + blockNumber, err := initData.GetNextBlockNumber() + if err != nil { + return nil, err + } + timestamp := uint64(0) + if blockNumber > 0 { + prevHash = rawdb.ReadCanonicalHash(chainDb, blockNumber-1) + if prevHash == EmptyHash { + return nil, fmt.Errorf("block number %d not found in database", chainDb) + } + prevHeader := rawdb.ReadHeader(chainDb, prevHash, blockNumber-1) + if prevHeader == nil { + return nil, fmt.Errorf("block header for block %d not found in database", chainDb) + } + timestamp = prevHeader.Time + } + stateRoot, err := arbosState.InitializeArbosInDatabase(chainDb, cacheConfig, initData, chainConfig, genesisArbOSInit, initMessage, timestamp, accountsPerSync) + if err != nil { + return nil, err + } + + return arbosState.MakeGenesisBlock(prevHash, blockNumber, timestamp, stateRoot, chainConfig), nil +} + +type Config struct { + Caching gethexec.CachingConfig `koanf:"caching"` + GenesisJsonFile string `koanf:"genesis-json-file"` + AccountsPerSync uint `koanf:"accounts-per-sync"` + InitialL1BaseFee int64 `koanf:"initial-l1-base-fee"` +} + +var ConfigDefault = Config{ + Caching: gethexec.DefaultCachingConfig, + GenesisJsonFile: "", + AccountsPerSync: 100000, + InitialL1BaseFee: arbostypes.DefaultInitialL1BaseFee.Int64(), +} + +func ConfigAddOptions(f *flag.FlagSet) { + gethexec.CachingConfigAddOptions("caching", f) + f.String("genesis-json-file", ConfigDefault.GenesisJsonFile, "path for genesis json file") + f.Uint("accounts-per-sync", ConfigDefault.AccountsPerSync, "during init - sync database every X accounts. Lower value for low-memory systems. 0 disables.") + f.Int64("initial-l1-base-fee", ConfigDefault.InitialL1BaseFee, "initial L1 base fee for genesis block") +} diff --git a/cmd/mel-replay/db.go b/cmd/mel-replay/db.go new file mode 100644 index 0000000000..ea4a0a820a --- /dev/null +++ b/cmd/mel-replay/db.go @@ -0,0 +1,125 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" + + "github.com/offchainlabs/nitro/arbutil" +) + +// Ensures that the DB implementation satisfied the ethdb.Database interface. +var _ ethdb.Database = (*DB)(nil) + +type DB struct { + resolver preimageResolver +} + +func (d *DB) Get(key []byte) ([]byte, error) { + if len(key) != 32 { + return nil, fmt.Errorf("expected 32 byte key query, but got %d bytes: %x", len(key), key) + } + preimage, err := d.resolver.ResolveTypedPreimage(arbutil.Keccak256PreimageType, common.BytesToHash(key)) + if err != nil { + return nil, fmt.Errorf("error resolving preimage for %#x: %w", key, err) + } + return preimage, nil +} + +func (d *DB) Has(key []byte) (bool, error) { + return false, errors.New("unimplemented") +} + +func (d *DB) Put(key []byte, value []byte) error { + return errors.New("unimplemented") +} + +func (p DB) Delete(key []byte) error { + return errors.New("unimplemented") +} + +func (d *DB) DeleteRange(start, end []byte) error { + return errors.New("unimplemented") +} + +func (p DB) Stat() (string, error) { + return "", errors.New("unimplemented") +} + +func (p DB) NewBatch() ethdb.Batch { + panic("unimplemented") +} + +func (p DB) NewBatchWithSize(size int) ethdb.Batch { + panic("unimplemented") +} + +func (p DB) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + panic("unimplemented") +} + +func (p DB) Compact(start []byte, limit []byte) error { + return nil // no-op +} + +func (p DB) Close() error { + return nil +} + +func (d *DB) HasAncient(kind string, number uint64) (bool, error) { + return false, errors.New("unimplemented") +} + +func (d *DB) Ancient(kind string, number uint64) ([]byte, error) { + return nil, errors.New("unimplemented") +} + +func (d *DB) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + return nil, errors.New("unimplemented") +} + +func (d *DB) Ancients() (uint64, error) { + return 0, errors.New("unimplemented") +} + +func (d *DB) Tail() (uint64, error) { + return 0, errors.New("unimplemented") +} + +func (d *DB) AncientSize(kind string) (uint64, error) { + return 0, errors.New("unimplemented") +} + +func (d *DB) ReadAncients(fn func(ethdb.AncientReaderOp) error) (err error) { + panic("unimplemented") +} + +func (d *DB) ModifyAncients(f func(ethdb.AncientWriteOp) error) (int64, error) { + return 0, errors.New("unimplemented") +} + +func (d *DB) TruncateHead(n uint64) (uint64, error) { + return 0, errors.New("unimplemented") +} + +func (d *DB) TruncateTail(n uint64) (uint64, error) { + return 0, errors.New("unimplemented") +} + +func (d *DB) Sync() error { + return errors.New("unimplemented") +} + +func (d *DB) MigrateTable(s string, f func([]byte) ([]byte, error)) error { + return errors.New("unimplemented") +} + +func (d *DB) AncientDatadir() (string, error) { + return "", errors.New("unimplemented") +} + +func (d *DB) WasmDataBase() ethdb.KeyValueStore { + panic("unimplemented") +} diff --git a/cmd/mel-replay/delayed_message_db.go b/cmd/mel-replay/delayed_message_db.go new file mode 100644 index 0000000000..05d8c5cb9f --- /dev/null +++ b/cmd/mel-replay/delayed_message_db.go @@ -0,0 +1,85 @@ +package main + +import ( + "bytes" + "context" + "fmt" + "math/bits" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbutil" +) + +type delayedMessageDatabase struct { + preimageResolver preimageResolver +} + +func (d *delayedMessageDatabase) ReadDelayedMessage( + ctx context.Context, + state *mel.State, + msgIndex uint64, +) (*mel.DelayedInboxMessage, error) { + originalMsgIndex := msgIndex + totalMsgsSeen := state.DelayedMessagedSeen + if msgIndex >= totalMsgsSeen { + return nil, fmt.Errorf("index %d out of range, total delayed messages seen: %d", msgIndex, totalMsgsSeen) + } + treeSize := nextPowerOfTwo(totalMsgsSeen) + merkleDepth := bits.TrailingZeros64(treeSize) + + // Start traversal from root, which is the delayed messages seen root. + merkleRoot := state.DelayedMessagesSeenRoot + currentHash := merkleRoot + currentDepth := merkleDepth + + // Traverse down the Merkle tree to find the leaf at the given index. + for currentDepth > 0 { + // Resolve the preimage to get left and right children. + result, err := d.preimageResolver.ResolveTypedPreimage(arbutil.Keccak256PreimageType, currentHash) + if err != nil { + return nil, err + } + if len(result) != 64 { + return nil, fmt.Errorf("invalid preimage result length: %d, wanted 64", len(result)) + } + // Split result into left and right halves. + mid := len(result) / 2 + left := result[:mid] + right := result[mid:] + + // Calculate which subtree contains our index. + subtreeSize := uint64(1) << (currentDepth - 1) + if msgIndex < subtreeSize { + // Go left. + currentHash = common.BytesToHash(left) + } else { + // Go right. + currentHash = common.BytesToHash(right) + msgIndex -= subtreeSize + } + currentDepth-- + } + // At this point, currentHash should be the hash of the delayed message. + delayedMsgBytes, err := d.preimageResolver.ResolveTypedPreimage(arbutil.Keccak256PreimageType, currentHash) + if err != nil { + return nil, err + } + delayedMessage := new(mel.DelayedInboxMessage) + if err = rlp.Decode(bytes.NewBuffer(delayedMsgBytes), &delayedMessage); err != nil { + return nil, fmt.Errorf("failed to decode delayed message at index %d: %w", originalMsgIndex, err) + } + return delayedMessage, nil +} + +func nextPowerOfTwo(n uint64) uint64 { + if n == 0 { + return 1 + } + if n&(n-1) == 0 { + return n + } + return 1 << bits.Len64(n) +} diff --git a/cmd/mel-replay/delayed_message_db_test.go b/cmd/mel-replay/delayed_message_db_test.go new file mode 100644 index 0000000000..c759c9a14b --- /dev/null +++ b/cmd/mel-replay/delayed_message_db_test.go @@ -0,0 +1,196 @@ +package main + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbnode/mel" + "github.com/offchainlabs/nitro/arbos/arbostypes" +) + +var _ preimageResolver = (*mockPreimageResolver)(nil) +var _ mel.DelayedMessageDatabase = (*delayedMessageDatabase)(nil) + +func TestReadDelayedMessage(t *testing.T) { + ctx := context.Background() + t.Run("message index out of range", func(t *testing.T) { + db := &delayedMessageDatabase{} + state := &mel.State{ + DelayedMessagedSeen: 5, + } + _, err := db.ReadDelayedMessage(ctx, state, 5) + require.ErrorContains(t, err, "index 5 out of range, total delayed messages seen: 5") + }) + t.Run("single message in Merkle tree", func(t *testing.T) { + // If there is only a single delayed message in the + // Merkle tree, then it should be easy to retrieve as a preimage + // lookup of the root itself. + messages := []*mel.DelayedInboxMessage{ + buildDelayedMessage(t, 100, []byte("foobar")), + } + + preimages, root := buildMerkleTree(t, messages) + + resolver := &mockPreimageResolver{preimages: preimages} + db := &delayedMessageDatabase{preimageResolver: resolver} + state := &mel.State{ + DelayedMessagedSeen: 1, + DelayedMessagesSeenRoot: root, + } + + msg, err := db.ReadDelayedMessage(ctx, state, uint64(0)) // #nosec G115 + require.NoError(t, err) + require.Equal(t, []byte("foobar"), msg.Message.L2msg) + }) + t.Run("Merkle tree with 2 levels can fetch left or right delayed message", func(t *testing.T) { + // We have a Merkle tree for delayed messages that looks like this: + // + // hash(A++B) + // / \ + // A B + // + // Where A and B are delayed messages hashes. + // If we want to fetch delayed message at index 0, we should get A, + // and if we want to fetch delayed message at index 1, we should get B + // through our algorithm. + messages := []*mel.DelayedInboxMessage{ + buildDelayedMessage(t, 1, []byte("a")), + buildDelayedMessage(t, 2, []byte("b")), + } + + preimages, root := buildMerkleTree(t, messages) + + resolver := &mockPreimageResolver{preimages: preimages} + db := &delayedMessageDatabase{preimageResolver: resolver} + state := &mel.State{ + DelayedMessagedSeen: 2, + DelayedMessagesSeenRoot: root, + } + + // Test each message + expectedData := [][]byte{[]byte("a"), []byte("b")} + for i, expected := range expectedData { + msg, err := db.ReadDelayedMessage(ctx, state, uint64(i)) // #nosec G115 + require.NoError(t, err) + require.Equal(t, expected, msg.Message.L2msg) + } + }) + t.Run("Merkle tree with 3 levels can fetch specific delayed messages", func(t *testing.T) { + // We have a Merkle tree for delayed messages that looks like this: + // + // hash(hash(A++B)++hash(C++D)) + // / \ + // hash(A++B) hash(C++D) + // / \ / \ + // A B C EMPTY + // + // We should be able to fetch A, B, C. + messages := []*mel.DelayedInboxMessage{ + buildDelayedMessage(t, 1, []byte("a")), + buildDelayedMessage(t, 2, []byte("b")), + buildDelayedMessage(t, 3, []byte("c")), + } + + preimages, root := buildMerkleTree(t, messages) + + resolver := &mockPreimageResolver{preimages: preimages} + db := &delayedMessageDatabase{preimageResolver: resolver} + state := &mel.State{ + DelayedMessagedSeen: 3, + DelayedMessagesSeenRoot: root, + } + + // Test each message + expectedData := [][]byte{[]byte("a"), []byte("b"), []byte("c")} + for i, expected := range expectedData { + msg, err := db.ReadDelayedMessage(ctx, state, uint64(i)) // #nosec G115 + require.NoError(t, err) + require.Equal(t, expected, msg.Message.L2msg) + } + }) +} + +func TestNextPowerOfTwo(t *testing.T) { + testCases := []struct { + input uint64 + expected uint64 + }{ + {0, 1}, + {1, 1}, + {2, 2}, + {3, 4}, + {4, 4}, + {5, 8}, + {8, 8}, + {9, 16}, + {16, 16}, + {17, 32}, + } + + for _, tc := range testCases { + result := nextPowerOfTwo(tc.input) + if result != tc.expected { + t.Errorf("nextPowerOfTwo(%d) = %d, expected %d", tc.input, result, tc.expected) + } + } +} + +func buildDelayedMessage( + _ *testing.T, + blockNumber uint64, + msgData []byte, +) *mel.DelayedInboxMessage { + msg := &mel.DelayedInboxMessage{ + ParentChainBlockNumber: blockNumber, + Message: &arbostypes.L1IncomingMessage{ + Header: &arbostypes.L1IncomingMessageHeader{ + Kind: arbostypes.L1MessageType_L2Message, + }, + L2msg: msgData, + }, + } + return msg +} + +func buildMerkleTree(t *testing.T, messages []*mel.DelayedInboxMessage) (map[common.Hash][]byte, common.Hash) { + preimages := make(map[common.Hash][]byte) + leafHashes := make([]common.Hash, len(messages)) + for i, msg := range messages { + encoded, err := rlp.EncodeToBytes(msg) + require.NoError(t, err) + hash := crypto.Keccak256Hash(encoded) + preimages[hash] = encoded + leafHashes[i] = hash + } + + currentLevel := leafHashes + for len(currentLevel) > 1 { + nextLevel := make([]common.Hash, 0, (len(currentLevel)+1)/2) + + for i := 0; i < len(currentLevel); i += 2 { + left := currentLevel[i] + var right common.Hash + + if i+1 < len(currentLevel) { + right = currentLevel[i+1] + } else { + right = common.Hash{} + } + + preimage := make([]byte, 0) + preimage = append(preimage, left[:]...) + preimage = append(preimage, right[:]...) + parent := crypto.Keccak256Hash(left[:], right[:]) + preimages[parent] = preimage + nextLevel = append(nextLevel, parent) + } + currentLevel = nextLevel + } + return preimages, currentLevel[0] +} diff --git a/cmd/mel-replay/main.go b/cmd/mel-replay/main.go new file mode 100644 index 0000000000..2f4c4e3d30 --- /dev/null +++ b/cmd/mel-replay/main.go @@ -0,0 +1,17 @@ +// Copyright 2025-2026, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package main + +import ( + "github.com/ethereum/go-ethereum/common" + + "github.com/offchainlabs/nitro/arbutil" +) + +type preimageResolver interface { + ResolveTypedPreimage(preimageType arbutil.PreimageType, hash common.Hash) ([]byte, error) +} + +func main() { +} diff --git a/cmd/mel-replay/receipt_fetcher.go b/cmd/mel-replay/receipt_fetcher.go new file mode 100644 index 0000000000..d8c499ea36 --- /dev/null +++ b/cmd/mel-replay/receipt_fetcher.go @@ -0,0 +1,165 @@ +// Copyright 2025-2026, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package main + +import ( + "bytes" + "context" + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbutil" +) + +type receiptFetcherForBlock struct { + header *types.Header + preimageResolver preimageResolver +} + +// ReceiptForTransactionIndex fetches a receipt for a specific transaction index by walking +// the receipt trie of the block header. It uses the preimage resolver to fetch the preimages +// of the trie nodes as needed. +func (rf *receiptFetcherForBlock) ReceiptForTransactionIndex( + ctx context.Context, + txIndex uint, +) (*types.Receipt, error) { + return fetchReceiptFromBlock(rf.header.ReceiptHash, txIndex, rf.preimageResolver) +} + +// Fetches a specific receipt index from a block's receipt trie by navigating its +// Merkle Patricia Trie structure. It uses the preimage resolver to fetch preimages +// of trie nodes as needed, and determines how to navigate depending on the structure of the trie nodes. +func fetchReceiptFromBlock( + receiptsRoot common.Hash, + receiptIndex uint, + preimageResolver preimageResolver, +) (*types.Receipt, error) { + currentNodeHash := receiptsRoot + currentPath := []byte{} // Track nibbles consumed so far. + receiptKey, err := rlp.EncodeToBytes(receiptIndex) + if err != nil { + return nil, err + } + targetNibbles := keyToNibbles(receiptKey) + for { + nodeData, err := preimageResolver.ResolveTypedPreimage(arbutil.Keccak256PreimageType, currentNodeHash) + if err != nil { + return nil, err + } + var node []any + if err = rlp.DecodeBytes(nodeData, &node); err != nil { + return nil, fmt.Errorf("failed to decode RLP node: %w", err) + } + switch len(node) { + case 17: + // We hit a branch node, which has 16 children and a value. + if len(currentPath) == len(targetNibbles) { + // A branch node's 17th item could be the value, so we check if it contains the receipt. + if valueBytes, ok := node[16].([]byte); ok && len(valueBytes) > 0 { + // This branch node has the actual value as the last item, so we decode the receipt + return decodeReceipt(valueBytes) + } + return nil, fmt.Errorf("no receipt found at target key") + } + // Get the next nibble to follow. + targetNibble := targetNibbles[len(currentPath)] + childData, ok := node[targetNibble].([]byte) + if !ok || len(childData) == 0 { + return nil, fmt.Errorf("no child at nibble %d", targetNibble) + } + // Move to the child node, which is the next hash we have to navigate. + currentNodeHash = common.BytesToHash(childData) + currentPath = append(currentPath, targetNibble) + case 2: + keyPath, ok := node[0].([]byte) + if !ok { + return nil, fmt.Errorf("invalid key path in node") + } + key := extractKeyNibbles(keyPath) + expectedPath := make([]byte, 0) + expectedPath = append(expectedPath, currentPath...) + expectedPath = append(expectedPath, key...) + + // Check if it is a leaf or extension node. + leaf, err := isLeaf(keyPath) + if err != nil { + return nil, err + } + if leaf { + // Check that the keyPath matches the target nibbles, + // otherwise, the receipt does not exist in the trie. + if !bytes.Equal(expectedPath, targetNibbles) { + return nil, fmt.Errorf("leaf key does not match target nibbles") + } + rawData, ok := node[1].([]byte) + if !ok { + return nil, fmt.Errorf("invalid receipt data in leaf node") + } + return decodeReceipt(rawData) + } + // If the node is not a leaf node, it is an extension node. + // Check if our target key matches this extension path. + if len(expectedPath) > len(targetNibbles) || !bytes.Equal(expectedPath, targetNibbles[:len(expectedPath)]) { + return nil, fmt.Errorf("extension path mismatch") + } + nextNodeBytes, ok := node[1].([]byte) + if !ok { + return nil, fmt.Errorf("invalid next node in extension") + } + // We navigate to the next node in the trie. + currentNodeHash = common.BytesToHash(nextNodeBytes) + currentPath = expectedPath + default: + return nil, fmt.Errorf("invalid node structure: unexpected length %d", len(node)) + } + } +} + +// Converts a byte slice key into a slice of nibbles (4-bit values). +// Keys are encoded in big endian format, which is required by Ethereum MPTs. +func keyToNibbles(key []byte) []byte { + nibbles := make([]byte, len(key)*2) + for i, b := range key { + nibbles[i*2] = b >> 4 + nibbles[i*2+1] = b & 0x0f + } + return nibbles +} + +// Extracts the key nibbles from a key path, handling odd/even length cases. +func extractKeyNibbles(keyPath []byte) []byte { + if len(keyPath) == 0 { + return nil + } + nibbles := keyToNibbles(keyPath) + if nibbles[0]&1 != 0 { + return nibbles[1:] + } + return nibbles[2:] +} + +func isLeaf(keyPath []byte) (bool, error) { + firstByte := keyPath[0] + firstNibble := firstByte >> 4 + // 2 or 3 indicates leaf, while 0 or 1 indicates extension nodes in the Ethereum MPT specification. + if firstNibble > 3 { + return false, errors.New("first nibble cannot be greater than 3") + } + return firstNibble >= 2, nil +} + +func decodeReceipt(data []byte) (*types.Receipt, error) { + if len(data) == 0 { + return nil, errors.New("empty data cannot be decoded into receipt") + } + rpt := new(types.Receipt) + if err := rpt.UnmarshalBinary(data); err != nil { + return nil, err + } + return rpt, nil +} diff --git a/cmd/mel-replay/receipt_fetcher_test.go b/cmd/mel-replay/receipt_fetcher_test.go new file mode 100644 index 0000000000..bec77c9ea9 --- /dev/null +++ b/cmd/mel-replay/receipt_fetcher_test.go @@ -0,0 +1,123 @@ +// Copyright 2025-2026, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package main + +import ( + "context" + "fmt" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/trie" + + "github.com/offchainlabs/nitro/arbutil" +) + +func TestFetchReceiptFromBlock_Multiple(t *testing.T) { + ctx := context.Background() + // Creates a block with 42 transactions and receipts. + numReceipts := 42 + receipts := createTestReceipts(numReceipts) + hasher := newRecordingHasher() + receiptsRoot := types.DeriveSha(types.Receipts(receipts), hasher) + header := &types.Header{} + txes := make([]*types.Transaction, numReceipts) + for i := 0; i < numReceipts; i++ { + txes[i] = types.NewTransaction(uint64(i), common.Address{}, big.NewInt(0), 21000, big.NewInt(1), nil) // #nosec G115 + } + body := &types.Body{ + Transactions: txes, + } + blk := types.NewBlock(header, body, receipts, hasher) + require.Equal(t, blk.ReceiptHash(), receiptsRoot) + preimages := hasher.GetPreimages() + mockPreimageResolver := &mockPreimageResolver{ + preimages: preimages, + } + receiptFetcher := &receiptFetcherForBlock{ + header: blk.Header(), + preimageResolver: mockPreimageResolver, + } + for i := 0; i < numReceipts; i++ { + receipt, err := receiptFetcher.ReceiptForTransactionIndex(ctx, uint(i)) // #nosec G115 + require.NoError(t, err) + require.Equal(t, receipts[i].CumulativeGasUsed, receipt.CumulativeGasUsed) + } +} + +type mockPreimageResolver struct { + preimages map[common.Hash][]byte +} + +func (m *mockPreimageResolver) ResolveTypedPreimage(preimageType arbutil.PreimageType, hash common.Hash) ([]byte, error) { + if preimage, exists := m.preimages[hash]; exists { + return preimage, nil + } + return nil, fmt.Errorf("preimage not found for hash: %s", hash.Hex()) +} + +// Implements a hasher that captures preimages of hashes as it computes them. +type preimageRecordingHasher struct { + trie *trie.StackTrie + preimages map[common.Hash][]byte +} + +func newRecordingHasher() *preimageRecordingHasher { + h := &preimageRecordingHasher{ + preimages: make(map[common.Hash][]byte), + } + // OnTrieNode callback captures all trie nodes. + onTrieNode := func(path []byte, hash common.Hash, blob []byte) { + // Deep copy the blob since the callback warns contents may change, so this is required. + h.preimages[hash] = common.CopyBytes(blob) + } + + h.trie = trie.NewStackTrie(onTrieNode) + return h +} + +func (h *preimageRecordingHasher) Reset() { + onTrieNode := func(path []byte, hash common.Hash, blob []byte) { + h.preimages[hash] = common.CopyBytes(blob) + } + h.trie = trie.NewStackTrie(onTrieNode) +} + +func (h *preimageRecordingHasher) Update(key, value []byte) error { + valueHash := crypto.Keccak256Hash(value) + h.preimages[valueHash] = common.CopyBytes(value) + return h.trie.Update(key, value) +} + +func (h *preimageRecordingHasher) Hash() common.Hash { + return h.trie.Hash() +} + +func (h *preimageRecordingHasher) GetPreimages() map[common.Hash][]byte { + return h.preimages +} + +func createTestReceipts(count int) types.Receipts { + receipts := make(types.Receipts, count) + for i := 0; i < count; i++ { + receipt := &types.Receipt{ + Status: 1, + CumulativeGasUsed: 50_000 + uint64(i), // #nosec G115 + TxHash: common.Hash{}, + ContractAddress: common.Address{}, + Logs: []*types.Log{}, + BlockHash: common.BytesToHash([]byte("foobar")), + BlockNumber: big.NewInt(100), + TransactionIndex: uint(i), // #nosec G115 + } + receipt.Bloom = types.BytesToBloom(make([]byte, 256)) + receipts[i] = receipt + } + return receipts +} diff --git a/cmd/mel-replay/txs_fetcher.go b/cmd/mel-replay/txs_fetcher.go new file mode 100644 index 0000000000..2307ba0792 --- /dev/null +++ b/cmd/mel-replay/txs_fetcher.go @@ -0,0 +1,90 @@ +package main + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +type txsFetcherForBlock struct { + header *types.Header + preimageResolver preimageResolver +} + +func (tf *txsFetcherForBlock) TransactionsByHeader( + ctx context.Context, + parentChainHeaderHash common.Hash, +) (types.Transactions, error) { + preimageDB := &DB{ + resolver: tf.preimageResolver, + } + tdb := triedb.NewDatabase(preimageDB, nil) + tr, err := trie.New(trie.TrieID(tf.header.TxHash), tdb) + if err != nil { + panic(err) + } + entries, indices := tf.collectTrieEntries(tr) + rawTxs := tf.reconstructOrderedData(entries, indices) + return tf.decodeTransactionData(rawTxs) +} + +func (btr *txsFetcherForBlock) collectTrieEntries(txTrie *trie.Trie) ([][]byte, []uint64) { + nodeIterator, iterErr := txTrie.NodeIterator(nil) + if iterErr != nil { + panic(iterErr) + } + + var rawValues [][]byte + var indexKeys []uint64 + + for nodeIterator.Next(true) { + if !nodeIterator.Leaf() { + continue + } + + leafKey := nodeIterator.LeafKey() + var decodedIndex uint64 + + decodeErr := rlp.DecodeBytes(leafKey, &decodedIndex) + if decodeErr != nil { + panic(fmt.Errorf("key decoding error: %w", decodeErr)) + } + + indexKeys = append(indexKeys, decodedIndex) + rawValues = append(rawValues, nodeIterator.LeafBlob()) + } + + return rawValues, indexKeys +} + +func (btr *txsFetcherForBlock) reconstructOrderedData(rawValues [][]byte, indices []uint64) []hexutil.Bytes { + orderedData := make([]hexutil.Bytes, len(rawValues)) + for position, index := range indices { + if index >= uint64(len(rawValues)) { + panic(fmt.Sprintf("index out of bounds: %d", index)) + } + if orderedData[index] != nil { + panic(fmt.Sprintf("index collision detected: %d", index)) + } + orderedData[index] = rawValues[position] + } + return orderedData +} + +func (btr *txsFetcherForBlock) decodeTransactionData(encodedData []hexutil.Bytes) (types.Transactions, error) { + transactionList := make(types.Transactions, 0, len(encodedData)) + for _, encodedTx := range encodedData { + decodedTx := new(types.Transaction) + if decodeErr := decodedTx.UnmarshalBinary(encodedTx); decodeErr != nil { + return nil, fmt.Errorf("transaction decoding failed: %w", decodeErr) + } + transactionList = append(transactionList, decodedTx) + } + return transactionList, nil +} diff --git a/cmd/mel-replay/txs_fetcher_test.go b/cmd/mel-replay/txs_fetcher_test.go new file mode 100644 index 0000000000..ec8c651516 --- /dev/null +++ b/cmd/mel-replay/txs_fetcher_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + "math/big" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +func TestFetchTransactionsForBlockHeader_DynamicFeeTxs(t *testing.T) { + ctx := context.Background() + total := uint64(42) + txes := make([]*types.Transaction, total) + for i := uint64(0); i < total; i++ { + txData := types.DynamicFeeTx{ + Nonce: i, + To: nil, + Gas: 21000, + GasTipCap: big.NewInt(1), + GasFeeCap: big.NewInt(1), + } + txes[i] = types.NewTx(&txData) + } + hasher := newRecordingHasher() + txsRoot := types.DeriveSha(types.Transactions(txes), hasher) + header := &types.Header{ + TxHash: txsRoot, + } + preimages := hasher.GetPreimages() + mockPreimageResolver := &mockPreimageResolver{ + preimages: preimages, + } + txsFetcher := &txsFetcherForBlock{ + header: header, + preimageResolver: mockPreimageResolver, + } + fetched, err := txsFetcher.TransactionsByHeader(ctx, header.Hash()) + require.NoError(t, err) + require.True(t, uint64(len(fetched)) == total) // #nosec G115 + for i, tx := range fetched { + require.Equal(t, txes[i].Hash(), tx.Hash()) + require.Equal(t, uint64(i), tx.Nonce()) // #nosec G115 + } +} + +func TestFetchTransactionsForBlockHeader_LegacyTxs(t *testing.T) { + ctx := context.Background() + total := uint64(42) + txes := make([]*types.Transaction, total) + for i := uint64(0); i < total; i++ { + txes[i] = types.NewTransaction(i, common.Address{}, big.NewInt(0), 21000, big.NewInt(1), nil) + } + hasher := newRecordingHasher() + txsRoot := types.DeriveSha(types.Transactions(txes), hasher) + header := &types.Header{ + TxHash: txsRoot, + } + preimages := hasher.GetPreimages() + mockPreimageResolver := &mockPreimageResolver{ + preimages: preimages, + } + txsFetcher := &txsFetcherForBlock{ + header: header, + preimageResolver: mockPreimageResolver, + } + fetched, err := txsFetcher.TransactionsByHeader(ctx, header.Hash()) + require.NoError(t, err) + require.True(t, uint64(len(fetched)) == total) // #nosec G115 + for i, tx := range fetched { + require.Equal(t, txes[i].Hash(), tx.Hash()) + require.Equal(t, uint64(i), tx.Nonce()) // #nosec G115 + } +} diff --git a/cmd/nitro/init.go b/cmd/nitro/init.go index a216d66ab5..4584ca1151 100644 --- a/cmd/nitro/init.go +++ b/cmd/nitro/init.go @@ -25,6 +25,7 @@ import ( "github.com/cavaliergopher/grab/v3" "github.com/codeclysm/extract/v3" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" @@ -36,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + protocol "github.com/offchainlabs/bold/chain-abstraction" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/arbostypes" @@ -44,16 +46,21 @@ import ( "github.com/offchainlabs/nitro/cmd/pruning" "github.com/offchainlabs/nitro/cmd/staterecovery" "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" + "github.com/offchainlabs/nitro/staker/bold" "github.com/offchainlabs/nitro/statetransfer" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/dbutil" + "github.com/offchainlabs/nitro/util/headerreader" ) var notFoundError = errors.New("file not found") -// Based on https://github.com/wasmerio/wasmer/blob/6de934035a4b34c2878552320f058862faea4651/lib/types/src/serialize.rs#L16 +// taken from wasmer's lib/types/src/serialize.rs: MetadataHeader::CURRENT_VERSION +// 8 is a bug (should have been 6) but we're skipping the real version 8 so it does not matter const WasmerSerializeVersion = 8 +const InitialWasmerSerializeVersion = 8 func initializeAndDownloadInit(ctx context.Context, initConfig *conf.InitConfig, stack *node.Node) (string, func(), error) { cleanUpTmp := func() {} @@ -290,11 +297,12 @@ func joinArchive(parts []string, archivePath string) (string, error) { if err != nil { return "", fmt.Errorf("failed to open part file %s: %w", part, err) } - defer partFile.Close() _, err = io.Copy(archive, partFile) if err != nil { + partFile.Close() return "", fmt.Errorf("failed to copy part file %s: %w", part, err) } + partFile.Close() log.Info("Joined database part into archive", "part", part) } log.Info("Successfully joined parts into archive", "archive", archivePath) @@ -434,7 +442,7 @@ func isWasmDb(path string) bool { func extractSnapshot(archive string, location string, importWasm bool) error { reader, err := os.Open(archive) if err != nil { - return fmt.Errorf("couln't open init '%v' archive: %w", archive, err) + return fmt.Errorf("couldn't open init '%v' archive: %w", archive, err) } defer reader.Close() stat, err := reader.Stat() @@ -453,7 +461,7 @@ func extractSnapshot(archive string, location string, importWasm bool) error { } err = extract.Archive(context.Background(), reader, location, rename) if err != nil { - return fmt.Errorf("couln't extract init archive '%v' err: %w", archive, err) + return fmt.Errorf("couldn't extract init archive '%v' err: %w", archive, err) } return nil } @@ -502,8 +510,8 @@ func validateOrUpgradeWasmerSerializeVersion(db ethdb.Database) error { if !databaseIsEmpty(db) { versionInDB, err := rawdb.ReadWasmerSerializeVersion(db) if err != nil { - if dbutil.IsErrNotFound(err) { - versionInDB = 0 + if rawdb.IsDbErrNotFound(err) { + versionInDB = InitialWasmerSerializeVersion } else { return fmt.Errorf("Failed to retrieve wasmer serialize version: %w", err) } @@ -530,7 +538,7 @@ func validateOrUpgradeWasmStoreSchemaVersion(db ethdb.Database) error { if !databaseIsEmpty(db) { version, err := rawdb.ReadWasmSchemaVersion(db) if err != nil { - if dbutil.IsErrNotFound(err) { + if rawdb.IsDbErrNotFound(err) { version = []byte{0} } else { return fmt.Errorf("Failed to retrieve wasm schema version: %w", err) @@ -546,7 +554,7 @@ func validateOrUpgradeWasmStoreSchemaVersion(db ethdb.Database) error { if err := deleteWasmEntries(db, prefixes, true, keyLength); err != nil { return fmt.Errorf("Failed to purge wasm store version 0 entries: %w", err) } - log.Info("Wasm store schama version 0 entries successfully removed.") + log.Info("Wasm store schema version 0 entries successfully removed.") } } rawdb.WriteWasmSchemaVersion(db) @@ -574,7 +582,7 @@ func rebuildLocalWasm(ctx context.Context, config *gethexec.Config, l2BlockChain } else { position, err = gethexec.ReadFromKeyValueStore[common.Hash](wasmDb, gethexec.RebuildingPositionKey) if err != nil { - log.Info("Unable to get codehash position in rebuilding of wasm store, its possible it isnt initialized yet, so initializing it and starting rebuilding", "err", err) + log.Info("Unable to get codehash position in rebuilding of wasm store, its possible it isn't initialized yet, so initializing it and starting rebuilding", "err", err) if err := gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingPositionKey, common.Hash{}); err != nil { return nil, nil, fmt.Errorf("unable to initialize codehash position in rebuilding of wasm store to beginning: %w", err) } @@ -583,7 +591,7 @@ func rebuildLocalWasm(ctx context.Context, config *gethexec.Config, l2BlockChain if position != gethexec.RebuildingDone { startBlockHash, err := gethexec.ReadFromKeyValueStore[common.Hash](wasmDb, gethexec.RebuildingStartBlockHashKey) if err != nil { - log.Info("Unable to get start block hash in rebuilding of wasm store, its possible it isnt initialized yet, so initializing it to latest block hash", "err", err) + log.Info("Unable to get start block hash in rebuilding of wasm store, its possible it isn't initialized yet, so initializing it to latest block hash", "err", err) if err := gethexec.WriteToKeyValueStore(wasmDb, gethexec.RebuildingStartBlockHashKey, latestBlock.Hash()); err != nil { return nil, nil, fmt.Errorf("unable to initialize start block hash in rebuilding of wasm store to latest block hash: %w", err) } @@ -626,7 +634,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err := dbutil.UnfinishedConversionCheck(wasmDb); err != nil { return nil, nil, fmt.Errorf("wasm unfinished database conversion check error: %w", err) } - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb, 1, targetConfig.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb) _, err = rawdb.ParseStateScheme(cacheConfig.StateScheme, chainDb) if err != nil { return nil, nil, err @@ -635,7 +643,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err != nil { return chainDb, nil, fmt.Errorf("error pruning: %w", err) } - l2BlockChain, err := gethexec.GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, config.Execution.TxLookupLimit) + l2BlockChain, err := gethexec.GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, &config.Execution.TxIndexer) if err != nil { return chainDb, nil, err } @@ -702,7 +710,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if err := validateOrUpgradeWasmStoreSchemaVersion(wasmDb); err != nil { return nil, nil, err } - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb, 1, targetConfig.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmDb) _, err = rawdb.ParseStateScheme(cacheConfig.StateScheme, chainDb) if err != nil { return nil, nil, err @@ -789,7 +797,7 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if chainConfig == nil { return chainDb, nil, errors.New("no --init.* mode supplied and chain data not in expected directory") } - l2BlockChain, err = gethexec.GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, config.Execution.TxLookupLimit) + l2BlockChain, err = gethexec.GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, &config.Execution.TxIndexer) if err != nil { return chainDb, nil, err } @@ -803,6 +811,14 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo } testUpdateTxIndex(chainDb, chainConfig, &txIndexWg) } else { + var initDataReaderHasAccounts bool + if config.Init.ValidateGenesisAssertion { + accountsReader, err := initDataReader.GetAccountDataReader() + if err != nil { + return chainDb, nil, err + } + initDataReaderHasAccounts = accountsReader.More() + } genesisBlockNr, err := initDataReader.GetNextBlockNumber() if err != nil { return chainDb, nil, err @@ -888,10 +904,18 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo if !emptyBlockChain && (cacheConfig.StateScheme == rawdb.PathScheme) && config.Init.Force { return chainDb, nil, errors.New("It is not possible to force init with non-empty blockchain when using path scheme") } - l2BlockChain, err = gethexec.WriteOrTestBlockChain(chainDb, cacheConfig, initDataReader, chainConfig, genesisArbOSInit, tracer, parsedInitMessage, config.Execution.TxLookupLimit, config.Init.AccountsPerSync) + l2BlockChain, err = gethexec.WriteOrTestBlockChain(chainDb, cacheConfig, initDataReader, chainConfig, genesisArbOSInit, tracer, parsedInitMessage, &config.Execution.TxIndexer, config.Init.AccountsPerSync) if err != nil { return chainDb, nil, err } + if config.Init.ValidateGenesisAssertion { + if err := validateGenesisAssertion(ctx, rollupAddrs.Rollup, l1Client, l2BlockChain.Genesis().Hash(), initDataReaderHasAccounts); err != nil { + if !config.Init.Force { + return chainDb, nil, fmt.Errorf("error testing genesis assertion: %w", err) + } + log.Error("Error testing genesis assertions", "err", err) + } + } } txIndexWg.Wait() @@ -913,6 +937,39 @@ func openInitializeChainDb(ctx context.Context, stack *node.Node, config *NodeCo return rebuildLocalWasm(ctx, &config.Execution, l2BlockChain, chainDb, wasmDb, config.Init.RebuildLocalWasm) } +func validateGenesisAssertion(ctx context.Context, rollupAddress common.Address, l1Client *ethclient.Client, genesisBlockHash common.Hash, initDataReaderHasAccounts bool) error { + userLogic, err := rollupgen.NewRollupUserLogic(rollupAddress, l1Client) + if err != nil { + return err + } + _, err = userLogic.ChallengeGracePeriodBlocks(&bind.CallOpts{Context: ctx}) + if err != nil { + if !headerreader.IsExecutionReverted(err) { + return err + } + log.Warn("Genesis Assertion is not tested") // not a bold chain + return nil + } + genesisAssertionHash, err := userLogic.GenesisAssertionHash(&bind.CallOpts{Context: context.Background()}) + if err != nil { + return err + } + genesisAssertionCreationInfo, err := bold.ReadBoldAssertionCreationInfo(ctx, userLogic, l1Client, rollupAddress, genesisAssertionHash) + if err != nil { + return err + } + beforeGlobalState := protocol.GoGlobalStateFromSolidity(genesisAssertionCreationInfo.BeforeState.GlobalState) + afterGlobalState := protocol.GoGlobalStateFromSolidity(genesisAssertionCreationInfo.AfterState.GlobalState) + isNullAssertion := beforeGlobalState.Batch == afterGlobalState.Batch && beforeGlobalState.PosInBatch == afterGlobalState.PosInBatch + if isNullAssertion && initDataReaderHasAccounts { + return errors.New("genesis assertion is null but there are accounts in the init data") + } + if !isNullAssertion && afterGlobalState.BlockHash != genesisBlockHash { + return errors.New("genesis assertion is non null and its afterGlobalState.BlockHash doesn't match the genesis blockHash") + } + return nil +} + func testTxIndexUpdated(chainDb ethdb.Database, lastBlock uint64) bool { var transactions types.Transactions blockHash := rawdb.ReadCanonicalHash(chainDb, lastBlock) diff --git a/cmd/nitro/init_test.go b/cmd/nitro/init_test.go index cfd163021f..bb624ffbd9 100644 --- a/cmd/nitro/init_test.go +++ b/cmd/nitro/init_test.go @@ -424,10 +424,12 @@ func TestOpenInitializeChainDbIncompatibleStateScheme(t *testing.T) { nodeConfig := NodeConfigDefault nodeConfig.Execution.Caching.StateScheme = rawdb.PathScheme + nodeConfig.Execution.RPC.StateScheme = rawdb.PathScheme nodeConfig.Chain.ID = 42161 nodeConfig.Node = *arbnode.ConfigDefaultL2Test() nodeConfig.Init.DevInit = true nodeConfig.Init.DevInitAddress = "0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E" + nodeConfig.Init.ValidateGenesisAssertion = false l1Client := ethclient.NewClient(stack.Attach()) @@ -437,7 +439,7 @@ func TestOpenInitializeChainDbIncompatibleStateScheme(t *testing.T) { stack, &nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), - gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), + gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), defaultStylusTargetConfigForTest(t), nil, &nodeConfig.Persistent, @@ -455,7 +457,7 @@ func TestOpenInitializeChainDbIncompatibleStateScheme(t *testing.T) { stack, &nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), - gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), + gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), defaultStylusTargetConfigForTest(t), nil, &nodeConfig.Persistent, @@ -469,12 +471,13 @@ func TestOpenInitializeChainDbIncompatibleStateScheme(t *testing.T) { // opening with a different state scheme errors nodeConfig.Execution.Caching.StateScheme = rawdb.HashScheme + nodeConfig.Execution.RPC.StateScheme = rawdb.HashScheme _, _, err = openInitializeChainDb( ctx, stack, &nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), - gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), + gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), defaultStylusTargetConfigForTest(t), nil, &nodeConfig.Persistent, @@ -690,9 +693,11 @@ func TestOpenInitializeChainDbEmptyInit(t *testing.T) { nodeConfig := NodeConfigDefault nodeConfig.Execution.Caching.StateScheme = env.GetTestStateScheme() + nodeConfig.Execution.RPC.StateScheme = env.GetTestStateScheme() nodeConfig.Chain.ID = 42161 nodeConfig.Node = *arbnode.ConfigDefaultL2Test() nodeConfig.Init.Empty = true + nodeConfig.Init.ValidateGenesisAssertion = false l1Client := ethclient.NewClient(stack.Attach()) @@ -701,7 +706,7 @@ func TestOpenInitializeChainDbEmptyInit(t *testing.T) { stack, &nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), - gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), + gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), defaultStylusTargetConfigForTest(t), nil, &nodeConfig.Persistent, diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index 22d6321cf9..ffd7ea918c 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -63,6 +63,7 @@ import ( "github.com/offchainlabs/nitro/solgen/go/rollupgen" legacystaker "github.com/offchainlabs/nitro/staker/legacy" "github.com/offchainlabs/nitro/staker/validatorwallet" + nitroutil "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/colors" "github.com/offchainlabs/nitro/util/dbutil" "github.com/offchainlabs/nitro/util/headerreader" @@ -199,6 +200,7 @@ func mainImpl() int { } log.Info("Running Arbitrum nitro node", "revision", vcsRevision, "vcs.time", vcsTime) + log.Info("Resources detected", "GOMAXPROCS", nitroutil.GoMaxProcs()) if nodeConfig.Node.Dangerous.NoL1Listener { nodeConfig.Node.ParentChainReader.Enable = false @@ -434,7 +436,7 @@ func mainImpl() int { log.Info("enabling custom tracer", "name", traceConfig.TracerName) } - chainDb, l2BlockChain, err := openInitializeChainDb(ctx, stack, nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), gethexec.DefaultCacheConfigFor(stack, &nodeConfig.Execution.Caching), &nodeConfig.Execution.StylusTarget, tracer, &nodeConfig.Persistent, l1Client, rollupAddrs) + chainDb, l2BlockChain, err := openInitializeChainDb(ctx, stack, nodeConfig, new(big.Int).SetUint64(nodeConfig.Chain.ID), gethexec.DefaultCacheConfigFor(&nodeConfig.Execution.Caching), &nodeConfig.Execution.StylusTarget, tracer, &nodeConfig.Persistent, l1Client, rollupAddrs) if l2BlockChain != nil { deferFuncs = append(deferFuncs, func() { l2BlockChain.Stop() }) } @@ -499,12 +501,6 @@ func mainImpl() int { return 1 } - if l2BlockChain.Config().ArbitrumChainParams.DataAvailabilityCommittee != nodeConfig.Node.DataAvailability.Enable { - flag.Usage() - log.Error(fmt.Sprintf("data availability service usage for this chain is set to %v but --node.data-availability.enable is set to %v", l2BlockChain.Config().ArbitrumChainParams.DataAvailabilityCommittee, nodeConfig.Node.DataAvailability.Enable)) - return 1 - } - var valNode *valnode.ValidationNode if sameProcessValidationNodeEnabled { valNode, err = valnode.CreateValidationNode( @@ -582,9 +578,9 @@ func mainImpl() int { return 1 } } - // If batchPoster is enabled, validate MaxSize to be at least 10kB below the sequencer inbox’s maxDataSize if the data availability service is not enabled. + // If batchPoster is enabled, validate MaxSize to be at least 10kB below the sequencer inbox’s maxDataSize if the data availability service and celestia DA are not enabled. // The 10kB gap is because its possible for the batch poster to exceed its MaxSize limit and produce batches of slightly larger size. - if nodeConfig.Node.BatchPoster.Enable && !nodeConfig.Node.DataAvailability.Enable { + if nodeConfig.Node.BatchPoster.Enable && (!nodeConfig.Node.DataAvailability.Enable && !nodeConfig.Node.DAProvider.Enable) { if nodeConfig.Node.BatchPoster.MaxSize > seqInboxMaxDataSize-10000 { log.Error("batchPoster's MaxSize is too large") return 1 @@ -710,7 +706,7 @@ func mainImpl() int { err = execNode.InitializeTimeboost(ctx, chainInfo.ChainConfig) if err != nil { - fatalErrChan <- fmt.Errorf("error intializing timeboost: %w", err) + fatalErrChan <- fmt.Errorf("error initializing timeboost: %w", err) } err = nil @@ -956,9 +952,10 @@ func ParseNode(ctx context.Context, args []string) (*NodeConfig, *genericconf.Wa nodeConfig.Node.MessagePruner.Enable = false } - if nodeConfig.Execution.Caching.Archive && nodeConfig.Execution.TxLookupLimit != 0 { + if nodeConfig.Execution.Caching.Archive && (!nodeConfig.Execution.TxIndexer.Enable || nodeConfig.Execution.TxIndexer.TxLookupLimit != 0) { log.Info("retaining ability to lookup full transaction history as archive mode is enabled") - nodeConfig.Execution.TxLookupLimit = 0 + nodeConfig.Execution.TxIndexer.Enable = true + nodeConfig.Execution.TxIndexer.TxLookupLimit = 0 } err = nodeConfig.Validate() diff --git a/cmd/pruning/pruning.go b/cmd/pruning/pruning.go index db1260397d..c9eae56110 100644 --- a/cmd/pruning/pruning.go +++ b/cmd/pruning/pruning.go @@ -22,7 +22,6 @@ import ( "github.com/ethereum/go-ethereum/rpc" protocol "github.com/offchainlabs/bold/chain-abstraction" - boldrollup "github.com/offchainlabs/bold/solgen/go/rollupgen" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbutil" @@ -30,6 +29,7 @@ import ( "github.com/offchainlabs/nitro/cmd/conf" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/solgen/go/bridgegen" + boldrollup "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker" boldstaker "github.com/offchainlabs/nitro/staker/bold" legacystaker "github.com/offchainlabs/nitro/staker/legacy" diff --git a/cmd/replay/main.go b/cmd/replay/main.go index a89f28489f..437ba004fa 100644 --- a/cmd/replay/main.go +++ b/cmd/replay/main.go @@ -6,6 +6,7 @@ package main import ( "bytes" "context" + "encoding/binary" "encoding/hex" "encoding/json" "fmt" @@ -33,6 +34,8 @@ import ( "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/daprovider" + "github.com/offchainlabs/nitro/daprovider/celestia/tree" + celestiaTypes "github.com/offchainlabs/nitro/daprovider/celestia/types" "github.com/offchainlabs/nitro/daprovider/das/dastree" "github.com/offchainlabs/nitro/daprovider/das/dasutil" "github.com/offchainlabs/nitro/gethhook" @@ -162,6 +165,126 @@ func (r *BlobPreimageReader) Initialize(ctx context.Context) error { return nil } +type PreimageCelestiaReader struct { +} + +func (dasReader *PreimageCelestiaReader) Read(ctx context.Context, blobPointer *celestiaTypes.BlobPointer) ([]byte, *celestiaTypes.SquareData, error) { + oracle := func(hash common.Hash) ([]byte, error) { + return wavmio.ResolveTypedPreimage(arbutil.Sha2_256PreimageType, hash) + } + + if blobPointer.SharesLength == 0 { + return nil, nil, fmt.Errorf("Error, shares length is %v", blobPointer.SharesLength) + } + // first, walk down the merkle tree + leaves, err := tree.MerkleTreeContent(oracle, common.BytesToHash(blobPointer.DataRoot[:])) + if err != nil { + log.Warn("Error revealing contents behind data root", "err", err) + return nil, nil, err + } + + squareSize := uint64(len(leaves)) / 2 + // split leaves in half to get row roots + rowRoots := leaves[:squareSize] + // We get the original data square size, wich is (size_of_the_extended_square / 2) + odsSize := squareSize / 2 + + startRow := blobPointer.Start / odsSize + + if blobPointer.Start >= odsSize*odsSize { + // check that the square isn't just our share (very niche case, should only happens on local testing) + if blobPointer.Start != odsSize*odsSize && odsSize > 1 { + return nil, nil, fmt.Errorf("Error Start Index out of ODS bounds: index=%v odsSize=%v", blobPointer.Start, odsSize) + } + } + + // adjusted_end_index = adjusted_start_index + length - 1 + if blobPointer.Start+blobPointer.SharesLength < 1 { + return nil, nil, fmt.Errorf("Error getting number of shares in first row: index+length %v > 1", blobPointer.Start+blobPointer.SharesLength) + } + endIndexOds := blobPointer.Start + blobPointer.SharesLength - 1 + if endIndexOds >= odsSize*odsSize { + // check that the square isn't just our share (very niche case, should only happens on local testing) + if endIndexOds != odsSize*odsSize && odsSize > 1 { + return nil, nil, fmt.Errorf("Error End Index out of ODS bounds: index=%v odsSize=%v", endIndexOds, odsSize) + } + } + endRow := endIndexOds / odsSize + + if endRow >= odsSize || startRow >= odsSize { + return nil, nil, fmt.Errorf("Error rows out of bounds: startRow=%v endRow=%v odsSize=%v", startRow, endRow, odsSize) + } + + startColumn := blobPointer.Start % odsSize + endColumn := endIndexOds % odsSize + + if startRow == endRow && startColumn > endColumn { + log.Error("start and end row are the same, and startColumn >= endColumn", "startColumn", startColumn, "endColumn ", endColumn) + return []byte{}, nil, nil + } + + // adjust the math in the CelestiaPayload function in the inbox + + // we can take ods * ods -> end index in ods + // then we check that start index is in bounds, otherwise ignore -> return empty batch + // then we check that end index is in bounds, otherwise ignore + + // get rows behind row root and shares for our blob + rows := [][][]byte{} + shares := [][]byte{} + for i := startRow; i <= endRow; i++ { + row, err := tree.NmtContent(oracle, rowRoots[i]) + if err != nil { + return nil, nil, err + } + rows = append(rows, row) + + odsRow := row[:odsSize] + + // TODO explain the logic behind this branching + if startRow == endRow { + shares = append(shares, odsRow[startColumn:endColumn+1]...) + break + } else if i == startRow { + shares = append(shares, odsRow[startColumn:]...) + } else if i == endRow { + shares = append(shares, odsRow[:endColumn+1]...) + } else { + shares = append(shares, odsRow...) + } + } + + data := []byte{} + if tree.NamespaceSize*2+1 > uint64(len(shares[0])) || tree.NamespaceSize*2+5 > uint64(len(shares[0])) { + return nil, nil, fmt.Errorf("Error getting sequence length on share of size %v", len(shares[0])) + } + sequenceLength := binary.BigEndian.Uint32(shares[0][tree.NamespaceSize*2+1 : tree.NamespaceSize*2+5]) + for i, share := range shares { + // trim extra namespace + share := share[tree.NamespaceSize:] + if i == 0 { + data = append(data, share[tree.NamespaceSize+5:]...) + continue + } + data = append(data, share[tree.NamespaceSize+1:]...) + } + + data = data[:sequenceLength] + squareData := celestiaTypes.SquareData{ + RowRoots: rowRoots, + ColumnRoots: leaves[squareSize:], + Rows: rows, + SquareSize: squareSize, + StartRow: startRow, + EndRow: endRow, + } + return data, &squareData, nil +} + +func (dasReader *PreimageCelestiaReader) GetProof(ctx context.Context, msg []byte) ([]byte, error) { + return nil, nil +} + // To generate: // key, _ := crypto.HexToECDSA("0000000000000000000000000000000000000000000000000000000000000001") // sig, _ := crypto.Sign(make([]byte, 32), key) @@ -219,27 +342,19 @@ func main() { } return wavmio.ReadInboxMessage(batchNum), nil } - readMessage := func(dasEnabled bool) *arbostypes.MessageWithMetadata { + readMessage := func() *arbostypes.MessageWithMetadata { var delayedMessagesRead uint64 if lastBlockHeader != nil { delayedMessagesRead = lastBlockHeader.Nonce.Uint64() } - var dasReader dasutil.DASReader - var dasKeysetFetcher dasutil.DASKeysetFetcher - if dasEnabled { - // DAS batch and keysets are all together in the same preimage binary. - dasReader = &PreimageDASReader{} - dasKeysetFetcher = &PreimageDASReader{} - } backend := WavmInbox{} var keysetValidationMode = daprovider.KeysetPanicIfInvalid if backend.GetPositionWithinMessage() > 0 { keysetValidationMode = daprovider.KeysetDontValidate } var dapReaders []daprovider.Reader - if dasReader != nil { - dapReaders = append(dapReaders, dasutil.NewReaderForDAS(dasReader, dasKeysetFetcher)) - } + dapReaders = append(dapReaders, dasutil.NewReaderForDAS(&PreimageDASReader{}, &PreimageDASReader{})) + dapReaders = append(dapReaders, celestiaTypes.NewReaderForCelestia(&PreimageCelestiaReader{})) dapReaders = append(dapReaders, daprovider.NewReaderForBlobReader(&BlobPreimageReader{})) inboxMultiplexer := arbstate.NewInboxMultiplexer(backend, delayedMessagesRead, dapReaders, keysetValidationMode) ctx := context.Background() @@ -297,17 +412,20 @@ func main() { } } - message := readMessage(chainConfig.ArbitrumChainParams.DataAvailabilityCommittee) + // need to add Celestia or just "ExternalDA" as an option to the ArbitrumChainParams + // for now we hard code Cthis to treu and hardcode Celestia in `readMessage` + // to test the integration + message := readMessage() chainContext := WavmChainContext{chainConfig: chainConfig} - newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, false, core.MessageReplayMode) + newBlock, _, err = arbos.ProduceBlock(message.Message, message.DelayedMessagesRead, lastBlockHeader, statedb, chainContext, false, core.NewMessageReplayContext()) if err != nil { panic(err) } } else { // Initialize ArbOS with this init message and create the genesis block. - message := readMessage(false) + message := readMessage() initMessage, err := message.Message.ParseInitMessage() if err != nil { diff --git a/cmd/seq-coordinator-manager/seq-coordinator-manager.go b/cmd/seq-coordinator-manager/seq-coordinator-manager.go index cbf2a245ac..0654db87bb 100644 --- a/cmd/seq-coordinator-manager/seq-coordinator-manager.go +++ b/cmd/seq-coordinator-manager/seq-coordinator-manager.go @@ -281,7 +281,7 @@ func (sm *manager) addSeqPriorityForm(ctx context.Context) *tview.Form { pages.SwitchToPage("Menu") }) addSeqForm.AddButton("Add", func() { - // check if url is valid, i.e it doesnt already exist in the priority list + // check if url is valid, i.e it doesn't already exist in the priority list if _, ok := sm.prioritiesSet[URL]; !ok && URL != "" { sm.prioritiesSet[URL] = true sm.priorityList = append(sm.priorityList, URL) diff --git a/contracts b/contracts index bdb8f8c68b..8b17454ca8 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit bdb8f8c68b2229fe9309fe9c03b37017abd1a2cd +Subproject commit 8b17454ca87e4aa0c66d70844e6fbdcf57f7c43c diff --git a/contracts-local/Makefile b/contracts-local/Makefile index f2c64a91c8..be666ff7a3 100644 --- a/contracts-local/Makefile +++ b/contracts-local/Makefile @@ -1,6 +1,6 @@ .PHONY: build build-forge build-forge-sol build-forge-gas-dimensions build-forge-gas-dimensions-yul -install: +install: forge install --no-git build: build-forge @@ -8,8 +8,9 @@ build: build-forge build-forge: build-forge-sol build-forge-gas-dimensions build-forge-gas-dimensions-yul build-forge-sol: - FOUNDRY_PROFILE=default forge build --skip *.yul --skip src/mocks/HostioTest.sol --skip src/mocks/ArbOS11To32UpgradeTest.sol && \ - FOUNDRY_PROFILE=solc824 forge build src/mocks/HostioTest.sol src/mocks/ArbOS11To32UpgradeTest.sol + FOUNDRY_PROFILE=default forge build --skip *.yul --skip src/mocks/HostioTest.sol --skip src/mocks/ArbOS11To32UpgradeTest.sol --skip src/mocks/ArbNativeTokenManagerTest.sol && \ + FOUNDRY_PROFILE=solc824 forge build src/mocks/HostioTest.sol src/mocks/ArbOS11To32UpgradeTest.sol src/mocks/ArbNativeTokenManagerTest.sol && \ + FOUNDRY_PROFILE=default forge build src/precompiles --out out/precompiles build-forge-gas-dimensions: FOUNDRY_PROFILE=gas-dimensions forge build diff --git a/contracts-local/foundry.toml b/contracts-local/foundry.toml index e3208d20aa..dbb8aee06d 100644 --- a/contracts-local/foundry.toml +++ b/contracts-local/foundry.toml @@ -75,4 +75,4 @@ sort_imports = false [fuzz] runs = 1000 -# See more config options https://github.com/foundry-rs/foundry/tree/master/config +# See more config options https://github.com/foundry-rs/foundry/tree/master/crates/config diff --git a/contracts-local/gas-dimensions/scripts/NestedCall.s.sol b/contracts-local/gas-dimensions/scripts/NestedCall.s.sol new file mode 100644 index 0000000000..610589c1ef --- /dev/null +++ b/contracts-local/gas-dimensions/scripts/NestedCall.s.sol @@ -0,0 +1,24 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see: +// https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.13; + +import {Script, VmSafe, console} from "forge-std/Script.sol"; +import {NestedCall, NestedTarget} from "../src/NestedCall.sol"; + +contract NestedCallTestScript is Script { + function setUp() public {} + + function run() public { + vm.startBroadcast(address(0x3f1Eae7D46d88F08fc2F8ed27FCb2AB183EB2d0E)); + + NestedCall nestedCall = new NestedCall(); + NestedTarget nestedTarget = new NestedTarget(); + + nestedCall.executeNestedCall(address(nestedTarget)); + + vm.stopBroadcast(); + } +} diff --git a/contracts-local/gas-dimensions/src/NestedCall.sol b/contracts-local/gas-dimensions/src/NestedCall.sol new file mode 100644 index 0000000000..43d6c5af90 --- /dev/null +++ b/contracts-local/gas-dimensions/src/NestedCall.sol @@ -0,0 +1,38 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see: +// https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.13; + +contract NestedCall { + uint256 public mainValue; + uint256 public nestedValue; + uint256 public callCount; + + // Entrypoint function + function entrypoint(address target) public { + mainValue = 0x0FFC13A1171AB5; + callCount++; + this.intermediateCall(target); + } + + // Intermediate function that does a DELEGATECALL inside it + function intermediateCall(address target) public { + mainValue = 0x0A4B1CAFE; + (bool success,) = target.delegatecall( + abi.encodeWithSelector(NestedTarget.performNestedAction.selector) + ); + require(success, "Delegatecall failed"); + } +} + +contract NestedTarget { + function performNestedAction() public { + // Need to access the storage slot directly + // since we're in delegate called contract context + assembly { + sstore(0x1, 0xA4B1) // slot 1 for nestedValue + } + } +} diff --git a/contracts-local/src/mocks/ArbNativeTokenManagerTest.sol b/contracts-local/src/mocks/ArbNativeTokenManagerTest.sol new file mode 100644 index 0000000000..727691e304 --- /dev/null +++ b/contracts-local/src/mocks/ArbNativeTokenManagerTest.sol @@ -0,0 +1,13 @@ +// Copyright 2025, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +pragma solidity ^0.8.24; + +import "../precompiles/ArbNativeTokenManager.sol"; + +contract ArbNativeTokenManagerTest { + function mint(uint256 amount) external { + ArbNativeTokenManager(address(0x73)).mintNativeToken(amount); + } +} diff --git a/contracts-local/src/mocks/Simple.sol b/contracts-local/src/mocks/Simple.sol index cf9501442f..e692678e3f 100644 --- a/contracts-local/src/mocks/Simple.sol +++ b/contracts-local/src/mocks/Simple.sol @@ -179,5 +179,3 @@ contract Simple { } } } - - diff --git a/daprovider/celestia/celestiaDasRpcClient.go b/daprovider/celestia/celestiaDasRpcClient.go new file mode 100644 index 0000000000..4b0778c600 --- /dev/null +++ b/daprovider/celestia/celestiaDasRpcClient.go @@ -0,0 +1,93 @@ +package celestia + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + "github.com/spf13/pflag" + + "github.com/ethereum/go-ethereum/rpc" + celestiaTypes "github.com/offchainlabs/nitro/daprovider/celestia/types" + "github.com/offchainlabs/nitro/util/pretty" +) + +type CelestiaConfig struct { + Enable bool `koanf:"enable"` + URL string `koanf:"url"` +} + +type CelestiaDASClient struct { + clnt *rpc.Client + url string +} + +func CelestiaDAConfigAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".enable", false, "Enable Celestia DA") + f.String(prefix+".url", "http://localhost:9876", "address to use against Celestia DA RPC service") +} + +func NewCelestiaDASRPCClient(target string) (*CelestiaDASClient, error) { + clnt, err := rpc.Dial(target) + if err != nil { + log.Error("Could not dial to Celestia DAS", "err", err) + return nil, err + } + return &CelestiaDASClient{ + clnt: clnt, + url: target, + }, nil +} + +func (c *CelestiaDASClient) Store(ctx context.Context, message []byte) ([]byte, error) { + log.Trace("celestia.CelestiaDASClient.Store(...)", "message", pretty.FirstFewBytes(message)) + ret := []byte{} + if err := c.clnt.CallContext(ctx, &ret, "celestia_store", hexutil.Bytes(message)); err != nil { + return nil, err + } + log.Info("Got result from Celestia DAS", "result", ret) + return ret, nil +} + +func (c *CelestiaDASClient) String() string { + return fmt.Sprintf("CelestiaDASClient{url:%s}", c.url) +} + +type ReadResult struct { + Message []byte `json:"message"` + RowRoots [][]byte `json:"row_roots"` + ColumnRoots [][]byte `json:"column_roots"` + Rows [][][]byte `json:"rows"` + SquareSize uint64 `json:"square_size"` // Refers to original data square size + StartRow uint64 `json:"start_row"` + EndRow uint64 `json:"end_row"` +} + +func (c *CelestiaDASClient) Read(ctx context.Context, blobPointer *celestiaTypes.BlobPointer) ([]byte, *celestiaTypes.SquareData, error) { + log.Trace("celestia.CelestiaDASClient.Read(...)", "blobPointer", blobPointer) + var ret ReadResult + if err := c.clnt.CallContext(ctx, &ret, "celestia_read", blobPointer); err != nil { + return nil, nil, err + } + + squareData := celestiaTypes.SquareData{ + RowRoots: ret.RowRoots, + ColumnRoots: ret.ColumnRoots, + Rows: ret.Rows, + SquareSize: ret.SquareSize, + StartRow: ret.StartRow, + EndRow: ret.EndRow, + } + + return ret.Message, &squareData, nil +} + +func (c *CelestiaDASClient) GetProof(ctx context.Context, msg []byte) ([]byte, error) { + res := []byte{} + err := c.clnt.CallContext(ctx, &res, "celestia_getProof", msg) + if err != nil { + return nil, err + } + return res, nil +} diff --git a/daprovider/celestia/tree/hash.go b/daprovider/celestia/tree/hash.go new file mode 100644 index 0000000000..bef9fcd7de --- /dev/null +++ b/daprovider/celestia/tree/hash.go @@ -0,0 +1,37 @@ +package tree + +import ( + "github.com/offchainlabs/nitro/arbutil" + "github.com/tendermint/tendermint/crypto/tmhash" + + "github.com/ethereum/go-ethereum/common" +) + +// TODO: make these have a large predefined capacity +var ( + leafPrefix = []byte{0} + innerPrefix = []byte{1} +) + +// returns tmhash() +func emptyHash() []byte { + return tmhash.Sum([]byte{}) +} + +// returns tmhash(0x00 || leaf) +func leafHash(record func(bytes32, []byte, arbutil.PreimageType), leaf []byte) []byte { + preimage := append(leafPrefix, leaf...) + hash := tmhash.Sum(preimage) + + record(common.BytesToHash(hash), preimage, arbutil.Sha2_256PreimageType) + return hash +} + +// returns tmhash(0x01 || left || right) +func innerHash(record func(bytes32, []byte, arbutil.PreimageType), left []byte, right []byte) []byte { + preimage := append(innerPrefix, append(left, right...)...) + hash := tmhash.Sum(preimage) + + record(common.BytesToHash(hash), preimage, arbutil.Sha2_256PreimageType) + return tmhash.Sum(append(innerPrefix, append(left, right...)...)) +} diff --git a/daprovider/celestia/tree/merkle_tree.go b/daprovider/celestia/tree/merkle_tree.go new file mode 100644 index 0000000000..3fb8c19faf --- /dev/null +++ b/daprovider/celestia/tree/merkle_tree.go @@ -0,0 +1,78 @@ +package tree + +import ( + "math/bits" + + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbutil" +) + +type bytes32 = common.Hash + +// HashFromByteSlices computes a Merkle tree where the leaves are the byte slice, +// in the provided order. It follows RFC-6962. +func HashFromByteSlices(record func(bytes32, []byte, arbutil.PreimageType), items [][]byte) []byte { + switch len(items) { + case 0: + emptyHash := emptyHash() + record(common.BytesToHash(emptyHash), []byte{}, arbutil.Sha2_256PreimageType) + return emptyHash + case 1: + return leafHash(record, items[0]) + default: + k := getSplitPoint(int64(len(items))) + left := HashFromByteSlices(record, items[:k]) + right := HashFromByteSlices(record, items[k:]) + return innerHash(record, left, right) + } +} + +// getSplitPoint returns the largest power of 2 less than length +func getSplitPoint(length int64) int64 { + if length < 1 { + panic("Trying to split a tree with size < 1") + } + uLength := uint(length) + bitlen := bits.Len(uLength) + k := int64(1 << uint(bitlen-1)) + if k == length { + k >>= 1 + } + return k +} + +// getChildrenHashes splits the preimage into the hashes of the left and right children. +func getChildrenHashes(preimage []byte) (leftChild, rightChild common.Hash, err error) { + leftChild = common.BytesToHash(preimage[:32]) + rightChild = common.BytesToHash(preimage[32:]) + return leftChild, rightChild, nil +} + +// MerkleTreeContent recursively walks down the Merkle tree and collects leaf node data. +func MerkleTreeContent(oracle func(bytes32) ([]byte, error), rootHash common.Hash) ([][]byte, error) { + stack := []common.Hash{rootHash} + var data [][]byte + + for len(stack) > 0 { + currentHash := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + preimage, err := oracle(currentHash) + if err != nil { + return nil, err + } + + if preimage[0] == leafPrefix[0] { + data = append(data, preimage[1:]) + } else { + leftChildHash, rightChildHash, err := getChildrenHashes(preimage[1:]) + if err != nil { + return nil, err + } + stack = append(stack, rightChildHash) + stack = append(stack, leftChildHash) + } + } + + return data, nil +} diff --git a/daprovider/celestia/tree/nmt.go b/daprovider/celestia/tree/nmt.go new file mode 100644 index 0000000000..f0d2a7b953 --- /dev/null +++ b/daprovider/celestia/tree/nmt.go @@ -0,0 +1,74 @@ +package tree + +import ( + "errors" + + "github.com/celestiaorg/rsmt2d" + "github.com/ethereum/go-ethereum/common" +) + +// need to pass square size and axis index +func ComputeNmtRoot(createTreeFn rsmt2d.TreeConstructorFn, index uint, shares [][]byte) ([]byte, error) { + // create NMT with custom Hasher + // use create tree function, pass it to the ComputeNmtRoot function + tree := createTreeFn(rsmt2d.Row, index) + if !isComplete(shares) { + return nil, errors.New("can not compute root of incomplete row") + } + for _, d := range shares { + err := tree.Push(d) + if err != nil { + return nil, err + } + } + + return tree.Root() +} + +// isComplete returns true if all the shares are non-nil. +func isComplete(shares [][]byte) bool { + for _, share := range shares { + if share == nil { + return false + } + } + return true +} + +// getNmtChildrenHashes splits the preimage into the hashes of the left and right children of the NMT +// note that a leaf has the format minNID || maxNID || hash, here hash is the hash of the left and right +// (NodePrefix) || (leftMinNID || leftMaxNID || leftHash) || (rightMinNID || rightMaxNID || rightHash) +func getNmtChildrenHashes(hash []byte) (leftChild, rightChild []byte) { + hash = hash[1:] + flagLen := int(NamespaceSize * 2) + sha256Len := 32 + leftChild = hash[:flagLen+sha256Len] + rightChild = hash[flagLen+sha256Len:] + return leftChild, rightChild +} + +// walkMerkleTree recursively walks down the Merkle tree and collects leaf node data. +func NmtContent(oracle func(bytes32) ([]byte, error), rootHash []byte) ([][]byte, error) { + stack := [][]byte{rootHash} + var data [][]byte + + for len(stack) > 0 { + currentHash := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + preimage, err := oracle(common.BytesToHash(currentHash[NamespaceSize*2:])) + if err != nil { + return nil, err + } + + if preimage[0] == leafPrefix[0] { + data = append(data, preimage[1:]) + } else { + leftChildHash, rightChildHash := getNmtChildrenHashes(preimage) + stack = append(stack, rightChildHash) + stack = append(stack, leftChildHash) + } + } + + return data, nil +} diff --git a/daprovider/celestia/tree/nmt_hasher.go b/daprovider/celestia/tree/nmt_hasher.go new file mode 100644 index 0000000000..c7c5a23cd8 --- /dev/null +++ b/daprovider/celestia/tree/nmt_hasher.go @@ -0,0 +1,43 @@ +package tree + +import ( + "crypto/sha256" + "hash" + + "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbutil" +) + +// customHasher embeds hash.Hash and includes a map for the hash-to-preimage mapping +type NmtPreimageHasher struct { + hash.Hash + record func(bytes32, []byte, arbutil.PreimageType) + data []byte +} + +// Need to make sure this is writting relevant data into the tree +// Override the Sum method to capture the preimage +func (h *NmtPreimageHasher) Sum(b []byte) []byte { + hashed := h.Hash.Sum(nil) + hashKey := common.BytesToHash(hashed) + h.record(hashKey, append([]byte(nil), h.data...), arbutil.Sha2_256PreimageType) + return h.Hash.Sum(b) +} + +func (h *NmtPreimageHasher) Write(p []byte) (n int, err error) { + h.data = append(h.data, p...) + return h.Hash.Write(p) +} + +// Override the Reset method to clean the hash state and the data slice +func (h *NmtPreimageHasher) Reset() { + h.Hash.Reset() + h.data = h.data[:0] // Reset the data slice to be empty, but keep the underlying array +} + +func newNmtPreimageHasher(record func(bytes32, []byte, arbutil.PreimageType)) hash.Hash { + return &NmtPreimageHasher{ + Hash: sha256.New(), + record: record, + } +} diff --git a/daprovider/celestia/tree/nmt_wrapper.go b/daprovider/celestia/tree/nmt_wrapper.go new file mode 100644 index 0000000000..2ab8abd6a7 --- /dev/null +++ b/daprovider/celestia/tree/nmt_wrapper.go @@ -0,0 +1,175 @@ +package tree + +import ( + "bytes" + "fmt" + "math" + + "github.com/celestiaorg/nmt" + "github.com/celestiaorg/nmt/namespace" + "github.com/celestiaorg/rsmt2d" + "github.com/offchainlabs/nitro/arbutil" +) + +// NMT Wrapper from celestia-app with support for populating a mapping of preimages + +const ( + NamespaceSize uint64 = 29 + NamespaceIDSize = 28 + NamespaceVersionMax = math.MaxUint8 +) + +// Fulfills the rsmt2d.Tree interface and rsmt2d.TreeConstructorFn function +var ( + _ rsmt2d.Tree = &ErasuredNamespacedMerkleTree{} + ParitySharesNamespace = secondaryReservedNamespace(0xFF) +) + +func secondaryReservedNamespace(lastByte byte) Namespace { + return Namespace{ + Version: NamespaceVersionMax, + ID: append(bytes.Repeat([]byte{0xFF}, NamespaceIDSize-1), lastByte), + } +} + +type Namespace struct { + Version uint8 + ID []byte +} + +// Bytes returns this namespace as a byte slice. +func (n Namespace) Bytes() []byte { + return append([]byte{n.Version}, n.ID...) +} + +// ErasuredNamespacedMerkleTree wraps NamespaceMerkleTree to conform to the +// rsmt2d.Tree interface while also providing the correct namespaces to the +// underlying NamespaceMerkleTree. It does this by adding the already included +// namespace to the first half of the tree, and then uses the parity namespace +// ID for each share pushed to the second half of the tree. This allows for the +// namespaces to be included in the erasure data, while also keeping the nmt +// library sufficiently general +type ErasuredNamespacedMerkleTree struct { + squareSize uint64 // note: this refers to the width of the original square before erasure-coded + options []nmt.Option + tree Tree + // axisIndex is the index of the axis (row or column) that this tree is on. This is passed + // by rsmt2d and used to help determine which quadrant each leaf belongs to. + axisIndex uint64 + // shareIndex is the index of the share in a row or column that is being + // pushed to the tree. It is expected to be in the range: 0 <= shareIndex < + // 2*squareSize. shareIndex is used to help determine which quadrant each + // leaf belongs to, along with keeping track of how many leaves have been + // added to the tree so far. + shareIndex uint64 +} + +// Tree is an interface that wraps the methods of the underlying +// NamespaceMerkleTree that are used by ErasuredNamespacedMerkleTree. This +// interface is mainly used for testing. It is not recommended to use this +// interface by implementing a different implementation. +type Tree interface { + Root() ([]byte, error) + Push(namespacedData namespace.PrefixedData) error + ProveRange(start, end int) (nmt.Proof, error) +} + +// NewErasuredNamespacedMerkleTree creates a new ErasuredNamespacedMerkleTree +// with an underlying NMT of namespace size `29` and with +// `ignoreMaxNamespace=true`. axisIndex is the index of the row or column that +// this tree is committing to. squareSize must be greater than zero. +func NewErasuredNamespacedMerkleTree(record func(bytes32, []byte, arbutil.PreimageType), squareSize uint64, axisIndex uint, options ...nmt.Option) ErasuredNamespacedMerkleTree { + if squareSize == 0 { + panic("cannot create a ErasuredNamespacedMerkleTree of squareSize == 0") + } + options = append(options, nmt.NamespaceIDSize(29)) + options = append(options, nmt.IgnoreMaxNamespace(true)) + tree := nmt.New(newNmtPreimageHasher(record), options...) + return ErasuredNamespacedMerkleTree{squareSize: squareSize, options: options, tree: tree, axisIndex: uint64(axisIndex), shareIndex: 0} +} + +type constructor struct { + record func(bytes32, []byte, arbutil.PreimageType) + squareSize uint64 + opts []nmt.Option +} + +// NewConstructor creates a tree constructor function as required by rsmt2d to +// calculate the data root. It creates that tree using the +// wrapper.ErasuredNamespacedMerkleTree. +func NewConstructor(record func(bytes32, []byte, arbutil.PreimageType), squareSize uint64, opts ...nmt.Option) rsmt2d.TreeConstructorFn { + return constructor{ + record: record, + squareSize: squareSize, + opts: opts, + }.NewTree +} + +// NewTree creates a new rsmt2d.Tree using the +// wrapper.ErasuredNamespacedMerkleTree with predefined square size and +// nmt.Options +func (c constructor) NewTree(_ rsmt2d.Axis, axisIndex uint) rsmt2d.Tree { + newTree := NewErasuredNamespacedMerkleTree(c.record, c.squareSize, axisIndex, c.opts...) + return &newTree +} + +// Push adds the provided data to the underlying NamespaceMerkleTree, and +// automatically uses the first DefaultNamespaceIDLen number of bytes as the +// namespace unless the data pushed to the second half of the tree. Fulfills the +// rsmt.Tree interface. NOTE: panics if an error is encountered while pushing or +// if the tree size is exceeded. +func (w *ErasuredNamespacedMerkleTree) Push(data []byte) error { + if w.axisIndex+1 > 2*w.squareSize || w.shareIndex+1 > 2*w.squareSize { + return fmt.Errorf("pushed past predetermined square size: boundary at %d index at %d %d", 2*w.squareSize, w.axisIndex, w.shareIndex) + } + // + if len(data) < int(NamespaceSize) { + return fmt.Errorf("data is too short to contain namespace ID") + } + nidAndData := make([]byte, int(NamespaceSize)+len(data)) + copy(nidAndData[NamespaceSize:], data) + // use the parity namespace if the cell is not in Q0 of the extended data square + if w.isQuadrantZero() { + copy(nidAndData[:NamespaceSize], data[:NamespaceSize]) + } else { + copy(nidAndData[:NamespaceSize], ParitySharesNamespace.Bytes()) + } + err := w.tree.Push(nidAndData) + if err != nil { + return err + } + w.incrementShareIndex() + return nil +} + +// Root fulfills the rsmt.Tree interface by generating and returning the +// underlying NamespaceMerkleTree Root. +func (w *ErasuredNamespacedMerkleTree) Root() ([]byte, error) { + root, err := w.tree.Root() + if err != nil { + return nil, err + } + return root, nil +} + +// ProveRange returns a Merkle range proof for the leaf range [start, end] where `end` is non-inclusive. +func (w *ErasuredNamespacedMerkleTree) ProveRange(start, end int) (nmt.Proof, error) { + return w.tree.ProveRange(start, end) +} + +// incrementShareIndex increments the share index by one. +func (w *ErasuredNamespacedMerkleTree) incrementShareIndex() { + w.shareIndex++ +} + +// isQuadrantZero returns true if the current share index and axis index are both +// in the original data square. +func (w *ErasuredNamespacedMerkleTree) isQuadrantZero() bool { + return w.shareIndex < w.squareSize && w.axisIndex < w.squareSize +} + +// SetTree sets the underlying tree to the provided tree. This is used for +// testing purposes only. +func (w *ErasuredNamespacedMerkleTree) SetTree(tree Tree) { + w.tree = tree +} diff --git a/daprovider/celestia/types/blob.go b/daprovider/celestia/types/blob.go new file mode 100644 index 0000000000..f711cc0117 --- /dev/null +++ b/daprovider/celestia/types/blob.go @@ -0,0 +1,77 @@ +package types + +import ( + "bytes" + "encoding/binary" +) + + +// BlobPointer contains the reference to the data blob on Celestia +type BlobPointer struct { + BlockHeight uint64 `json:"block_height"` + Start uint64 `json:"start"` + SharesLength uint64 `json:"shares_length"` + TxCommitment [32]byte `json:"tx_commitment"` + DataRoot [32]byte `json:"data_root"` +} + +// MarshalBinary encodes the BlobPointer to binary +// serialization format: height + start + end + commitment + data root +func (b *BlobPointer) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + + // Writing fixed-size values + if err := binary.Write(buf, binary.BigEndian, b.BlockHeight); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, b.Start); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, b.SharesLength); err != nil { + return nil, err + } + + // Writing fixed-size byte arrays directly + if _, err := buf.Write(b.TxCommitment[:]); err != nil { + return nil, err + } + if _, err := buf.Write(b.DataRoot[:]); err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +// UnmarshalBinary decodes the binary to BlobPointer +// serialization format: height + start + end + commitment + data root +func (b *BlobPointer) UnmarshalBinary(data []byte) error { + buf := bytes.NewReader(data) + // Reading fixed-size values + if err := binary.Read(buf, binary.BigEndian, &b.BlockHeight); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &b.Start); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &b.SharesLength); err != nil { + return err + } + + // Reading fixed-size byte arrays directly + if err := readFixedBytes(buf, b.TxCommitment[:]); err != nil { + return err + } + if err := readFixedBytes(buf, b.DataRoot[:]); err != nil { + return err + } + + return nil +} + +// readFixedBytes reads a fixed number of bytes into a byte slice +func readFixedBytes(buf *bytes.Reader, data []byte) error { + if _, err := buf.Read(data); err != nil { + return err + } + return nil +} diff --git a/daprovider/celestia/types/da_interface.go b/daprovider/celestia/types/da_interface.go new file mode 100644 index 0000000000..e6483d740a --- /dev/null +++ b/daprovider/celestia/types/da_interface.go @@ -0,0 +1,23 @@ +package types + +import ( + "context" +) + +type CelestiaWriter interface { + Store(context.Context, []byte) ([]byte, error) +} + +type SquareData struct { + RowRoots [][]byte `json:"row_roots"` + ColumnRoots [][]byte `json:"column_roots"` + Rows [][][]byte `json:"rows"` + SquareSize uint64 `json:"square_size"` // Refers to original data square size + StartRow uint64 `json:"start_row"` + EndRow uint64 `json:"end_row"` +} + +type CelestiaReader interface { + Read(context.Context, *BlobPointer) ([]byte, *SquareData, error) + GetProof(ctx context.Context, msg []byte) ([]byte, error) +} diff --git a/daprovider/celestia/types/reader.go b/daprovider/celestia/types/reader.go new file mode 100644 index 0000000000..d18b430481 --- /dev/null +++ b/daprovider/celestia/types/reader.go @@ -0,0 +1,136 @@ +package types + +import ( + "bytes" + "context" + "errors" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/daprovider" + "github.com/offchainlabs/nitro/daprovider/celestia/tree" +) + +func NewReaderForCelestia(celestiaReader CelestiaReader) *readerForCelestia { + return &readerForCelestia{celestiaReader: celestiaReader} +} + +type readerForCelestia struct { + celestiaReader CelestiaReader +} + +func (c *readerForCelestia) IsValidHeaderByte(ctx context.Context, headerByte byte) bool { + return IsCelestiaMessageHeaderByte(headerByte) +} + +// CelestiaMessageHeaderFlag indicates that this data is a Blob Pointer +// which will be used to retrieve data from Celestia +const CelestiaMessageHeaderFlag byte = 0x63 + +func hasBits(checking byte, bits byte) bool { + return (checking & bits) == bits +} + +func IsCelestiaMessageHeaderByte(header byte) bool { + return hasBits(header, CelestiaMessageHeaderFlag) +} + +func (c *readerForCelestia) GetProof(ctx context.Context, msg []byte) ([]byte, error) { + return c.celestiaReader.GetProof(ctx, msg) +} + +func (c *readerForCelestia) RecoverPayloadFromBatch( + ctx context.Context, + batchNum uint64, + batchBlockHash common.Hash, + sequencerMsg []byte, + preimages daprovider.PreimagesMap, + validateSeqMsg bool, +) ([]byte, daprovider.PreimagesMap, error) { + return RecoverPayloadFromCelestiaBatch(ctx, batchNum, sequencerMsg, c.celestiaReader, preimages, validateSeqMsg) +} + +func RecoverPayloadFromCelestiaBatch( + ctx context.Context, + batchNum uint64, + sequencerMsg []byte, + celestiaReader CelestiaReader, + preimages daprovider.PreimagesMap, + validateSeqMsg bool, +) ([]byte, daprovider.PreimagesMap, error) { + var preimageRecorder daprovider.PreimageRecorder + if preimages != nil { + preimageRecorder = daprovider.RecordPreimagesTo(preimages) + } + buf := bytes.NewBuffer(sequencerMsg[40:]) + + header, err := buf.ReadByte() + if err != nil { + log.Error("Couldn't deserialize Celestia header byte", "err", err) + return nil, nil, nil + } + if !IsCelestiaMessageHeaderByte(header) { + log.Error("Couldn't deserialize Celestia header byte", "err", errors.New("tried to deserialize a message that doesn't have the Celestia header")) + return nil, nil, nil + } + + blobPointer := BlobPointer{} + blobBytes := buf.Bytes() + err = blobPointer.UnmarshalBinary(blobBytes) + if err != nil { + log.Error("Couldn't unmarshal Celestia blob pointer", "err", err) + return nil, nil, nil + } + + payload, squareData, err := celestiaReader.Read(ctx, &blobPointer) + if err != nil { + log.Error("Failed to resolve blob pointer from celestia", "err", err) + return nil, nil, err + } + + // we read a batch that is to be discarded, so we return the empty batch + if len(payload) == 0 { + return payload, nil, nil + } + + if preimageRecorder != nil { + if squareData == nil { + log.Error("squareData is nil, read from replay binary, but preimages are empty") + return nil, nil, err + } + + odsSize := squareData.SquareSize / 2 + rowIndex := squareData.StartRow + for _, row := range squareData.Rows { + treeConstructor := tree.NewConstructor(preimageRecorder, odsSize) + root, err := tree.ComputeNmtRoot(treeConstructor, uint(rowIndex), row) + if err != nil { + log.Error("Failed to compute row root", "err", err) + return nil, nil, err + } + + rowRootMatches := bytes.Equal(squareData.RowRoots[rowIndex], root) + if !rowRootMatches { + log.Error("Row roots do not match", "eds row root", squareData.RowRoots[rowIndex], "calculated", root) + log.Error("Row roots", "row_roots", squareData.RowRoots) + return nil, nil, err + } + rowIndex += 1 + } + + rowsCount := len(squareData.RowRoots) + slices := make([][]byte, rowsCount+rowsCount) + copy(slices[0:rowsCount], squareData.RowRoots) + copy(slices[rowsCount:], squareData.ColumnRoots) + + dataRoot := tree.HashFromByteSlices(preimageRecorder, slices) + + dataRootMatches := bytes.Equal(dataRoot, blobPointer.DataRoot[:]) + if !dataRootMatches { + log.Error("Data Root do not match", "blobPointer data root", blobPointer.DataRoot, "calculated", dataRoot) + return nil, nil, nil + } + } + + return payload, preimages, nil +} diff --git a/daprovider/celestia/types/writer.go b/daprovider/celestia/types/writer.go new file mode 100644 index 0000000000..bdf547fc83 --- /dev/null +++ b/daprovider/celestia/types/writer.go @@ -0,0 +1,30 @@ +package types + +import ( + "context" + "errors" +) + +func NewWriterForCelestia(celestiaWriter CelestiaWriter) *writerForCelestia { + return &writerForCelestia{celestiaWriter: celestiaWriter} +} + +type writerForCelestia struct { + celestiaWriter CelestiaWriter +} + +func (c *writerForCelestia) Store(ctx context.Context, message []byte, timeout uint64, disableFallbackStoreDataOnChain bool) ([]byte, error) { + msg, err := c.celestiaWriter.Store(ctx, message) + if err != nil { + if disableFallbackStoreDataOnChain { + return nil, errors.New("unable to batch to Celestia and fallback storing data on chain is disabled") + } + return nil, err + } + message = msg + return message, nil +} + +func (d *writerForCelestia) Type() string { + return "celestia" +} diff --git a/daprovider/das/dasserver/dasserver.go b/daprovider/das/dasserver/dasserver.go index c161db96c2..cc24927c9d 100644 --- a/daprovider/das/dasserver/dasserver.go +++ b/daprovider/das/dasserver/dasserver.go @@ -59,7 +59,7 @@ func ServerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Uint64(prefix+".port", DefaultServerConfig.Port, "JSON rpc server listening port") f.String(prefix+".jwtsecret", DefaultServerConfig.JWTSecret, "path to file with jwtsecret for validation") f.Bool(prefix+".enable-da-writer", DefaultServerConfig.EnableDAWriter, "implies if the das server supports daprovider's writer interface") - f.Int("rpc-server-body-limit", DefaultServerConfig.RPCServerBodyLimit, "HTTP-RPC server maximum request body size in bytes; the default (0) uses geth's 5MB limit") + f.Int(prefix+".rpc-server-body-limit", DefaultServerConfig.RPCServerBodyLimit, "HTTP-RPC server maximum request body size in bytes; the default (0) uses geth's 5MB limit") das.DataAvailabilityConfigAddNodeOptions(prefix+".data-availability", f) genericconf.HTTPServerTimeoutConfigAddOptions(prefix+".server-timeouts", f) } diff --git a/daprovider/util.go b/daprovider/util.go index 7244498cfc..e0d9de3506 100644 --- a/daprovider/util.go +++ b/daprovider/util.go @@ -1,5 +1,5 @@ // Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md package daprovider @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto/kzg4844" - "github.com/offchainlabs/nitro/arbutil" ) @@ -72,11 +71,15 @@ const ZeroheavyMessageHeaderFlag byte = 0x20 // BlobHashesHeaderFlag indicates that this message contains EIP 4844 versioned hashes of the commitments calculated over the blob data for the batch data. const BlobHashesHeaderFlag byte = L1AuthenticatedMessageHeaderFlag | 0x10 // 0x50 +// CelestiaMessageHeaderFlag indicates that this data is a Blob Pointer +// which will be used to retrieve data from Celestia +const CelestiaMessageHeaderFlag byte = 0x63 + // BrotliMessageHeaderByte indicates that the message is brotli-compressed. const BrotliMessageHeaderByte byte = 0 // KnownHeaderBits is all header bits with known meaning to this nitro version -const KnownHeaderBits byte = DASMessageHeaderFlag | TreeDASMessageHeaderFlag | L1AuthenticatedMessageHeaderFlag | ZeroheavyMessageHeaderFlag | BlobHashesHeaderFlag | BrotliMessageHeaderByte +const KnownHeaderBits byte = DASMessageHeaderFlag | TreeDASMessageHeaderFlag | CelestiaMessageHeaderFlag | L1AuthenticatedMessageHeaderFlag | ZeroheavyMessageHeaderFlag | BlobHashesHeaderFlag | BrotliMessageHeaderByte var DefaultDASRetentionPeriod time.Duration = time.Hour * 24 * 15 @@ -105,6 +108,10 @@ func IsBlobHashesHeaderByte(header byte) bool { return hasBits(header, BlobHashesHeaderFlag) } +func IsCelestiaMessageHeaderByte(header byte) bool { + return hasBits(header, CelestiaMessageHeaderFlag) +} + func IsBrotliMessageHeaderByte(b uint8) bool { return b == BrotliMessageHeaderByte } diff --git a/daprovider/writer.go b/daprovider/writer.go index cc32670045..f49351e9b5 100644 --- a/daprovider/writer.go +++ b/daprovider/writer.go @@ -1,5 +1,5 @@ // Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md package daprovider diff --git a/docs/celestia/docs.md b/docs/celestia/docs.md new file mode 100644 index 0000000000..eec2127375 --- /dev/null +++ b/docs/celestia/docs.md @@ -0,0 +1,60 @@ +# Orbit with Celestia Underneath ✨ +![image](https://github.com/celestiaorg/nitro/assets/31937514/dfe451b5-21ee-446b-8140-869ea4e2a7eb) + + +## Overview + +The integration of Celestia with Arbitrum Orbit and the Nitro tech stack marks the first external contribution to the Arbitrum Orbit protocol layer, offering developers an additional option for selecting a data availability layer alongside Arbitrum AnyTrust. The integration allows developers to deploy an Orbit Chain that uses Celestia for data availability and settles on Arbitrum One, Ethereum, or other EVM chains. + +## Key Components + +The integration of Celestia with Arbitrum orbit is possible thanks to 3 components: +- DA Provider Implementation +- Preimage Oracle +- Blobstream + +# DA Provider Implementation + +The Arbitrum Nitro code has a `DataAvailabilityProvider` interface that is used across the codebase to store and retrieve data from a specific provider (eip4844 blobs, Anytrust, and now Celestia). + +This integration implements the [`DataAvailabilityProvider` interface for Celestia DA](https://github.com/celestiaorg/nitro/blob/966e631f1a03b49d49f25bea67a92b275d3bacb9/arbstate/inbox.go#L366-L477) + +Additionally, this integrations comes with the necessary code for a Nitro chain node to post and retrieve data from Celestia, which can be found [here.](https://github.com/celestiaorg/nitro/tree/celestia-v2.3.1/das/celestia) + +The core logic behind posting and retrieving data happens in [celestia.go](https://github.com/celestiaorg/nitro/blob/celestia-v2.3.1/das/celestia/celestia.go) where data is stored on Celestia and serialized into a small batch of data that gets published once the necessary range of headers (data roots) has been relayed to the [BlobstreamX contract](https://github.com/succinctlabs/blobstreamx). +Then the `Read` logic takes care of taking the deserialized Blob Pointer struct and consuming it in order to fetch the data from Celestia and additionally inform the fetcher about the position of the data on Celestia (we'll get back to this in the next section). + +The following represents a non-exhaustive list of considerations when running a Batch Poster node for a chain with Celestia underneath: +- You will need to use a consensus full node RPC endpoint, you can find a list of them for Mocha [here](https://docs.celestia.org/nodes/mocha-testnet#rpc-endpoints) +- The Batch Poster will only post a Celestia batch to the underlying chain if the height for which it posted is in a recent range in BlobstreamX and if the verification succeeds, otherwise it will discard the batch. Since it will wait until a range is relayed, it can take several minutes for a batch to be posted, but one can always make an on-chain request for the BlobstreamX contract to relay a header promptly. +- + +The following represents a non-exhaustive list of considerations when running a Nitro node for a chain with Celestia underneath: +- The `TendermintRpc` endpoint is only needed by the batch poster, every other node can operate without a connection to a full node. +- The message header flag for Celestia batches is `0x0c`. +- You will need to know the namespace for the chain that you are trying to connect to, but don't worry if you don't find it, as the information in the BlobPointer can be used to identify where a batch of data is in the Celestia Data Square for a given height, and thus can be used to find out the namespace as well! + +# Preimage Oracle Implementation + +In order to support fraud proofs, this integration has the necessary code for a Nitro validator to pupolate its preimage mapping with Celestia hashes that then get "unpealed" in order to reveal the full data for a Blob. You can read more about the "Hash Oracle Trick" [here.](https://docs.arbitrum.io/inside-arbitrum-nitro/#readpreimage-and-the-hash-oracle-trick) + +The data structures and hashing functions for this can be found in the [`nitro/das/celestia/tree` folder](https://github.com/celestiaorg/nitro/tree/celestia-v2.3.1/das/celestia/tree) + +You can see where the preimage oracle gets used in the fraud proof replay binary [here](https://github.com/celestiaorg/nitro/blob/966e631f1a03b49d49f25bea67a92b275d3bacb9/cmd/replay/main.go#L153-L294) + +Something important to note is that the preimage oracle only keeps track of hashes for the rows in the Celestia data square in which a blob resides in, this way each Orbit chain with Celestia underneath does not need validators to recompute an entire Celestia Data Square, but instead, only have to compute the row roots for the rows in which it's data lives in, and the header data root, which is the binary merkle tree hash built using the row roots and column roots fetched from a Celestia node. Because only data roots that can be confirmed on Blobstream get accepted into the sequencer inbox, one can have a high degree of certainty that the canonical data root being unpealed as well as the row roots are in fact correct. + +# DA Proof and BlobstreamX + +Finally, the integration only accepts batches of 89 bytes in length for a celestia header flag. This means that a Celestia Batch has 88 bytes of information, which are the block height, the start index of the blob, the length in shares of the blob, the transaction commitment, and the data root for the given height. + +In the case of a challenge, for a celestia batch, the OSP will require an additionally appended "da proof", which is verified against BlobstreamX. Here's what happens based on the result of the BlobstreamX verification: + +- **IN_BLOBSTREAM**: means the batch was verified against blobstrea, the height and data root in the batch match, and the start + legth do not go out of bounds. This will cause the rest of the OSP to proceed as normal. +- **COUNTERFACTUAL_COMMITMENT**: the height can be verified against blobstream, but the posted data root does not match, or the start + length go out of bounds. Or the Batch Poster tried posting a height too far into the ftureu (1000 blocks ahead of BlobstreamX). This will cause the OSP to proceed with an empty batch. Note that Nitro nodes for a chain with Celestia DA will also discard any batches that cannot be correctly validated. +- **UNDECIDED**: the height has not been relayed yet, so we revert and wait until the latest height in blobstream is the greater than the batch's height. + +You can see how BlobstreamX is integrated into the `OneStepProverHostIO.sol` contract [here]([https://github.com/celestiaorg/nitro-contracts/blob/celestia-v1.2.1/src/bridge/SequencerInbox.sol#L584-L630](https://github.com/celestiaorg/nitro-contracts/blob/contracts-v1.2.1/src/osp/OneStepProverHostIo.sol#L301)), which allows us to discard batches with otherwise faulty data roots, thus giving us a high degree of confidence that the data root can be safely unpacked in case of a challenge. + + + diff --git a/docs/decisions/0002-multi-dimensional-gas-metering.md b/docs/decisions/0002-multi-dimensional-gas-metering.md new file mode 100644 index 0000000000..88ba88633b --- /dev/null +++ b/docs/decisions/0002-multi-dimensional-gas-metering.md @@ -0,0 +1,36 @@ +# Multi-Dimensional (MultiGas) Gas Metering with Resource Kinds + +## Context and Problem Statement + +As part of the implementation of a constraint-based gas pricing model, Ethereum’s traditional single-dimensional gas accounting (`uint64`) is being replaced by a multi-dimensional system in which gas is tracked separately for distinct resource categories. The objective is to isolate and measure consumption across orthogonal resource axes, such as computation, state access, state growth, and historical data appendage. This separation enables fine-grained pricing adjustments per resource in response to network load or policy considerations. + +## Decision Outcome + +A multi-dimensional gas metering approach is adopted, introducing distinct `ResourceKind` categories. Each opcode’s dynamic gas cost is mapped to one or more resource kinds. The following resource kinds have been identified: + +- ResourceKindComputation. Represents pure computational effort, CPU-bound operations that do not mutate global state: + - Opcode execution + - Memory expansion + - Call gas forwarding (EIP-150) + - Value transfers (unless to empty accounts, then it's StorageGrowth) + - Contract init code execution (CREATE, CREATE2) + - Hashing + - Bloom filter updates + +- ResourceKindStorageAccess. Represents read access to the global state: + - Account lookups (CALL, EXTCODESIZE, BALANCE) + - Storage slot reads + - Storage slot writes (nonzero → nonzero and nonzero → zero) + - Witness generation for reads (e.g. Verkle/stateless mode) + - Access list updates (EIP-2929/2930) + - Verkle proof traversal + - Target address resolution (DELEGATECALL, STATICCALL) + +- ResourceKindStorageGrowth. Includes operations that increase the persistent state size: + - New account creation + - Storage slot writes (zero → nonzero) + - Merkle/Verkle trie growth (EIP-4762) + - Contract deployment deposit cost + +- ResourceKindHistoryGrowth. Represents writes to the append-only event log history: + - Event logs (LOG0–LOG4) diff --git a/execution/gethexec/api.go b/execution/gethexec/api.go index 05a05de3f2..b070569131 100644 --- a/execution/gethexec/api.go +++ b/execution/gethexec/api.go @@ -23,6 +23,7 @@ import ( "github.com/offchainlabs/nitro/arbos/arbosState" "github.com/offchainlabs/nitro/arbos/retryables" + "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/timeboost" "github.com/offchainlabs/nitro/util/arbmath" ) @@ -30,12 +31,14 @@ import ( type ArbAPI struct { txPublisher TransactionPublisher bulkBlockMetadataFetcher *BulkBlockMetadataFetcher + execEngine *ExecutionEngine } -func NewArbAPI(publisher TransactionPublisher, bulkBlockMetadataFetcher *BulkBlockMetadataFetcher) *ArbAPI { +func NewArbAPI(publisher TransactionPublisher, bulkBlockMetadataFetcher *BulkBlockMetadataFetcher, execEngine *ExecutionEngine) *ArbAPI { return &ArbAPI{ txPublisher: publisher, bulkBlockMetadataFetcher: bulkBlockMetadataFetcher, + execEngine: execEngine, } } @@ -44,6 +47,10 @@ type NumberAndBlockMetadata struct { RawMetadata hexutil.Bytes `json:"rawMetadata"` } +func (a *ArbAPI) MaintenanceStatus(ctx context.Context) execution.MaintenanceStatus { + return *a.execEngine.MaintenanceStatus() +} + func (a *ArbAPI) CheckPublisherHealth(ctx context.Context) error { return a.txPublisher.CheckHealth(ctx) } diff --git a/execution/gethexec/block_recorder.go b/execution/gethexec/block_recorder.go index febf317674..ac16ed4274 100644 --- a/execution/gethexec/block_recorder.go +++ b/execution/gethexec/block_recorder.go @@ -158,7 +158,7 @@ func (r *BlockRecorder) RecordBlockCreation( recordingdb, chaincontext, false, - core.MessageReplayMode, + core.NewMessageRecordingContext(r.execEngine.wasmTargets), ) if err != nil { return nil, err diff --git a/execution/gethexec/blockchain.go b/execution/gethexec/blockchain.go index 36ec61a0f4..338f0e1c6b 100644 --- a/execution/gethexec/blockchain.go +++ b/execution/gethexec/blockchain.go @@ -15,7 +15,6 @@ import ( "github.com/ethereum/go-ethereum/eth/ethconfig" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos" @@ -29,6 +28,7 @@ type CachingConfig struct { Archive bool `koanf:"archive"` BlockCount uint64 `koanf:"block-count"` BlockAge time.Duration `koanf:"block-age"` + TrieTimeLimitBeforeFlushMaintenance time.Duration `koanf:"trie-time-limit-before-flush-maintenance"` TrieTimeLimit time.Duration `koanf:"trie-time-limit"` TrieTimeLimitRandomOffset time.Duration `koanf:"trie-time-limit-random-offset"` TrieDirtyCache int `koanf:"trie-dirty-cache"` @@ -51,6 +51,7 @@ func CachingConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".archive", DefaultCachingConfig.Archive, "retain past block state") f.Uint64(prefix+".block-count", DefaultCachingConfig.BlockCount, "minimum number of recent blocks to keep in memory") f.Duration(prefix+".block-age", DefaultCachingConfig.BlockAge, "minimum age of recent blocks to keep in memory") + f.Duration(prefix+".trie-time-limit-before-flush-maintenance", DefaultCachingConfig.TrieTimeLimitBeforeFlushMaintenance, "Execution will suggest that maintenance is run if the block processing time required to reach trie-time-limit is smaller or equal than trie-time-limit-before-flush-maintenance") f.Duration(prefix+".trie-time-limit", DefaultCachingConfig.TrieTimeLimit, "maximum block processing time before trie is written to hard-disk") f.Duration(prefix+".trie-time-limit-random-offset", DefaultCachingConfig.TrieTimeLimitRandomOffset, "if greater then 0, the block processing time period of each trie write to hard-disk is shortened by a random value from range [0, trie-time-limit-random-offset)") f.Int(prefix+".trie-dirty-cache", DefaultCachingConfig.TrieDirtyCache, "amount of memory in megabytes to cache state diffs against disk with (larger cache lowers database growth)") @@ -75,27 +76,27 @@ func getStateHistory(maxBlockSpeed time.Duration) uint64 { } var DefaultCachingConfig = CachingConfig{ - Archive: false, - BlockCount: 128, - BlockAge: 30 * time.Minute, - TrieTimeLimit: time.Hour, - TrieTimeLimitRandomOffset: 0, - TrieDirtyCache: 1024, - TrieCleanCache: 600, - TrieCapLimit: 100, - SnapshotCache: 400, - DatabaseCache: 2048, - SnapshotRestoreGasLimit: 300_000_000_000, - HeadRewindBlocksLimit: 4 * 7 * 24 * 3600, // 4 blocks per second over 7 days (an arbitrary value, should be greater than the number of blocks between state commits in full node; the state commit period depends both on chain activity and TrieTimeLimit) - MaxNumberOfBlocksToSkipStateSaving: 0, - MaxAmountOfGasToSkipStateSaving: 0, - StylusLRUCacheCapacity: 256, - StateScheme: rawdb.HashScheme, - StateHistory: getStateHistory(DefaultSequencerConfig.MaxBlockSpeed), + Archive: false, + BlockCount: 128, + BlockAge: 30 * time.Minute, + TrieTimeLimitBeforeFlushMaintenance: 0, + TrieTimeLimit: time.Hour, + TrieTimeLimitRandomOffset: 0, + TrieDirtyCache: 1024, + TrieCleanCache: 600, + TrieCapLimit: 100, + SnapshotCache: 400, + DatabaseCache: 2048, + SnapshotRestoreGasLimit: 300_000_000_000, + HeadRewindBlocksLimit: 4 * 7 * 24 * 3600, // 4 blocks per second over 7 days (an arbitrary value, should be greater than the number of blocks between state commits in full node; the state commit period depends both on chain activity and TrieTimeLimit) + MaxNumberOfBlocksToSkipStateSaving: 0, + MaxAmountOfGasToSkipStateSaving: 0, + StylusLRUCacheCapacity: 256, + StateScheme: rawdb.HashScheme, + StateHistory: getStateHistory(DefaultSequencerConfig.MaxBlockSpeed), } -// TODO remove stack from parameters as it is no longer needed here -func DefaultCacheConfigFor(stack *node.Node, cachingConfig *CachingConfig) *core.CacheConfig { +func DefaultCacheConfigFor(cachingConfig *CachingConfig) *core.CacheConfig { baseConf := ethconfig.Defaults if cachingConfig.Archive { baseConf = ethconfig.ArchiveDefaults @@ -224,7 +225,7 @@ func GetBlockChain( cacheConfig *core.CacheConfig, chainConfig *params.ChainConfig, tracer *tracing.Hooks, - txLookupLimit uint64, + txIndexerConfig *TxIndexerConfig, ) (*core.BlockChain, error) { engine := arbos.Engine{ IsSequencer: true, @@ -235,7 +236,15 @@ func GetBlockChain( Tracer: tracer, } - return core.NewBlockChain(chainDb, cacheConfig, chainConfig, nil, nil, engine, vmConfig, &txLookupLimit) + var coreTxIndexerConfig *core.TxIndexerConfig // nil if disabled + if txIndexerConfig.Enable { + coreTxIndexerConfig = &core.TxIndexerConfig{ + Limit: txIndexerConfig.TxLookupLimit, + Threads: txIndexerConfig.Threads, + MinBatchDelay: txIndexerConfig.MinBatchDelay, + } + } + return core.NewBlockChainExtended(chainDb, cacheConfig, chainConfig, nil, nil, engine, vmConfig, coreTxIndexerConfig) } func WriteOrTestBlockChain( @@ -246,7 +255,7 @@ func WriteOrTestBlockChain( genesisArbOSInit *params.ArbOSInit, tracer *tracing.Hooks, initMessage *arbostypes.ParsedInitMessage, - txLookupLimit uint64, + txIndexerConfig *TxIndexerConfig, accountsPerSync uint, ) (*core.BlockChain, error) { emptyBlockChain := rawdb.ReadHeadHeader(chainDb) == nil @@ -254,7 +263,7 @@ func WriteOrTestBlockChain( // When using path scheme, and the stored state trie is not empty, // WriteOrTestGenBlock is not able to recover EmptyRootHash state trie node. // In that case Nitro doesn't test genblock, but just returns the BlockChain. - return GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, txLookupLimit) + return GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, txIndexerConfig) } err := WriteOrTestGenblock(chainDb, cacheConfig, initData, chainConfig, genesisArbOSInit, initMessage, accountsPerSync) @@ -265,7 +274,7 @@ func WriteOrTestBlockChain( if err != nil { return nil, err } - return GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, txLookupLimit) + return GetBlockChain(chainDb, cacheConfig, chainConfig, tracer, txIndexerConfig) } func init() { diff --git a/execution/gethexec/contract_adapter.go b/execution/gethexec/contract_adapter.go index 4a38004983..6e182c3844 100644 --- a/execution/gethexec/contract_adapter.go +++ b/execution/gethexec/contract_adapter.go @@ -85,8 +85,8 @@ func (a *contractAdapter) CallContract(ctx context.Context, call ethereum.CallMs AccessList: call.AccessList, SkipNonceChecks: true, SkipFromEOACheck: true, - TxRunMode: core.MessageEthcallMode, // Indicate this is an eth_call - SkipL1Charging: true, // Skip L1 data fees + TxRunContext: core.NewMessageEthcallContext(), // Indicate this is an eth_call + SkipL1Charging: true, // Skip L1 data fees } evm := a.apiBackend.GetEVM(ctx, state, header, &vm.Config{NoBaseFee: true}, nil) diff --git a/execution/gethexec/executionengine.go b/execution/gethexec/executionengine.go index 1f5df74c54..a7e476e5d8 100644 --- a/execution/gethexec/executionengine.go +++ b/execution/gethexec/executionengine.go @@ -24,6 +24,7 @@ import ( "runtime/pprof" "runtime/trace" "sync" + "sync/atomic" "testing" "time" @@ -37,6 +38,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbosState" @@ -44,7 +46,6 @@ import ( "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbos/programs" "github.com/offchainlabs/nitro/arbutil" - "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/sharedmetrics" @@ -58,8 +59,8 @@ var ( txCountHistogram = metrics.NewRegisteredHistogram("arb/block/transactions/count", nil, metrics.NewBoundedHistogramSample()) txGasUsedHistogram = metrics.NewRegisteredHistogram("arb/block/transactions/gasused", nil, metrics.NewBoundedHistogramSample()) gasUsedSinceStartupCounter = metrics.NewRegisteredCounter("arb/gas_used", nil) - blockExecutionTimer = metrics.NewRegisteredTimer("arb/block/execution", nil) - blockWriteToDbTimer = metrics.NewRegisteredTimer("arb/block/writetodb", nil) + blockExecutionTimer = metrics.NewRegisteredHistogram("arb/block/execution", nil, metrics.NewBoundedHistogramSample()) + blockWriteToDbTimer = metrics.NewRegisteredHistogram("arb/block/writetodb", nil, metrics.NewBoundedHistogramSample()) ) var ExecutionEngineBlockCreationStopped = errors.New("block creation stopped in execution engine") @@ -68,8 +69,6 @@ var ResultNotFound = errors.New("result not found") type L1PriceDataOfMsg struct { callDataUnits uint64 cummulativeCallDataUnits uint64 - l1GasCharged uint64 - cummulativeL1GasCharged uint64 } type L1PriceData struct { @@ -103,7 +102,12 @@ type ExecutionEngine struct { prefetchBlock bool cachedL1PriceData *L1PriceData - syncTillBlock uint64 + + wasmTargets []rawdb.WasmTarget + + syncTillBlock uint64 + + runningMaintenance atomic.Bool } func NewL1PriceData() *L1PriceData { @@ -135,25 +139,12 @@ func (s *ExecutionEngine) backlogCallDataUnits() uint64 { s.cachedL1PriceData.msgToL1PriceData[0].callDataUnits) } -func (s *ExecutionEngine) backlogL1GasCharged() uint64 { - s.cachedL1PriceData.mutex.RLock() - defer s.cachedL1PriceData.mutex.RUnlock() - - size := len(s.cachedL1PriceData.msgToL1PriceData) - if size == 0 { - return 0 - } - return (s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeL1GasCharged - - s.cachedL1PriceData.msgToL1PriceData[0].cummulativeL1GasCharged + - s.cachedL1PriceData.msgToL1PriceData[0].l1GasCharged) -} - func (s *ExecutionEngine) MarkFeedStart(to arbutil.MessageIndex) { s.cachedL1PriceData.mutex.Lock() defer s.cachedL1PriceData.mutex.Unlock() if to < s.cachedL1PriceData.startOfL1PriceDataCache { - log.Debug("trying to trim older L1 price data cache which doesnt exist anymore") + log.Debug("trying to trim older L1 price data cache which doesn't exist anymore") } else if to >= s.cachedL1PriceData.endOfL1PriceDataCache { s.cachedL1PriceData.startOfL1PriceDataCache = 0 s.cachedL1PriceData.endOfL1PriceDataCache = 0 @@ -204,6 +195,7 @@ func (s *ExecutionEngine) Initialize(rustCacheCapacityMB uint32, targetConfig *S if err := PopulateStylusTargetCache(targetConfig); err != nil { return fmt.Errorf("error populating stylus target cache: %w", err) } + s.wasmTargets = targetConfig.WasmTargets() return nil } @@ -311,7 +303,7 @@ func (s *ExecutionEngine) Reorg(msgIdxOfFirstMsgToAdd arbutil.MessageIndex, newM s.bc.SetFinalized(nil) } - tag := s.bc.StateCache().WasmCacheTag() + tag := core.NewMessageCommitContext(nil).WasmCacheTag() // we don't pass any targets, we just want the tag // reorg Rust-side VM state C.stylus_reorg_vm(C.uint64_t(lastBlockNumToKeep), C.uint32_t(tag)) @@ -380,10 +372,14 @@ func (s *ExecutionEngine) NextDelayedMessageNumber() (uint64, error) { return currentHeader.Nonce.Uint64(), nil } -func MessageFromTxes(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, txErrors []error) (*arbostypes.L1IncomingMessage, error) { +func MessageFromTxes(header *arbostypes.L1IncomingMessageHeader, hooks *arbos.SequencingHooks) (*arbostypes.L1IncomingMessage, error) { var l2Message []byte - if len(txes) == 1 && txErrors[0] == nil { - txBytes, err := txes[0].MarshalBinary() + if len(hooks.TxErrors) == 1 && hooks.TxErrors[0] == nil { + tx, err := hooks.SequencedTx(0) + if err != nil { + return nil, err + } + txBytes, err := tx.MarshalBinary() if err != nil { return nil, err } @@ -392,10 +388,14 @@ func MessageFromTxes(header *arbostypes.L1IncomingMessageHeader, txes types.Tran } else { l2Message = append(l2Message, arbos.L2MessageKind_Batch) sizeBuf := make([]byte, 8) - for i, tx := range txes { - if txErrors[i] != nil { + for i := 0; i < len(hooks.TxErrors); i++ { + if hooks.TxErrors[i] != nil { continue } + tx, err := hooks.SequencedTx(i) + if err != nil { + return nil, err + } txBytes, err := tx.MarshalBinary() if err != nil { return nil, err @@ -459,9 +459,9 @@ func (s *ExecutionEngine) resequenceReorgedMessages(messages []*arbostypes.Messa log.Warn("failed to parse sequencer message found from reorg", "err", err) continue } - hooks := arbos.NoopSequencingHooks() + hooks := arbos.NoopSequencingHooks(txes) hooks.DiscardInvalidTxsEarly = true - _, err = s.sequenceTransactionsWithBlockMutex(msg.Message.Header, txes, hooks, nil) + _, err = s.sequenceTransactionsWithBlockMutex(msg.Message.Header, hooks, nil) if err != nil { log.Error("failed to re-sequence old user message removed by reorg", "err", err) return @@ -498,17 +498,17 @@ func (s *ExecutionEngine) sequencerWrapper(sequencerFunc func() (*types.Block, e } } -func (s *ExecutionEngine) SequenceTransactions(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks, timeboostedTxs map[common.Hash]struct{}) (*types.Block, error) { +func (s *ExecutionEngine) SequenceTransactions(header *arbostypes.L1IncomingMessageHeader, hooks *arbos.SequencingHooks, timeboostedTxs map[common.Hash]struct{}) (*types.Block, error) { return s.sequencerWrapper(func() (*types.Block, error) { hooks.TxErrors = nil - return s.sequenceTransactionsWithBlockMutex(header, txes, hooks, timeboostedTxs) + return s.sequenceTransactionsWithBlockMutex(header, hooks, timeboostedTxs) }) } // SequenceTransactionsWithProfiling runs SequenceTransactions with tracing and // CPU profiling enabled. If the block creation takes longer than 2 seconds, it // keeps both and prints out filenames in an error log line. -func (s *ExecutionEngine) SequenceTransactionsWithProfiling(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks, timeboostedTxs map[common.Hash]struct{}) (*types.Block, error) { +func (s *ExecutionEngine) SequenceTransactionsWithProfiling(header *arbostypes.L1IncomingMessageHeader, hooks *arbos.SequencingHooks, timeboostedTxs map[common.Hash]struct{}) (*types.Block, error) { pprofBuf, traceBuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil) if err := pprof.StartCPUProfile(pprofBuf); err != nil { log.Error("Starting CPU profiling", "error", err) @@ -517,7 +517,7 @@ func (s *ExecutionEngine) SequenceTransactionsWithProfiling(header *arbostypes.L log.Error("Starting tracing", "error", err) } start := time.Now() - res, err := s.SequenceTransactions(header, txes, hooks, timeboostedTxs) + res, err := s.SequenceTransactions(header, hooks, timeboostedTxs) elapsed := time.Since(start) pprof.StopCPUProfile() trace.Stop() @@ -543,7 +543,7 @@ func writeAndLog(pprof, trace *bytes.Buffer) { log.Info("Transactions sequencing took longer than 2 seconds, created pprof and trace files", "pprof", pprofFile, "traceFile", traceFile) } -func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes.L1IncomingMessageHeader, txes types.Transactions, hooks *arbos.SequencingHooks, timeboostedTxs map[common.Hash]struct{}) (*types.Block, error) { +func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes.L1IncomingMessageHeader, hooks *arbos.SequencingHooks, timeboostedTxs map[common.Hash]struct{}) (*types.Block, error) { lastBlockHeader, err := s.getCurrentHeader() if err != nil { return nil, err @@ -571,23 +571,19 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. startTime := time.Now() block, receipts, err := arbos.ProduceBlockAdvanced( header, - txes, delayedMessagesRead, lastBlockHeader, statedb, s.bc, hooks, false, - core.MessageCommitMode, + core.NewMessageCommitContext(s.wasmTargets), ) if err != nil { return nil, err } blockCalcTime := time.Since(startTime) - blockExecutionTimer.Update(blockCalcTime) - if len(hooks.TxErrors) != len(txes) { - return nil, fmt.Errorf("unexpected number of error results: %v vs number of txes %v", len(hooks.TxErrors), len(txes)) - } + blockExecutionTimer.Update(blockCalcTime.Nanoseconds()) if len(receipts) == 0 { return nil, nil @@ -604,7 +600,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. return nil, nil } - msg, err := MessageFromTxes(header, txes, hooks.TxErrors) + msg, err := MessageFromTxes(header, hooks) if err != nil { return nil, err } @@ -635,7 +631,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. if err != nil { return nil, err } - s.cacheL1PriceDataOfMsg(msgIdx, receipts, block, false) + s.cacheL1PriceDataOfMsg(msgIdx, block, false) return block, nil } @@ -644,7 +640,7 @@ func (s *ExecutionEngine) sequenceTransactionsWithBlockMutex(header *arbostypes. // or not. The first byte of blockMetadata byte array is reserved to indicate the version, // starting from the second byte, (N)th bit would represent if (N)th tx is timeboosted or not, 1 means yes and 0 means no // blockMetadata[index / 8 + 1] & (1 << (index % 8)) != 0; where index = (N - 1), implies whether (N)th tx in a block is timeboosted -// note that number of txs in a block will always lag behind (len(blockMetadata) - 1) * 8 but it wont lag more than a value of 7 +// note that number of txs in a block will always lag behind (len(blockMetadata) - 1) * 8 but it won't lag more than a value of 7 func (s *ExecutionEngine) blockMetadataFromBlock(block *types.Block, timeboostedTxs map[common.Hash]struct{}) common.BlockMetadata { bits := make(common.BlockMetadata, 1+arbmath.DivCeil(uint64(len(block.Transactions())), 8)) if len(timeboostedTxs) == 0 { @@ -696,7 +692,7 @@ func (s *ExecutionEngine) sequenceDelayedMessageWithBlockMutex(message *arbostyp return nil, err } blockCalcTime := time.Since(startTime) - blockExecutionTimer.Update(blockCalcTime) + blockExecutionTimer.Update(blockCalcTime.Nanoseconds()) msgResult, err := s.resultFromHeader(block.Header()) if err != nil { @@ -712,7 +708,7 @@ func (s *ExecutionEngine) sequenceDelayedMessageWithBlockMutex(message *arbostyp if err != nil { return nil, err } - s.cacheL1PriceDataOfMsg(msgIdx, receipts, block, true) + s.cacheL1PriceDataOfMsg(msgIdx, block, true) log.Info("ExecutionEngine: Added DelayedMessages", "msgIdx", msgIdx, "delayedMsgIdx", delayedMsgIdx, "block-header", block.Header()) @@ -766,9 +762,11 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith statedb.StartPrefetcher("TransactionStreamer", witness) defer statedb.StopPrefetcher() - runMode := core.MessageCommitMode + var runCtx *core.MessageRunContext if isMsgForPrefetch { - runMode = core.MessageReplayMode + runCtx = core.NewMessagePrefetchContext() + } else { + runCtx = core.NewMessageCommitContext(s.wasmTargets) } block, receipts, err := arbos.ProduceBlock( msg.Message, @@ -777,7 +775,7 @@ func (s *ExecutionEngine) createBlockFromNextMessage(msg *arbostypes.MessageWith statedb, s.bc, isMsgForPrefetch, - runMode, + runCtx, ) return block, statedb, receipts, err @@ -805,7 +803,7 @@ func (s *ExecutionEngine) appendBlock(block *types.Block, statedb *state.StateDB return errors.New("geth rejected block as non-canonical") } } - blockWriteToDbTimer.Update(time.Since(startTime)) + blockWriteToDbTimer.Update(time.Since(startTime).Nanoseconds()) baseFeeGauge.Update(block.BaseFee().Int64()) txCountHistogram.Update(int64(len(block.Transactions()) - 1)) var blockGasused uint64 @@ -874,24 +872,17 @@ func (s *ExecutionEngine) getL1PricingSurplus() (int64, error) { return surplus.Int64(), nil } -func (s *ExecutionEngine) cacheL1PriceDataOfMsg(msgIdx arbutil.MessageIndex, receipts types.Receipts, block *types.Block, blockBuiltUsingDelayedMessage bool) { - var gasUsedForL1 uint64 +func (s *ExecutionEngine) cacheL1PriceDataOfMsg(msgIdx arbutil.MessageIndex, block *types.Block, blockBuiltUsingDelayedMessage bool) { var callDataUnits uint64 if !blockBuiltUsingDelayedMessage { // s.cachedL1PriceData tracks L1 price data for messages posted by Nitro, // so delayed messages should not update cummulative values kept on it. - // First transaction in every block is an Arbitrum internal transaction, - // so we skip it here. - for i := 1; i < len(receipts); i++ { - gasUsedForL1 += receipts[i].GasUsedForL1 - } for _, tx := range block.Transactions() { _, cachedUnits := tx.GetRawCachedCalldataUnits() callDataUnits += cachedUnits } } - l1GasCharged := gasUsedForL1 * block.BaseFee().Uint64() s.cachedL1PriceData.mutex.Lock() defer s.cachedL1PriceData.mutex.Unlock() @@ -902,8 +893,6 @@ func (s *ExecutionEngine) cacheL1PriceDataOfMsg(msgIdx arbutil.MessageIndex, rec s.cachedL1PriceData.msgToL1PriceData = []L1PriceDataOfMsg{{ callDataUnits: callDataUnits, cummulativeCallDataUnits: callDataUnits, - l1GasCharged: l1GasCharged, - cummulativeL1GasCharged: l1GasCharged, }} } size := len(s.cachedL1PriceData.msgToL1PriceData) @@ -925,12 +914,9 @@ func (s *ExecutionEngine) cacheL1PriceDataOfMsg(msgIdx arbutil.MessageIndex, rec } } else { cummulativeCallDataUnits := s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeCallDataUnits - cummulativeL1GasCharged := s.cachedL1PriceData.msgToL1PriceData[size-1].cummulativeL1GasCharged s.cachedL1PriceData.msgToL1PriceData = append(s.cachedL1PriceData.msgToL1PriceData, L1PriceDataOfMsg{ callDataUnits: callDataUnits, cummulativeCallDataUnits: cummulativeCallDataUnits + callDataUnits, - l1GasCharged: l1GasCharged, - cummulativeL1GasCharged: cummulativeL1GasCharged + l1GasCharged, }) s.cachedL1PriceData.endOfL1PriceDataCache = msgIdx } @@ -977,13 +963,13 @@ func (s *ExecutionEngine) digestMessageWithBlockMutex(msgIdxToDigest arbutil.Mes return nil, err } blockCalcTime := time.Since(startTime) - blockExecutionTimer.Update(blockCalcTime) + blockExecutionTimer.Update(blockCalcTime.Nanoseconds()) err = s.appendBlock(block, statedb, receipts, blockCalcTime) if err != nil { return nil, err } - s.cacheL1PriceDataOfMsg(msgIdxToDigest, receipts, block, false) + s.cacheL1PriceDataOfMsg(msgIdxToDigest, block, false) if time.Now().After(s.nextScheduledVersionCheck) { s.nextScheduledVersionCheck = time.Now().Add(time.Minute) @@ -1005,17 +991,16 @@ func (s *ExecutionEngine) digestMessageWithBlockMutex(msgIdxToDigest arbutil.Mes timestamp = time.Unix(int64(timestampInt), 0) timeUntilUpgrade = time.Until(timestamp) } - maxSupportedVersion := chaininfo.ArbitrumDevTestChainConfig().ArbitrumChainParams.InitialArbOSVersion logLevel := log.Warn if timeUntilUpgrade < time.Hour*24 { logLevel = log.Error } - if version > maxSupportedVersion { + if version > params.MaxArbosVersionSupported { logLevel( "you need to update your node to the latest version before this scheduled ArbOS upgrade", "timeUntilUpgrade", timeUntilUpgrade, "upgradeScheduledFor", timestamp, - "maxSupportedArbosVersion", maxSupportedVersion, + "maxSupportedArbosVersion", params.MaxArbosVersionSupported, "pendingArbosUpgradeVersion", version, ) } @@ -1104,8 +1089,48 @@ func (s *ExecutionEngine) Start(ctx_in context.Context) { } } -func (s *ExecutionEngine) Maintenance(capLimit uint64) error { - s.createBlocksMutex.Lock() - defer s.createBlocksMutex.Unlock() - return s.bc.FlushTrieDB(common.StorageSize(capLimit)) +func (s *ExecutionEngine) ShouldTriggerMaintenance(trieLimitBeforeFlushMaintenance time.Duration) bool { + if s.runningMaintenance.Load() { + return false + } + + procTimeBeforeFlush, err := s.bc.ProcTimeBeforeFlush() + if err != nil { + log.Error("failed to get time before flush", "err") + return false + } + + if procTimeBeforeFlush <= trieLimitBeforeFlushMaintenance/2 { + log.Warn("Time before flush is too low, maintenance should be triggered soon", "procTimeBeforeFlush", procTimeBeforeFlush) + } + return procTimeBeforeFlush <= trieLimitBeforeFlushMaintenance +} + +func (s *ExecutionEngine) TriggerMaintenance(capLimit uint64) { + if s.runningMaintenance.Swap(true) { + log.Info("Maintenance already running, skipping") + return + } + + // Flushing the trie DB can be a long operation, so we run it in a new thread + s.LaunchThread(func(ctx context.Context) { + defer s.runningMaintenance.Store(false) + + s.createBlocksMutex.Lock() + defer s.createBlocksMutex.Unlock() + + log.Info("Flushing trie db through maintenance, it can take a while") + err := s.bc.FlushTrieDB(common.StorageSize(capLimit)) + if err != nil { + log.Error("Failed to flush trie db through maintenance", "err", err) + } else { + log.Info("Flushed trie db through maintenance completed successfully") + } + }) +} + +func (s *ExecutionEngine) MaintenanceStatus() *execution.MaintenanceStatus { + return &execution.MaintenanceStatus{ + IsRunning: s.runningMaintenance.Load(), + } } diff --git a/execution/gethexec/express_lane_service.go b/execution/gethexec/express_lane_service.go index 7564d3f3d3..4e7bf2cf95 100644 --- a/execution/gethexec/express_lane_service.go +++ b/execution/gethexec/express_lane_service.go @@ -83,7 +83,7 @@ pending: const maxRetries = 5 if errors.Is(err, bind.ErrNoCode) && retries < maxRetries { wait := time.Millisecond * 250 * (1 << retries) - log.Info("ExpressLaneAuction contract not ready, will retry afer wait", "err", err, "wait", wait, "maxRetries", maxRetries) + log.Info("ExpressLaneAuction contract not ready, will retry after wait", "err", err, "wait", wait, "maxRetries", maxRetries) retries++ time.Sleep(wait) goto pending diff --git a/execution/gethexec/express_lane_tracker.go b/execution/gethexec/express_lane_tracker.go index 12a1e22e34..37fdd4da3b 100644 --- a/execution/gethexec/express_lane_tracker.go +++ b/execution/gethexec/express_lane_tracker.go @@ -10,8 +10,8 @@ import ( "github.com/pkg/errors" "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/arbitrum" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -26,6 +26,10 @@ type RoundListener interface { NextRound(round uint64, controller common.Address) } +type HeaderProvider interface { + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) +} + // ExpressLaneTracker knows what round it is type ExpressLaneTracker struct { stopwaiter.StopWaiter @@ -34,18 +38,19 @@ type ExpressLaneTracker struct { pollInterval time.Duration chainConfig *params.ChainConfig - apiBackend *arbitrum.APIBackend + headerProvider HeaderProvider auctionContract *express_lane_auctiongen.ExpressLaneAuction auctionContractAddr common.Address earlySubmissionGrace time.Duration roundControl containers.SyncMap[uint64, common.Address] // thread safe + useLogs bool } func NewExpressLaneTracker( roundTimingInfo timeboost.RoundTimingInfo, pollInterval time.Duration, - apiBackend *arbitrum.APIBackend, + headerProvider HeaderProvider, auctionContract *express_lane_auctiongen.ExpressLaneAuction, auctionContractAddr common.Address, chainConfig *params.ChainConfig, @@ -53,24 +58,86 @@ func NewExpressLaneTracker( return &ExpressLaneTracker{ roundTimingInfo: roundTimingInfo, pollInterval: pollInterval, - apiBackend: apiBackend, + headerProvider: headerProvider, auctionContract: auctionContract, auctionContractAddr: auctionContractAddr, earlySubmissionGrace: earlySubmissionGrace, chainConfig: chainConfig, + useLogs: false, // default to use contract polling } } func (t *ExpressLaneTracker) Start(ctxIn context.Context) { + if t.useLogs { + t.startViaLogIterator(ctxIn) + } else { + t.startViaContractPolling(ctxIn) + } +} + +func (t *ExpressLaneTracker) RoundController(round uint64) (common.Address, error) { + controller, ok := t.roundControl.Load(round) + if !ok { + return common.Address{}, timeboost.ErrNoOnchainController + } + return controller, nil +} + +// validateExpressLaneTx checks for the correctness of all fields of msg +func (t *ExpressLaneTracker) ValidateExpressLaneTx(msg *timeboost.ExpressLaneSubmission) error { + if msg == nil || msg.Transaction == nil || msg.Signature == nil { + return timeboost.ErrMalformedData + } + if msg.ChainId.Cmp(t.chainConfig.ChainID) != 0 { + return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, t.chainConfig.ChainID) + } + if msg.AuctionContractAddress != t.auctionContractAddr { + return errors.Wrapf(timeboost.ErrWrongAuctionContract, "msg auction contract address %#x does not match sequencer auction contract address %#x", msg.AuctionContractAddress, t.auctionContractAddr) + } + + currentRound := t.roundTimingInfo.RoundNumber() + if msg.Round != currentRound { + timeTilNextRound := t.roundTimingInfo.TimeTilNextRound() + // We allow txs to come in for the next round if it is close enough to that round, + // but we sleep until the round starts. + if msg.Round == currentRound+1 && timeTilNextRound <= t.earlySubmissionGrace { + time.Sleep(timeTilNextRound) + } else { + return errors.Wrapf(timeboost.ErrBadRoundNumber, "express lane tx round %d does not match current round %d", msg.Round, currentRound) + } + } + + controller, ok := t.roundControl.Load(msg.Round) + if !ok { + return timeboost.ErrNoOnchainController + } + // Extract sender address and cache it to be later used by sequenceExpressLaneSubmission + sender, err := msg.Sender() + if err != nil { + return err + } + if sender != controller { + return timeboost.ErrNotExpressLaneController + } + return nil +} + +func (t *ExpressLaneTracker) AuctionContractAddr() common.Address { + return t.auctionContractAddr +} + +// --- internals --- + +func (t *ExpressLaneTracker) startViaLogIterator(ctxIn context.Context) { t.StopWaiter.Start(ctxIn, t) t.LaunchThread(func(ctx context.Context) { // Monitor for auction resolutions from the auction manager smart contract // and set the express lane controller for the upcoming round accordingly. - log.Info("Monitoring express lane auction contract") + log.Info("Monitoring express lane auction contract via logs") var fromBlock uint64 - latestBlock, err := t.apiBackend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + latestBlock, err := t.headerProvider.HeaderByNumber(ctx, rpc.LatestBlockNumber) if err != nil { log.Error("ExpressLaneService could not get the latest header", "err", err) } else { @@ -92,7 +159,7 @@ func (t *ExpressLaneTracker) Start(ctxIn context.Context) { case <-ticker.C: } - latestBlock, err := t.apiBackend.HeaderByNumber(ctx, rpc.LatestBlockNumber) + latestBlock, err := t.headerProvider.HeaderByNumber(ctx, rpc.LatestBlockNumber) if err != nil { log.Error("ExpressLaneTracker could not get the latest header", "err", err) continue @@ -113,7 +180,7 @@ func (t *ExpressLaneTracker) Start(ctxIn context.Context) { continue } for it.Next() { - timeSinceAuctionClose := t.roundTimingInfo.AuctionClosing - t.roundTimingInfo.TimeTilNextRound() + timeSinceAuctionClose := t.elapsedSinceAuctionClose(it.Event.Round) auctionResolutionLatency.Update(timeSinceAuctionClose.Nanoseconds()) log.Info( "AuctionResolved: New express lane controller assigned", @@ -125,10 +192,68 @@ func (t *ExpressLaneTracker) Start(ctxIn context.Context) { t.roundControl.Store(it.Event.Round, it.Event.FirstPriceExpressLaneController) } + + if it.Error() != nil { + log.Error("Error occurred while iterating auction resolutions", "error", it.Error()) + } + fromBlock = toBlock + 1 } }) + t.roundHeartbeatThread() +} + +func (t *ExpressLaneTracker) startViaContractPolling(ctxIn context.Context) { + t.StopWaiter.Start(ctxIn, t) + + // poll contract state via resolvedRounds() + t.LaunchThread(func(ctx context.Context) { + log.Info("Monitoring express lane auction contract via resolvedRounds") + + ticker := time.NewTicker(t.pollInterval) + defer ticker.Stop() + + var highestSeenRound uint64 + var initialized bool + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + + record, ok := t.readLatestResolvedRound(ctx) + if !ok { + continue + } + + if record.round > highestSeenRound { + highestSeenRound = record.round + + if !initialized { + initialized = true + } else { + timeSinceAuctionClose := t.elapsedSinceAuctionClose(record.round) + auctionResolutionLatency.Update(timeSinceAuctionClose.Nanoseconds()) + log.Info( + "AuctionResolved: New express lane controller assigned", + "round", record.round, + "controller", record.controller, + "timeSinceAuctionClose", timeSinceAuctionClose, + ) + } + + t.roundControl.Store(record.round, record.controller) + } + } + }) + + t.roundHeartbeatThread() +} + +func (t *ExpressLaneTracker) roundHeartbeatThread() { t.LaunchThread(func(ctx context.Context) { // Log every new express lane auction round. log.Info("Watching for new express lane rounds") @@ -165,53 +290,51 @@ func (t *ExpressLaneTracker) Start(ctxIn context.Context) { }) } -func (t *ExpressLaneTracker) RoundController(round uint64) (common.Address, error) { - controller, ok := t.roundControl.Load(round) - if !ok { - return common.Address{}, timeboost.ErrNoOnchainController - } - return controller, nil +// resolvedRecord is a helper for parsed resolvedRounds entries +type resolvedRecord struct { + round uint64 + controller common.Address } -// validateExpressLaneTx checks for the correctness of all fields of msg -func (t *ExpressLaneTracker) ValidateExpressLaneTx(msg *timeboost.ExpressLaneSubmission) error { - if msg == nil || msg.Transaction == nil || msg.Signature == nil { - return timeboost.ErrMalformedData - } - if msg.ChainId.Cmp(t.chainConfig.ChainID) != 0 { - return errors.Wrapf(timeboost.ErrWrongChainId, "express lane tx chain ID %d does not match current chain ID %d", msg.ChainId, t.chainConfig.ChainID) - } - if msg.AuctionContractAddress != t.auctionContractAddr { - return errors.Wrapf(timeboost.ErrWrongAuctionContract, "msg auction contract address %#x does not match sequencer auction contract address %#x", msg.AuctionContractAddress, t.auctionContractAddr) - } - - currentRound := t.roundTimingInfo.RoundNumber() - if msg.Round != currentRound { - timeTilNextRound := t.roundTimingInfo.TimeTilNextRound() - // We allow txs to come in for the next round if it is close enough to that round, - // but we sleep until the round starts. - if msg.Round == currentRound+1 && timeTilNextRound <= t.earlySubmissionGrace { - time.Sleep(timeTilNextRound) - } else { - return errors.Wrapf(timeboost.ErrBadRoundNumber, "express lane tx round %d does not match current round %d", msg.Round, currentRound) - } +// returns the latest resolved round information +// assuming the first round in the 2 round array is always the most recent round +func (t *ExpressLaneTracker) readLatestResolvedRound(parentCtx context.Context) (resolvedRecord, bool) { + // Per-call timeout shorter than poll interval to avoid a slow node stalling the loop + timeout := t.pollInterval / 2 + if timeout <= 0 { + timeout = 2 * time.Second // default timeout 2 seconds } + ctx, cancel := context.WithTimeout(parentCtx, timeout) + defer cancel() - controller, ok := t.roundControl.Load(msg.Round) - if !ok { - return timeboost.ErrNoOnchainController - } - // Extract sender address and cache it to be later used by sequenceExpressLaneSubmission - sender, err := msg.Sender() + r0, _, err := t.auctionContract.ResolvedRounds(&bind.CallOpts{Context: ctx}) if err != nil { - return err + log.Warn("ExpressLaneTracker: resolvedRounds call failed", "err", err) + return resolvedRecord{}, false } - if sender != controller { - return timeboost.ErrNotExpressLaneController + + controller := r0.ExpressLaneController // adjust if binding fields differ + round := r0.Round + if controller == (common.Address{}) || round == 0 { + return resolvedRecord{}, false } - return nil + return resolvedRecord{round: round, controller: controller}, true } -func (t *ExpressLaneTracker) AuctionContractAddr() common.Address { - return t.auctionContractAddr +// elapsedSinceAuctionClose returns how long ago the auction for `round` closed. +// If the close time is in the future relative to now, it returns 0. +func (t *ExpressLaneTracker) elapsedSinceAuctionClose(round uint64) time.Duration { + rti := t.roundTimingInfo + + var roundsAgo int64 + if cur := rti.RoundNumber(); cur >= round { + // #nosec G115 — safe cast: round numbers are protocol-bounded + roundsAgo = int64(cur - round) + } + + elapsed := time.Duration(roundsAgo)*rti.Round + (rti.AuctionClosing - rti.TimeTilNextRound()) + if elapsed < 0 { + return 0 + } + return elapsed } diff --git a/execution/gethexec/forwarder.go b/execution/gethexec/forwarder.go index f6b295c3ab..c43f334ae1 100644 --- a/execution/gethexec/forwarder.go +++ b/execution/gethexec/forwarder.go @@ -446,7 +446,7 @@ func (f *RedisTxForwarder) update(ctx context.Context) time.Duration { newSequencerUrl = f.fallbackTarget } else { // TODO panic? - there is no way to recover from this point - log.Error("redis coordinator not initilized, no fallback available") + log.Error("redis coordinator not initialized, no fallback available") return f.retryAfterError() } } diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 1f255fff61..385774d602 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -7,6 +7,7 @@ import ( "reflect" "sort" "sync/atomic" + "time" flag "github.com/spf13/pflag" @@ -29,6 +30,7 @@ import ( "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/execution" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/dbutil" @@ -41,24 +43,24 @@ type StylusTargetConfig struct { Host string `koanf:"host"` ExtraArchs []string `koanf:"extra-archs"` - wasmTargets []ethdb.WasmTarget + wasmTargets []rawdb.WasmTarget } -func (c *StylusTargetConfig) WasmTargets() []ethdb.WasmTarget { +func (c *StylusTargetConfig) WasmTargets() []rawdb.WasmTarget { return c.wasmTargets } func (c *StylusTargetConfig) Validate() error { - targetsSet := make(map[ethdb.WasmTarget]bool, len(c.ExtraArchs)) + targetsSet := make(map[rawdb.WasmTarget]bool, len(c.ExtraArchs)) for _, arch := range c.ExtraArchs { - target := ethdb.WasmTarget(arch) + target := rawdb.WasmTarget(arch) if !rawdb.IsSupportedWasmTarget(target) { return fmt.Errorf("unsupported architecture: %v, possible values: %s, %s, %s, %s", arch, rawdb.TargetWavm, rawdb.TargetArm64, rawdb.TargetAmd64, rawdb.TargetHost) } targetsSet[target] = true } targetsSet[rawdb.LocalTarget()] = true - targets := make([]ethdb.WasmTarget, 0, len(c.ExtraArchs)+1) + targets := make([]rawdb.WasmTarget, 0, len(c.ExtraArchs)+1) for target := range targetsSet { targets = append(targets, target) } @@ -85,6 +87,27 @@ func StylusTargetConfigAddOptions(prefix string, f *flag.FlagSet) { f.StringSlice(prefix+".extra-archs", DefaultStylusTargetConfig.ExtraArchs, fmt.Sprintf("Comma separated list of extra architectures to cross-compile stylus program to and cache in wasm store (additionally to local target). Currently must include at least %s. (supported targets: %s, %s, %s, %s)", rawdb.TargetWavm, rawdb.TargetWavm, rawdb.TargetArm64, rawdb.TargetAmd64, rawdb.TargetHost)) } +type TxIndexerConfig struct { + Enable bool `koanf:"enable"` + TxLookupLimit uint64 `koanf:"tx-lookup-limit"` + Threads int `koanf:"threads"` + MinBatchDelay time.Duration `koanf:"min-batch-delay"` +} + +var DefaultTxIndexerConfig = TxIndexerConfig{ + Enable: true, + TxLookupLimit: 126_230_400, // 1 year at 4 blocks per second + Threads: util.GoMaxProcs(), + MinBatchDelay: time.Second, +} + +func TxIndexerConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Bool(prefix+".enable", DefaultTxIndexerConfig.Enable, "enables transaction indexer") + f.Uint64(prefix+".tx-lookup-limit", DefaultTxIndexerConfig.TxLookupLimit, "retain the ability to lookup transactions by hash for the past N blocks (0 = all blocks)") + f.Int(prefix+".threads", DefaultTxIndexerConfig.Threads, "number of threads used to RLP decode blocks during indexing/unindexing of historical transactions") + f.Duration(prefix+".min-batch-delay", DefaultTxIndexerConfig.MinBatchDelay, "minimum delay between transaction indexing/unindexing batches; the bigger the delay, the more blocks can be included in each batch") +} + type Config struct { ParentChainReader headerreader.Config `koanf:"parent-chain-reader" reload:"hot"` Sequencer SequencerConfig `koanf:"sequencer" reload:"hot"` @@ -95,7 +118,7 @@ type Config struct { SecondaryForwardingTarget []string `koanf:"secondary-forwarding-target"` Caching CachingConfig `koanf:"caching"` RPC arbitrum.Config `koanf:"rpc"` - TxLookupLimit uint64 `koanf:"tx-lookup-limit"` + TxIndexer TxIndexerConfig `koanf:"tx-indexer"` EnablePrefetchBlock bool `koanf:"enable-prefetch-block"` SyncMonitor SyncMonitorConfig `koanf:"sync-monitor"` StylusTarget StylusTargetConfig `koanf:"stylus-target"` @@ -135,6 +158,7 @@ func (c *Config) Validate() error { func ConfigAddOptions(prefix string, f *flag.FlagSet) { arbitrum.ConfigAddOptions(prefix+".rpc", f) + TxIndexerConfigAddOptions(prefix+".tx-indexer", f) SequencerConfigAddOptions(prefix+".sequencer", f) headerreader.AddOptions(prefix+".parent-chain-reader", f) BlockRecorderConfigAddOptions(prefix+".recording-database", f) @@ -144,7 +168,6 @@ func ConfigAddOptions(prefix string, f *flag.FlagSet) { TxPreCheckerConfigAddOptions(prefix+".tx-pre-checker", f) CachingConfigAddOptions(prefix+".caching", f) SyncMonitorConfigAddOptions(prefix+".sync-monitor", f) - f.Uint64(prefix+".tx-lookup-limit", ConfigDefault.TxLookupLimit, "retain the ability to lookup transactions by hash for the past N blocks (0 = all blocks)") f.Bool(prefix+".enable-prefetch-block", ConfigDefault.EnablePrefetchBlock, "enable prefetching of blocks") StylusTargetConfigAddOptions(prefix+".stylus-target", f) f.Uint64(prefix+".block-metadata-api-cache-size", ConfigDefault.BlockMetadataApiCacheSize, "size (in bytes) of lru cache storing the blockMetadata to service arb_getRawBlockMetadata") @@ -169,13 +192,13 @@ func LiveTracingConfigAddOptions(prefix string, f *flag.FlagSet) { var ConfigDefault = Config{ RPC: arbitrum.DefaultConfig, + TxIndexer: DefaultTxIndexerConfig, Sequencer: DefaultSequencerConfig, ParentChainReader: headerreader.DefaultConfig, RecordingDatabase: DefaultBlockRecorderConfig, ForwardingTarget: "", SecondaryForwardingTarget: []string{}, TxPreChecker: DefaultTxPreCheckerConfig, - TxLookupLimit: 126_230_400, // 1 year at 4 blocks per second Caching: DefaultCachingConfig, Forwarder: DefaultNodeForwarderConfig, EnablePrefetchBlock: true, @@ -300,7 +323,7 @@ func CreateExecutionNode( apis := []rpc.API{{ Namespace: "arb", Version: "1.0", - Service: NewArbAPI(txPublisher, bulkBlockMetadataFetcher), + Service: NewArbAPI(txPublisher, bulkBlockMetadataFetcher, execEngine), Public: false, }} apis = append(apis, rpc.API{ @@ -509,15 +532,17 @@ func (n *ExecutionNode) BlockNumberToMessageIndex(blockNum uint64) containers.Pr return containers.NewReadyPromise(n.ExecEngine.BlockNumberToMessageIndex(blockNum)) } -func (n *ExecutionNode) Maintenance() containers.PromiseInterface[struct{}] { - trieCapLimitBytes := arbmath.SaturatingUMul(uint64(n.ConfigFetcher().Caching.TrieCapLimit), 1024*1024) - err := n.ExecEngine.Maintenance(trieCapLimitBytes) - if err != nil { - return containers.NewReadyPromise(struct{}{}, err) - } +func (n *ExecutionNode) ShouldTriggerMaintenance() containers.PromiseInterface[bool] { + return containers.NewReadyPromise(n.ExecEngine.ShouldTriggerMaintenance(n.ConfigFetcher().Caching.TrieTimeLimitBeforeFlushMaintenance), nil) +} +func (n *ExecutionNode) MaintenanceStatus() containers.PromiseInterface[*execution.MaintenanceStatus] { + return containers.NewReadyPromise(n.ExecEngine.MaintenanceStatus(), nil) +} - err = n.ChainDB.Compact(nil, nil) - return containers.NewReadyPromise(struct{}{}, err) +func (n *ExecutionNode) TriggerMaintenance() containers.PromiseInterface[struct{}] { + trieCapLimitBytes := arbmath.SaturatingUMul(uint64(n.ConfigFetcher().Caching.TrieCapLimit), 1024*1024) + n.ExecEngine.TriggerMaintenance(trieCapLimitBytes) + return containers.NewReadyPromise(struct{}{}, nil) } func (n *ExecutionNode) Synced(ctx context.Context) bool { diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index e8b45adce8..7e6104aa58 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -50,13 +50,12 @@ var ( nonceCacheClearedCounter = metrics.NewRegisteredCounter("arb/sequencer/noncecache/cleared", nil) nonceFailureCacheSizeGauge = metrics.NewRegisteredGauge("arb/sequencer/noncefailurecache/size", nil) nonceFailureCacheOverflowCounter = metrics.NewRegisteredGauge("arb/sequencer/noncefailurecache/overflow", nil) - blockCreationTimer = metrics.NewRegisteredTimer("arb/sequencer/block/creation", nil) + blockCreationTimer = metrics.NewRegisteredHistogram("arb/sequencer/block/creation", nil, metrics.NewBoundedHistogramSample()) successfulBlocksCounter = metrics.NewRegisteredCounter("arb/sequencer/block/successful", nil) conditionalTxRejectedBySequencerCounter = metrics.NewRegisteredCounter("arb/sequencer/conditionaltx/rejected", nil) conditionalTxAcceptedBySequencerCounter = metrics.NewRegisteredCounter("arb/sequencer/conditionaltx/accepted", nil) l1GasPriceGauge = metrics.NewRegisteredGauge("arb/sequencer/l1gasprice", nil) callDataUnitsBacklogGauge = metrics.NewRegisteredGauge("arb/sequencer/calldataunitsbacklog", nil) - unusedL1GasChargeGauge = metrics.NewRegisteredGauge("arb/sequencer/unusedl1gascharge", nil) currentSurplusGauge = metrics.NewRegisteredGauge("arb/sequencer/currentsurplus", nil) expectedSurplusGauge = metrics.NewRegisteredGauge("arb/sequencer/expectedsurplus", nil) ) @@ -64,6 +63,7 @@ var ( type SequencerConfig struct { Enable bool `koanf:"enable"` MaxBlockSpeed time.Duration `koanf:"max-block-speed" reload:"hot"` + ReadFromTxQueueTimeout time.Duration `koanf:"read-from-tx-queue-timeout" reload:"hot"` MaxRevertGasReject uint64 `koanf:"max-revert-gas-reject" reload:"hot"` MaxAcceptableTimestampDelta time.Duration `koanf:"max-acceptable-timestamp-delta" reload:"hot"` SenderWhitelist []string `koanf:"sender-whitelist"` @@ -74,6 +74,7 @@ type SequencerConfig struct { MaxTxDataSize int `koanf:"max-tx-data-size" reload:"hot"` NonceFailureCacheSize int `koanf:"nonce-failure-cache-size" reload:"hot"` NonceFailureCacheExpiry time.Duration `koanf:"nonce-failure-cache-expiry" reload:"hot"` + ExpectedSurplusGasPriceMode string `koanf:"expected-surplus-gas-price-mode"` ExpectedSurplusSoftThreshold string `koanf:"expected-surplus-soft-threshold" reload:"hot"` ExpectedSurplusHardThreshold string `koanf:"expected-surplus-hard-threshold" reload:"hot"` EnableProfiling bool `koanf:"enable-profiling" reload:"hot"` @@ -123,15 +124,21 @@ func (c *SequencerConfig) Validate() error { return fmt.Errorf("sequencer sender whitelist entry \"%v\" is not a valid address", address) } } + if c.ExpectedSurplusGasPriceMode != "CalldataPrice" && + c.ExpectedSurplusGasPriceMode != "BlobPrice" && + c.ExpectedSurplusGasPriceMode != "CalldataPrice7623" { + return fmt.Errorf("undefined expected-surplus-gas-price-mode: %s", c.ExpectedSurplusGasPriceMode) + } + var err error if c.ExpectedSurplusSoftThreshold != "default" { if c.expectedSurplusSoftThreshold, err = strconv.Atoi(c.ExpectedSurplusSoftThreshold); err != nil { - return fmt.Errorf("invalid expected-surplus-soft-threshold value provided in batchposter config %w", err) + return fmt.Errorf("invalid expected-surplus-soft-threshold value provided in sequencer config %w", err) } } if c.ExpectedSurplusHardThreshold != "default" { if c.expectedSurplusHardThreshold, err = strconv.Atoi(c.ExpectedSurplusHardThreshold); err != nil { - return fmt.Errorf("invalid expected-surplus-hard-threshold value provided in batchposter config %w", err) + return fmt.Errorf("invalid expected-surplus-hard-threshold value provided in sequencer config %w", err) } } if c.expectedSurplusSoftThreshold < c.expectedSurplusHardThreshold { @@ -156,6 +163,9 @@ func (c *SequencerConfig) Validate() error { } } } + if c.ReadFromTxQueueTimeout >= c.MaxBlockSpeed { + log.Warn("Sequencer ReadFromTxQueueTimeout is higher than MaxBlockSpeed", "ReadFromTxQueueTimeout", c.ReadFromTxQueueTimeout, "MaxBlockSpeed", c.MaxBlockSpeed) + } return nil } @@ -164,6 +174,7 @@ type SequencerConfigFetcher func() *SequencerConfig var DefaultSequencerConfig = SequencerConfig{ Enable: false, MaxBlockSpeed: time.Millisecond * 250, + ReadFromTxQueueTimeout: time.Millisecond * 10, MaxRevertGasReject: 0, MaxAcceptableTimestampDelta: time.Hour, SenderWhitelist: []string{}, @@ -176,6 +187,7 @@ var DefaultSequencerConfig = SequencerConfig{ MaxTxDataSize: 95000, NonceFailureCacheSize: 1024, NonceFailureCacheExpiry: time.Second, + ExpectedSurplusGasPriceMode: "BlobPrice", ExpectedSurplusSoftThreshold: "default", ExpectedSurplusHardThreshold: "default", EnableProfiling: false, @@ -185,11 +197,11 @@ var DefaultSequencerConfig = SequencerConfig{ var DefaultDangerousConfig = DangerousConfig{ DisableSeqInboxMaxDataSizeCheck: false, - DisableBlobBaseFeeCheck: false, } func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultSequencerConfig.Enable, "act and post to l1 as sequencer") + f.Duration(prefix+".read-from-tx-queue-timeout", DefaultSequencerConfig.ReadFromTxQueueTimeout, "timeout for reading new messages") f.Duration(prefix+".max-block-speed", DefaultSequencerConfig.MaxBlockSpeed, "minimum delay between blocks (sets a maximum speed of block production)") f.Uint64(prefix+".max-revert-gas-reject", DefaultSequencerConfig.MaxRevertGasReject, "maximum gas executed in a revert for the sequencer to reject the transaction instead of posting it (anti-DOS)") f.Duration(prefix+".max-acceptable-timestamp-delta", DefaultSequencerConfig.MaxAcceptableTimestampDelta, "maximum acceptable time difference between the local time and the latest L1 block's timestamp") @@ -204,6 +216,7 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Int(prefix+".max-tx-data-size", DefaultSequencerConfig.MaxTxDataSize, "maximum transaction size the sequencer will accept") f.Int(prefix+".nonce-failure-cache-size", DefaultSequencerConfig.NonceFailureCacheSize, "number of transactions with too high of a nonce to keep in memory while waiting for their predecessor") f.Duration(prefix+".nonce-failure-cache-expiry", DefaultSequencerConfig.NonceFailureCacheExpiry, "maximum amount of time to wait for a predecessor before rejecting a tx with nonce too high") + f.String(prefix+".expected-surplus-gas-price-mode", DefaultSequencerConfig.ExpectedSurplusGasPriceMode, "gas price setting to be used in calculating estimated surplus. Allowed values- CalldataPrice, BlobPrice and CalldataPrice7523") f.String(prefix+".expected-surplus-soft-threshold", DefaultSequencerConfig.ExpectedSurplusSoftThreshold, "if expected surplus is lower than this value, warnings are posted") f.String(prefix+".expected-surplus-hard-threshold", DefaultSequencerConfig.ExpectedSurplusHardThreshold, "if expected surplus is lower than this value, new incoming transactions will be denied") f.Bool(prefix+".enable-profiling", DefaultSequencerConfig.EnableProfiling, "enable CPU profiling and tracing") @@ -884,14 +897,59 @@ func (s *Sequencer) handleInactive(ctx context.Context, queueItems []txQueueItem var sequencerInternalError = errors.New("sequencer internal error") -func (s *Sequencer) makeSequencingHooks() *arbos.SequencingHooks { - return &arbos.SequencingHooks{ - PreTxFilter: s.preTxFilter, - PostTxFilter: s.postTxFilter, - DiscardInvalidTxsEarly: true, - TxErrors: []error{}, - ConditionalOptionsForTx: nil, +type fullSequencingHooks struct { + arbos.SequencingHooks + queueItems []txQueueItem + sequencedQueueItemsCount int + sequencedTxsSizeSoFar int + maxSequencedTxsSize int +} + +// GetNextTx returns the next tx to be sequenced into a block while maintaining a running count of total block size so far, if adding a tx would exceed the allowed +// max-size, then GetNextTx signals to stop adding txs to the block and instead try the remaining txs in the next block +func (s *fullSequencingHooks) GetNextTx() (*types.Transaction, error) { + // This is not supposed to happen, if so we have a bug + if len(s.SequencingHooks.TxErrors) != s.sequencedQueueItemsCount { + return nil, fmt.Errorf("fullSequencingHooks: GetNextTx detected out of order request to sequence tx. hookTxErrors: %d, nextTxIdToBeSequenced: %d", len(s.SequencingHooks.TxErrors), s.sequencedQueueItemsCount) + } + if s.sequencedQueueItemsCount > 0 && s.TxErrors[s.sequencedQueueItemsCount-1] == nil { + s.sequencedTxsSizeSoFar += s.queueItems[s.sequencedQueueItemsCount-1].txSize + } + if s.sequencedQueueItemsCount >= len(s.queueItems) { + return nil, nil + } + if s.sequencedTxsSizeSoFar+s.queueItems[s.sequencedQueueItemsCount].txSize > s.maxSequencedTxsSize { + return nil, nil + } + s.sequencedQueueItemsCount += 1 + return s.queueItems[s.sequencedQueueItemsCount-1].tx, nil +} + +func (s *fullSequencingHooks) GetScheduledTx(txId int) (*types.Transaction, error) { + // This is not supposed to happen, if so we have a bug + if txId > s.sequencedQueueItemsCount { + return nil, fmt.Errorf("transaction queried for was not scheduled by the fullSequencingHooks. txId: %d, sequencedCount: %d", txId, s.sequencedQueueItemsCount) } + return s.queueItems[txId].tx, nil +} + +func (s *Sequencer) makeSequencingHooks(items []txQueueItem) *fullSequencingHooks { + res := &fullSequencingHooks{ + arbos.SequencingHooks{ + PreTxFilter: s.preTxFilter, + PostTxFilter: s.postTxFilter, + DiscardInvalidTxsEarly: true, + TxErrors: []error{}, + ConditionalOptionsForTx: nil, + }, + items, + 0, + 0, + s.config().MaxTxDataSize, + } + res.SequencingHooks.NextTxToSequence = res.GetNextTx + res.SequencingHooks.SequencedTx = res.GetScheduledTx + return res } func (s *Sequencer) expireNonceFailures() *time.Timer { @@ -905,13 +963,24 @@ func (s *Sequencer) expireNonceFailures() *time.Timer { if untilExpiry > 0 { return time.NewTimer(untilExpiry) } + + // Check queueCtx status before notifying client + queueItem := failure.queueItem + err := queueItem.ctx.Err() + if err != nil { + // queueCtx has already timed out, return that error + queueItem.returnResult(err) + } else { + // nonce-failure-cache-expiry timeout, return the original nonce error + queueItem.returnResult(failure.nonceErr) + } + s.nonceFailures.RemoveOldest() } } // There's no guarantee that returned tx nonces will be correct -func (s *Sequencer) precheckNonces(queueItems []txQueueItem, totalBlockSize int) []txQueueItem { - config := s.config() +func (s *Sequencer) precheckNonces(queueItems []txQueueItem) []txQueueItem { bc := s.execEngine.bc latestHeader := bc.CurrentBlock() latestState, err := bc.StateAt(latestHeader.Root) @@ -962,13 +1031,7 @@ func (s *Sequencer) precheckNonces(queueItems []txQueueItem, totalBlockSize int) if err != nil { revivingFailure.queueItem.returnResult(err) } else { - if arbmath.SaturatingAdd(totalBlockSize, revivingFailure.queueItem.txSize) > config.MaxTxDataSize { - // This tx would be too large to add to this block - s.txRetryQueue.Push(revivingFailure.queueItem) - } else { - nextQueueItem = &revivingFailure.queueItem - totalBlockSize += revivingFailure.queueItem.txSize - } + nextQueueItem = &revivingFailure.queueItem } } } else if txNonce < stateNonce || txNonce > pendingNonce { @@ -1004,7 +1067,6 @@ func (s *Sequencer) precheckNonces(queueItems []txQueueItem, totalBlockSize int) func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { var queueItems []txQueueItem - var totalBlockSize int defer func() { panicErr := recover() @@ -1036,7 +1098,15 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { } }() + var startOfReadingFromTxQueue time.Time + for { + if len(queueItems) == 1 { + startOfReadingFromTxQueue = time.Now() + } else if len(queueItems) > 1 && time.Since(startOfReadingFromTxQueue) > config.ReadFromTxQueueTimeout { + break + } + var queueItem txQueueItem if s.txRetryQueue.Len() > 0 { @@ -1115,7 +1185,7 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { queueItem.blockStamp, queueItem.blockStamp+config.Timeboost.QueueTimeoutInBlocks, ) - queueItem.returnResult(err) // this isnt read by anyone, so we log + queueItem.returnResult(err) // this isn't read by anyone, so we log log.Info("Error sequencing timeboost tx", "err", err) continue } @@ -1123,46 +1193,22 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { queueItem.returnResult(fmt.Errorf("%w: maxFeePerGas: %s baseFee: %s", core.ErrFeeCapTooLow, queueItem.tx.GasFeeCap(), lastBlock.BaseFee)) continue } - if totalBlockSize+queueItem.txSize > config.MaxTxDataSize { - // This tx would be too large to add to this batch - s.txRetryQueue.Push(queueItem) - // End the batch here to put this tx in the next one - break - } - totalBlockSize += queueItem.txSize queueItems = append(queueItems, queueItem) } s.nonceCache.Resize(config.NonceCacheSize) // Would probably be better in a config hook but this is basically free s.nonceCache.BeginNewBlock() - queueItems = s.precheckNonces(queueItems, totalBlockSize) - txes := make([]*types.Transaction, len(queueItems)) + queueItems = s.precheckNonces(queueItems) timeboostedTxs := make(map[common.Hash]struct{}) - hooks := s.makeSequencingHooks() + hooks := s.makeSequencingHooks(queueItems) hooks.ConditionalOptionsForTx = make([]*arbitrum_types.ConditionalOptions, len(queueItems)) - totalBlockSize = 0 // recompute the totalBlockSize to double check it for i, queueItem := range queueItems { - txes[i] = queueItem.tx - totalBlockSize = arbmath.SaturatingAdd(totalBlockSize, queueItem.txSize) hooks.ConditionalOptionsForTx[i] = queueItem.options if queueItem.isTimeboosted { timeboostedTxs[queueItem.tx.Hash()] = struct{}{} } } - if totalBlockSize > config.MaxTxDataSize { - for _, queueItem := range queueItems { - s.txRetryQueue.Push(queueItem) - } - log.Error( - "put too many transactions in a block", - "numTxes", len(queueItems), - "totalBlockSize", totalBlockSize, - "maxTxDataSize", config.MaxTxDataSize, - ) - return false - } - if s.handleInactive(ctx, queueItems) { return false } @@ -1202,21 +1248,27 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) { err error ) if config.EnableProfiling { - block, err = s.execEngine.SequenceTransactionsWithProfiling(header, txes, hooks, timeboostedTxs) + block, err = s.execEngine.SequenceTransactionsWithProfiling(header, &hooks.SequencingHooks, timeboostedTxs) } else { - block, err = s.execEngine.SequenceTransactions(header, txes, hooks, timeboostedTxs) + block, err = s.execEngine.SequenceTransactions(header, &hooks.SequencingHooks, timeboostedTxs) } elapsed := time.Since(start) - blockCreationTimer.Update(elapsed) + blockCreationTimer.Update(elapsed.Nanoseconds()) if elapsed >= time.Second*5 { var blockNum *big.Int if block != nil { blockNum = block.Number() } - log.Warn("took over 5 seconds to sequence a block", "elapsed", elapsed, "numTxes", len(txes), "success", block != nil, "l2Block", blockNum) + log.Warn("took over 5 seconds to sequence a block", "elapsed", elapsed, "numTxes", hooks.sequencedQueueItemsCount, "success", block != nil, "l2Block", blockNum) } - if err == nil && len(hooks.TxErrors) != len(txes) { - err = fmt.Errorf("unexpected number of error results: %v vs number of txes %v", len(hooks.TxErrors), len(txes)) + if err == nil { + if len(hooks.TxErrors) != hooks.sequencedQueueItemsCount { // This is not supposed to happen, if so we have a bug + err = fmt.Errorf("unexpected number of error results: %v vs number of txes %v", len(hooks.TxErrors), hooks.sequencedQueueItemsCount) + } else { + for i := hooks.sequencedQueueItemsCount; i < len(hooks.queueItems); i++ { + s.txRetryQueue.Push(hooks.queueItems[i]) + } + } } if errors.Is(err, execution.ErrRetrySequencer) { log.Warn("error sequencing transactions", "err", err) @@ -1348,35 +1400,47 @@ func (s *Sequencer) updateExpectedSurplus(ctx context.Context) (int64, error) { if err != nil { return 0, fmt.Errorf("error encountered getting latest header from l1reader while updating expectedSurplus: %w", err) } - l1GasPrice := header.BaseFee.Uint64() - if header.BlobGasUsed != nil && !s.config().Dangerous.DisableBlobBaseFeeCheck { - if header.ExcessBlobGas != nil { + l1GasPrice := header.BaseFee.Int64() + + // #nosec G115 + backlogCallDataUnits := int64(s.execEngine.backlogCallDataUnits()) + var backlogCost int64 // tx's cached calldata units are already scaled by TxDataNonZeroGasEIP2028 = 16, so we divide them by 16 while calculating cost for blobs and for EIP7623 pricing accordingly + switch s.config().ExpectedSurplusGasPriceMode { + case "CalldataPrice": + backlogCost = backlogCallDataUnits * header.BaseFee.Int64() + case "BlobPrice": + if s.config().Dangerous.DisableBlobBaseFeeCheck { + log.Warn("expected surplus calculation is set to use blob price but --execution.sequencer.dangerous.disable-blob-base-fee-check is set, falling back to calldata price model") + backlogCost = backlogCallDataUnits * header.BaseFee.Int64() + } else if header.BlobGasUsed == nil || header.ExcessBlobGas == nil { + log.Warn("expected surplus calculation is set to use blob price but latest parent chain header has BlobGasUsed or ExcessBlobGas as nil, falling back to calldata price model") + backlogCost = backlogCallDataUnits * header.BaseFee.Int64() + } else { blobFeePerByte, err := s.l1Reader.Client().BlobBaseFee(ctx) if err != nil { return 0, fmt.Errorf("error encountered getting blob base fee while updating expectedSurplus: %w", err) } blobFeePerByte.Mul(blobFeePerByte, blobTxBlobGasPerBlob) blobFeePerByte.Div(blobFeePerByte, usableBytesInBlob) - if l1GasPrice > blobFeePerByte.Uint64()/16 { - l1GasPrice = blobFeePerByte.Uint64() / 16 - } + l1GasPrice = blobFeePerByte.Int64() / 16 + backlogCost = (backlogCallDataUnits * blobFeePerByte.Int64()) / 16 } + case "CalldataPrice7623": + l1GasPrice = (header.BaseFee.Int64() * 40) / 16 + backlogCost = (backlogCallDataUnits * header.BaseFee.Int64() * 40) / 16 + default: + return 0, fmt.Errorf("unrecognized ExpectedSurplusGasPriceMode: %s", s.config().ExpectedSurplusGasPriceMode) } + surplus, err := s.execEngine.getL1PricingSurplus() if err != nil { return 0, fmt.Errorf("error encountered getting l1 pricing surplus while updating expectedSurplus: %w", err) } - // #nosec G115 - backlogL1GasCharged := int64(s.execEngine.backlogL1GasCharged()) - // #nosec G115 - backlogCallDataUnits := int64(s.execEngine.backlogCallDataUnits()) - // #nosec G115 - expectedSurplus := int64(surplus) + backlogL1GasCharged - backlogCallDataUnits*int64(l1GasPrice) + expectedSurplus := surplus - backlogCost + // update metrics - // #nosec G115 - l1GasPriceGauge.Update(int64(l1GasPrice)) + l1GasPriceGauge.Update(l1GasPrice) callDataUnitsBacklogGauge.Update(backlogCallDataUnits) - unusedL1GasChargeGauge.Update(backlogL1GasCharged) currentSurplusGauge.Update(surplus) expectedSurplusGauge.Update(expectedSurplus) config := s.config() diff --git a/execution/gethexec/stylus_tracer.go b/execution/gethexec/stylus_tracer.go index 9dd1711e17..e13e29c468 100644 --- a/execution/gethexec/stylus_tracer.go +++ b/execution/gethexec/stylus_tracer.go @@ -41,12 +41,12 @@ type HostioTraceInfo struct { Name string `json:"name"` // Arguments of the HostIO encoded as binary. - // For details about the encoding check the HostIO implemenation on + // For details about the encoding check the HostIO implementation on // arbitrator/wasm-libraries/user-host-trait. Args hexutil.Bytes `json:"args"` // Outputs of the HostIO encoded as binary. - // For details about the encoding check the HostIO implemenation on + // For details about the encoding check the HostIO implementation on // arbitrator/wasm-libraries/user-host-trait. Outs hexutil.Bytes `json:"outs"` diff --git a/execution/gethexec/wasmstorerebuilder.go b/execution/gethexec/wasmstorerebuilder.go index 0f0c169237..08941f8b14 100644 --- a/execution/gethexec/wasmstorerebuilder.go +++ b/execution/gethexec/wasmstorerebuilder.go @@ -52,7 +52,7 @@ func WriteToKeyValueStore[T any](store ethdb.KeyValueStore, key []byte, val T) e } // RebuildWasmStore function runs a loop looking at every codehash in diskDb, checking if its an activated stylus contract and -// saving it to wasm store if it doesnt already exists. When errored it logs them and silently returns +// saving it to wasm store if it doesn't already exists. When errored it logs them and silently returns // // It stores the status of rebuilding to wasm store by updating the codehash (of the latest successfully checked contract) in // RebuildingPositionKey after every second of work. @@ -67,6 +67,7 @@ func RebuildWasmStore(ctx context.Context, wasmStore ethdb.KeyValueStore, chainD if err := PopulateStylusTargetCache(targetConfig); err != nil { return fmt.Errorf("error populating stylus target cache: %w", err) } + targets := targetConfig.WasmTargets() latestHeader := l2Blockchain.CurrentBlock() // Attempt to get state at the start block when rebuilding commenced, if not available (in case of non-archival nodes) use latest state @@ -93,7 +94,7 @@ func RebuildWasmStore(ctx context.Context, wasmStore ethdb.KeyValueStore, chainD codeHash := common.BytesToHash(codeHashBytes) code := iter.Value() if state.IsStylusProgram(code) { - if err := programs.SaveActiveProgramToWasmStore(stateDb, codeHash, code, latestHeader.Time, l2Blockchain.Config().DebugMode(), rebuildingStartHeader.Time); err != nil { + if err := programs.SaveActiveProgramToWasmStore(stateDb, codeHash, code, latestHeader.Time, l2Blockchain.Config().DebugMode(), rebuildingStartHeader.Time, targets); err != nil { return fmt.Errorf("error while rebuilding of wasm store, aborting rebuilding: %w", err) } } diff --git a/execution/interface.go b/execution/interface.go index 48503f03aa..780584b9db 100644 --- a/execution/interface.go +++ b/execution/interface.go @@ -12,6 +12,10 @@ import ( "github.com/offchainlabs/nitro/util/containers" ) +type MaintenanceStatus struct { + IsRunning bool `json:"isRunning"` +} + type MessageResult struct { BlockHash common.Hash SendRoot common.Hash @@ -43,7 +47,9 @@ type ExecutionClient interface { SetFinalityData(ctx context.Context, safeFinalityData *arbutil.FinalityData, finalizedFinalityData *arbutil.FinalityData, validatedFinalityData *arbutil.FinalityData) containers.PromiseInterface[struct{}] MarkFeedStart(to arbutil.MessageIndex) containers.PromiseInterface[struct{}] - Maintenance() containers.PromiseInterface[struct{}] + TriggerMaintenance() containers.PromiseInterface[struct{}] + ShouldTriggerMaintenance() containers.PromiseInterface[bool] + MaintenanceStatus() containers.PromiseInterface[*MaintenanceStatus] Start(ctx context.Context) error StopAndWait() diff --git a/execution/nodeInterface/NodeInterface.go b/execution/nodeInterface/NodeInterface.go index eb37518636..e6ff04ad14 100644 --- a/execution/nodeInterface/NodeInterface.go +++ b/execution/nodeInterface/NodeInterface.go @@ -216,7 +216,7 @@ func (n NodeInterface) EstimateRetryableTicket( } // ArbitrumSubmitRetryableTx is unsigned so the following won't panic - msg, err := core.TransactionToMessage(types.NewTx(submitTx), types.NewArbitrumSigner(nil), nil, core.MessageGasEstimationMode) + msg, err := core.TransactionToMessage(types.NewTx(submitTx), types.NewArbitrumSigner(nil), nil, core.NewMessageGasEstimationContext()) if err != nil { return err } @@ -531,7 +531,7 @@ func (n NodeInterface) GasEstimateL1Component( if !ok { return 0, nil, nil, errors.New("failed to cast to stateDB") } - msg := args.ToMessage(evm.Context.BaseFee, randomGas, n.header, sdb, core.MessageEthcallMode, true, true) + msg := args.ToMessage(evm.Context.BaseFee, randomGas, n.header, sdb, core.NewMessageEthcallContext(), true, true) pricing := c.State.L1PricingState() l1BaseFeeEstimate, err := pricing.PricePerUnit() @@ -591,7 +591,7 @@ func (n NodeInterface) GasEstimateComponents( if !ok { return 0, 0, nil, nil, errors.New("failed to cast to stateDB") } - msg := args.ToMessage(evm.Context.BaseFee, gasCap, n.header, sdb, core.MessageGasEstimationMode, true, true) + msg := args.ToMessage(evm.Context.BaseFee, gasCap, n.header, sdb, core.NewMessageGasEstimationContext(), true, true) brotliCompressionLevel, err := c.State.BrotliCompressionLevel() if err != nil { return 0, 0, nil, nil, fmt.Errorf("failed to get brotli compression level: %w", err) @@ -608,7 +608,7 @@ func (n NodeInterface) GasEstimateComponents( } // Compute the fee paid for L1 in L2 terms - gasForL1 := arbos.GetPosterGas(c.State, baseFee, core.MessageGasEstimationMode, feeForL1) + gasForL1 := arbos.GetPosterGas(c.State, baseFee, core.NewMessageGasEstimationContext(), feeForL1) return total, gasForL1, baseFee, l1BaseFeeEstimate, nil } diff --git a/execution/nodeInterface/virtual-contracts.go b/execution/nodeInterface/virtual-contracts.go index 99ff1d0346..2c77b00d0d 100644 --- a/execution/nodeInterface/virtual-contracts.go +++ b/execution/nodeInterface/virtual-contracts.go @@ -106,6 +106,7 @@ func init() { } res := &ExecutionResult{ UsedGas: msg.GasLimit - gasLeft, + MaxUsedGas: msg.GasLimit - gasLeft, Err: nil, ReturnData: output, ScheduledTxes: nil, @@ -136,7 +137,7 @@ func init() { } posterCost, _ := state.L1PricingState().PosterDataCost(msg, l1pricing.BatchPosterAddress, brotliCompressionLevel) // Use estimate mode because this is used to raise the gas cap, so we don't want to underestimate. - return arbos.GetPosterGas(state, header.BaseFee, core.MessageGasEstimationMode, posterCost), nil + return arbos.GetPosterGas(state, header.BaseFee, core.NewMessageGasEstimationContext(), posterCost), nil } core.GetArbOSSpeedLimitPerSecond = func(statedb *state.StateDB) (uint64, error) { diff --git a/fastcache b/fastcache deleted file mode 160000 index cd4f9b8d15..0000000000 --- a/fastcache +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd4f9b8d15b0b22bc628cbbf1dba11540d023904 diff --git a/go-ethereum b/go-ethereum index fcd6eade1b..8a5871262c 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit fcd6eade1b62c94eb144a40fc9a7f0419e9e6674 +Subproject commit 8a5871262c201f06169a0e5422b58aa482f0c4fb diff --git a/go.mod b/go.mod index 331e6858f9..3044bbaa24 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/offchainlabs/nitro -go 1.23.0 - -replace github.com/VictoriaMetrics/fastcache => ./fastcache +go 1.24.5 replace github.com/ethereum/go-ethereum => ./go-ethereum @@ -22,6 +20,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.64.1 github.com/cavaliergopher/grab/v3 v3.0.1 github.com/ccoveille/go-safecast v1.1.0 + github.com/celestiaorg/nmt v0.20.0 + github.com/celestiaorg/rsmt2d v0.11.0 github.com/cockroachdb/pebble v1.1.2 github.com/codeclysm/extract/v3 v3.0.2 github.com/dgraph-io/badger/v4 v4.2.0 @@ -42,7 +42,7 @@ require ( github.com/knadh/koanf v1.4.0 github.com/mailru/easygo v0.0.0-20190618140210-3c14a0dc985f github.com/mattn/go-sqlite3 v1.14.22 - github.com/mitchellh/mapstructure v1.4.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/offchainlabs/bold v0.0.3-0.20250313062923-4b76649f2abc github.com/pkg/errors v0.9.1 github.com/r3labs/diff/v3 v3.0.1 @@ -51,11 +51,12 @@ require ( github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.10.0 github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 + github.com/tendermint/tendermint v0.34.29 github.com/wealdtech/go-merkletree v1.0.0 go.uber.org/automaxprocs v1.5.2 golang.org/x/crypto v0.36.0 golang.org/x/sync v0.12.0 - golang.org/x/sys v0.31.0 + golang.org/x/sys v0.34.0 golang.org/x/term v0.30.0 golang.org/x/tools v0.29.0 google.golang.org/api v0.187.0 @@ -68,7 +69,10 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.2 // indirect cloud.google.com/go/compute/metadata v0.3.0 // indirect cloud.google.com/go/iam v1.1.8 // indirect + github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 // indirect github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect + github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect + github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect github.com/ethereum/go-verkle v0.2.2 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.1 // indirect @@ -104,7 +108,7 @@ require ( require ( github.com/DataDog/zstd v1.5.2 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/VictoriaMetrics/fastcache v1.13.0 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.5 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect @@ -121,17 +125,16 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.31.4 // indirect github.com/aws/smithy-go v1.22.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.17.0 // indirect + github.com/bits-and-blooms/bitset v1.20.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/consensys/bavard v0.1.22 // indirect - github.com/consensys/gnark-crypto v0.14.0 // indirect + github.com/consensys/bavard v0.1.27 // indirect + github.com/consensys/gnark-crypto v0.16.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect - github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect @@ -140,7 +143,6 @@ require ( github.com/dlclark/regexp2 v1.7.0 // indirect github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 // indirect github.com/dustin/go-humanize v1.0.0 // indirect - github.com/ethereum/c-kzg-4844 v1.0.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gammazero/deque v0.2.1 // indirect github.com/gdamore/encoding v1.0.0 // indirect @@ -150,7 +152,7 @@ require ( github.com/gobwas/pool v0.2.1 // indirect github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/golang/snappy v1.0.0 // indirect github.com/google/flatbuffers v1.12.1 // indirect github.com/google/go-github/v62 v62.0.0 github.com/google/pprof v0.0.0-20231023181126-ff6d637d2a7b // indirect @@ -166,6 +168,8 @@ require ( github.com/juju/errors v0.0.0-20181118221551-089d3ea4e4d5 // indirect github.com/juju/loggo v0.0.0-20180524022052-584905176618 // indirect github.com/klauspost/compress v1.17.2 // indirect + github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect @@ -187,7 +191,7 @@ require ( github.com/rhnvrm/simples3 v0.6.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - github.com/rs/cors v1.7.0 // indirect + github.com/rs/cors v1.8.2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect github.com/supranational/blst v0.3.14 // indirect @@ -202,6 +206,11 @@ require ( go.opencensus.io v0.24.0 // indirect golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.22.0 + golang.org/x/oauth2 v0.27.0 rsc.io/tmplfunc v0.0.3 // indirect ) + +replace ( + github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 + github.com/tendermint/tendermint => github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 +) diff --git a/go.sum b/go.sum index 3812f81fae..17032d71b3 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VictoriaMetrics/fastcache v1.13.0 h1:AW4mheMR5Vd9FkAPUv+NH6Nhw+fmbTMGMsNAoA/+4G0= +github.com/VictoriaMetrics/fastcache v1.13.0/go.mod h1:hHXhl4DA2fTL2HTZDJFXWgW0LNjo6B+4aj2Wmng3TjU= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis/v2 v2.32.1 h1:Bz7CciDnYSaa0mX5xODh6GUITRSx+cVhjNoOR4JssBo= @@ -89,8 +91,8 @@ github.com/aws/smithy-go v1.22.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxY github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= -github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU= +github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -99,11 +101,18 @@ github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIH github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/ccoveille/go-safecast v1.1.0 h1:iHKNWaZm+OznO7Eh6EljXPjGfGQsSfa6/sxPlIEKO+g= github.com/ccoveille/go-safecast v1.1.0/go.mod h1:QqwNjxQ7DAqY0C721OIO9InMk9zCwcsO7tnRuHytad8= +github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29 h1:Fd7ymPUzExPGNl2gZw4i5S74arMw+iDHLE78M/cCxl4= +github.com/celestiaorg/celestia-core v1.29.0-tm-v0.34.29/go.mod h1:xrICN0PBhp3AdTaZ8q4wS5Jvi32V02HNjaC2EsWiEKk= +github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4 h1:CJdIpo8n5MFP2MwK0gSRcOVlDlFdQJO1p+FqdxYzmvc= +github.com/celestiaorg/merkletree v0.0.0-20210714075610-a84dc3ddbbe4/go.mod h1:fzuHnhzj1pUygGz+1ZkB3uQbEUL4htqCGJ4Qs2LwMZA= +github.com/celestiaorg/nmt v0.20.0 h1:9i7ultZ8Wv5ytt8ZRaxKQ5KOOMo4A2K2T/aPGjIlSas= +github.com/celestiaorg/nmt v0.20.0/go.mod h1:Oz15Ub6YPez9uJV0heoU4WpFctxazuIhKyUtaYNio7E= +github.com/celestiaorg/rsmt2d v0.11.0 h1:lcto/637WyTEZR3dLRoNvyuExfnUbxvdvKi3qz/2V4k= +github.com/celestiaorg/rsmt2d v0.11.0/go.mod h1:6Y580I3gVr0+OVFfW6m2JTwnCCmvW3WfbwSLfuT+HCA= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -130,12 +139,14 @@ github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAK github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= github.com/codeclysm/extract/v3 v3.0.2 h1:sB4LcE3Php7LkhZwN0n2p8GCwZe92PEQutdbGURf5xc= github.com/codeclysm/extract/v3 v3.0.2/go.mod h1:NKsw+hqua9H+Rlwy/w/3Qgt9jDonYEgB6wJu+25eOKw= -github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= -github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= -github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= -github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= +github.com/consensys/bavard v0.1.27 h1:j6hKUrGAy/H+gpNrpLU3I26n1yc+VMGmd6ID5+gAhOs= +github.com/consensys/bavard v0.1.27/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= +github.com/consensys/gnark-crypto v0.16.0 h1:8Dl4eYmUWK9WmlP1Bj6je688gBRJCJbT8Mw4KoTAawo= +github.com/consensys/gnark-crypto v0.16.0/go.mod h1:Ke3j06ndtPTVvo++PhGNgvm+lgpLvzbcE2MqljY7diU= github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-eth-kzg v1.3.0 h1:05GrhASN9kDAidaFJOda6A4BEvgvuXbazXg/0E3OOdI= +github.com/crate-crypto/go-eth-kzg v1.3.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= @@ -150,6 +161,8 @@ github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5il github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/deepmap/oapi-codegen v1.8.2 h1:SegyeYGcdi0jLLrpbCMoJxnUUn8GBXHsvr4rbzjuhfU= +github.com/deepmap/oapi-codegen v1.8.2/go.mod h1:YLgSKSDv/bZQB7N4ws6luhozi3cEdRktEqrX88CvjIw= github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= @@ -174,8 +187,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= -github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/ethereum/c-kzg-4844/v2 v2.1.0 h1:gQropX9YFBhl3g4HYhwE70zq3IHFRgbbNPw0Shwzf5w= +github.com/ethereum/c-kzg-4844/v2 v2.1.0/go.mod h1:TC48kOKjJKPbN7C++qIgt0TJzZ70QznYR7Ob+WXl57E= github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -184,12 +197,16 @@ github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4 github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk= +github.com/ferranbt/fastssz v0.1.2/go.mod h1:X5UPrE2u1UJjxHA8X54u04SBwdAQjG2sFtWs39YxyWs= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI= +github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= @@ -222,8 +239,6 @@ github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484 h1:XC9N1eiAyO1z github.com/gobwas/ws-examples v0.0.0-20190625122829-a9e8908d9484/go.mod h1:5nDZF4afNA1S7ZKcBXCMvDo4nuCTp1931DND7/W4aXo= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -236,6 +251,8 @@ github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -248,8 +265,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= @@ -330,6 +347,12 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/influxdata/influxdb-client-go/v2 v2.12.2 h1:uYABKdrEKlYm+++qfKdbgaHKBPmoWR5wpbmj6MBB/2g= +github.com/influxdata/influxdb-client-go/v2 v2.12.2/go.mod h1:YteV91FiQxRdccyJ2cHvj2f/5sq4y4Njqu1fQzsQCOU= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c h1:qSHzRbhzK8RdXOsAdfDgO49TtqC1oZ+acxPrkfTxcCs= +github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= +github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -355,6 +378,10 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= +github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= +github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/knadh/koanf v1.4.0 h1:/k0Bh49SqLyLNfte9r6cvuZWrApOQhglOmhIU3L/zDw= github.com/knadh/koanf v1.4.0/go.mod h1:1cfH5223ZeZUOs8FU2UdTmaNfHpqgtjV0+NHjRO43gs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -390,6 +417,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -399,8 +428,9 @@ github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go. github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -426,8 +456,11 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7 h1:oYW+YCJ1pachXTQmzR3rNLYGGz4g/UgFcjb28p/viDM= +github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= @@ -462,6 +495,8 @@ github.com/r3labs/diff/v3 v3.0.1 h1:CBKqf3XmNRHXKmdU7mZP1w7TV0pDyVCis1AUHtA4Xtg= github.com/r3labs/diff/v3 v3.0.1/go.mod h1:f1S9bourRbiM66NskseyUdo0fTmEE0qKrikYJX63dgo= github.com/redis/go-redis/v9 v9.6.3 h1:8Dr5ygF1QFXRxIH/m3Xg9MMG1rS8YCtAgosrsewT6i0= github.com/redis/go-redis/v9 v9.6.3/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1 h1:OHEc+q5iIAXpqiqFKeLpu5NwTIkVXUs48vFMwzqpqY4= +github.com/regen-network/protobuf v1.3.3-alpha.regen.1/go.mod h1:2DjTFR1HhMQhiWC5sZ4OhQ3+NtdbZ6oBDKQwq5Ou+FI= github.com/rhnvrm/simples3 v0.6.1 h1:H0DJwybR6ryQE+Odi9eqkHuzjYAeJgtGcGtuBwOhsH8= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= github.com/rivo/tview v0.0.0-20240307173318-e804876934a1 h1:bWLHTRekAy497pE7+nXSuzXwwFHI0XauRzz6roUvY+s= @@ -474,8 +509,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.8.2 h1:KCooALfAYGs415Cwu5ABvv9n9509fSiG5SQJn/AQo4U= +github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -505,6 +540,12 @@ github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= @@ -526,6 +567,11 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +gitlab.com/NebulousLabs/errors v0.0.0-20171229012116-7ead97ef90b8/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= +gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975 h1:L/ENs/Ar1bFzUeKx6m3XjlmBgIUlykX9dzvp5k9NGxc= +gitlab.com/NebulousLabs/errors v0.0.0-20200929122200-06c536cf6975/go.mod h1:ZkMZ0dpQyWwlENaeZVBiQRjhMEZvk6VTXquzl3FOFP8= +gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40 h1:dizWJqTWjwyD8KGcMOwgrkqu1JIkofYgKkmDeNE7oAs= +gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= @@ -547,6 +593,7 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20180214000028-650f4a345ab4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200109152110-61a87790db17/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= @@ -574,6 +621,7 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -587,8 +635,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -620,7 +668,6 @@ golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -634,8 +681,8 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -685,6 +732,7 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200324203455-a04cca1dde73/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d h1:PksQg4dV6Sem3/HkBX+Ltq8T0ke0PKIRBNBatoDTVls= google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= @@ -698,6 +746,7 @@ google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= diff --git a/nitro-testnode b/nitro-testnode index f695cb8981..69f6ad6fe9 160000 --- a/nitro-testnode +++ b/nitro-testnode @@ -1 +1 @@ -Subproject commit f695cb8981fda310ad688fd27c12d52c073f7aec +Subproject commit 69f6ad6fe9df468faad21826b37560b29f075086 diff --git a/precompiles/ArbAddressTable_test.go b/precompiles/ArbAddressTable_test.go index 0729154dd6..9620092537 100644 --- a/precompiles/ArbAddressTable_test.go +++ b/precompiles/ArbAddressTable_test.go @@ -167,9 +167,9 @@ func newMockEVMForTesting() *vm.EVM { return newMockEVMForTestingWithVersion(nil) } -func newMockEVMForTestingWithVersionAndRunMode(version *uint64, runMode core.MessageRunMode) *vm.EVM { +func newMockEVMForTestingWithVersionAndRunMode(version *uint64, runCtx *core.MessageRunContext) *vm.EVM { evm := newMockEVMForTestingWithVersion(version) - evm.ProcessingHook = arbos.NewTxProcessor(evm, &core.Message{TxRunMode: runMode}) + evm.ProcessingHook = arbos.NewTxProcessor(evm, &core.Message{TxRunContext: runCtx}) return evm } diff --git a/precompiles/ArbOwner_test.go b/precompiles/ArbOwner_test.go index 5afb51cf44..c978988a0e 100644 --- a/precompiles/ArbOwner_test.go +++ b/precompiles/ArbOwner_test.go @@ -209,7 +209,7 @@ func TestArbOwner(t *testing.T) { } func TestArbOwnerSetChainConfig(t *testing.T) { - evm := newMockEVMForTestingWithVersionAndRunMode(nil, core.MessageGasEstimationMode) + evm := newMockEVMForTestingWithVersionAndRunMode(nil, core.NewMessageGasEstimationContext()) caller := common.BytesToAddress(crypto.Keccak256([]byte{})[:20]) tracer := util.NewTracingInfo(evm, testhelpers.RandomAddress(), types.ArbosAddress, util.TracingDuringEVM) state, err := arbosState.OpenArbosState(evm.StateDB, burn.NewSystemBurner(tracer, false)) diff --git a/precompiles/ArbWasm.go b/precompiles/ArbWasm.go index f7be50aa44..e864e49059 100644 --- a/precompiles/ArbWasm.go +++ b/precompiles/ArbWasm.go @@ -34,14 +34,14 @@ type ArbWasm struct { // Compile a wasm program with the latest instrumentation func (con ArbWasm) ActivateProgram(c ctx, evm mech, value huge, program addr) (uint16, huge, error) { debug := evm.ChainConfig().DebugMode() - runMode := c.txProcessor.RunMode() + runCtx := c.txProcessor.RunContext() programs := c.State.Programs() // charge a fixed cost up front to begin activation if err := c.Burn(1659168); err != nil { return 0, nil, err } - version, codeHash, moduleHash, dataFee, takeAllGas, err := programs.ActivateProgram(evm, program, runMode, debug) + version, codeHash, moduleHash, dataFee, takeAllGas, err := programs.ActivateProgram(evm, program, runCtx, debug) if takeAllGas { _ = c.BurnOut() } diff --git a/precompiles/ArbWasmCache.go b/precompiles/ArbWasmCache.go index 3408d6266c..dbdd8077a1 100644 --- a/precompiles/ArbWasmCache.go +++ b/precompiles/ArbWasmCache.go @@ -57,12 +57,12 @@ func (con ArbWasmCache) setProgramCached(c ctx, evm mech, address addr, codehash return err } debugMode := evm.ChainConfig().DebugMode() - txRunMode := c.txProcessor.RunMode() + runCtx := c.txProcessor.RunContext() emitEvent := func() error { return con.UpdateProgramCache(c, evm, c.caller, codehash, cached) } return programs.SetProgramCached( - emitEvent, evm.StateDB, codehash, address, cached, evm.Context.Time, params, txRunMode, debugMode, + emitEvent, evm.StateDB, codehash, address, cached, evm.Context.Time, params, runCtx, debugMode, ) } diff --git a/precompiles/precompile.go b/precompiles/precompile.go index 77050aa677..5fa74e3006 100644 --- a/precompiles/precompile.go +++ b/precompiles/precompile.go @@ -631,11 +631,6 @@ func Precompiles() map[addr]ArbosPrecompile { arbos.InternalTxStartBlockMethodID = ArbosActs.GetMethodID("StartBlock") arbos.InternalTxBatchPostingReportMethodID = ArbosActs.GetMethodID("BatchPostingReport") - for _, contract := range contracts { - precompile := contract.Precompile() - arbosState.PrecompileMinArbOSVersions[precompile.address] = precompile.arbosVersion - } - ArbOwner.methodsByName["SetCalldataPriceIncrease"].arbosVersion = params.ArbosVersion_40 ArbOwnerPublic.methodsByName["IsCalldataPriceIncreaseEnabled"].arbosVersion = params.ArbosVersion_40 @@ -647,11 +642,16 @@ func Precompiles() map[addr]ArbosPrecompile { ArbOwner.methodsByName["IsNativeTokenOwner"].arbosVersion = params.ArbosVersion_41 ArbOwner.methodsByName["GetAllNativeTokenOwners"].arbosVersion = params.ArbosVersion_41 - _, ArbNativeTokenManager := MakePrecompile(pgen.ArbNativeTokenManagerMetaData, &ArbNativeTokenManager{Address: types.ArbNativeTokenManagerAddress}) + ArbNativeTokenManager := insert(MakePrecompile(pgen.ArbNativeTokenManagerMetaData, &ArbNativeTokenManager{Address: types.ArbNativeTokenManagerAddress})) ArbNativeTokenManager.arbosVersion = params.ArbosVersion_41 ArbNativeTokenManager.methodsByName["MintNativeToken"].arbosVersion = params.ArbosVersion_41 ArbNativeTokenManager.methodsByName["BurnNativeToken"].arbosVersion = params.ArbosVersion_41 - insert(ArbNativeTokenManager.address, ArbNativeTokenManager) + + // this should be executed after all precompiles have been inserted + for _, contract := range contracts { + precompile := contract.Precompile() + arbosState.PrecompileMinArbOSVersions[precompile.address] = precompile.arbosVersion + } return contracts } diff --git a/pubsub/consumer.go b/pubsub/consumer.go index 2442f9dd62..2de9c105a3 100644 --- a/pubsub/consumer.go +++ b/pubsub/consumer.go @@ -39,8 +39,12 @@ var TestConsumerConfig = ConsumerConfig{ var ErrAlreadySet = errors.New("redis key already set") func ConsumerConfigAddOptions(prefix string, f *pflag.FlagSet) { - f.Duration(prefix+".response-entry-timeout", DefaultConsumerConfig.ResponseEntryTimeout, "timeout for response entry") - f.Duration(prefix+".idletime-to-autoclaim", DefaultConsumerConfig.IdletimeToAutoclaim, "After a message spends this amount of time in PEL (Pending Entries List i.e claimed by another consumer but not Acknowledged) it will be allowed to be autoclaimed by other consumers") + ConsumerConfigAddOptionsWithDefaults(prefix, f, DefaultConsumerConfig) +} + +func ConsumerConfigAddOptionsWithDefaults(prefix string, f *pflag.FlagSet, defaultConfig ConsumerConfig) { + f.Duration(prefix+".response-entry-timeout", defaultConfig.ResponseEntryTimeout, "timeout for response entry") + f.Duration(prefix+".idletime-to-autoclaim", defaultConfig.IdletimeToAutoclaim, "After a message spends this amount of time in PEL (Pending Entries List i.e claimed by another consumer but not Acknowledged) it will be allowed to be autoclaimed by other consumers") } // Consumer implements a consumer for redis stream provides heartbeat to @@ -199,7 +203,7 @@ func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Req }).Result(); err != nil { log.Error("Error claiming message, it might be possible that other consumers might pick this request", "msgID", messages[0].ID) } else if len(ids) == 0 { - log.Warn("XClaimJustID returned empty response when indicating hearbeat", "msgID", messages[0].ID) + log.Warn("XClaimJustID returned empty response when indicating heartbeat", "msgID", messages[0].ID) } else if len(ids) > 1 { log.Error("XClaimJustID returned response with more than entry", "msgIDs", ids) } @@ -207,7 +211,7 @@ func (c *Consumer[Request, Response]) Consume(ctx context.Context) (*Message[Req case <-ackNotifier: return case <-ctx.Done(): - log.Info("Context done while claiming message to indicate hearbeat", "messageID", messages[0].ID, "error", ctx.Err().Error()) + log.Info("Context done while claiming message to indicate heartbeat", "messageID", messages[0].ID, "error", ctx.Err().Error()) if c.StopWaiter.GetParentContext().Err() == nil { // Proceeding to set the Idle time of message to IdletimeToAutoclaim to allow it to be picked by other consumers if err := c.client.Do(c.StopWaiter.GetParentContext(), "XCLAIM", c.redisStream, c.redisGroup, c.id, 0, messages[0].ID, "IDLE", c.cfg.IdletimeToAutoclaim.Milliseconds()).Err(); err != nil { diff --git a/pubsub/producer.go b/pubsub/producer.go index 3714f8b483..dbdc8ff213 100644 --- a/pubsub/producer.go +++ b/pubsub/producer.go @@ -1,10 +1,10 @@ // Package pubsub implements publisher/subscriber model (one to many). // During normal operation, publisher returns "Promise" when publishing a -// message, which will return resposne from consumer when awaited. +// message, which will return response from consumer when awaited. // If the consumer processing the request becomes inactive, message is // re-inserted (if EnableReproduce flag is enabled), and will be picked up by // another consumer. -// We are assuming here that keeepAliveTimeout is set to some sensible value +// We are assuming here that keepAliveTimeout is set to some sensible value // and once consumer becomes inactive, it doesn't activate without restart. package pubsub @@ -196,8 +196,8 @@ func (p *Producer[Request, Response]) clearMessages(ctx context.Context) time.Du if err != nil { log.Error("error getting PEL data from xpending, xtrimming is disabled", "err", err) } - // XDEL on consumer side already deletes acked messages (mark as deleted) but doesnt claim the memory back, XTRIM helps in claiming this memory in normal conditions - // pelData might be outdated when we do the xtrim, but thats ok as the messages are also being trimmed by other producers + // XDEL on consumer side already deletes acked messages (mark as deleted) but doesn't claim the memory back, XTRIM helps in claiming this memory in normal conditions + // pelData might be outdated when we do the xtrim, but that's ok as the messages are also being trimmed by other producers if pelData != nil && pelData.Lower != "" { trimmed, trimErr := p.client.XTrimMinID(ctx, p.redisStream, pelData.Lower).Result() log.Debug("trimming", "xTrimMinID", pelData.Lower, "trimmed", trimmed, "trim-err", trimErr) @@ -212,15 +212,15 @@ func (p *Producer[Request, Response]) clearMessages(ctx context.Context) time.Du MinIdle: 0, Messages: []string{pelData.Lower}, }).Err(); err != nil { - log.Error("error claiming PEL's lower message thats past its TTL", "msgID", pelData.Lower, "err", err) + log.Error("error claiming PEL's lower message that's past its TTL", "msgID", pelData.Lower, "err", err) return 5 * p.cfg.CheckResultInterval } if _, err := p.client.XAck(ctx, p.redisStream, p.redisGroup, pelData.Lower).Result(); err != nil { - log.Error("error acking PEL's lower message thats past its TTL", "msgID", pelData.Lower, "err", err) + log.Error("error acking PEL's lower message that's past its TTL", "msgID", pelData.Lower, "err", err) return 5 * p.cfg.CheckResultInterval } if _, err := p.client.XDel(ctx, p.redisStream, pelData.Lower).Result(); err != nil { - log.Error("error deleting PEL's lower message thats past its TTL", "msgID", pelData.Lower, "err", err) + log.Error("error deleting PEL's lower message that's past its TTL", "msgID", pelData.Lower, "err", err) return 5 * p.cfg.CheckResultInterval } return 0 diff --git a/safe-smart-account b/safe-smart-account index 192c7dc672..dc437e8fba 160000 --- a/safe-smart-account +++ b/safe-smart-account @@ -1 +1 @@ -Subproject commit 192c7dc67290940fcbc75165522bb86a37187069 +Subproject commit dc437e8fba8b4805d76bcbd1c668c9fd3d1e83be diff --git a/scripts/build-brotli.sh b/scripts/build-brotli.sh index ede2eccfd7..83c5be949f 100755 --- a/scripts/build-brotli.sh +++ b/scripts/build-brotli.sh @@ -68,14 +68,14 @@ while getopts "n:s:t:c:D:wldhf" option; do esac done -if ! $BUILD_WASM && ! $BUILD_LOCAL && ! $BUILD_SOFT; then +if ! $BUILD_WASM && ! $BUILD_LOCAL && ! $BUILD_SOFTFLOAT; then usage exit fi if [ ! -d "$TARGET_DIR" ]; then - mkdir -p "${TARGET_DIR}lib" - ln -s "lib" "${TARGET_DIR}lib64" # Fedora build + mkdir -p "${TARGET_DIR}/lib" + ln -s "lib" "${TARGET_DIR}/lib64" # Fedora build fi TARGET_DIR_ABS=$(cd -P "$TARGET_DIR"; pwd) diff --git a/scripts/check-build.sh b/scripts/check-build.sh index 5cc6ac0abe..62da03de0a 100755 --- a/scripts/check-build.sh +++ b/scripts/check-build.sh @@ -8,6 +8,20 @@ YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +node_version_needed="v24" +rust_version_needed="1.83.0" +golangci_lint_version_needed="2.3.0" + +if [[ -f go.mod ]]; then + go_version_needed=$(grep "^go " go.mod | awk '{print $2}') +else + if [[ -f ../go.mod ]]; then + go_version_needed=$(grep "^go " ../go.mod | awk '{print $2}') + else + go_version_needed="unknown" + fi +fi + # Documentation link for installation instructions INSTALLATION_DOCS_URL="Refer to https://docs.arbitrum.io/run-arbitrum-node/nitro/build-nitro-locally for installation." @@ -22,7 +36,7 @@ EXIT_CODE=0 OS=$(uname -s) echo -e "${BLUE}Detected OS: $OS${NC}" -# Step 1: Check Docker Installation +# Check Docker Installation if command_exists docker; then echo -e "${GREEN}Docker is installed.${NC}" else @@ -30,7 +44,7 @@ else EXIT_CODE=1 fi -# Step 2: Check if Docker service is running +# Check if Docker service is running if [[ "$OS" == "Linux" ]] && ! pidof dockerd >/dev/null; then echo -e "${YELLOW}Docker service is not running on Linux. Start it with: sudo service docker start${NC}" EXIT_CODE=1 @@ -41,7 +55,7 @@ else echo -e "${GREEN}Docker service is running.${NC}" fi -# Step 3: Check the version tag +# Check the version tag VERSION_TAG=$(git tag --points-at HEAD | sed '/-/!s/$/_/' | sort -rV | sed 's/_$//' | head -n 1 | grep ^ || git show -s --pretty=%D | sed 's/, /\n/g' | grep -v '^origin/' | grep -v '^grafted\|HEAD\|master\|main$' || echo "") if [[ -z "${VERSION_TAG}" ]]; then echo -e "${YELLOW}Untagged version of Nitro checked out, may not be fully tested.${NC}" @@ -51,33 +65,38 @@ fi # Check if submodules are properly initialized and updated if git submodule status | grep -qE '^-|\+'; then - echo -e "${YELLOW}Submodules are not properly initialized or updated. Run: git submodule update --init --recursive --force${NC}" + echo -e "${YELLOW}Submodules are not properly initialized or updated. Run: git submodule update --init --recursive${NC}" EXIT_CODE=1 else echo -e "${GREEN}All submodules are properly initialized and up to date.${NC}" fi -# Step 4: Check if Nitro Docker Image is built +# Check if Nitro Docker Image is built if docker images | grep -q "nitro-node"; then echo -e "${GREEN}Nitro Docker image is built.${NC}" else echo -e "${YELLOW}Nitro Docker image is not built. Build it using: docker build . --tag nitro-node${NC}" fi -# Step 5: Check prerequisites for building binaries +# Check prerequisites for building binaries +prerequisites=(git go curl clang make cmake npm wasm2wat wasm-ld yarn gotestsum python3) if [[ "$OS" == "Linux" ]]; then - prerequisites=(git curl make cmake npm golang clang make gotestsum wasm2wat wasm-ld python3 yarn) + prerequisites+=() else - prerequisites=(git curl make cmake npm go golangci-lint wasm2wat clang wasm-ld gotestsum yarn) + prerequisites+=() fi for pkg in "${prerequisites[@]}"; do - EXISTS=$(command_exists "$pkg") + if command_exists "$pkg"; then + exists=true + else + exists=false + fi [[ "$pkg" == "make" ]] && pkg="build-essential" [[ "$pkg" == "wasm2wat" ]] && pkg="wabt" [[ "$pkg" == "clang" ]] && pkg="llvm" [[ "$pkg" == "wasm-ld" ]] && pkg="lld" - if $EXISTS; then + if $exists; then # There is no way to check for wabt / llvm directly, since they install multiple tools # So instead, we check for wasm2wat and clang, which are part of wabt and llvm respectively # and if they are installed, we assume wabt / llvm is installed else we ask the user to install wabt / llvm @@ -89,23 +108,23 @@ for pkg in "${prerequisites[@]}"; do fi done -# Step 6: Check Node.js version -if command_exists node && node -v | grep -q "v18"; then - echo -e "${GREEN}Node.js version 18 is installed.${NC}" +# Check Node.js version +if command_exists node && node -v | grep -q "$node_version_needed"; then + echo -e "${GREEN}Node.js version $node_version_needed is installed.${NC}" else - echo -e "${RED}Node.js version 18 not installed.${NC}" + echo -e "${RED}Node.js version $node_version_needed not installed.${NC}" EXIT_CODE=1 fi -# Step 7a: Check Rust version -if command_exists rustc && rustc --version | grep -q "1.83.0"; then - echo -e "${GREEN}Rust version 1.83.0 is installed.${NC}" +# Check Rust version +if command_exists rustc && rustc --version | grep -q "$rust_version_needed"; then + echo -e "${GREEN}Rust version $rust_version_needed is installed.${NC}" else - echo -e "${RED}Rust version 1.83.0 is not installed.${NC}" + echo -e "${RED}Rust version $rust_version_needed is not installed.${NC}" EXIT_CODE=1 fi -# Step 7b: Check Rust nightly toolchain +# Check Rust nightly toolchain if rustup toolchain list | grep -q "nightly"; then echo -e "${GREEN}Rust nightly toolchain is installed.${NC}" else @@ -113,8 +132,7 @@ else EXIT_CODE=1 fi -# Step 8: Check Go version -go_version_needed=$(grep "^go " go.mod | awk '{print $2}') +# Check Go version if command_exists go && go version | grep -q "$go_version_needed"; then echo -e "${GREEN}Go version $go_version_needed is installed.${NC}" else @@ -122,7 +140,15 @@ else EXIT_CODE=1 fi -# Step 9: Check Foundry installation +# Check Go Linter version +if command_exists golangci-lint && golangci-lint version | grep -q "$golangci_lint_version_needed"; then + echo -e "${GREEN}golangci-lint version $golangci_lint_version_needed is installed.${NC}" +else + echo -e "${RED}golangci-lint version $golangci_lint_version_needed not installed.${NC}" + EXIT_CODE=1 +fi + +# Check Foundry installation if command_exists foundryup; then echo -e "${GREEN}Foundry is installed.${NC}" else diff --git a/scripts/download-machine.sh b/scripts/download-machine.sh index 3022c350a0..8e93f16854 100755 --- a/scripts/download-machine.sh +++ b/scripts/download-machine.sh @@ -5,10 +5,11 @@ mkdir "$2" ln -sfT "$2" latest cd "$2" echo "$2" > module-root.txt -url_base="https://github.com/OffchainLabs/nitro/releases/download/$1" +url_org="${3:-OffchainLabs}" +url_base="https://github.com/$url_org/nitro/releases/download/$1" wget "$url_base/machine.wavm.br" status_code="$(curl -LI "$url_base/replay.wasm" -so /dev/null -w '%{http_code}')" if [ "$status_code" -ne 404 ]; then wget "$url_base/replay.wasm" -fi +fi \ No newline at end of file diff --git a/solgen/gen.go b/solgen/gen.go index 878a90ce77..38c6e64769 100644 --- a/solgen/gen.go +++ b/solgen/gen.go @@ -12,7 +12,7 @@ import ( "runtime" "strings" - "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/abigen" ) type HardHatArtifact struct { @@ -98,6 +98,10 @@ func main() { continue } + if strings.Contains(path, "precompiles") { + continue + } + dir, file := filepath.Split(path) dir, _ = filepath.Split(dir[:len(dir)-1]) _, module := filepath.Split(dir[:len(dir)-1]) @@ -155,6 +159,7 @@ func main() { if err := json.Unmarshal(data, &artifact); err != nil { log.Fatal("failed to parse contract", name, err) } + fmt.Printf("Contract name: %v\n", name) yulModInfo.addArtifact(HardHatArtifact{ ContractName: name, Abi: artifact.Abi, @@ -218,6 +223,34 @@ func main() { }) } + precompilesFilePaths, err := filepath.Glob(filepath.Join(parent, "contracts-local", "out", "precompiles", "*.sol", "*.json")) + if err != nil { + log.Fatal(err) + } + precompilesModInfo := modules["precompilesgen"] + if precompilesModInfo == nil { + precompilesModInfo = &moduleInfo{} + modules["precompilesgen"] = precompilesModInfo + } + for _, path := range precompilesFilePaths { + _, file := filepath.Split(path) + name := file[:len(file)-5] + + data, err := os.ReadFile(path) + if err != nil { + log.Fatal("could not read", path, "for contract", name, err) + } + artifact := FoundryArtifact{} + if err := json.Unmarshal(data, &artifact); err != nil { + log.Fatal("failed to parse contract", name, err) + } + precompilesModInfo.addArtifact(HardHatArtifact{ + ContractName: name, + Abi: artifact.Abi, + Bytecode: artifact.Bytecode.Object, + }) + } + // add upgrade executor module which is not compiled locally, but imported from 'nitro-contracts' dependencies upgExecutorPath := filepath.Join(parent, "contracts", "node_modules", "@offchainlabs", "upgrade-executor", "build", "contracts", "src", "UpgradeExecutor.sol", "UpgradeExecutor.json") _, err = os.Stat(upgExecutorPath) @@ -241,13 +274,12 @@ func main() { for module, info := range modules { - code, err := bind.Bind( + code, err := abigen.Bind( info.contractNames, info.abis, info.bytecodes, nil, module, - bind.LangGo, nil, nil, ) diff --git a/staker/block_validator.go b/staker/block_validator.go index 069f513a04..9eead7a056 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -68,7 +68,7 @@ type BlockValidator struct { nextCreateStartGS validator.GoGlobalState nextCreatePrevDelayed uint64 - // can only be accessed from from validation thread or if holding reorg-write + // can only be accessed from validation thread or if holding reorg-write lastValidGS validator.GoGlobalState legacyValidInfo *legacyLastBlockValidatedDbInfo @@ -309,7 +309,7 @@ func (s *validationStatus) replaceStatus(old, new valStatusField) bool { return s.Status.CompareAndSwap(uint32(old), uint32(new)) } -// gets how many miliseconds last step took, and starts measuring a new step +// gets how many milliseconds last step took, and starts measuring a new step func (s *validationStatus) profileStep() int64 { start := s.profileTS s.profileTS = time.Now().UnixMilli() @@ -394,7 +394,7 @@ func NewBlockValidator( streamer.SetBlockValidator(ret) inbox.SetBlockValidator(ret) if config().MemoryFreeLimit != "" { - limtchecker, err := resourcemanager.NewCgroupsMemoryLimitCheckerIfSupported(config().memoryFreeLimit) + limitchecker, err := resourcemanager.NewCgroupsMemoryLimitCheckerIfSupported(config().memoryFreeLimit) if err != nil { if config().MemoryFreeLimit == "default" { log.Warn("Cgroups V1 or V2 is unsupported, memory-free-limit feature inside block-validator is disabled") @@ -402,7 +402,7 @@ func NewBlockValidator( return nil, fmt.Errorf("failed to create MemoryFreeLimitChecker, Cgroups V1 or V2 is unsupported") } } else { - ret.MemoryFreeLimitChecker = limtchecker + ret.MemoryFreeLimitChecker = limitchecker } } return ret, nil @@ -623,7 +623,7 @@ func (v *BlockValidator) SetCurrentWasmModuleRoot(hash common.Hash) error { return nil } return fmt.Errorf( - "unexpected wasmModuleRoot! cannot validate! found %v , current %v, pending %v", + "unexpected wasmModuleRoot! cannot validate! found %v, current %v, pending %v", hash, v.currentWasmModuleRoot, v.pendingWasmModuleRoot, ) } diff --git a/staker/bold/bold_staker.go b/staker/bold/bold_staker.go index f69e7ce964..d05738478d 100644 --- a/staker/bold/bold_staker.go +++ b/staker/bold/bold_staker.go @@ -16,7 +16,9 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" protocol "github.com/offchainlabs/bold/chain-abstraction" @@ -24,18 +26,24 @@ import ( challengemanager "github.com/offchainlabs/bold/challenge-manager" boldtypes "github.com/offchainlabs/bold/challenge-manager/types" l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" - "github.com/offchainlabs/bold/solgen/go/challengeV2gen" - boldrollup "github.com/offchainlabs/bold/solgen/go/rollupgen" "github.com/offchainlabs/bold/util" "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + boldrollup "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker" legacystaker "github.com/offchainlabs/nitro/staker/legacy" + "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" ) +var ( + boldStakerBalanceGauge = metrics.NewRegisteredGaugeFloat64("arb/staker/balance", nil) + boldStakerAmountStakedGauge = metrics.NewRegisteredGauge("arb/staker/amount_staked", nil) +) + var assertionCreatedId common.Hash func init() { @@ -195,13 +203,14 @@ type BOLDStaker struct { blockValidator *staker.BlockValidator rollupAddress common.Address l1Reader *headerreader.HeaderReader - client protocol.ChainBackend + client *util.BackendWrapper callOpts bind.CallOpts wallet legacystaker.ValidatorWalletInterface stakedNotifiers []legacystaker.LatestStakedNotifier confirmedNotifiers []legacystaker.LatestConfirmedNotifier inboxTracker staker.InboxTrackerInterface inboxStreamer staker.TransactionStreamerInterface + fatalErr chan<- error } func NewBOLDStaker( @@ -221,6 +230,7 @@ func NewBOLDStaker( inboxTracker staker.InboxTrackerInterface, inboxStreamer staker.TransactionStreamerInterface, inboxReader staker.InboxReaderInterface, + fatalErr chan<- error, ) (*BOLDStaker, error) { if err := config.Validate(); err != nil { return nil, err @@ -243,6 +253,7 @@ func NewBOLDStaker( confirmedNotifiers: confirmedNotifiers, inboxTracker: inboxTracker, inboxStreamer: inboxStreamer, + fatalErr: fatalErr, }, nil } @@ -303,6 +314,10 @@ func (b *BOLDStaker) Start(ctxIn context.Context) { confirmedMsgCount, confirmedGlobalState, err := b.getLatestState(ctx, true) if err != nil { log.Error("staker: error checking latest confirmed", "err", err) + if errors.Is(err, staker.ErrGlobalStateNotInChain) { + b.fatalErr <- err + } + return b.config.AssertionPostingInterval } agreedMsgCount, agreedGlobalState, err := b.getLatestState(ctx, false) @@ -327,10 +342,43 @@ func (b *BOLDStaker) Start(ctxIn context.Context) { notifier.UpdateLatestConfirmed(confirmedMsgCount, *confirmedGlobalState) } } + err = b.updateStakerBalanceMetric(ctx) + if err != nil { + log.Warn("error updating staker balance metric", "err", err) + } return b.config.AssertionPostingInterval }) } +func (b *BOLDStaker) updateStakerBalanceMetric(ctx context.Context) error { + walletAddressOrZero := b.wallet.AddressOrZero() + if walletAddressOrZero != (common.Address{}) { + rollupUserLogic, err := boldrollup.NewRollupUserLogic(b.rollupAddress, b.client) + if err != nil { + return fmt.Errorf("error creating rollup user logic: %w", err) + } + amountStaked, err := rollupUserLogic.AmountStaked(&bind.CallOpts{Context: ctx}, walletAddressOrZero) + if err != nil { + return fmt.Errorf("error getting amount staked: %w", err) + } + boldStakerAmountStakedGauge.Update(arbmath.BigDivByUint(amountStaked, params.Ether).Int64()) + } else { + boldStakerAmountStakedGauge.Update(0) + } + + txSenderAddress := b.wallet.TxSenderAddress() + if txSenderAddress != nil { + balance, err := b.client.BalanceAt(ctx, *txSenderAddress, nil) + if err != nil { + return fmt.Errorf("error getting balance for %v: %w", txSenderAddress, err) + } + boldStakerBalanceGauge.Update(arbmath.BalancePerEther(balance)) + } else { + boldStakerBalanceGauge.Update(0) + } + return nil +} + func (b *BOLDStaker) getLatestState(ctx context.Context, confirmed bool) (arbutil.MessageIndex, *validator.GoGlobalState, error) { var globalState protocol.GoGlobalState var err error @@ -353,7 +401,8 @@ func (b *BOLDStaker) getLatestState(ctx context.Context, confirmed bool) (arbuti if errors.Is(err, staker.ErrGlobalStateNotInChain) { return 0, nil, fmt.Errorf("latest %s assertion of %v not yet in our node: %w", assertionType, globalState, err) } - return 0, nil, fmt.Errorf("error getting message count: %w", err) + log.Error("error getting message count", "err", err) + return 0, nil, nil } if !caughtUp { @@ -363,7 +412,8 @@ func (b *BOLDStaker) getLatestState(ctx context.Context, confirmed bool) (arbuti processedCount, err := b.inboxStreamer.GetProcessedMessageCount() if err != nil { - return 0, nil, err + log.Error("error getting processed message count", "err", err) + return 0, nil, nil } if processedCount < count { diff --git a/staker/bold/bold_state_provider.go b/staker/bold/bold_state_provider.go index 6d135067ae..d534bbf4fd 100644 --- a/staker/bold/bold_state_provider.go +++ b/staker/bold/bold_state_provider.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" protocol "github.com/offchainlabs/bold/chain-abstraction" @@ -373,7 +372,7 @@ func (s *BOLDStateProvider) CollectMachineHashes( if err != nil { return nil, err } - input, err := entry.ToInput([]ethdb.WasmTarget{rawdb.TargetWavm}) + input, err := entry.ToInput([]rawdb.WasmTarget{rawdb.TargetWavm}) if err != nil { return nil, err } @@ -491,7 +490,7 @@ func (s *BOLDStateProvider) CollectProof( if err != nil { return nil, err } - input, err := entry.ToInput([]ethdb.WasmTarget{rawdb.TargetWavm}) + input, err := entry.ToInput([]rawdb.WasmTarget{rawdb.TargetWavm}) if err != nil { return nil, err } diff --git a/staker/legacy/challenge_manager.go b/staker/legacy/challenge_manager.go index 7db7cc2653..fe6def90f9 100644 --- a/staker/legacy/challenge_manager.go +++ b/staker/legacy/challenge_manager.go @@ -4,6 +4,7 @@ package legacystaker import ( + "bytes" "context" "encoding/binary" "errors" @@ -16,11 +17,11 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbutil" + celestiaTypes "github.com/offchainlabs/nitro/daprovider/celestia/types" "github.com/offchainlabs/nitro/solgen/go/challenge_legacy_gen" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/validator" @@ -34,6 +35,8 @@ var initiatedChallengeID common.Hash var challengeBisectedID common.Hash var executionChallengeBegunID common.Hash +const ReadInboxMessage uint16 = 0x8021 + func init() { parsedChallengeManagerABI, err := challenge_legacy_gen.ChallengeManagerMetaData.GetAbi() if err != nil { @@ -450,6 +453,11 @@ func (m *ChallengeManager) IssueOneStepProof( if err != nil { return nil, fmt.Errorf("error getting OSP from challenge %v backend at step %v: %w", m.challengeIndex, position, err) } + proof, err = m.getDAProof(ctx, proof) + if err != nil { + return nil, fmt.Errorf("error getting DA Proof for OSP for challenge %v at step %v: %w", m.challengeIndex, position, err) + } + return m.challengeCore.con.OneStepProveExecution( m.challengeCore.auth, m.challengeCore.challengeIndex, @@ -473,7 +481,7 @@ func (m *ChallengeManager) createExecutionBackend(ctx context.Context, step uint if err != nil { return fmt.Errorf("error creating validation entry for challenge %v msg %v for execution challenge: %w", m.challengeIndex, initialCount, err) } - input, err := entry.ToInput([]ethdb.WasmTarget{rawdb.TargetWavm}) + input, err := entry.ToInput([]rawdb.WasmTarget{rawdb.TargetWavm}) if err != nil { return fmt.Errorf("error getting validation entry input of challenge %v msg %v: %w", m.challengeIndex, initialCount, err) } @@ -584,3 +592,57 @@ func (m *ChallengeManager) Act(ctx context.Context) (*types.Transaction, error) machineStepCount, ) } + +func (m *ChallengeManager) getDAProof(ctx context.Context, proof []byte) ([]byte, error) { + // get the proof's opcode + opCodeBytes := proof[len(proof)-2:] + opCode := binary.BigEndian.Uint16(opCodeBytes) + // remove opcode bytes + proof = proof[:len(proof)-2] + if opCode == ReadInboxMessage { + messageType := proof[len(proof)-1] + // remove inbox message type byte + proof = proof[:len(proof)-1] + if messageType == 0x0 { + // Read the last 8 bytes as a uint64 to get our batch number + batchNumBytes := proof[len(proof)-8:] + batchNum := binary.BigEndian.Uint64(batchNumBytes) + batchData, _, err := m.validator.InboxReader().GetSequencerMessageBytes(ctx, batchNum) + if err != nil { + log.Error("Couldn't get sequencer message bytes", "err", err) + return nil, err + } + + buf := bytes.NewBuffer(batchData[40:]) + + header, err := buf.ReadByte() + if err != nil { + log.Error("Couldn't deserialize Celestia header byte", "err", err) + return nil, nil + } + daProof := []byte{} + if celestiaTypes.IsCelestiaMessageHeaderByte(header) { + log.Info("Fetching da proof for Celestia", "batchNum", batchNum) + blobBytes := buf.Bytes() + + var celestiaReader celestiaTypes.CelestiaReader + for _, dapReader := range m.validator.DapReaders() { + switch reader := dapReader.(type) { + case celestiaTypes.CelestiaReader: + celestiaReader = reader + } + } + daProof, err = celestiaReader.GetProof(ctx, blobBytes) + if err != nil { + return nil, err + } + } + + // remove batch number from proof + proof = proof[:len(proof)-8] + proof = append(proof, daProof...) + } + } + + return proof, nil +} diff --git a/staker/legacy/fast_confirm.go b/staker/legacy/fast_confirm.go index 049c8b928c..0dfde07cd8 100644 --- a/staker/legacy/fast_confirm.go +++ b/staker/legacy/fast_confirm.go @@ -243,6 +243,6 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex } return true, nil } - log.Info("Not enough Safe tx approvals yet to fast confirm", "safeHash", common.BytesToHash(safeTxHash[:]), "approved", approvedHashCount, "threshold", f.threshold, "self", f.wallet.Address()) + log.Info("Not enough Safe tx approvals yet to fast confirm", "safeHash", common.BytesToHash(safeTxHash[:]).Hex(), "approved", approvedHashCount, "threshold", f.threshold, "self", f.wallet.Address()) return false, nil } diff --git a/staker/legacy/mock_machine_test.go b/staker/legacy/mock_machine_test.go index 37f22435b7..71a63e691f 100644 --- a/staker/legacy/mock_machine_test.go +++ b/staker/legacy/mock_machine_test.go @@ -106,6 +106,10 @@ func (m *IncorrectMachine) ProveNextStep() []byte { return m.inner.ProveNextStep() } +func (m *IncorrectMachine) GetNextOpcode() uint16 { + return m.inner.GetNextOpcode() +} + func (m *IncorrectMachine) Freeze() { m.inner.Freeze() } diff --git a/staker/legacy/staker.go b/staker/legacy/staker.go index a803f7bddd..bb98fb6cad 100644 --- a/staker/legacy/staker.go +++ b/staker/legacy/staker.go @@ -376,6 +376,9 @@ func (s *Staker) Initialize(ctx context.Context) error { // #nosec G115 stakerLatestStakedNodeGauge.Update(int64(latestStaked)) if latestStaked == 0 { + if s.config().EnableFastConfirmation { + return errors.New("staker: fast confirmation enabled at genesis") + } return nil } diff --git a/staker/multi_protocol/multi_protocol_staker.go b/staker/multi_protocol/multi_protocol_staker.go index 2d9931d72e..0ad2694933 100644 --- a/staker/multi_protocol/multi_protocol_staker.go +++ b/staker/multi_protocol/multi_protocol_staker.go @@ -11,8 +11,8 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" - boldrollup "github.com/offchainlabs/bold/solgen/go/rollupgen" "github.com/offchainlabs/nitro/solgen/go/bridgegen" + boldrollup "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker" boldstaker "github.com/offchainlabs/nitro/staker/bold" legacystaker "github.com/offchainlabs/nitro/staker/legacy" @@ -52,6 +52,7 @@ type MultiProtocolStaker struct { inboxTracker staker.InboxTrackerInterface inboxStreamer staker.TransactionStreamerInterface inboxReader staker.InboxReaderInterface + fatalErr chan<- error } func NewMultiProtocolStaker( @@ -121,6 +122,7 @@ func NewMultiProtocolStaker( inboxTracker: inboxTracker, inboxStreamer: inboxStreamer, inboxReader: inboxReader, + fatalErr: fatalErr, }, nil } @@ -262,6 +264,7 @@ func (m *MultiProtocolStaker) setupBoldStaker( m.inboxTracker, m.inboxStreamer, m.inboxReader, + m.fatalErr, ) if err != nil { return err diff --git a/staker/stateless_block_validator.go b/staker/stateless_block_validator.go index 65f835b30f..cdb47716ce 100644 --- a/staker/stateless_block_validator.go +++ b/staker/stateless_block_validator.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" @@ -145,7 +146,7 @@ type validationEntry struct { DelayedMsg []byte } -func (e *validationEntry) ToInput(stylusArchs []ethdb.WasmTarget) (*validator.ValidationInput, error) { +func (e *validationEntry) ToInput(stylusArchs []rawdb.WasmTarget) (*validator.ValidationInput, error) { if e.Stage != Ready { return nil, errors.New("cannot create input from non-ready entry") } @@ -154,7 +155,7 @@ func (e *validationEntry) ToInput(stylusArchs []ethdb.WasmTarget) (*validator.Va HasDelayedMsg: e.HasDelayedMsg, DelayedMsgNr: e.DelayedMsgNr, Preimages: e.Preimages, - UserWasms: make(map[ethdb.WasmTarget]map[common.Hash][]byte, len(e.UserWasms)), + UserWasms: make(map[rawdb.WasmTarget]map[common.Hash][]byte, len(e.UserWasms)), BatchInfo: e.BatchInfo, DelayedMsg: e.DelayedMsg, StartState: e.Start, @@ -299,10 +300,18 @@ func (v *StatelessBlockValidator) ExecutionSpawners() []validator.ExecutionSpawn return v.execSpawners } +func (v *StatelessBlockValidator) InboxReader() InboxReaderInterface { + return v.inboxReader +} + func (v *StatelessBlockValidator) BOLDExecutionSpawners() []validator.BOLDExecutionSpawner { return v.boldExecSpawners } +func (v *StatelessBlockValidator) DapReaders() []daprovider.Reader { + return v.dapReaders +} + func (v *StatelessBlockValidator) readFullBatch(ctx context.Context, batchNum uint64) (bool, *FullBatchInfo, error) { batchCount, err := v.inboxTracker.GetBatchCount() if err != nil { @@ -537,7 +546,7 @@ func (v *StatelessBlockValidator) ValidateResult( return true, &entry.End, nil } -func (v *StatelessBlockValidator) ValidationInputsAt(ctx context.Context, pos arbutil.MessageIndex, targets ...ethdb.WasmTarget) (server_api.InputJSON, error) { +func (v *StatelessBlockValidator) ValidationInputsAt(ctx context.Context, pos arbutil.MessageIndex, targets ...rawdb.WasmTarget) (server_api.InputJSON, error) { entry, err := v.CreateReadyValidationEntry(ctx, pos) if err != nil { return server_api.InputJSON{}, err diff --git a/statetransfer/jsondatareader.go b/statetransfer/jsondatareader.go index 54605cecd7..f669604732 100644 --- a/statetransfer/jsondatareader.go +++ b/statetransfer/jsondatareader.go @@ -166,7 +166,7 @@ func (r *JsonInitDataReader) GetAddressTableReader() (AddressReader, error) { }, nil } -type JsonAccountDataReaderr struct { +type JsonAccountDataReader struct { JsonListReader } @@ -178,7 +178,7 @@ type AccountInitializationInfoJson struct { ClassicHash common.Hash } -func (r *JsonAccountDataReaderr) GetNext() (*AccountInitializationInfo, error) { +func (r *JsonAccountDataReader) GetNext() (*AccountInitializationInfo, error) { if !r.More() { return nil, errNoMore } @@ -206,7 +206,7 @@ func (r *JsonInitDataReader) GetAccountDataReader() (AccountDataReader, error) { if err != nil { return nil, err } - return &JsonAccountDataReaderr{ + return &JsonAccountDataReader{ JsonListReader: listreader, }, nil } diff --git a/statetransfer/memdatareader.go b/statetransfer/memdatareader.go index a17bf7d83d..49ee238822 100644 --- a/statetransfer/memdatareader.go +++ b/statetransfer/memdatareader.go @@ -78,11 +78,11 @@ func (r *MemoryInitDataReader) GetAddressTableReader() (AddressReader, error) { }, nil } -type MemoryAccountDataReaderr struct { +type MemoryAccountDataReader struct { FieldReader } -func (r *MemoryAccountDataReaderr) GetNext() (*AccountInitializationInfo, error) { +func (r *MemoryAccountDataReader) GetNext() (*AccountInitializationInfo, error) { if !r.More() { return nil, errNoMore } @@ -91,7 +91,7 @@ func (r *MemoryAccountDataReaderr) GetNext() (*AccountInitializationInfo, error) } func (r *MemoryInitDataReader) GetAccountDataReader() (AccountDataReader, error) { - return &MemoryAccountDataReaderr{ + return &MemoryAccountDataReader{ FieldReader: FieldReader{ m: r, length: len(r.d.Accounts), diff --git a/system_tests/aliasing_test.go b/system_tests/aliasing_test.go index 441461de20..5c463bbf5b 100644 --- a/system_tests/aliasing_test.go +++ b/system_tests/aliasing_test.go @@ -19,7 +19,6 @@ import ( ) func TestAliasing(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/arbos_upgrade_test.go b/system_tests/arbos_upgrade_test.go index c9d7d990b6..1694546a59 100644 --- a/system_tests/arbos_upgrade_test.go +++ b/system_tests/arbos_upgrade_test.go @@ -14,6 +14,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/arbosState" @@ -89,14 +90,12 @@ func checkArbOSVersion(t *testing.T, testClient *TestClient, expectedVersion uin state, err := arbosState.OpenSystemArbosState(statedb, nil, true) Require(t, err, "could not open ArbOS state", scenario) if state.ArbOSVersion() != expectedVersion { - t.Errorf("%s: expected ArbOS version %v, got %v", scenario, expectedVersion, state.ArbOSVersion()) + t.Fatalf("%s: expected ArbOS version %v, got %v", scenario, expectedVersion, state.ArbOSVersion()) } } func TestArbos11To32UpgradeWithMcopy(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -198,9 +197,122 @@ func TestArbos11To32UpgradeWithMcopy(t *testing.T) { } } -func TestArbos11To32UpgradeWithCalldata(t *testing.T) { - t.Parallel() +func TestArbNativeTokenManagerInArbos32To41Upgrade(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + initialVersion := uint64(32) + finalVersion := uint64(41) + + arbOSInit := ¶ms.ArbOSInit{ + NativeTokenSupplyManagementEnabled: true, + } + builder := NewNodeBuilder(ctx). + DefaultConfig(t, true). + WithArbOSVersion(initialVersion). + WithArbOSInit(arbOSInit) + builder.execConfig.TxPreChecker.Strictness = gethexec.TxPreCheckerStrictnessLikelyCompatible + cleanup := builder.Build(t) + defer cleanup() + + authOwner := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + authOwner.GasLimit = 32000000 + + // makes Owner a chain owner + arbDebug, err := precompilesgen.NewArbDebug(types.ArbDebugAddress, builder.L2.Client) + Require(t, err) + tx, err := arbDebug.BecomeChainOwner(&authOwner) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, builder.L2.Client, tx) + Require(t, err) + + checkArbOSVersion(t, builder.L2, initialVersion, "") + + arbOwner, err := precompilesgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + + callOpts := &bind.CallOpts{Context: ctx} + + nativeTokenOwnerName := "NativeTokenOwner" + builder.L2Info.GenerateAccount(nativeTokenOwnerName) + nativeTokenOwnerAddr := builder.L2Info.GetAddress(nativeTokenOwnerName) + + // checks that IsNativeTokenOwner doesn't exist in ArbOwner before upgrade + _, err = arbOwner.IsNativeTokenOwner(callOpts, nativeTokenOwnerAddr) + if err == nil || !strings.Contains(err.Error(), "execution reverted") { + t.Fatalf("expected IsNativeTokenOwner to fail before upgrade, got %v", err) + } + + // schedule arbos upgrade + tx, err = arbOwner.ScheduleArbOSUpgrade(&authOwner, finalVersion, 0) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // checks upgrade worked + var data []byte + for i := range 10 { + for range 100 { + data = append(data, byte(i)) + } + } + tx = builder.L2Info.PrepareTx("Owner", "Owner", builder.L2Info.TransferGas, big.NewInt(1e12), data) + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + checkArbOSVersion(t, builder.L2, finalVersion, "") + + // checks that IsNativeTokenOwner works after upgrade + _, err = arbOwner.IsNativeTokenOwner(callOpts, nativeTokenOwnerAddr) + Require(t, err) + + // adds native token owner + tx, err = arbOwner.AddNativeTokenOwner(&authOwner, nativeTokenOwnerAddr) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + // funds the native token owner + tx = builder.L2Info.PrepareTx("Owner", nativeTokenOwnerName, builder.L2Info.TransferGas, big.NewInt(500000000000000000), nil) + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + arbNativeTokenManager, err := precompilesgen.NewArbNativeTokenManager(types.ArbNativeTokenManagerAddress, builder.L2.Client) + Require(t, err) + + // checks minting + nativeTokenOwnerABI, err := precompilesgen.ArbNativeTokenManagerMetaData.GetAbi() + Require(t, err) + mintTopic := nativeTokenOwnerABI.Events["NativeTokenMinted"].ID + authNativeTokenOwner := builder.L2Info.GetDefaultTransactOpts(nativeTokenOwnerName, ctx) + authNativeTokenOwner.GasLimit = 32000000 + toMint := big.NewInt(100) + tx, err = arbNativeTokenManager.MintNativeToken(&authNativeTokenOwner, toMint) + Require(t, err) + receipt, err := builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + mintLogged := false + for _, log := range receipt.Logs { + if log.Topics[0] == mintTopic { + mintLogged = true + parsedLog, err := arbNativeTokenManager.ParseNativeTokenMinted(*log) + Require(t, err) + if parsedLog.To != nativeTokenOwnerAddr { + t.Fatal("expected mint to be to", nativeTokenOwnerAddr, "got", parsedLog.To) + } + if parsedLog.Amount.Cmp(toMint) != 0 { + t.Fatal("expected mint amount to be", toMint, "got", parsedLog.Amount) + } + } + } + if !mintLogged { + t.Fatal("expected mint event to be logged") + } +} + +func TestArbos11To32UpgradeWithCalldata(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/batch_poster_test.go b/system_tests/batch_poster_test.go index 720e57f309..a69aeb6505 100644 --- a/system_tests/batch_poster_test.go +++ b/system_tests/batch_poster_test.go @@ -21,10 +21,10 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" - "github.com/offchainlabs/bold/solgen/go/bridgegen" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbnode/dataposter/externalsignertest" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" "github.com/offchainlabs/nitro/util/redisutil" ) @@ -208,7 +208,6 @@ func testBatchPosterParallel(t *testing.T, useRedis bool) { } func TestBatchPosterLargeTx(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -281,7 +280,6 @@ func TestBatchPosterKeepsUp(t *testing.T) { } func testAllowPostingFirstBatchWhenSequencerMessageCountMismatch(t *testing.T, enabled bool) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -406,7 +404,7 @@ func testBatchPosterDelayBuffer(t *testing.T, delayBufferEnabled bool) { _, err := builder.L2.ConsensusNode.BatchPoster.MaybePostSequencerBatch(ctx) Require(t, err) - // Check messages did't appear in 2nd node + // Check messages didn't appear in 2nd node _, err = WaitForTx(ctx, testClientB.Client, txs[0].Hash(), 100*time.Millisecond) if err == nil || !errors.Is(err, context.DeadlineExceeded) { Fatal(t, "expected context-deadline exceeded error, but got:", err) @@ -486,8 +484,6 @@ func TestBatchPosterDelayBufferDontForceNonDelayedMessages(t *testing.T) { } func TestParentChainNonEIP7623(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/benchmarks_test.go b/system_tests/benchmarks_test.go index b71672caa9..50865c9709 100644 --- a/system_tests/benchmarks_test.go +++ b/system_tests/benchmarks_test.go @@ -13,6 +13,7 @@ import ( "time" "github.com/ethereum/go-ethereum/core/types" + "github.com/offchainlabs/nitro/solgen/go/localgen" ) diff --git a/system_tests/block_validator_bench_test.go b/system_tests/block_validator_bench_test.go index d01e6a1b2b..40f5b731d3 100644 --- a/system_tests/block_validator_bench_test.go +++ b/system_tests/block_validator_bench_test.go @@ -8,9 +8,15 @@ package arbtest import ( - "testing" + "testing" ) func TestBlockValidatorBenchmark(t *testing.T) { - testBlockValidatorSimple(t, "onchain", 1, depleteGas, true) + opts := Options{ + dasModeString: "onchain", + workloadLoops: 1, + workload: depleteGas, + arbitrator: true, + } + testBlockValidatorSimple(t, opts) } diff --git a/system_tests/block_validator_test.go b/system_tests/block_validator_test.go index 2c208d067b..0fe0c2b147 100644 --- a/system_tests/block_validator_test.go +++ b/system_tests/block_validator_test.go @@ -53,7 +53,6 @@ type Options struct { } func testBlockValidatorSimple(t *testing.T, opts Options) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -71,7 +70,8 @@ func testBlockValidatorSimple(t *testing.T, opts Options) { builder := NewNodeBuilder(ctx).DefaultConfig(t, true) builder = builder.WithWasmRootDir(opts.wasmRootDir) // For now PathDB is not supported when using block validation - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) + builder.nodeConfig = l1NodeConfigA builder.chainConfig = chainConfig builder.L2Info = nil diff --git a/system_tests/blocks_reexecutor_test.go b/system_tests/blocks_reexecutor_test.go index 9dd12a98d0..9e8c05116b 100644 --- a/system_tests/blocks_reexecutor_test.go +++ b/system_tests/blocks_reexecutor_test.go @@ -23,7 +23,9 @@ func testBlocksReExecutorModes(t *testing.T, onMultipleRanges bool) { defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + // For now PathDB is not supported + builder.RequireScheme(t, rawdb.HashScheme) + // This allows us to see reexecution of multiple ranges if onMultipleRanges { builder.execConfig.Caching.Archive = true diff --git a/system_tests/bloom_test.go b/system_tests/bloom_test.go deleted file mode 100644 index 99a3d6c006..0000000000 --- a/system_tests/bloom_test.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md - -// race detection makes things slow and miss timeouts -//go:build !race -// +build !race - -package arbtest - -import ( - "context" - "math/big" - "math/rand" - "testing" - "time" - - "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - - "github.com/offchainlabs/nitro/solgen/go/localgen" -) - -func TestBloom(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - builder.execConfig.RPC.BloomBitsBlocks = 256 - builder.execConfig.RPC.BloomConfirms = 1 - builder.takeOwnership = false - cleanup := builder.Build(t) - - defer cleanup() - - builder.L2Info.GenerateAccount("User2") - - ownerTxOpts := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) - ownerTxOpts.Context = ctx - _, simple := builder.L2.DeploySimple(t, ownerTxOpts) - simpleABI, err := localgen.SimpleMetaData.GetAbi() - Require(t, err) - - countsNum := 800 - eventsNum := 20 - nullEventsNum := 50 - - eventCounts := make(map[uint64]struct{}) - nullEventCounts := make(map[uint64]struct{}) - - for i := 0; i < eventsNum; i++ { - // #nosec G115 - count := uint64(rand.Int() % countsNum) - eventCounts[count] = struct{}{} - } - - for i := 0; i < nullEventsNum; i++ { - // #nosec G115 - count := uint64(rand.Int() % countsNum) - nullEventCounts[count] = struct{}{} - } - - for i := 0; i <= countsNum; i++ { - var tx *types.Transaction - var err error - // #nosec G115 - _, sendNullEvent := nullEventCounts[uint64(i)] - if sendNullEvent { - tx, err = simple.EmitNullEvent(&ownerTxOpts) - Require(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - Require(t, err) - } - - // #nosec G115 - _, sendEvent := eventCounts[uint64(i)] - if sendEvent { - tx, err = simple.IncrementEmit(&ownerTxOpts) - } else { - tx, err = simple.Increment(&ownerTxOpts) - } - Require(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - Require(t, err) - if i%100 == 0 { - t.Log("counts: ", i, "/", countsNum) - } - } - for { - sectionSize, sectionNum := builder.L2.ExecNode.Backend.APIBackend().BloomStatus() - if sectionSize != 256 { - Fatal(t, "unexpected section size: ", sectionSize) - } - // #nosec G115 - t.Log("sections: ", sectionNum, "/", uint64(countsNum)/sectionSize) - // #nosec G115 - if sectionSize*(sectionNum+1) > uint64(countsNum) && sectionNum > 1 { - break - } - <-time.After(time.Second) - } - lastHeader, err := builder.L2.Client.HeaderByNumber(ctx, nil) - Require(t, err) - nullEventQuery := ethereum.FilterQuery{ - FromBlock: big.NewInt(0), - ToBlock: lastHeader.Number, - Topics: [][]common.Hash{{simpleABI.Events["NullEvent"].ID}}, - } - logs, err := builder.L2.Client.FilterLogs(ctx, nullEventQuery) - Require(t, err) - if len(logs) != len(nullEventCounts) { - Fatal(t, "expected ", len(nullEventCounts), " logs, got ", len(logs)) - } - incrementEventQuery := ethereum.FilterQuery{ - Topics: [][]common.Hash{{simpleABI.Events["CounterEvent"].ID}}, - } - logs, err = builder.L2.Client.FilterLogs(ctx, incrementEventQuery) - Require(t, err) - if len(logs) != len(eventCounts) { - Fatal(t, "expected ", len(eventCounts), " logs, got ", len(logs)) - } - for _, log := range logs { - parsedLog, err := simple.ParseCounterEvent(log) - Require(t, err) - _, expected := eventCounts[parsedLog.Count-1] - if !expected { - Fatal(t, "unxpected count in logs: ", parsedLog.Count) - } - } -} diff --git a/system_tests/bold_challenge_protocol_test.go b/system_tests/bold_challenge_protocol_test.go index 807e69b295..22f10f1270 100644 --- a/system_tests/bold_challenge_protocol_test.go +++ b/system_tests/bold_challenge_protocol_test.go @@ -30,15 +30,12 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + protocol "github.com/offchainlabs/bold/chain-abstraction" solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" challengemanager "github.com/offchainlabs/bold/challenge-manager" modes "github.com/offchainlabs/bold/challenge-manager/types" l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" - "github.com/offchainlabs/bold/solgen/go/bridgegen" - "github.com/offchainlabs/bold/solgen/go/challengeV2gen" - "github.com/offchainlabs/bold/solgen/go/mocksgen" - "github.com/offchainlabs/bold/solgen/go/rollupgen" challengetesting "github.com/offchainlabs/bold/testing" "github.com/offchainlabs/bold/testing/setup" butil "github.com/offchainlabs/bold/util" @@ -50,6 +47,10 @@ import ( "github.com/offchainlabs/nitro/arbstate" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/staker/bold" "github.com/offchainlabs/nitro/statetransfer" @@ -603,12 +604,10 @@ func createTestNodeOnL1ForBoldProtocol( l1info.SetContract("Rollup", addresses.Rollup) l1info.SetContract("UpgradeExecutor", addresses.UpgradeExecutor) - execConfig := ExecConfigDefaultNonSequencerTest(t) + execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) Require(t, execConfig.Validate()) - execConfig.Caching.StateScheme = rawdb.HashScheme - useWasmCache := uint32(1) initMessage := getInitMessage(ctx, t, l1client, addresses) - _, l2stack, l2chainDb, l2arbDb, l2blockchain = createNonL1BlockChainWithStackConfig(t, l2info, "", chainConfig, nil, initMessage, nil, execConfig, useWasmCache, true) + _, l2stack, l2chainDb, l2arbDb, l2blockchain = createNonL1BlockChainWithStackConfig(t, l2info, "", chainConfig, nil, initMessage, nil, execConfig, true) var sequencerTxOptsPtr *bind.TransactOpts var dataSigner signature.DataSignerFunc if isSequencer { @@ -831,11 +830,10 @@ func create2ndNodeWithConfigForBoldProtocol( initReader := statetransfer.NewMemoryInitDataReader(l2InitData) initMessage := getInitMessage(ctx, t, l1client, first.DeployInfo) - execConfig := ExecConfigDefaultNonSequencerTest(t) + execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) Require(t, execConfig.Validate()) - execConfig.Caching.StateScheme = rawdb.HashScheme - coreCacheConfig := gethexec.DefaultCacheConfigFor(l2stack, &execConfig.Caching) - l2blockchain, err := gethexec.WriteOrTestBlockChain(l2chainDb, coreCacheConfig, initReader, chainConfig, nil, nil, initMessage, execConfig.TxLookupLimit, 0) + coreCacheConfig := gethexec.DefaultCacheConfigFor(&execConfig.Caching) + l2blockchain, err := gethexec.WriteOrTestBlockChain(l2chainDb, coreCacheConfig, initReader, chainConfig, nil, nil, initMessage, &execConfig.TxIndexer, 0) Require(t, err) execConfigFetcher := func() *gethexec.Config { return execConfig } diff --git a/system_tests/bold_l3_support_test.go b/system_tests/bold_l3_support_test.go index 20d57a76fa..d53353edc1 100644 --- a/system_tests/bold_l3_support_test.go +++ b/system_tests/bold_l3_support_test.go @@ -18,16 +18,17 @@ import ( "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" challengemanager "github.com/offchainlabs/bold/challenge-manager" modes "github.com/offchainlabs/bold/challenge-manager/types" l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" - "github.com/offchainlabs/bold/solgen/go/challengeV2gen" - "github.com/offchainlabs/bold/solgen/go/rollupgen" butil "github.com/offchainlabs/bold/util" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" "github.com/offchainlabs/nitro/solgen/go/localgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/bold" ) @@ -40,6 +41,7 @@ func TestL3ChallengeProtocolBOLD(t *testing.T) { // Block validation requires db hash scheme. builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.execConfig.RPC.StateScheme = rawdb.HashScheme builder.nodeConfig.BlockValidator.Enable = true builder.nodeConfig.Staker.Enable = true builder.nodeConfig.Staker.Strategy = "MakeNodes" @@ -53,6 +55,7 @@ func TestL3ChallengeProtocolBOLD(t *testing.T) { defer cleanupL1AndL2() builder.l3Config.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.l3Config.execConfig.RPC.StateScheme = rawdb.HashScheme builder.l3Config.nodeConfig.Staker.Enable = true builder.l3Config.nodeConfig.BlockValidator.Enable = true builder.l3Config.nodeConfig.Staker.Strategy = "MakeNodes" diff --git a/system_tests/bold_new_challenge_test.go b/system_tests/bold_new_challenge_test.go index 74a82717c2..b5c71d8375 100644 --- a/system_tests/bold_new_challenge_test.go +++ b/system_tests/bold_new_challenge_test.go @@ -18,19 +18,20 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" + protocol "github.com/offchainlabs/bold/chain-abstraction" solimpl "github.com/offchainlabs/bold/chain-abstraction/sol-implementation" challengemanager "github.com/offchainlabs/bold/challenge-manager" modes "github.com/offchainlabs/bold/challenge-manager/types" "github.com/offchainlabs/bold/containers/option" l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" - "github.com/offchainlabs/bold/solgen/go/challengeV2gen" - "github.com/offchainlabs/bold/solgen/go/mocksgen" - "github.com/offchainlabs/bold/solgen/go/rollupgen" "github.com/offchainlabs/bold/state-commitments/history" butil "github.com/offchainlabs/bold/util" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" + "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/bold" ) @@ -136,7 +137,7 @@ func testChallengeProtocolBOLDVirtualBlocks(t *testing.T, wrongAtFirstVirtual bo builder := NewNodeBuilder(ctx).DefaultConfig(t, true).WithBoldDeployment() // Block validation requires db hash scheme - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) builder.nodeConfig.BlockValidator.Enable = true builder.valnodeConfig.UseJit = false diff --git a/system_tests/bold_state_provider_test.go b/system_tests/bold_state_provider_test.go index 0689612658..e082c31e70 100644 --- a/system_tests/bold_state_provider_test.go +++ b/system_tests/bold_state_provider_test.go @@ -13,8 +13,6 @@ import ( "testing" "time" - "github.com/offchainlabs/nitro/validator/server_common" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" @@ -24,22 +22,22 @@ import ( "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" + protocol "github.com/offchainlabs/bold/chain-abstraction" + "github.com/offchainlabs/bold/containers/option" + l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" + prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" + mockmanager "github.com/offchainlabs/bold/testing/mocks/state-provider" + "github.com/offchainlabs/bold/testing/setup" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/staker/bold" "github.com/offchainlabs/nitro/util" + "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" - - protocol "github.com/offchainlabs/bold/chain-abstraction" - "github.com/offchainlabs/bold/containers/option" - l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" - "github.com/offchainlabs/bold/solgen/go/bridgegen" - "github.com/offchainlabs/bold/solgen/go/mocksgen" - prefixproofs "github.com/offchainlabs/bold/state-commitments/prefix-proofs" - mockmanager "github.com/offchainlabs/bold/testing/mocks/state-provider" - "github.com/offchainlabs/bold/testing/setup" ) func TestChallengeProtocolBOLD_Bisections(t *testing.T) { diff --git a/system_tests/classic_redirect_test.go b/system_tests/classic_redirect_test.go index 6f90c7b308..76a8c1afc6 100644 --- a/system_tests/classic_redirect_test.go +++ b/system_tests/classic_redirect_test.go @@ -14,8 +14,6 @@ import ( ) func TestClassicRedirectURLNotLeaked(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -35,7 +33,7 @@ func TestClassicRedirectURLNotLeaked(t *testing.T) { var result traceResult err = l2rpc.CallContext(ctx, &result, "arbtrace_call", callTxArgs{}, []string{"trace"}, rpc.BlockNumberOrHash{}) - // checks that it errors and that the error message does not contains the classic redirect URL + // checks that it errors and that the error message does not contain the classic redirect URL expectedErrMsg := "Failed to call fallback API" if err == nil || err.Error() != expectedErrMsg { t.Fatalf("Expected error message to be %s, got %v", expectedErrMsg, err) diff --git a/system_tests/client_wrapper.go b/system_tests/client_wrapper.go index e580a02630..e271375477 100644 --- a/system_tests/client_wrapper.go +++ b/system_tests/client_wrapper.go @@ -13,7 +13,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" ) -// ClientWrapper wraps a RPC client to manipulate inbound requests. +// ClientWrapper wraps an RPC client to manipulate inbound requests. type ClientWrapper struct { mutex *sync.Mutex innerClient rpc.ClientInterface diff --git a/system_tests/common_test.go b/system_tests/common_test.go index 6a3c466265..f331b6ce62 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -10,6 +10,7 @@ import ( "encoding/hex" "encoding/json" "flag" + "fmt" "io" "log/slog" "math/big" @@ -19,6 +20,8 @@ import ( "reflect" "strconv" "strings" + "sync" + "sync/atomic" "testing" "time" @@ -52,13 +55,12 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - "github.com/offchainlabs/bold/solgen/go/rollupgen" "github.com/offchainlabs/bold/testing/setup" butil "github.com/offchainlabs/bold/util" "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" - "github.com/offchainlabs/nitro/arbos/util" + arbosutil "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/blsSignatures" "github.com/offchainlabs/nitro/cmd/chaininfo" @@ -72,8 +74,10 @@ import ( "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/localgen" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" "github.com/offchainlabs/nitro/statetransfer" + "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" "github.com/offchainlabs/nitro/util/redisutil" @@ -99,7 +103,6 @@ type SecondNodeParams struct { dasConfig *das.DataAvailabilityConfig initData *statetransfer.ArbosInitializationInfo addresses *chaininfo.RollupAddresses - wasmCacheTag uint32 useExecutionClientOnly bool } @@ -121,42 +124,52 @@ func NewTestClient(ctx context.Context) *TestClient { } func (tc *TestClient) SendSignedTx(t *testing.T, l2Client *ethclient.Client, transaction *types.Transaction, lInfo info) *types.Receipt { + t.Helper() return SendSignedTxViaL1(t, tc.ctx, lInfo, tc.Client, l2Client, transaction) } func (tc *TestClient) SendUnsignedTx(t *testing.T, l2Client *ethclient.Client, transaction *types.Transaction, lInfo info) *types.Receipt { + t.Helper() return SendUnsignedTxViaL1(t, tc.ctx, lInfo, tc.Client, l2Client, transaction) } func (tc *TestClient) TransferBalance(t *testing.T, from string, to string, amount *big.Int, lInfo info) (*types.Transaction, *types.Receipt) { + t.Helper() return TransferBalanceTo(t, from, lInfo.GetAddress(to), amount, lInfo, tc.Client, tc.ctx) } func (tc *TestClient) TransferBalanceTo(t *testing.T, from string, to common.Address, amount *big.Int, lInfo info) (*types.Transaction, *types.Receipt) { + t.Helper() return TransferBalanceTo(t, from, to, amount, lInfo, tc.Client, tc.ctx) } func (tc *TestClient) GetBalance(t *testing.T, account common.Address) *big.Int { + t.Helper() return GetBalance(t, tc.ctx, tc.Client, account) } func (tc *TestClient) GetBaseFee(t *testing.T) *big.Int { + t.Helper() return GetBaseFee(t, tc.Client, tc.ctx) } func (tc *TestClient) GetBaseFeeAt(t *testing.T, blockNum *big.Int) *big.Int { + t.Helper() return GetBaseFeeAt(t, tc.Client, tc.ctx, blockNum) } func (tc *TestClient) SendWaitTestTransactions(t *testing.T, txs []*types.Transaction) []*types.Receipt { + t.Helper() return SendWaitTestTransactions(t, tc.ctx, tc.Client, txs) } func (tc *TestClient) DeployBigMap(t *testing.T, auth bind.TransactOpts) (common.Address, *localgen.BigMap) { + t.Helper() return deployBigMap(t, tc.ctx, auth, tc.Client) } func (tc *TestClient) DeploySimple(t *testing.T, auth bind.TransactOpts) (common.Address, *localgen.Simple) { + t.Helper() return deploySimple(t, tc.ctx, auth, tc.Client) } @@ -180,6 +193,7 @@ var DefaultTestForwarderConfig = gethexec.ForwarderConfig{ var TestSequencerConfig = gethexec.SequencerConfig{ Enable: true, MaxBlockSpeed: time.Millisecond * 10, + ReadFromTxQueueTimeout: time.Second, // Dont want this to affect tests MaxRevertGasReject: params.TxGas + 10000, MaxAcceptableTimestampDelta: time.Hour, SenderWhitelist: []string{}, @@ -190,14 +204,16 @@ var TestSequencerConfig = gethexec.SequencerConfig{ MaxTxDataSize: 95000, NonceFailureCacheSize: 1024, NonceFailureCacheExpiry: time.Second, + ExpectedSurplusGasPriceMode: "CalldataPrice", ExpectedSurplusSoftThreshold: "default", ExpectedSurplusHardThreshold: "default", EnableProfiling: false, } -func ExecConfigDefaultNonSequencerTest(t *testing.T) *gethexec.Config { +func ExecConfigDefaultNonSequencerTest(t *testing.T, stateScheme string) *gethexec.Config { config := gethexec.ConfigDefault - config.Caching.StateScheme = env.GetTestStateScheme() + config.Caching.StateScheme = stateScheme + config.RPC.StateScheme = stateScheme config.ParentChainReader = headerreader.TestConfig config.Sequencer.Enable = false config.Forwarder = DefaultTestForwarderConfig @@ -209,9 +225,10 @@ func ExecConfigDefaultNonSequencerTest(t *testing.T) *gethexec.Config { return &config } -func ExecConfigDefaultTest(t *testing.T) *gethexec.Config { +func ExecConfigDefaultTest(t *testing.T, stateScheme string) *gethexec.Config { config := gethexec.ConfigDefault - config.Caching.StateScheme = env.GetTestStateScheme() + config.Caching.StateScheme = stateScheme + config.RPC.StateScheme = stateScheme config.Sequencer = TestSequencerConfig config.ParentChainReader = headerreader.TestConfig config.ForwardingTarget = "null" @@ -225,6 +242,7 @@ func ExecConfigDefaultTest(t *testing.T) *gethexec.Config { type NodeBuilder struct { // NodeBuilder configuration ctx context.Context + ctxCancel context.CancelFunc chainConfig *params.ChainConfig arbOSInit *params.ArbOSInit nodeConfig *arbnode.Config @@ -234,6 +252,7 @@ type NodeBuilder struct { valnodeConfig *valnode.Config l3Config *NitroConfig deployBold bool + parallelise bool L1Info info L2Info info L3Info info @@ -243,12 +262,12 @@ type NodeBuilder struct { isSequencer bool takeOwnership bool withL1 bool + defaultDbScheme string addresses *chaininfo.RollupAddresses l3Addresses *chaininfo.RollupAddresses initMessage *arbostypes.ParsedInitMessage l3InitMessage *arbostypes.ParsedInitMessage withProdConfirmPeriodBlocks bool - wasmCacheTag uint32 delayBufferThreshold uint64 useFreezer bool withL1ClientWrapper bool @@ -298,7 +317,7 @@ func L3NitroConfigDefaultTest(t *testing.T) *NitroConfig { return &NitroConfig{ chainConfig: chainConfig, nodeConfig: arbnode.ConfigDefaultL1Test(), - execConfig: ExecConfigDefaultTest(t), + execConfig: ExecConfigDefaultTest(t, rawdb.HashScheme), stackConfig: testhelpers.CreateStackConfigForTest(t.TempDir()), valnodeConfig: &valnodeConfig, @@ -307,13 +326,15 @@ func L3NitroConfigDefaultTest(t *testing.T) *NitroConfig { } } -func NewNodeBuilder(ctx context.Context) *NodeBuilder { - return &NodeBuilder{ctx: ctx} +func NewNodeBuilder(ctxIn context.Context) *NodeBuilder { + ctx, cancel := context.WithCancel(ctxIn) + return &NodeBuilder{ctx: ctx, ctxCancel: cancel} } func (b *NodeBuilder) DefaultConfig(t *testing.T, withL1 bool) *NodeBuilder { // most used values across current tests are set here as default b.withL1 = withL1 + b.parallelise = true if withL1 { b.isSequencer = true b.nodeConfig = arbnode.ConfigDefaultL1Test() @@ -329,12 +350,21 @@ func (b *NodeBuilder) DefaultConfig(t *testing.T, withL1 bool) *NodeBuilder { b.l2StackConfig = testhelpers.CreateStackConfigForTest(b.dataDir) cp := valnode.TestValidationConfig b.valnodeConfig = &cp - b.execConfig = ExecConfigDefaultTest(t) + b.defaultDbScheme = rawdb.HashScheme + if *testflag.StateSchemeFlag == rawdb.PathScheme || *testflag.StateSchemeFlag == rawdb.HashScheme { + b.defaultDbScheme = *testflag.StateSchemeFlag + } + b.execConfig = ExecConfigDefaultTest(t, b.defaultDbScheme) b.l3Config = L3NitroConfigDefaultTest(t) b.useFreezer = true return b } +func (b *NodeBuilder) DontParalellise() *NodeBuilder { + b.parallelise = false + return b +} + func (b *NodeBuilder) WithArbOSVersion(arbosVersion uint64) *NodeBuilder { newChainConfig := *b.chainConfig newChainConfig.ArbitrumChainParams.InitialArbOSVersion = arbosVersion @@ -367,15 +397,6 @@ func (b *NodeBuilder) WithExtraArchs(targets []string) *NodeBuilder { return b } -func (b *NodeBuilder) WithStylusLongTermCache(enabled bool) *NodeBuilder { - if enabled { - b.wasmCacheTag = 1 - } else { - b.wasmCacheTag = 0 - } - return b -} - // WithDelayBuffer sets the delay-buffer threshold, which is the number of blocks the batch-poster // is allowed to delay a batch with a delayed message. // Setting the threshold to zero disabled the delay buffer (default behaviour). @@ -384,6 +405,26 @@ func (b *NodeBuilder) WithDelayBuffer(threshold uint64) *NodeBuilder { return b } +func (b *NodeBuilder) RequireScheme(t *testing.T, scheme string) *NodeBuilder { + if testflag.StateSchemeFlag != nil && *testflag.StateSchemeFlag != "" && *testflag.StateSchemeFlag != scheme { + t.Skip("skipping because db scheme is set and not ", scheme) + } + if b.defaultDbScheme != scheme && b.execConfig != nil { + b.execConfig.Caching.StateScheme = scheme + b.execConfig.RPC.StateScheme = scheme + Require(t, b.execConfig.Validate()) + } + b.defaultDbScheme = scheme + return b +} + +func (b *NodeBuilder) ExecConfigDefaultTest(t *testing.T, sequencer bool) *gethexec.Config { + if sequencer { + ExecConfigDefaultTest(t, b.defaultDbScheme) + } + return ExecConfigDefaultNonSequencerTest(t, b.defaultDbScheme) +} + // WithL1ClientWrapper creates a ClientWrapper for the L1 RPC client before passing it to the L2 node. func (b *NodeBuilder) WithL1ClientWrapper(t *testing.T) *NodeBuilder { if !b.withL1 { @@ -394,6 +435,10 @@ func (b *NodeBuilder) WithL1ClientWrapper(t *testing.T) *NodeBuilder { } func (b *NodeBuilder) Build(t *testing.T) func() { + if b.parallelise { + b.parallelise = false + t.Parallel() + } b.CheckConfig(t) if b.withL1 { b.BuildL1(t) @@ -402,6 +447,97 @@ func (b *NodeBuilder) Build(t *testing.T) func() { return b.BuildL2(t) } +type testCollection struct { + room atomic.Int64 + cond *sync.Cond + running map[string]int64 + waiting map[string]int64 +} + +var globalCollection *testCollection + +func initTestCollection() { + if globalCollection != nil { + panic("trying to init testCollection twice") + } + globalCollection = &testCollection{} + globalCollection.cond = sync.NewCond(&sync.Mutex{}) + room := int64(util.GoMaxProcs()) + if room < 2 { + room = 2 + } + globalCollection.running = make(map[string]int64) + globalCollection.waiting = make(map[string]int64) + globalCollection.room.Store(room) +} + +func runningWithContext(ctx context.Context, weight int64, name string) { + current := globalCollection.running[name] + globalCollection.running[name] = current + weight + globalCollection.cond.L.Unlock() + go func() { + <-ctx.Done() + globalCollection.cond.L.Lock() + current := globalCollection.running[name] + if current-weight <= 0 { + delete(globalCollection.running, name) + } else { + globalCollection.running[name] = current - weight + } + if globalCollection.room.Add(weight) > 0 { + globalCollection.cond.Broadcast() + } + globalCollection.cond.L.Unlock() + }() +} + +func WaitAndRun(ctx context.Context, weight int64, name string) error { + globalCollection.cond.L.Lock() + current := globalCollection.waiting[name] + globalCollection.waiting[name] = current + weight + for globalCollection.room.Add(0-weight) < 0 { + if globalCollection.room.Add(weight) > 0 { + globalCollection.cond.Broadcast() + } + if ctx.Err() != nil { + return fmt.Errorf("Context cancelled while waiting to launch test: %s", name) + } + globalCollection.cond.Wait() + } + current = globalCollection.waiting[name] + if current-weight <= 0 { + delete(globalCollection.waiting, name) + } else { + globalCollection.waiting[name] = current - weight + } + runningWithContext(ctx, weight, name) + return nil +} + +func DontWaitAndRun(ctx context.Context, weight int64, name string) { + globalCollection.room.Add(0 - weight) + globalCollection.cond.L.Lock() + runningWithContext(ctx, weight, name) +} + +func CurrentlyRunning() (map[string]int64, map[string]int64) { + running := make(map[string]int64) + waiting := make(map[string]int64) + globalCollection.cond.L.Lock() + for k, v := range globalCollection.running { + if v > 0 { + running[k] = v + } + } + for k, v := range globalCollection.waiting { + if v > 0 { + waiting[k] = v + } + } + globalCollection.cond.L.Unlock() + return running, waiting +} + func (b *NodeBuilder) CheckConfig(t *testing.T) { if b.chainConfig == nil { b.chainConfig = chaininfo.ArbitrumDevTestChainConfig() @@ -409,8 +545,19 @@ func (b *NodeBuilder) CheckConfig(t *testing.T) { if b.nodeConfig == nil { b.nodeConfig = arbnode.ConfigDefaultL1Test() } + if b.nodeConfig.ValidatorRequired() { + // validation currently requires hash + b.RequireScheme(t, rawdb.HashScheme) + } + if b.defaultDbScheme == "" { + b.defaultDbScheme = env.GetTestStateScheme() + } if b.execConfig == nil { - b.execConfig = ExecConfigDefaultTest(t) + b.execConfig = b.ExecConfigDefaultTest(t, true) + } + if b.execConfig.Caching.Archive { + // archive currently requires hash + b.RequireScheme(t, rawdb.HashScheme) } if b.L1Info == nil { b.L1Info = NewL1TestInfo(t) @@ -428,6 +575,14 @@ func (b *NodeBuilder) CheckConfig(t *testing.T) { } func (b *NodeBuilder) BuildL1(t *testing.T) { + if b.parallelise { + b.parallelise = false + t.Parallel() + } + err := WaitAndRun(b.ctx, 2, t.Name()) + if err != nil { + t.Fatal(err) + } b.L1 = NewTestClient(b.ctx) b.L1Info, b.L1.Client, b.L1.L1Backend, b.L1.Stack, b.L1.ClientWrapper = createTestL1BlockChain(t, b.L1Info, b.withL1ClientWrapper) locator, err := server_common.NewMachineLocator(b.valnodeConfig.Wasm.RootPath) @@ -470,7 +625,6 @@ func buildOnParentChain( initMessage *arbostypes.ParsedInitMessage, addresses *chaininfo.RollupAddresses, - wasmCacheTag uint32, useFreezer bool, ) *TestClient { if parentChainTestClient == nil { @@ -483,7 +637,7 @@ func buildOnParentChain( var arbDb ethdb.Database var blockchain *core.BlockChain _, chainTestClient.Stack, chainDb, arbDb, blockchain = createNonL1BlockChainWithStackConfig( - t, chainInfo, dataDir, chainConfig, arbOSInit, initMessage, stackConfig, execConfig, wasmCacheTag, useFreezer) + t, chainInfo, dataDir, chainConfig, arbOSInit, initMessage, stackConfig, execConfig, useFreezer) var sequencerTxOptsPtr *bind.TransactOpts var dataSigner signature.DataSignerFunc @@ -534,6 +688,7 @@ func buildOnParentChain( } func (b *NodeBuilder) BuildL3OnL2(t *testing.T) func() { + DontWaitAndRun(b.ctx, 1, t.Name()) b.L3Info = NewArbTestInfo(t, b.l3Config.chainConfig.ChainID) locator, err := server_common.NewMachineLocator(b.l3Config.valnodeConfig.Wasm.RootPath) @@ -577,7 +732,6 @@ func (b *NodeBuilder) BuildL3OnL2(t *testing.T) func() { b.l3InitMessage, b.l3Addresses, - b.wasmCacheTag, b.useFreezer, ) @@ -609,7 +763,6 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { b.initMessage, b.addresses, - b.wasmCacheTag, b.useFreezer, ) @@ -618,12 +771,21 @@ func (b *NodeBuilder) BuildL2OnL1(t *testing.T) func() { if b.L1 != nil && b.L1.cleanup != nil { b.L1.cleanup() } + b.ctxCancel() } } // L2 -Only. Enough for tests that needs no interface to L1 // Requires precompiles.AllowDebugPrecompiles = true func (b *NodeBuilder) BuildL2(t *testing.T) func() { + if b.parallelise { + b.parallelise = false + t.Parallel() + } + err := WaitAndRun(b.ctx, 1, t.Name()) + if err != nil { + Fatal(t, err) + } b.L2 = NewTestClient(b.ctx) AddValNodeIfNeeded(t, b.ctx, b.nodeConfig, true, "", b.valnodeConfig.Wasm.RootPath) @@ -632,7 +794,7 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { var arbDb ethdb.Database var blockchain *core.BlockChain b.L2Info, b.L2.Stack, chainDb, arbDb, blockchain = createNonL1BlockChainWithStackConfig( - t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, nil, b.l2StackConfig, b.execConfig, b.wasmCacheTag, b.useFreezer) + t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, nil, b.l2StackConfig, b.execConfig, b.useFreezer) Require(t, b.execConfig.Validate()) execConfig := b.execConfig @@ -675,7 +837,10 @@ func (b *NodeBuilder) BuildL2(t *testing.T) func() { b.L2.ExecNode = getExecNode(t, b.L2.ConsensusNode) b.L2.cleanup = func() { b.L2.ConsensusNode.StopAndWait() } - return func() { b.L2.cleanup() } + return func() { + b.L2.cleanup() + b.ctxCancel() + } } // L2 -Only. RestartL2Node shutdowns the existing l2 node and start it again using the same data dir. @@ -685,7 +850,7 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { } b.L2.cleanup() - l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, b.initMessage, b.l2StackConfig, b.execConfig, b.wasmCacheTag, b.useFreezer) + l2info, stack, chainDb, arbDb, blockchain := createNonL1BlockChainWithStackConfig(t, b.L2Info, b.dataDir, b.chainConfig, b.arbOSInit, b.initMessage, b.l2StackConfig, b.execConfig, b.useFreezer) execConfigFetcher := func() *gethexec.Config { return b.execConfig } execNode, err := gethexec.CreateExecutionNode(b.ctx, stack, chainDb, blockchain, nil, execConfigFetcher, 0) @@ -694,7 +859,19 @@ func (b *NodeBuilder) RestartL2Node(t *testing.T) { feedErrChan := make(chan error, 10) locator, err := server_common.NewMachineLocator(b.valnodeConfig.Wasm.RootPath) Require(t, err) - currentNode, err := arbnode.CreateNodeFullExecutionClient(b.ctx, stack, execNode, execNode, execNode, execNode, arbDb, NewFetcherFromConfig(b.nodeConfig), blockchain.Config(), nil, nil, nil, nil, nil, feedErrChan, big.NewInt(1337), nil, locator.LatestWasmModuleRoot()) + var sequencerTxOpts *bind.TransactOpts + var validatorTxOpts *bind.TransactOpts + var dataSigner signature.DataSignerFunc + var l1Client *ethclient.Client + if b.withL1 { + sequencerTxOptsNP := b.L1Info.GetDefaultTransactOpts("Sequencer", b.ctx) + sequencerTxOpts = &sequencerTxOptsNP + validatorTxOptsNP := b.L1Info.GetDefaultTransactOpts("Validator", b.ctx) + validatorTxOpts = &validatorTxOptsNP + dataSigner = signature.DataSignerFromPrivateKey(b.L1Info.GetInfoWithPrivKey("Sequencer").PrivateKey) + l1Client = b.L1.Client + } + currentNode, err := arbnode.CreateNodeFullExecutionClient(b.ctx, stack, execNode, execNode, execNode, execNode, arbDb, NewFetcherFromConfig(b.nodeConfig), blockchain.Config(), l1Client, b.addresses, validatorTxOpts, sequencerTxOpts, dataSigner, feedErrChan, big.NewInt(1337), nil, locator.LatestWasmModuleRoot()) Require(t, err) Require(t, currentNode.Start(b.ctx)) @@ -765,13 +942,14 @@ func build2ndNode( testClient := NewTestClient(ctx) testClient.Client, testClient.ConsensusNode = - Create2ndNodeWithConfig(t, ctx, firstNodeTestClient.ConsensusNode, parentChainTestClient.Stack, parentChainInfo, params.initData, params.nodeConfig, params.execConfig, params.stackConfig, valnodeConfig, params.addresses, initMessage, params.wasmCacheTag, params.useExecutionClientOnly) + Create2ndNodeWithConfig(t, ctx, firstNodeTestClient.ConsensusNode, parentChainTestClient.Stack, parentChainInfo, params.initData, params.nodeConfig, params.execConfig, params.stackConfig, valnodeConfig, params.addresses, initMessage, params.useExecutionClientOnly) testClient.ExecNode = getExecNode(t, testClient.ConsensusNode) testClient.cleanup = func() { testClient.ConsensusNode.StopAndWait() } return testClient, func() { testClient.cleanup() } } func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*TestClient, func()) { + DontWaitAndRun(b.ctx, 1, t.Name()) if b.L2 == nil { t.Fatal("builder did not previously built an L2 Node") } @@ -800,6 +978,7 @@ func (b *NodeBuilder) Build2ndNode(t *testing.T, params *SecondNodeParams) (*Tes } func (b *NodeBuilder) Build2ndNodeOnL3(t *testing.T, params *SecondNodeParams) (*TestClient, func()) { + DontWaitAndRun(b.ctx, 1, t.Name()) if b.L3 == nil { t.Fatal("builder did not previously built an L3 Node") } @@ -1018,7 +1197,7 @@ func SendUnsignedTxViaL1( Require(t, err) usertxopts := l1info.GetDefaultTransactOpts("User", ctx) - remapped := util.RemapL1Address(usertxopts.From) + remapped := arbosutil.RemapL1Address(usertxopts.From) nonce, err := l2client.NonceAt(ctx, remapped, nil) Require(t, err) @@ -1201,6 +1380,7 @@ func AddValNode(t *testing.T, ctx context.Context, nodeConfig *arbnode.Config, u conf := valnode.TestValidationConfig conf.UseJit = useJit conf.Wasm.RootPath = wasmRootDir + DontWaitAndRun(ctx, 2, t.Name()) // Enable redis streams when URL is specified if redisURL != "" { conf.Arbitrator.RedisValidationServerConfig = rediscons.TestValidationServerConfig @@ -1222,7 +1402,7 @@ func createTestL1BlockChain(t *testing.T, l1info info, withClientWrapper bool) ( if l1info == nil { l1info = NewL1TestInfo(t) } - stackConfig := testhelpers.CreateStackConfigForTest(t.TempDir()) + stackConfig := testhelpers.CreateStackConfigForTest("") l1info.GenerateAccount("Faucet") chainConfig := chaininfo.ArbitrumDevTestChainConfig() @@ -1248,7 +1428,7 @@ func createTestL1BlockChain(t *testing.T, l1info info, withClientWrapper bool) ( l1backend, err := eth.New(stack, &nodeConf) Require(t, err) - simBeacon, err := catalyst.NewSimulatedBeacon(0, l1backend) + simBeacon, err := catalyst.NewSimulatedBeacon(0, common.Address{}, l1backend) Require(t, err) catalyst.RegisterSimulatedBeaconAPIs(stack, simBeacon) stack.RegisterLifecycle(simBeacon) @@ -1441,7 +1621,7 @@ func deployOnParentChain( } func createNonL1BlockChainWithStackConfig( - t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, arbOSInit *params.ArbOSInit, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, wasmCacheTag uint32, useFreezer bool, + t *testing.T, info *BlockchainTestInfo, dataDir string, chainConfig *params.ChainConfig, arbOSInit *params.ArbOSInit, initMessage *arbostypes.ParsedInitMessage, stackConfig *node.Config, execConfig *gethexec.Config, useFreezer bool, ) (*BlockchainTestInfo, *node.Node, ethdb.Database, ethdb.Database, *core.BlockChain) { if info == nil { info = NewArbTestInfo(t, chainConfig.ChainID) @@ -1450,7 +1630,7 @@ func createNonL1BlockChainWithStackConfig( stackConfig = testhelpers.CreateStackConfigForTest(dataDir) } if execConfig == nil { - execConfig = ExecConfigDefaultTest(t) + execConfig = ExecConfigDefaultTest(t, env.GetTestStateScheme()) } Require(t, execConfig.Validate()) @@ -1468,7 +1648,7 @@ func createNonL1BlockChainWithStackConfig( wasmData, err := stack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, wasmCacheTag, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData) arbDb, err := stack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) @@ -1483,8 +1663,8 @@ func createNonL1BlockChainWithStackConfig( SerializedChainConfig: serializedChainConfig, } } - coreCacheConfig := gethexec.DefaultCacheConfigFor(stack, &execConfig.Caching) - blockchain, err := gethexec.WriteOrTestBlockChain(chainDb, coreCacheConfig, initReader, chainConfig, arbOSInit, nil, initMessage, ExecConfigDefaultTest(t).TxLookupLimit, 0) + coreCacheConfig := gethexec.DefaultCacheConfigFor(&execConfig.Caching) + blockchain, err := gethexec.WriteOrTestBlockChain(chainDb, coreCacheConfig, initReader, chainConfig, arbOSInit, nil, initMessage, &gethexec.ConfigDefault.TxIndexer, 0) Require(t, err) return info, stack, chainDb, arbDb, blockchain @@ -1539,14 +1719,13 @@ func Create2ndNodeWithConfig( valnodeConfig *valnode.Config, addresses *chaininfo.RollupAddresses, initMessage *arbostypes.ParsedInitMessage, - wasmCacheTag uint32, useExecutionClientOnly bool, ) (*ethclient.Client, *arbnode.Node) { if nodeConfig == nil { nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() } if execConfig == nil { - execConfig = ExecConfigDefaultNonSequencerTest(t) + t.Fatal("should not be nil") } Require(t, execConfig.Validate()) @@ -1564,7 +1743,7 @@ func Create2ndNodeWithConfig( Require(t, err) wasmData, err := chainStack.OpenDatabaseWithExtraOptions("wasm", 0, 0, "wasm/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("wasm")) Require(t, err) - chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData, wasmCacheTag, execConfig.StylusTarget.WasmTargets()) + chainDb := rawdb.WrapDatabaseWithWasm(chainData, wasmData) arbDb, err := chainStack.OpenDatabaseWithExtraOptions("arbitrumdata", 0, 0, "arbitrumdata/", false, conf.PersistentConfigDefault.Pebble.ExtraOptions("arbitrumdata")) Require(t, err) @@ -1577,13 +1756,13 @@ func Create2ndNodeWithConfig( chainConfig := firstExec.ArbInterface.BlockChain().Config() - coreCacheConfig := gethexec.DefaultCacheConfigFor(chainStack, &execConfig.Caching) + coreCacheConfig := gethexec.DefaultCacheConfigFor(&execConfig.Caching) var tracer *tracing.Hooks if execConfig.VmTrace.TracerName != "" { tracer, err = tracers.LiveDirectory.New(execConfig.VmTrace.TracerName, json.RawMessage(execConfig.VmTrace.JSONConfig)) Require(t, err) } - blockchain, err := gethexec.WriteOrTestBlockChain(chainDb, coreCacheConfig, initReader, chainConfig, nil, tracer, initMessage, ExecConfigDefaultTest(t).TxLookupLimit, 0) + blockchain, err := gethexec.WriteOrTestBlockChain(chainDb, coreCacheConfig, initReader, chainConfig, nil, tracer, initMessage, &execConfig.TxIndexer, 0) Require(t, err) AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", valnodeConfig.Wasm.RootPath) @@ -1844,7 +2023,7 @@ func doUntil(t *testing.T, delay time.Duration, max int, lambda func() bool) { Fatal(t, "failed to complete after ", delay*time.Duration(max)) } -func TestMain(m *testing.M) { +func initDefaultTestLog() { flag.Parse() if *testflag.LogLevelFlag != "" { logLevel, err := strconv.ParseInt(*testflag.LogLevelFlag, 10, 32) @@ -1856,6 +2035,11 @@ func TestMain(m *testing.M) { glogger.Verbosity(slog.Level(logLevel)) log.SetDefault(log.NewLogger(glogger)) } +} + +func TestMain(m *testing.M) { + initDefaultTestLog() + initTestCollection() code := m.Run() os.Exit(code) } @@ -1870,7 +2054,7 @@ func getExecNode(t *testing.T, node *arbnode.Node) *gethexec.ExecutionNode { } func logParser[T any](t *testing.T, source string, name string) func(*types.Log) *T { - parser := util.NewLogParser[T](source, name) + parser := arbosutil.NewLogParser[T](source, name) return func(log *types.Log) *T { t.Helper() event, err := parser(log) @@ -1882,7 +2066,7 @@ func logParser[T any](t *testing.T, source string, name string) func(*types.Log) // recordBlock writes a json file with all of the data needed to validate a block. // // This can be used as an input to the arbitrator prover to validate a block. -func recordBlock(t *testing.T, block uint64, builder *NodeBuilder, targets ...ethdb.WasmTarget) { +func recordBlock(t *testing.T, block uint64, builder *NodeBuilder, targets ...rawdb.WasmTarget) { t.Helper() if !*testflag.RecordBlockInputsEnable { return diff --git a/system_tests/conditionaltx_test.go b/system_tests/conditionaltx_test.go index d060ed9f5e..823ec3a11a 100644 --- a/system_tests/conditionaltx_test.go +++ b/system_tests/conditionaltx_test.go @@ -402,7 +402,6 @@ func TestSendRawTransactionConditionalMultiRoutine(t *testing.T) { } func TestSendRawTransactionConditionalPreCheck(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/contract_tx_test.go b/system_tests/contract_tx_test.go index b9ba3e8244..1851d7f9e3 100644 --- a/system_tests/contract_tx_test.go +++ b/system_tests/contract_tx_test.go @@ -22,7 +22,6 @@ import ( ) func TestContractTxDeploy(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, false) diff --git a/system_tests/das_test.go b/system_tests/das_test.go index f18aa944b9..979c2c64a7 100644 --- a/system_tests/das_test.go +++ b/system_tests/das_test.go @@ -191,7 +191,6 @@ func checkBatchPosting(t *testing.T, ctx context.Context, l1client, l2clientA *e } func TestDASComplexConfigAndRestMirror(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/db_conversion_test.go b/system_tests/db_conversion_test.go index 9d8c14354d..48e48f5e1d 100644 --- a/system_tests/db_conversion_test.go +++ b/system_tests/db_conversion_test.go @@ -20,7 +20,7 @@ import ( func TestDatabaseConversion(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() builder.useFreezer = false builder.l2StackConfig.DBEngine = "leveldb" builder.l2StackConfig.Name = "testl2" @@ -30,12 +30,7 @@ func TestDatabaseConversion(t *testing.T) { } cleanup := builder.Build(t) dataDir := builder.dataDir - cleanupDone := false - defer func() { // TODO we should be able to call cleanup twice, rn it gets stuck then - if !cleanupDone { - cleanup() - } - }() + defer cleanup() builder.L2Info.GenerateAccount("User2") var txs []*types.Transaction for i := uint64(0); i < 200; i++ { @@ -53,8 +48,7 @@ func TestDatabaseConversion(t *testing.T) { user2Balance := builder.L2.GetBalance(t, builder.L2Info.GetAddress("User2")) ownerBalance := builder.L2.GetBalance(t, builder.L2Info.GetAddress("Owner")) - cleanup() - cleanupDone = true + builder.L2.cleanup() t.Log("stopped first node") instanceDir := filepath.Join(dataDir, builder.l2StackConfig.Name) diff --git a/system_tests/debug_trace_test.go b/system_tests/debug_trace_test.go index 13d9ed834b..1a83e5ad2f 100644 --- a/system_tests/debug_trace_test.go +++ b/system_tests/debug_trace_test.go @@ -13,7 +13,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" ) @@ -23,8 +22,6 @@ func TestDebugTraceCallForRecentBlock(t *testing.T) { defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) builder.execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - builder.execConfig.Caching.StateScheme = rawdb.HashScheme cleanup := builder.Build(t) defer cleanup() builder.L2Info.GenerateAccount("User2") diff --git a/system_tests/delayedinbox_test.go b/system_tests/delayedinbox_test.go index 719c2a7100..d2c38de40a 100644 --- a/system_tests/delayedinbox_test.go +++ b/system_tests/delayedinbox_test.go @@ -36,7 +36,6 @@ func WrapL2ForDelayed(t *testing.T, l2Tx *types.Transaction, l1info *BlockchainT } func TestDelayInboxSimple(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/delayedinboxlong_test.go b/system_tests/delayedinboxlong_test.go index 1a7d2e5645..6c449be5ed 100644 --- a/system_tests/delayedinboxlong_test.go +++ b/system_tests/delayedinboxlong_test.go @@ -18,14 +18,13 @@ import ( ) func TestDelayInboxLong(t *testing.T) { - t.Parallel() addLocalLoops := 3 messagesPerAddLocal := 1000 messagesPerDelayed := 10 ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() cleanup := builder.Build(t) defer cleanup() diff --git a/system_tests/eth_sync_test.go b/system_tests/eth_sync_test.go index ce9994fb1e..af1fd4943f 100644 --- a/system_tests/eth_sync_test.go +++ b/system_tests/eth_sync_test.go @@ -10,7 +10,6 @@ import ( ) func TestEthSyncing(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/execution_client_only_test.go b/system_tests/execution_client_only_test.go index 9dcb45a42c..a3f2c27b38 100644 --- a/system_tests/execution_client_only_test.go +++ b/system_tests/execution_client_only_test.go @@ -13,8 +13,6 @@ import ( ) func TestExecutionClientOnly(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/fast_confirm_test.go b/system_tests/fast_confirm_test.go index 36b9859cfb..f084fee28f 100644 --- a/system_tests/fast_confirm_test.go +++ b/system_tests/fast_confirm_test.go @@ -45,7 +45,6 @@ import ( ) func TestFastConfirmationWithdrawal(t *testing.T) { - t.Parallel() ctx, cancelCtx := context.WithCancel(context.Background()) defer cancelCtx() builder, stakerA, cleanupBuilder, cleanupBackgroundTx := setupFastConfirmation(ctx, t) @@ -176,7 +175,7 @@ func setupFastConfirmation(ctx context.Context, t *testing.T) (*NodeBuilder, *le }() var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs - builder := NewNodeBuilder(ctx).DefaultConfig(t, true).WithProdConfirmPeriodBlocks() + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).WithProdConfirmPeriodBlocks().DontParalellise() builder.L2Info = NewBlockChainTestInfo( t, types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), diff --git a/system_tests/fees_test.go b/system_tests/fees_test.go index b004e1d210..da1d5e944a 100644 --- a/system_tests/fees_test.go +++ b/system_tests/fees_test.go @@ -19,19 +19,18 @@ import ( "time" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbos/l1pricing" - - "github.com/ethereum/go-ethereum/common" "github.com/offchainlabs/nitro/solgen/go/precompilesgen" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/colors" ) func TestSequencerFeePaid(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/finality_data_test.go b/system_tests/finality_data_test.go index 901be60d62..ae05c71256 100644 --- a/system_tests/finality_data_test.go +++ b/system_tests/finality_data_test.go @@ -17,6 +17,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/util/testhelpers/env" ) func generateBlocks(t *testing.T, ctx context.Context, builder *NodeBuilder, testClient2ndNode *TestClient, transactions int) { @@ -32,8 +33,6 @@ func generateBlocks(t *testing.T, ctx context.Context, builder *NodeBuilder, tes } func TestFinalizedBlocksMovedToAncients(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -129,8 +128,6 @@ func checksFinalityData( } func TestFinalityDataWaitForBlockValidator(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -147,7 +144,7 @@ func TestFinalityDataWaitForBlockValidator(t *testing.T) { defer cleanup() nodeConfig2ndNode := arbnode.ConfigDefaultL1NonSequencerTest() - execConfig2ndNode := ExecConfigDefaultTest(t) + execConfig2ndNode := ExecConfigDefaultTest(t, env.GetTestStateScheme()) testClient2ndNode, cleanup2ndNode := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: nodeConfig2ndNode, execConfig: execConfig2ndNode}) defer cleanup2ndNode() @@ -222,8 +219,6 @@ func ensureSafeBlockDoesNotExist(t *testing.T, ctx context.Context, testClient * } func TestFinalityDataPushedFromConsensusToExecution(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -279,8 +274,6 @@ func TestFinalityDataPushedFromConsensusToExecution(t *testing.T) { } func TestFinalityAfterReorg(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -295,7 +288,7 @@ func TestFinalityAfterReorg(t *testing.T) { defer cleanup() nodeConfig2ndNode := arbnode.ConfigDefaultL1NonSequencerTest() - execConfig2ndNode := ExecConfigDefaultTest(t) + execConfig2ndNode := ExecConfigDefaultTest(t, env.GetTestStateScheme()) testClient2ndNode, cleanup2ndNode := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: nodeConfig2ndNode, execConfig: execConfig2ndNode}) defer cleanup2ndNode() @@ -335,8 +328,6 @@ func TestFinalityAfterReorg(t *testing.T) { } func TestSetFinalityBlockHashMismatch(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -351,7 +342,7 @@ func TestSetFinalityBlockHashMismatch(t *testing.T) { defer cleanup() nodeConfig2ndNode := arbnode.ConfigDefaultL1NonSequencerTest() - execConfig2ndNode := ExecConfigDefaultTest(t) + execConfig2ndNode := ExecConfigDefaultTest(t, env.GetTestStateScheme()) testClient2ndNode, cleanup2ndNode := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: nodeConfig2ndNode, execConfig: execConfig2ndNode}) defer cleanup2ndNode() @@ -381,8 +372,6 @@ func TestSetFinalityBlockHashMismatch(t *testing.T) { } func TestFinalityDataNodeOutOfSync(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -397,7 +386,7 @@ func TestFinalityDataNodeOutOfSync(t *testing.T) { defer cleanup() nodeConfig2ndNode := arbnode.ConfigDefaultL1NonSequencerTest() - execConfig2ndNode := ExecConfigDefaultTest(t) + execConfig2ndNode := builder.ExecConfigDefaultTest(t, true) testClient2ndNode, cleanup2ndNode := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: nodeConfig2ndNode, execConfig: execConfig2ndNode}) defer cleanup2ndNode() diff --git a/system_tests/forwarder_test.go b/system_tests/forwarder_test.go index a0fb2e1f84..fae585b9c0 100644 --- a/system_tests/forwarder_test.go +++ b/system_tests/forwarder_test.go @@ -20,6 +20,7 @@ import ( "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/testhelpers/env" ) var transferAmount = big.NewInt(1e12) // amount of ether to use for transactions in tests @@ -40,7 +41,7 @@ func TestStaticForwarder(t *testing.T) { clientA := builder.L2.Client nodeConfigB := arbnode.ConfigDefaultL1Test() - execConfigB := ExecConfigDefaultTest(t) + execConfigB := ExecConfigDefaultTest(t, env.GetTestStateScheme()) execConfigB.Sequencer.Enable = false nodeConfigB.Sequencer = false nodeConfigB.DelayedSequencer.Enable = false @@ -111,7 +112,7 @@ func createForwardingNode(t *testing.T, builder *NodeBuilder, ipcPath string, re nodeConfig.Sequencer = false nodeConfig.DelayedSequencer.Enable = false nodeConfig.BatchPoster.Enable = false - execConfig := ExecConfigDefaultTest(t) + execConfig := builder.ExecConfigDefaultTest(t, true) execConfig.Sequencer.Enable = false execConfig.Forwarder.RedisUrl = redisUrl execConfig.ForwardingTarget = fallbackPath diff --git a/system_tests/full_celestia_challenge_test.backup_go b/system_tests/full_celestia_challenge_test.backup_go new file mode 100644 index 0000000000..3ff9d62793 --- /dev/null +++ b/system_tests/full_celestia_challenge_test.backup_go @@ -0,0 +1,481 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +// race detection makes things slow and miss timeouts +//go:build !race +// +build !race + +package arbtest + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "math/big" + "net/http" + _ "net/http/pprof" + "os" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" + + "github.com/offchainlabs/nitro/arbcompress" + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos" + "github.com/offchainlabs/nitro/arbstate" + "github.com/offchainlabs/nitro/arbstate/daprovider" + "github.com/offchainlabs/nitro/das/celestia" + celestiaTypes "github.com/offchainlabs/nitro/das/celestia/types" + "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/ospgen" + + "github.com/offchainlabs/nitro/staker" + "github.com/offchainlabs/nitro/validator" + "github.com/offchainlabs/nitro/validator/server_common" + "github.com/offchainlabs/nitro/validator/valnode" +) + +func init() { + go func() { + fmt.Println(http.ListenAndServe("localhost:6060", nil)) + }() +} + +func DeployOneStepProofEntryCelestia(t *testing.T, ctx context.Context, auth *bind.TransactOpts, client *ethclient.Client) common.Address { + osp0, tx, _, err := ospgen.DeployOneStepProver0(auth, client) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + ospMem, tx, _, err := ospgen.DeployOneStepProverMemory(auth, client) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + ospMath, tx, _, err := ospgen.DeployOneStepProverMath(auth, client) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + ospHostIo, tx, _, err := mocksgen.DeployOneStepProverHostIoCelestiaMock(auth, client) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + ospEntry, tx, _, err := ospgen.DeployOneStepProofEntry(auth, client, osp0, ospMem, ospMath, ospHostIo) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, client, tx) + Require(t, err) + + return ospEntry +} + +func writeTxToCelestiaBatch(writer io.Writer, tx *types.Transaction) error { + txData, err := tx.MarshalBinary() + if err != nil { + return err + } + var segment []byte + segment = append(segment, arbstate.BatchSegmentKindL2Message) + segment = append(segment, arbos.L2MessageKind_SignedTx) + segment = append(segment, txData...) + err = rlp.Encode(writer, segment) + return err +} + +func makeCelestiaBatch(t *testing.T, l2Node *arbnode.Node, celestiaDA *celestia.CelestiaDASClient, undecided bool, counterfactual bool, mockStream *mocksgen.Mockstream, deployer *bind.TransactOpts, l2Info *BlockchainTestInfo, backend *ethclient.Client, sequencer *bind.TransactOpts, seqInbox *mocksgen.SequencerInboxStub, seqInboxAddr common.Address, modStep int64) { + ctx := context.Background() + + batchBuffer := bytes.NewBuffer([]byte{}) + for i := int64(0); i < makeBatch_MsgsPerBatch; i++ { + value := i + if i == modStep { + value++ + } + err := writeTxToCelestiaBatch(batchBuffer, l2Info.PrepareTx("Owner", "Destination", 1000000, big.NewInt(value), []byte{})) + Require(t, err) + } + compressed, err := arbcompress.CompressWell(batchBuffer.Bytes()) + Require(t, err) + message := append([]byte{0}, compressed...) + message, err = celestiaDA.Store(ctx, message) + Require(t, err) + + buf := bytes.NewBuffer(message) + + header, err := buf.ReadByte() + Require(t, err) + if !celestiaTypes.IsCelestiaMessageHeaderByte(header) { + err := errors.New("tried to deserialize a message that doesn't have the Celestia header") + Require(t, err) + } + + blobPointer := celestiaTypes.BlobPointer{} + blobBytes := buf.Bytes() + err = blobPointer.UnmarshalBinary(blobBytes) + Require(t, err) + + dataCommitment, err := celestiaDA.Prover.Trpc.DataCommitment(ctx, blobPointer.BlockHeight-1, blobPointer.BlockHeight+1) + if err != nil { + t.Log("Error when fetching data commitment:", err) + } + Require(t, err) + mockStream.SubmitDataCommitment(deployer, [32]byte(dataCommitment.DataCommitment), blobPointer.BlockHeight-1, blobPointer.BlockHeight+1) + if counterfactual { + mockStream.UpdateGenesisState(deployer, (blobPointer.BlockHeight - 1100)) + } else if undecided { + t.Log("Block Height before change: ", blobPointer.BlockHeight) + mockStream.UpdateGenesisState(deployer, (blobPointer.BlockHeight - 100)) + } + seqNum := new(big.Int).Lsh(common.Big1, 256) + seqNum.Sub(seqNum, common.Big1) + tx, err := seqInbox.AddSequencerL2BatchFromOrigin8f111f3c(sequencer, seqNum, message, big.NewInt(1), common.Address{}, big.NewInt(0), big.NewInt(0)) + Require(t, err) + receipt, err := EnsureTxSucceeded(ctx, backend, tx) + Require(t, err) + + nodeSeqInbox, err := arbnode.NewSequencerInbox(backend, seqInboxAddr, 0) + Require(t, err) + batches, err := nodeSeqInbox.LookupBatchesInRange(ctx, receipt.BlockNumber, receipt.BlockNumber) + Require(t, err) + if len(batches) == 0 { + Fatal(t, "batch not found after AddSequencerL2BatchFromOrigin") + } + err = l2Node.InboxTracker.AddSequencerBatches(ctx, backend, batches) + Require(t, err) + _, err = l2Node.InboxTracker.GetBatchMetadata(0) + Require(t, err, "failed to get batch metadata after adding batch:") +} + +func RunCelestiaChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, challengeMsgIdx int64, undecided bool, counterFactual bool) { + + glogger := log.NewGlogHandler( + log.NewTerminalHandler(io.Writer(os.Stderr), false)) + glogger.Verbosity(log.LvlInfo) + log.SetDefault(log.NewLogger(glogger)) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + initialBalance := new(big.Int).Lsh(big.NewInt(1), 200) + l1Info := NewL1TestInfo(t) + l1Info.GenerateGenesisAccount("deployer", initialBalance) + l1Info.GenerateGenesisAccount("asserter", initialBalance) + l1Info.GenerateGenesisAccount("challenger", initialBalance) + l1Info.GenerateGenesisAccount("sequencer", initialBalance) + + chainConfig := params.ArbitrumDevTestChainConfig() + l1Info, l1Backend, _, _ := createTestL1BlockChain(t, l1Info) + conf := arbnode.ConfigDefaultL1Test() + conf.BlockValidator.Enable = false + conf.BatchPoster.Enable = false + conf.InboxReader.CheckDelay = time.Second + + deployerTxOpts := l1Info.GetDefaultTransactOpts("deployer", ctx) + blobstream, tx, mockStreamWrapper, err := mocksgen.DeployMockstream(&deployerTxOpts, l1Backend) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l1Backend, tx) + Require(t, err) + + conf.Celestia = celestia.DAConfig{ + Enable: true, + GasPrice: 0.1, + Rpc: "http://localhost:26658", + NamespaceId: "000008e5f679bf7116cb", + AuthToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJBbGxvdyI6WyJwdWJsaWMiLCJyZWFkIiwid3JpdGUiLCJhZG1pbiJdfQ.8iCpZJaiui7QPTCj4m5f2M7JyHkJtr6Xha0bmE5Vv7Y", + ValidatorConfig: &celestia.ValidatorConfig{ + TendermintRPC: "http://localhost:26657", + BlobstreamAddr: blobstream.Hex(), + }, + } + + t.Log("Blobstream Address: ", blobstream.Hex()) + + celestiaDa, err := celestia.NewCelestiaDA(&conf.Celestia, l1Backend) + Require(t, err) + // Initialize Mockstream before the tests + header, err := celestiaDa.Client.Header.NetworkHead(ctx) + Require(t, err) + mockStreamWrapper.Initialize(&deployerTxOpts, header.Height()) + + var valStack *node.Node + var mockSpawn *mockSpawner + if useStubs { + mockSpawn, valStack = createMockValidationNode(t, ctx, &valnode.TestValidationConfig.Arbitrator) + } else { + _, valStack = createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + } + configByValidationNode(conf, valStack) + + fatalErrChan := make(chan error, 10) + asserterRollupAddresses, initMessage := DeployOnTestL1(t, ctx, l1Info, l1Backend, chainConfig) + + sequencerTxOpts := l1Info.GetDefaultTransactOpts("sequencer", ctx) + asserterTxOpts := l1Info.GetDefaultTransactOpts("asserter", ctx) + challengerTxOpts := l1Info.GetDefaultTransactOpts("challenger", ctx) + + asserterBridgeAddr, asserterSeqInbox, asserterSeqInboxAddr := setupSequencerInboxStub(ctx, t, l1Info, l1Backend, chainConfig) + challengerBridgeAddr, challengerSeqInbox, challengerSeqInboxAddr := setupSequencerInboxStub(ctx, t, l1Info, l1Backend, chainConfig) + + asserterL2Info, asserterL2Stack, asserterL2ChainDb, asserterL2ArbDb, asserterL2Blockchain := createL2BlockChainWithStackConfig(t, nil, "", chainConfig, initMessage, nil, nil) + asserterRollupAddresses.Bridge = asserterBridgeAddr + asserterRollupAddresses.SequencerInbox = asserterSeqInboxAddr + asserterExec, err := gethexec.CreateExecutionNode(ctx, asserterL2Stack, asserterL2ChainDb, asserterL2Blockchain, l1Backend, gethexec.ConfigDefaultTest) + Require(t, err) + parentChainID := big.NewInt(1337) + asserterL2, err := arbnode.CreateNode(ctx, asserterL2Stack, asserterExec, asserterL2ArbDb, NewFetcherFromConfig(conf), chainConfig, l1Backend, asserterRollupAddresses, nil, nil, nil, fatalErrChan, parentChainID, nil) + Require(t, err) + err = asserterL2.Start(ctx) + Require(t, err) + + challengerL2Info, challengerL2Stack, challengerL2ChainDb, challengerL2ArbDb, challengerL2Blockchain := createL2BlockChainWithStackConfig(t, nil, "", chainConfig, initMessage, nil, nil) + challengerRollupAddresses := *asserterRollupAddresses + challengerRollupAddresses.Bridge = challengerBridgeAddr + challengerRollupAddresses.SequencerInbox = challengerSeqInboxAddr + challengerExec, err := gethexec.CreateExecutionNode(ctx, challengerL2Stack, challengerL2ChainDb, challengerL2Blockchain, l1Backend, gethexec.ConfigDefaultTest) + Require(t, err) + challengerL2, err := arbnode.CreateNode(ctx, challengerL2Stack, challengerExec, challengerL2ArbDb, NewFetcherFromConfig(conf), chainConfig, l1Backend, &challengerRollupAddresses, nil, nil, nil, fatalErrChan, parentChainID, nil) + Require(t, err) + err = challengerL2.Start(ctx) + Require(t, err) + + asserterL2Info.GenerateAccount("Destination") + challengerL2Info.SetFullAccountInfo("Destination", asserterL2Info.GetInfoWithPrivKey("Destination")) + + if challengeMsgIdx < 1 || challengeMsgIdx > 3*makeBatch_MsgsPerBatch { + Fatal(t, "challengeMsgIdx illegal") + } + + // seqNum := common.Big2 + makeCelestiaBatch(t, asserterL2, celestiaDa, undecided, counterFactual, mockStreamWrapper, &deployerTxOpts, asserterL2Info, l1Backend, &sequencerTxOpts, asserterSeqInbox, asserterSeqInboxAddr, -1) + makeCelestiaBatch(t, challengerL2, celestiaDa, undecided, counterFactual, mockStreamWrapper, &deployerTxOpts, challengerL2Info, l1Backend, &sequencerTxOpts, challengerSeqInbox, challengerSeqInboxAddr, challengeMsgIdx-1) + + // seqNum.Add(seqNum, common.Big1) + makeCelestiaBatch(t, asserterL2, celestiaDa, undecided, counterFactual, mockStreamWrapper, &deployerTxOpts, asserterL2Info, l1Backend, &sequencerTxOpts, asserterSeqInbox, asserterSeqInboxAddr, -1) + makeCelestiaBatch(t, challengerL2, celestiaDa, undecided, counterFactual, mockStreamWrapper, &deployerTxOpts, challengerL2Info, l1Backend, &sequencerTxOpts, challengerSeqInbox, challengerSeqInboxAddr, challengeMsgIdx-makeBatch_MsgsPerBatch-1) + + // seqNum.Add(seqNum, common.Big1) + makeCelestiaBatch(t, asserterL2, celestiaDa, undecided, counterFactual, mockStreamWrapper, &deployerTxOpts, asserterL2Info, l1Backend, &sequencerTxOpts, asserterSeqInbox, asserterSeqInboxAddr, -1) + makeCelestiaBatch(t, challengerL2, celestiaDa, undecided, counterFactual, mockStreamWrapper, &deployerTxOpts, challengerL2Info, l1Backend, &sequencerTxOpts, challengerSeqInbox, challengerSeqInboxAddr, challengeMsgIdx-makeBatch_MsgsPerBatch*2-1) + + trueSeqInboxAddr := challengerSeqInboxAddr + trueDelayedBridge := challengerBridgeAddr + expectedWinner := l1Info.GetAddress("challenger") + if asserterIsCorrect { + trueSeqInboxAddr = asserterSeqInboxAddr + trueDelayedBridge = asserterBridgeAddr + expectedWinner = l1Info.GetAddress("asserter") + } + ospEntry := DeployOneStepProofEntryCelestia(t, ctx, &deployerTxOpts, l1Backend) + + locator, err := server_common.NewMachineLocator("") + if err != nil { + Fatal(t, err) + } + var wasmModuleRoot common.Hash + if useStubs { + wasmModuleRoot = mockWasmModuleRoots[0] + } else { + wasmModuleRoot = locator.LatestWasmModuleRoot() + if (wasmModuleRoot == common.Hash{}) { + Fatal(t, "latest machine not found") + } + } + + asserterGenesis := asserterExec.ArbInterface.BlockChain().Genesis() + challengerGenesis := challengerExec.ArbInterface.BlockChain().Genesis() + if asserterGenesis.Hash() != challengerGenesis.Hash() { + Fatal(t, "asserter and challenger have different genesis hashes") + } + asserterLatestBlock := asserterExec.ArbInterface.BlockChain().CurrentBlock() + challengerLatestBlock := challengerExec.ArbInterface.BlockChain().CurrentBlock() + if asserterLatestBlock.Hash() == challengerLatestBlock.Hash() { + Fatal(t, "asserter and challenger have the same end block") + } + + asserterStartGlobalState := validator.GoGlobalState{ + BlockHash: asserterGenesis.Hash(), + Batch: 1, + PosInBatch: 0, + } + asserterEndGlobalState := validator.GoGlobalState{ + BlockHash: asserterLatestBlock.Hash(), + Batch: 4, + PosInBatch: 0, + } + numBlocks := asserterLatestBlock.Number.Uint64() - asserterGenesis.NumberU64() + + resultReceiver, challengeManagerAddr := CreateChallenge( + t, + ctx, + &deployerTxOpts, + l1Backend, + ospEntry, + trueSeqInboxAddr, + trueDelayedBridge, + wasmModuleRoot, + asserterStartGlobalState, + asserterEndGlobalState, + numBlocks, + l1Info.GetAddress("asserter"), + l1Info.GetAddress("challenger"), + ) + + confirmLatestBlock(ctx, t, l1Info, l1Backend) + + // Add the L1 backend to Celestia DA + celestiaDa.Prover.EthClient = l1Backend + + celestiaReader := celestiaTypes.NewReaderForCelestia(celestiaDa) + + asserterValidator, err := staker.NewStatelessBlockValidator(asserterL2.InboxReader, asserterL2.InboxTracker, asserterL2.TxStreamer, asserterExec.Recorder, asserterL2ArbDb, []daprovider.Reader{celestiaReader}, StaticFetcherFrom(t, &conf.BlockValidator), valStack) + if err != nil { + Fatal(t, err) + } + if useStubs { + asserterRecorder := newMockRecorder(asserterValidator, asserterL2.TxStreamer) + asserterValidator.OverrideRecorder(t, asserterRecorder) + } + err = asserterValidator.Start(ctx) + if err != nil { + Fatal(t, err) + } + defer asserterValidator.Stop() + asserterManager, err := staker.NewChallengeManager(ctx, l1Backend, &asserterTxOpts, asserterTxOpts.From, challengeManagerAddr, 1, asserterValidator, 0, 0) + if err != nil { + Fatal(t, err) + } + challengerValidator, err := staker.NewStatelessBlockValidator(challengerL2.InboxReader, challengerL2.InboxTracker, challengerL2.TxStreamer, challengerExec.Recorder, challengerL2ArbDb, []daprovider.Reader{celestiaReader}, StaticFetcherFrom(t, &conf.BlockValidator), valStack) + if err != nil { + Fatal(t, err) + } + if useStubs { + challengerRecorder := newMockRecorder(challengerValidator, challengerL2.TxStreamer) + challengerValidator.OverrideRecorder(t, challengerRecorder) + } + err = challengerValidator.Start(ctx) + if err != nil { + Fatal(t, err) + } + defer challengerValidator.Stop() + challengerManager, err := staker.NewChallengeManager(ctx, l1Backend, &challengerTxOpts, challengerTxOpts.From, challengeManagerAddr, 1, challengerValidator, 0, 0) + if err != nil { + Fatal(t, err) + } + + confirmLatestBlock(ctx, t, l1Info, l1Backend) + + for i := 0; i < 100; i++ { + var tx *types.Transaction + var currentCorrect bool + // Gas cost is slightly reduced if done in the same timestamp or block as previous call. + // This might make gas estimation undersestimate next move. + // Invoke a new L1 block, with a new timestamp, before estimating. + time.Sleep(time.Second) + SendWaitTestTransactions(t, ctx, l1Backend, []*types.Transaction{ + l1Info.PrepareTx("Faucet", "User", 30000, big.NewInt(1e12), nil), + }) + + if i%2 == 0 { + currentCorrect = !asserterIsCorrect + tx, err = challengerManager.Act(ctx) + } else { + currentCorrect = asserterIsCorrect + tx, err = asserterManager.Act(ctx) + } + if err != nil { + if !currentCorrect && (strings.Contains(err.Error(), "lost challenge") || + strings.Contains(err.Error(), "SAME_OSP_END") || + strings.Contains(err.Error(), "BAD_SEQINBOX_MESSAGE")) || + strings.Contains(err.Error(), "BLOBSTREAM_UNDECIDED") { + t.Log("challenge completed! asserter hit expected error:", err) + return + } else if (currentCorrect && counterFactual) && strings.Contains(err.Error(), "BAD_SEQINBOX_MESSAGE") { + t.Log("counterfactual challenge challenge completed! asserter hit expected error:", err) + return + } + Fatal(t, "challenge step", i, "hit error:", err) + } + if tx == nil { + Fatal(t, "no move") + } + + if useStubs { + if len(mockSpawn.ExecSpawned) != 0 { + if len(mockSpawn.ExecSpawned) != 1 { + Fatal(t, "bad number of spawned execRuns: ", len(mockSpawn.ExecSpawned)) + } + if mockSpawn.ExecSpawned[0] != uint64(challengeMsgIdx) { + Fatal(t, "wrong spawned execRuns: ", mockSpawn.ExecSpawned[0], " expected: ", challengeMsgIdx) + } + return + } + } + + _, err = EnsureTxSucceeded(ctx, l1Backend, tx) + if err != nil { + if !currentCorrect && strings.Contains(err.Error(), "BAD_SEQINBOX_MESSAGE") { + t.Log("challenge complete! Tx failed as expected:", err) + return + } + Fatal(t, err) + } + + confirmLatestBlock(ctx, t, l1Info, l1Backend) + + winner, err := resultReceiver.Winner(&bind.CallOpts{}) + if err != nil { + Fatal(t, err) + } + if winner == (common.Address{}) { + continue + } + if winner != expectedWinner { + Fatal(t, "wrong party won challenge") + } + } + + Fatal(t, "challenge timed out without winner") +} + +func TestCelestiaChallengeManagerFullAsserterIncorrect(t *testing.T) { + t.Parallel() + RunCelestiaChallengeTest(t, false, false, makeBatch_MsgsPerBatch+1, false, false) +} + +func TestCelestiaChallengeManagerFullAsserterCorrect(t *testing.T) { + t.Parallel() + RunCelestiaChallengeTest(t, true, false, makeBatch_MsgsPerBatch+2, false, false) +} + +func TestCelestiaChallengeManagerFullAsserterIncorrectUndecided(t *testing.T) { + t.Parallel() + RunCelestiaChallengeTest(t, false, false, makeBatch_MsgsPerBatch+1, true, false) +} + +func TestCelestiaChallengeManagerFullAsserterCorrectUndecided(t *testing.T) { + t.Parallel() + RunCelestiaChallengeTest(t, true, false, makeBatch_MsgsPerBatch+2, true, false) +} + +func TestCelestiaChallengeManagerFullAsserterIncorrectCounterfactual(t *testing.T) { + t.Parallel() + RunCelestiaChallengeTest(t, false, false, makeBatch_MsgsPerBatch+1, false, true) +} + +func TestCelestiaChallengeManagerFullAsserterCorrectCounterfactual(t *testing.T) { + t.Parallel() + RunCelestiaChallengeTest(t, true, false, makeBatch_MsgsPerBatch+2, false, true) +} diff --git a/system_tests/full_challenge_impl_test.go b/system_tests/full_challenge_impl_test.go index 5eea0a84bd..7c58ef42c9 100644 --- a/system_tests/full_challenge_impl_test.go +++ b/system_tests/full_challenge_impl_test.go @@ -236,7 +236,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() initialBalance := new(big.Int).Lsh(big.NewInt(1), 200) l1Info := builder.L1Info l1Info.GenerateGenesisAccount("deployer", initialBalance) @@ -257,7 +257,7 @@ func RunChallengeTest(t *testing.T, asserterIsCorrect bool, useStubs bool, chall mockSpawn, valStack = createMockValidationNode(t, ctx, &builder.valnodeConfig.Arbitrator) } else { // For now validation only works with HashScheme set - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) _, valStack = createTestValidationNode(t, ctx, builder.valnodeConfig) } configByValidationNode(conf, valStack) diff --git a/system_tests/full_challenge_test.go b/system_tests/full_challenge_test.go index 588b7821cf..77456a020e 100644 --- a/system_tests/full_challenge_test.go +++ b/system_tests/full_challenge_test.go @@ -14,13 +14,11 @@ import ( ) func TestChallengeManagerFullAsserterIncorrect(t *testing.T) { - t.Parallel() defaultWasmRootDir := "" RunChallengeTest(t, false, false, makeBatch_MsgsPerBatch+1, defaultWasmRootDir) } func TestChallengeManagerFullAsserterIncorrectWithPublishedMachine(t *testing.T) { - t.Parallel() cr, err := github.LatestConsensusRelease(context.Background()) Require(t, err) machPath := populateMachineDir(t, cr) @@ -28,13 +26,11 @@ func TestChallengeManagerFullAsserterIncorrectWithPublishedMachine(t *testing.T) } func TestChallengeManagerFullAsserterCorrect(t *testing.T) { - t.Parallel() defaultWasmRootDir := "" RunChallengeTest(t, true, false, makeBatch_MsgsPerBatch+2, defaultWasmRootDir) } func TestChallengeManagerFullAsserterCorrectWithPublishedMachine(t *testing.T) { - t.Parallel() cr, err := github.LatestConsensusRelease(context.Background()) Require(t, err) machPath := populateMachineDir(t, cr) diff --git a/system_tests/gas_dim_log_a_common_test.go b/system_tests/gas_dim_log_a_common_test.go index af85085528..5ae0d55725 100644 --- a/system_tests/gas_dim_log_a_common_test.go +++ b/system_tests/gas_dim_log_a_common_test.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/eth/tracers/native" "github.com/ethereum/go-ethereum/params" @@ -40,7 +39,6 @@ const ( // containing only the computation-only opcodes and that the gas in the computation // only opcodes is equal to the OneDimensionalGasCost. func TestDimLogComputationOnlyOpcodes(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -93,8 +91,6 @@ func gasDimensionTestSetup(t *testing.T, expectRevert bool) ( ctx, cancel = context.WithCancel(context.Background()) builder = NewNodeBuilder(ctx).DefaultConfig(t, true) builder.execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - builder.execConfig.Caching.StateScheme = rawdb.HashScheme if expectRevert { builder.execConfig.Sequencer.MaxRevertGasReject = 0 } @@ -174,6 +170,9 @@ func callDebugTraceTransactionWithLogger( var result json.RawMessage err := rpcClient.CallContext(ctx, &result, "debug_traceTransaction", txHash, map[string]interface{}{ "tracer": "txGasDimensionLogger", + "tracerConfig": map[string]interface{}{ + "debug": true, + }, }) Require(t, err) diff --git a/system_tests/gas_dim_log_call_test.go b/system_tests/gas_dim_log_call_test.go index 2896cfb7d5..7494f4e2ab 100644 --- a/system_tests/gas_dim_log_call_test.go +++ b/system_tests/gas_dim_log_call_test.go @@ -48,7 +48,6 @@ const ( // the callee is virgin (has never been seen before on the network) // there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) func TestDimLogCallColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -81,7 +80,6 @@ func TestDimLogCallColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { // the callee is virgin (has never been seen before on the network) // there is memory expansion, the CALL writes to memory outside the bounds of the original memory func TestDimLogCallColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -114,7 +112,6 @@ func TestDimLogCallColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { // the callee has been funded before (someone sent it money in the past) // there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) func TestDimLogCallColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -150,7 +147,6 @@ func TestDimLogCallColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { // the callee has been funded before (someone sent it money in the past) // there is memory expansion, the CALL writes to memory outside the bounds of the original memory func TestDimLogCallColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -186,7 +182,6 @@ func TestDimLogCallColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { // the callee has been funded before (someone sent it money in the past) // there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) func TestDimLogCallColdNoTransferContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -219,7 +214,6 @@ func TestDimLogCallColdNoTransferContractFundedMemUnchanged(t *testing.T) { // the callee has been funded before (someone sent it money in the past) // there is memory expansion, the CALL writes to memory outside the bounds of the original memory func TestDimLogCallColdNoTransferContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -252,7 +246,6 @@ func TestDimLogCallColdNoTransferContractFundedMemExpansion(t *testing.T) { // the callee is virgin (has never been seen before on the network) // there is no memory expansion as part of the CALL opcode itself (it writes to memory but stays inside the bounds of the original memory) func TestDimLogCallColdPayingNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -1726,3 +1719,35 @@ func TestDimLogCallCodeWarmPayingContractFundedMemExpansion(t *testing.T) { checkGasDimensionsMatch(t, expected, callLog) checkGasDimensionsEqualOneDimensionalGas(t, callLog) } + +// This tests a call that does another call, +// specifically a CALL that then does a DELEGATECALL +// and we check that the gas dimensions are correct +// for the parent. +func TestDimLogNestedCall(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployNestedCall) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployNestedTarget) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.Entrypoint, calleeAddress) + + traceResult := callDebugTraceTransactionWithLogger(t, ctx, builder, receipt.TxHash) + callLog := getSpecificDimensionLog(t, traceResult.DimensionLogs, "CALL") + + var callChildExecutionCost uint64 = 25921 // from the struct logger output + + expected := ExpectedGasCosts{ + OneDimensionalGasCost: params.WarmStorageReadCostEIP2929, + Computation: params.WarmStorageReadCostEIP2929, + StateAccess: 0, + StateGrowth: 0, + HistoryGrowth: 0, + StateGrowthRefund: 0, + ChildExecutionCost: callChildExecutionCost, + } + checkGasDimensionsMatch(t, expected, callLog) + checkGasDimensionsEqualOneDimensionalGas(t, callLog) +} diff --git a/system_tests/gas_dim_log_create_test.go b/system_tests/gas_dim_log_create_test.go index f10c809d16..5891b3d464 100644 --- a/system_tests/gas_dim_log_create_test.go +++ b/system_tests/gas_dim_log_create_test.go @@ -73,7 +73,6 @@ var ( // the history growth to be 0 // the state growth refund to be 0 func TestDimLogCreateNoTransferMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -103,7 +102,6 @@ func TestDimLogCreateNoTransferMemUnchanged(t *testing.T) { // in this test, we do a CREATE of a new contract with no transfer of value // and the creation writes to new additional memory, causing memory expansion func TestDimLogCreateNoTransferMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -136,7 +134,6 @@ func TestDimLogCreateNoTransferMemExpansion(t *testing.T) { // The gas costs are identical to the case with NoTransfer, see // the comments for TestDimLogCreateNoTransferMemUnchanged above func TestDimLogCreatePayingMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -168,7 +165,6 @@ func TestDimLogCreatePayingMemUnchanged(t *testing.T) { // in this test, we do a CREATE of a new contract with transfer of value // and the creation writes to new additional memory, causing memory expansion func TestDimLogCreatePayingMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -229,7 +225,6 @@ func TestDimLogCreatePayingMemExpansion(t *testing.T) { // the history growth to be 0 // the state growth refund to be 0 func TestDimLogCreate2NoTransferMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -261,7 +256,6 @@ func TestDimLogCreate2NoTransferMemUnchanged(t *testing.T) { // in this test, we do a CREATE2 of a new contract with no transfer of value // and the creation writes to new additional memory, causing memory expansion func TestDimLogCreate2NoTransferMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -296,7 +290,6 @@ func TestDimLogCreate2NoTransferMemExpansion(t *testing.T) { // The gas costs are identical to the case with NoTransfer, see // the comments for TestDimLogCreateNoTransferMemUnchanged above func TestDimLogCreate2PayingMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -328,7 +321,6 @@ func TestDimLogCreate2PayingMemUnchanged(t *testing.T) { // in this test, we do a CREATE2 of a new contract with transfer of value // and the creation writes to new additional memory, causing memory expansion func TestDimLogCreate2PayingMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_extcodecopy_test.go b/system_tests/gas_dim_log_extcodecopy_test.go index a58951161e..8fdc633c84 100644 --- a/system_tests/gas_dim_log_extcodecopy_test.go +++ b/system_tests/gas_dim_log_extcodecopy_test.go @@ -93,7 +93,6 @@ var extCodeCopyMemoryExpansionCost uint64 = 53 // 2500 + the minimum word cost, // and all other gas dimensions to be 0 func TestDimLogExtCodeCopyColdMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -132,7 +131,6 @@ func TestDimLogExtCodeCopyColdMemUnchanged(t *testing.T) { // the state access to be 2500 + the minimum word cost, // and all other gas dimensions to be 0 func TestDimLogExtCodeCopyColdMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -169,7 +167,6 @@ func TestDimLogExtCodeCopyColdMemExpansion(t *testing.T) { // just the minimum word cost, // and all other gas dimensions to be 0 func TestDimLogExtCodeCopyWarmMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -208,7 +205,6 @@ func TestDimLogExtCodeCopyWarmMemUnchanged(t *testing.T) { // the state access to be the minimum word cost, // and all other gas dimensions to be 0 func TestDimLogExtCodeCopyWarmMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_invalid_test.go b/system_tests/gas_dim_log_invalid_test.go index e125e9497f..35f90839dc 100644 --- a/system_tests/gas_dim_log_invalid_test.go +++ b/system_tests/gas_dim_log_invalid_test.go @@ -17,7 +17,6 @@ import ( // the invalid opcode uses all of the remaining gas in a transaction // and halts transaction execution func TestDimLogInvalid(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -97,7 +96,6 @@ func TestDimLogInvalid(t *testing.T) { // but the revert does not stop the entire transaction // execution, it's inside a try/catch func TestDimLogInvalidInTryCatch(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -151,7 +149,6 @@ func TestDimLogInvalidInTryCatchWithMemoryExpansion(t *testing.T) { } func TestDimLogRevert(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, true) defer cancel() defer cleanup() @@ -194,7 +191,6 @@ func TestDimLogRevert(t *testing.T) { } func TestDimLogRevertWithMessage(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, true) defer cancel() defer cleanup() @@ -237,7 +233,6 @@ func TestDimLogRevertWithMessage(t *testing.T) { } func TestDimLogRevertWithMemoryExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, true) defer cancel() defer cleanup() @@ -290,7 +285,6 @@ func TestDimLogRevertWithMemoryExpansion(t *testing.T) { // but the tracer should not fail // and the gas should still make sense func TestDimLogInvalidJump(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_log_test.go b/system_tests/gas_dim_log_log_test.go index 7a7dd70615..99d59f628f 100644 --- a/system_tests/gas_dim_log_log_test.go +++ b/system_tests/gas_dim_log_log_test.go @@ -49,7 +49,6 @@ const ( // Therefore we expect the one dimensional gas cost to be // 375, computation to be 375, and all other gas dimensions to be 0 func TestDimLogLog0TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -83,7 +82,6 @@ func TestDimLogLog0TopicsOnlyMemUnchanged(t *testing.T) { // Therefore we expect the one dimensional gas cost to be 375 + the data gas cost, // computation to be 375, the history growth to be 8 * 7, and all other gas dimensions to be 0 func TestDimLogLog0ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -157,7 +155,6 @@ func TestDimLogLog1TopicsOnlyMemUnchanged(t *testing.T) { // computation to be 375 + 119, the state access to be 0, the state growth to be 0, // the history growth to be 256 + 8 * 9, and the state growth refund to be 0 func TestDimLogLog1ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -194,7 +191,6 @@ func TestDimLogLog1ExtraDataMemUnchanged(t *testing.T) { // computation to be 375 + 2 * 119, the state access to be 0, the state growth to be 0, // the history growth to be 2 * 256, and the state growth refund to be 0 func TestDimLogLog2TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -233,7 +229,6 @@ func TestDimLogLog2TopicsOnlyMemUnchanged(t *testing.T) { // the state access to be 0, the state growth to be 0, // the history growth to be 2 * 256 + 32*8, and the state growth refund to be 0 func TestDimLogLog2ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -270,7 +265,6 @@ func TestDimLogLog2ExtraDataMemUnchanged(t *testing.T) { // computation to be 375 + 3 * 119, the state access to be 0, the state growth to be 0, // the history growth to be 3 * 256, and the state growth refund to be 0 func TestDimLogLog3TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -309,7 +303,6 @@ func TestDimLogLog3TopicsOnlyMemUnchanged(t *testing.T) { // the state access to be 0, the state growth to be 0, // the history growth to be 3 * 256 + 32*8, and the state growth refund to be 0 func TestDimLogLog3ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -346,7 +339,6 @@ func TestDimLogLog3ExtraDataMemUnchanged(t *testing.T) { // computation to be 375 + 4 * 119, the state access to be 0, the state growth to be 0, // the history growth to be 4 * 256, and the state growth refund to be 0 func TestDimLogLog4TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -385,7 +377,6 @@ func TestDimLogLog4TopicsOnlyMemUnchanged(t *testing.T) { // the state access to be 0, the state growth to be 0, // the history growth to be 4 * 256 + 32*8, and the state growth refund to be 0 func TestDimLogLog4ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -443,7 +434,6 @@ var logNMemoryExpansionCost uint64 = 6 // we expect the state access to be 0, the state growth to be 0, // the history growth to be 64*8, and the state growth refund to be 0 func TestDimLogLog0ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -484,7 +474,6 @@ func TestDimLogLog0ExtraDataMemExpansion(t *testing.T) { // computation to be 375 + 119 + 6, the state access to be 0, the state growth to be 0, // the history growth to be 256 + 8 * 64, and the state growth refund to be 0 func TestDimLogLog1ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -526,7 +515,6 @@ func TestDimLogLog1ExtraDataMemExpansion(t *testing.T) { // computation to be 375 + 2*119 + 6, the state access to be 0, the state growth to be 0, // the history growth to be 2*256 + 8 * 64, and the state growth refund to be 0 func TestDimLogLog2ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -568,7 +556,6 @@ func TestDimLogLog2ExtraDataMemExpansion(t *testing.T) { // computation to be 375 + 3*119 + 6, the state access to be 0, the state growth to be 0, // the history growth to be 3*256 + 8 * 64, and the state growth refund to be 0 func TestDimLogLog3ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -610,7 +597,6 @@ func TestDimLogLog3ExtraDataMemExpansion(t *testing.T) { // computation to be 375 + 4*119 + 6, the state access to be 0, the state growth to be 0, // the history growth to be 4*256 + 8 * 64, and the state growth refund to be 0 func TestDimLogLog4ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_precompiles_test.go b/system_tests/gas_dim_log_precompiles_test.go index 36c73dc3b2..c7b9f6a31c 100644 --- a/system_tests/gas_dim_log_precompiles_test.go +++ b/system_tests/gas_dim_log_precompiles_test.go @@ -4,7 +4,6 @@ import ( "fmt" "testing" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -16,7 +15,6 @@ import ( // This test calls the ArbBlockNumber function on the ArbSys precompile // which calls SLOAD inside the precompile, for this test func TestDimLogArbSysBlockNumberForSload(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cleanup() defer cancel() @@ -54,7 +52,6 @@ func TestDimLogActivateProgramForSstoreAndCall(t *testing.T) { func(builder *NodeBuilder) { // Match gasDimensionTestSetup settings builder.execConfig.Caching.Archive = true - builder.execConfig.Caching.StateScheme = rawdb.HashScheme builder.execConfig.Sequencer.MaxRevertGasReject = 0 builder.WithArbOSVersion(params.MaxArbosVersionSupported) }, diff --git a/system_tests/gas_dim_log_read_call_test.go b/system_tests/gas_dim_log_read_call_test.go index 82766f42e1..141b95f19a 100644 --- a/system_tests/gas_dim_log_read_call_test.go +++ b/system_tests/gas_dim_log_read_call_test.go @@ -48,7 +48,6 @@ const ( // state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallColdNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -85,7 +84,6 @@ func TestDimLogDelegateCallColdNoCodeMemUnchanged(t *testing.T) { // state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallWarmNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -124,7 +122,6 @@ func TestDimLogDelegateCallWarmNoCodeMemUnchanged(t *testing.T) { // state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallColdContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -163,7 +160,6 @@ func TestDimLogDelegateCallColdContractMemUnchanged(t *testing.T) { // state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallWarmContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -203,7 +199,6 @@ func TestDimLogDelegateCallWarmContractMemUnchanged(t *testing.T) { // state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallColdNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -241,7 +236,6 @@ func TestDimLogDelegateCallColdNoCodeMemExpansion(t *testing.T) { // state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallWarmNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -281,7 +275,6 @@ func TestDimLogDelegateCallWarmNoCodeMemExpansion(t *testing.T) { // state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallColdContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -321,7 +314,6 @@ func TestDimLogDelegateCallColdContractMemExpansion(t *testing.T) { // state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogDelegateCallWarmContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -356,7 +348,6 @@ func TestDimLogDelegateCallWarmContractMemExpansion(t *testing.T) { // computation to be 100 for the warm access list read cost, state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallColdNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -391,7 +382,6 @@ func TestDimLogStaticCallColdNoCodeMemUnchanged(t *testing.T) { // computation to be 100 for the warm access list read cost, state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallWarmNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -426,7 +416,6 @@ func TestDimLogStaticCallWarmNoCodeMemUnchanged(t *testing.T) { // computation to be 100 for the warm access list read cost, state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallColdContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -460,7 +449,6 @@ func TestDimLogStaticCallColdContractMemUnchanged(t *testing.T) { // computation to be 100 for the warm access list read cost, state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallWarmContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -495,7 +483,6 @@ func TestDimLogStaticCallWarmContractMemUnchanged(t *testing.T) { // computation to be 100+6 for the warm access list read cost, state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallColdNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -531,7 +518,6 @@ func TestDimLogStaticCallColdNoCodeMemExpansion(t *testing.T) { // computation to be 100+6 for the warm access list read cost, state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallWarmNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -568,7 +554,6 @@ func TestDimLogStaticCallWarmNoCodeMemExpansion(t *testing.T) { // computation to be 100+6 for the warm access list read cost, state access to be 2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallColdContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -604,7 +589,6 @@ func TestDimLogStaticCallColdContractMemExpansion(t *testing.T) { // computation to be 100+6 for the warm access list read cost, state access to be 0, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogStaticCallWarmContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_selfdestruct_test.go b/system_tests/gas_dim_log_selfdestruct_test.go index d29cffd49b..cf4c00fd0a 100644 --- a/system_tests/gas_dim_log_selfdestruct_test.go +++ b/system_tests/gas_dim_log_selfdestruct_test.go @@ -44,7 +44,6 @@ import ( // state access to be 5000+2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructColdNoTransferVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -80,7 +79,6 @@ func TestDimLogSelfdestructColdNoTransferVirgin(t *testing.T) { // state access to be 5000+2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructColdNoTransferFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -121,7 +119,6 @@ func TestDimLogSelfdestructColdNoTransferFunded(t *testing.T) { // state access to be 5000+2500, state growth to be 25000, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructColdPayingVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -161,7 +158,6 @@ func TestDimLogSelfdestructColdPayingVirgin(t *testing.T) { // state access to be 5000+2500, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructColdPayingFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -201,7 +197,6 @@ func TestDimLogSelfdestructColdPayingFunded(t *testing.T) { // state access to be 4900, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructWarmNoTransferVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -237,7 +232,6 @@ func TestDimLogSelfdestructWarmNoTransferVirgin(t *testing.T) { // state access to be 4900, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructWarmNoTransferFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -278,7 +272,6 @@ func TestDimLogSelfdestructWarmNoTransferFunded(t *testing.T) { // that gives us a computation of 100, state access of 4900, state growth of 25000, // history growth of 0, and state growth refund of 0 func TestDimLogSelfdestructWarmPayingVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -315,7 +308,6 @@ func TestDimLogSelfdestructWarmPayingVirgin(t *testing.T) { // state access to be 4900, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSelfdestructWarmPayingFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_sstore_test.go b/system_tests/gas_dim_log_sstore_test.go index a0d69f9c58..e4244c145f 100644 --- a/system_tests/gas_dim_log_sstore_test.go +++ b/system_tests/gas_dim_log_sstore_test.go @@ -42,7 +42,6 @@ import ( // we expect computation to be 0, state access to be 2200, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreColdZeroToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -73,7 +72,6 @@ func TestDimLogSstoreColdZeroToZero(t *testing.T) { // we expect computation to be 0, state read/write to be 2100, state growth to be 20000, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreColdZeroToNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -104,7 +102,6 @@ func TestDimLogSstoreColdZeroToNonZeroValue(t *testing.T) { // we expect computation to be 100, state read/write to be 0, state growth to be 4900, // history growth to be 0, and state growth refund to be 4800 func TestDimLogSstoreColdNonZeroValueToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -136,7 +133,6 @@ func TestDimLogSstoreColdNonZeroValueToZero(t *testing.T) { // we expect computation to be 0, state read/write to be 2200, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreColdNonZeroToSameNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -168,7 +164,6 @@ func TestDimLogSstoreColdNonZeroToSameNonZeroValue(t *testing.T) { // we expect computation to be 0, state read/write to be 5000, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreColdNonZeroToDifferentNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -198,7 +193,6 @@ func TestDimLogSstoreColdNonZeroToDifferentNonZeroValue(t *testing.T) { // we expect computation to be 0, state access to be 100, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreWarmZeroToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -228,7 +222,6 @@ func TestDimLogSstoreWarmZeroToZero(t *testing.T) { // we expect computation to be 0, state access to be 0, state growth to be 20000, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreWarmZeroToNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -259,7 +252,6 @@ func TestDimLogSstoreWarmZeroToNonZeroValue(t *testing.T) { // we expect computation to be 0, state read/write to be 2900, state growth to be 0, // history growth to be 0, and state growth refund to be 4800 func TestDimLogSstoreWarmNonZeroValueToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -290,7 +282,6 @@ func TestDimLogSstoreWarmNonZeroValueToZero(t *testing.T) { // we expect computation to be 0, state read/write to be 100, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreWarmNonZeroToSameNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -321,7 +312,6 @@ func TestDimLogSstoreWarmNonZeroToSameNonZeroValue(t *testing.T) { // we expect computation to be 0, state read/write to be 2900, state growth to be 0, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreWarmNonZeroToDifferentNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -355,7 +345,6 @@ func TestDimLogSstoreWarmNonZeroToDifferentNonZeroValue(t *testing.T) { // we expect computation to be 0, state read/write to be 0, state growth to be 100, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreMultipleWarmNonZeroToNonZeroToNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -390,7 +379,6 @@ func TestDimLogSstoreMultipleWarmNonZeroToNonZeroToNonZero(t *testing.T) { // we expect computation to be 0, state read/write to be 0, state growth to be 100, // history growth to be 0, and state growth refund to be 2800 func TestDimLogSstoreMultipleWarmNonZeroToNonZeroToSameNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -426,7 +414,6 @@ func TestDimLogSstoreMultipleWarmNonZeroToNonZeroToSameNonZero(t *testing.T) { // we expect computation to be 0, state read/write to be 0, state growth to be 100, // history growth to be 0, and state growth refund to be -4800 func TestDimLogSstoreMultipleWarmNonZeroToZeroToNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -462,7 +449,6 @@ func TestDimLogSstoreMultipleWarmNonZeroToZeroToNonZero(t *testing.T) { // we expect computation to be 0, state read/write to be 0, state growth to be 100, // history growth to be 0, and state growth refund to be -2000 func TestDimLogSstoreMultipleWarmNonZeroToZeroToSameNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -496,7 +482,6 @@ func TestDimLogSstoreMultipleWarmNonZeroToZeroToSameNonZero(t *testing.T) { // we expect computation to be 0, state read/write to be 0, state growth to be 100, // history growth to be 0, and state growth refund to be 0 func TestDimLogSstoreMultipleWarmZeroToNonZeroToNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -531,7 +516,6 @@ func TestDimLogSstoreMultipleWarmZeroToNonZeroToNonZero(t *testing.T) { // we expect computation to be 0, state read/write to be 0, state growth to be 100, // history growth to be 0, and state growth refund to be 19900 func TestDimLogSstoreMultipleWarmZeroToNonZeroBackToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_state_lookup_test.go b/system_tests/gas_dim_log_state_lookup_test.go index 502adecd32..8bfb3d3183 100644 --- a/system_tests/gas_dim_log_state_lookup_test.go +++ b/system_tests/gas_dim_log_state_lookup_test.go @@ -33,7 +33,6 @@ const ColdSloadCost = params.ColdSloadCostEIP2929 // and the state access to be 2500 (for the cold access cost of the address) // and all other gas dimensions to be 0 func TestDimLogBalanceCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -66,7 +65,6 @@ func TestDimLogBalanceCold(t *testing.T) { // the computation to be 100 (for the warm access cost of the address) // and all other gas dimensions to be 0 func TestDimLogBalanceWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -104,7 +102,6 @@ func TestDimLogBalanceWarm(t *testing.T) { // and the state access to be 2500 (for the cold access cost of the address) // and all other gas dimensions to be 0 func TestDimLogExtCodeSizeCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -137,7 +134,6 @@ func TestDimLogExtCodeSizeCold(t *testing.T) { // the computation to be 100 (for the warm access cost of the address) // and all other gas dimensions to be 0 func TestDimLogExtCodeSizeWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -175,7 +171,6 @@ func TestDimLogExtCodeSizeWarm(t *testing.T) { // and the state access to be 2500 (for the cold access cost of the address) // and all other gas dimensions to be 0 func TestDimLogExtCodeHashCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -208,7 +203,6 @@ func TestDimLogExtCodeHashCold(t *testing.T) { // the computation to be 100 (for the warm access cost of the address) // and all other gas dimensions to be 0 func TestDimLogExtCodeHashWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -249,7 +243,6 @@ func TestDimLogExtCodeHashWarm(t *testing.T) { // the state access to be 2000 (for the cold sload cost) // all others zero func TestDimLogSloadCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -283,7 +276,6 @@ func TestDimLogSloadCold(t *testing.T) { // the computation to be 100 (for the warm base access cost) // all others zero func TestDimLogSloadWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_log_stylus_test.go b/system_tests/gas_dim_log_stylus_test.go index 95070b3df9..b532986c77 100644 --- a/system_tests/gas_dim_log_stylus_test.go +++ b/system_tests/gas_dim_log_stylus_test.go @@ -9,7 +9,6 @@ import ( // This test calls the Keccak wasm program directly // which calls SLOAD inside the wasm for "free" func TestDimLogStylusKeccakForSload(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, true, gasDimPrecompileBuilderOpts()...) ctx := builder.ctx l2client := builder.L2.Client @@ -50,7 +49,6 @@ func TestDimLogStylusKeccakForSload(t *testing.T) { // flow from non-stylus to stylus // inside stylus, we call SLOAD for "free" func TestDimLogStylusKeccakForSloadFromProxy(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, true, gasDimPrecompileBuilderOpts()...) ctx := builder.ctx l2client := builder.L2.Client diff --git a/system_tests/gas_dim_tx_op_a_common_test.go b/system_tests/gas_dim_tx_op_a_common_test.go index 2751fd7ee4..708ee57742 100644 --- a/system_tests/gas_dim_tx_op_a_common_test.go +++ b/system_tests/gas_dim_tx_op_a_common_test.go @@ -28,7 +28,6 @@ type OpcodeSumTraceResult = native.TxGasDimensionByOpcodeExecutionResult // used for a transaction as the TX receipt, for // computation-only opcodes. func TestDimTxOpComputationOnlyOpcodes(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -45,6 +44,8 @@ func TestDimTxOpComputationOnlyOpcodes(t *testing.T) { // ######################################################################################################### // ######################################################################################################### +// helper function that automates calling debug_traceTransaction with the txGasDimensionByOpcode tracer +// and does some minimal validation of the result func callDebugTraceTransactionWithTxGasDimensionByOpcodeTracer( t *testing.T, ctx context.Context, @@ -57,6 +58,9 @@ func callDebugTraceTransactionWithTxGasDimensionByOpcodeTracer( var result json.RawMessage err := rpcClient.CallContext(ctx, &result, "debug_traceTransaction", txHash, map[string]interface{}{ "tracer": "txGasDimensionByOpcode", + "tracerConfig": map[string]interface{}{ + "debug": true, + }, }) Require(t, err) diff --git a/system_tests/gas_dim_tx_op_call_test.go b/system_tests/gas_dim_tx_op_call_test.go index dd79b424d7..f9ac57d2af 100644 --- a/system_tests/gas_dim_tx_op_call_test.go +++ b/system_tests/gas_dim_tx_op_call_test.go @@ -39,7 +39,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, virgin, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -56,7 +55,6 @@ func TestDimTxOpCallColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, virgin, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -73,7 +71,6 @@ func TestDimTxOpCallColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -93,7 +90,6 @@ func TestDimTxOpCallColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -113,7 +109,6 @@ func TestDimTxOpCallColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, contract callee, no value transfer, no memory expansion. func TestDimTxOpCallColdNoTransferContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -130,7 +125,6 @@ func TestDimTxOpCallColdNoTransferContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, contract callee, no value transfer, with memory expansion. func TestDimTxOpCallColdNoTransferContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -147,7 +141,6 @@ func TestDimTxOpCallColdNoTransferContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, virgin, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallColdPayingNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -166,7 +159,6 @@ func TestDimTxOpCallColdPayingNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, virgin, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallColdPayingNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -185,7 +177,6 @@ func TestDimTxOpCallColdPayingNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallColdPayingNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -204,7 +195,6 @@ func TestDimTxOpCallColdPayingNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallColdPayingNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -223,7 +213,6 @@ func TestDimTxOpCallColdPayingNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, contract callee, with value transfer, no memory expansion. func TestDimTxOpCallColdPayingContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -245,7 +234,6 @@ func TestDimTxOpCallColdPayingContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a cold, funded, contract callee, with value transfer, with memory expansion. func TestDimTxOpCallColdPayingContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -267,7 +255,6 @@ func TestDimTxOpCallColdPayingContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, virgin, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -284,7 +271,6 @@ func TestDimTxOpCallWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, virgin, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -301,7 +287,6 @@ func TestDimTxOpCallWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -321,7 +306,6 @@ func TestDimTxOpCallWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -341,7 +325,6 @@ func TestDimTxOpCallWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, contract callee, no value transfer, no memory expansion. func TestDimTxOpCallWarmNoTransferContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -358,7 +341,6 @@ func TestDimTxOpCallWarmNoTransferContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, contract callee, no value transfer, with memory expansion. func TestDimTxOpCallWarmNoTransferContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -378,7 +360,6 @@ func TestDimTxOpCallWarmNoTransferContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, virgin, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -399,7 +380,6 @@ func TestDimTxOpCallWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, virgin, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallWarmPayingNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -420,7 +400,6 @@ func TestDimTxOpCallWarmPayingNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -442,7 +421,6 @@ func TestDimTxOpCallWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallWarmPayingNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -464,7 +442,6 @@ func TestDimTxOpCallWarmPayingNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, contract callee, with value transfer, no memory expansion. func TestDimTxOpCallWarmPayingContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -486,7 +463,6 @@ func TestDimTxOpCallWarmPayingContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALL to a warm, funded, contract callee, with value transfer, with memory expansion. func TestDimTxOpCallWarmPayingContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -522,7 +498,6 @@ func TestDimTxOpCallWarmPayingContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, virgin, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallCodeColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -539,7 +514,6 @@ func TestDimTxOpCallCodeColdNoTransferNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, virgin, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallCodeColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -556,7 +530,6 @@ func TestDimTxOpCallCodeColdNoTransferNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallCodeColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -576,7 +549,6 @@ func TestDimTxOpCallCodeColdNoTransferNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallCodeColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -596,7 +568,6 @@ func TestDimTxOpCallCodeColdNoTransferNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, contract callee, no value transfer, no memory expansion. func TestDimTxOpCallCodeColdNoTransferContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -613,7 +584,6 @@ func TestDimTxOpCallCodeColdNoTransferContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, contract callee, no value transfer, with memory expansion. func TestDimTxOpCallCodeColdNoTransferContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -630,7 +600,6 @@ func TestDimTxOpCallCodeColdNoTransferContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, virgin, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallCodeColdPayingNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -649,7 +618,6 @@ func TestDimTxOpCallCodeColdPayingNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, virgin, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallCodeColdPayingNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -668,7 +636,6 @@ func TestDimTxOpCallCodeColdPayingNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallCodeColdPayingNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -687,7 +654,6 @@ func TestDimTxOpCallCodeColdPayingNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallCodeColdPayingNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -706,7 +672,6 @@ func TestDimTxOpCallCodeColdPayingNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, contract callee, with value transfer, no memory expansion. func TestDimTxOpCallCodeColdPayingContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -728,7 +693,6 @@ func TestDimTxOpCallCodeColdPayingContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a cold, funded, contract callee, with value transfer, with memory expansion. func TestDimTxOpCallCodeColdPayingContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -750,7 +714,6 @@ func TestDimTxOpCallCodeColdPayingContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, virgin, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallCodeWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -767,7 +730,6 @@ func TestDimTxOpCallCodeWarmNoTransferNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, virgin, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallCodeWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -784,7 +746,6 @@ func TestDimTxOpCallCodeWarmNoTransferNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, no-code callee, no value transfer, no memory expansion. func TestDimTxOpCallCodeWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -804,7 +765,6 @@ func TestDimTxOpCallCodeWarmNoTransferNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, no-code callee, no value transfer, with memory expansion. func TestDimTxOpCallCodeWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -824,7 +784,6 @@ func TestDimTxOpCallCodeWarmNoTransferNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, contract callee, no value transfer, no memory expansion. func TestDimTxOpCallCodeWarmNoTransferContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -841,7 +800,6 @@ func TestDimTxOpCallCodeWarmNoTransferContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, contract callee, no value transfer, with memory expansion. func TestDimTxOpCallCodeWarmNoTransferContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -861,7 +819,6 @@ func TestDimTxOpCallCodeWarmNoTransferContractFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, virgin, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallCodeWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -882,7 +839,6 @@ func TestDimTxOpCallCodeWarmPayingNoCodeVirginMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, virgin, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallCodeWarmPayingNoCodeVirginMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -903,7 +859,6 @@ func TestDimTxOpCallCodeWarmPayingNoCodeVirginMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, no-code callee, with value transfer, no memory expansion. func TestDimTxOpCallCodeWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -925,7 +880,6 @@ func TestDimTxOpCallCodeWarmPayingNoCodeFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, no-code callee, with value transfer, with memory expansion. func TestDimTxOpCallCodeWarmPayingNoCodeFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -947,7 +901,6 @@ func TestDimTxOpCallCodeWarmPayingNoCodeFundedMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, contract callee, with value transfer, no memory expansion. func TestDimTxOpCallCodeWarmPayingContractFundedMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -969,7 +922,6 @@ func TestDimTxOpCallCodeWarmPayingContractFundedMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CALLCODE to a warm, funded, contract callee, with value transfer, with memory expansion. func TestDimTxOpCallCodeWarmPayingContractFundedMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -986,3 +938,18 @@ func TestDimTxOpCallCodeWarmPayingContractFundedMemExpansion(t *testing.T) { TxOpTraceAndCheck(t, ctx, builder, receipt) } + +// This tests a call that does another call, +// specifically a CALL that then does a DELEGATECALL +func TestDimTxOpCallNestedCall(t *testing.T) { + ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) + defer cancel() + defer cleanup() + + _, caller := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployNestedCall) + calleeAddress, _ := deployGasDimensionTestContract(t, builder, auth, gas_dimensionsgen.DeployNestedTarget) + + receipt := callOnContractWithOneArg(t, builder, auth, caller.Entrypoint, calleeAddress) + + TxOpTraceAndCheck(t, ctx, builder, receipt) +} diff --git a/system_tests/gas_dim_tx_op_create_test.go b/system_tests/gas_dim_tx_op_create_test.go index 69248537f0..a67e40dded 100644 --- a/system_tests/gas_dim_tx_op_create_test.go +++ b/system_tests/gas_dim_tx_op_create_test.go @@ -27,7 +27,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE operation with no value transfer and no memory expansion. func TestDimTxOpCreateNoTransferMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -43,7 +42,6 @@ func TestDimTxOpCreateNoTransferMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE operation with no value transfer and memory expansion. func TestDimTxOpCreateNoTransferMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -59,7 +57,6 @@ func TestDimTxOpCreateNoTransferMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE operation with value transfer and no memory expansion. func TestDimTxOpCreatePayingMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -77,7 +74,6 @@ func TestDimTxOpCreatePayingMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE operation with value transfer and memory expansion. func TestDimTxOpCreatePayingMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -99,7 +95,6 @@ func TestDimTxOpCreatePayingMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE2 operation with no value transfer and no memory expansion. func TestDimTxOpCreate2NoTransferMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -115,7 +110,6 @@ func TestDimTxOpCreate2NoTransferMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE2 operation with no value transfer and memory expansion. func TestDimTxOpCreate2NoTransferMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -131,7 +125,6 @@ func TestDimTxOpCreate2NoTransferMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE2 operation with value transfer and no memory expansion. func TestDimTxOpCreate2PayingMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -147,7 +140,6 @@ func TestDimTxOpCreate2PayingMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: CREATE2 operation with value transfer and memory expansion. func TestDimTxOpCreate2PayingMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_extcodecopy_test.go b/system_tests/gas_dim_tx_op_extcodecopy_test.go index 2787083452..6bcc972c70 100644 --- a/system_tests/gas_dim_tx_op_extcodecopy_test.go +++ b/system_tests/gas_dim_tx_op_extcodecopy_test.go @@ -27,7 +27,6 @@ import ( // Scenario: EXTCODECOPY with cold code access, no memory expansion. // Expected: 2600 gas total (100 computation + 2500 state access + minimum word cost). func TestDimTxOpExtCodeCopyColdMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -43,7 +42,6 @@ func TestDimTxOpExtCodeCopyColdMemUnchanged(t *testing.T) { // Scenario: EXTCODECOPY with cold code access and memory expansion. // Expected: 2600 gas + memory expansion cost (100 computation + 2500 state access + minimum word cost + memory expansion). func TestDimTxOpExtCodeCopyColdMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -59,7 +57,6 @@ func TestDimTxOpExtCodeCopyColdMemExpansion(t *testing.T) { // Scenario: EXTCODECOPY with warm code access, no memory expansion. // Expected: 100 gas total (100 computation + minimum word cost). func TestDimTxOpExtCodeCopyWarmMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -75,7 +72,6 @@ func TestDimTxOpExtCodeCopyWarmMemUnchanged(t *testing.T) { // Scenario: EXTCODECOPY with warm code access and memory expansion. // Expected: 100 gas + memory expansion cost (100 computation + minimum word cost + memory expansion). func TestDimTxOpExtCodeCopyWarmMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_invalid_test.go b/system_tests/gas_dim_tx_op_invalid_test.go index d72110a49d..53360aec7a 100644 --- a/system_tests/gas_dim_tx_op_invalid_test.go +++ b/system_tests/gas_dim_tx_op_invalid_test.go @@ -19,7 +19,6 @@ import ( // but the tracer should not fail // and the gas should still make sense func TestDimTxOpInvalid(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -59,7 +58,6 @@ func TestDimTxOpInvalid(t *testing.T) { // but the revert does not stop the entire transaction // execution, it's inside a try/catch func TestDimTxOpRevertInTryCatch(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -75,7 +73,6 @@ func TestDimTxOpRevertInTryCatch(t *testing.T) { // execution, it's inside a try/catch // and the revert has a memory expansion func TestDimTxOpRevertInTryCatchWithMemoryExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -91,7 +88,6 @@ func TestDimTxOpRevertInTryCatchWithMemoryExpansion(t *testing.T) { // but the tracer should not fail // and the gas should still make sense func TestDimTxOpRevert(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, true) defer cancel() defer cleanup() @@ -129,7 +125,6 @@ func TestDimTxOpRevert(t *testing.T) { // but the tracer should not fail // and the gas should still make sense func TestDimTxOpRevertWithMessage(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, true) defer cancel() defer cleanup() @@ -163,7 +158,6 @@ func TestDimTxOpRevertWithMessage(t *testing.T) { } func TestDimTxOpRevertWithMemoryExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, true) defer cancel() defer cleanup() @@ -201,7 +195,6 @@ func TestDimTxOpRevertWithMemoryExpansion(t *testing.T) { // but the tracer should not fail // and the gas should still make sense func TestDimTxOpInvalidJump(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_log_test.go b/system_tests/gas_dim_tx_op_log_test.go index f312a6fe83..6560404333 100644 --- a/system_tests/gas_dim_tx_op_log_test.go +++ b/system_tests/gas_dim_tx_op_log_test.go @@ -36,7 +36,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG0 with no topics, no data, no memory expansion. func TestDimTxOpLog0TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -51,7 +50,6 @@ func TestDimTxOpLog0TopicsOnlyMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG0 with no topics, 7 bytes of data, no memory expansion. func TestDimTxOpLog0ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -66,7 +64,6 @@ func TestDimTxOpLog0ExtraDataMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG1 with one topic, no data, no memory expansion. func TestDimTxOpLog1TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -81,7 +78,6 @@ func TestDimTxOpLog1TopicsOnlyMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG1 with one topic, 9 bytes of data, no memory expansion. func TestDimTxOpLog1ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -96,7 +92,6 @@ func TestDimTxOpLog1ExtraDataMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG2 with two topics, no data, no memory expansion. func TestDimTxOpLog2TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -111,7 +106,6 @@ func TestDimTxOpLog2TopicsOnlyMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG2 with two topics, 32 bytes of data (address), no memory expansion. func TestDimTxOpLog2ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -126,7 +120,6 @@ func TestDimTxOpLog2ExtraDataMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG3 with three topics, no data, no memory expansion. func TestDimTxOpLog3TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -141,7 +134,6 @@ func TestDimTxOpLog3TopicsOnlyMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG3 with three topics, 32 bytes of data (bytes32), no memory expansion. func TestDimTxOpLog3ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -156,7 +148,6 @@ func TestDimTxOpLog3ExtraDataMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG4 with four topics, no data, no memory expansion. func TestDimTxOpLog4TopicsOnlyMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -171,7 +162,6 @@ func TestDimTxOpLog4TopicsOnlyMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG4 with four topics, 32 bytes of data (bytes32), no memory expansion. func TestDimTxOpLog4ExtraDataMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -186,7 +176,6 @@ func TestDimTxOpLog4ExtraDataMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG0 with no topics, 64 bytes of data, memory expansion from 96 to 160 bytes. func TestDimTxOpLog0ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -201,7 +190,6 @@ func TestDimTxOpLog0ExtraDataMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG1 with one topic, 64 bytes of data, memory expansion from 96 to 160 bytes. func TestDimTxOpLog1ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -216,7 +204,6 @@ func TestDimTxOpLog1ExtraDataMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG2 with two topics, 64 bytes of data, memory expansion from 96 to 160 bytes. func TestDimTxOpLog2ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -231,7 +218,6 @@ func TestDimTxOpLog2ExtraDataMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG3 with three topics, 64 bytes of data, memory expansion from 96 to 160 bytes. func TestDimTxOpLog3ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -246,7 +232,6 @@ func TestDimTxOpLog3ExtraDataMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: LOG4 with four topics, 64 bytes of data, memory expansion from 96 to 160 bytes. func TestDimTxOpLog4ExtraDataMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_precompiles_test.go b/system_tests/gas_dim_tx_op_precompiles_test.go index 7117c642b8..705bb74f07 100644 --- a/system_tests/gas_dim_tx_op_precompiles_test.go +++ b/system_tests/gas_dim_tx_op_precompiles_test.go @@ -3,7 +3,6 @@ package arbtest import ( "testing" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -14,7 +13,6 @@ import ( // this test calls the ArbBlockNumber function on the ArbSys precompile func TestDimTxOpArbSysBlockNumberForSload(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cleanup() defer cancel() @@ -33,7 +31,6 @@ func TestDimTxOpArbSysBlockNumberForSload(t *testing.T) { // that in turn calls ActivateProgram on the ArbWasm precompile // this tests that the logic for counting gas works from a proxy contract func TestDimTxOpArbWasmActivateProgramForSstoreAndCallFromProxy(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, false, gasDimPrecompileBuilderOpts()...) ctx := builder.ctx l2client := builder.L2.Client @@ -61,7 +58,6 @@ func TestDimTxOpArbWasmActivateProgramForSstoreAndCallFromProxy(t *testing.T) { // this test calls the ActivateProgram function on the ArbWasm precompile // which calls SSTORE and CALL inside the precompile, for this test func TestDimTxOpActivateProgramForSstoreAndCall(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, false, gasDimPrecompileBuilderOpts()...) ctx := builder.ctx l2client := builder.L2.Client @@ -92,7 +88,6 @@ func gasDimPrecompileBuilderOpts() []func(*NodeBuilder) { builderOpts := func(builder *NodeBuilder) { // Match gasDimensionTestSetup settings builder.execConfig.Caching.Archive = true - builder.execConfig.Caching.StateScheme = rawdb.HashScheme builder.execConfig.Sequencer.MaxRevertGasReject = 0 builder.WithArbOSVersion(params.MaxArbosVersionSupported) } diff --git a/system_tests/gas_dim_tx_op_read_call_test.go b/system_tests/gas_dim_tx_op_read_call_test.go index 88aba0249c..131334d86d 100644 --- a/system_tests/gas_dim_tx_op_read_call_test.go +++ b/system_tests/gas_dim_tx_op_read_call_test.go @@ -33,7 +33,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a cold, no-code address, no memory expansion. func TestDimTxOpDelegateCallColdNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -50,7 +49,6 @@ func TestDimTxOpDelegateCallColdNoCodeMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a warm, no-code address, no memory expansion. func TestDimTxOpDelegateCallWarmNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -67,7 +65,6 @@ func TestDimTxOpDelegateCallWarmNoCodeMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a cold contract address, no memory expansion. func TestDimTxOpDelegateCallColdContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -84,7 +81,6 @@ func TestDimTxOpDelegateCallColdContractMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a warm contract address, no memory expansion. func TestDimTxOpDelegateCallWarmContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -102,7 +98,6 @@ func TestDimTxOpDelegateCallWarmContractMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a cold, no-code address, with memory expansion. func TestDimTxOpDelegateCallColdNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -119,7 +114,6 @@ func TestDimTxOpDelegateCallColdNoCodeMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a warm, no-code address, with memory expansion. func TestDimTxOpDelegateCallWarmNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -136,7 +130,6 @@ func TestDimTxOpDelegateCallWarmNoCodeMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a cold contract address, with memory expansion. func TestDimTxOpDelegateCallColdContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -153,7 +146,6 @@ func TestDimTxOpDelegateCallColdContractMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: DELEGATECALL to a warm contract address, with memory expansion. func TestDimTxOpDelegateCallWarmContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -170,7 +162,6 @@ func TestDimTxOpDelegateCallWarmContractMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a cold, no-code address, no memory expansion. func TestDimTxOpStaticCallColdNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -187,7 +178,6 @@ func TestDimTxOpStaticCallColdNoCodeMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a warm, no-code address, no memory expansion. func TestDimTxOpStaticCallWarmNoCodeMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -204,7 +194,6 @@ func TestDimTxOpStaticCallWarmNoCodeMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a cold contract address, no memory expansion. func TestDimTxOpStaticCallColdContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -220,7 +209,6 @@ func TestDimTxOpStaticCallColdContractMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a warm contract address, no memory expansion. func TestDimTxOpStaticCallWarmContractMemUnchanged(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -236,7 +224,6 @@ func TestDimTxOpStaticCallWarmContractMemUnchanged(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a cold, no-code address, with memory expansion. func TestDimTxOpStaticCallColdNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -253,7 +240,6 @@ func TestDimTxOpStaticCallColdNoCodeMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a warm, no-code address, with memory expansion. func TestDimTxOpStaticCallWarmNoCodeMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -270,7 +256,6 @@ func TestDimTxOpStaticCallWarmNoCodeMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a cold contract address, with memory expansion. func TestDimTxOpStaticCallColdContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -286,7 +271,6 @@ func TestDimTxOpStaticCallColdContractMemExpansion(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: STATICCALL to a warm contract address, with memory expansion. func TestDimTxOpStaticCallWarmContractMemExpansion(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_selfdestruct_test.go b/system_tests/gas_dim_tx_op_selfdestruct_test.go index d80e525d3a..d1c2a23ca3 100644 --- a/system_tests/gas_dim_tx_op_selfdestruct_test.go +++ b/system_tests/gas_dim_tx_op_selfdestruct_test.go @@ -36,7 +36,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a cold, virgin address, no value transfer. func TestDimTxOpSelfdestructColdNoTransferVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -54,7 +53,6 @@ func TestDimTxOpSelfdestructColdNoTransferVirgin(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a cold, funded address, no value transfer. func TestDimTxOpSelfdestructColdNoTransferFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -76,7 +74,6 @@ func TestDimTxOpSelfdestructColdNoTransferFunded(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a cold, virgin address with value transfer. func TestDimTxOpSelfdestructColdPayingVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -97,7 +94,6 @@ func TestDimTxOpSelfdestructColdPayingVirgin(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a cold, funded address with value transfer. func TestDimTxOpSelfdestructColdPayingFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -119,7 +115,6 @@ func TestDimTxOpSelfdestructColdPayingFunded(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a warm, virgin address, no value transfer. func TestDimTxOpSelfdestructWarmNoTransferVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -137,7 +132,6 @@ func TestDimTxOpSelfdestructWarmNoTransferVirgin(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a warm, funded address, no value transfer. func TestDimTxOpSelfdestructWarmNoTransferFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -159,7 +153,6 @@ func TestDimTxOpSelfdestructWarmNoTransferFunded(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a warm, virgin address with value transfer. func TestDimTxOpSelfdestructWarmPayingVirgin(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -180,7 +173,6 @@ func TestDimTxOpSelfdestructWarmPayingVirgin(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SELFDESTRUCT to a warm, funded address with value transfer. func TestDimTxOpSelfdestructWarmPayingFunded(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_sstore_test.go b/system_tests/gas_dim_tx_op_sstore_test.go index 4f69162266..6aa5cf2f88 100644 --- a/system_tests/gas_dim_tx_op_sstore_test.go +++ b/system_tests/gas_dim_tx_op_sstore_test.go @@ -27,7 +27,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE cold slot from 0 to 0 (no state change). func TestDimTxOpSstoreColdZeroToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -42,7 +41,6 @@ func TestDimTxOpSstoreColdZeroToZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE cold slot from 0 to non-zero (state growth). func TestDimTxOpSstoreColdZeroToNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -57,7 +55,6 @@ func TestDimTxOpSstoreColdZeroToNonZeroValue(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE cold slot from non-zero to 0 (state cleanup with refund). func TestDimTxOpSstoreColdNonZeroValueToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -72,7 +69,6 @@ func TestDimTxOpSstoreColdNonZeroValueToZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE cold slot from non-zero to same non-zero value (no state change). func TestDimTxOpSstoreColdNonZeroToSameNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -87,7 +83,6 @@ func TestDimTxOpSstoreColdNonZeroToSameNonZeroValue(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE cold slot from non-zero to different non-zero value (state update). func TestDimTxOpSstoreColdNonZeroToDifferentNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -102,7 +97,6 @@ func TestDimTxOpSstoreColdNonZeroToDifferentNonZeroValue(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE warm slot from 0 to 0 (no state change). func TestDimTxOpSstoreWarmZeroToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -117,7 +111,6 @@ func TestDimTxOpSstoreWarmZeroToZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE warm slot from 0 to non-zero (state growth). func TestDimTxOpSstoreWarmZeroToNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -132,7 +125,6 @@ func TestDimTxOpSstoreWarmZeroToNonZeroValue(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE warm slot from non-zero to 0 (state cleanup with refund). func TestDimTxOpSstoreWarmNonZeroValueToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -147,7 +139,6 @@ func TestDimTxOpSstoreWarmNonZeroValueToZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE warm slot from non-zero to same non-zero value (no state change). func TestDimTxOpSstoreWarmNonZeroToSameNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -162,7 +153,6 @@ func TestDimTxOpSstoreWarmNonZeroToSameNonZeroValue(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SSTORE warm slot from non-zero to different non-zero value (state update). func TestDimTxOpSstoreWarmNonZeroToDifferentNonZeroValue(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -177,7 +167,6 @@ func TestDimTxOpSstoreWarmNonZeroToDifferentNonZeroValue(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: Multiple SSTORE operations on warm slot: non-zero -> non-zero -> different non-zero. func TestDimTxOpSstoreMultipleWarmNonZeroToNonZeroToNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -192,7 +181,6 @@ func TestDimTxOpSstoreMultipleWarmNonZeroToNonZeroToNonZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: Multiple SSTORE operations on warm slot: non-zero -> non-zero -> same non-zero (with refund). func TestDimTxOpSstoreMultipleWarmNonZeroToNonZeroToSameNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -207,7 +195,6 @@ func TestDimTxOpSstoreMultipleWarmNonZeroToNonZeroToSameNonZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: Multiple SSTORE operations on warm slot: non-zero -> zero -> non-zero (refund adjustment). func TestDimTxOpSstoreMultipleWarmNonZeroToZeroToNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -222,7 +209,6 @@ func TestDimTxOpSstoreMultipleWarmNonZeroToZeroToNonZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: Multiple SSTORE operations on warm slot: non-zero -> zero -> same non-zero (refund adjustment). func TestDimTxOpSstoreMultipleWarmNonZeroToZeroToSameNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -237,7 +223,6 @@ func TestDimTxOpSstoreMultipleWarmNonZeroToZeroToSameNonZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: Multiple SSTORE operations on warm slot: zero -> non-zero -> different non-zero. func TestDimTxOpSstoreMultipleWarmZeroToNonZeroToNonZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -252,7 +237,6 @@ func TestDimTxOpSstoreMultipleWarmZeroToNonZeroToNonZero(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: Multiple SSTORE operations on warm slot: zero -> non-zero -> zero (with refund). func TestDimTxOpSstoreMultipleWarmZeroToNonZeroBackToZero(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_state_lookup_test.go b/system_tests/gas_dim_tx_op_state_lookup_test.go index 76f8cca86b..8ba284f76a 100644 --- a/system_tests/gas_dim_tx_op_state_lookup_test.go +++ b/system_tests/gas_dim_tx_op_state_lookup_test.go @@ -26,7 +26,6 @@ import ( // and that all gas dimension components sum to the total gas consumed. // Scenario: BALANCE operation on a cold address (not in access list). func TestDimTxOpBalanceCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -41,7 +40,6 @@ func TestDimTxOpBalanceCold(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: BALANCE operation on a warm address (in access list). func TestDimTxOpBalanceWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -60,7 +58,6 @@ func TestDimTxOpBalanceWarm(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: EXTCODESIZE operation on a cold address (not in access list). func TestDimTxOpExtCodeSizeCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -75,7 +72,6 @@ func TestDimTxOpExtCodeSizeCold(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: EXTCODESIZE operation on a warm address (in access list). func TestDimTxOpExtCodeSizeWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -94,7 +90,6 @@ func TestDimTxOpExtCodeSizeWarm(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: EXTCODEHASH operation on a cold address (not in access list). func TestDimTxOpExtCodeHashCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -109,7 +104,6 @@ func TestDimTxOpExtCodeHashCold(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: EXTCODEHASH operation on a warm address (in access list). func TestDimTxOpExtCodeHashWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -130,7 +124,6 @@ func TestDimTxOpExtCodeHashWarm(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SLOAD operation on a cold storage slot (not previously accessed). func TestDimTxOpSloadCold(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() @@ -145,7 +138,6 @@ func TestDimTxOpSloadCold(t *testing.T) { // and that all gas dimension components sum to the total gas consumed. // Scenario: SLOAD operation on a warm storage slot (previously accessed). func TestDimTxOpSloadWarm(t *testing.T) { - t.Parallel() ctx, cancel, builder, auth, cleanup := gasDimensionTestSetup(t, false) defer cancel() defer cleanup() diff --git a/system_tests/gas_dim_tx_op_stylus_test.go b/system_tests/gas_dim_tx_op_stylus_test.go index 19ac2750bb..21db4f98e9 100644 --- a/system_tests/gas_dim_tx_op_stylus_test.go +++ b/system_tests/gas_dim_tx_op_stylus_test.go @@ -9,7 +9,6 @@ import ( // This test runs the tracer on a transaction that includes a call to a // stylus contract. func TestDimTxOpStylus(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, true, gasDimPrecompileBuilderOpts()...) ctx := builder.ctx l2client := builder.L2.Client @@ -33,7 +32,6 @@ func TestDimTxOpStylus(t *testing.T) { // a proxy solidity contract in the EVM, testing the // flow from non-stylus to stylus func TestDimTxOpStylusKeccakForSloadFromProxy(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, true, gasDimPrecompileBuilderOpts()...) ctx := builder.ctx l2client := builder.L2.Client diff --git a/system_tests/historical_block_hash_test.go b/system_tests/historical_block_hash_test.go index 77fd5a8cd2..69a5dec481 100644 --- a/system_tests/historical_block_hash_test.go +++ b/system_tests/historical_block_hash_test.go @@ -12,7 +12,6 @@ import ( ) func TestHistoricalBlockHash(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/infra_fee_test.go b/system_tests/infra_fee_test.go index 49c750bcf2..9766315410 100644 --- a/system_tests/infra_fee_test.go +++ b/system_tests/infra_fee_test.go @@ -20,7 +20,6 @@ import ( ) func TestInfraFee(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/initialization_test.go b/system_tests/initialization_test.go index 254c0826ef..2d0ff3ef59 100644 --- a/system_tests/initialization_test.go +++ b/system_tests/initialization_test.go @@ -46,7 +46,6 @@ func InitOneContract(prand *testhelpers.PseudoRandomDataSource) (*statetransfer. } func TestInitContract(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() expectedSums := make(map[common.Address]*big.Int) diff --git a/system_tests/l3_test.go b/system_tests/l3_test.go index 97eabcee78..b4366c4e1b 100644 --- a/system_tests/l3_test.go +++ b/system_tests/l3_test.go @@ -10,7 +10,6 @@ import ( ) func TestSimpleL3(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/maintenance_test.go b/system_tests/maintenance_test.go index bab25fcde4..cbaab52990 100644 --- a/system_tests/maintenance_test.go +++ b/system_tests/maintenance_test.go @@ -5,21 +5,22 @@ package arbtest import ( "context" + "errors" "fmt" + "math" "math/big" "testing" -) + "time" -func TestMaintenance(t *testing.T) { - t.Parallel() + "github.com/redis/go-redis/v9" - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + "github.com/ethereum/go-ethereum/log" - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - cleanup := builder.Build(t) - defer cleanup() + "github.com/offchainlabs/nitro/util/redisutil" + "github.com/offchainlabs/nitro/util/testhelpers" +) +func checkMaintenanceRun(t *testing.T, builder *NodeBuilder, ctx context.Context, logHandler *testhelpers.LogHandler) { numberOfTransfers := 10 for i := 2; i < 3+numberOfTransfers; i++ { account := fmt.Sprintf("User%d", i) @@ -32,9 +33,27 @@ func TestMaintenance(t *testing.T) { Require(t, err) } - _, err := builder.L2.ExecNode.Maintenance().Await(ctx) - Require(t, err) + maybeRunMaintenanceDone := make(chan struct{}) + go func() { + builder.L2.ConsensusNode.MaintenanceRunner.MaybeRunMaintenance(ctx) + close(maybeRunMaintenanceDone) + }() + select { + case <-maybeRunMaintenanceDone: + case <-time.After(10 * time.Second): + t.Fatal("Maintenance did not complete in time") + case <-ctx.Done(): + t.Fatal("Context cancelled before maintenance completed") + } + + if !logHandler.WasLogged("Execution is not running maintenance anymore, maintenance completed successfully") { + t.Fatal("Maintenance did not complete successfully from Consensus perspective") + } + if !logHandler.WasLogged("Flushed trie db through maintenance completed successfully") { + t.Fatal("Expected log message not found") + } + // checks that balances are correct after maintenance for i := 2; i < 3+numberOfTransfers; i++ { account := fmt.Sprintf("User%d", i) balance, err := builder.L2.Client.BalanceAt(ctx, builder.L2Info.GetAddress(account), nil) @@ -44,3 +63,65 @@ func TestMaintenance(t *testing.T) { } } } + +func TestMaintenanceWithoutSeqCoordinator(t *testing.T) { + logHandler := testhelpers.InitTestLog(t, log.LvlTrace) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() + builder.nodeConfig.Maintenance.Enable = true + builder.execConfig.Caching.TrieTimeLimitBeforeFlushMaintenance = time.Duration(math.MaxInt64) // effectively execution will always suggest to run maintenance + cleanup := builder.Build(t) + defer cleanup() + + checkMaintenanceRun(t, builder, ctx, logHandler) +} + +func TestMaintenanceWithSeqCoordinator(t *testing.T) { + logHandler := testhelpers.InitTestLog(t, log.LvlTrace) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() + builder.nodeConfig.Maintenance.Enable = true + builder.execConfig.Caching.TrieTimeLimitBeforeFlushMaintenance = time.Duration(math.MaxInt64) // effectively execution will always suggest to run maintenance + builder.nodeConfig.BatchPoster.Enable = false + builder.nodeConfig.SeqCoordinator.Enable = true + builder.nodeConfig.SeqCoordinator.RedisUrl = redisutil.CreateTestRedis(ctx, t) + + nodeNames := []string{"stdio://A", "stdio://B"} + initRedisForTest(t, ctx, builder.nodeConfig.SeqCoordinator.RedisUrl, nodeNames) + builder.nodeConfig.SeqCoordinator.MyUrl = nodeNames[0] + + cleanup := builder.Build(t) + defer cleanup() + + redisClient, err := redisutil.RedisClientFromURL(builder.nodeConfig.SeqCoordinator.RedisUrl) + Require(t, err) + defer redisClient.Close() + + // wait for sequencerA to become master + for { + err := redisClient.Get(ctx, redisutil.CHOSENSEQ_KEY).Err() + if errors.Is(err, redis.Nil) { + time.Sleep(builder.nodeConfig.SeqCoordinator.UpdateInterval) + continue + } + Require(t, err) + break + } + + nodeConfigDup := *builder.nodeConfig + nodeConfigDup.SeqCoordinator.MyUrl = nodeNames[1] + _, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: &nodeConfigDup}) + defer cleanupB() + + checkMaintenanceRun(t, builder, ctx, logHandler) + + if !logHandler.WasLogged("Avoided lockout and handed off chosen one") { + t.Fatal("Expected log message not found") + } +} diff --git a/system_tests/meaningless_reorg_test.go b/system_tests/meaningless_reorg_test.go index 96136e6b64..0f090d66ec 100644 --- a/system_tests/meaningless_reorg_test.go +++ b/system_tests/meaningless_reorg_test.go @@ -15,7 +15,6 @@ import ( ) func TestMeaninglessBatchReorg(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/nodeinterface_test.go b/system_tests/nodeinterface_test.go index 3e42ec1963..a71182b55a 100644 --- a/system_tests/nodeinterface_test.go +++ b/system_tests/nodeinterface_test.go @@ -24,7 +24,7 @@ func TestFindBatch(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() l1Info := builder.L1Info initialBalance := new(big.Int).Lsh(big.NewInt(1), 200) l1Info.GenerateGenesisAccount("deployer", initialBalance) @@ -86,7 +86,6 @@ func TestFindBatch(t *testing.T) { } func TestL2BlockRangeForL1(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -146,7 +145,6 @@ func TestL2BlockRangeForL1(t *testing.T) { } func TestGetL1Confirmations(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/outbox_test.go b/system_tests/outbox_test.go index ac4b350752..31495c5b04 100644 --- a/system_tests/outbox_test.go +++ b/system_tests/outbox_test.go @@ -52,7 +52,6 @@ func TestP256VerifyEnabled(t *testing.T) { } func TestOutboxProofs(t *testing.T) { - t.Parallel() gethhook.RequireHookedGeth() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/overflow_assertions_test.go b/system_tests/overflow_assertions_test.go index 99e9ffd518..e984c10f0c 100644 --- a/system_tests/overflow_assertions_test.go +++ b/system_tests/overflow_assertions_test.go @@ -14,9 +14,8 @@ import ( "testing" "time" - "github.com/offchainlabs/nitro/validator/server_common" - "github.com/ccoveille/go-safecast" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" @@ -26,16 +25,17 @@ import ( challengemanager "github.com/offchainlabs/bold/challenge-manager" modes "github.com/offchainlabs/bold/challenge-manager/types" l2stateprovider "github.com/offchainlabs/bold/layer2-state-provider" - "github.com/offchainlabs/bold/solgen/go/bridgegen" - "github.com/offchainlabs/bold/solgen/go/mocksgen" - "github.com/offchainlabs/bold/solgen/go/rollupgen" "github.com/offchainlabs/bold/testing/setup" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/cmd/chaininfo" "github.com/offchainlabs/nitro/execution/gethexec" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/staker/bold" "github.com/offchainlabs/nitro/util" + "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" ) diff --git a/system_tests/pendingblock_test.go b/system_tests/pendingblock_test.go index edf6e87caf..c7311c8ae0 100644 --- a/system_tests/pendingblock_test.go +++ b/system_tests/pendingblock_test.go @@ -12,7 +12,6 @@ import ( ) func TestPendingBlockTimeAndNumberAdvance(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -32,7 +31,6 @@ func TestPendingBlockTimeAndNumberAdvance(t *testing.T) { } func TestPendingBlockArbBlockHashReturnsLatest(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/precompile_doesnt_revert_test.go b/system_tests/precompile_doesnt_revert_test.go index e1132e1bfa..391b8690c7 100644 --- a/system_tests/precompile_doesnt_revert_test.go +++ b/system_tests/precompile_doesnt_revert_test.go @@ -24,8 +24,6 @@ import ( // They are not a substitute for unit tests, as they don't test the actual functionality of the precompile. func TestArbAddressTableDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -78,8 +76,6 @@ func TestArbAddressTableDoesntRevert(t *testing.T) { } func TestArbAggregatorDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -103,8 +99,6 @@ func TestArbAggregatorDoesntRevert(t *testing.T) { } func TestArbosTestDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -122,8 +116,6 @@ func TestArbosTestDoesntRevert(t *testing.T) { } func TestArbSysDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -143,8 +135,6 @@ func TestArbSysDoesntRevert(t *testing.T) { } func TestArbOwnerDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -183,8 +173,6 @@ func TestArbOwnerDoesntRevert(t *testing.T) { } func TestArbGasInfoDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -230,8 +218,6 @@ func TestArbGasInfoDoesntRevert(t *testing.T) { } func TestArbRetryableTxDoesntRevert(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/precompile_test.go b/system_tests/precompile_test.go index 135b997a50..343c04c180 100644 --- a/system_tests/precompile_test.go +++ b/system_tests/precompile_test.go @@ -268,8 +268,6 @@ func setupArbOwnerAndArbGasInfo( } func TestL1BaseFeeEstimateInertia(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -288,8 +286,6 @@ func TestL1BaseFeeEstimateInertia(t *testing.T) { // Similar to TestL1BaseFeeEstimateInertia, but now using a different setter from ArbOwner func TestL1PricingInertia(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -307,8 +303,6 @@ func TestL1PricingInertia(t *testing.T) { } func TestL1PricingRewardRate(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -326,8 +320,6 @@ func TestL1PricingRewardRate(t *testing.T) { } func TestL1PricingRewardRecipient(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -345,8 +337,6 @@ func TestL1PricingRewardRecipient(t *testing.T) { } func TestL2GasPricingInertia(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -364,8 +354,6 @@ func TestL2GasPricingInertia(t *testing.T) { } func TestL2GasBacklogTolerance(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -383,8 +371,6 @@ func TestL2GasBacklogTolerance(t *testing.T) { } func TestPerBatchGasCharge(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -402,8 +388,6 @@ func TestPerBatchGasCharge(t *testing.T) { } func TestL1PricingEquilibrationUnits(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -421,8 +405,6 @@ func TestL1PricingEquilibrationUnits(t *testing.T) { } func TestGasAccountingParams(t *testing.T) { - t.Parallel() - builder, cleanup, auth, arbOwner, arbGasInfo := setupArbOwnerAndArbGasInfo(t) defer cleanup() ctx := builder.ctx @@ -454,8 +436,6 @@ func TestGasAccountingParams(t *testing.T) { } func TestCurrentTxL1GasFees(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -476,6 +456,66 @@ func TestCurrentTxL1GasFees(t *testing.T) { } } +func TestArbNativeTokenManagerThroughSolidityContract(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + + arbOSInit := ¶ms.ArbOSInit{ + NativeTokenSupplyManagementEnabled: true, + } + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).WithArbOSInit(arbOSInit).WithArbOSVersion(params.ArbosVersion_41) + cleanup := builder.Build(t) + defer cleanup() + + authOwner := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + authOwner.GasLimit = 32000000 + + // deploys test contract + contractAddr, tx, contract, err := localgen.DeployArbNativeTokenManagerTest(&authOwner, builder.L2.Client) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, builder.L2.Client, tx) + Require(t, err) + + // adds native token owner + arbOwner, err := precompilesgen.NewArbOwner(types.ArbOwnerAddress, builder.L2.Client) + Require(t, err) + tx, err = arbOwner.AddNativeTokenOwner(&authOwner, contractAddr) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // mints + toMint := big.NewInt(100) + tx, err = contract.Mint(&authOwner, toMint) + Require(t, err) + receipt, err := builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // checks minting + arbNativeTokenManager, err := precompilesgen.NewArbNativeTokenManager(types.ArbNativeTokenManagerAddress, builder.L2.Client) + Require(t, err) + nativeTokenOwnerABI, err := precompilesgen.ArbNativeTokenManagerMetaData.GetAbi() + Require(t, err) + mintTopic := nativeTokenOwnerABI.Events["NativeTokenMinted"].ID + mintLogged := false + for _, log := range receipt.Logs { + if log.Topics[0] == mintTopic { + mintLogged = true + parsedLog, err := arbNativeTokenManager.ParseNativeTokenMinted(*log) + Require(t, err) + if parsedLog.To != contractAddr { + t.Fatal("expected mint to be to", contractAddr, "got", parsedLog.To) + } + if parsedLog.Amount.Cmp(toMint) != 0 { + t.Fatal("expected mint amount to be", toMint, "got", parsedLog.Amount) + } + } + } + if !mintLogged { + t.Fatal("expected mint event to be logged") + } +} + func TestArbNativeTokenManager(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -893,8 +933,6 @@ func TestGetBrotliCompressionLevel(t *testing.T) { } func TestArbStatistics(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -918,8 +956,6 @@ func TestArbStatistics(t *testing.T) { } func TestArbosFeatures(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -958,8 +994,6 @@ func TestArbosFeatures(t *testing.T) { } func TestArbFunctionTable(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -994,8 +1028,6 @@ func TestArbFunctionTable(t *testing.T) { } func TestArbAggregatorBaseFee(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1022,8 +1054,6 @@ func TestArbAggregatorBaseFee(t *testing.T) { } func TestFeeAccounts(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1064,8 +1094,6 @@ func TestFeeAccounts(t *testing.T) { } func TestChainOwners(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1139,8 +1167,6 @@ func TestChainOwners(t *testing.T) { } func TestArbAggregatorBatchPosters(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -1188,8 +1214,6 @@ func TestArbAggregatorBatchPosters(t *testing.T) { } func TestArbAggregatorGetPreferredAggregator(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/program_ink_test.go b/system_tests/program_ink_test.go index 08b394e892..5b70e3f518 100644 --- a/system_tests/program_ink_test.go +++ b/system_tests/program_ink_test.go @@ -30,8 +30,6 @@ const PTR_INK uint64 = 5040 const EVM_API_INK uint64 = 59673 func TestSimpleInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -165,8 +163,6 @@ func TestSimpleInkUsage(t *testing.T) { } func TestAccountCodeInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -192,8 +188,6 @@ func TestAccountCodeInkUsage(t *testing.T) { } func TestPowInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -220,8 +214,6 @@ func TestPowInkUsage(t *testing.T) { } func TestStorageInkCost(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("multicall")) @@ -309,8 +301,6 @@ func TestStorageInkCost(t *testing.T) { } func TestLogInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -346,8 +336,6 @@ func TestLogInkUsage(t *testing.T) { } func TestReturnDataInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("multicall")) @@ -374,8 +362,6 @@ func TestReturnDataInkUsage(t *testing.T) { } func TestCallInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("multicall")) @@ -427,8 +413,6 @@ func TestCallInkUsage(t *testing.T) { } func TestCreateInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("create")) @@ -451,8 +435,6 @@ func TestCreateInkUsage(t *testing.T) { } func TestKeccakInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -478,8 +460,6 @@ func TestKeccakInkUsage(t *testing.T) { } func TestWriteResultInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -508,8 +488,6 @@ func TestWriteResultInkUsage(t *testing.T) { } func TestReadArgsInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -535,8 +513,6 @@ func TestReadArgsInkUsage(t *testing.T) { } func TestMsgReentrantInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -548,8 +524,6 @@ func TestMsgReentrantInkUsage(t *testing.T) { } func TestStorageCacheBytes32InkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) @@ -562,8 +536,6 @@ func TestStorageCacheBytes32InkUsage(t *testing.T) { } func TestPayForMemoryGrowInkUsage(t *testing.T) { - t.Parallel() - builder := setupGasCostTest(t) auth := builder.L2Info.GetDefaultTransactOpts("Owner", builder.ctx) stylusProgram := deployWasm(t, builder.ctx, auth, builder.L2.Client, rustFile("hostio-test")) diff --git a/system_tests/program_norace_test.go b/system_tests/program_norace_test.go index d12d67787e..d6d97d52a0 100644 --- a/system_tests/program_norace_test.go +++ b/system_tests/program_norace_test.go @@ -106,7 +106,6 @@ func validateBlockRange( } func TestProgramEvmData(t *testing.T) { - t.Parallel() testEvmData(t, true) } diff --git a/system_tests/program_recursive_test.go b/system_tests/program_recursive_test.go index d60291bb10..9af133e801 100644 --- a/system_tests/program_recursive_test.go +++ b/system_tests/program_recursive_test.go @@ -111,7 +111,7 @@ func testProgramRecursiveCall(t *testing.T, builder *NodeBuilder, slotVals map[s return receipt.BlockNumber.Uint64() } -func testProgramResursiveCalls(t *testing.T, tests [][]multiCallRecurse, jit bool) { +func testProgramRecursiveCalls(t *testing.T, tests [][]multiCallRecurse, jit bool) { builder, auth, cleanup := setupProgramTest(t, jit) ctx := builder.ctx l2client := builder.L2.Client @@ -195,5 +195,5 @@ func TestProgramCallSimple(t *testing.T) { }, }, } - testProgramResursiveCalls(t, tests, true) + testProgramRecursiveCalls(t, tests, true) } diff --git a/system_tests/program_test.go b/system_tests/program_test.go index 5bc9174d2a..524563ede8 100644 --- a/system_tests/program_test.go +++ b/system_tests/program_test.go @@ -50,8 +50,9 @@ var oneEth = arbmath.UintToBig(1e18) var allWasmTargets = []string{string(rawdb.TargetWavm), string(rawdb.TargetArm64), string(rawdb.TargetAmd64), string(rawdb.TargetHost)} +var localTargetOnly = []string{string(rawdb.LocalTarget())} + func TestProgramKeccak(t *testing.T) { - t.Parallel() t.Run("WithDefaultWasmTargets", func(t *testing.T) { keccakTest(t, true) }) @@ -64,7 +65,7 @@ func TestProgramKeccak(t *testing.T) { t.Run("WithOnlyLocalTarget", func(t *testing.T) { keccakTest(t, true, func(builder *NodeBuilder) { - builder.WithExtraArchs([]string{string(rawdb.LocalTarget())}) + builder.WithExtraArchs(localTargetOnly) }) }) } @@ -163,7 +164,6 @@ func keccakTest(t *testing.T, jit bool, builderOpts ...func(*NodeBuilder)) { } func TestProgramActivateTwice(t *testing.T) { - t.Parallel() t.Run("WithDefaultWasmTargets", func(t *testing.T) { testActivateTwice(t, true) }) @@ -270,7 +270,6 @@ func testActivateTwice(t *testing.T, jit bool, builderOpts ...func(*NodeBuilder) } func TestStylusUpgrade(t *testing.T) { - t.Parallel() testStylusUpgrade(t, true) } @@ -366,7 +365,6 @@ func testStylusUpgrade(t *testing.T, jit bool) { } func TestProgramErrors(t *testing.T) { - t.Parallel() errorTest(t, true) } @@ -408,7 +406,6 @@ func errorTest(t *testing.T, jit bool) { } func TestProgramStorage(t *testing.T) { - t.Parallel() storageTest(t, true) } @@ -509,7 +506,6 @@ func transientStorageTest(t *testing.T, jit bool) { } func TestProgramMath(t *testing.T) { - t.Parallel() fastMathTest(t, true) } @@ -537,7 +533,6 @@ func fastMathTest(t *testing.T, jit bool) { } func TestProgramCalls(t *testing.T) { - t.Parallel() testCalls(t, true) } @@ -752,7 +747,6 @@ func testCalls(t *testing.T, jit bool) { } func TestProgramReturnData(t *testing.T) { - t.Parallel() testReturnData(t, true) } @@ -805,12 +799,10 @@ func testReturnData(t *testing.T, jit bool) { } func TestProgramLogs(t *testing.T) { - t.Parallel() testLogs(t, true, false) } func TestProgramLogsWithTracing(t *testing.T) { - t.Parallel() testLogs(t, true, true) } @@ -919,7 +911,6 @@ func testLogs(t *testing.T, jit, tracing bool) { } func TestProgramCreate(t *testing.T) { - t.Parallel() testCreate(t, true) } @@ -1015,11 +1006,13 @@ func testCreate(t *testing.T, jit bool) { } func TestProgramInfiniteLoopShouldCauseErrOutOfGas(t *testing.T) { - t.Parallel() - testInfiniteLoopCausesErrOutOfGas(t, true) testInfiniteLoopCausesErrOutOfGas(t, false) } +func TestProgramInfiniteLoopShouldCauseErrOutOfGas_Jit(t *testing.T) { + testInfiniteLoopCausesErrOutOfGas(t, true) +} + func testInfiniteLoopCausesErrOutOfGas(t *testing.T, jit bool) { builder, auth, cleanup := setupProgramTest(t, jit) ctx := builder.ctx @@ -1040,7 +1033,6 @@ func testInfiniteLoopCausesErrOutOfGas(t *testing.T, jit bool) { } func TestProgramMemory(t *testing.T) { - t.Parallel() testMemory(t, true) } @@ -1198,7 +1190,6 @@ func testMemory(t *testing.T, jit bool) { } func TestProgramActivateFails(t *testing.T) { - t.Parallel() testActivateFails(t, true) } @@ -1237,7 +1228,6 @@ func testActivateFails(t *testing.T, jit bool) { } func TestProgramSdkStorage(t *testing.T) { - t.Parallel() testSdkStorage(t, true) } @@ -1433,7 +1423,6 @@ func TestStylusPrecompileMethodsSimple(t *testing.T) { } func TestProgramActivationLogs(t *testing.T) { - t.Parallel() builder, auth, cleanup := setupProgramTest(t, true) l2client := builder.L2.Client ctx := builder.ctx @@ -1473,7 +1462,6 @@ func TestProgramActivationLogs(t *testing.T) { } func TestProgramEarlyExit(t *testing.T) { - t.Parallel() testEarlyExit(t, true) } @@ -1684,7 +1672,10 @@ func testReturnDataCost(t *testing.T, arbosVersion uint64) { } func TestReturnDataCost(t *testing.T) { - testReturnDataCost(t, params.ArbosVersion_Stylus) + testReturnDataCost(t, params.ArbosVersion_StylusFixes) +} + +func TestReturnDataCost_StylusFixes(t *testing.T) { testReturnDataCost(t, params.ArbosVersion_StylusFixes) } @@ -1701,7 +1692,7 @@ func setupProgramTest(t *testing.T, jit bool, builderOpts ...func(*NodeBuilder)) // setupProgramTest is being called by tests that validate blocks. // For now validation only works with HashScheme set. - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) builder.nodeConfig.BlockValidator.Enable = false builder.nodeConfig.Staker.Enable = true builder.nodeConfig.BatchPoster.Enable = true @@ -1715,6 +1706,9 @@ func setupProgramTest(t *testing.T, jit bool, builderOpts ...func(*NodeBuilder)) builder.execConfig.Sequencer.MaxRevertGasReject = 0 + // Increase call timeout to 30 seconds to avoid flaky CI + builder.execConfig.RPC.RPCEVMTimeout = 30 * time.Second + builderCleanup := builder.Build(t) cleanup := func() { @@ -1938,7 +1932,7 @@ func formatTime(duration time.Duration) string { return fmt.Sprintf("%.2f%s", span, units[unit]) } -func testWasmRecreate(t *testing.T, builder *NodeBuilder, storeTx *types.Transaction, loadTx *types.Transaction, want []byte) { +func testWasmRecreate(t *testing.T, builder *NodeBuilder, targetsBefore, targetsAfter []string, numModules int, removeWasmDbBetween bool, storeTx, loadTx *types.Transaction, want []byte) { ctx := builder.ctx l2info := builder.L2Info l2client := builder.L2.Client @@ -1950,7 +1944,9 @@ func testWasmRecreate(t *testing.T, builder *NodeBuilder, storeTx *types.Transac testDir := t.TempDir() nodeBStack := testhelpers.CreateStackConfigForTest(testDir) - nodeB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + nodeBExecConfigBefore := *builder.execConfig + nodeBExecConfigBefore.StylusTarget.ExtraArchs = targetsBefore + nodeB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack, execConfig: &nodeBExecConfigBefore}) _, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx) Require(t, err) @@ -1961,21 +1957,26 @@ func testWasmRecreate(t *testing.T, builder *NodeBuilder, storeTx *types.Transac if !bytes.Equal(result, want) { t.Fatalf("got wrong value, got %x, want %x", result, want) } + wasmDb := nodeB.ExecNode.Backend.ArbInterface().BlockChain().StateCache().WasmStore() + checkWasmStoreContent(t, wasmDb, nodeBExecConfigBefore.StylusTarget.WasmTargets(), numModules) // close nodeB cleanupB() - // delete wasm dir of nodeB - - wasmPath := filepath.Join(testDir, "system_tests.test", "wasm") - dirContents, err := os.ReadDir(wasmPath) - Require(t, err) - if len(dirContents) == 0 { - Fatal(t, "not contents found before delete") + wasmPath := filepath.Join(testDir, nodeBStack.Name, "wasm") + if removeWasmDbBetween { + // remove wasm dir of nodeB + dirContents, err := os.ReadDir(wasmPath) + Require(t, err) + if len(dirContents) == 0 { + Fatal(t, "not contents found before delete") + } + os.RemoveAll(wasmPath) } - os.RemoveAll(wasmPath) // recreate nodeB - using same source dir (wasm deleted) - nodeB, cleanupB = builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack}) + nodeBExecConfigAfter := *builder.execConfig + nodeBExecConfigAfter.StylusTarget.ExtraArchs = targetsAfter + nodeB, cleanupB = builder.Build2ndNode(t, &SecondNodeParams{stackConfig: nodeBStack, execConfig: &nodeBExecConfigAfter}) // test nodeB - sees existing transaction _, err = EnsureTxSucceeded(ctx, nodeB.Client, storeTx) @@ -1997,16 +1998,85 @@ func testWasmRecreate(t *testing.T, builder *NodeBuilder, storeTx *types.Transac _, err = EnsureTxSucceeded(ctx, nodeB.Client, loadTx) Require(t, err) + wasmDb = nodeB.ExecNode.Backend.ArbInterface().BlockChain().StateCache().WasmStore() + checkWasmStoreContent(t, wasmDb, nodeBExecConfigAfter.StylusTarget.WasmTargets(), numModules) + cleanupB() - dirContents, err = os.ReadDir(wasmPath) + dirContents, err := os.ReadDir(wasmPath) Require(t, err) if len(dirContents) == 0 { - Fatal(t, "not contents found before delete") + Fatal(t, "no contents found before delete") } os.RemoveAll(wasmPath) } func TestWasmRecreate(t *testing.T) { + testCases := []struct { + name string + removeWasmDbBetween bool + targetsBefore []string + targetsAfter []string + }{ + { + name: "with local target only with wasmdb removal", + removeWasmDbBetween: true, + targetsBefore: localTargetOnly, + targetsAfter: localTargetOnly, + }, + { + name: "with local target only without wasmdb removal", + removeWasmDbBetween: false, + targetsBefore: localTargetOnly, + targetsAfter: localTargetOnly, + }, + { + name: "with all targets with wasmdb removal", + removeWasmDbBetween: true, + targetsBefore: allWasmTargets, + targetsAfter: allWasmTargets, + }, + { + name: "with all targets without wasmdb removal", + removeWasmDbBetween: false, + targetsBefore: allWasmTargets, + targetsAfter: allWasmTargets, + }, + { + name: "more targets to recreate with wasmdb removal", + removeWasmDbBetween: true, + targetsBefore: localTargetOnly, + targetsAfter: allWasmTargets, + }, + { + name: "more targets to recreate without wasmdb removal", + removeWasmDbBetween: false, + targetsBefore: localTargetOnly, + targetsAfter: allWasmTargets, + }, + { + name: "less targets to recreate with wasmdb removal", + removeWasmDbBetween: true, + targetsBefore: allWasmTargets, + targetsAfter: localTargetOnly, + }, + { + name: "less targets to recreate without wasmdb removal", + removeWasmDbBetween: false, + targetsBefore: allWasmTargets, + targetsAfter: localTargetOnly, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testWasmRecreateWithCall(t, tc.targetsBefore, tc.targetsAfter, tc.removeWasmDbBetween) + }) + t.Run(tc.name+" with delegate call", func(t *testing.T) { + testWasmRecreateWithDelegatecall(t, tc.targetsBefore, tc.targetsAfter, tc.removeWasmDbBetween) + }) + } +} + +func testWasmRecreateWithCall(t *testing.T, targetsBefore, targetsAfter []string, removeWasmDbBetween bool) { builder, auth, cleanup := setupProgramTest(t, true) ctx := builder.ctx l2info := builder.L2Info @@ -2021,10 +2091,10 @@ func TestWasmRecreate(t *testing.T) { storeTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageWrite(zero, val)) loadTx := l2info.PrepareTxTo("Owner", &storage, l2info.TransferGas, nil, argsForStorageRead(zero)) - testWasmRecreate(t, builder, storeTx, loadTx, val[:]) + testWasmRecreate(t, builder, localTargetOnly, allWasmTargets, 1, false, storeTx, loadTx, val[:]) } -func TestWasmRecreateWithDelegatecall(t *testing.T) { +func testWasmRecreateWithDelegatecall(t *testing.T, targetsBefore, targetsAfter []string, removeWasmDbBetween bool) { builder, auth, cleanup := setupProgramTest(t, true) ctx := builder.ctx l2info := builder.L2Info @@ -2043,7 +2113,7 @@ func TestWasmRecreateWithDelegatecall(t *testing.T) { data = argsForMulticall(vm.DELEGATECALL, storage, big.NewInt(0), argsForStorageRead(zero)) loadTx := l2info.PrepareTxTo("Owner", &multicall, l2info.TransferGas, nil, data) - testWasmRecreate(t, builder, storeTx, loadTx, val[:]) + testWasmRecreate(t, builder, localTargetOnly, allWasmTargets, 2, true, storeTx, loadTx, val[:]) } // createMapFromDb is used in verifying if wasm store rebuilding works @@ -2112,7 +2182,7 @@ func TestWasmStoreRebuilding(t *testing.T) { cleanupB() // delete wasm dir of nodeB - wasmPath := filepath.Join(testDir, "system_tests.test", "wasm") + wasmPath := filepath.Join(testDir, nodeBStack.Name, "wasm") dirContents, err := os.ReadDir(wasmPath) Require(t, err) if len(dirContents) == 0 { @@ -2191,14 +2261,14 @@ func readModuleHashes(t *testing.T, wasmDb ethdb.KeyValueStore) []common.Hash { return modules } -func checkWasmStoreContent(t *testing.T, wasmDb ethdb.KeyValueStore, expectedTargets []ethdb.WasmTarget, numModules int) { +func checkWasmStoreContent(t *testing.T, wasmDb ethdb.KeyValueStore, expectedTargets []rawdb.WasmTarget, numModules int) { t.Helper() modules := readModuleHashes(t, wasmDb) if len(modules) != numModules { t.Fatalf("Unexpected number of module hashes found in wasm store, want: %d, have: %d", numModules, len(modules)) } readAsm := func(module common.Hash, target string) []byte { - wasmTarget := ethdb.WasmTarget(target) + wasmTarget := rawdb.WasmTarget(target) if !rawdb.IsSupportedWasmTarget(wasmTarget) { t.Fatalf("internal test error - unsupported target passed to checkWasmStoreContent: %v", target) } @@ -2216,7 +2286,7 @@ func checkWasmStoreContent(t *testing.T, wasmDb ethdb.KeyValueStore, expectedTar for _, target := range allWasmTargets { var expected bool for _, expectedTarget := range expectedTargets { - if ethdb.WasmTarget(target) == expectedTarget { + if rawdb.WasmTarget(target) == expectedTarget { expected = true break } @@ -2260,8 +2330,10 @@ func deployWasmAndGetEntrySizeEstimateBytes( statedb, err := builder.L2.ExecNode.Backend.ArbInterface().BlockChain().State() Require(t, err, ", wasmName:", wasmName) - module, err := statedb.TryGetActivatedAsm(rawdb.LocalTarget(), log.ModuleHash) - Require(t, err, ", wasmName:", wasmName) + module := statedb.ActivatedAsm(rawdb.LocalTarget(), log.ModuleHash) + if len(module) == 0 { + Fatal(t, "missing asm for local target, wasmName:", wasmName) + } entrySizeEstimateBytes := programs.GetEntrySizeEstimateBytes(module, log.Version, true) // just a sanity check @@ -2272,7 +2344,12 @@ func deployWasmAndGetEntrySizeEstimateBytes( } func TestWasmLruCache(t *testing.T) { - builder, auth, cleanup := setupProgramTest(t, true) + builder, auth, cleanup := setupProgramTest(t, true, func(b *NodeBuilder) { + // TestWasmLruCache shouldn't be run in parallel as it targets global Wasm LRU Cache, + // programs.ClearWasmLruCache is called in the test. + b.DontParalellise() + }) + ctx := builder.ctx l2info := builder.L2Info l2client := builder.L2.Client @@ -2368,8 +2445,10 @@ func checkLruCacheMetrics(t *testing.T, expected programs.WasmLruCacheMetrics) { } func TestWasmLongTermCache(t *testing.T) { - builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { - builder.WithStylusLongTermCache(true) + builder, ownerAuth, cleanup := setupProgramTest(t, true, func(b *NodeBuilder) { + // TestWasmLongTermCache shouldn't be run in parallel as it targets global Wasm Long Term Cache, + // programs.ClearWasmLongTermCache is called in the test. + b.DontParalellise() }) ctx := builder.ctx l2info := builder.L2Info @@ -2506,8 +2585,10 @@ func TestWasmLongTermCache(t *testing.T) { } func TestRepopulateWasmLongTermCacheFromLru(t *testing.T) { - builder, ownerAuth, cleanup := setupProgramTest(t, true, func(builder *NodeBuilder) { - builder.WithStylusLongTermCache(true) + builder, ownerAuth, cleanup := setupProgramTest(t, true, func(b *NodeBuilder) { + // TestRepopulateWasmLongTermCacheFromLru shouldn't be run in parallel as it targets global Wasm Long Term Cache and Wasm LRU Cache, + // programs.ClearWasmLongTermCache and programs.ClearWasmLruCache are called in the test.o + b.DontParalellise() }) ctx := builder.ctx l2info := builder.L2Info diff --git a/system_tests/pruning_test.go b/system_tests/pruning_test.go index fc8f435efa..13b5736d46 100644 --- a/system_tests/pruning_test.go +++ b/system_tests/pruning_test.go @@ -41,13 +41,13 @@ func TestPruning(t *testing.T) { } func testPruning(t *testing.T, mode string, pruneParallelStorageTraversal bool) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) // PathScheme prunes the state trie by itself, so only HashScheme should be tested - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) + _ = builder.Build(t) l2cleanupDone := false defer func() { @@ -102,7 +102,7 @@ func testPruning(t *testing.T, mode string, pruneParallelStorageTraversal bool) initConfig := conf.InitConfigDefault initConfig.Prune = mode initConfig.PruneParallelStorageTraversal = pruneParallelStorageTraversal - coreCacheConfig := gethexec.DefaultCacheConfigFor(stack, &builder.execConfig.Caching) + coreCacheConfig := gethexec.DefaultCacheConfigFor(&builder.execConfig.Caching) persistentConfig := conf.PersistentConfigDefault err = pruning.PruneChainDb(ctx, chainDb, stack, &initConfig, coreCacheConfig, &persistentConfig, builder.L1.Client, *builder.L2.ConsensusNode.DeployInfo, false) Require(t, err) diff --git a/system_tests/recreatestate_rpc_test.go b/system_tests/recreatestate_rpc_test.go index 035bfcf077..1f1c235979 100644 --- a/system_tests/recreatestate_rpc_test.go +++ b/system_tests/recreatestate_rpc_test.go @@ -24,6 +24,7 @@ import ( "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/util" + "github.com/offchainlabs/nitro/util/testhelpers/env" ) func makeSomeTransfers(t *testing.T, ctx context.Context, builder *NodeBuilder, txCount uint64) { @@ -40,14 +41,12 @@ func makeSomeTransfers(t *testing.T, ctx context.Context, builder *NodeBuilder, } } -func prepareNodeWithHistory(t *testing.T, ctx context.Context, execConfig *gethexec.Config, txCount uint64) (*NodeBuilder, func()) { +func buildWithHistory(t *testing.T, ctx context.Context, builder *NodeBuilder, blockCount uint64) func() { t.Helper() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) - builder.execConfig = execConfig cleanup := builder.Build(t) builder.L2Info.GenerateAccount("User2") - makeSomeTransfers(t, ctx, builder, txCount) - return builder, cleanup + makeSomeTransfers(t, ctx, builder, blockCount) + return cleanup } func fillHeaderCache(t *testing.T, bc *core.BlockChain, from, to uint64) { @@ -97,19 +96,17 @@ func removeStatesFromDb(t *testing.T, bc *core.BlockChain, db ethdb.Database, fr func TestRecreateStateForRPCNoDepthLimit(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme - execConfig.Caching.SnapshotCache = 0 // disable snapshots + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching.Archive = true + builder.execConfig.Caching.SnapshotCache = 0 // disable snapshots // disable trie/Database.cleans cache, so as states removed from ChainDb won't be cached there - execConfig.Caching.TrieCleanCache = 0 - execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 - execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 32) + builder.execConfig.Caching.TrieCleanCache = 0 + builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 + builder.execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 + cancelNode := buildWithHistory(t, ctx, builder, 32) defer cancelNode() execNode, l2client := builder.L2.ExecNode, builder.L2.Client bc := execNode.Backend.ArbInterface().BlockChain() @@ -136,18 +133,16 @@ func TestRecreateStateForRPCBigEnoughDepthLimit(t *testing.T) { defer cancel() // #nosec G115 depthGasLimit := int64(256 * util.NormalizeL2GasForL1GasInitial(800_000, params.GWei)) - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = depthGasLimit - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig.RPC.MaxRecreateStateDepth = depthGasLimit + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching.Archive = true // disable trie/Database.cleans cache, so as states removed from ChainDb won't be cached there - execConfig.Caching.TrieCleanCache = 0 - execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 - execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 32) + builder.execConfig.Caching.TrieCleanCache = 0 + builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 + builder.execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 + cancelNode := buildWithHistory(t, ctx, builder, 32) defer cancelNode() execNode, l2client := builder.L2.ExecNode, builder.L2.Client bc := execNode.Backend.ArbInterface().BlockChain() @@ -173,18 +168,16 @@ func TestRecreateStateForRPCBigEnoughDepthLimit(t *testing.T) { func TestRecreateStateForRPCDepthLimitExceeded(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = int64(200) - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig.RPC.MaxRecreateStateDepth = int64(200) + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching.Archive = true // disable trie/Database.cleans cache, so as states removed from ChainDb won't be cached there - execConfig.Caching.TrieCleanCache = 0 - execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 - execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 32) + builder.execConfig.Caching.TrieCleanCache = 0 + builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 + builder.execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 + cancelNode := buildWithHistory(t, ctx, builder, 32) defer cancelNode() execNode, l2client := builder.L2.ExecNode, builder.L2.Client bc := execNode.Backend.ArbInterface().BlockChain() @@ -210,18 +203,17 @@ func TestRecreateStateForRPCMissingBlockParent(t *testing.T) { var headerCacheLimit uint64 = 512 ctx, cancel := context.WithCancel(context.Background()) defer cancel() - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + + builder.execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching.Archive = true // disable trie/Database.cleans cache, so as states removed from ChainDb won't be cached there - execConfig.Caching.TrieCleanCache = 0 - execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 - execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, headerCacheLimit+5) + builder.execConfig.Caching.TrieCleanCache = 0 + builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 + builder.execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 + cancelNode := buildWithHistory(t, ctx, builder, headerCacheLimit+5) defer cancelNode() execNode, l2client := builder.L2.ExecNode, builder.L2.Client bc := execNode.Backend.ArbInterface().BlockChain() @@ -257,19 +249,17 @@ func TestRecreateStateForRPCMissingBlockParent(t *testing.T) { func TestRecreateStateForRPCBeyondGenesis(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme + builder.execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching.Archive = true // disable trie/Database.cleans cache, so as states removed from ChainDb won't be cached there - execConfig.Caching.TrieCleanCache = 0 - execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 - execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 32) + builder.execConfig.Caching.TrieCleanCache = 0 + builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 + builder.execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 + cancelNode := buildWithHistory(t, ctx, builder, 32) execNode, l2client := builder.L2.ExecNode, builder.L2.Client defer cancelNode() bc := execNode.Backend.ArbInterface().BlockChain() @@ -296,19 +286,18 @@ func TestRecreateStateForRPCBlockNotFoundWhileRecreating(t *testing.T) { var blockCacheLimit uint64 = 256 ctx, cancel := context.WithCancel(context.Background()) defer cancel() - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + + builder.execConfig.RPC.MaxRecreateStateDepth = arbitrum.InfiniteMaxRecreateStateDepth + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching.Archive = true // disable trie/Database.cleans cache, so as states removed from ChainDb won't be cached there - execConfig.Caching.TrieCleanCache = 0 + builder.execConfig.Caching.TrieCleanCache = 0 - execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 - execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, blockCacheLimit+4) + builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 0 + builder.execConfig.Caching.MaxAmountOfGasToSkipStateSaving = 0 + cancelNode := buildWithHistory(t, ctx, builder, blockCacheLimit+4) execNode, l2client := builder.L2.ExecNode, builder.L2.Client defer cancelNode() bc := execNode.Backend.ArbInterface().BlockChain() @@ -340,22 +329,19 @@ func TestRecreateStateForRPCBlockNotFoundWhileRecreating(t *testing.T) { } func testSkippingSavingStateAndRecreatingAfterRestart(t *testing.T, cacheConfig *gethexec.CachingConfig, txCount int) { - t.Parallel() maxRecreateStateDepth := int64(30 * 1000 * 1000) ctx, cancel := context.WithCancel(context.Background()) defer cancel() + builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - execConfig := ExecConfigDefaultTest(t) - execConfig.RPC.MaxRecreateStateDepth = maxRecreateStateDepth - execConfig.Sequencer.MaxBlockSpeed = 0 - execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 - execConfig.Caching = *cacheConfig + builder.execConfig.RPC.MaxRecreateStateDepth = maxRecreateStateDepth + builder.execConfig.Sequencer.MaxBlockSpeed = 0 + builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 + builder.execConfig.Caching = *cacheConfig - skipBlocks := execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving - skipGas := execConfig.Caching.MaxAmountOfGasToSkipStateSaving + skipBlocks := cacheConfig.MaxNumberOfBlocksToSkipStateSaving + skipGas := cacheConfig.MaxAmountOfGasToSkipStateSaving - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) - builder.execConfig = execConfig cleanup := builder.Build(t) defer cleanup() @@ -449,8 +435,6 @@ func testSkippingSavingStateAndRecreatingAfterRestart(t *testing.T, cacheConfig func TestSkippingSavingStateAndRecreatingAfterRestart(t *testing.T) { cacheConfig := gethexec.DefaultCachingConfig cacheConfig.Archive = true - // For now Archive node should use HashScheme - cacheConfig.StateScheme = rawdb.HashScheme cacheConfig.SnapshotCache = 0 // disable snapshots cacheConfig.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are @@ -497,7 +481,9 @@ func TestSkippingSavingStateAndRecreatingAfterRestart(t *testing.T) { func testGettingState(t *testing.T, execConfig *gethexec.Config) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder, cancelNode := prepareNodeWithHistory(t, ctx, execConfig, 16) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig = execConfig + cancelNode := buildWithHistory(t, ctx, builder, 16) execNode := builder.L2.ExecNode defer cancelNode() bc := execNode.Backend.ArbInterface().BlockChain() @@ -549,7 +535,7 @@ func testGettingState(t *testing.T, execConfig *gethexec.Config) { } func TestGettingState(t *testing.T) { - execConfig := ExecConfigDefaultTest(t) + execConfig := ExecConfigDefaultTest(t, env.GetTestStateScheme()) execConfig.Caching.SnapshotCache = 0 // disable snapshots execConfig.Caching.BlockAge = 0 // use only Caching.BlockCount to keep only last N blocks in dirties cache, no matter how new they are execConfig.Sequencer.MaxBlockSpeed = 0 @@ -558,10 +544,8 @@ func TestGettingState(t *testing.T) { testGettingState(t, execConfig) }) - execConfig = ExecConfigDefaultTest(t) + execConfig = ExecConfigDefaultTest(t, env.GetTestStateScheme()) execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - execConfig.Caching.StateScheme = rawdb.HashScheme execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 128 execConfig.Caching.BlockCount = 128 execConfig.Caching.SnapshotCache = 0 // disable snapshots @@ -581,8 +565,6 @@ func TestStateAndHeaderForRecentBlock(t *testing.T) { defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) builder.execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - builder.execConfig.Caching.StateScheme = rawdb.HashScheme builder.execConfig.RPC.MaxRecreateStateDepth = 0 cleanup := builder.Build(t) defer cleanup() diff --git a/system_tests/reorg_resequencing_test.go b/system_tests/reorg_resequencing_test.go index 219cc4799e..76e44e5f35 100644 --- a/system_tests/reorg_resequencing_test.go +++ b/system_tests/reorg_resequencing_test.go @@ -16,7 +16,6 @@ import ( ) func TestReorgResequencing(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/retryable_test.go b/system_tests/retryable_test.go index ca6b8294af..1f4399f1a8 100644 --- a/system_tests/retryable_test.go +++ b/system_tests/retryable_test.go @@ -30,6 +30,7 @@ import ( "github.com/offchainlabs/nitro/arbos/retryables" "github.com/offchainlabs/nitro/arbos/util" "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/localgen" "github.com/offchainlabs/nitro/solgen/go/node_interfacegen" @@ -55,7 +56,7 @@ func retryableSetup(t *testing.T, modifyNodeConfig ...func(*NodeBuilder)) ( // retryableSetup is being called by tests that validate blocks. // For now validation only works with HashScheme set. - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) builder.nodeConfig.BlockValidator.Enable = false builder.nodeConfig.Staker.Enable = true builder.nodeConfig.BatchPoster.Enable = true @@ -157,7 +158,6 @@ func TestRetryableNoExist(t *testing.T) { } func TestEstimateRetryableTicketWithNoFundsAndZeroGasPrice(t *testing.T) { - t.Parallel() builder, _, _, ctx, teardown := retryableSetup(t) defer teardown() @@ -189,7 +189,6 @@ func TestEstimateRetryableTicketWithNoFundsAndZeroGasPrice(t *testing.T) { } func TestSubmitRetryableImmediateSuccess(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() @@ -267,7 +266,6 @@ func TestSubmitRetryableImmediateSuccess(t *testing.T) { } func testSubmitRetryableEmptyEscrow(t *testing.T, arbosVersion uint64) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t, func(builder *NodeBuilder) { builder.WithArbOSVersion(arbosVersion) }) @@ -356,7 +354,6 @@ func TestSubmitRetryableEmptyEscrowArbOS30(t *testing.T) { } func TestSubmitRetryableFailThenRetry(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() @@ -513,7 +510,6 @@ func insertRetriables( } func TestSubmitManyRetryableFailThenRetry(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() infraFeeAddr, networkFeeAddr := setupFeeAddresses(t, ctx, builder) @@ -675,8 +671,6 @@ func TestSubmitManyRetryableFailThenRetry(t *testing.T) { } func TestGetLifetime(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -712,15 +706,14 @@ func warpL1Time(t *testing.T, builder *NodeBuilder, ctx context.Context, current RequestId: nil, L1BaseFee: nil, } - hooks := arbos.NoopSequencingHooks() tx := builder.L2Info.PrepareTx("Faucet", "User2", 300000, big.NewInt(1), nil) - _, err = builder.L2.ExecNode.ExecEngine.SequenceTransactions(timeWarpHeader, types.Transactions{tx}, hooks, nil) + hooks := arbos.NoopSequencingHooks(types.Transactions{tx}) + _, err = builder.L2.ExecNode.ExecEngine.SequenceTransactions(timeWarpHeader, hooks, nil) Require(t, err) return newL1Timestamp } func TestRetryableExpiry(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() @@ -787,7 +780,6 @@ func TestRetryableExpiry(t *testing.T) { } func TestKeepaliveAndRetryableExpiry(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() @@ -877,7 +869,6 @@ func TestKeepaliveAndRetryableExpiry(t *testing.T) { } func TestKeepaliveAndCancelRetryable(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() @@ -966,7 +957,6 @@ func TestKeepaliveAndCancelRetryable(t *testing.T) { } func TestSubmissionGasCosts(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() infraFeeAddr, networkFeeAddr := setupFeeAddresses(t, ctx, builder) @@ -1138,7 +1128,6 @@ func waitForL1DelayBlocks(t *testing.T, builder *NodeBuilder) { } func TestDepositETH(t *testing.T) { - t.Parallel() builder, delayedInbox, lookupL2Tx, ctx, teardown := retryableSetup(t) defer teardown() @@ -1233,7 +1222,6 @@ func TestArbitrumContractTx(t *testing.T) { } func TestL1FundedUnsignedTransaction(t *testing.T) { - t.Parallel() ctx := context.Background() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) cleanup := builder.Build(t) @@ -1588,7 +1576,7 @@ func elevateL2Basefee(t *testing.T, ctx context.Context, builder *NodeBuilder) { _, err = precompilesgen.NewArbosTest(common.HexToAddress("0x69"), builder.L2.Client) Require(t, err, "failed to deploy ArbosTest") - burnAmount := ExecConfigDefaultTest(t).RPC.RPCGasCap + burnAmount := gethexec.ConfigDefault.RPC.RPCGasCap burnTarget := uint64(5 * l2pricing.InitialSpeedLimitPerSecondV6 * l2pricing.InitialBacklogTolerance) for i := uint64(0); i < (burnTarget+burnAmount)/burnAmount; i++ { burnArbGas := arbosTestAbi.Methods["burnArbGas"] diff --git a/system_tests/revalidation_test.go b/system_tests/revalidation_test.go index e74407d762..57283b6794 100644 --- a/system_tests/revalidation_test.go +++ b/system_tests/revalidation_test.go @@ -6,7 +6,6 @@ import ( "testing" "time" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -23,8 +22,7 @@ func TestRevalidationForSpecifiedRange(t *testing.T) { var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs // 1st node with sequencer, stays up all the time. - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() builder.nodeConfig.BlockValidator.Enable = true builder.L2Info = NewBlockChainTestInfo( t, @@ -53,7 +51,7 @@ func TestRevalidationForSpecifiedRange(t *testing.T) { // Create transactions till batch count is 15 createTransactionTillBatchCount(ctx, t, builder, 15) // Wait for nodeB to sync up to the first node - waitForBlocksToCatchup(ctx, t, builder.L2.Client, nodeB.Client) + waitForBlocksToCatchup(ctx, t, builder.L2.Client, nodeB.Client, 10*time.Minute) // Create a config with revalidation range and same database directory as the 2nd node nodeConfig := createNodeConfigWithRevalidationRange(builder) diff --git a/system_tests/self_destruct_test.go b/system_tests/self_destruct_test.go index e0f17daff8..97b5661088 100644 --- a/system_tests/self_destruct_test.go +++ b/system_tests/self_destruct_test.go @@ -11,7 +11,6 @@ import ( ) func TestSelfDestruct(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/seq_coordinator_test.go b/system_tests/seq_coordinator_test.go index 3072f32c3b..a07f438e8b 100644 --- a/system_tests/seq_coordinator_test.go +++ b/system_tests/seq_coordinator_test.go @@ -48,7 +48,7 @@ func TestRedisSeqCoordinatorPriorities(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builder.takeOwnership = false builder.nodeConfig.SeqCoordinator.Enable = true builder.nodeConfig.SeqCoordinator.RedisUrl = redisutil.CreateTestRedis(ctx, t) @@ -68,6 +68,7 @@ func TestRedisSeqCoordinatorPriorities(t *testing.T) { builder.L2Info = l2Info builder.dataDir = t.TempDir() // set new data dir for each node builder.l2StackConfig = testhelpers.CreateStackConfigForTest(builder.dataDir) + builder.parallelise = false builder.Build(t) testNodes[nodeNum] = builder.L2 } @@ -291,7 +292,7 @@ func testCoordinatorMessageSync(t *testing.T, successCase bool) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() builder.nodeConfig.SeqCoordinator.Enable = true builder.nodeConfig.SeqCoordinator.RedisUrl = redisutil.CreateTestRedis(ctx, t) builder.nodeConfig.BatchPoster.Enable = false diff --git a/system_tests/seq_filter_test.go b/system_tests/seq_filter_test.go index d57bb8fd0a..fcbbe10553 100644 --- a/system_tests/seq_filter_test.go +++ b/system_tests/seq_filter_test.go @@ -21,18 +21,16 @@ import ( ) func TestSequencerTxFilter(t *testing.T) { - t.Parallel() - builder, header, txes, hooks, cleanup := setupSequencerFilterTest(t, false) defer cleanup() - block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, txes, hooks, nil) + block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, hooks, nil) Require(t, err) // There shouldn't be any error in block generation if block == nil { t.Fatal("block should be generated as second tx should pass") } if len(block.Transactions()) != 2 { - t.Fatalf("expecting two txs found: %d", len(block.Transactions())) + t.Fatalf("expecting two txs, found: %d", len(block.Transactions())) } if block.Transactions()[1].Hash() != txes[1].Hash() { t.Fatal("tx hash mismatch, expecting second tx to be present in the block") @@ -49,12 +47,10 @@ func TestSequencerTxFilter(t *testing.T) { } func TestSequencerBlockFilterReject(t *testing.T) { - t.Parallel() - - builder, header, txes, hooks, cleanup := setupSequencerFilterTest(t, true) + builder, header, _, hooks, cleanup := setupSequencerFilterTest(t, true) defer cleanup() - block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, txes, hooks, nil) + block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, hooks, nil) if block != nil { t.Fatal("block shouldn't be generated when all txes have failed") } @@ -67,12 +63,11 @@ func TestSequencerBlockFilterReject(t *testing.T) { } func TestSequencerBlockFilterAccept(t *testing.T) { - t.Parallel() - builder, header, txes, hooks, cleanup := setupSequencerFilterTest(t, true) defer cleanup() - - block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, txes[1:], hooks, nil) + _, err := hooks.NextTxToSequence() // remove first transaction from hooks + Require(t, err) + block, err := builder.L2.ExecNode.ExecEngine.SequenceTransactions(header, hooks, nil) Require(t, err) if block == nil { t.Fatal("block should be generated as the tx should pass") @@ -114,7 +109,7 @@ func setupSequencerFilterTest(t *testing.T, isBlockFilter bool) (*NodeBuilder, * txes = append(txes, builder.L2Info.PrepareTx("Owner", "User", builder.L2Info.TransferGas, big.NewInt(1e12), []byte{1, 2, 3})) txes = append(txes, builder.L2Info.PrepareTx("User", "Owner", builder.L2Info.TransferGas, big.NewInt(1e12), nil)) - hooks := arbos.NoopSequencingHooks() + hooks := arbos.NoopSequencingHooks(txes) if isBlockFilter { hooks.BlockFilter = func(_ *types.Header, _ *state.StateDB, txes types.Transactions, _ types.Receipts) error { if len(txes[1].Data()) > 0 { diff --git a/system_tests/seq_nonce_test.go b/system_tests/seq_nonce_test.go index 648a902925..c85508fe83 100644 --- a/system_tests/seq_nonce_test.go +++ b/system_tests/seq_nonce_test.go @@ -15,12 +15,12 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" "github.com/offchainlabs/nitro/util/arbmath" ) func TestSequencerParallelNonces(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -59,7 +59,6 @@ func TestSequencerParallelNonces(t *testing.T) { } func TestSequencerNonceTooHigh(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -86,7 +85,6 @@ func TestSequencerNonceTooHigh(t *testing.T) { } func TestSequencerNonceTooHighQueueFull(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -124,3 +122,90 @@ func TestSequencerNonceTooHighQueueFull(t *testing.T) { time.Sleep(time.Millisecond * 100) } } + +func TestSequencerNonceHandling(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.execConfig.Sequencer.MaxBlockSpeed = time.Second + builder.execConfig.Sequencer.NonceFailureCacheExpiry = 4 * time.Second + cleanup := builder.Build(t) + defer cleanup() + + userAccount := "User" + builder.L2Info.GenerateAccount(userAccount) + userAccount2 := "User2" + builder.L2Info.GenerateAccount(userAccount2) + userAccount3 := "User3" + builder.L2Info.GenerateAccount(userAccount3) + val := big.NewInt(1e18) + builder.L2.TransferBalance(t, "Owner", "User", val, builder.L2Info) + builder.L2.TransferBalance(t, "Owner", "User2", val, builder.L2Info) + userBal, err := builder.L2.Client.BalanceAt(ctx, builder.L2Info.GetAddress(userAccount), nil) + Require(t, err) + if userBal.Cmp(val) != 0 { + t.Fatal("balance mismatch") + } + userBal2, err := builder.L2.Client.BalanceAt(ctx, builder.L2Info.GetAddress(userAccount2), nil) + Require(t, err) + if userBal2.Cmp(val) != 0 { + t.Fatal("balance mismatch") + } + + size := 40000 + data := make([]byte, size) + _, err = rand.Read(data) + Require(t, err) + + var largeTxs types.Transactions + builder.L2Info.GetInfoWithPrivKey(userAccount).Nonce.Store(0) + largeTxs = append(largeTxs, builder.L2Info.PrepareTx(userAccount, userAccount3, 70000000, big.NewInt(1e8), data)) + builder.L2Info.GetInfoWithPrivKey(userAccount).Nonce.Store(0) + largeTxs = append(largeTxs, builder.L2Info.PrepareTx(userAccount, userAccount3, 70000000, big.NewInt(1e9), data)) + builder.L2Info.GetInfoWithPrivKey(userAccount).Nonce.Store(0) + txFirst := builder.L2Info.PrepareTx(userAccount, userAccount3, 7000000, big.NewInt(1e8), data) + builder.L2Info.GetInfoWithPrivKey(userAccount).Nonce.Store(1) + largeTxs = append(largeTxs, builder.L2Info.PrepareTx(userAccount, userAccount3, 35000000, big.NewInt(1e5), data)) + builder.L2Info.GetInfoWithPrivKey(userAccount).Nonce.Store(1) + largeTxs = append(largeTxs, builder.L2Info.PrepareTx(userAccount, userAccount3, 35000000, big.NewInt(1e5+1), data)) + builder.L2Info.GetInfoWithPrivKey(userAccount).Nonce.Store(1) + largeTxs = append(largeTxs, builder.L2Info.PrepareTx(userAccount, userAccount3, 35000000, big.NewInt(1e5+2), data)) + + var allTxs types.Transactions + allTxs = append(allTxs, txFirst) + allTxs = append(allTxs, largeTxs...) + for i := 0; i < 5; i++ { + allTxs = append(allTxs, builder.L2Info.PrepareTx("Owner", userAccount3, 7000000, big.NewInt(1e8), nil)) + } + var wg sync.WaitGroup + wg.Add(len(allTxs)) + for i, tx := range allTxs { + go func(w *sync.WaitGroup, txParallel *types.Transaction) { + time.Sleep(time.Duration(i * 10 * int(time.Millisecond))) + _ = builder.L2.Client.SendTransaction(ctx, txParallel) + w.Done() + }(&wg, tx) + } + wg.Wait() + var blockNumsOfAcceptedTxs []uint64 + for _, tx := range allTxs { + receipt, err := builder.L2.Client.TransactionReceipt(ctx, tx.Hash()) + if err == nil { + blockNumsOfAcceptedTxs = append(blockNumsOfAcceptedTxs, receipt.BlockNumber.Uint64()) + } + } + if len(blockNumsOfAcceptedTxs) != 7 { + t.Fatalf("unexpected number of block nums in blockNumsOfAcceptedTxs. Have: %d, Want: 7", len(blockNumsOfAcceptedTxs)) + } + if blockNumsOfAcceptedTxs[0] != blockNumsOfAcceptedTxs[1] { + t.Fatal("first and second valid txs shouldnt have been sequenced in two different blocks") + } + if blockNumsOfAcceptedTxs[2] != blockNumsOfAcceptedTxs[0]+1 || + blockNumsOfAcceptedTxs[3] != blockNumsOfAcceptedTxs[0]+1 || + blockNumsOfAcceptedTxs[4] != blockNumsOfAcceptedTxs[0]+1 || + blockNumsOfAcceptedTxs[5] != blockNumsOfAcceptedTxs[0]+1 || + blockNumsOfAcceptedTxs[6] != blockNumsOfAcceptedTxs[0]+1 { + t.Fatal("all the following valid txs should have been sequenced in the immediate next block") + } +} diff --git a/system_tests/seq_pause_test.go b/system_tests/seq_pause_test.go index c867a98271..1f102e01b1 100644 --- a/system_tests/seq_pause_test.go +++ b/system_tests/seq_pause_test.go @@ -13,7 +13,6 @@ import ( ) func TestSequencerPause(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/seq_reject_test.go b/system_tests/seq_reject_test.go index 98b8939b0e..a795d4b4f5 100644 --- a/system_tests/seq_reject_test.go +++ b/system_tests/seq_reject_test.go @@ -24,17 +24,16 @@ import ( ) func TestSequencerRejection(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() feedErrChan := make(chan error, 10) - builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false) + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() cleanupSeq := builderSeq.Build(t) defer cleanupSeq() - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builder.takeOwnership = false port := testhelpers.AddrTCPPort(builderSeq.L2.ConsensusNode.BroadcastServer.ListenerAddr(), t) builder.nodeConfig.Feed.Input = *newBroadcastClientConfigTest(port) diff --git a/system_tests/seqcompensation_test.go b/system_tests/seqcompensation_test.go index 63bd170d1e..34e478fb73 100644 --- a/system_tests/seqcompensation_test.go +++ b/system_tests/seqcompensation_test.go @@ -16,7 +16,6 @@ import ( // L1 Pricer pool address gets something when the sequencer posts batches func TestSequencerCompensation(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) diff --git a/system_tests/seqfeed_test.go b/system_tests/seqfeed_test.go index 5d2e57ca17..a5d0e11c8a 100644 --- a/system_tests/seqfeed_test.go +++ b/system_tests/seqfeed_test.go @@ -16,6 +16,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/arbostypes" "github.com/offchainlabs/nitro/arbos/l1pricing" "github.com/offchainlabs/nitro/arbutil" @@ -55,14 +56,14 @@ func TestSequencerFeed(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false) + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() cleanupSeq := builderSeq.Build(t) defer cleanupSeq() seqInfo, seqNode, seqClient := builderSeq.L2Info, builderSeq.L2.ConsensusNode, builderSeq.L2.Client port := testhelpers.AddrTCPPort(seqNode.BroadcastServer.ListenerAddr(), t) - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builder.nodeConfig.Feed.Input = *newBroadcastClientConfigTest(port) builder.takeOwnership = false cleanup := builder.Build(t) @@ -93,11 +94,10 @@ func TestSequencerFeed(t *testing.T) { } func TestRelayedSequencerFeed(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false) + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builderSeq.nodeConfig.Feed.Output = *newBroadcasterConfigTest() cleanupSeq := builderSeq.Build(t) defer cleanupSeq() @@ -120,7 +120,7 @@ func TestRelayedSequencerFeed(t *testing.T) { defer currentRelay.StopAndWait() port = testhelpers.AddrTCPPort(currentRelay.GetListenerAddr(), t) - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builder.nodeConfig.Feed.Input = *newBroadcastClientConfigTest(port) builder.takeOwnership = false cleanup := builder.Build(t) @@ -187,8 +187,6 @@ func compareAllMsgResultsFromConsensusAndExecution( } func testLyingSequencer(t *testing.T, dasModeStr string) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -198,7 +196,7 @@ func testLyingSequencer(t *testing.T, dasModeStr string) { nodeConfigA.BatchPoster.Enable = true nodeConfigA.Feed.Output.Enable = false - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() builder.nodeConfig = nodeConfigA builder.chainConfig = chainConfig builder.L2Info = nil @@ -363,7 +361,7 @@ func testBlockHashComparison(t *testing.T, blockHash *common.Hash, mustMismatch port := testhelpers.AddrTCPPort(wsBroadcastServer.ListenerAddr(), t) - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() builder.nodeConfig.Feed.Input = *newBroadcastClientConfigTest(port) cleanup := builder.Build(t) defer cleanup() @@ -380,10 +378,13 @@ func testBlockHashComparison(t *testing.T, blockHash *common.Hash, mustMismatch RequestId: nil, L1BaseFee: nil, } + hooks := arbos.NoopSequencingHooks(types.Transactions{tx}) + _, err = hooks.NextTxToSequence() + Require(t, err) + hooks.TxErrors = []error{nil} l1IncomingMsg, err := gethexec.MessageFromTxes( &l1IncomingMsgHeader, - types.Transactions{tx}, - []error{nil}, + hooks, ) Require(t, err) diff --git a/system_tests/seqinbox_test.go b/system_tests/seqinbox_test.go index d340dc2d31..2c55f08d88 100644 --- a/system_tests/seqinbox_test.go +++ b/system_tests/seqinbox_test.go @@ -10,6 +10,7 @@ import ( "fmt" "math/big" "math/rand" + "strings" "testing" "time" @@ -17,6 +18,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient/gethclient" "github.com/ethereum/go-ethereum/params" @@ -134,11 +136,10 @@ func deployGasRefunder(ctx context.Context, t *testing.T, builder *NodeBuilder) } func testSequencerInboxReaderImpl(t *testing.T, validator bool) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() if validator { builder.nodeConfig.BlockValidator.Enable = true } @@ -222,8 +223,14 @@ func testSequencerInboxReaderImpl(t *testing.T, validator bool) { builder.L1Info.GetInfoWithPrivKey("Faucet").Nonce.Store(currNonce) for j := uint64(0); j < blocksToPad; j++ { tx := builder.L1Info.PrepareTx("Faucet", "User", 30000, big.NewInt(1e12), nil) - Require(t, builder.L1.Client.SendTransaction(ctx, tx)) - _, _ = builder.L1.EnsureTxSucceeded(tx) + err = builder.L1.Client.SendTransaction(ctx, tx) + if err != nil { + if !strings.Contains(err.Error(), "already known") && !strings.Contains(err.Error(), core.ErrNonceTooLow.Error()) { + t.Fatalf("error sending txs to create padding for reorg: %s", err.Error()) + } + } else { + _, _ = builder.L1.EnsureTxSucceeded(tx) + } } currentHeader, err = builder.L1.Client.HeaderByNumber(ctx, nil) Require(t, err) diff --git a/system_tests/simulatev1_push0_test.go b/system_tests/simulatev1_push0_test.go index 09a460d3c3..ac0960dddc 100644 --- a/system_tests/simulatev1_push0_test.go +++ b/system_tests/simulatev1_push0_test.go @@ -14,7 +14,6 @@ import ( ) func TestSimulateV1Push0(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/snap_sync_test.go b/system_tests/snap_sync_test.go index 43bee07137..0f2d616dd7 100644 --- a/system_tests/snap_sync_test.go +++ b/system_tests/snap_sync_test.go @@ -28,8 +28,9 @@ func TestSnapSync(t *testing.T) { var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs // 1st node with sequencer, stays up all the time. - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() + // only supported for hash scheme + builder.RequireScheme(t, rawdb.HashScheme) builder.L2Info = NewBlockChainTestInfo( t, types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), @@ -57,7 +58,7 @@ func TestSnapSync(t *testing.T) { // Create transactions till batch count is 10 createTransactionTillBatchCount(ctx, t, builder, 10) // Wait for nodeB to sync up to the first node - waitForBlocksToCatchup(ctx, t, builder.L2.Client, nodeB.Client) + waitForBlocksToCatchup(ctx, t, builder.L2.Client, nodeB.Client, 10*time.Minute) // Create a config with snap sync enabled and same database directory as the 2nd node nodeConfig := createNodeConfigWithSnapSync(t, builder) @@ -130,7 +131,8 @@ func waitForBlockToCatchupToMessageCount( } } -func waitForBlocksToCatchup(ctx context.Context, t *testing.T, clientA *ethclient.Client, clientB *ethclient.Client) { +// waitForBlocksToCatchup has a time "limit" factor to limit running this function forever in weird cases such as running with race detection in nightly CI +func waitForBlocksToCatchup(ctx context.Context, t *testing.T, clientA *ethclient.Client, clientB *ethclient.Client, limit time.Duration) { for { select { case <-ctx.Done(): @@ -143,6 +145,8 @@ func waitForBlocksToCatchup(ctx context.Context, t *testing.T, clientA *ethclien if headerA.Number.Cmp(headerB.Number) == 0 { return } + case <-time.After(limit): + t.Fatal("waitForBlocksToCatchup didnt finish") } } } @@ -166,7 +170,9 @@ func waitForBatchCountToCatchup(ctx context.Context, t *testing.T, inboxTrackerA } func createTransactionTillBatchCount(ctx context.Context, t *testing.T, builder *NodeBuilder, finalCount uint64) { - for { + // We run the loop for 6000 iterations ~ maximum of 10 minutes of run time before failing. This is to avoid + // running this function forever in weird cases such as running with race detection in nightly CI + for i := uint64(0); i < 6000; i++ { Require(t, ctx.Err()) tx := builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, big.NewInt(1), nil) err := builder.L2.Client.SendTransaction(ctx, tx) @@ -176,9 +182,11 @@ func createTransactionTillBatchCount(ctx context.Context, t *testing.T, builder count, err := builder.L2.ConsensusNode.InboxTracker.GetBatchCount() Require(t, err) if count > finalCount { - break + return } + time.Sleep(100 * time.Millisecond) // give some time for other components (reader/tracker) to read the batches from L1 } + t.Fatal("createTransactionTillBatchCount didnt finish") } func createNodeConfigWithSnapSync(t *testing.T, builder *NodeBuilder) *arbnode.Config { diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index a52f924962..138d7c8dcd 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -46,6 +46,7 @@ import ( func makeBackgroundTxs(ctx context.Context, builder *NodeBuilder) error { for i := uint64(0); ctx.Err() == nil; i++ { + time.Sleep(time.Millisecond * 100) builder.L2Info.Accounts["BackgroundUser"].Nonce.Store(i) tx := builder.L2Info.PrepareTx("BackgroundUser", "BackgroundUser", builder.L2Info.TransferGas, common.Big0, nil) err := builder.L2.Client.SendTransaction(ctx, tx) @@ -74,7 +75,7 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) }() var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).DontParalellise() builder.L2Info = NewBlockChainTestInfo( t, types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), @@ -82,7 +83,7 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) ) // For now validation only works with HashScheme set - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour cleanupA := builder.Build(t) @@ -497,8 +498,6 @@ func TestStakersCooperative(t *testing.T) { } func TestGetValidatorWalletContractWithDataposterOnlyUsedToCreateValidatorWalletContract(t *testing.T) { - t.Parallel() - ctx, cancelCtx := context.WithCancel(context.Background()) defer cancelCtx() diff --git a/system_tests/state_fuzz_test.go b/system_tests/state_fuzz_test.go index 48da1fe394..c6ec9563b8 100644 --- a/system_tests/state_fuzz_test.go +++ b/system_tests/state_fuzz_test.go @@ -38,7 +38,7 @@ func BuildBlock( chainContext core.ChainContext, inbox arbstate.InboxBackend, seqBatch []byte, - runMode core.MessageRunMode, + runCtx *core.MessageRunContext, ) (*types.Block, error) { var delayedMessagesRead uint64 if lastBlockHeader != nil { @@ -66,7 +66,7 @@ func BuildBlock( } block, _, err := arbos.ProduceBlock( - l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, false, runMode, + l1Message, delayedMessagesRead, lastBlockHeader, statedb, chainContext, false, runCtx, ) return block, err } @@ -136,7 +136,7 @@ func (c noopChainContext) GetHeader(common.Hash, uint64) *types.Header { } func FuzzStateTransition(f *testing.F) { - f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte, runModeSeed uint8) { + f.Fuzz(func(t *testing.T, compressSeqMsg bool, seqMsg []byte, delayedMsg []byte, targetsSeed uint8, runCtxSeed uint8) { if len(seqMsg) > 0 && daprovider.IsL1AuthenticatedMessageHeaderByte(seqMsg[0]) { return } @@ -198,9 +198,40 @@ func FuzzStateTransition(f *testing.F) { positionWithinMessage: 0, delayedMessages: delayedMessages, } - numberOfMessageRunModes := uint8(core.MessageReplayMode) + 1 // TODO update number of run modes when new mode is added - runMode := core.MessageRunMode(runModeSeed % numberOfMessageRunModes) - _, err = BuildBlock(statedb, genesis.Header(), noopChainContext{chainConfig: chaininfo.ArbitrumDevTestChainConfig()}, inbox, seqBatch, runMode) + + localTarget := rawdb.LocalTarget() + targets := []rawdb.WasmTarget{localTarget} + if targetsSeed&1 != 0 { + targets = append(targets, rawdb.TargetWavm) + } + if targetsSeed&2 != 0 && localTarget != rawdb.TargetArm64 { + targets = append(targets, rawdb.TargetArm64) + } + if targetsSeed&4 != 0 && localTarget != rawdb.TargetAmd64 { + targets = append(targets, rawdb.TargetAmd64) + } + if targetsSeed&8 != 0 && localTarget != rawdb.TargetHost { + targets = append(targets, rawdb.TargetHost) + } + + runCtxNumber := runCtxSeed % 6 + var runCtx *core.MessageRunContext + switch runCtxNumber { + case 0: + runCtx = core.NewMessageCommitContext(targets) + case 1: + runCtx = core.NewMessageReplayContext() + case 2: + runCtx = core.NewMessageRecordingContext(targets) + case 3: + runCtx = core.NewMessagePrefetchContext() + case 4: + runCtx = core.NewMessageEthcallContext() + case 5: + runCtx = core.NewMessageGasEstimationContext() + } + + _, err = BuildBlock(statedb, genesis.Header(), noopChainContext{chainConfig: chaininfo.ArbitrumDevTestChainConfig()}, inbox, seqBatch, runCtx) if err != nil { // With the fixed header it shouldn't be possible to read a delayed message, // and no other type of error should be possible. diff --git a/system_tests/staterecovery_test.go b/system_tests/staterecovery_test.go index f4eb9b247f..45aa75b2f0 100644 --- a/system_tests/staterecovery_test.go +++ b/system_tests/staterecovery_test.go @@ -21,8 +21,6 @@ func TestRectreateMissingStates(t *testing.T) { defer cancel() builder := NewNodeBuilder(ctx).DefaultConfig(t, true) builder.execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - builder.execConfig.Caching.StateScheme = rawdb.HashScheme builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 16 builder.execConfig.Caching.SnapshotCache = 0 // disable snapshots _ = builder.Build(t) @@ -60,8 +58,8 @@ func TestRectreateMissingStates(t *testing.T) { cachingConfig := gethexec.DefaultCachingConfig // For now Archive node should use HashScheme cachingConfig.StateScheme = rawdb.HashScheme - cacheConfig := gethexec.DefaultCacheConfigFor(stack, &cachingConfig) - bc, err := gethexec.GetBlockChain(chainDb, cacheConfig, builder.chainConfig, nil, builder.execConfig.TxLookupLimit) + cacheConfig := gethexec.DefaultCacheConfigFor(&cachingConfig) + bc, err := gethexec.GetBlockChain(chainDb, cacheConfig, builder.chainConfig, nil, &builder.execConfig.TxIndexer) Require(t, err) err = staterecovery.RecreateMissingStates(chainDb, bc, cacheConfig, 1) Require(t, err) diff --git a/system_tests/storage_trie_test.go b/system_tests/storage_trie_test.go index 75a2f5aee6..7937c5b0d4 100644 --- a/system_tests/storage_trie_test.go +++ b/system_tests/storage_trie_test.go @@ -17,7 +17,6 @@ import ( ) func TestStorageTrie(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -26,7 +25,7 @@ func TestStorageTrie(t *testing.T) { // This test tests validates blocks at the end. // For now, validation only works with HashScheme set. - builder.execConfig.Caching.StateScheme = rawdb.HashScheme + builder.RequireScheme(t, rawdb.HashScheme) builder.nodeConfig.BlockValidator.Enable = false builder.nodeConfig.Staker.Enable = true builder.nodeConfig.BatchPoster.Enable = true diff --git a/system_tests/stylus_test.go b/system_tests/stylus_test.go index e6e9175b96..033ca12e29 100644 --- a/system_tests/stylus_test.go +++ b/system_tests/stylus_test.go @@ -57,12 +57,10 @@ func TestProgramArbitratorMemory(t *testing.T) { } func TestProgramArbitratorActivateTwice(t *testing.T) { - t.Parallel() testActivateTwice(t, false) } func TestProgramArbitratorActivateFails(t *testing.T) { - t.Parallel() testActivateFails(t, false) } @@ -102,11 +100,11 @@ func fullRecurseTest() [][]multiCallRecurse { } func TestProgramLongCall(t *testing.T) { - testProgramResursiveCalls(t, fullRecurseTest(), true) + testProgramRecursiveCalls(t, fullRecurseTest(), true) } func TestProgramLongArbitratorCall(t *testing.T) { - testProgramResursiveCalls(t, fullRecurseTest(), false) + testProgramRecursiveCalls(t, fullRecurseTest(), false) } func TestProgramArbitratorStylusUpgrade(t *testing.T) { diff --git a/system_tests/stylus_trace_test.go b/system_tests/stylus_trace_test.go index 1bbad44ec8..da05678f55 100644 --- a/system_tests/stylus_trace_test.go +++ b/system_tests/stylus_trace_test.go @@ -431,7 +431,7 @@ func TestStylusOpcodeTraceCreate(t *testing.T) { checkOpcode(t, result, 11, vm.POP, create2Addr[:]) } -// TestStylusOpcodeTraceEquivalence compares a Stylus trace with a equivalent Solidity/EVM trace. Notice +// TestStylusOpcodeTraceEquivalence compares a Stylus trace with an equivalent Solidity/EVM trace. Notice // the Stylus trace does not contain all opcodes from the Solidity/EVM trace. Instead, this test // only checks that both traces contain the same basic opcodes. func TestStylusOpcodeTraceEquivalence(t *testing.T) { diff --git a/system_tests/timeboost_test.go b/system_tests/timeboost_test.go index 01c8fe7745..ea5bb14c8b 100644 --- a/system_tests/timeboost_test.go +++ b/system_tests/timeboost_test.go @@ -53,14 +53,13 @@ import ( ) func TestTimeboostTxsTimeoutByBlock(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() numTxs, blockBasedTimeout := uint64(10), uint64(5) - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, blockBasedTimeout) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, blockBasedTimeout) seqClient, seqInfo := builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() seqInfo.GenerateAccount("User2") @@ -143,13 +142,12 @@ func TestTimeboostAuctionResolutionDuringATieMultipleRuns(t *testing.T) { } func testAuctionResolutionDuringATie(t *testing.T, multiRuns bool) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, _, builderSeq, cleanupSeq, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, _, builderSeq, cleanupSeq, _, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) _, seqClient, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() @@ -169,9 +167,9 @@ func testAuctionResolutionDuringATie(t *testing.T, multiRuns bool) { for { // For the next round, we will send equal bids and verify we get the correct winner t.Logf("Alice and Bob now submitting their equal bids at %v", time.Now()) - aliceBid, err := aliceBidderClient.Bid(ctx, big.NewInt(1), aliceAddr) + aliceBid, err := aliceBidderClient.Bid(ctx, big.NewInt(2), aliceAddr) Require(t, err) - bobBid, err := bobBidderClient.Bid(ctx, big.NewInt(1), bobAddr) + bobBid, err := bobBidderClient.Bid(ctx, big.NewInt(2), bobAddr) Require(t, err) t.Logf("Alice bid %+v", aliceBid) t.Logf("Bob bid %+v", bobBid) @@ -252,13 +250,12 @@ func TestTimeboostExpressLaneTxsHandlingDuringSequencerSwapDueToActiveSequencerC } func testTxsHandlingDuringSequencerSwap(t *testing.T, dueToCrash bool) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, forwarder, cleanupForwarder := setupExpressLaneAuction(t, tmpDir, ctx, withForwardingSeq, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, forwarder, cleanupForwarder, _ := setupExpressLaneAuction(t, tmpDir, ctx, withForwardingSeq, 0) seqB, seqClientB, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info seqA := forwarder.ConsensusNode if !dueToCrash { @@ -328,7 +325,7 @@ func testTxsHandlingDuringSequencerSwap(t *testing.T, dueToCrash bool) { currentChosen, err := redisCoordinatorGetter.CurrentChosenSequencer(ctx) Require(t, err) if currentChosen != seqB.Stack.HTTPEndpoint() { - t.Fatalf("unexepcted current chosen sequencer. Want: %s, Got: %s", seqB.Stack.HTTPEndpoint(), currentChosen) + t.Fatalf("unexpected current chosen sequencer. Want: %s, Got: %s", seqB.Stack.HTTPEndpoint(), currentChosen) } redisCoordinatorSetter := &rediscoordinator.RedisCoordinator{RedisCoordinator: redisCoordinatorGetter} @@ -381,13 +378,12 @@ func testTxsHandlingDuringSequencerSwap(t *testing.T, dueToCrash bool) { } func TestTimeboostForwardingExpressLaneTxs(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, forwarder, cleanupForwarder := setupExpressLaneAuction(t, tmpDir, ctx, withForwardingSeq, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, forwarder, cleanupForwarder, _ := setupExpressLaneAuction(t, tmpDir, ctx, withForwardingSeq, 0) seqClient, seqInfo := builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() defer cleanupForwarder() @@ -422,13 +418,12 @@ func TestTimeboostForwardingExpressLaneTxs(t *testing.T) { } func TestTimeboostExpressLaneTransactionHandlingComplex(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) seq, seqClient, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() @@ -523,13 +518,12 @@ func TestTimeboostExpressLaneTransactionHandlingComplex(t *testing.T) { } func TestTimeboostExpressLaneTransactionHandling(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) seq, seqClient, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() @@ -668,8 +662,6 @@ func dbKey(prefix []byte, pos uint64) []byte { } func TestTimeboostBulkBlockMetadataFetcher(t *testing.T) { - t.Parallel() - ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -930,7 +922,7 @@ func TestTimeboostBulkBlockMetadataAPI(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builder.nodeConfig.TransactionStreamer.TrackBlockMetadataFrom = 1 builder.execConfig.BlockMetadataApiCacheSize = 0 // Caching is disabled cleanup := builder.Build(t) @@ -1137,13 +1129,12 @@ func TestTimeboostBulkBlockMetadataAPI(t *testing.T) { // } func TestTimeboostSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, _, _, _ := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) seq, seqClient, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() @@ -1177,14 +1168,12 @@ func TestTimeboostSequencerFeed_ExpressLaneAuction_ExpressLaneTxsHaveAdvantage(t } func TestTimeboostSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespected_TimeboostedFieldIsCorrect(t *testing.T) { - t.Parallel() - logHandler := testhelpers.InitTestLog(t, log.LevelInfo) ctx, cancel := context.WithCancel(context.Background()) defer cancel() tmpDir := t.TempDir() - auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, feedListener, cleanupFeedListener := setupExpressLaneAuction(t, tmpDir, ctx, withFeedListener, 0) + auctionContractAddr, aliceBidderClient, bobBidderClient, roundDuration, builderSeq, cleanupSeq, feedListener, cleanupFeedListener, _ := setupExpressLaneAuction(t, tmpDir, ctx, withFeedListener, 0) seq, seqClient, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info defer cleanupSeq() defer cleanupFeedListener() @@ -1300,7 +1289,7 @@ func TestTimeboostSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespecte verifyTimeboostedCorrectness(t, ctx, "Alice", seq, seqClient, false, aliceTx, aliceBlock) verifyTimeboostedCorrectness(t, ctx, "Charlie", seq, seqClient, true, charlie0, charlieBlock) - // Verify that timeboosted byte array receieved via sequencer feed is correct + // Verify that timeboosted byte array received via sequencer feed is correct _, err = WaitForTx(ctx, feedListener.Client, charlie0.Hash(), time.Second*5) Require(t, err) _, err = WaitForTx(ctx, feedListener.Client, aliceTx.Hash(), time.Second*5) @@ -1313,6 +1302,24 @@ func TestTimeboostSequencerFeed_ExpressLaneAuction_InnerPayloadNoncesAreRespecte } } +func TestTimeboostBidValidator_FailEthcallValidation(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tmpDir := t.TempDir() + + _, _, bobBidderClient, _, builderSeq, cleanupSeq, _, _, bidValidator := setupExpressLaneAuction(t, tmpDir, ctx, 0, 0) + _, _, seqInfo := builderSeq.L2.ConsensusNode, builderSeq.L2.Client, builderSeq.L2Info + defer cleanupSeq() + + // We set reservePrice to a lower value (0) than what it is on-chain (3), so that validator's initial checks pass, but eth_call validation fails + bidValidator.SetReservePrice(common.Big0) + _, err := bobBidderClient.Bid(ctx, big.NewInt(1), seqInfo.GetAddress("Bob")) + if err == nil { + t.Fatal("bidValidator failed to reject bid with lower than reservePrice amount, eth_call validation failed") + } +} + // verifyTimeboostedCorrectness is used to check if the timeboosted byte array in both the sequencer's tx streamer and the client node's tx streamer (which is connected // to the sequencer feed) is accurate, i.e it represents correctly whether a tx is timeboosted or not func verifyTimeboostedCorrectness(t *testing.T, ctx context.Context, user string, tNode *arbnode.Node, tClient *ethclient.Client, isTimeboosted bool, userTx *types.Transaction, userTxBlockNum uint64) { @@ -1338,7 +1345,7 @@ func verifyTimeboostedCorrectness(t *testing.T, ctx context.Context, user string t.Fatalf("incorrect timeboosted bit for %s's tx, it should be timeboosted", user) } } else if got { - // Other tx's right now shouln't be timeboosted + // Other tx's right now shouldn't be timeboosted t.Fatalf("incorrect timeboosted bit for nonspecified tx with index: %d, it shouldn't be timeboosted", txIndex) } } @@ -1358,9 +1365,9 @@ func placeBidsAndDecideWinner(t *testing.T, ctx context.Context, seqClient *ethc // We are now in the bidding round, both issue their bids. winner will win t.Logf("%s and %s now submitting their bids at %v", winner, loser, time.Now()) - winnerBid, err := winnerBidderClient.Bid(ctx, big.NewInt(2), seqInfo.GetAddress(winner)) + winnerBid, err := winnerBidderClient.Bid(ctx, big.NewInt(3), seqInfo.GetAddress(winner)) Require(t, err) - loserBid, err := loserBidderClient.Bid(ctx, big.NewInt(1), seqInfo.GetAddress(loser)) + loserBid, err := loserBidderClient.Bid(ctx, big.NewInt(2), seqInfo.GetAddress(loser)) Require(t, err) t.Logf("%s bid %+v", winner, winnerBid) t.Logf("%s bid %+v", loser, loserBid) @@ -1464,7 +1471,7 @@ func setupExpressLaneAuction( ctx context.Context, extraNodeTy extraNodeType, queueTimeoutInBlocks uint64, -) (common.Address, *timeboost.BidderClient, *timeboost.BidderClient, time.Duration, *NodeBuilder, func(), *TestClient, func()) { +) (common.Address, *timeboost.BidderClient, *timeboost.BidderClient, time.Duration, *NodeBuilder, func(), *TestClient, func(), *timeboost.BidValidator) { seqPort := getRandomPort(t) forwarderPort := getRandomPort(t) @@ -1472,7 +1479,7 @@ func setupExpressLaneAuction( expressLaneRedisURL := redisutil.CreateTestRedis(ctx, t) initRedisForTest(t, ctx, expressLaneRedisURL, nodeNames) - builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false) + builderSeq := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builderSeq.isSequencer = true builderSeq.l2StackConfig.HTTPHost = "localhost" builderSeq.l2StackConfig.HTTPPort = seqPort @@ -1500,7 +1507,7 @@ func setupExpressLaneAuction( var cleanupExtraNode func() switch extraNodeTy { case withForwardingSeq: - extraNodebuilder := NewNodeBuilder(ctx).DefaultConfig(t, false) + extraNodebuilder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() extraNodebuilder.isSequencer = true extraNodebuilder.takeOwnership = false extraNodebuilder.l2StackConfig.HTTPHost = "localhost" @@ -1528,7 +1535,7 @@ func setupExpressLaneAuction( t.Fatalf("failed to cast listener address to *net.TCPAddr") } port := tcpAddr.Port - extraNodebuilder := NewNodeBuilder(ctx).DefaultConfig(t, false) + extraNodebuilder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() extraNodebuilder.takeOwnership = false extraNodebuilder.nodeConfig.Feed.Input = *newBroadcastClientConfigTest(port) extraNodebuilder.nodeConfig.Feed.Input.Timeout = broadcastclient.DefaultConfig.Timeout @@ -1621,7 +1628,7 @@ func setupExpressLaneAuction( biddingToken := erc20Addr auctionClosingSeconds := bidRoundSeconds / 2 reserveSubmissionSeconds := uint64(1) - minReservePrice := big.NewInt(1) // 1 wei. + minReservePrice := big.NewInt(2) // 1 wei. roleAdmin := auctioneerAddr tx, err = auctionContract.Initialize( &ownerOpts, @@ -1748,11 +1755,13 @@ func setupExpressLaneAuction( stack, err := node.New(&stackConf) Require(t, err) cfg := &timeboost.BidValidatorConfig{ - RpcEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), - AuctionContractAddress: proxyAddr.Hex(), - RedisURL: redisURL, - ProducerConfig: pubsub.TestProducerConfig, - MaxBidsPerSender: 5, + RpcEndpoint: fmt.Sprintf("http://localhost:%d", seqPort), + AuctionContractAddress: proxyAddr.Hex(), + RedisURL: redisURL, + ProducerConfig: pubsub.TestProducerConfig, + MaxBidsPerSender: 5, + EnableEthcallValidation: true, + AuctioneerAddress: seqInfo.Accounts["AuctionContract"].Address.Hex(), } fetcher := func() *timeboost.BidValidatorConfig { return cfg @@ -1836,7 +1845,7 @@ func setupExpressLaneAuction( t.Logf("Alice and Bob are now deposited into the autonomous auction contract, waiting %v for bidding round..., timestamp %v", waitTime, time.Now()) time.Sleep(roundTimingInfo.TimeTilNextRound()) t.Logf("Reached the bidding round at %v", time.Now()) - return proxyAddr, alice, bob, roundDuration, builderSeq, cleanupSeq, extraNode, cleanupExtraNode + return proxyAddr, alice, bob, roundDuration, builderSeq, cleanupSeq, extraNode, cleanupExtraNode, bidValidator } func awaitAuctionResolved( diff --git a/system_tests/transfer_test.go b/system_tests/transfer_test.go index 0b68303484..eb940713e0 100644 --- a/system_tests/transfer_test.go +++ b/system_tests/transfer_test.go @@ -62,7 +62,7 @@ func TestP256Verify(t *testing.T) { }, } { t.Run(tc.desc, func(t *testing.T) { - builder := NewNodeBuilder(ctx).DefaultConfig(t, false) + builder := NewNodeBuilder(ctx).DefaultConfig(t, false).DontParalellise() builder.chainConfig.ArbitrumChainParams.InitialArbOSVersion = tc.initialVersion cleanup := builder.Build(t) defer cleanup() diff --git a/system_tests/triedb_race_test.go b/system_tests/triedb_race_test.go index 78a7258aea..eeb0785207 100644 --- a/system_tests/triedb_race_test.go +++ b/system_tests/triedb_race_test.go @@ -8,7 +8,6 @@ import ( "github.com/ethereum/go-ethereum/arbitrum" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rpc" @@ -25,8 +24,6 @@ func TestTrieDBCommitRace(t *testing.T) { builder.execConfig.Sequencer.MaxBlockSpeed = 0 builder.execConfig.Sequencer.MaxTxDataSize = 150 // 1 test tx ~= 110 builder.execConfig.Caching.Archive = true - // For now Archive node should use HashScheme - builder.execConfig.Caching.StateScheme = rawdb.HashScheme builder.execConfig.Caching.BlockCount = 127 builder.execConfig.Caching.BlockAge = 0 builder.execConfig.Caching.MaxNumberOfBlocksToSkipStateSaving = 127 diff --git a/system_tests/twonodes_test.go b/system_tests/twonodes_test.go index 2b18e9438d..2aeec3d1ee 100644 --- a/system_tests/twonodes_test.go +++ b/system_tests/twonodes_test.go @@ -13,7 +13,6 @@ import ( ) func testTwoNodesSimple(t *testing.T, dasModeStr string) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/system_tests/twonodeslong_test.go b/system_tests/twonodeslong_test.go index e088194007..8be108fd6c 100644 --- a/system_tests/twonodeslong_test.go +++ b/system_tests/twonodeslong_test.go @@ -21,7 +21,6 @@ import ( ) func testTwoNodesLong(t *testing.T, dasModeStr string) { - t.Parallel() largeLoops := 8 avgL2MsgsPerLoop := 30 avgDelayedMessagesPerLoop := 10 @@ -78,7 +77,7 @@ func testTwoNodesLong(t *testing.T, dasModeStr string) { if delayedFaucetBalance.Cmp(delayedFaucetNeeds) != 0 { t.Fatalf("Unexpected balance, has %v, expects %v", delayedFaucetBalance, delayedFaucetNeeds) } - t.Logf("DelayedFaucet has %v, per delayd: %v, baseprice: %v", delayedFaucetBalance, fundsPerDelayed, l2pricing.InitialBaseFeeWei) + t.Logf("DelayedFaucet has %v, per delayed: %v, baseprice: %v", delayedFaucetBalance, fundsPerDelayed, l2pricing.InitialBaseFeeWei) if avgTotalL1MessagesPerLoop < avgDelayedMessagesPerLoop { Fatal(t, "bad params, avgTotalL1MessagesPerLoop should include avgDelayedMessagesPerLoop") diff --git a/system_tests/unsupported_txtypes_test.go b/system_tests/unsupported_txtypes_test.go index a45172a8f2..50f482660f 100644 --- a/system_tests/unsupported_txtypes_test.go +++ b/system_tests/unsupported_txtypes_test.go @@ -12,6 +12,7 @@ import ( "errors" "math/big" "testing" + "time" "github.com/holiman/uint256" @@ -111,7 +112,9 @@ func TestBlobAndInternalTxsAsDelayedMsgReject(t *testing.T) { Require(t, err) } - blocknum, err := builder.L2.Client.BlockNumber(ctx) + receipt, err := WaitForTx(ctx, builder.L2.Client, delayedTx2.Hash(), time.Second*30) + + blocknum := receipt.BlockNumber.Uint64() Require(t, err) for i := uint64(0); i <= blocknum; i++ { block, err := builder.L2.Client.BlockByNumber(ctx, new(big.Int).SetUint64(i)) diff --git a/system_tests/validation_mock_test.go b/system_tests/validation_mock_test.go index 130a2d6d11..57b90eb8ea 100644 --- a/system_tests/validation_mock_test.go +++ b/system_tests/validation_mock_test.go @@ -8,9 +8,9 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/rpc" @@ -62,8 +62,8 @@ func (s *mockSpawner) WasmModuleRoots() ([]common.Hash, error) { return mockWasmModuleRoots, nil } -func (s *mockSpawner) StylusArchs() []ethdb.WasmTarget { - return []ethdb.WasmTarget{"mock"} +func (s *mockSpawner) StylusArchs() []rawdb.WasmTarget { + return []rawdb.WasmTarget{"mock"} } func (s *mockSpawner) Launch(entry *validator.ValidationInput, moduleRoot common.Hash) validator.ValidationRun { @@ -203,7 +203,6 @@ func createMockValidationNode(t *testing.T, ctx context.Context, config *server_ // mostly tests translation to/from json and running over network func TestValidationServerAPI(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() _, validationDefault := createMockValidationNode(t, ctx, nil) @@ -273,7 +272,6 @@ func TestValidationServerAPI(t *testing.T) { } func TestValidationClientRoom(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() mockSpawner, spawnerStack := createMockValidationNode(t, ctx, nil) @@ -352,7 +350,6 @@ func TestValidationClientRoom(t *testing.T) { } func TestExecutionKeepAlive(t *testing.T) { - t.Parallel() ctx, cancel := context.WithCancel(context.Background()) defer cancel() _, validationDefault := createMockValidationNode(t, ctx, nil) diff --git a/system_tests/wrap_transaction_test.go b/system_tests/wrap_transaction_test.go index dd68c25d6a..ca851bc26d 100644 --- a/system_tests/wrap_transaction_test.go +++ b/system_tests/wrap_transaction_test.go @@ -84,7 +84,7 @@ func EnsureTxSucceeded(ctx context.Context, client *ethclient.Client, tx *types. func EnsureTxSucceededWithTimeout(ctx context.Context, client *ethclient.Client, tx *types.Transaction, timeout time.Duration) (*types.Receipt, error) { receipt, err := WaitForTx(ctx, client, tx.Hash(), timeout) if err != nil { - return nil, fmt.Errorf("waitFoxTx (tx=%s) got: %w", tx.Hash().Hex(), err) + return nil, fmt.Errorf("waitForTx (tx=%s) got: %w", tx.Hash().Hex(), err) } if receipt.Status == types.ReceiptStatusSuccessful && tx.ChainId().Cmp(simulatedChainID) == 0 { for { @@ -115,7 +115,7 @@ func EnsureTxFailedWithTimeout(t *testing.T, ctx context.Context, client *ethcli receipt, err := WaitForTx(ctx, client, tx.Hash(), timeout) Require(t, err) if receipt.Status != types.ReceiptStatusFailed { - Fatal(t, "unexpected succeess") + Fatal(t, "unexpected success") } return receipt } diff --git a/timeboost/auctioneer.go b/timeboost/auctioneer.go index 56b8c0630c..95111466c9 100644 --- a/timeboost/auctioneer.go +++ b/timeboost/auctioneer.go @@ -105,7 +105,7 @@ var TestAuctioneerServerConfig = AuctioneerServerConfig{ func AuctioneerServerConfigAddOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enable", DefaultAuctioneerServerConfig.Enable, "enable auctioneer server") f.String(prefix+".redis-url", DefaultAuctioneerServerConfig.RedisURL, "url of redis server to receive bids from bid validators") - pubsub.ConsumerConfigAddOptions(prefix+".consumer-config", f) + pubsub.ConsumerConfigAddOptionsWithDefaults(prefix+".consumer-config", f, DefaultAuctioneerConsumerConfig) f.Duration(prefix+".stream-timeout", DefaultAuctioneerServerConfig.StreamTimeout, "Timeout on polling for existence of redis streams") genericconf.WalletConfigAddOptions(prefix+".wallet", f, "wallet for auctioneer server") f.String(prefix+".sequencer-endpoint", DefaultAuctioneerServerConfig.SequencerEndpoint, "sequencer RPC endpoint") @@ -344,9 +344,15 @@ func (a *AuctioneerServer) updateCoordination(ctx context.Context) time.Duration elapsed := time.Now().UnixMilli() - storedTimestamp if elapsed > a.auctioneerLivenessTimeout.Milliseconds() { log.Trace("Lock is stale, deleting and trying to acquire", "id", a.myId, "storedId", storedId, "elapsedMs", elapsed) - // Delete the stale lock - deleted := a.redisClient.Del(ctx, AUCTIONEER_CHOSEN_KEY).Val() - if deleted > 0 { + if delErr := a.redisClient.Del(ctx, AUCTIONEER_CHOSEN_KEY).Err(); delErr != nil { + log.Error("Error deleting stale lock key", + "id", a.myId, + "key", AUCTIONEER_CHOSEN_KEY, + "error", delErr, + "storedId", storedId, + "storedTimestamp", storedTimestamp, + "elapsedMs", elapsed) + } else { // Try to acquire with SetNX success = a.redisClient.SetNX(ctx, AUCTIONEER_CHOSEN_KEY, candidateValue, a.auctioneerLivenessTimeout).Val() if success { @@ -509,7 +515,7 @@ func (a *AuctioneerServer) resolveAuction(ctx context.Context) error { if newRpc { a.auctionContract, err = express_lane_auctiongen.NewExpressLaneAuction(a.auctionContractAddr, ethclient.NewClient(sequencerRpc)) if err != nil { - return fmt.Errorf("failed to recreate ExpressLaneAuction conctract bindings with new sequencer endpoint: %w", err) + return fmt.Errorf("failed to recreate ExpressLaneAuction contract bindings with new sequencer endpoint: %w", err) } } @@ -705,7 +711,7 @@ func (a *AuctioneerServer) GetId() string { func (a *AuctioneerServer) StopAndWait() { // The AUCTIONEER_CHOSEN_KEY lock will be considered expired by other auctioneers after - // auctioneerLivenessTimeout. This timeout gives time for existing messages to become + // auctioneerLivenessTimeout. This timeout gives time for existing messages to become // unclaimed after IdleTimeToAutoclaim before the secondary auctioneer starts consuming // messages. a.StopWaiter.StopAndWait() diff --git a/timeboost/bid_validator.go b/timeboost/bid_validator.go index 6e1f1fe5b0..c1205f9fae 100644 --- a/timeboost/bid_validator.go +++ b/timeboost/bid_validator.go @@ -2,6 +2,7 @@ package timeboost import ( "context" + "encoding/hex" "fmt" "math/big" "sync" @@ -11,6 +12,7 @@ import ( "github.com/redis/go-redis/v9" "github.com/spf13/pflag" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -32,23 +34,27 @@ type BidValidatorConfig struct { RedisURL string `koanf:"redis-url"` ProducerConfig pubsub.ProducerConfig `koanf:"producer-config"` // Timeout on polling for existence of each redis stream. - RpcEndpoint string `koanf:"rpc-endpoint"` - AuctionContractAddress string `koanf:"auction-contract-address"` - MaxBidsPerSender uint8 `koanf:"max-bids-per-sender"` + RpcEndpoint string `koanf:"rpc-endpoint"` + AuctionContractAddress string `koanf:"auction-contract-address"` + MaxBidsPerSender uint8 `koanf:"max-bids-per-sender"` + EnableEthcallValidation bool `koanf:"enable-ethcall-validation"` + AuctioneerAddress string `koanf:"auctioneer-address"` } var DefaultBidValidatorConfig = BidValidatorConfig{ - Enable: true, - RedisURL: "", - ProducerConfig: pubsub.DefaultProducerConfig, - MaxBidsPerSender: 5, + Enable: true, + RedisURL: "", + ProducerConfig: pubsub.DefaultProducerConfig, + MaxBidsPerSender: 5, + EnableEthcallValidation: true, } var TestBidValidatorConfig = BidValidatorConfig{ - Enable: true, - RedisURL: "", - ProducerConfig: pubsub.TestProducerConfig, - MaxBidsPerSender: 5, + Enable: true, + RedisURL: "", + ProducerConfig: pubsub.TestProducerConfig, + MaxBidsPerSender: 5, + EnableEthcallValidation: true, } func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { @@ -56,8 +62,10 @@ func BidValidatorConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".redis-url", DefaultBidValidatorConfig.RedisURL, "url of redis server") pubsub.ProducerAddConfigAddOptions(prefix+".producer-config", f) f.String(prefix+".rpc-endpoint", DefaultBidValidatorConfig.RpcEndpoint, "url of rpc endpoint") - f.String(prefix+".auction-contract-address", DefaultAuctioneerServerConfig.AuctionContractAddress, "express lane auction contract address") + f.String(prefix+".auction-contract-address", DefaultBidValidatorConfig.AuctionContractAddress, "express lane auction contract address") f.Uint8(prefix+".max-bids-per-sender", DefaultBidValidatorConfig.MaxBidsPerSender, "maximum number of bids a sender can submit per round") + f.Bool(prefix+".enable-ethcall-validation", DefaultBidValidatorConfig.EnableEthcallValidation, "enable eth_call validation of bids") + f.String(prefix+".auctioneer-address", DefaultBidValidatorConfig.AuctioneerAddress, "Address of the Timeboost Autonomous Auctioneer required for eth_call validation of bids") } @@ -80,6 +88,9 @@ type BidValidator struct { reservePrice *big.Int bidsPerSenderInRound map[common.Address]uint8 maxBidsPerSenderInRound uint8 + enableEthcallValidation bool + auctioneerAddr common.Address + auctionContractAbi *abi.ABI } func NewBidValidator( @@ -134,6 +145,19 @@ func NewBidValidator( return nil, err } + var auctioneerAddr common.Address + var auctionContractAbi *abi.ABI + if cfg.EnableEthcallValidation { + if cfg.AuctioneerAddress == "" { + return nil, fmt.Errorf("auctioneer address cannot be empty, used for eth_call validation of bids") + } + auctioneerAddr = common.HexToAddress(cfg.AuctioneerAddress) + auctionContractAbi, err = express_lane_auctiongen.ExpressLaneAuctionMetaData.GetAbi() + if err != nil { + return nil, errors.Wrap(err, "getting ExpressLaneAuctionABI") + } + } + bidValidator := &BidValidator{ chainId: chainId, client: rpcClient, @@ -149,6 +173,9 @@ func NewBidValidator( bidsPerSenderInRound: make(map[common.Address]uint8), maxBidsPerSenderInRound: cfg.MaxBidsPerSender, producerCfg: &cfg.ProducerConfig, + enableEthcallValidation: cfg.EnableEthcallValidation, + auctioneerAddr: auctioneerAddr, + auctionContractAbi: auctionContractAbi, } api := &BidValidatorAPI{bidValidator} valAPIs := []rpc.API{{ @@ -224,7 +251,7 @@ func (bv *BidValidator) Start(ctx_in context.Context) { } log.Info("Reserve price updated", "old", currentReservePrice.String(), "new", rp.String()) - bv.setReservePrice(rp) + bv.SetReservePrice(rp) case <-auctionCloseTicker.c: bv.Lock() @@ -265,7 +292,8 @@ func (bv *BidValidatorAPI) SubmitBid(ctx context.Context, bid *JsonBid) error { return nil } -func (bv *BidValidator) setReservePrice(p *big.Int) { +// SetReservePrice is exported for testing eth_call validation +func (bv *BidValidator) SetReservePrice(p *big.Int) { bv.reservePriceLock.Lock() defer bv.reservePriceLock.Unlock() bv.reservePrice = p @@ -278,7 +306,7 @@ func (bv *BidValidator) fetchReservePrice() *big.Int { } // Check time-related constraints for bid. -// It's useful to split out to be able to re-check just these contraints after +// It's useful to split out to be able to re-check just these constraints after // time has elapsed. func validateBidTimeConstraints(roundTimingInfo *RoundTimingInfo, bidRound uint64) error { // Check if the bid is intended for upcoming round. @@ -377,6 +405,33 @@ func (bv *BidValidator) validateBid( if depositBal.Cmp(bid.Amount) < 0 { return nil, errors.Wrapf(ErrInsufficientBalance, "bidder %s, onchain balance %#x, bid amount %#x", bidder.Hex(), depositBal, bid.Amount) } + + if bv.enableEthcallValidation { + var timeOverride map[string]interface{} + if !bv.roundTimingInfo.IsWithinAuctionCloseWindow(time.Now()) { + newTimestamp := bv.roundTimingInfo.TimeOfNextRound().Add(-1 * bv.roundTimingInfo.AuctionClosing) + timeOverride = map[string]interface{}{ + "time": fmt.Sprintf("0x%x", newTimestamp.Unix()), + } + } + calldata, err := bv.auctionContractAbi.Pack("resolveSingleBidAuction", bid) + if err != nil { + return nil, fmt.Errorf("error creating calldata for eth_call bid validation: %w", err) + } + params := []interface{}{ + map[string]interface{}{ + "from": bv.auctioneerAddr.Hex(), + "to": bv.auctionContractAddr.Hex(), + "data": "0x" + hex.EncodeToString(calldata), + }, nil, nil, timeOverride, + } + var result string + err = bv.client.Client().CallContext(bv.GetContext(), &result, "eth_call", params...) + if err != nil { + return nil, fmt.Errorf("error validating bid via eth_call auction resolution: %w", err) + } + } + vb := &ValidatedBid{ ExpressLaneController: bid.ExpressLaneController, Amount: bid.Amount, diff --git a/timeboost/bidder_client.go b/timeboost/bidder_client.go index 66c69991f4..28430c0295 100644 --- a/timeboost/bidder_client.go +++ b/timeboost/bidder_client.go @@ -154,7 +154,7 @@ func (bd *BidderClient) Deposit(ctx context.Context, amount *big.Int) error { if amount.Cmp(allowance) > 0 { log.Info("Spend allowance of bidding token from auction contract is insufficient, increasing allowance", "from", bd.txOpts.From, "auctionContract", bd.auctionContractAddress, "biddingToken", bd.biddingTokenAddress, "amount", amount.Int64()) - // defecit := arbmath.BigSub(allowance, amount) + // deficit := arbmath.BigSub(allowance, amount) tx, err := bd.biddingTokenContract.Approve(bd.txOpts, bd.auctionContractAddress, amount) if err != nil { return err diff --git a/timeboost/db.go b/timeboost/db.go index 6825843775..dcaf8426b6 100644 --- a/timeboost/db.go +++ b/timeboost/db.go @@ -162,14 +162,14 @@ func (d *SqliteDatabase) GetBids(maxDbRows int) ([]*SqliteDatabaseBid, uint64, e if err := d.sqlDB.Select(&sqlDBbids, "SELECT * FROM Bids WHERE Round < ? ORDER BY Round ASC LIMIT ?", maxRound, maxDbRows); err != nil { return nil, 0, err } - // We should return contiguous set of bids + // We should return a contiguous set of bids for i := len(sqlDBbids) - 1; i > 0; i-- { if sqlDBbids[i].Round != sqlDBbids[i-1].Round { return sqlDBbids[:i], sqlDBbids[i].Round, nil } } // If we can't determine a contiguous set of bids, we abort and retry again. - // Saves us from cases where we sometime push same batch data twice + // Saves us from cases where we sometimes push the same batch data twice return nil, 0, nil } diff --git a/timeboost/redis_coordinator.go b/timeboost/redis_coordinator.go index 09ae7406c6..e4991958cd 100644 --- a/timeboost/redis_coordinator.go +++ b/timeboost/redis_coordinator.go @@ -97,7 +97,7 @@ func (rc *RedisCoordinator) trackSequenceCountUpdates(ctx context.Context) { continue } roundSeqUpdate = update - // Attempt to pull upto next 5 updates from the channel (batching logic) + // Attempt to pull up to next 5 updates from the channel (batching logic) for i := 0; i < 5; i++ { select { case update := <-rc.roundSeqUpdateChan: diff --git a/timeboost/roundtiminginfo.go b/timeboost/roundtiminginfo.go index 3b8896ca67..70dc4acd36 100644 --- a/timeboost/roundtiminginfo.go +++ b/timeboost/roundtiminginfo.go @@ -46,7 +46,7 @@ func validateRoundTimingInfo(c *express_lane_auctiongen.RoundTimingInfo) error { // RoundTimingInfo holds the information from the Solidity type of the same name, // validated and converted into higher level time types, with helpful methods -// for calculating round number, if a round is closed, and time til close. +// for calculating round number, if a round is closed, and time till close. type RoundTimingInfo struct { Offset time.Time Round time.Duration diff --git a/timeboost/s3_storage.go b/timeboost/s3_storage.go index 950c1fbcfb..1adffde178 100644 --- a/timeboost/s3_storage.go +++ b/timeboost/s3_storage.go @@ -199,7 +199,7 @@ func (s *S3StorageService) uploadBatches(ctx context.Context) time.Duration { log.Error("error deleting s3-persisted bids from sql db", "round", deletRound, "err", err) s.lastFailedDeleteRound = deletRound } else { - // Previously failed deletes dont matter anymore as the recent one (larger round number) succeeded + // Previously failed deletes don't matter anymore as the recent one (larger round number) succeeded s.lastFailedDeleteRound = 0 } return nil diff --git a/timeboost/types.go b/timeboost/types.go index 73a6f35d52..cabdf94467 100644 --- a/timeboost/types.go +++ b/timeboost/types.go @@ -107,7 +107,7 @@ func (v *ValidatedBid) BigIntHash(domainSeparator [32]byte) *big.Int { Round: v.Round, Amount: v.Amount, } - // Since ToEIP712Hash is deterministic, this error can be ignored here, as the bidvalidator + // Since ToEIP712Hash is deterministic, this error can be ignored here, as the bid validator // would have previously validated it when calculating bidHash bidHash, _ := bid.ToEIP712Hash(domainSeparator) bidder := v.Bidder.Bytes() diff --git a/util/arbmath/bips.go b/util/arbmath/bips.go index 52ea2754b6..73d557b6b9 100644 --- a/util/arbmath/bips.go +++ b/util/arbmath/bips.go @@ -5,7 +5,11 @@ package arbmath import "math/big" +// Bips (basis points) are used to represent percentages as integers. type Bips int64 + +// UBips (unsigned basis points) are used to represent percentages as +// unsigned integers. type UBips uint64 const OneInBips Bips = 10000 diff --git a/util/arbmath/math.go b/util/arbmath/math.go index f7580e3b38..792c82d2cc 100644 --- a/util/arbmath/math.go +++ b/util/arbmath/math.go @@ -141,7 +141,7 @@ func BigToUintOrPanic(value *big.Int) uint64 { return value.Uint64() } -// UfracToBigFloat casts an rational to a big float +// UfracToBigFloat casts a rational to a big float func UfracToBigFloat(numerator, denominator uint64) *big.Float { float := new(big.Float) float.Quo(UintToBigFloat(numerator), UintToBigFloat(denominator)) @@ -242,7 +242,7 @@ func BigMulByInt(multiplicand *big.Int, multiplier int64) *big.Int { return new(big.Int).Mul(multiplicand, big.NewInt(multiplier)) } -// BigMulByUint multiply a huge by a unsigned integer +// BigMulByUint multiply a huge by an unsigned integer func BigMulByUint(multiplicand *big.Int, multiplier uint64) *big.Int { return new(big.Int).Mul(multiplicand, new(big.Int).SetUint64(multiplier)) } @@ -400,8 +400,12 @@ func DivCeil[T Unsigned](value, divisor T) T { return value/divisor + 1 } -// ApproxExpBasisPoints return the Maclaurin series approximation of e^x, where x is denominated in basis points. -// The quartic polynomial will underestimate e^x by about 5% as x approaches 20000 bips. +// ApproxExpBasisPoints return the Maclaurin series approximation of e^x, where +// x is denominated in basis points. +// The 4th degree Maclaurin series (for example) is: +// b*(1 + (x/b) +(x/b)^2 / 2! + (x/b)^3 / 6! + (x/b)^4/4!) +// The quartic polynomial (accuracy = 4) will underestimate e^x by about 5% as +// x approaches 20000 bips. func ApproxExpBasisPoints(value Bips, accuracy uint64) Bips { input := value negative := value < 0 @@ -411,15 +415,32 @@ func ApproxExpBasisPoints(value Bips, accuracy uint64) Bips { // This cast is safe because input is always positive // #nosec G115 x := uint64(input) - bips := uint64(OneInBips) - - res := bips + x/accuracy - for i := uint64(1); i < accuracy; i++ { - res = bips + SaturatingUMul(res, x)/((accuracy-i)*bips) + b := uint64(OneInBips) + + // This is actually an optimization for computing the Maclaurin series + // inspired by Horner's method for solving taylor polynomials. + // It is used to reduce the number of multiplications and divisions required + // to compute the polynomial series by one. + + // Despite the name, this method is not really an approximation of e^x + // It is an approximation of b*e^{x/b} Where b is 1 denominated in basis + // points. + // + // Horner's method tells us that this is equivalent to: + // b * (1 + x/b * (1 + x/(2*b) * (1 + x/(3*b)))) when accuracy = 4. + // + // And, if you multiply the b into the parenthesis, you get: + // b * (1 + x/b * (1 + x/(2*b) * (1 + x/(3*b)))) + // b + x * (b + x/(2*b) * (b + x/3))) + // On the inner-most term, the b cancels out on the top and bottom of the + // fraction, so we just use b + x/accuracy to initialize res. + res := b + x/accuracy + for i := accuracy - 1; i > 0; i-- { + res = b + SaturatingUMul(res, x)/(i*b) } if negative { - return Bips(SaturatingCast[int64](bips * bips / res)) + return Bips(SaturatingCast[int64](b * b / res)) } else { return Bips(SaturatingCast[int64](res)) } diff --git a/util/arbmath/math_test.go b/util/arbmath/math_test.go index 20e88ec361..5441b1de75 100644 --- a/util/arbmath/math_test.go +++ b/util/arbmath/math_test.go @@ -230,6 +230,29 @@ func TestSaturatingNeg(t *testing.T) { } } +func TestApproxExpBasisPoints(t *testing.T) { + tests := []struct { + input Bips + expected Bips + }{ + {0, 10000}, + {500, 10512}, + {-500, 9512}, + {2000, 12214}, + {10000, 27083}, // e^1 ≈ 2.7183 + {20000, 70000}, // e^2 ≈ 7.3890 + } + + for _, tc := range tests { + t.Run(fmt.Sprintf("ApproxExp(%d) = %d", tc.input, tc.expected), func(t *testing.T) { + result := ApproxExpBasisPoints(tc.input, 4) + if result != tc.expected { + t.Errorf("ApproxExpBasisPoints(%d) = %d: expected %d", tc.input, result, tc.expected) + } + }) + } +} + func Fail(t *testing.T, printables ...interface{}) { t.Helper() testhelpers.FailImpl(t, printables...) diff --git a/util/blobs/blobs.go b/util/blobs/blobs.go index e6fe1cb61f..74f8d7944b 100644 --- a/util/blobs/blobs.go +++ b/util/blobs/blobs.go @@ -111,7 +111,7 @@ func CommitmentToVersionedHash(commitment kzg4844.Commitment) common.Hash { return hash } -// Return KZG commitments, proofs, and versioned hashes that corresponds to these blobs +// Return KZG commitments, proofs, and versioned hashes that correspond to these blobs func ComputeCommitmentsAndHashes(blobs []kzg4844.Blob) ([]kzg4844.Commitment, []common.Hash, error) { commitments := make([]kzg4844.Commitment, len(blobs)) versionedHashes := make([]common.Hash, len(blobs)) diff --git a/util/colors/colors.go b/util/colors/colors.go index 06f52b708c..fe94b1871c 100644 --- a/util/colors/colors.go +++ b/util/colors/colors.go @@ -22,6 +22,12 @@ var Orange = "\033[38;5;202;1m" var Clear = "\033[0;0m" +// Pre-compiled regular expressions for better performance +var ( + uncolorRegex = regexp.MustCompile("\x1b\\[([0-9]+;)*[0-9]+m") + unwhiteRegex = regexp.MustCompile(`\s+`) +) + func PrintBlue(args ...interface{}) { print(Blue) fmt.Print(args...) @@ -59,9 +65,6 @@ func PrintPink(args ...interface{}) { } func Uncolor(text string) string { - uncolor := regexp.MustCompile("\x1b\\[([0-9]+;)*[0-9]+m") - unwhite := regexp.MustCompile(`\s+`) - - text = uncolor.ReplaceAllString(text, "") - return unwhite.ReplaceAllString(text, " ") + text = uncolorRegex.ReplaceAllString(text, "") + return unwhiteRegex.ReplaceAllString(text, " ") } diff --git a/util/containers/promise.go b/util/containers/promise.go index 54f0df195d..a66a82f960 100644 --- a/util/containers/promise.go +++ b/util/containers/promise.go @@ -98,7 +98,7 @@ func (p *Promise[R]) Produce(value R) { } } -// cancel might be called multiple times while no value or error produced +// cancel might be called multiple times while no value or error is produced // cancel will be called by Await if it's context is done func NewPromise[R any](cancel func()) Promise[R] { return Promise[R]{ diff --git a/util/dbutil/dbutil.go b/util/dbutil/dbutil.go index 4f58df357c..45cfc1b091 100644 --- a/util/dbutil/dbutil.go +++ b/util/dbutil/dbutil.go @@ -9,17 +9,9 @@ import ( "io/fs" "regexp" - "github.com/cockroachdb/pebble" - "github.com/syndtr/goleveldb/leveldb" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/ethdb/memorydb" ) -func IsErrNotFound(err error) bool { - return errors.Is(err, leveldb.ErrNotFound) || errors.Is(err, pebble.ErrNotFound) || errors.Is(err, memorydb.ErrMemorydbNotFound) -} - var pebbleNotExistErrorRegex = regexp.MustCompile("pebble: database .* does not exist") func isPebbleNotExistError(err error) bool { diff --git a/util/dbutil/dbutil_test.go b/util/dbutil/dbutil_test.go index b303bb56b6..24544d0d66 100644 --- a/util/dbutil/dbutil_test.go +++ b/util/dbutil/dbutil_test.go @@ -13,7 +13,7 @@ func testIsNotExistError(t *testing.T, dbEngine string, isNotExist func(error) b stackConf.DBEngine = dbEngine stack, err := node.New(&stackConf) if err != nil { - t.Fatalf("Failed to created test stack: %v", err) + t.Fatalf("Failed to create test stack: %v", err) } defer stack.Close() readonly := true diff --git a/util/headerreader/blob_client.go b/util/headerreader/blob_client.go index 46d3f75560..cc5e124559 100644 --- a/util/headerreader/blob_client.go +++ b/util/headerreader/blob_client.go @@ -35,6 +35,7 @@ type BlobClient struct { secondaryBeaconUrl *url.URL httpClient atomic.Pointer[http.Client] authorization string + useLegacyEndpoint bool // Filled in in Initialize() genesisTime uint64 @@ -42,13 +43,26 @@ type BlobClient struct { // Directory to save the fetched blobs blobDirectory string + + // Dangerous options + skipBlobProofVerification bool +} + +type BlobClientDangerousConfig struct { + SkipBlobProofVerification bool `koanf:"skip-blob-proof-verification"` } type BlobClientConfig struct { - BeaconUrl string `koanf:"beacon-url"` - SecondaryBeaconUrl string `koanf:"secondary-beacon-url"` - BlobDirectory string `koanf:"blob-directory"` - Authorization string `koanf:"authorization"` + BeaconUrl string `koanf:"beacon-url"` + SecondaryBeaconUrl string `koanf:"secondary-beacon-url"` + BlobDirectory string `koanf:"blob-directory"` + Authorization string `koanf:"authorization"` + UseLegacyEndpoint bool `koanf:"use-legacy-endpoint"` + Dangerous BlobClientDangerousConfig `koanf:"dangerous"` +} + +var DefaultDangerousConfig = BlobClientDangerousConfig{ + SkipBlobProofVerification: false, } var DefaultBlobClientConfig = BlobClientConfig{ @@ -56,6 +70,8 @@ var DefaultBlobClientConfig = BlobClientConfig{ SecondaryBeaconUrl: "", BlobDirectory: "", Authorization: "", + UseLegacyEndpoint: false, + Dangerous: DefaultDangerousConfig, } func BlobClientAddOptions(prefix string, f *pflag.FlagSet) { @@ -63,6 +79,12 @@ func BlobClientAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".secondary-beacon-url", DefaultBlobClientConfig.SecondaryBeaconUrl, "Backup beacon Chain RPC URL to use for fetching blobs (normally on port 3500) when unable to fetch from primary") f.String(prefix+".blob-directory", DefaultBlobClientConfig.BlobDirectory, "Full path of the directory to save fetched blobs") f.String(prefix+".authorization", DefaultBlobClientConfig.Authorization, "Value to send with the HTTP Authorization: header for Beacon REST requests, must include both scheme and scheme parameters") + f.Bool(prefix+".use-legacy-endpoint", DefaultBlobClientConfig.UseLegacyEndpoint, "Use the legacy blob_sidecars endpoint instead of the blobs endpoint") + BlobClientDangerousAddOptions(prefix+".dangerous", f) +} + +func BlobClientDangerousAddOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".skip-blob-proof-verification", DefaultDangerousConfig.SkipBlobProofVerification, "DANGEROUS! Skips verification of KZG proofs for blobs fetched from the beacon node.") } func NewBlobClient(config BlobClientConfig, ec *ethclient.Client) (*BlobClient, error) { @@ -88,11 +110,13 @@ func NewBlobClient(config BlobClientConfig, ec *ethclient.Client) (*BlobClient, } } blobClient := &BlobClient{ - ec: ec, - beaconUrl: beaconUrl, - secondaryBeaconUrl: secondaryBeaconUrl, - authorization: config.Authorization, - blobDirectory: config.BlobDirectory, + ec: ec, + beaconUrl: beaconUrl, + secondaryBeaconUrl: secondaryBeaconUrl, + authorization: config.Authorization, + useLegacyEndpoint: config.UseLegacyEndpoint, + blobDirectory: config.BlobDirectory, + skipBlobProofVerification: config.Dangerous.SkipBlobProofVerification, } blobClient.httpClient.Store(&http.Client{}) return blobClient, nil @@ -102,14 +126,15 @@ type fullResult[T any] struct { Data T `json:"data"` } -func beaconRequest[T interface{}](b *BlobClient, ctx context.Context, beaconPath string) (T, error) { - // Unfortunately, methods on a struct can't be generic. - +func beaconRequest[T interface{}](b *BlobClient, ctx context.Context, beaconPath string, queryParams url.Values) (T, error) { var empty T - fetchData := func(url url.URL) (*http.Response, error) { - url.Path = path.Join(url.Path, beaconPath) - req, err := http.NewRequestWithContext(ctx, "GET", url.String(), http.NoBody) + fetchData := func(beaconUrl url.URL) (*http.Response, error) { + beaconUrl.Path = path.Join(beaconUrl.Path, beaconPath) + if queryParams != nil { + beaconUrl.RawQuery = queryParams.Encode() + } + req, err := http.NewRequestWithContext(ctx, "GET", beaconUrl.String(), http.NoBody) if err != nil { return nil, err } @@ -170,7 +195,23 @@ func (b *BlobClient) GetBlobs(ctx context.Context, blockHash common.Hash, versio return nil, errors.New("BlobClient hasn't been initialized") } slot := (header.Time - b.genesisTime) / b.secondsPerSlot - blobs, err := b.blobSidecars(ctx, slot, versionedHashes) + + return b.GetBlobsBySlot(ctx, slot, versionedHashes) +} + +// Get blobs for a specific beacon chain slot. +func (b *BlobClient) GetBlobsBySlot(ctx context.Context, slot uint64, versionedHashes []common.Hash) ([]kzg4844.Blob, error) { + if b.secondsPerSlot == 0 { + return nil, errors.New("BlobClient hasn't been initialized") + } + + var blobs []kzg4844.Blob + var err error + if b.useLegacyEndpoint { + blobs, err = b.blobSidecars(ctx, slot, versionedHashes) + } else { + blobs, err = b.getBlobs(ctx, slot, versionedHashes) + } if err != nil { // Creates a new http client to avoid reusing the same transport layer connection in the next request. // This strategy can be useful if there is a network load balancer in front of the beacon chain server. @@ -178,11 +219,56 @@ func (b *BlobClient) GetBlobs(ctx context.Context, blockHash common.Hash, versio // we can potentially connect to a different, and healthy, beacon chain node in the next request. b.httpClient.Store(&http.Client{}) - return nil, fmt.Errorf("error fetching blobs in %d l1 block: %w", header.Number, err) + b.useLegacyEndpoint = !b.useLegacyEndpoint + + return nil, fmt.Errorf("error fetching blobs for slot %d: %w", slot, err) } return blobs, nil } +func (b *BlobClient) getBlobs(ctx context.Context, slot uint64, versionedHashes []common.Hash) ([]kzg4844.Blob, error) { + queryParams := url.Values{} + for _, hash := range versionedHashes { + queryParams.Add("versioned_hashes", hash.Hex()) + } + + response, err := beaconRequest[[]hexutil.Bytes](b, ctx, fmt.Sprintf("/eth/v1/beacon/blobs/%d", slot), queryParams) + if err != nil { + // #nosec G115 + roughAgeOfSlot := uint64(time.Now().Unix()) - (b.genesisTime + slot*b.secondsPerSlot) + if roughAgeOfSlot > b.secondsPerSlot*32*4096 { + return nil, fmt.Errorf("beacon client in getBlobs got error fetching older blobs in slot: %d, an archive endpoint is required, please refer to https://docs.arbitrum.io/run-arbitrum-node/l1-ethereum-beacon-chain-rpc-providers, err: %w", slot, err) + } else { + return nil, fmt.Errorf("beacon client in getBlobs got error fetching non-expired blobs in slot: %d, err: %w", slot, err) + } + } + + if len(versionedHashes) > 0 && len(response) != len(versionedHashes) { + return nil, fmt.Errorf("expected %d blobs for slot %d but got %d", len(versionedHashes), slot, len(response)) + } + + output := make([]kzg4844.Blob, len(response)) + for i, blobData := range response { + if len(blobData) != len(output[i]) { + return nil, fmt.Errorf("blob at index %d has incorrect length %d, expected %d", i, len(blobData), len(output[i])) + } + copy(output[i][:], blobData) + + if len(versionedHashes) > 0 { + commitment, err := kzg4844.BlobToCommitment(&output[i]) + if err != nil { + return nil, fmt.Errorf("failed to compute commitment for blob %d: %w", i, err) + } + computedHash := blobs.CommitmentToVersionedHash(commitment) + if computedHash != versionedHashes[i] { + return nil, fmt.Errorf("blob %d versioned hash mismatch: expected %s, got %s", i, versionedHashes[i].Hex(), computedHash.Hex()) + } + } + } + + return output, nil +} + type blobResponseItem struct { BlockRoot string `json:"block_root"` Index jsonapi.Uint64String `json:"index"` @@ -197,15 +283,15 @@ type blobResponseItem struct { const trailingCharsOfResponse = 25 func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHashes []common.Hash) ([]kzg4844.Blob, error) { - rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot)) + rawData, err := beaconRequest[json.RawMessage](b, ctx, fmt.Sprintf("/eth/v1/beacon/blob_sidecars/%d", slot), nil) if err != nil || len(rawData) == 0 { - // blobs are pruned after 4096 epochs (1 epoch = 32 slots), we determine if the requested slot were to be pruned by a non-archive endpoint + // blobs are pruned after 4096 epochs (1 epoch = 32 slots), we determine if the requested slot was to be pruned by a non-archive endpoint // #nosec G115 roughAgeOfSlot := uint64(time.Now().Unix()) - (b.genesisTime + slot*b.secondsPerSlot) if roughAgeOfSlot > b.secondsPerSlot*32*4096 { return nil, fmt.Errorf("beacon client in blobSidecars got error or empty response fetching older blobs in slot: %d, an archive endpoint is required, please refer to https://docs.arbitrum.io/run-arbitrum-node/l1-ethereum-beacon-chain-rpc-providers, err: %w", slot, err) } else { - return nil, fmt.Errorf("beacon client in blobSidecars got error or empty response fetching non-expired blobs in slot: %d, if using a prysm endpoint, try --enable-experimental-backfill flag, err: %w", slot, err) + return nil, fmt.Errorf("beacon client in blobSidecars got error or empty response fetching non-expired blobs in slot: %d, if using a Prysm endpoint, try --enable-experimental-backfill flag, err: %w", slot, err) } } var response []blobResponseItem @@ -254,12 +340,14 @@ func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHas copy(output[outputIdx][:], blobItem.Blob) - var proof kzg4844.Proof - copy(proof[:], blobItem.KzgProof) + if !b.skipBlobProofVerification { + var proof kzg4844.Proof + copy(proof[:], blobItem.KzgProof) - err = kzg4844.VerifyBlobProof(&output[outputIdx], commitment, proof) - if err != nil { - return nil, fmt.Errorf("failed to verify blob proof for blob at slot(%d) at index(%d), blob(%s)", slot, blobItem.Index, pretty.FirstFewChars(blobItem.Blob.String())) + err = kzg4844.VerifyBlobProof(&output[outputIdx], commitment, proof) + if err != nil { + return nil, fmt.Errorf("failed to verify blob proof for blob at slot(%d) at index(%d), blob(%s)", slot, blobItem.Index, pretty.FirstFewChars(blobItem.Blob.String())) + } } } @@ -306,13 +394,13 @@ type getSpecResponse struct { } func (b *BlobClient) Initialize(ctx context.Context) error { - genesis, err := beaconRequest[genesisResponse](b, ctx, "/eth/v1/beacon/genesis") + genesis, err := beaconRequest[genesisResponse](b, ctx, "/eth/v1/beacon/genesis", nil) if err != nil { return fmt.Errorf("error calling beacon client to get genesisTime: %w", err) } b.genesisTime = uint64(genesis.GenesisTime) - spec, err := beaconRequest[getSpecResponse](b, ctx, "/eth/v1/config/spec") + spec, err := beaconRequest[getSpecResponse](b, ctx, "/eth/v1/config/spec", nil) if err != nil { return fmt.Errorf("error calling beacon client to get secondsPerSlot: %w", err) } diff --git a/util/headerreader/header_reader.go b/util/headerreader/header_reader.go index c2b24c4f1e..bb3e9dbc44 100644 --- a/util/headerreader/header_reader.go +++ b/util/headerreader/header_reader.go @@ -166,7 +166,7 @@ func (s *HeaderReader) Config() *Config { return s.config() } // Subscribe to block header updates. // Subscribers are notified when there is a change. // Channel could be missing headers and have duplicates. -// Listening to the channel will make sure listenere is notified when header changes. +// Listening to the channel will make sure listener is notified when header changes. // Warning: listeners must not modify the header or its number, as they're shared between listeners. func (s *HeaderReader) Subscribe(requireBlockNrUpdates bool) (<-chan *types.Header, func()) { s.chanMutex.Lock() diff --git a/util/iostat/iostat.go b/util/iostat/iostat.go index e3030039df..fe5db9e3a4 100644 --- a/util/iostat/iostat.go +++ b/util/iostat/iostat.go @@ -28,7 +28,7 @@ func RegisterAndPopulateMetrics(ctx context.Context, spawnInterval, maxDeviceCou return } if _, ok := deviceMetrics[stat.DeviceName]; !ok { - // Register metrics for a maximum of maxDeviceCount (fail safe incase iostat command returns incorrect names indefinitely) + // Register metrics for a maximum of maxDeviceCount (fail safe in case iostat command returns incorrect names indefinitely) if len(deviceMetrics) < maxDeviceCount { // Replace hyphens with underscores to avoid metric name issues sanitizedDeviceName := strings.ReplaceAll(stat.DeviceName, "-", "_") diff --git a/util/jsonapi/preimages_test.go b/util/jsonapi/preimages_test.go index 7716d1ccd4..dca095aab3 100644 --- a/util/jsonapi/preimages_test.go +++ b/util/jsonapi/preimages_test.go @@ -36,7 +36,7 @@ func TestPreimagesMapJson(t *testing.T) { t.Run(fmt.Sprintf("%v preimages", len(preimages.Map)), func(t *testing.T) { // These test cases are fast enough that t.Parallel() probably isn't worth it serialized, err := preimages.MarshalJSON() - Require(t, err, "Failed to marshal preimagesj") + Require(t, err, "Failed to marshal preimages") // Make sure that `serialized` is a valid JSON map stringMap := make(map[string]string) diff --git a/util/jsonapi/uint64_string.go b/util/jsonapi/uint64_string.go index 8114382fbf..ed561a8be9 100644 --- a/util/jsonapi/uint64_string.go +++ b/util/jsonapi/uint64_string.go @@ -9,7 +9,7 @@ import ( "strconv" ) -// Uint64String is a uint64 that JSON marshals and unmarshals as string in decimal +// Uint64String is a uint64 that JSON marshals and unmarshals as a string in decimal type Uint64String uint64 func (u *Uint64String) UnmarshalJSON(b []byte) error { diff --git a/util/log.go b/util/log.go index 25ef90c7f8..60880b7e33 100644 --- a/util/log.go +++ b/util/log.go @@ -1,13 +1,14 @@ package util import ( + "reflect" "strings" "time" "github.com/ethereum/go-ethereum/log" ) -// EphemeralErrorHandler handles errors that are ephemeral in nature i.h these are errors +// EphemeralErrorHandler handles errors that are ephemeral in nature i.e. these are errors // that we would like to log as a warning unless they repeat for more than a certain duration of time. type EphemeralErrorHandler struct { Duration time.Duration @@ -28,7 +29,7 @@ func NewEphemeralErrorHandler(duration time.Duration, errorString string, ignore } } -// LogLevel method defaults to returning the input currentLogLevel if the given error doesnt contain the errorSubstring, +// LogLevel method defaults to returning the input currentLogLevel if the given error doesn't contain the errorSubstring, // but if it does, then returns one of the corresponding loglevels as follows // - IgnoredErrLogLevel - if the error has been repeating for less than the IgnoreDuration of time. Defaults to log.Debug // - log.Warn - if the error has been repeating for less than the given duration of time @@ -65,3 +66,8 @@ func (h *EphemeralErrorHandler) LogLevel(err error, currentLogLevel func(msg str func (h *EphemeralErrorHandler) Reset() { *h.FirstOccurrence = time.Time{} } + +// CompareLogLevels returns true if the logging functions provided are the same +func CompareLogLevels(f1, f2 func(msg string, ctx ...interface{})) bool { + return reflect.ValueOf(f1).Pointer() == reflect.ValueOf(f2).Pointer() +} diff --git a/util/log_test.go b/util/log_test.go index f8007373f2..facb222bd4 100644 --- a/util/log_test.go +++ b/util/log_test.go @@ -2,33 +2,29 @@ package util import ( "errors" - "reflect" "testing" "time" "github.com/ethereum/go-ethereum/log" ) -func compareFunctions(f1, f2 func(msg string, ctx ...interface{})) bool { - return reflect.ValueOf(f1).Pointer() == reflect.ValueOf(f2).Pointer() -} func TestSimple(t *testing.T) { allErrHandler := NewEphemeralErrorHandler(2500*time.Millisecond, "", time.Second) err := errors.New("sample error") logLevel := allErrHandler.LogLevel(err, log.Error) - if !compareFunctions(log.Debug, logLevel) { + if !CompareLogLevels(log.Debug, logLevel) { t.Fatalf("incorrect loglevel output. Want: Debug") } time.Sleep(1 * time.Second) logLevel = allErrHandler.LogLevel(err, log.Error) - if !compareFunctions(log.Warn, logLevel) { + if !CompareLogLevels(log.Warn, logLevel) { t.Fatalf("incorrect loglevel output. Want: Warn") } time.Sleep(2 * time.Second) logLevel = allErrHandler.LogLevel(err, log.Error) - if !compareFunctions(log.Error, logLevel) { + if !CompareLogLevels(log.Error, logLevel) { t.Fatalf("incorrect loglevel output. Want: Error") } } @@ -47,24 +43,24 @@ func TestComplex(t *testing.T) { } errA := errors.New("this is a sample errorA") - if !compareFunctions(log.Warn, chainingErrHandlers(errA)) { + if !CompareLogLevels(log.Warn, chainingErrHandlers(errA)) { t.Fatalf("incorrect loglevel output. Want: Warn") } time.Sleep(2 * time.Second) - if !compareFunctions(log.Error, chainingErrHandlers(errA)) { + if !CompareLogLevels(log.Error, chainingErrHandlers(errA)) { t.Fatalf("incorrect loglevel output. Want: Error") } errB := errors.New("this is a sample errorB") - if !compareFunctions(log.Warn, chainingErrHandlers(errB)) { + if !CompareLogLevels(log.Warn, chainingErrHandlers(errB)) { t.Fatalf("incorrect loglevel output. Want: Warn") } - if !compareFunctions(log.Warn, chainingErrHandlers(errA)) { + if !CompareLogLevels(log.Warn, chainingErrHandlers(errA)) { t.Fatalf("incorrect loglevel output. Want: Warn") } errC := errors.New("random error") - if !compareFunctions(log.Error, chainingErrHandlers(errC)) { + if !CompareLogLevels(log.Error, chainingErrHandlers(errC)) { t.Fatalf("incorrect loglevel output. Want: Error") } } diff --git a/util/redisutil/redis_coordinator.go b/util/redisutil/redis_coordinator.go index 1670ac716a..6b25d22de9 100644 --- a/util/redisutil/redis_coordinator.go +++ b/util/redisutil/redis_coordinator.go @@ -31,8 +31,8 @@ const INVALID_URL string = "" type RedisCoordinator struct { Client redis.UniversalClient - firstSequencerWantingLockoutErrorTime time.Time // Time of the first error logged for no sequencer wanting the lockout. - lastLockoutErrorLogTime time.Time // Add this field to track when we last logged lockout errors. + firstSequencerWantingLockoutErrorTime atomic.Int64 // Time of the first error logged for no sequencer wanting the lockout. + lastLockoutErrorLogTime atomic.Int64 // Add this field to track when we last logged lockout errors. // If Client is a sentinel client, sentinelMaster string // The master name of the sentinel client. @@ -74,8 +74,8 @@ func (c *RedisCoordinator) RecommendSequencerWantingLockout(ctx context.Context) } // We found a sequencer that wants the lockout, so we reset the last time we observed the error // to a value of zero for logging purposes below. - c.firstSequencerWantingLockoutErrorTime = time.Time{} - c.lastLockoutErrorLogTime = time.Time{} // Reset log throttling timer when state changes. + c.firstSequencerWantingLockoutErrorTime.Store(0) + c.lastLockoutErrorLogTime.Store(0) // Reset log throttling timer when state changes. return url, nil } @@ -88,15 +88,16 @@ func (c *RedisCoordinator) RecommendSequencerWantingLockout(ctx context.Context) level("no sequencer appears to want the lockout on redis", args...) } - if c.firstSequencerWantingLockoutErrorTime.IsZero() { - c.firstSequencerWantingLockoutErrorTime = time.Now() - c.lastLockoutErrorLogTime = time.Now() + if c.firstSequencerWantingLockoutErrorTime.Load() == 0 { + now := time.Now().UnixMilli() + c.firstSequencerWantingLockoutErrorTime.Store(now) + c.lastLockoutErrorLogTime.Store(now) logMessage(log.Debug) } else { - elapsedTime := time.Since(c.firstSequencerWantingLockoutErrorTime) + elapsedTime := time.Since(time.UnixMilli(c.firstSequencerWantingLockoutErrorTime.Load())) // Only log if it's been at least 5 seconds since the last log, // as these logs would otherwise be spammed at a high rate when they occur. - if time.Since(c.lastLockoutErrorLogTime) >= 5*time.Second { + if time.Since(time.UnixMilli(c.lastLockoutErrorLogTime.Load())) >= 5*time.Second { if elapsedTime > 20*time.Second { logMessage(log.Error) } else if elapsedTime > 10*time.Second { @@ -104,7 +105,7 @@ func (c *RedisCoordinator) RecommendSequencerWantingLockout(ctx context.Context) } else { logMessage(log.Debug) } - c.lastLockoutErrorLogTime = time.Now() // Update last log time. + c.lastLockoutErrorLogTime.Store(time.Now().UnixMilli()) // Update last log time. } } return "", nil @@ -138,9 +139,11 @@ func (rc *RedisCoordinator) GetPriorities(ctx context.Context) ([]string, error) // GetLiveliness returns a list of sequencers that have their liveliness set to OK func (rc *RedisCoordinator) GetLiveliness(ctx context.Context) ([]string, error) { var livelinessList []string - cursor := uint64(0) + var cursor uint64 for { - keySlice, cursor, err := rc.Client.Scan(ctx, cursor, WANTS_LOCKOUT_KEY_PREFIX+"*", 0).Result() + var keySlice []string + var err error + keySlice, cursor, err = rc.Client.Scan(ctx, cursor, WANTS_LOCKOUT_KEY_PREFIX+"*", 0).Result() if err != nil { return []string{}, err } diff --git a/util/redisutil/redis_coordinator_test.go b/util/redisutil/redis_coordinator_test.go new file mode 100644 index 0000000000..3b8ec10daf --- /dev/null +++ b/util/redisutil/redis_coordinator_test.go @@ -0,0 +1,41 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package redisutil + +import ( + "context" + "sort" + "testing" + "time" +) + +func TestRedisCoordinatorGetLiveliness(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + redisUrl := CreateTestRedis(ctx, t) + redisCoordinator, err := NewRedisCoordinator(redisUrl, 0) + if err != nil { + t.Fatalf("error creating redis coordinator: %s", err.Error()) + } + wantLivelinessList := []string{"a", "b", "c", "d", "e", "f"} + for _, url := range wantLivelinessList { + if _, err = redisCoordinator.Client.Set(ctx, WantsLockoutKeyFor(url), WANTS_LOCKOUT_VAL, time.Minute).Result(); err != nil { + t.Fatalf("error setting liveliness key for: %s err: %s", url, err.Error()) + } + } + haveLivelinessList, err := redisCoordinator.GetLiveliness(ctx) + if err != nil { + t.Fatalf("error getting liveliness list: %s", err.Error()) + } + sort.Strings(haveLivelinessList) + if len(wantLivelinessList) != len(haveLivelinessList) { + t.Fatalf("liveliness list length mismatch") + } + for i, want := range wantLivelinessList { + if haveLivelinessList[i] != want { + t.Fatalf("liveliness list url mismatch. want: %s have: %s", want, haveLivelinessList[i]) + } + } +} diff --git a/util/redisutil/redisutil.go b/util/redisutil/redisutil.go index 27f0d2c5e6..7e733f4027 100644 --- a/util/redisutil/redisutil.go +++ b/util/redisutil/redisutil.go @@ -44,7 +44,7 @@ func RedisClientWithSentinelMasterNameFromURL(redisUrl string) (redis.UniversalC } // Designed using https://github.com/redis/go-redis/blob/a8590e987945b7ba050569cc3b94b8ece49e99e3/options.go#L283 as reference -// Example Usage : +// Example Usage: // // redis+sentinel://:@:,:,:/?dial_timeout=3&db=1&read_timeout=6s&max_retries=2 func parseFailoverRedisUrl(redisUrl string) (*redis.FailoverOptions, error) { diff --git a/util/runtime.go b/util/runtime.go index 3b9424d89a..38b1e0dda0 100644 --- a/util/runtime.go +++ b/util/runtime.go @@ -3,11 +3,16 @@ package util import ( "runtime" - _ "go.uber.org/automaxprocs" + "go.uber.org/automaxprocs/maxprocs" ) -// Automaxprocs automatically set GOMAXPROCS to match Linux container CPU quota. -// So we are wrapping it here to make sure we do not call it anywhere else without importing automaxprocs. +func init() { + // Disable maxprocs logs + _, _ = maxprocs.Set() +} + +// GoMaxProcs wraps runtime.GOMAXPROCS here to ensure that maxprocs.Set() +// is always called first. func GoMaxProcs() int { return runtime.GOMAXPROCS(-1) } diff --git a/util/testhelpers/flag/flag.go b/util/testhelpers/flag/flag.go index 547ef6eaa4..8f526c5d4b 100644 --- a/util/testhelpers/flag/flag.go +++ b/util/testhelpers/flag/flag.go @@ -24,7 +24,7 @@ var ( // This is a workaround for the fact that we can only pass flags to the package in which they are defined. // So to avoid doing that we pass the flags after adding a delimiter "--" to the command line. -// We then parse the argument only after the delimiter to the flagset. +// We then parse the arguments only after the delimiter to the flagset. func init() { var args []string foundDelimiter := false diff --git a/util/testhelpers/github/releases.go b/util/testhelpers/github/releases.go index 5555c90aa1..e6546504b6 100644 --- a/util/testhelpers/github/releases.go +++ b/util/testhelpers/github/releases.go @@ -33,7 +33,7 @@ func getAuthGitClient(ctx context.Context) *github.Client { func NitroReleases(ctx context.Context) ([]*github.RepositoryRelease, error) { client := getAuthGitClient(ctx) opts := &github.ListOptions{ - PerPage: 50, + PerPage: 100, } releases, _, err := client.Repositories.ListReleases(ctx, "OffchainLabs", "nitro", opts) return releases, err diff --git a/util/testhelpers/github/releases_test.go b/util/testhelpers/github/releases_test.go index a25d68c543..947c8b675f 100644 --- a/util/testhelpers/github/releases_test.go +++ b/util/testhelpers/github/releases_test.go @@ -13,8 +13,8 @@ func TestReleases(t *testing.T) { if len(rels) == 0 { t.Error("No releases found") } - if len(rels) != 50 { - t.Errorf("Expected 50 releases, got %d", len(rels)) + if len(rels) != 100 { + t.Errorf("Expected 100 releases, got %d", len(rels)) } } diff --git a/util/testhelpers/port.go b/util/testhelpers/port.go index c17e9d9ec2..d283a19143 100644 --- a/util/testhelpers/port.go +++ b/util/testhelpers/port.go @@ -22,7 +22,7 @@ func AddrTCPPort(n net.Addr, t *testing.T) int { t.Helper() tcpAddr, ok := n.(*net.TCPAddr) if !ok { - t.Fatal("Could not get TCP address net.Addr") + t.Fatal("Could not get TCP address from net.Addr") } return tcpAddr.Port } diff --git a/util/testhelpers/stackconfig.go b/util/testhelpers/stackconfig.go index 5dc8fe2bdb..baebae302c 100644 --- a/util/testhelpers/stackconfig.go +++ b/util/testhelpers/stackconfig.go @@ -3,10 +3,17 @@ package testhelpers -import "github.com/ethereum/go-ethereum/node" +import ( + "github.com/ethereum/go-ethereum/node" +) func CreateStackConfigForTest(dataDir string) *node.Config { stackConf := node.DefaultConfig + // stackConf.Name is used when creating data path used by the node + // if stackConf is not set, program binary name is used instead + // We hardcode it to enable running the tests that need to know the path also when test binary name is different than default, + // eg. when debugging with dlv test the debug binary name differs from normal test build + stackConf.Name = "test-stack-name" stackConf.DataDir = dataDir stackConf.UseLightweightKDF = true stackConf.WSPort = 0 diff --git a/validator/client/redis/producer.go b/validator/client/redis/producer.go index 4bfb721f59..5fd258b397 100644 --- a/validator/client/redis/producer.go +++ b/validator/client/redis/producer.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/pubsub" @@ -38,7 +37,7 @@ func (c ValidationClientConfig) Enabled() bool { func (c ValidationClientConfig) Validate() error { for _, arch := range c.StylusArchs { - if !rawdb.IsSupportedWasmTarget(ethdb.WasmTarget(arch)) { + if !rawdb.IsSupportedWasmTarget(rawdb.WasmTarget(arch)) { return fmt.Errorf("Invalid stylus arch: %v", arch) } } @@ -110,7 +109,7 @@ func (c *ValidationClient) Initialize(ctx context.Context, moduleRoots []common. } } if _, exists := c.producers[mr]; exists { - log.Warn("Producer already existsw for module root", "hash", mr) + log.Warn("Producer already exists for module root", "hash", mr) continue } p, err := pubsub.NewProducer[*validator.ValidationInput, validator.GoGlobalState]( @@ -165,10 +164,10 @@ func (c *ValidationClient) Name() string { return c.config.Name } -func (c *ValidationClient) StylusArchs() []ethdb.WasmTarget { - stylusArchs := make([]ethdb.WasmTarget, 0, len(c.config.StylusArchs)) +func (c *ValidationClient) StylusArchs() []rawdb.WasmTarget { + stylusArchs := make([]rawdb.WasmTarget, 0, len(c.config.StylusArchs)) for _, arch := range c.config.StylusArchs { - stylusArchs = append(stylusArchs, ethdb.WasmTarget(arch)) + stylusArchs = append(stylusArchs, rawdb.WasmTarget(arch)) } return stylusArchs } diff --git a/validator/client/validation_client.go b/validator/client/validation_client.go index bf2c20e851..22c165ded8 100644 --- a/validator/client/validation_client.go +++ b/validator/client/validation_client.go @@ -13,11 +13,9 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" - "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/rpcclient" @@ -33,7 +31,7 @@ type ValidationClient struct { stopwaiter.StopWaiter client *rpcclient.RpcClient name string - stylusArchs []ethdb.WasmTarget + stylusArchs []rawdb.WasmTarget room atomic.Int32 wasmModuleRoots []common.Hash } @@ -42,7 +40,7 @@ func NewValidationClient(config rpcclient.ClientConfigFetcher, stack *node.Node) return &ValidationClient{ client: rpcclient.NewRpcClient(config, stack), name: "not started", - stylusArchs: []ethdb.WasmTarget{"not started"}, + stylusArchs: []rawdb.WasmTarget{"not started"}, } } @@ -69,20 +67,15 @@ func (c *ValidationClient) Start(ctx context.Context) error { if len(name) == 0 { return errors.New("couldn't read name from server") } - var stylusArchs []ethdb.WasmTarget + var stylusArchs []rawdb.WasmTarget if err := c.client.CallContext(ctx, &stylusArchs, server_api.Namespace+"_stylusArchs"); err != nil { - var rpcError rpc.Error - ok := errors.As(err, &rpcError) - if !ok || rpcError.ErrorCode() != -32601 { - return fmt.Errorf("could not read stylus arch from server: %w", err) - } - stylusArchs = []ethdb.WasmTarget{ethdb.WasmTarget("pre-stylus")} // invalid, will fail if trying to validate block with stylus + return fmt.Errorf("could not read stylus arch from server: %w", err) } else { if len(stylusArchs) == 0 { return fmt.Errorf("could not read stylus archs from validation server") } for _, stylusArch := range stylusArchs { - if !rawdb.IsSupportedWasmTarget(ethdb.WasmTarget(stylusArch)) && stylusArch != "mock" { + if !rawdb.IsSupportedWasmTarget(rawdb.WasmTarget(stylusArch)) && stylusArch != "mock" { return fmt.Errorf("unsupported stylus architecture: %v", stylusArch) } } @@ -120,11 +113,11 @@ func (c *ValidationClient) WasmModuleRoots() ([]common.Hash, error) { return nil, errors.New("not started") } -func (c *ValidationClient) StylusArchs() []ethdb.WasmTarget { +func (c *ValidationClient) StylusArchs() []rawdb.WasmTarget { if c.Started() { return c.stylusArchs } - return []ethdb.WasmTarget{"not started"} + return []rawdb.WasmTarget{"not started"} } func (c *ValidationClient) Stop() { diff --git a/validator/inputs/writer.go b/validator/inputs/writer.go index 1a476c52a3..2429c7af95 100644 --- a/validator/inputs/writer.go +++ b/validator/inputs/writer.go @@ -13,26 +13,26 @@ import ( // // The default Writer will write to a path like: // -// $HOME/.arbuitrum/validation-inputs//block_inputs_.json +// $HOME/.arbitrum/validation-inputs//block_inputs_.json // // The path can be nested under a slug directory so callers can provide a // recognizable name to differentiate various contexts in which the InputJSON // is being written. If the Writer is configured by calling WithSlug, then the // path will be like: // -// $HOME/.arbuitrum/validation-inputs///block_inputs_.json +// $HOME/.arbitrum/validation-inputs///block_inputs_.json // // The inclusion of BlockId in the file's name is on by default, however that can be disabled // by calling WithBlockIdInFileNameEnabled(false). In which case, the path will be like: // -// $HOME/.arbuitrum/validation-inputs///block_inputs.json +// $HOME/.arbitrum/validation-inputs///block_inputs.json // // The inclusion of a timestamp directory is on by default to avoid conflicts which // would result in files being overwritten. However, the Writer can be configured // to not use a timestamp directory. If the Writer is configured by calling // WithTimestampDirEnabled(false), then the path will be like: // -// $HOME/.arbuitrum/validation-inputs//block_inputs_.json +// $HOME/.arbitrum/validation-inputs//block_inputs_.json // // Finally, to give complete control to the clients, the base directory can be // set directly with WithBaseDir. In which case, the path will be like: diff --git a/validator/interface.go b/validator/interface.go index ef862e287c..4756ff61f2 100644 --- a/validator/interface.go +++ b/validator/interface.go @@ -4,7 +4,7 @@ import ( "context" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/offchainlabs/nitro/util/containers" ) @@ -15,7 +15,7 @@ type ValidationSpawner interface { Start(context.Context) error Stop() Name() string - StylusArchs() []ethdb.WasmTarget + StylusArchs() []rawdb.WasmTarget Room() int } diff --git a/validator/server_api/json.go b/validator/server_api/json.go index 99b4e17bbf..52ce864585 100644 --- a/validator/server_api/json.go +++ b/validator/server_api/json.go @@ -9,7 +9,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbutil" @@ -63,7 +63,7 @@ type InputJSON struct { BatchInfo []BatchInfoJson DelayedMsgB64 string StartState validator.GoGlobalState - UserWasms map[ethdb.WasmTarget]map[common.Hash]string + UserWasms map[rawdb.WasmTarget]map[common.Hash]string DebugChain bool MaxUserWasmSize uint64 `json:"max-user-wasmSize,omitempty"` } @@ -90,7 +90,7 @@ func ValidationInputToJson(entry *validator.ValidationInput) *InputJSON { DelayedMsgB64: base64.StdEncoding.EncodeToString(entry.DelayedMsg), StartState: entry.StartState, PreimagesB64: jsonPreimagesMap, - UserWasms: make(map[ethdb.WasmTarget]map[common.Hash]string), + UserWasms: make(map[rawdb.WasmTarget]map[common.Hash]string), DebugChain: entry.DebugChain, } for _, binfo := range entry.BatchInfo { @@ -127,7 +127,7 @@ func ValidationInputFromJson(entry *InputJSON) (*validator.ValidationInput, erro DelayedMsgNr: entry.DelayedMsgNr, StartState: entry.StartState, Preimages: preimages, - UserWasms: make(map[ethdb.WasmTarget]map[common.Hash][]byte), + UserWasms: make(map[rawdb.WasmTarget]map[common.Hash][]byte), DebugChain: entry.DebugChain, } delayed, err := base64.StdEncoding.DecodeString(entry.DelayedMsgB64) diff --git a/validator/server_arb/bold_machine.go b/validator/server_arb/bold_machine.go index 6ca48ba228..b76b0321e8 100644 --- a/validator/server_arb/bold_machine.go +++ b/validator/server_arb/bold_machine.go @@ -143,3 +143,10 @@ func (m *BoldMachine) ProveNextStep() []byte { } return m.inner.ProveNextStep() } + +func (m *BoldMachine) GetNextOpcode() uint16 { + if !m.hasStepped { + return m.zeroMachine.GetNextOpcode() + } + return m.inner.GetNextOpcode() +} diff --git a/validator/server_arb/execution_run.go b/validator/server_arb/execution_run.go index 615015001b..b27af29a75 100644 --- a/validator/server_arb/execution_run.go +++ b/validator/server_arb/execution_run.go @@ -5,6 +5,7 @@ package server_arb import ( "context" + "encoding/binary" "fmt" "sync" "time" @@ -175,7 +176,21 @@ func (e *executionRun) GetProofAt(position uint64) containers.PromiseInterface[[ if err != nil { return nil, err } - return machine.ProveNextStep(), nil + + opcodeBytes := make([]byte, 2) + if machine.IsRunning() { + opcode := machine.GetNextOpcode() + + binary.BigEndian.PutUint16(opcodeBytes, opcode) + } else { + // append dummy opcode if the machine is halted + binary.BigEndian.PutUint16(opcodeBytes, 0xFFFF) + } + + proof := machine.ProveNextStep() + + proof = append(proof, opcodeBytes...) + return proof, nil }) } diff --git a/validator/server_arb/execution_run_test.go b/validator/server_arb/execution_run_test.go index 381cfa63a8..f6d9cd8aee 100644 --- a/validator/server_arb/execution_run_test.go +++ b/validator/server_arb/execution_run_test.go @@ -60,6 +60,9 @@ func (m *mockMachine) Status() uint8 { func (m *mockMachine) ProveNextStep() []byte { return nil } +func (m *mockMachine) GetNextOpcode() uint16 { + return 0 +} func (m *mockMachine) Freeze() {} func (m *mockMachine) Destroy() {} diff --git a/validator/server_arb/machine.go b/validator/server_arb/machine.go index 719a223369..4b38ce3ddf 100644 --- a/validator/server_arb/machine.go +++ b/validator/server_arb/machine.go @@ -46,6 +46,7 @@ type MachineInterface interface { Hash() common.Hash GetGlobalState() validator.GoGlobalState ProveNextStep() []byte + GetNextOpcode() uint16 Freeze() Destroy() } @@ -315,6 +316,14 @@ func (m *ArbitratorMachine) ProveNextStep() []byte { return proofBytes } +func (m *ArbitratorMachine) GetNextOpcode() uint16 { + defer runtime.KeepAlive(m) + m.mutex.Lock() + defer m.mutex.Unlock() + + return uint16(C.arbitrator_get_opcode(m.ptr)) +} + func (m *ArbitratorMachine) SerializeState(path string) error { defer runtime.KeepAlive(m) m.mutex.Lock() diff --git a/validator/server_arb/validator_spawner.go b/validator/server_arb/validator_spawner.go index 8b39fd1684..7958114e30 100644 --- a/validator/server_arb/validator_spawner.go +++ b/validator/server_arb/validator_spawner.go @@ -11,7 +11,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -34,7 +33,7 @@ type ArbitratorSpawnerConfig struct { RedisValidationServerConfig redis.ValidationServerConfig `koanf:"redis-validation-server-config"` } -type ArbitratorSpawnerConfigFecher func() *ArbitratorSpawnerConfig +type ArbitratorSpawnerConfigFetcher func() *ArbitratorSpawnerConfig var DefaultArbitratorSpawnerConfig = ArbitratorSpawnerConfig{ Workers: 0, @@ -58,7 +57,7 @@ func DefaultArbitratorSpawnerConfigFetcher() *ArbitratorSpawnerConfig { // MachineWrapper is a function that wraps a MachineInterface // -// This is a mechanism to allow clients of the AribtratorSpawner to inject +// This is a mechanism to allow clients of the ArbitratorSpawner to inject // functionality around the arbitrator machine. Possible use cases include // mocking out the machine for testing purposes, or having the machine behave // differently when certain features (like BoLD) are enabled. @@ -71,9 +70,9 @@ type ArbitratorSpawner struct { count atomic.Int32 locator *server_common.MachineLocator machineLoader *ArbMachineLoader - // Oreder of wrappers is important. The first wrapper is the innermost. + // Order of wrappers is important. The first wrapper is the innermost. machineWrappers []MachineWrapper - config ArbitratorSpawnerConfigFecher + config ArbitratorSpawnerConfigFetcher } func WithWrapper(wrapper MachineWrapper) SpawnerOption { @@ -82,7 +81,7 @@ func WithWrapper(wrapper MachineWrapper) SpawnerOption { } } -func NewArbitratorSpawner(locator *server_common.MachineLocator, config ArbitratorSpawnerConfigFecher, opts ...SpawnerOption) (*ArbitratorSpawner, error) { +func NewArbitratorSpawner(locator *server_common.MachineLocator, config ArbitratorSpawnerConfigFetcher, opts ...SpawnerOption) (*ArbitratorSpawner, error) { // TODO: preload machines spawner := &ArbitratorSpawner{ locator: locator, @@ -105,8 +104,8 @@ func (s *ArbitratorSpawner) WasmModuleRoots() ([]common.Hash, error) { return s.locator.ModuleRoots(), nil } -func (s *ArbitratorSpawner) StylusArchs() []ethdb.WasmTarget { - return []ethdb.WasmTarget{rawdb.TargetWavm} +func (s *ArbitratorSpawner) StylusArchs() []rawdb.WasmTarget { + return []rawdb.WasmTarget{rawdb.TargetWavm} } func (s *ArbitratorSpawner) Name() string { diff --git a/validator/server_jit/machine_loader.go b/validator/server_jit/machine_loader.go index a4ccede324..2008a0e70c 100644 --- a/validator/server_jit/machine_loader.go +++ b/validator/server_jit/machine_loader.go @@ -18,15 +18,24 @@ type JitMachineConfig struct { ProverBinPath string JitCranelift bool WasmMemoryUsageLimit int + JitPath string } var DefaultJitMachineConfig = JitMachineConfig{ JitCranelift: true, ProverBinPath: "replay.wasm", WasmMemoryUsageLimit: 4294967296, + JitPath: "", // Empty string means use default path resolution } -func getJitPath() (string, error) { +func getJitPath(configPath string) (string, error) { + // If a custom path is provided, use it directly + if configPath != "" { + _, err := os.Stat(configPath) + return configPath, err + } + + // Fall back to original logic for auto-detection var jitBinary string executable, err := os.Executable() if err == nil { @@ -55,7 +64,7 @@ type JitMachineLoader struct { } func NewJitMachineLoader(config *JitMachineConfig, locator *server_common.MachineLocator, maxExecutionTime time.Duration, fatalErrChan chan error) (*JitMachineLoader, error) { - jitPath, err := getJitPath() + jitPath, err := getJitPath(config.JitPath) if err != nil { return nil, err } diff --git a/validator/server_jit/spawner.go b/validator/server_jit/spawner.go index a2822fb4fc..8df1465fb3 100644 --- a/validator/server_jit/spawner.go +++ b/validator/server_jit/spawner.go @@ -10,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/ethdb" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/stopwaiter" @@ -22,6 +21,7 @@ type JitSpawnerConfig struct { Workers int `koanf:"workers" reload:"hot"` Cranelift bool `koanf:"cranelift"` MaxExecutionTime time.Duration `koanf:"max-execution-time" reload:"hot"` + JitPath string `koanf:"jit-path"` // TODO: change WasmMemoryUsageLimit to a string and use resourcemanager.ParseMemLimit WasmMemoryUsageLimit int `koanf:"wasm-memory-usage-limit"` @@ -34,6 +34,7 @@ var DefaultJitSpawnerConfig = JitSpawnerConfig{ Cranelift: true, WasmMemoryUsageLimit: 4294967296, // 2^32 WASM memory limit MaxExecutionTime: time.Minute * 10, + JitPath: "", // Empty string means use default path resolution } func JitSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { @@ -41,6 +42,7 @@ func JitSpawnerConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".cranelift", DefaultJitSpawnerConfig.Cranelift, "use Cranelift instead of LLVM when validating blocks using the jit-accelerated block validator") f.Int(prefix+".wasm-memory-usage-limit", DefaultJitSpawnerConfig.WasmMemoryUsageLimit, "if memory used by a jit wasm exceeds this limit, a warning is logged") f.Duration(prefix+".max-execution-time", DefaultJitSpawnerConfig.MaxExecutionTime, "if execution time used by a jit wasm exceeds this limit, a rpc error is returned") + f.String(prefix+".jit-path", DefaultJitSpawnerConfig.JitPath, "path to jit executable, if empty, attempts to find jit executable relative to nitro binary or in PATH") } type JitSpawner struct { @@ -56,6 +58,7 @@ func NewJitSpawner(locator *server_common.MachineLocator, config JitSpawnerConfi machineConfig := DefaultJitMachineConfig machineConfig.JitCranelift = config().Cranelift machineConfig.WasmMemoryUsageLimit = config().WasmMemoryUsageLimit + machineConfig.JitPath = config().JitPath maxExecutionTime := config().MaxExecutionTime loader, err := NewJitMachineLoader(&machineConfig, locator, maxExecutionTime, fatalErrChan) if err != nil { @@ -78,8 +81,8 @@ func (v *JitSpawner) WasmModuleRoots() ([]common.Hash, error) { return v.locator.ModuleRoots(), nil } -func (v *JitSpawner) StylusArchs() []ethdb.WasmTarget { - return []ethdb.WasmTarget{rawdb.LocalTarget()} +func (v *JitSpawner) StylusArchs() []rawdb.WasmTarget { + return []rawdb.WasmTarget{rawdb.LocalTarget()} } func (v *JitSpawner) execute( diff --git a/validator/validation_entry.go b/validator/validation_entry.go index 69726e7af7..3323a62d1f 100644 --- a/validator/validation_entry.go +++ b/validator/validation_entry.go @@ -2,7 +2,7 @@ package validator import ( "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/offchainlabs/nitro/daprovider" ) @@ -17,7 +17,7 @@ type ValidationInput struct { HasDelayedMsg bool DelayedMsgNr uint64 Preimages daprovider.PreimagesMap - UserWasms map[ethdb.WasmTarget]map[common.Hash][]byte + UserWasms map[rawdb.WasmTarget]map[common.Hash][]byte BatchInfo []BatchInfo DelayedMsg []byte StartState GoGlobalState diff --git a/validator/valnode/redis/consumer.go b/validator/valnode/redis/consumer.go index 32b639dc4b..085c699ea7 100644 --- a/validator/valnode/redis/consumer.go +++ b/validator/valnode/redis/consumer.go @@ -171,7 +171,7 @@ func (s *ValidationServer) Start(ctx_in context.Context) { } else { log.Debug("done work", "thread", i, "workid", work.req.ID) err := s.consumers[work.moduleRoot].SetResult(ctx, work.req.ID, res) - // Even in error we close ackNotifier as there's no retry mechanism here and closing it will alow other consumers to autoclaim + // Even in error we close ackNotifier as there's no retry mechanism here and closing it will allow other consumers to autoclaim work.req.Ack() if err != nil { log.Error("Error setting result for request", "id", work.req.ID, "result", res, "error", err) diff --git a/validator/valnode/redis/consumer_test.go b/validator/valnode/redis/consumer_test.go index 595aecc9ca..f88e08fb4d 100644 --- a/validator/valnode/redis/consumer_test.go +++ b/validator/valnode/redis/consumer_test.go @@ -20,7 +20,7 @@ func TestTimeout(t *testing.T) { TestValidationServerConfig.StreamTimeout = 100 * time.Millisecond vs, err := NewValidationServer(&TestValidationServerConfig, nil) if err != nil { - t.Fatalf("NewValidationSever() unexpected error: %v", err) + t.Fatalf("NewValidationServer() unexpected error: %v", err) } vs.Start(ctx) time.Sleep(time.Second) diff --git a/validator/valnode/validation_api.go b/validator/valnode/validation_api.go index fea269b88c..fc587e74c4 100644 --- a/validator/valnode/validation_api.go +++ b/validator/valnode/validation_api.go @@ -12,7 +12,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/core/rawdb" "github.com/offchainlabs/nitro/util/stopwaiter" "github.com/offchainlabs/nitro/validator" @@ -45,7 +45,7 @@ func (a *ValidationServerAPI) WasmModuleRoots() ([]common.Hash, error) { return a.spawner.WasmModuleRoots() } -func (a *ValidationServerAPI) StylusArchs() ([]ethdb.WasmTarget, error) { +func (a *ValidationServerAPI) StylusArchs() ([]rawdb.WasmTarget, error) { return a.spawner.StylusArchs(), nil } @@ -63,18 +63,18 @@ type ExecServerAPI struct { ValidationServerAPI execSpawner validator.ExecutionSpawner - config server_arb.ArbitratorSpawnerConfigFecher + config server_arb.ArbitratorSpawnerConfigFetcher runIdLock sync.Mutex nextId uint64 runs map[uint64]*execRunEntry } -func NewExecutionServerAPI(valSpawner validator.ValidationSpawner, execution validator.ExecutionSpawner, config server_arb.ArbitratorSpawnerConfigFecher) *ExecServerAPI { +func NewExecutionServerAPI(valSpawner validator.ValidationSpawner, execution validator.ExecutionSpawner, config server_arb.ArbitratorSpawnerConfigFetcher) *ExecServerAPI { return &ExecServerAPI{ ValidationServerAPI: *NewValidationServerAPI(valSpawner), execSpawner: execution, - nextId: rand.Uint64(), // good-enough to aver reusing ids after reboot + nextId: rand.Uint64(), // good-enough to avoid reusing ids after reboot runs: make(map[uint64]*execRunEntry), config: config, } diff --git a/validator/valnode/valnode.go b/validator/valnode/valnode.go index 4120a73b3d..273dee1128 100644 --- a/validator/valnode/valnode.go +++ b/validator/valnode/valnode.go @@ -26,7 +26,7 @@ type WasmConfig struct { func WasmConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".root-path", DefaultWasmConfig.RootPath, "path to machine folders, each containing wasm files (machine.wavm.br, replay.wasm)") f.Bool(prefix+".enable-wasmroots-check", DefaultWasmConfig.EnableWasmrootsCheck, "enable check for compatibility of on-chain WASM module root with node") - f.StringSlice(prefix+".allowed-wasm-module-roots", DefaultWasmConfig.AllowedWasmModuleRoots, "list of WASM module roots or mahcine base paths to match against on-chain WasmModuleRoot") + f.StringSlice(prefix+".allowed-wasm-module-roots", DefaultWasmConfig.AllowedWasmModuleRoots, "list of WASM module roots or machine base paths to match against on-chain WasmModuleRoot") } var DefaultWasmConfig = WasmConfig{ diff --git a/wavmio/higher.go b/wavmio/higher.go index a3eb9642a4..8f6684484d 100644 --- a/wavmio/higher.go +++ b/wavmio/higher.go @@ -10,6 +10,7 @@ import ( "unsafe" "github.com/ethereum/go-ethereum/common" + "github.com/offchainlabs/nitro/arbutil" ) diff --git a/wsbroadcastserver/connectionlimiter.go b/wsbroadcastserver/connectionlimiter.go index 188e5b658b..04a70b268d 100644 --- a/wsbroadcastserver/connectionlimiter.go +++ b/wsbroadcastserver/connectionlimiter.go @@ -80,7 +80,7 @@ type ipStringAndLimit struct { func (l *ConnectionLimiter) getIpStringsAndLimits(ip net.IP) []ipStringAndLimit { var result []ipStringAndLimit if ip == nil || ip.IsPrivate() || ip.IsLoopback() { - log.Warn("Ignoring private, looback, or unparseable IP. Please check relay and network configuration to ensure client IP addresses are detected correctly", "ip", ip) + log.Warn("Ignoring private, loopback, or unparseable IP. Please check relay and network configuration to ensure client IP addresses are detected correctly", "ip", ip) return result } diff --git a/wsbroadcastserver/dictionary.go b/wsbroadcastserver/dictionary.go index 98aa950c55..da761b87dd 100644 --- a/wsbroadcastserver/dictionary.go +++ b/wsbroadcastserver/dictionary.go @@ -4,7 +4,7 @@ import "compress/flate" const DeflateCompressionLevel = flate.BestCompression -// The static dictionary was created by appending manually created dictionary to a dictionary generated with dictator tool (https://github.com/vkrasnov/dictator) +// The static dictionary was created by appending a manually created dictionary to a dictionary generated with dictator tool (https://github.com/vkrasnov/dictator) // * the dictator tool was used with default parameters, except for threshold which was set to 0.05% and compression level to 9 // * the input for the generator consisted of 61512 preprocessed messages gathered from public feed with wscat // * the preprocessing substituted sequential numeric fields with pseudorandom values to minimize overfitting diff --git a/wsbroadcastserver/wsbroadcastserver.go b/wsbroadcastserver/wsbroadcastserver.go index d41556351c..4bbc513937 100644 --- a/wsbroadcastserver/wsbroadcastserver.go +++ b/wsbroadcastserver/wsbroadcastserver.go @@ -36,8 +36,8 @@ var ( HTTPHeaderFeedClientVersion = textproto.CanonicalMIMEHeaderKey("Arbitrum-Feed-Client-Version") HTTPHeaderRequestedSequenceNumber = textproto.CanonicalMIMEHeaderKey("Arbitrum-Requested-Sequence-Number") HTTPHeaderChainId = textproto.CanonicalMIMEHeaderKey("Arbitrum-Chain-Id") - upgradeToWSTimer = metrics.NewRegisteredTimer("arb/feed/clients/upgrade/duration", nil) - startWithHeaderTimer = metrics.NewRegisteredTimer("arb/feed/clients/start/duration", nil) + upgradeToWSTimer = metrics.NewRegisteredHistogram("arb/feed/clients/upgrade/duration", nil, metrics.NewBoundedHistogramSample()) + startWithHeaderTimer = metrics.NewRegisteredHistogram("arb/feed/clients/start/duration", nil, metrics.NewBoundedHistogramSample()) ) const ( @@ -210,7 +210,7 @@ func (s *WSBroadcastServer) Start(ctx context.Context) error { startTime := time.Now() err := s.StartWithHeader(ctx, header) elapsed := time.Since(startTime) - startWithHeaderTimer.Update(elapsed) + startWithHeaderTimer.Update(elapsed.Nanoseconds()) return err } @@ -329,7 +329,7 @@ func (s *WSBroadcastServer) StartWithHeader(ctx context.Context, header ws.Hands startTime := time.Now() _, err = upgrader.Upgrade(conn) elapsed := time.Since(startTime) - upgradeToWSTimer.Update(elapsed) + upgradeToWSTimer.Update(elapsed.Nanoseconds()) if err != nil { if err.Error() != "" {