diff --git a/.github/actions/setup-kurtosis/action.yml b/.github/actions/setup-kurtosis/action.yml new file mode 100644 index 00000000000..fcea1609f7a --- /dev/null +++ b/.github/actions/setup-kurtosis/action.yml @@ -0,0 +1,74 @@ + +name: "Setup Kurtosis" +description: "Setup Kurtosis CDK for tests" +runs: + using: "composite" + steps: + - name: Checkout cdk-erigon + uses: actions/checkout@v4 + with: + path: cdk-erigon + + - name: Checkout kurtosis-cdk + uses: actions/checkout@v4 + with: + repository: 0xPolygon/kurtosis-cdk + ref: v0.2.24 + path: kurtosis-cdk + + - name: Install Kurtosis CDK tools + uses: ./kurtosis-cdk/.github/actions/setup-kurtosis-cdk + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install polycli + shell: bash + run: | + tmp_dir=$(mktemp -d) && curl -L https://github.com/0xPolygon/polygon-cli/releases/download/v0.1.48/polycli_v0.1.48_linux_amd64.tar.gz | tar -xz -C "$tmp_dir" && mv "$tmp_dir"/* /usr/local/bin/polycli && rm -rf "$tmp_dir" + sudo chmod +x /usr/local/bin/polycli + /usr/local/bin/polycli version + + - name: Install yq + shell: bash + run: | + sudo curl -L https://github.com/mikefarah/yq/releases/download/v4.44.2/yq_linux_amd64 -o /usr/local/bin/yq + sudo chmod +x /usr/local/bin/yq + /usr/local/bin/yq --version + + - name: Build docker image + working-directory: ./cdk-erigon + shell: bash + run: docker build -t cdk-erigon:local --file Dockerfile . + + - name: Remove unused flags + working-directory: ./kurtosis-cdk + shell: bash + run: | + sed -i '/zkevm.sequencer-batch-seal-time:/d' templates/cdk-erigon/config.yml + sed -i '/zkevm.sequencer-non-empty-batch-seal-time:/d' templates/cdk-erigon/config.yml + sed -i '/zkevm\.sequencer-initial-fork-id/d' ./templates/cdk-erigon/config.yml + sed -i '/sentry.drop-useless-peers:/d' templates/cdk-erigon/config.yml + sed -i '/zkevm\.pool-manager-url/d' ./templates/cdk-erigon/config.yml + sed -i '$a\zkevm.disable-virtual-counters: true' ./templates/cdk-erigon/config.yml + sed -i '/zkevm.l2-datastreamer-timeout:/d' templates/cdk-erigon/config.yml + + - name: Create params.yml overrides + working-directory: ./kurtosis-cdk + shell: bash + run: | + echo 'args:' > params.yml + echo ' cdk_erigon_node_image: cdk-erigon:local' >> params.yml + echo ' el-1-geth-lighthouse: ethpandaops/lighthouse@sha256:4902d9e4a6b6b8d4c136ea54f0e51582a32f356f3dec7194a1adee13ed2d662e' >> params.yml + /usr/local/bin/yq -i '.args.data_availability_mode = "${{ matrix.da-mode }}"' params.yml + sed -i 's/"londonBlock": [0-9]\+/"londonBlock": 0/' ./templates/cdk-erigon/chainspec.json + sed -i 's/"normalcyBlock": [0-9]\+/"normalcyBlock": 0/' ./templates/cdk-erigon/chainspec.json + sed -i 's/"shanghaiTime": [0-9]\+/"shanghaiTime": 0/' ./templates/cdk-erigon/chainspec.json + sed -i 's/"cancunTime": [0-9]\+/"cancunTime": 0/' ./templates/cdk-erigon/chainspec.json + sed -i '/"terminalTotalDifficulty"/d' ./templates/cdk-erigon/chainspec.json + + - name: Deploy Kurtosis CDK package + working-directory: ./kurtosis-cdk + shell: bash + run: | + kurtosis run --enclave cdk-v1 --args-file params.yml --image-download always . '{"args": {"erigon_strict_mode": false, "cdk_erigon_node_image": "cdk-erigon:local"}}' \ No newline at end of file diff --git a/.github/scripts/test_resequence.sh b/.github/scripts/test_resequence.sh index b36bc878236..d59c780a33c 100755 --- a/.github/scripts/test_resequence.sh +++ b/.github/scripts/test_resequence.sh @@ -50,7 +50,7 @@ wait_for_l1_batch() { current_batch=$(cast logs --rpc-url "$(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc)" --address 0x1Fe038B54aeBf558638CA51C91bC8cCa06609e91 --from-block 0 --json | jq -r '.[] | select(.topics[0] == "0x3e54d0825ed78523037d00a81759237eb436ce774bd546993ee67a1b67b6e766") | .topics[1]' | tail -n 1 | sed 's/^0x//') current_batch=$((16#$current_batch)) elif [ "$batch_type" = "verified" ]; then - current_batch=$(cast rpc zkevm_verifiedBatchNumber --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc)" | sed 's/^"//;s/"$//') + current_batch=$(cast rpc zkevm_verifiedBatchNumber --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc)" | sed 's/^"//;s/"$//') else echo "Invalid batch type. Use 'virtual' or 'verified'." return 1 @@ -121,7 +121,7 @@ kurtosis service exec cdk-v1 cdk-erigon-sequencer-001 "nohup cdk-erigon --pprof= sleep 30 echo "Running loadtest using polycli" -/usr/local/bin/polycli loadtest --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc)" --private-key "0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" --verbosity 600 --requests 2000 --rate-limit 500 --mode uniswapv3 --legacy +/usr/local/bin/polycli loadtest --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc)" --private-key "0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" --verbosity 600 --requests 2000 --rate-limit 500 --mode uniswapv3 --legacy echo "Waiting for batch virtualization" if ! wait_for_l1_batch 600 "virtual"; then @@ -174,13 +174,13 @@ echo "Getting block hash from sequencer" sequencer_hash=$(cast block $comparison_block --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-sequencer-001 rpc)" | grep "hash" | awk '{print $2}') # wait for block to be available on sync node -if ! wait_for_l2_block_number $comparison_block "$(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc)"; then +if ! wait_for_l2_block_number $comparison_block "$(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc)"; then echo "Failed to wait for batch verification" exit 1 fi echo "Getting block hash from node" -node_hash=$(cast block $comparison_block --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc)" | grep "hash" | awk '{print $2}') +node_hash=$(cast block $comparison_block --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc)" | grep "hash" | awk '{print $2}') echo "Sequencer block hash: $sequencer_hash" echo "Node block hash: $node_hash" diff --git a/.github/workflows/ci_zkevm.yml b/.github/workflows/ci_zkevm.yml index 21447af3ebb..443ead2bfba 100644 --- a/.github/workflows/ci_zkevm.yml +++ b/.github/workflows/ci_zkevm.yml @@ -73,46 +73,8 @@ jobs: steps: - name: Checkout cdk-erigon uses: actions/checkout@v4 - with: - path: cdk-erigon - - - name: Checkout kurtosis-cdk - uses: actions/checkout@v4 - with: - repository: 0xPolygon/kurtosis-cdk - ref: v0.2.12 - path: kurtosis-cdk - - - name: Install Kurtosis CDK tools (Kurtosis, yq, Foundry, disable analytics) - uses: ./kurtosis-cdk/.github/actions/setup-kurtosis-cdk - - - name: Install yq - run: | - sudo curl -L https://github.com/mikefarah/yq/releases/download/v4.44.2/yq_linux_amd64 -o /usr/local/bin/yq - sudo chmod +x /usr/local/bin/yq - /usr/local/bin/yq --version - - - name: Build docker image - working-directory: ./cdk-erigon - run: docker build -t cdk-erigon:local --file Dockerfile . - - - name: Remove unused flags - working-directory: ./kurtosis-cdk - run: | - sed -i '/zkevm.sequencer-batch-seal-time:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm.sequencer-non-empty-batch-seal-time:/d' templates/cdk-erigon/config.yml - sed -i '/sentry.drop-useless-peers:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm.l2-datastreamer-timeout:/d' templates/cdk-erigon/config.yml - - name: Configure Kurtosis CDK - working-directory: ./kurtosis-cdk - run: | - /usr/local/bin/yq -i '.args.data_availability_mode = "${{ matrix.da-mode }}"' params.yml - /usr/local/bin/yq -i '.args.cdk_erigon_node_image = "cdk-erigon:local"' params.yml - - - name: Deploy Kurtosis CDK package - working-directory: ./kurtosis-cdk - run: | - kurtosis run --enclave cdk-v1 --image-download always . '{"args": {"data_availability_mode": "${{ matrix.da-mode }}", "cdk_erigon_node_image": "cdk-erigon:local"}}' + - name: Setup kurtosis + uses: ./.github/actions/setup-kurtosis - name: Run process with CPU monitoring working-directory: ./cdk-erigon @@ -134,9 +96,7 @@ jobs: - name: Monitor verified batches working-directory: ./kurtosis-cdk shell: bash - env: - ENCLAVE_NAME: cdk-v1 - run: timeout 900s .github/scripts/monitor-verified-batches.sh --rpc-url $(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc) --target 20 --timeout 900 + run: timeout 900s .github/scripts/monitor-verified-batches.sh --enclave zdk-v1 --rpc-url $(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc) --target 20 --timeout 900 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 @@ -146,9 +106,8 @@ jobs: kurtosis files download cdk-v1 bridge-config-artifact echo "BRIDGE_ADDRESS=$(/usr/local/bin/yq '.NetworkConfig.PolygonBridgeAddress' bridge-config-artifact/bridge-config.toml)" >> $GITHUB_ENV echo "ETH_RPC_URL=$(kurtosis port print cdk-v1 el-1-geth-lighthouse rpc)" >> $GITHUB_ENV - echo "L2_RPC_URL=$(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc)" >> $GITHUB_ENV echo "BRIDGE_API_URL=$(kurtosis port print cdk-v1 zkevm-bridge-service-001 rpc)" >> $GITHUB_ENV - + echo "L2_RPC_URL=$(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc)" >> $GITHUB_ENV - name: Clone bridge repository run: git clone --recurse-submodules -j8 https://github.com/0xPolygonHermez/zkevm-bridge-service.git -b develop bridge @@ -186,7 +145,7 @@ jobs: run: | mkdir -p ci_logs cd ci_logs - kurtosis service logs cdk-v1 cdk-erigon-node-001 --all > cdk-erigon-node-001.log + kurtosis service logs cdk-v1 cdk-erigon-rpc-001 --all > cdk-erigon-rpc-001.log kurtosis service logs cdk-v1 cdk-erigon-sequencer-001 --all > cdk-erigon-sequencer-001.log kurtosis service logs cdk-v1 zkevm-agglayer-001 --all > zkevm-agglayer-001.log kurtosis service logs cdk-v1 zkevm-prover-001 --all > zkevm-prover-001.log @@ -210,62 +169,12 @@ jobs: - name: Checkout kurtosis-cdk uses: actions/checkout@v4 - with: - repository: 0xPolygon/kurtosis-cdk - ref: v0.2.12 - path: kurtosis-cdk - - - name: Install Kurtosis CDK tools - uses: ./kurtosis-cdk/.github/actions/setup-kurtosis-cdk - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Install yq - run: | - sudo curl -L https://github.com/mikefarah/yq/releases/download/v4.44.2/yq_linux_amd64 -o /usr/local/bin/yq - sudo chmod +x /usr/local/bin/yq - /usr/local/bin/yq --version - - - name: Install polycli - run: | - tmp_dir=$(mktemp -d) && curl -L https://github.com/0xPolygon/polygon-cli/releases/download/v0.1.48/polycli_v0.1.48_linux_amd64.tar.gz | tar -xz -C "$tmp_dir" && mv "$tmp_dir"/* /usr/local/bin/polycli && rm -rf "$tmp_dir" - sudo chmod +x /usr/local/bin/polycli - /usr/local/bin/polycli version + - name: Setup kurtosis + uses: ./.github/actions/setup-kurtosis - - name: Build docker image - working-directory: ./cdk-erigon - run: docker build -t cdk-erigon:local --file Dockerfile . - - - name: Modify cdk-erigon flags - working-directory: ./kurtosis-cdk - run: | - sed -i '/zkevm.sequencer-batch-seal-time:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm.sequencer-non-empty-batch-seal-time:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm\.sequencer-initial-fork-id/d' ./templates/cdk-erigon/config.yml - sed -i '/sentry.drop-useless-peers:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm\.pool-manager-url/d' ./templates/cdk-erigon/config.yml - sed -i '$a\zkevm.disable-virtual-counters: true' ./templates/cdk-erigon/config.yml - sed -i '/zkevm.l2-datastreamer-timeout:/d' templates/cdk-erigon/config.yml - - - - name: Configure Kurtosis CDK - working-directory: ./kurtosis-cdk - run: | - sed -i 's/"londonBlock": [0-9]\+/"londonBlock": 0/' ./templates/cdk-erigon/chainspec.json - sed -i 's/"normalcyBlock": [0-9]\+/"normalcyBlock": 0/' ./templates/cdk-erigon/chainspec.json - sed -i 's/"shanghaiTime": [0-9]\+/"shanghaiTime": 0/' ./templates/cdk-erigon/chainspec.json - sed -i 's/"cancunTime": [0-9]\+/"cancunTime": 0/' ./templates/cdk-erigon/chainspec.json - sed -i '/"terminalTotalDifficulty"/d' ./templates/cdk-erigon/chainspec.json - - - name: Deploy Kurtosis CDK package - working-directory: ./kurtosis-cdk - run: | - kurtosis run --enclave cdk-v1 --image-download always . '{"args": {"erigon_strict_mode": false, "cdk_erigon_node_image": "cdk-erigon:local"}}' - - name: Dynamic gas fee tx load test working-directory: ./kurtosis-cdk - run: /usr/local/bin/polycli loadtest --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-node-001 rpc)" --private-key "0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" --verbosity 700 --requests 500 --rate-limit 50 --mode uniswapv3 + run: /usr/local/bin/polycli loadtest --rpc-url "$(kurtosis port print cdk-v1 cdk-erigon-rpc-001 rpc)" --private-key "0x12d7de8621a77640c9241b2595ba78ce443d05e94090365ab3bb5e19df82c625" --verbosity 700 --requests 500 --rate-limit 50 --mode uniswapv3 --legacy - name: Upload logs uses: actions/upload-artifact@v3 @@ -279,7 +188,7 @@ jobs: run: | mkdir -p ci_logs cd ci_logs - kurtosis service logs cdk-v1 cdk-erigon-node-001 --all > cdk-erigon-node-001.log + kurtosis service logs cdk-v1 cdk-erigon-rpc-001 --all > cdk-erigon-rpc-001.log kurtosis service logs cdk-v1 cdk-erigon-sequencer-001 --all > cdk-erigon-sequencer-001.log - name: Upload logs diff --git a/.github/workflows/test-resequence.yml b/.github/workflows/test-resequence.yml index 63029d21c56..cee0118e0e1 100644 --- a/.github/workflows/test-resequence.yml +++ b/.github/workflows/test-resequence.yml @@ -18,51 +18,8 @@ jobs: steps: - name: Checkout cdk-erigon uses: actions/checkout@v4 - with: - path: cdk-erigon - - - name: Checkout kurtosis-cdk - uses: actions/checkout@v4 - with: - repository: 0xPolygon/kurtosis-cdk - ref: v0.2.12 - path: kurtosis-cdk - - - name: Install Kurtosis CDK tools - uses: ./kurtosis-cdk/.github/actions/setup-kurtosis-cdk - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Install yq - run: | - sudo curl -L https://github.com/mikefarah/yq/releases/download/v4.44.2/yq_linux_amd64 -o /usr/local/bin/yq - sudo chmod +x /usr/local/bin/yq - /usr/local/bin/yq --version - - name: Install polycli - run: | - tmp_dir=$(mktemp -d) && curl -L https://github.com/0xPolygon/polygon-cli/releases/download/v0.1.48/polycli_v0.1.48_linux_amd64.tar.gz | tar -xz -C "$tmp_dir" && mv "$tmp_dir"/* /usr/local/bin/polycli && rm -rf "$tmp_dir" - sudo chmod +x /usr/local/bin/polycli - /usr/local/bin/polycli version - - name: Build docker image - working-directory: ./cdk-erigon - run: docker build -t cdk-erigon:local --file Dockerfile . - - - name: Remove unused flags - working-directory: ./kurtosis-cdk - run: | - sed -i '/zkevm.sequencer-batch-seal-time:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm.sequencer-non-empty-batch-seal-time:/d' templates/cdk-erigon/config.yml - sed -i '/sentry.drop-useless-peers:/d' templates/cdk-erigon/config.yml - sed -i '/zkevm.pool-manager-url/d' templates/cdk-erigon/config.yml - sed -i '/zkevm.l2-datastreamer-timeout:/d' templates/cdk-erigon/config.yml - - name: Configure Kurtosis CDK - working-directory: ./kurtosis-cdk - run: | - /usr/local/bin/yq -i '.args.cdk_erigon_node_image = "cdk-erigon:local"' params.yml - - name: Deploy Kurtosis CDK package - working-directory: ./kurtosis-cdk - run: kurtosis run --enclave cdk-v1 --args-file params.yml --image-download always . + - name: Setup kurtosis + uses: ./.github/actions/setup-kurtosis - name: Test resequence working-directory: ./cdk-erigon @@ -80,7 +37,7 @@ jobs: run: | mkdir -p ci_logs cd ci_logs - kurtosis service logs cdk-v1 cdk-erigon-node-001 --all > cdk-erigon-node-001.log + kurtosis service logs cdk-v1 cdk-erigon-rpc-001 --all > cdk-erigon-rpc-001.log kurtosis service logs cdk-v1 cdk-erigon-sequencer-001 --all > cdk-erigon-sequencer-001.log kurtosis service logs cdk-v1 zkevm-agglayer-001 --all > zkevm-agglayer-001.log kurtosis service logs cdk-v1 zkevm-prover-001 --all > zkevm-prover-001.log diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 319b3437fbf..c670d212209 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -773,6 +773,16 @@ var ( Usage: "Mock the witness generation", Value: false, } + WitnessCacheEnable = cli.BoolFlag{ + Name: "zkevm.witness-cache-enable", + Usage: "Enable witness cache", + Value: false, + } + WitnessCacheLimit = cli.UintFlag{ + Name: "zkevm.witness-cache-limit", + Usage: "Amount of blocks behind the last executed one to keep witnesses for. Needs a lot of HDD space. Default value 10 000.", + Value: 10000, + } WitnessContractInclusion = cli.StringFlag{ Name: "zkevm.witness-contract-inclusion", Usage: "Contracts that will have all of their storage added to the witness every time", diff --git a/core/rawdb/accessors_chain_zkevm.go b/core/rawdb/accessors_chain_zkevm.go index f50d073eb64..e6bfe2787d0 100644 --- a/core/rawdb/accessors_chain_zkevm.go +++ b/core/rawdb/accessors_chain_zkevm.go @@ -6,6 +6,7 @@ import ( "fmt" "math" + "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/dbg" "github.com/ledgerwatch/erigon-lib/common/hexutility" @@ -252,3 +253,29 @@ func ReadReceipts_zkEvm(db kv.Tx, block *types.Block, senders []libcommon.Addres } return receipts } + +func ReadHeaderByNumber_zkevm(db kv.Getter, number uint64) (header *types.Header, err error) { + hash, err := ReadCanonicalHash(db, number) + if err != nil { + return nil, fmt.Errorf("ReadCanonicalHash: %w", err) + } + if hash == (common.Hash{}) { + return nil, nil + } + + return ReadHeader_zkevm(db, hash, number) +} + +// ReadHeader retrieves the block header corresponding to the hash. +func ReadHeader_zkevm(db kv.Getter, hash common.Hash, number uint64) (header *types.Header, err error) { + data := ReadHeaderRLP(db, hash, number) + if len(data) == 0 { + return nil, nil + } + + header = new(types.Header) + if err := rlp.Decode(bytes.NewReader(data), header); err != nil { + return nil, fmt.Errorf("invalid block header RLP hash: %v, err: %w", hash, err) + } + return header, nil +} diff --git a/core/state/trie_db.go b/core/state/trie_db.go index 3a13013b83e..965562315d0 100644 --- a/core/state/trie_db.go +++ b/core/state/trie_db.go @@ -740,7 +740,7 @@ type TrieStateWriter struct { tds *TrieDbState } -func (tds *TrieDbState) TrieStateWriter() *TrieStateWriter { +func (tds *TrieDbState) NewTrieStateWriter() *TrieStateWriter { return &TrieStateWriter{tds: tds} } diff --git a/erigon-lib/kv/tables.go b/erigon-lib/kv/tables.go index ce8baaa5b8b..e9aebf625fe 100644 --- a/erigon-lib/kv/tables.go +++ b/erigon-lib/kv/tables.go @@ -547,6 +547,7 @@ const ( TableHashKey = "HermezSmtHashKey" TablePoolLimbo = "PoolLimbo" BATCH_ENDS = "batch_ends" + WITNESS_CACHE = "witness_cache" //Diagnostics tables DiagSystemInfo = "DiagSystemInfo" DiagSyncStages = "DiagSyncStages" @@ -791,6 +792,7 @@ var ChaindataTables = []string{ TableHashKey, TablePoolLimbo, BATCH_ENDS, + WITNESS_CACHE, } const ( diff --git a/eth/ethconfig/config_zkevm.go b/eth/ethconfig/config_zkevm.go index 31b069531f0..3142a368e57 100644 --- a/eth/ethconfig/config_zkevm.go +++ b/eth/ethconfig/config_zkevm.go @@ -94,6 +94,8 @@ type Zk struct { BadBatches []uint64 SealBatchImmediatelyOnOverflow bool MockWitnessGeneration bool + WitnessCacheEnabled bool + WitnessCacheLimit uint64 WitnessContractInclusion []common.Address } diff --git a/eth/stagedsync/stages/stages_zk.go b/eth/stagedsync/stages/stages_zk.go index 4ac4583fa82..42936bdb615 100644 --- a/eth/stagedsync/stages/stages_zk.go +++ b/eth/stagedsync/stages/stages_zk.go @@ -31,4 +31,5 @@ var ( // HighestUsedL1InfoIndex SyncStage = "HighestUsedL1InfoTree" SequenceExecutorVerify SyncStage = "SequenceExecutorVerify" L1BlockSync SyncStage = "L1BlockSync" + Witness SyncStage = "Witness" ) diff --git a/smt/pkg/db/mdbx.go b/smt/pkg/db/mdbx.go index adca963eaac..c3c642a1037 100644 --- a/smt/pkg/db/mdbx.go +++ b/smt/pkg/db/mdbx.go @@ -252,7 +252,7 @@ func (m *EriRoDb) GetKeySource(key utils.NodeKey) ([]byte, error) { } if data == nil { - return nil, fmt.Errorf("key %x not found", keyConc.Bytes()) + return nil, ErrNotFound } return data, nil diff --git a/smt/pkg/db/mem-db.go b/smt/pkg/db/mem-db.go index 949f267b402..bd45994628a 100644 --- a/smt/pkg/db/mem-db.go +++ b/smt/pkg/db/mem-db.go @@ -9,6 +9,10 @@ import ( "github.com/ledgerwatch/erigon/smt/pkg/utils" ) +var ( + ErrNotFound = fmt.Errorf("key not found") +) + type MemDb struct { Db map[string][]string DbAccVal map[string][]string @@ -184,7 +188,7 @@ func (m *MemDb) GetKeySource(key utils.NodeKey) ([]byte, error) { s, ok := m.DbKeySource[keyConc.String()] if !ok { - return nil, fmt.Errorf("key not found") + return nil, ErrNotFound } return s, nil @@ -224,7 +228,7 @@ func (m *MemDb) GetHashKey(key utils.NodeKey) (utils.NodeKey, error) { s, ok := m.DbHashKey[k] if !ok { - return utils.NodeKey{}, fmt.Errorf("key not found") + return utils.NodeKey{}, ErrNotFound } nv := big.NewInt(0).SetBytes(s) @@ -243,7 +247,7 @@ func (m *MemDb) GetCode(codeHash []byte) ([]byte, error) { s, ok := m.DbCode["0x"+hex.EncodeToString(codeHash)] if !ok { - return nil, fmt.Errorf("key not found") + return nil, ErrNotFound } return s, nil diff --git a/smt/pkg/smt/smt.go b/smt/pkg/smt/smt.go index 08c9b682250..6d541cc8914 100644 --- a/smt/pkg/smt/smt.go +++ b/smt/pkg/smt/smt.go @@ -718,7 +718,6 @@ func (s *RoSMT) traverse(ctx context.Context, node *big.Int, action TraverseActi childPrefix[len(prefix)] = byte(i) err := s.traverse(ctx, child.ToBigInt(), action, childPrefix) if err != nil { - fmt.Println(err) return err } } diff --git a/smt/pkg/smt/smt_utils.go b/smt/pkg/smt/smt_utils.go new file mode 100644 index 00000000000..aed504d1643 --- /dev/null +++ b/smt/pkg/smt/smt_utils.go @@ -0,0 +1,49 @@ +package smt + +import ( + "fmt" + + "github.com/ledgerwatch/erigon/smt/pkg/utils" +) + +var ( + ErrEmptySearchPath = fmt.Errorf("search path is empty") +) + +func (s *SMT) GetNodeAtPath(path []int) (nodeV *utils.NodeValue12, err error) { + pathLen := len(path) + if pathLen == 0 { + return nil, ErrEmptySearchPath + } + + var sl utils.NodeValue12 + + oldRoot, err := s.getLastRoot() + if err != nil { + return nil, fmt.Errorf("getLastRoot: %w", err) + } + + for level, pathByte := range path { + sl, err = s.Db.Get(oldRoot) + if err != nil { + return nil, err + } + + if sl.IsFinalNode() { + foundRKey := utils.NodeKeyFromBigIntArray(sl[0:4]) + if level < pathLen-1 || + foundRKey.GetPath()[0] != pathByte { + return nil, nil + } + + break + } else { + oldRoot = utils.NodeKeyFromBigIntArray(sl[pathByte*4 : pathByte*4+4]) + if oldRoot.IsZero() { + return nil, nil + } + } + } + + return &sl, nil +} diff --git a/smt/pkg/smt/smt_utils_test.go b/smt/pkg/smt/smt_utils_test.go new file mode 100644 index 00000000000..f30d1646bd5 --- /dev/null +++ b/smt/pkg/smt/smt_utils_test.go @@ -0,0 +1,92 @@ +package smt + +import ( + "math/big" + "testing" + + "github.com/ledgerwatch/erigon/smt/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func Test_DoesNodeExist(t *testing.T) { + tests := []struct { + name string + insertPaths [][]int + searchPath []int + expectedResult bool + expectedError error + }{ + { + name: "empty tree", + insertPaths: [][]int{}, + searchPath: []int{1}, + expectedResult: false, + expectedError: nil, + }, + { + name: "Search for empty path", + insertPaths: [][]int{{1}}, + searchPath: []int{}, + expectedResult: false, + expectedError: ErrEmptySearchPath, + }, + { + name: "Insert 1 node and search for it", + insertPaths: [][]int{{1}}, + searchPath: []int{1}, + expectedResult: true, + expectedError: nil, + }, + { + name: "Insert 1 node and search for the one next to it", + insertPaths: [][]int{{1}}, + searchPath: []int{0}, + expectedResult: false, + expectedError: nil, + }, + { + name: "Insert 2 nodes and search for the first one", + insertPaths: [][]int{{1}, {1, 1}}, + searchPath: []int{1}, + expectedResult: true, + expectedError: nil, + }, + { + name: "Insert 2 nodes and search for the second one", + insertPaths: [][]int{{1}, {1, 1}}, + searchPath: []int{1, 1}, + expectedResult: true, + expectedError: nil, + }, + { + name: "Search for node with longer path than the depth", + insertPaths: [][]int{{1}}, + searchPath: []int{1, 1}, + expectedResult: false, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewSMT(nil, false) + for _, insertPath := range tt.insertPaths { + fullPath := make([]int, 256) + copy(fullPath, insertPath) + nodeKey, err := utils.NodeKeyFromPath(fullPath) + assert.NoError(t, err, tt.name+": Failed to create node key from path ") + _, err = s.InsertKA(nodeKey, new(big.Int).SetUint64(1) /*arbitrary, not used in test*/) + assert.NoError(t, err, tt.name+": Failed to insert node") + } + + result, err := s.GetNodeAtPath(tt.searchPath) + if tt.expectedError != nil { + assert.Error(t, err, tt.name) + assert.Equal(t, tt.expectedError, err, tt.name) + } else { + assert.NoError(t, err, tt.name) + } + assert.Equal(t, tt.expectedResult, result != nil, tt.name) + }) + } +} diff --git a/smt/pkg/smt/witness.go b/smt/pkg/smt/witness.go index 5fc7d64e336..ef80f6ab3ed 100644 --- a/smt/pkg/smt/witness.go +++ b/smt/pkg/smt/witness.go @@ -5,17 +5,18 @@ import ( "fmt" "math/big" - libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/smt/pkg/db" "github.com/ledgerwatch/erigon/smt/pkg/utils" "github.com/ledgerwatch/erigon/turbo/trie" "github.com/status-im/keycard-go/hexutils" ) // BuildWitness creates a witness from the SMT -func BuildWitness(s *SMT, rd trie.RetainDecider, ctx context.Context) (*trie.Witness, error) { +func (s *RoSMT) BuildWitness(rd trie.RetainDecider, ctx context.Context) (*trie.Witness, error) { operands := make([]trie.WitnessOperator, 0) - root, err := s.Db.GetLastRoot() + root, err := s.DbRo.GetLastRoot() if err != nil { return nil, err } @@ -47,7 +48,7 @@ func BuildWitness(s *SMT, rd trie.RetainDecider, ctx context.Context) (*trie.Wit } if !retain { - h := libcommon.BigToHash(k.ToBigInt()) + h := common.BigToHash(k.ToBigInt()) hNode := trie.OperatorHash{Hash: h} operands = append(operands, &hNode) return false, nil @@ -55,12 +56,17 @@ func BuildWitness(s *SMT, rd trie.RetainDecider, ctx context.Context) (*trie.Wit } if v.IsFinalNode() { - actualK, err := s.Db.GetHashKey(k) - if err != nil { + actualK, err := s.DbRo.GetHashKey(k) + if err == db.ErrNotFound { + h := common.BigToHash(k.ToBigInt()) + hNode := trie.OperatorHash{Hash: h} + operands = append(operands, &hNode) + return false, nil + } else if err != nil { return false, err } - keySource, err := s.Db.GetKeySource(actualK) + keySource, err := s.DbRo.GetKeySource(actualK) if err != nil { return false, err } @@ -71,14 +77,14 @@ func BuildWitness(s *SMT, rd trie.RetainDecider, ctx context.Context) (*trie.Wit } valHash := v.Get4to8() - v, err := s.Db.Get(*valHash) + v, err := s.DbRo.Get(*valHash) if err != nil { return false, err } vInBytes := utils.ArrayBigToScalar(utils.BigIntArrayFromNodeValue8(v.GetNodeValue8())).Bytes() if t == utils.SC_CODE { - code, err := s.Db.GetCode(vInBytes) + code, err := s.DbRo.GetCode(vInBytes) if err != nil { return false, err } @@ -86,11 +92,15 @@ func BuildWitness(s *SMT, rd trie.RetainDecider, ctx context.Context) (*trie.Wit operands = append(operands, &trie.OperatorCode{Code: code}) } + storageKeyBytes := storage.Bytes() + if t != utils.SC_STORAGE { + storageKeyBytes = []byte{} + } // fmt.Printf("Node hash: %s, Node type: %d, address %x, storage %x, value %x\n", utils.ConvertBigIntToHex(k.ToBigInt()), t, addr, storage, utils.ArrayBigToScalar(value8).Bytes()) operands = append(operands, &trie.OperatorSMTLeafValue{ NodeType: uint8(t), Address: addr.Bytes(), - StorageKey: storage.Bytes(), + StorageKey: storageKeyBytes, Value: vInBytes, }) return false, nil @@ -118,10 +128,18 @@ func BuildWitness(s *SMT, rd trie.RetainDecider, ctx context.Context) (*trie.Wit } // BuildSMTfromWitness builds SMT from witness -func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { +func BuildSMTFromWitness(w *trie.Witness) (*SMT, error) { // using memdb s := NewSMT(nil, false) + if err := AddWitnessToSMT(s, w); err != nil { + return nil, fmt.Errorf("AddWitnessToSMT: %w", err) + } + + return s, nil +} + +func AddWitnessToSMT(s *SMT, w *trie.Witness) error { balanceMap := make(map[string]*big.Int) nonceMap := make(map[string]*big.Int) contractMap := make(map[string]string) @@ -135,7 +153,7 @@ func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { type nodeHash struct { path []int - hash libcommon.Hash + hash common.Hash } nodeHashes := make([]nodeHash, 0) @@ -144,8 +162,7 @@ func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { switch op := operator.(type) { case *trie.OperatorSMTLeafValue: valScaler := big.NewInt(0).SetBytes(op.Value) - addr := libcommon.BytesToAddress(op.Address) - + addr := common.BytesToAddress(op.Address) switch op.NodeType { case utils.KEY_BALANCE: balanceMap[addr.String()] = valScaler @@ -165,7 +182,6 @@ func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { storageMap[addr.String()][stKey] = valScaler.String() } - path = path[:len(path)-1] NodeChildCountMap[intArrayToString(path)] += 1 @@ -177,12 +193,12 @@ func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { } case *trie.OperatorCode: - addr := libcommon.BytesToAddress(w.Operators[i+1].(*trie.OperatorSMTLeafValue).Address) + addr := common.BytesToAddress(w.Operators[i+1].(*trie.OperatorSMTLeafValue).Address) code := hexutils.BytesToHex(op.Code) if len(code) > 0 { if err := s.Db.AddCode(hexutils.HexToBytes(code)); err != nil { - return nil, err + return err } code = fmt.Sprintf("0x%s", code) } @@ -212,7 +228,6 @@ func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { pathCopy := make([]int, len(path)) copy(pathCopy, path) nodeHashes = append(nodeHashes, nodeHash{path: pathCopy, hash: op.Hash}) - path = path[:len(path)-1] NodeChildCountMap[intArrayToString(path)] += 1 @@ -225,57 +240,52 @@ func BuildSMTfromWitness(w *trie.Witness) (*SMT, error) { default: // Unsupported operator type - return nil, fmt.Errorf("unsupported operator type: %T", op) + return fmt.Errorf("unsupported operator type: %T", op) } } for _, nodeHash := range nodeHashes { - _, err := s.InsertHashNode(nodeHash.path, nodeHash.hash.Big()) + // should not replace with hash node if there are nodes under it on the current smt + // we would lose needed data i we replace it with a hash node + node, err := s.GetNodeAtPath(nodeHash.path) if err != nil { - return nil, err + return fmt.Errorf("GetNodeAtPath: %w", err) + } + if node != nil { + continue + } + if _, err := s.InsertHashNode(nodeHash.path, nodeHash.hash.Big()); err != nil { + return fmt.Errorf("InsertHashNode: %w", err) } - _, err = s.Db.GetLastRoot() - if err != nil { - return nil, err + if _, err = s.Db.GetLastRoot(); err != nil { + return fmt.Errorf("GetLastRoot: %w", err) } } for addr, balance := range balanceMap { - _, err := s.SetAccountBalance(addr, balance) - if err != nil { - return nil, err + if _, err := s.SetAccountBalance(addr, balance); err != nil { + return fmt.Errorf("SetAccountBalance: %w", err) } } for addr, nonce := range nonceMap { - _, err := s.SetAccountNonce(addr, nonce) - if err != nil { - return nil, err + if _, err := s.SetAccountNonce(addr, nonce); err != nil { + return fmt.Errorf("SetAccountNonce: %w", err) } } for addr, code := range contractMap { - err := s.SetContractBytecode(addr, code) - if err != nil { - return nil, err + if err := s.SetContractBytecode(addr, code); err != nil { + return fmt.Errorf("SetContractBytecode: %w", err) } } for addr, storage := range storageMap { - _, err := s.SetContractStorage(addr, storage, nil) - if err != nil { - fmt.Println("error : unable to set contract storage", err) + if _, err := s.SetContractStorage(addr, storage, nil); err != nil { + return fmt.Errorf("SetContractStorage: %w", err) } } - return s, nil -} - -func intArrayToString(a []int) string { - s := "" - for _, v := range a { - s += fmt.Sprintf("%d", v) - } - return s + return nil } diff --git a/smt/pkg/smt/witness_test.go b/smt/pkg/smt/witness_test.go index 87dae548915..6d3415214f5 100644 --- a/smt/pkg/smt/witness_test.go +++ b/smt/pkg/smt/witness_test.go @@ -17,6 +17,7 @@ import ( "github.com/ledgerwatch/erigon/smt/pkg/utils" "github.com/ledgerwatch/erigon/turbo/trie" "github.com/stretchr/testify/require" + "gotest.tools/v3/assert" ) func prepareSMT(t *testing.T) (*smt.SMT, *trie.RetainList) { @@ -31,7 +32,7 @@ func prepareSMT(t *testing.T) (*smt.SMT, *trie.RetainList) { tds := state.NewTrieDbState(libcommon.Hash{}, tx, 0, state.NewPlainStateReader(tx)) - w := tds.TrieStateWriter() + w := tds.NewTrieStateWriter() intraBlockState := state.New(tds) @@ -46,7 +47,7 @@ func prepareSMT(t *testing.T) (*smt.SMT, *trie.RetainList) { intraBlockState.AddBalance(contract, balance) intraBlockState.SetState(contract, &sKey, *sVal) - err := intraBlockState.FinalizeTx(&chain.Rules{}, tds.TrieStateWriter()) + err := intraBlockState.FinalizeTx(&chain.Rules{}, tds.NewTrieStateWriter()) require.NoError(t, err, "error finalising 1st tx") err = intraBlockState.CommitBlock(&chain.Rules{}, w) @@ -112,7 +113,7 @@ func TestSMTWitnessRetainList(t *testing.T) { sKey := libcommon.HexToHash("0x5") sVal := uint256.NewInt(0xdeadbeef) - witness, err := smt.BuildWitness(smtTrie, rl, context.Background()) + witness, err := smtTrie.BuildWitness(rl, context.Background()) require.NoError(t, err, "error building witness") foundCode := findNode(t, witness, contract, libcommon.Hash{}, utils.SC_CODE) @@ -139,7 +140,7 @@ func TestSMTWitnessRetainListEmptyVal(t *testing.T) { _, err := smtTrie.SetAccountState(contract.String(), balance.ToBig(), uint256.NewInt(0).ToBig()) require.NoError(t, err) - witness, err := smt.BuildWitness(smtTrie, rl, context.Background()) + witness, err := smtTrie.BuildWitness(rl, context.Background()) require.NoError(t, err, "error building witness") foundCode := findNode(t, witness, contract, libcommon.Hash{}, utils.SC_CODE) @@ -160,10 +161,10 @@ func TestSMTWitnessRetainListEmptyVal(t *testing.T) { func TestWitnessToSMT(t *testing.T) { smtTrie, rl := prepareSMT(t) - witness, err := smt.BuildWitness(smtTrie, rl, context.Background()) + witness, err := smtTrie.BuildWitness(rl, context.Background()) require.NoError(t, err, "error building witness") - newSMT, err := smt.BuildSMTfromWitness(witness) + newSMT, err := smt.BuildSMTFromWitness(witness) require.NoError(t, err, "error building SMT from witness") root, err := newSMT.Db.GetLastRoot() @@ -190,12 +191,15 @@ func TestWitnessToSMTStateReader(t *testing.T) { expectedRoot, err := smtTrie.Db.GetLastRoot() require.NoError(t, err, "error getting last root") - witness, err := smt.BuildWitness(smtTrie, rl, context.Background()) + witness, err := smtTrie.BuildWitness(rl, context.Background()) require.NoError(t, err, "error building witness") - newSMT, err := smt.BuildSMTfromWitness(witness) + newSMT, err := smt.BuildSMTFromWitness(witness) require.NoError(t, err, "error building SMT from witness") + _, err = newSMT.BuildWitness(rl, context.Background()) + require.NoError(t, err, "error rebuilding witness") + root, err := newSMT.Db.GetLastRoot() require.NoError(t, err, "error getting the last root from db") @@ -239,3 +243,29 @@ func TestWitnessToSMTStateReader(t *testing.T) { // assert that the storage value is the same require.Equal(t, expectedStorageValue, newStorageValue) } + +func TestBlockWitnessLarge(t *testing.T) { + witnessBytes, err := hex.DecodeString(smt.Witness1) + require.NoError(t, err, "error decoding witness") + + w, err := trie.NewWitnessFromReader(bytes.NewReader(witnessBytes), false /* trace */) + if err != nil { + t.Error(err) + } + + smt1, err := smt.BuildSMTFromWitness(w) + require.NoError(t, err, "Could not restore trie from the block witness: %v", err) + + rl := &trie.AlwaysTrueRetainDecider{} + w2, err := smt1.BuildWitness(rl, context.Background()) + require.NoError(t, err, "error building witness") + + //create writer + var buff bytes.Buffer + w.WriteDiff(w2, &buff) + diff := buff.String() + if len(diff) > 0 { + fmt.Println(diff) + } + assert.Equal(t, 0, len(diff), "witnesses should be equal") +} diff --git a/smt/pkg/smt/witness_test_data.go b/smt/pkg/smt/witness_test_data.go new file mode 100644 index 00000000000..fab6aff4732 --- /dev/null +++ b/smt/pkg/smt/witness_test_data.go @@ -0,0 +1,6 @@ +package smt + +var ( + Witness1 = "0102030203020302030203020303ddd15247a8b234236d91271277b1059a674eaed56c29a6d8905b27ea9460c7e40344f7576ca6198b0bb6daa81b4eb6f594b46608e0f4d8d509361f0aac88eed2b50203020302030203020302030203037477c5b7ac361fa5a28f01782fc1b9577dfe27c9d91e5193c426916c166503f3033e6831fb92c6944c4869e9ff429fd40b9191f5a5a9fd8e4e26f67be29feb3d00020302030310c0064663f729ce8c12a4db054317ae8a3d309ee54378eba25ca39a4670758d03fa715595952a40ebcc9c06b02f6b1960a1f74a722c3a9fecba1aa66f32f1850e0203020303b010b79cdf4c9bd8f8164ad282defed968658e80fa57c26c19f5cadcfd9c890e0318f8d37b605fba62e9bd02f5554b8bd4784578021c737c4cb957c4ed5e8ad3b5020302030203020303c4ac3ac799860160a30a3304b765c2c90bc414edc3739a5d098bb7e18009548a0344ed07cf7b7b49fc2e7fc9c6c19d1b60e64990110188e15b445320a35660f91d02030203020303949f805ade2be05694c8011fa17fab3646a43f38f96d868386f0ba9558ba5f960302aabd9fbeceb9711f46d634513830181412c8405aea579f470a19b477d090140203020303db978a462b93b2efa3aa3da09e03370b570db692c6d361d52ae1051bdb26a3a903916d67432c505e1dc33f3617e0743d761aba44785726309191e79cb18b666e7402030203033edca13bcadc1db9305f3b15322cc6d774682fffdfe2b509f81d00b16ce2dcd003dc94780e238944094e7856154e6d3e54fec28293a9a70eaf1cc2a81e874e22170203020302010203070354000000000000000000000000000000005ca1ab1e5820e72de8a1b9696dd30f7886b15c4cc9234d52c6b41b9c33e2baaf8d88fc5b7c9f5820f8fb80310ac041e7a5e79c138d7261cda5d8a988dc9268b5a8dc5318fb610a90070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000358207d7b0aec16983b640324af57c161ae800ab5b0b61937d153540fd64ba724a431020303c6cbb686c9d7a94f49dfbee076ae1b87f1c9bb5f33b7c98a71816c33b4731a3b037514c2021b2bb805e2a6060b265dd53c069a4587e19cd7d1af99d0e9c3d0e550020303784570292bffbc3ee00153e5806a317459fed4c1d84de0515dcefc177404865003f08c92f6786e67148e8fb2bcd4eb813a665e16a270b475605d1d84b587450ff102030344c5e2a1775873020ab4e5a588e95d6702878cd46012682196dc39738fd8780703a6d5ee17fe3be7e20050e4e66c54b5188febbdd3615f832c35b073078258b214020303e5f90b59ef9f5ceee0e0a54551e41a62431ea06aa09be94779c474ca4d18683403e794dec8b1cbcd53bbecf14b61869699ed3f92ecbb4ac3d9a8bc744c09a3e69a020303476c478891a8f8d905ebf9e5c031ba1020ca1436538bf9b97c6eaa1b9512da97030e077b0c8215c8c43753d044e318788eb8e39de692fe0ccd46396d3b06ca6e0c020303ddaa569a922c9c28d9a225a4c212c076ad5e353bb7cceaab630a3884af855d2403d5ff4c122142c026a0b24b79b4a667d25ea916ef64d8a8215aa29a738c5588a50203034b1d465e96a44ba0d7983a6f4ce10a26bce7816b6d51ba8ac594c71892cc2af60381a6db28188e1b651603d41fbc2030bb2b7706e02b1eb3423d7f38ff6ef514e6020303f30f3c3ad2db979a1c81690619a35a801e3bcd77413f37e285b0011f2b6e2a4003239d1f94c6460af24c7228a2af86326ea1199e97365bf7dc5832ad029107445f0203038518fa303494de83c9ae1f80c877b5c0e6dba41880f6df1dbaaff30fa9b9c37a03653c1b2e876da5bd8b6535ce431ae69feb7be788cc67b2fa3dbff11c792c1f13020303d5efbfce398f4205569b3fc872e7405712796b7189d6846e61c7ff33a12ab0c5037aeb2da8a9e504490ac07aee509079823397fc6e9cd25257e658f6e0021ae771020302030203033bfe86ca5a55d4d2d42f5af48205ca0ab08df68e551e61b9a1bd5d575ff9cac3037462982abd4a0437ab5e12ab2af263ab382e0ceba69ff5de751519512149c70a0203020303980043fe396689718e09b0990d71b800219da2873a8e0c3c45d25ffe12bd9e6003f2f9aba950a1023ef8e02568c683c86ef2e77e16dfad909642ddc5cc57ac8c120203020303738b4a16af664d0e0c6b7ff278d1e3b602e6277085730d77844f1430c2f71bcd032c505136023a2005bd6b8abfc49eb783514ea36233d5439525478dc102ad67e402030203020303f30cfa6f63115cc17d752bd07a3848c463334bdf554ffeb5a57f2ac2535c4650037d85b4ea9025d3512a6fafe55d8e3570fc8c968eb67042e0ded283dcadc12ae8020302030351a20a2e192372b9383e5b8ef255adf58a3633e5aa4161424f7b52912e8053f603edc4f75f70c3608079c9f0b4584da6270879e9983bb3513d7e620024f15e659f02030203037e1734c6c90368548b9b6a882b4560d78e0630f3616dc7d4b4b8d77b96a42dbf03c4ed6f8e6cdc9797199a463a51287700852a10099a1386109a37561b538d228502030203020303d3858acf0781afe0adae49a25d1724b36c9989179cc884b9a9c6481f89e57706031da6fb50879ede58d816d1624292f0c60a8133bcbc671dd92d0f9cb8d50fc8030203020303521bd9da187efcbab07451097baf98589a33e32cd33501c5a912f48cf2552bef0352124f3ffee53f7e0f9a0068d5c0b0abfca5aaa148371c91e2e81df5fba6f8bf0203020303e00ce2232f3e208dcf050f887d02c7b91170c4b98e1d098ec5238bb3d387a41e038a0379dab30ce84865bb4f0834a9c3fd7bb2da17994abf03e89fd8d754bf7aab0203020302030333f5853903cb36caedffa12d0aa0a01c39e91309629a97dddafa8da4f738fb3e038e1dc6012aecd5998053b5878c6e3a398c8a286c7ad4dc0b55f043e4b4210950020302030317e041d91830fe8051fc73631cfd56519fc5bb88b5369298da9807b40d93733703dc7745bf8dfa058bcf1328f79efc9441cf5ad5fb5763c75bdebf9492f88a6c8302030203020303bf9a2780e63ac26ffca6df07f182db72e3e65802b277ea6d40057c383d5a37e3036c1548eb1c956ece0a876fff4463cc3f2e9c3b6eef88379f07e6d71013eb4aad020302030202020103c2eebac9e260dad1f051fa75846558dcc0ed120d1a7699fd1d6b739a3419015b020103b9d64db96b69b035a74a90069691afa2b34a705f3ad3aa82f3757c7a6f2a9f17070354000000000000000000000000000000005ca1ab1e58207af492bfc857210d76ff8398a35a942af892e866b1f4c241746b3ee89ca002595820f889596e5b3d4dbe5bcab5cde2af26d3ad8d88bc086e8b4f929885f33f6eec77020303cd3edcf79c7e0e0d6b87ae523729611faeda92e77bbb59f739e9a6127d890cdf03009a5c01c3cb27d2f1ffbd3fd77ff38f66991f64174f5df1411ea21ae2e22f250203035334694d8b56a2f5e0d0ded81283e7f36b3da6fbf2e2d1b469c008d7414296be03953388b0cacc587c5ca452ba8e96a9071958ef94439690bc14866f556a35ebc1020303227561f72db4ee550290a7b85931038224b1fa9c351395f5f5777f397016d7ae03dde5f312229c20faf5b1b27273112bc022bd0d1dad4195ffeeceb49c05001a07020303d4eebbde54471ef4008ea3e23e4bd31119b1d4fa51a2bce7771c95b70efba064038c6a2b8e1f68d72b2a95ef69cd8eb0ab32781e7687049eaf3b7381596c0bb8af0203036ae82b7b420a58afe9871a632d69be8475f745405df2183722c599f94a5cf15f038a575afe8d81ea9f181bee15a971affeffcb1964ed35ec291304be393899d80f02030203020302030203020303d634ac486eb2f4e325a096c1aac56ae5a0a3bba406dcbede2e9bd4837d1759f203ce1b43774de78b19d67b133fb575ead398fae6a712ebd63e26671f199c8e674302030203020302030203036068b215f89f68246518e7d8c967f8ae78b47c69bcb9e97deca5849a813b2e400384b630ffc67a1dd7b502c1b42165171a704d68ed15ced3b7cbb98bd150cd884b020302030203020303a3c7cf45ebdd7e21dade3434624c9fd521b0ab24a6956e3b8a777d700149806703b3637d0b1cf58c5272f28f8354a764d3cd48ff7c04f807237da8f4a1e2ef5db5020302030203020302030203020303c018dfe606efd5d17f3d45e91d33d3d7ef57d92a1d509291b1556bbb7e78dd0803b08ff5bb304aa8741af608415c541440edcd98bbc0fc849fe2a89c2c783341d502030203031e0eb0ac5664b1d0267d3c51dd66d1828c0d01a0903d599a317e4578926d4e3503330e2ccc546a3db52e796943aa8960d6d483a3b941ae0caa21cc7b9f7a3c2bbc070354a40d5f56745a118d0906a34e69aec8c0db1cb8fa5820000000000000000000000000000000000000000000000000000000000000000158206191319cb3bf48d9701195789dbbf6db5d3b99006317f5e7da37709f3d259374020303ac874a6acbf6de628134cd74ad9f336206e7aadb1ef09456b6267a770612485703ef323528b761720ce04927a54b81025a935f070420d655217e40eb2e084bd170020303a48199a63a429cf44fb39fdbb73098a49dc171e03d32800f483780adb9aa06580388796e8ab2076fc77f00a5096317ceff8a54da310b014a0310504bcd76f8b8da02030350179cb850b147782f26ff9a17895259e569b740cd6424a7a1479602bd8c822b0371b277886f0d14b6f82cfd063ecddab10fb5da5e0666e040992469d09a6bc8b0020303dee2b54587eeb7db2fe9ef0dc262b6ae679a5bfff89c8f403d181a1d79107b1d032aff27f522ef5fd88213c3865e01c7b4c1720d56778d1bd0e48e6a86fb3b07970203037d0d29240ad72800831a91d8e54019da745c6c6a630a625167723ace857bbb81031ede6b255a1c6ffd6fa2afc16d61aea6555a5cb85dc4669070b69b55a16ac58d020303335f1f02ebdb1926380c362d23b2d90d791f5ec8531287a47d3a1929d6304f1b037b80208ab1e9bc0411f128ccc859ac552945a650ebd0f9161a63fc9944e8d43f0203030d600bfcd6581d405aaed26aa7cee976fbb2bb9c1c1390bd3eb14cf5f6610223030bc731957e48cd1b0f92521fa971ca65f76bc8608eaddfa5243baf39838099810203034b176bbe991dbc4ae5738f23e31433597f9b892730ad4fdc86784eb928cbc642035fa76b91882dff00646e99735fb6accf89476e59c9dd28e0bc7b617870cc15e702030309cc884c89c3b546aecc35f4a95760a100cc3fb5457a82fc413ee2cd795345d2037ac7f4c6f9dc0e8f652f47fbda5c4b53428948acc95270be462ef8c909e5b742020303bb4f79f1339f6fc4ba3b4c61ff1940c27b29459942791fd7b160a04bc8aa411803628f665215c8c44bda1a6243487e35e5d9d922bf36f1976fd5f6c39264d16e8b0203036bdcb7f00d848df36ea8ffe2d419775be23396cb7344a2dd1ab44292769c922303710d1c2ccfa13ceec5ffc97b3469592c4f2495141e46bbaaae6f099c47b9737502030203035f97c209aeacbb5fc78e69d247a800528f4bcc5e649fdec626ae5ef510ee7a71036eeb37a43ca943f0bb09cb54bfcc7325ed27e97af16f59cab0822f88d3143888020302030373d9994e2d75a6f80adb914f45d533caf2ade8d952a0d8b73a40299199892f5f0347947690bda8388fbc8744af22af00157531bd0f37353b2407b573cff34e23c20203020303cd4c5dc3e51e3a3379cf73004c787ee7cb312c06c70800d0f08e65a0ee2313c40350adbcaba1f1a5b06ae4510704194cefdb5053ffacdca11f354a80cc04d0a2f402030203037ec1e64855ec72f6c39f1832616a45075eda4889495c393ffb673aa05f25e67d0361c764032c6e6f093f7e4e2db6e3324b29e59ee4df2f6df3536539ea135264cc02030203020302030325abb132a4c897744752a4707644448c653f00743c37cd560218074dfe1e4d2803fe62ee54fd13cf254cb8c3b2bf728d8c26703054588e529bb8b2a68a950ea4e0020302030203020303846e32cbe73ce37fdc6fb93afeed4425035df35d637127b54b8fc4c053d405ff0398c008e116cd33ceac2a28f29c392e533b755c24316cf6e847e4ef72b070dcc602030203037cc278f9b41fd17fb5eb3c839a725fcd1ef6000189fcebcb4214303f45dcd2d60386c3bc64da300f1a87efa2eb2724553e41348057fc99d5c23b5b20e216fde46d020302030376bf2ddfddca9910df80bb0785add76937d1e90e029c02b04c0cf421622a232803ba2219bc37e93a89b0effdfc3601f58645c1cb7e818f2254c8fd16fea4ba84440203020303fdf1c4799edef5fe2960f9148627fff521e591247a224eb8d05eae3f51675b560372adafa8e298a14d0da0a71e645a12a23def78db8e81f7a68ef92aac7d5700b40203020303f5a794d38718b283b47993f3bbcd67c76f84c47fcf2d35373fcb7f8a0f43a06b03a14b12d1f03790ac75797e463a8a7edcfb2bc80b65a7dc8d1b15d00cefb315d5020302010312f8462573dc83d436d2498e68019babdcc911f482e1e00c1b3fda70e1a166e40203020303adcfa2154e38e2cdbafd5d56bdaa5dca90a5bfb9c36bfbe140bb31ec0e66716503b5aaf1a6fa2e80ad8f4e49c51808d2898fd74f539ec5de974b57c27466f5e7490203070354000000000000000000000000000000005ca1ab1e58205956a0b12f607189a063054545ab26ce76ea5eb4c9bc1e8d8161646c93ac66515820da6aba51eaf87e14a7585e52e23cc0b789c61b3e808d2aef704ae932bb2ab49d070354ee5a4826068c5326a7f06fd6c7cbf816f096846c5820701c251f0448beefca8b47dce2e42f136d224b8e89e4900d24681e46a70e7448510237426c03237429400000000067445fb80203035b2ba27d2c4b5ccd82a303efb2a86cf208d08dd952ed0494acc5aff009a9809303e498dca26358e5fd56e5464288da82073a17cbbd112e322488c12bff1661b49b02030308e7f519768ebddac099b4d79b3da7e528e3e1e7f43fb5e815cc2e7e9bdb82ca03afa06c5681e457eed414f2a781c5cf2a752257a696aa0d741799d3e5c6ac65b6020303645cd7c283714f070784b2e6a24c049ba20cc01422792ec7507818b757b4d02103c6b1fb9b858f69c598cf4359d1a43ec9faa45a7f308dfcf087b529ecf6cba0d702030364631f25391237453ea4bdf6dcd59ec33334c8bf13b3f4ebc24512193a52368203d4a0ec404056d0dd6b14481bda731e46954f9d29e4d43aba64bb8ca52ca87bd902030329cd1de4c7edfcc761f57c5f2466970add88bd8705390cb23184091c99cbdde603eca27d7686e41e3d24558d66cbc83d2a5d35469522d922ab216914a84d977e36020303aa3f3aaee4ea8cc05d8b5a9f3c4528c8de0d5b4bd9bedd4456b8816a9d7195da036dee15633cb92bdefc8f632e08b85dcb8bf1d317f82dfcbb7b76e38f7421361502030203020303f1a4bc7768286c3e023725e4f781a6b00fb11d83f1dda2647000f13ca3c58544035e062fcd2f3f81c8d4d424e25bf7e77e301465425a25afa5d0bdbeee2c6284b202030203038482b5d9958175078c790a8a1effda8f156db2faa9ff2d6473d742b4f737143903bb264f8b66371fe289f7741ae353b137695ca94cbc9ed3ececd3ef601d54181d02030203020303b9a21b649304cec7a5d6d0464d6bd8ddffb475c672c0c9799b3457e4b9fc2a12038da99cc78f04ba4eaf3df326eeb15cb038c013a9e5b76698f493170bd356b13a020302030203039c69fd3c2b5b5200c89358d29432ddc4cdadbf9d1b05f2265bf4af27d968898503389f85ccddd9ba507ac3bae9f0a830a56eaf35ebde5aeb6c374dadfd0ab39aa9020302030318b62235f3bd9e0e268b30ff1a987af5548f00006ebcf51db0448e220c17e862034465f83c3781a2e121eca23c852e6b742e52e0fd76e2eaf886471d3f5c4a3e8502030203038b2faefda31a8d8e3e5590221ea164997bdaaba30fed699932fa0b65c6ab2fda0396915914ec53b6ea1fea28b0ede76ab410d1dafbf996f2fa7cd37f1b4ddeb59e020302030203020303455b9202298fcd235ea441cc50f29ce15a2a1a9504564159a849211c899dc08003f3df85b9d03df952c76c1f9853ce686f21949c732fc9b161b5759faa36b2cd55020302030203020303508d990b34daf5a3f925c435daac3d293f6d861094cc2d343a92c62428fa66da032f8b40a9211667e9c44328d6440091ecb3a46bc15832f7d7cdfa8ec130b527fc0203020303f993f7eae6e45a6f8557c6c5d0e912cb41b71d2bf37f38affc0b2d8e054193220315eeb3ab754628ce727cd0b7028ff8ed3291de7566b99066e127185d043f595702030203032f2c132f32f21e267ab64271e8f2c0c39fedbcc509c4589616cffec21d7332eb03839857347599c19c43a0acfe53e1bb5bbe0d68ddb49cee05f1b24c5acac24a150203020303dcd0869ad1107856680f6bf164623fc709d26d1a0842bb9c60a383f255c0ec2403c92cb1692742c4e2b6a91d13c3b371a9dccd29f898d8f6457ad052b1da9efcf6020302030203031408a1feb1c4cefd2e71c1d7ce58e6b4c2d139d48c67037d40dc0d60390af539039c51675ab13cc260ab6875b12824ed60903c1755add14024e27508ac0a3b9d81020102030376fdbe16ba7e2f048d9c311cb1f99291b4f624717ddd7e9f2aa653099d19314f032ebe85ea3fef7c7033338d1ed98e187eddf75dff4772a23e19392ce61690f77f020303f901f2ba5a7a95db9ea7106268f17f341206944377d1f006921211069cf8a0a103f43daf24401f9ed2d0691570a8ccdcd016c90b722786ff590276f7cd5933ff3d0203033cb8f613c530196a2ab151996cc3eb343199c5c0c0adc212268f74f6a092666c0355b4984426bd4db31ca5d70798a18280a4d319786bd897a29365d2db7489b32d020303d7f9465d351f2c4200659307b3cd7cf34d3fea84b9b23bffe5bec395f4a2d88a03ef3e9f16053b7f799f207451eb3403eb95301e9c9e721dfde0c41ebd8362485c0203036152db0543c6381b557c70b284c75fe77b405b54d279a37db0ea5a382a61abd603b70663772bf3728213f272a0d02b2ded9cd31441fbb82b9a44d266c56e7fdf58020303f967722c10537809246d339e984382cc197deea70a2c433df88fd7797701dc76036e06014c6d6c4d1358aefacae43b83631ffbbb39c93874faa4d589c1f60ca07302030341a2496071d2a84dec9f60bfd3288fdcf01683618900806b1a61a740fcb95d4b0338cf0dcf2e49a0359d0d543a3ac97474876f7605800e270d1c8671dc375720250203034a347b6bdf9e875c714c0790a2ad84b01edf7b15c4d23cacab0598c704417ea7039676ef3f389061effccb4e08a0afc2971c35bf69edbda2e91d9e88486113990e02030203020303927b20cc65cbc0d70e9880b16dfc67b8379ff4a96b95309302803a1819d95ea003eb0ebe2fcfd0a9002bd0985e47dac1c4a01561de0da69bea0bc25ff1b519d5b602030203020303a4c7f2025180b6de7674fc2c91392a565d9a28a77eb193f29d9ba706c6fdb42f03d2bca54ba531de1142b06bb35aed010d55ab6e0d862cdd7807e4136c1b9d0c490203020303ec1282aa791a0b578de360336d5cf95a7f3bf1ffda9cf697b3aacf9417aa38ad03cece3331be90852d59eb04e3bc87b03657c0993626d3e36ebeef97baedd928f00203020303afb305376ba08f5bfaced38f127295f994096684417f9de1a8f496fdccbb3547036bf14c6051f3bdb18d621bed206c3ceb8daf8ec24843921de9af2dc2ba70d5ba0203020303122291009057e848a0e15edd72e47061463ab3aee368289eddc782303e9299cd03678ace78eb9da91eb3fa9105c9969a0aa9abd66ac41ab138aa70346daadd327002030203020302030203020302030203037a46bc17ebfbc47f6d99661de00074c9958e0f7fd66df7c77c236b89b165472e034b58bfe7c7506d2891367c270ca350269dfc0a08b7466ec2496c6330dd602bb302030203039b58b0df7fae59a4cef25184d849214bc145cda115b2c0dfd85fd470ecdea70f0330923d4d299efbc138d4442519d30cd33a7827557231388b81a6ea9c65eabe6f0203020303af3fee608c2e8e5a30ffc6345d86ec1b2d55f10e518b4da5e8eb59abde07b59803c2016682405d3a953eba254601d5fc0b8966a33efaa51918a4a41b8e0acbeb4602030203034b1387aa6d0ab944e2ec65ce38c8643a0ddfca5c3059718f398dee501291569603528cbab25216c4397a402fcb572f0b512a773dfeafa59e401989a4da13406bfe02030203070354000000000000000000000000000000005ca1ab1e582054b6c4d9862a1658dedebe99a0f61d94c5d1515fd031d0dfe9ebce6a1454f5c658203f14693500ccd0260659fd9eaf69570edc0504867134ac88f871d91d388b63690203070354914e7547b9051ea6226c30495190a2efa15930c95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927548382be7cc5c2cd8b14f44108444ced6745c5fecb02030311ab6695ec969171698c1f56d4c05373d8505a2c7299fb05cda1d4351e22bfa403478b94ae515fbd01728835b532c7c45ccc78d200d3d004da6917337e139eb729020303ffd14369e7c7f7aec3a890a20234885f2c9fb9802ec318d8434ebcd58a696153030ddae742090ea458c3f232dc894bd8cd3378b4b4590a0523e09a44e0439fe0db020303b1688d8c7806365d931579ccac8dbf7a8d7705ac393159dfd9c0395ab7b5ca5b036a6c978a565b15267de4330de7b6166014082043c5cc80370953767ac501ccf2020303fd878c58bb70337606fc9f519700dcabaee2f175ffd956a6d246c56e38de3c5a034ece3162b251497a52be7f417b99722c20de63b35a0387e0cb1d8a1ef6bd34190203032db40fdeb2c5256d5a237b6134f844646b325bfc12c687916327e21a65b1ae6a03381ca5f3231b0698c6d69bd685fd1930924395002ee0c9f1f3dc9324570c4f52020303eaf50a55e8bc433b8c594aeb6ce2dff8d6dc8a6fa7d72076a07d6b771d13d78b0311a2827108d1c853cd8a63db81104ad8493e188969ca0339d0a01ed043b47cdd020303f7993cfe3bc67991b923b2f3470e42e23e78ad30096bf8278f293053de07b46703a0c8d263334a785d55f5b7be433841bca1d7ef1b8743e6dacb4e4fdffc52a77a020303b616a1ceb3607803c41329eee93ec3541b2ebbe690a4f29e3234441d7fe22710033646798b76e3f8d1cdcef03b5802388ad826a45b0ba508443fa26d5cd6fca96602030320f9766d80286663ec273eaab27d516a59305f6fdb96957af2602f4c0eef4b8a031c930c476ddc908dc2d5ec253fdd2c6687f32616ae7698ee6f0f6baee9871f780203032d1c40f0360f2f347afb931f1caff15b122c02dd058d53cd31e10da5eb3a5005038da2ec93073400637eda663a2b3095ba8bcf473b6bc0ddba6732c0d88ea26f0402030203020302030349147352cccb7f2119bbfbbbb0a4306ee33992973d1777a3f176a7420854218003a44f6acf78a34c96774821d091ce968f756f12c95ad543c97e52f1c041e5c1900203020303a8a8350630628c9ac16ce93f256b9d92a9cab6a1144cd60fee0f228e02d0d04403fd17945ef7c2a783662ce43c34c9e7ff044bcfb5a3fb24299f994e4317c620010203020303cec89e1b5d9d20c59a319c536ef1ae8c0a67e0ded8f6ce3a7eb1979ef183d3870348dbae09afb5d5232bc158cd3f9c2728348ae93f0742e0d71971d3b26c301c0c020302030376e9a3f309a69b0c2c7ca3184457ba9f0ce19145bc96d4bd846742585cf4e9a903b07dbe4dab435161a33e90e991fdd8ac5c0670d77cf2b72ae5bc530519e6fbaf020302030203020303c423e16fb3487a4d9126ad5c533cf130444a4f099a85777493cbd2d231f27b71033c4bbd0160fa760c6c008ce70d7342f2cd5524690247577a0ca36e15528565cd02030203031e5c59c8eb7467fe1b1b59f78351143028717a9679b5956d1a42ab64efbbdff403bc2db4433eb1e4eb520035e06ee46cdd233cd6f74e4ce438a0743af21cf67ba10203020303c1da641e5501813afe9c4653f2179710154bfe94ebce827d0bf64d70bd3baf7a03e2bf953702f6287b134eee978e1b18a36f65b41c2c673d75876215604661dd50020302030203020303a35901b035cd24570a277362d9ece906ef4d6e00821b55212d69b6fd6775472d037568928f5eecc9599b391e6cb75468d91ac18de51d7e984eb678105c39fc8a4a0203020303791a9ee8b5057a6ca65118869d354dba135fd5c518d63144d3860987f084bbcb033c8c390d481f51cf6b43c22677a971beae0e62e8b2ecfdaaed05b48ac0f60294020302030203020302030203070054000000000000000000000000000000005ca1ab1e45e8d4a5100002010341305ecddd1b56329ac9f09a1235eec6ce6be69492a9788db13e9187dc21e9dc020303fb1c6d1aa6d3f3bef7a0bf4130218b3b168f9447e69ebcd3b68c2b2f41d9b2ef03652ba6f9b69aee3d28404079416c2f8fba4078d66b558c7a8d9615cfe7d3bd30020303d9f042d0d2f152e24d8cde02d3a7d7a1fa234efc5dc259572d412a2e607215ba03c5b76ff595e1d74a22eb44a5aed94f3225b6126c2c28ef04bb75e1d3804925ad02030314a2b125da4db5ba673cd5c0aaae8c5bf0857fd45728b868cff3f40eaf9f82790393e93c4f4b58f6f9d397d136319a29aa6b691b652651513bfc2297107379ce62020303f00359907dd68b2ae8e2d252d3313f3ba2bba16d21995333b2162b24c9bbeac4036435af585f0f75e60d362629108f6768756f7b39f1c70ab7f79e6b4e1bd9f08f020303929e2f8eb833089a3773b497247338865ef336de61e7da4a362eb3e5d5601a7203323197b010e3205d910c230463758f39cd6c01258db0a11b9b47f4c278db049402030328314d2f79ba26dc4f34afce51e50e0e05d61b253861e5ab47cc47dab500310e038502bfdf255197b6c7929c445580eddc7013470aa85f531e89cd595628576ef6020303295e1973d07a067f281e3337e756bacf10dcc295f7074564874ea4401eb2a4e503de12047d931a054019fb113a4c5d531be2d56ec3d20f99b1628f4d617b15da1c02030361cb373dd54af082c98abe4331b16f360ae70b82b3f111dfe54eab9bb47a85f0031bf72cc92e51f3f38f18d4d0a77c173ee78ae62dce6027288dd37d7f1024df600203032d8279aaf065d93b0e811dfa25bb7c19325ad2e7f99cad95d0737c5390500982036bdc41d82cbe612f8caa639dda471df1d8efe19aba0f39e884b0569c597f68ea020302030320c7fa871d9cbf1112255d49920d07bf151532323d32ceb6ad4da291fad9327403fccd5f970aaf4f45086402c560eeb209d84b4da278dc69f17e3426ba0b273f890203020303c3d3043a6c5a67ae707239a66070748c2efc09d25efbcca01ed86206919ee23d03407bd9bd8d77985f52cc5d8781fc24a200ae2f8bdbaa77b753f7f245f6814c87020302030203020302030365f66ec8e09bf15d73a83402fdc462cbcc40578fdf5d4ef85bbfbf9b5ea5e002039ca41cc26f222ece8fb37316d9436cb914d7041ed51f1d5d3831b735ae2f0721020302030203020302030203020303ce8a414b8283b20263f621799a194ddf5d753bef21ab8253b41de0ba8adf661003a8e38716cdd8a09095a7036c686009bd8236b1c7eb9507540fb981baa9a8bc4b020302030203037ca01b97c87ac12c8995d3c80f7aab3313747ace5a829f08eb68381a5a9fc54003e5554fbb47341d48f82f64a26d175a7d3559378657e77cf2de2eff917b95be300203020303512c2cf0ab4340a1623bdddc301aa586932f9413ea9bf8c0f1849d2d70d5d0ff0375d0cc499c7f76c70939fd8d633c658747eebf0eb138c15d902c34f0de9098030203020303b78dcbd59a3668396357cbda038d7e5bc77aac4acdb3cd3a75e96eb05079a6bf03ceb3ed2850bca5df0bd69ce2e85e9daff43cdb4c79f58685340f521521a0943f0203020302030201034b33ab5a3b8d3b01c374c1d7fcfc714398b7f0704ba2f1ee757670269fd5a7f7020302020203070354000000000000000000000000000000005ca1ab1e582000000000000000000000000000000000000000000000000000000000000000004383b69e070354000000000000000000000000000000005ca1ab1e582010c18923d58801103b7e76ccd81e81a281713d174575a74b2ef0341f6b9a42fd5820b8b76bb549992d9bfc44e3b36d087a175b2e78b9584fc752eaa3013e0bdd31e8070354000000000000000000000000000000005ca1ab1e58209bd14ac8c1cf553e0ad3a2c109b9871eb74f3c116bf0bf492ef04d2983722555582090629dad0a40430445b7d2b25a8d19c5d7a929608ed7890877a499aaca01ca5002030315edee06840e36ef17d13817ab5475d12f7bd50113984febf31e2cd80c08952c03d360a7d78676862429feb7c95d052c1e63379b8ad3becf085a21baa353ab93d30203037a2fb952c2cf8e85d9706bcbcb5a69b83b13403b58f06b0767f4204acc5917930310de142eb3b2790cf1e3694b72eecc7e8ab3860f543c15cc24274ff69570f009020303879875563fe8a079ef71e84b6840b187c681499095de9d02d8b101c9dfcd111e0395e9fc3b000e49b65678f256d247786f72c91494c960d117b7668045c35502720203034c4d4621925c12b3878ebf267a1a013cc3d5675903cb0e22bc6d1df0bace3f8d03c092e19f1fd097b76813fc2412338735dab2f62302645b0e72d195b68a1e4d4702030350620914ec3787f2d03c118a874edb853c9678a3949ce426fc19489744df65e2033a2bcd06528de10b0bf7c316956d5af798ce85d8618011a8db4df56202c17f27020303c27ba5c9e177fdba8afc9cd524bb5616116bb12aac5aa30d1918e36228883fda03003a4d0233fc2ff4bfd5cb02b70ae195150d4d18b59449829e612204e831187b0203038cec528699f0b6819a574be7bea0d083f5999e462c6a464a37c582392140762a0393afd21f19e4329c0ef6b1b06baf963080c2980a73c5937cd6322ef7dc631dc00203038cf4931c97d6aa8c453db3175ebdf27d40e4e34b2b3ac67e8888dc34556a99b603cd716cb8821688b0df7e56b2c31036c17c53a5f6d50b50cfd4e68d30d2420120020303b81ba13ab693dd6dffd70ba32f7bd51fbd5ecd3f58bd8ee96d7b081dbe45efa803dff9ee8db1218deb4733e71215a2e1629d8c9f5e36bc0b8184d70f2ea6e8e01d0203031aafd4025804cbeabfe796224eda42a75577ec804c615abc88953b7c966766a4034baee3dbeedfb1b839869d087bbadb64bd8d4007cef5bfcd038c7f8436c4b7e5020303f659d8fb79866e5a2f9479b24ca74b34dae4e211e6a758e376a1407294fd840e032e9950f2c2283fc366c78f61d806a412a244bebf4dca45f250dceff31fd3a2a802030203020303375268372cd898f2295ec6c9a9838412658bf8a9ba5c309854a92dd747e4eb3c03bf0048ab25caf15956b958175c59038226d0331be1767f2c00ae19bc9f70f9ff020302030203030290f4c412920a6ea22d4ec8091a90d63fc62609d3e55e44da20097cdd8204430338962fdeb56eeda46eb38c254e32bd4fa863167913801664a58d773fa3a4882f02030203020303b83955a533913a8e816c0a9e001379dcbb9a89e48410b365841c552e93987a4a03e42aa480068387d975b85b52ab67acc0d5de816085765f419fec172afc69df34020302030203030e1f9af6f9a3833c51c53d2ee2b598421c0227dc651646350725e51762077ea3039ad12ef2e43458f28d5267d58f355ca92e3f625a595042518e0ccf8b0d4e96e002030203020302030203020303e8bb2dae757d043417195292bac773cda990500845f91a94d00179fe89525c3e039f7fc724f0bd8bd083a725fa7d2c8169bd8ca33d31c9146805940f0ee480c3dd02030203037d8dcb012bdde19a0dd178c1de91d21cc866a76b9b6315554fec4bc4f5daa7920383f4a4890e8cd73e6e32096e4b11c5c0c50991dff65297720ea9ab7b8ccf3ef302030203020303c998c4c602a03cfa0c92a1542165567f11d23f2ae5fb91d04e02292f8e297548039447848097d9500f21ebe819789f98461e01aff7cfcd442c8afe8e07b87a95690203020303f6441de6ba2bc5cc9ead4300519a94a14a80b85f2fb0fa41e2211d7c02af0e6703703a99e4c2133e89a6e892863f14f143cf5f2ad94bd527081c8be6143f14f3db020302030203031da626649ee092857f195eb0059639309d744972389a4e748c471f16b0fc3c2e03f4072cd036d5bbb777ad24fa0b1a235806ef25737404d7ce4f83babb76bf090802030203020303c7f6a9615846a2d627db4c940098479bce3c61c8fc1170d0b7f8c9ac2bec5ea4033664667108158e9377b49cf3632952090b6eab3ba6eaed4f48ca9d5beb273fd002010203070354000000000000000000000000000000005ca1ab1e5820eff9a5f21a1dc5ce907981aedce8e1f0d94116f871970a4c9488b2a6813ffd41582021bb230cc7b5a15416d28b65e85327b729b384a46e7d1208f17d1d74e498f445020102030203070354000000000000000000000000000000005ca1ab1e5820cfe58c94626d82e54c34444223348c504ae148f1e79868731da9b44fc91ddfd4582040fc722808ecb16a4f1cb2e145abfb2b8eb9d731283dbb46fe013c0e3441dfbc070354000000000000000000000000000000005ca1ab1e58200000000000000000000000000000000000000000000000000000000000000002446745baae070354000000000000000000000000000000005ca1ab1e5820bc32590bee71a54e9565024aca9df30307a01cf8f23561baed0ef54b30f5be68582007b7aa19b2ab0ca977cf557ea4cec4c0b84b00a9430cfe0323877011fa03269c020203e752c67cd3ffa4dacba7194a973b10998b056b5b936d17da2a8eae591a8e175e020303abdf2e1db4950317aadeff604ae51ac75e8281b1ea22e7f17349757c71dca21d03fd88dafa9a26e9c8f617b5de3e1a6021092dbff5c1fdb5d8efaeecb1f333565c020303b8a363b96519cb0eed132d807f6e42ea35f115584c443a74b14a19bbeac463d7038247bf369e033fcc19a5797960f1387f04f80cf396babac560060887288632db0203033497b9767463d12616a5b29b2d66156e49b3cccfe6598e2e73d90190e04a15120384203647fe683ea28ce78395875f0bc39f1fe1ce6c9670b8393161514dab4701020303be9a5bc3511c4f8466f2316f7d83370249d71f17357eda7cd7135b86b136090703f4ab8e9e9441ad40280807e7462de0147c3471983825b8f70b6066331bc7fdac020303fedefede445ae76cbb5f94df4e543537d9404b97450cef183b95f2fade25aa250384deb1e0e5121e700221d4f1ed13d488aaa4275c46416c2003d8d1eddfd78c180203035ed3f2ee894a97e0bd924d79ee091e8430799ba07601928b7912280a2260717b0365ca974c72e3e2db5912a9c87c5d5f84c2825a1f0196fa4793cee99d4d173351020303678452244e14f477cf17190203e3d2741cde4dada731914ad7987d94a95fe953030018accfc6cce1d6b0b884be4fdf54fc21d7f2b0f320711abea4aaf2dfe49d52020303a72a40bab31ce553d4d303b80f98837eb346dc83decd766ed763739ccaeb1d0f0334cd6263be1472af1b73f09f382ab57941ac5043ccd4feb8c4abeb0f2b0a869702030320b1e0149d703853b7ff9219e776215178aeed36a0c047635656c8a804e0f73f031e01e1d5be3de353277a132daf82f484854a9f96ba3555c42f03f63dac8a72db02030203020302030376f04ec9d36ba7e80f947ada6e0259810101c9e7d45de9422ca4db530e69bced032f63219f0d7ee4e45f66963dffc99e8c00abdc81eba9881462b2586539a99a280203020303608497d825d71973307cda5edcd6d1f94aaf6ffebc7430ce2f8a9d7d58116881030188ddfeeb33494d9da289334e6a850d240512bc570857f475ef749bf48e83a502030203020303053f6a4c851f9d91a49431b1d1a85baeaf2538b0f94b8cbb089e9c440263e6de03bd1e02a14a5262aa6cbec4a59e8040bf5765c7b59d08656f0e7be78b166a80690203020303856ac3a92e4d8741331982bc77755fc5831ec683050767f2b9d5aff1fa786cc60386b7ce083e8e52be86e0780b6ed5bb8680acbc96259692245ec1f521c01e26d702030203031bedfc4c9c092921f7e7093b07a76dfcc37b9d1c851d1f62a66f209c22913b4c03956a3522834623943651a19db780c3e98710a341789ae868713e46e1b9c1b98202030203020303b9828062d71ab57eb548db7b526b960e119dbc14ae39cac02d7d00f8a195db8803c44a3b5fecff2afe00f98bf696d082a6a5175c5df8dfe72521b2c5659911b8480203020303b59aba5f6d921a646bbebb06a5e66188f856d56c93703805fe05e98a54ba2cee034576a02a1bd3f5e7b0a9f8d07e5a1fc02840fb40cae2f9f6400700f945b53a3102030203032cd57ed327e45cf463bdcaee1f1a638c75288b1ed961644cb925f5fdc451a63903bd691a351ce40e663ca27d33cbc4a814e08f6f9ca16661964166d7efcc7e711f0203020302030381d90d25cc12dca6684d3ffebcf1e5408d0a365c9242224f678548c5963ea95003b33603045dce8c8ff12133b56e2a2d3ed094cf962edfb62711e929ea2ec4f37f02030203020302030203020303beae4badab0dddd8f2223233bd5c4be6b07ced210a07e1372dd0f271fede38a903c41fa2d194a23c8b2c481a227801390c6671150c1d39cda7c9c0e883a05d629f0203070354000000000000000000000000000000005ca1ab1e5820966c9e067cc4e8a48ff6ae64ca4d8bf749f01266043ce38d3671782ac86c738058209971e52f05a7ac040ea951a9f045f6b0ca4f28805a30e62273f331e4980b3a37070354000000000000000000000000000000005ca1ab1e5820693c07ff6229368d9eeeeacfb0e582d270d5bbb89324a80731633d2bbd5c77535820e893eedb484a91d508d6a0fa224172039b6c2fb9415b8e17729aa55b42ba046c02030389e3fca41e0d8f09375c382623317ca86dbc11e768d3092378ee9200e7d24b29037cf6827003ef81b19f0742ffe1d5ae37c11f93b8d3d4043889bf098c271f2e720203038a710f76a0386b80e437c9bb7e2b0795891cc8dea00d59b49f1e62c95c385b7303aab1cd0e8ec8a91b65dcd0e5b347817bb11adde535c72ada7effe7988d7fd7ec020303eba38e93550f3ac5a10e031758339464c3e9bb984e93c5eed408d709b33e437203374d10d55d31d4afc6583fc4cc50332529ed4926608f647c2137442ca096f1ef02030328c04fd39c18b7b519c893153c0291b3cebd6f815290312e4200f9fc82c92db30322339478fdcca672932963533acfa941a5a526cd3c8b8639135df8f9914afe0b02030327a8ae74de2be2de1caf4666e833e43d52e360ca878143e3897046b3c3690e56030915b77a3bc6ba544017c15f038d9d5aae50e7db83f6d6feec452467c41ff98f0203037511f8af75f75f986a1c8714ba6c45599dcaa9f61e250f7099d27915b46a8ee403a79923041d4666433c3bbc2b46c8c137c489a36d2fa5c84872f3e254780666af020303dc8a20e01f59403e932ea67c29b74d1615fed5768abcf1df2432e56a0bb0ee2c03462f1a21b3bf910d6ca08e90a47a11d25ad48c6e4d6bff7369f2f28dad30ac7802030379e75e6dec2ffd37919ce25da5a5a67adcff93790a7cef7ba0c1534aab2208c0034a95a94a94ae7a317e153903a888ab404b75042483abfc1b53994584fed445ba0203020303674f36f2a847c25e092483b595d6d69338bbf807516d5b424e3ab05fc95719cd031b713460225852cb9a5005429fdfdc44c8d71153e1aa04364570343a434c6388020302030325b7b7ced4578ad2120d4703c775f82d4fcf6ff0578d60d76b1b3d5bf982812e03ee4f0964b55a782cc4128ae4291d947dfd63300081d89319ddc357b6e9a7d365020302030360d8ba62d7544d9d354731fc286a41c33869212a45b7322d2c4e67504738e65103b0ae0178f65708516a57beaa38a2cd4d344fe8f3a217f1fe9c4cb2d41b2975b502030203020303b5419117efdf04c24efdb20fe6e1786d53529a5630a98108d2118c7dca7c136e03aaa09bc73d06dc35034f97fec917652f664c4d768d0c036b2539e661f2d8fc380203020302030203020303d51b1bcd3eab3b6a6c384a831ec8080cab63c1c44d898bd626248194376fe1de037ecc252f46692f6e8959f226a5e94a4ac314d38000dabd3c66c06489789651bc0203020303248599a8b4c29a8dfd17c29080088b4573842ac4a1fc2fb628f1f65350cbc2bb034f7ae2704632668f91a5605aa808807c7c83f999d35def3d40f72a825eb561ec020302030203020302030203020302030392af697495712b4013883b3f5ad2d370bdb93f0ed60416692b0267f10d9a3caa0386fa8ccd91ab622b232223f9a347f1785ca9c4b7323a2e0d19a7971c3afd63ff0203020303b4f12607fb8df583b854d7b235f4a64ccb2f4bc9819dc50f3a03ed0d4906910e038f64a125d14bb92752d65593faae8e41bb5e80e4f147b20f0c247078f6e7ca77070354000000000000000000000000000000005ca1ab1e58202d11035f2912c26c30c4f8957d3910a20622ea8709c8cd3e0ad87fa0f4460bbb5820c0bf0b2ab68768eaabe5fda7814227beaeaf4c4ee7e67f5d07aefaf5f0410ab80203034d5eb602925f13a2147a2c1439d43faa74e2561bb3d27811f02042466fb2804f035d9458bc537a1957fddbf6c5f13c6bfc9349abf1251df9d6dd48b5b574f6f48f020303bbf6401ad2a6b95a3e749f5b31224fc7fcdd083e7aeac9671ec3bebda312fe5c03393a914dd0b171b4cca2f5cef52cb4ed4b564278c0fb678e5e8f3d911b4addb302030356cdb16849ae7aff540b8724f73974149f71cd3f984360537159a273a5bfcc1d03791ad7bed137c9501bcee55dc6c9b030157a30c37fca14d39c25d9d5137ae88b02030392940198d6b1df58f0a6c3cc1da02efd9547043d626487166ec858a5aae7b61903efbf993292364275b60efda91d4c18f5a87549ebd407ba16763b1f0c6113a6cb0203039691d481d60a086435d9f914e8e2b5e5a68abfafb82dcc9d6de2176920c35ded03347f67f0fbbc63fa8a3b826c6491f42b13869a2abd2b6326d75d51cb30ea9cf1020303d822fd2ee43799eb3f714589ce7bea36550c8fe4a90b5a2aa68a95486490962c03718887bb3381bda095c52048955b7ce2e5b35e5bec11515e9fa86187fa9e0fa70203032d3bd6d97e29b994d2d73867b8e19f6b3b2a43d5992a6b9b3f350f8834c0da9a039f1aca3992b3c769a6735830359c9153c2daee33d937c9b903a05ed9ada8f5d0020303bddef4e591830295d3c1f6e27decf301748434c3df51aa8b451558ee7962deea03926ffe488854b96b623872e9a54c6e5bb737fa12f402bd8a2a79f34000ba21520203037b0ddd3f35449b7e435243005ad2a536fa28167cf7da21ecd2d4c3a55e6421a6030fd4916c5757cb6e137fac5d203ba3d5d50a5077a06e3804faa80e9783e2367d0203036fff8395e24c14d9e40936920b141c84e2127ed823a1625699eaebd1f26b69c703069db41eccbdb4aa227cb482a97d6b342d0413855bef3c9b432d74ef0be43e0b" + witness2 = "0102030203020302030203020303ddd15247a8b234236d91271277b1059a674eaed56c29a6d8905b27ea9460c7e40344f7576ca6198b0bb6daa81b4eb6f594b46608e0f4d8d509361f0aac88eed2b50203020302030203020302030203037477c5b7ac361fa5a28f01782fc1b9577dfe27c9d91e5193c426916c166503f3033e6831fb92c6944c4869e9ff429fd40b9191f5a5a9fd8e4e26f67be29feb3d00020302030310c0064663f729ce8c12a4db054317ae8a3d309ee54378eba25ca39a4670758d03fa715595952a40ebcc9c06b02f6b1960a1f74a722c3a9fecba1aa66f32f1850e0203020303b010b79cdf4c9bd8f8164ad282defed968658e80fa57c26c19f5cadcfd9c890e0318f8d37b605fba62e9bd02f5554b8bd4784578021c737c4cb957c4ed5e8ad3b5020302030203020303c4ac3ac799860160a30a3304b765c2c90bc414edc3739a5d098bb7e18009548a0344ed07cf7b7b49fc2e7fc9c6c19d1b60e64990110188e15b445320a35660f91d02030203020303949f805ade2be05694c8011fa17fab3646a43f38f96d868386f0ba9558ba5f960302aabd9fbeceb9711f46d634513830181412c8405aea579f470a19b477d090140203020303db978a462b93b2efa3aa3da09e03370b570db692c6d361d52ae1051bdb26a3a903916d67432c505e1dc33f3617e0743d761aba44785726309191e79cb18b666e7402030203033edca13bcadc1db9305f3b15322cc6d774682fffdfe2b509f81d00b16ce2dcd003dc94780e238944094e7856154e6d3e54fec28293a9a70eaf1cc2a81e874e22170203020302010203070354000000000000000000000000000000005ca1ab1e5820e72de8a1b9696dd30f7886b15c4cc9234d52c6b41b9c33e2baaf8d88fc5b7c9f5820f8fb80310ac041e7a5e79c138d7261cda5d8a988dc9268b5a8dc5318fb610a90070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000358207d7b0aec16983b640324af57c161ae800ab5b0b61937d153540fd64ba724a431020303c6cbb686c9d7a94f49dfbee076ae1b87f1c9bb5f33b7c98a71816c33b4731a3b037514c2021b2bb805e2a6060b265dd53c069a4587e19cd7d1af99d0e9c3d0e550020303784570292bffbc3ee00153e5806a317459fed4c1d84de0515dcefc177404865003f08c92f6786e67148e8fb2bcd4eb813a665e16a270b475605d1d84b587450ff102030344c5e2a1775873020ab4e5a588e95d6702878cd46012682196dc39738fd8780703a6d5ee17fe3be7e20050e4e66c54b5188febbdd3615f832c35b073078258b214020303e5f90b59ef9f5ceee0e0a54551e41a62431ea06aa09be94779c474ca4d18683403e794dec8b1cbcd53bbecf14b61869699ed3f92ecbb4ac3d9a8bc744c09a3e69a020303476c478891a8f8d905ebf9e5c031ba1020ca1436538bf9b97c6eaa1b9512da97030e077b0c8215c8c43753d044e318788eb8e39de692fe0ccd46396d3b06ca6e0c020303ddaa569a922c9c28d9a225a4c212c076ad5e353bb7cceaab630a3884af855d2403d5ff4c122142c026a0b24b79b4a667d25ea916ef64d8a8215aa29a738c5588a50203034b1d465e96a44ba0d7983a6f4ce10a26bce7816b6d51ba8ac594c71892cc2af60381a6db28188e1b651603d41fbc2030bb2b7706e02b1eb3423d7f38ff6ef514e6020303f30f3c3ad2db979a1c81690619a35a801e3bcd77413f37e285b0011f2b6e2a4003239d1f94c6460af24c7228a2af86326ea1199e97365bf7dc5832ad029107445f0203038518fa303494de83c9ae1f80c877b5c0e6dba41880f6df1dbaaff30fa9b9c37a03653c1b2e876da5bd8b6535ce431ae69feb7be788cc67b2fa3dbff11c792c1f13020303d5efbfce398f4205569b3fc872e7405712796b7189d6846e61c7ff33a12ab0c5037aeb2da8a9e504490ac07aee509079823397fc6e9cd25257e658f6e0021ae771020302030203033bfe86ca5a55d4d2d42f5af48205ca0ab08df68e551e61b9a1bd5d575ff9cac3037462982abd4a0437ab5e12ab2af263ab382e0ceba69ff5de751519512149c70a0203020303980043fe396689718e09b0990d71b800219da2873a8e0c3c45d25ffe12bd9e6003f2f9aba950a1023ef8e02568c683c86ef2e77e16dfad909642ddc5cc57ac8c120203020303738b4a16af664d0e0c6b7ff278d1e3b602e6277085730d77844f1430c2f71bcd032c505136023a2005bd6b8abfc49eb783514ea36233d5439525478dc102ad67e402030203020303f30cfa6f63115cc17d752bd07a3848c463334bdf554ffeb5a57f2ac2535c4650037d85b4ea9025d3512a6fafe55d8e3570fc8c968eb67042e0ded283dcadc12ae8020302030351a20a2e192372b9383e5b8ef255adf58a3633e5aa4161424f7b52912e8053f603edc4f75f70c3608079c9f0b4584da6270879e9983bb3513d7e620024f15e659f02030203037e1734c6c90368548b9b6a882b4560d78e0630f3616dc7d4b4b8d77b96a42dbf03c4ed6f8e6cdc9797199a463a51287700852a10099a1386109a37561b538d228502030203020303d3858acf0781afe0adae49a25d1724b36c9989179cc884b9a9c6481f89e57706031da6fb50879ede58d816d1624292f0c60a8133bcbc671dd92d0f9cb8d50fc8030203020303521bd9da187efcbab07451097baf98589a33e32cd33501c5a912f48cf2552bef0352124f3ffee53f7e0f9a0068d5c0b0abfca5aaa148371c91e2e81df5fba6f8bf0203020303e00ce2232f3e208dcf050f887d02c7b91170c4b98e1d098ec5238bb3d387a41e038a0379dab30ce84865bb4f0834a9c3fd7bb2da17994abf03e89fd8d754bf7aab0203020302030333f5853903cb36caedffa12d0aa0a01c39e91309629a97dddafa8da4f738fb3e038e1dc6012aecd5998053b5878c6e3a398c8a286c7ad4dc0b55f043e4b4210950020302030317e041d91830fe8051fc73631cfd56519fc5bb88b5369298da9807b40d93733703dc7745bf8dfa058bcf1328f79efc9441cf5ad5fb5763c75bdebf9492f88a6c8302030203020303bf9a2780e63ac26ffca6df07f182db72e3e65802b277ea6d40057c383d5a37e3036c1548eb1c956ece0a876fff4463cc3f2e9c3b6eef88379f07e6d71013eb4aad020302030202020103c2eebac9e260dad1f051fa75846558dcc0ed120d1a7699fd1d6b739a3419015b020103b9d64db96b69b035a74a90069691afa2b34a705f3ad3aa82f3757c7a6f2a9f17070354000000000000000000000000000000005ca1ab1e58207af492bfc857210d76ff8398a35a942af892e866b1f4c241746b3ee89ca002595820f889596e5b3d4dbe5bcab5cde2af26d3ad8d88bc086e8b4f929885f33f6eec77020303cd3edcf79c7e0e0d6b87ae523729611faeda92e77bbb59f739e9a6127d890cdf03009a5c01c3cb27d2f1ffbd3fd77ff38f66991f64174f5df1411ea21ae2e22f250203035334694d8b56a2f5e0d0ded81283e7f36b3da6fbf2e2d1b469c008d7414296be03953388b0cacc587c5ca452ba8e96a9071958ef94439690bc14866f556a35ebc1020303227561f72db4ee550290a7b85931038224b1fa9c351395f5f5777f397016d7ae03dde5f312229c20faf5b1b27273112bc022bd0d1dad4195ffeeceb49c05001a07020303d4eebbde54471ef4008ea3e23e4bd31119b1d4fa51a2bce7771c95b70efba064038c6a2b8e1f68d72b2a95ef69cd8eb0ab32781e7687049eaf3b7381596c0bb8af0203036ae82b7b420a58afe9871a632d69be8475f745405df2183722c599f94a5cf15f038a575afe8d81ea9f181bee15a971affeffcb1964ed35ec291304be393899d80f02030203020302030203020303d634ac486eb2f4e325a096c1aac56ae5a0a3bba406dcbede2e9bd4837d1759f203ce1b43774de78b19d67b133fb575ead398fae6a712ebd63e26671f199c8e674302030203020302030203036068b215f89f68246518e7d8c967f8ae78b47c69bcb9e97deca5849a813b2e400384b630ffc67a1dd7b502c1b42165171a704d68ed15ced3b7cbb98bd150cd884b020302030203020303a3c7cf45ebdd7e21dade3434624c9fd521b0ab24a6956e3b8a777d700149806703b3637d0b1cf58c5272f28f8354a764d3cd48ff7c04f807237da8f4a1e2ef5db5020302030203020302030203020303c018dfe606efd5d17f3d45e91d33d3d7ef57d92a1d509291b1556bbb7e78dd0803b08ff5bb304aa8741af608415c541440edcd98bbc0fc849fe2a89c2c783341d502030203031e0eb0ac5664b1d0267d3c51dd66d1828c0d01a0903d599a317e4578926d4e3503330e2ccc546a3db52e796943aa8960d6d483a3b941ae0caa21cc7b9f7a3c2bbc070354a40d5f56745a118d0906a34e69aec8c0db1cb8fa5820000000000000000000000000000000000000000000000000000000000000000158206191319cb3bf48d9701195789dbbf6db5d3b99006317f5e7da37709f3d259374020303ac874a6acbf6de628134cd74ad9f336206e7aadb1ef09456b6267a770612485703ef323528b761720ce04927a54b81025a935f070420d655217e40eb2e084bd170020303a48199a63a429cf44fb39fdbb73098a49dc171e03d32800f483780adb9aa06580388796e8ab2076fc77f00a5096317ceff8a54da310b014a0310504bcd76f8b8da02030350179cb850b147782f26ff9a17895259e569b740cd6424a7a1479602bd8c822b0371b277886f0d14b6f82cfd063ecddab10fb5da5e0666e040992469d09a6bc8b0020303dee2b54587eeb7db2fe9ef0dc262b6ae679a5bfff89c8f403d181a1d79107b1d032aff27f522ef5fd88213c3865e01c7b4c1720d56778d1bd0e48e6a86fb3b07970203037d0d29240ad72800831a91d8e54019da745c6c6a630a625167723ace857bbb81031ede6b255a1c6ffd6fa2afc16d61aea6555a5cb85dc4669070b69b55a16ac58d020303335f1f02ebdb1926380c362d23b2d90d791f5ec8531287a47d3a1929d6304f1b037b80208ab1e9bc0411f128ccc859ac552945a650ebd0f9161a63fc9944e8d43f0203030d600bfcd6581d405aaed26aa7cee976fbb2bb9c1c1390bd3eb14cf5f6610223030bc731957e48cd1b0f92521fa971ca65f76bc8608eaddfa5243baf39838099810203034b176bbe991dbc4ae5738f23e31433597f9b892730ad4fdc86784eb928cbc642035fa76b91882dff00646e99735fb6accf89476e59c9dd28e0bc7b617870cc15e702030309cc884c89c3b546aecc35f4a95760a100cc3fb5457a82fc413ee2cd795345d2037ac7f4c6f9dc0e8f652f47fbda5c4b53428948acc95270be462ef8c909e5b742020303bb4f79f1339f6fc4ba3b4c61ff1940c27b29459942791fd7b160a04bc8aa411803628f665215c8c44bda1a6243487e35e5d9d922bf36f1976fd5f6c39264d16e8b0203036bdcb7f00d848df36ea8ffe2d419775be23396cb7344a2dd1ab44292769c922303710d1c2ccfa13ceec5ffc97b3469592c4f2495141e46bbaaae6f099c47b9737502030203035f97c209aeacbb5fc78e69d247a800528f4bcc5e649fdec626ae5ef510ee7a71036eeb37a43ca943f0bb09cb54bfcc7325ed27e97af16f59cab0822f88d3143888020302030373d9994e2d75a6f80adb914f45d533caf2ade8d952a0d8b73a40299199892f5f0347947690bda8388fbc8744af22af00157531bd0f37353b2407b573cff34e23c20203020303cd4c5dc3e51e3a3379cf73004c787ee7cb312c06c70800d0f08e65a0ee2313c40350adbcaba1f1a5b06ae4510704194cefdb5053ffacdca11f354a80cc04d0a2f402030203037ec1e64855ec72f6c39f1832616a45075eda4889495c393ffb673aa05f25e67d0361c764032c6e6f093f7e4e2db6e3324b29e59ee4df2f6df3536539ea135264cc02030203020302030325abb132a4c897744752a4707644448c653f00743c37cd560218074dfe1e4d2803fe62ee54fd13cf254cb8c3b2bf728d8c26703054588e529bb8b2a68a950ea4e0020302030203020303846e32cbe73ce37fdc6fb93afeed4425035df35d637127b54b8fc4c053d405ff0398c008e116cd33ceac2a28f29c392e533b755c24316cf6e847e4ef72b070dcc602030203037cc278f9b41fd17fb5eb3c839a725fcd1ef6000189fcebcb4214303f45dcd2d60386c3bc64da300f1a87efa2eb2724553e41348057fc99d5c23b5b20e216fde46d020302030376bf2ddfddca9910df80bb0785add76937d1e90e029c02b04c0cf421622a232803ba2219bc37e93a89b0effdfc3601f58645c1cb7e818f2254c8fd16fea4ba84440203020303fdf1c4799edef5fe2960f9148627fff521e591247a224eb8d05eae3f51675b560372adafa8e298a14d0da0a71e645a12a23def78db8e81f7a68ef92aac7d5700b40203020303f5a794d38718b283b47993f3bbcd67c76f84c47fcf2d35373fcb7f8a0f43a06b03a14b12d1f03790ac75797e463a8a7edcfb2bc80b65a7dc8d1b15d00cefb315d5020302010312f8462573dc83d436d2498e68019babdcc911f482e1e00c1b3fda70e1a166e40203020303adcfa2154e38e2cdbafd5d56bdaa5dca90a5bfb9c36bfbe140bb31ec0e66716503b5aaf1a6fa2e80ad8f4e49c51808d2898fd74f539ec5de974b57c27466f5e7490203070354000000000000000000000000000000005ca1ab1e58205956a0b12f607189a063054545ab26ce76ea5eb4c9bc1e8d8161646c93ac66515820da6aba51eaf87e14a7585e52e23cc0b789c61b3e808d2aef704ae932bb2ab49d070354ee5a4826068c5326a7f06fd6c7cbf816f096846c5820701c251f0448beefca8b47dce2e42f136d224b8e89e4900d24681e46a70e7448510237426c03237429400000000067445fb80203035b2ba27d2c4b5ccd82a303efb2a86cf208d08dd952ed0494acc5aff009a9809303e498dca26358e5fd56e5464288da82073a17cbbd112e322488c12bff1661b49b02030308e7f519768ebddac099b4d79b3da7e528e3e1e7f43fb5e815cc2e7e9bdb82ca03afa06c5681e457eed414f2a781c5cf2a752257a696aa0d741799d3e5c6ac65b6020303645cd7c283714f070784b2e6a24c049ba20cc01422792ec7507818b757b4d02103c6b1fb9b858f69c598cf4359d1a43ec9faa45a7f308dfcf087b529ecf6cba0d702030364631f25391237453ea4bdf6dcd59ec33334c8bf13b3f4ebc24512193a52368203d4a0ec404056d0dd6b14481bda731e46954f9d29e4d43aba64bb8ca52ca87bd902030329cd1de4c7edfcc761f57c5f2466970add88bd8705390cb23184091c99cbdde603eca27d7686e41e3d24558d66cbc83d2a5d35469522d922ab216914a84d977e36020303aa3f3aaee4ea8cc05d8b5a9f3c4528c8de0d5b4bd9bedd4456b8816a9d7195da036dee15633cb92bdefc8f632e08b85dcb8bf1d317f82dfcbb7b76e38f7421361502030203020303f1a4bc7768286c3e023725e4f781a6b00fb11d83f1dda2647000f13ca3c58544035e062fcd2f3f81c8d4d424e25bf7e77e301465425a25afa5d0bdbeee2c6284b202030203038482b5d9958175078c790a8a1effda8f156db2faa9ff2d6473d742b4f737143903bb264f8b66371fe289f7741ae353b137695ca94cbc9ed3ececd3ef601d54181d02030203020303b9a21b649304cec7a5d6d0464d6bd8ddffb475c672c0c9799b3457e4b9fc2a12038da99cc78f04ba4eaf3df326eeb15cb038c013a9e5b76698f493170bd356b13a020302030203039c69fd3c2b5b5200c89358d29432ddc4cdadbf9d1b05f2265bf4af27d968898503389f85ccddd9ba507ac3bae9f0a830a56eaf35ebde5aeb6c374dadfd0ab39aa9020302030318b62235f3bd9e0e268b30ff1a987af5548f00006ebcf51db0448e220c17e862034465f83c3781a2e121eca23c852e6b742e52e0fd76e2eaf886471d3f5c4a3e8502030203038b2faefda31a8d8e3e5590221ea164997bdaaba30fed699932fa0b65c6ab2fda0396915914ec53b6ea1fea28b0ede76ab410d1dafbf996f2fa7cd37f1b4ddeb59e020302030203020303455b9202298fcd235ea441cc50f29ce15a2a1a9504564159a849211c899dc08003f3df85b9d03df952c76c1f9853ce686f21949c732fc9b161b5759faa36b2cd55020302030203020303508d990b34daf5a3f925c435daac3d293f6d861094cc2d343a92c62428fa66da032f8b40a9211667e9c44328d6440091ecb3a46bc15832f7d7cdfa8ec130b527fc0203020303f993f7eae6e45a6f8557c6c5d0e912cb41b71d2bf37f38affc0b2d8e054193220315eeb3ab754628ce727cd0b7028ff8ed3291de7566b99066e127185d043f595702030203032f2c132f32f21e267ab64271e8f2c0c39fedbcc509c4589616cffec21d7332eb03839857347599c19c43a0acfe53e1bb5bbe0d68ddb49cee05f1b24c5acac24a150203020303dcd0869ad1107856680f6bf164623fc709d26d1a0842bb9c60a383f255c0ec2403c92cb1692742c4e2b6a91d13c3b371a9dccd29f898d8f6457ad052b1da9efcf6020302030203031408a1feb1c4cefd2e71c1d7ce58e6b4c2d139d48c67037d40dc0d60390af539039c51675ab13cc260ab6875b12824ed60903c1755add14024e27508ac0a3b9d81020102030376fdbe16ba7e2f048d9c311cb1f99291b4f624717ddd7e9f2aa653099d19314f032ebe85ea3fef7c7033338d1ed98e187eddf75dff4772a23e19392ce61690f77f020303f901f2ba5a7a95db9ea7106268f17f341206944377d1f006921211069cf8a0a103f43daf24401f9ed2d0691570a8ccdcd016c90b722786ff590276f7cd5933ff3d0203033cb8f613c530196a2ab151996cc3eb343199c5c0c0adc212268f74f6a092666c0355b4984426bd4db31ca5d70798a18280a4d319786bd897a29365d2db7489b32d020303d7f9465d351f2c4200659307b3cd7cf34d3fea84b9b23bffe5bec395f4a2d88a03ef3e9f16053b7f799f207451eb3403eb95301e9c9e721dfde0c41ebd8362485c0203036152db0543c6381b557c70b284c75fe77b405b54d279a37db0ea5a382a61abd603b70663772bf3728213f272a0d02b2ded9cd31441fbb82b9a44d266c56e7fdf58020303f967722c10537809246d339e984382cc197deea70a2c433df88fd7797701dc76036e06014c6d6c4d1358aefacae43b83631ffbbb39c93874faa4d589c1f60ca07302030341a2496071d2a84dec9f60bfd3288fdcf01683618900806b1a61a740fcb95d4b0338cf0dcf2e49a0359d0d543a3ac97474876f7605800e270d1c8671dc375720250203034a347b6bdf9e875c714c0790a2ad84b01edf7b15c4d23cacab0598c704417ea7039676ef3f389061effccb4e08a0afc2971c35bf69edbda2e91d9e88486113990e02030203020303927b20cc65cbc0d70e9880b16dfc67b8379ff4a96b95309302803a1819d95ea003eb0ebe2fcfd0a9002bd0985e47dac1c4a01561de0da69bea0bc25ff1b519d5b602030203020303a4c7f2025180b6de7674fc2c91392a565d9a28a77eb193f29d9ba706c6fdb42f03d2bca54ba531de1142b06bb35aed010d55ab6e0d862cdd7807e4136c1b9d0c490203020303ec1282aa791a0b578de360336d5cf95a7f3bf1ffda9cf697b3aacf9417aa38ad03cece3331be90852d59eb04e3bc87b03657c0993626d3e36ebeef97baedd928f00203020303afb305376ba08f5bfaced38f127295f994096684417f9de1a8f496fdccbb3547036bf14c6051f3bdb18d621bed206c3ceb8daf8ec24843921de9af2dc2ba70d5ba0203020303122291009057e848a0e15edd72e47061463ab3aee368289eddc782303e9299cd03678ace78eb9da91eb3fa9105c9969a0aa9abd66ac41ab138aa70346daadd327002030203020302030203020302030203037a46bc17ebfbc47f6d99661de00074c9958e0f7fd66df7c77c236b89b165472e034b58bfe7c7506d2891367c270ca350269dfc0a08b7466ec2496c6330dd602bb302030203039b58b0df7fae59a4cef25184d849214bc145cda115b2c0dfd85fd470ecdea70f0330923d4d299efbc138d4442519d30cd33a7827557231388b81a6ea9c65eabe6f0203020303af3fee608c2e8e5a30ffc6345d86ec1b2d55f10e518b4da5e8eb59abde07b59803c2016682405d3a953eba254601d5fc0b8966a33efaa51918a4a41b8e0acbeb4602030203034b1387aa6d0ab944e2ec65ce38c8643a0ddfca5c3059718f398dee501291569603528cbab25216c4397a402fcb572f0b512a773dfeafa59e401989a4da13406bfe02030203070354000000000000000000000000000000005ca1ab1e582054b6c4d9862a1658dedebe99a0f61d94c5d1515fd031d0dfe9ebce6a1454f5c658203f14693500ccd0260659fd9eaf69570edc0504867134ac88f871d91d388b63690203070354914e7547b9051ea6226c30495190a2efa15930c95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927548382be7cc5c2cd8b14f44108444ced6745c5fecb02030311ab6695ec969171698c1f56d4c05373d8505a2c7299fb05cda1d4351e22bfa403478b94ae515fbd01728835b532c7c45ccc78d200d3d004da6917337e139eb729020303ffd14369e7c7f7aec3a890a20234885f2c9fb9802ec318d8434ebcd58a696153030ddae742090ea458c3f232dc894bd8cd3378b4b4590a0523e09a44e0439fe0db020303b1688d8c7806365d931579ccac8dbf7a8d7705ac393159dfd9c0395ab7b5ca5b036a6c978a565b15267de4330de7b6166014082043c5cc80370953767ac501ccf2020303fd878c58bb70337606fc9f519700dcabaee2f175ffd956a6d246c56e38de3c5a034ece3162b251497a52be7f417b99722c20de63b35a0387e0cb1d8a1ef6bd34190203032db40fdeb2c5256d5a237b6134f844646b325bfc12c687916327e21a65b1ae6a03381ca5f3231b0698c6d69bd685fd1930924395002ee0c9f1f3dc9324570c4f52020303eaf50a55e8bc433b8c594aeb6ce2dff8d6dc8a6fa7d72076a07d6b771d13d78b0311a2827108d1c853cd8a63db81104ad8493e188969ca0339d0a01ed043b47cdd020303f7993cfe3bc67991b923b2f3470e42e23e78ad30096bf8278f293053de07b46703a0c8d263334a785d55f5b7be433841bca1d7ef1b8743e6dacb4e4fdffc52a77a020303b616a1ceb3607803c41329eee93ec3541b2ebbe690a4f29e3234441d7fe22710033646798b76e3f8d1cdcef03b5802388ad826a45b0ba508443fa26d5cd6fca96602030320f9766d80286663ec273eaab27d516a59305f6fdb96957af2602f4c0eef4b8a031c930c476ddc908dc2d5ec253fdd2c6687f32616ae7698ee6f0f6baee9871f780203032d1c40f0360f2f347afb931f1caff15b122c02dd058d53cd31e10da5eb3a5005038da2ec93073400637eda663a2b3095ba8bcf473b6bc0ddba6732c0d88ea26f0402030203020302030349147352cccb7f2119bbfbbbb0a4306ee33992973d1777a3f176a7420854218003a44f6acf78a34c96774821d091ce968f756f12c95ad543c97e52f1c041e5c19002030203020302030203020303b990df7130026def95a6bd8c75f955e81827b81134286380b827ccc8d59020bb03acc511b7a7e46ccf36ec7d94e25df35ebd2b1bdb6754891973d081dbb84b74c3020302030203037bcfbafd729a64f6a285d2cb27f169b7f38544bcd685a9a551029d47527b7dc70315cfee27b492bf2f3b5144c78e9ffaadbcb1ab045fa6e58965731a597f48e9eb02030203020303d9b9c9b6e08cb6200a2ead0a7b44aa81f526cec46dd91a23e67370c74e198a1703502ff4229c44844597f1659b073f9ea36ead050cc08aa533e40f9a3a38d1407f02030203039708356582bcd2add889a2bafd2ad4c93eb63fa742f601c045b9e98e1149112903727c6112bad2314490ec2e9b95bdf87c45c93b0ee92fb4a0707bc806c0723c280203020302030309c10778ca4fd1e78c03c22cf95624e6d9b1845103efbf1dd6e56c4d47beab4a0357fd6003666e25f92b0e831fd0f7a0574664f4355a1cf4073937a1664bccea64020302030382b763d46efd8db57bde8120303a4dfd77ee10456b1b2a46916e75b6f0a29b140350037449c92721dfd8d234901d464a1cd6403666af10a822691082f192df864502030203020302030203070354ba42ee5864884c77a683e1dda390c6f6ae144167582089780709b74f02d53045c2451635aa24be6675c290344ab3fd48e15b49e3ea685713fc1f351312af1faacc6d6ecf4013834144b9d9b99c83070354000000000000000000000000000000005ca1ab1e5820565d8b0ba637731af59e8597dc7f3c3f039a169ba1e83c6daef2d43a8117db505820f4cbb10315f5e55053e70d60059ccf403d4e9e90cf36a791de2607fa7a89f1ed0203037d9fcfae52b9e7cb960a57b588a5e913fc59bcd8e1e3a72545680ae787cd9a080340f594bc38ee796cbfa7f62e7375b6e27cf3b90da8baef7cb8225b98a6dc06f9020303fe38e4536c064741e62c787ceaff5b8252f552b1e7c0dc2bd09b0ad991b62b0803d4a9342427b3fb884bbbbfdbef9db4edcfc599bcf4918022291891ff47a4473c020303133b5e6a3d0e759a8df9838b021551845f1e112062741d51919df0ba3110621c030b0cb1f937b231765bdfb0ada592cb4ec4e37b7ce68ad21315051f0bca23e93602030360b50daa81d1eebf433a86cdc63d639bb88efd0837df61c20ef4a07b86d4df9103c38b3536ac0104b6bbdc2fb47f57e4eef152aa4c727f7040d362604644adeb43020303685524b040ca7b4e87a1e2e05e3c0c0e289d68a623eb6b014a9b08d3525c072a03e2f0a7769adbb870f5ba21929643c23ce8a0e8149c6003bf635a14218eff4307020303c3d7afa90b5337e37f369666a5fe1e26675836c3adbf5b685277769112a2445e038d8f4c4e40c45232c7da072bf7ec8aa1feee967030d0ac4beb626cb50f2dbc8b0203038590c5066fd108dc4907febbdfb860f25f821349acf99d458bf4df063c0941d303d1c8f444e3b9f496780241daf46ee0ca3dfed98ccd58102a13bee062db56089802030381362d398fbe328b72f0cee739e72b7a5ace40a66aeaf298ef98f620c2b3b3da039ba3165764fca29bf2a7c01813fe58996e8e705882dfd43f6c5e17c54b307a4702030326b74aee4a5123aae28720835cc1a727db62addf31878a96a703fc43875a400203f2e248472501c5a6bb2e13c3ae7a208a35d01dbc41e44affa9e4c0300e2c2912020303ac5ba43d3be112366057444e9a2db12b96222bb7bb88139738320cc53924cacd03462978b4844a138be32fc8d45c274d7d1e77539a2839950eca4dbb779ade3db00203020303cec89e1b5d9d20c59a319c536ef1ae8c0a67e0ded8f6ce3a7eb1979ef183d3870348dbae09afb5d5232bc158cd3f9c2728348ae93f0742e0d71971d3b26c301c0c020302030376e9a3f309a69b0c2c7ca3184457ba9f0ce19145bc96d4bd846742585cf4e9a903b07dbe4dab435161a33e90e991fdd8ac5c0670d77cf2b72ae5bc530519e6fbaf020302030203020303c423e16fb3487a4d9126ad5c533cf130444a4f099a85777493cbd2d231f27b71033c4bbd0160fa760c6c008ce70d7342f2cd5524690247577a0ca36e15528565cd02030203031e5c59c8eb7467fe1b1b59f78351143028717a9679b5956d1a42ab64efbbdff403bc2db4433eb1e4eb520035e06ee46cdd233cd6f74e4ce438a0743af21cf67ba10203020303c1da641e5501813afe9c4653f2179710154bfe94ebce827d0bf64d70bd3baf7a03e2bf953702f6287b134eee978e1b18a36f65b41c2c673d75876215604661dd50020302030203020303a35901b035cd24570a277362d9ece906ef4d6e00821b55212d69b6fd6775472d037568928f5eecc9599b391e6cb75468d91ac18de51d7e984eb678105c39fc8a4a0203020303791a9ee8b5057a6ca65118869d354dba135fd5c518d63144d3860987f084bbcb033c8c390d481f51cf6b43c22677a971beae0e62e8b2ecfdaaed05b48ac0f60294020302030203020302030203070054000000000000000000000000000000005ca1ab1e45e8d4a5100002010341305ecddd1b56329ac9f09a1235eec6ce6be69492a9788db13e9187dc21e9dc020303fb1c6d1aa6d3f3bef7a0bf4130218b3b168f9447e69ebcd3b68c2b2f41d9b2ef03652ba6f9b69aee3d28404079416c2f8fba4078d66b558c7a8d9615cfe7d3bd30020303d9f042d0d2f152e24d8cde02d3a7d7a1fa234efc5dc259572d412a2e607215ba03c5b76ff595e1d74a22eb44a5aed94f3225b6126c2c28ef04bb75e1d3804925ad02030314a2b125da4db5ba673cd5c0aaae8c5bf0857fd45728b868cff3f40eaf9f82790393e93c4f4b58f6f9d397d136319a29aa6b691b652651513bfc2297107379ce62020303f00359907dd68b2ae8e2d252d3313f3ba2bba16d21995333b2162b24c9bbeac4036435af585f0f75e60d362629108f6768756f7b39f1c70ab7f79e6b4e1bd9f08f020303929e2f8eb833089a3773b497247338865ef336de61e7da4a362eb3e5d5601a7203323197b010e3205d910c230463758f39cd6c01258db0a11b9b47f4c278db049402030328314d2f79ba26dc4f34afce51e50e0e05d61b253861e5ab47cc47dab500310e038502bfdf255197b6c7929c445580eddc7013470aa85f531e89cd595628576ef6020303295e1973d07a067f281e3337e756bacf10dcc295f7074564874ea4401eb2a4e503de12047d931a054019fb113a4c5d531be2d56ec3d20f99b1628f4d617b15da1c02030361cb373dd54af082c98abe4331b16f360ae70b82b3f111dfe54eab9bb47a85f0031bf72cc92e51f3f38f18d4d0a77c173ee78ae62dce6027288dd37d7f1024df600203032d8279aaf065d93b0e811dfa25bb7c19325ad2e7f99cad95d0737c5390500982036bdc41d82cbe612f8caa639dda471df1d8efe19aba0f39e884b0569c597f68ea020302030320c7fa871d9cbf1112255d49920d07bf151532323d32ceb6ad4da291fad9327403fccd5f970aaf4f45086402c560eeb209d84b4da278dc69f17e3426ba0b273f890203020303c3d3043a6c5a67ae707239a66070748c2efc09d25efbcca01ed86206919ee23d03407bd9bd8d77985f52cc5d8781fc24a200ae2f8bdbaa77b753f7f245f6814c87020302030203020302030365f66ec8e09bf15d73a83402fdc462cbcc40578fdf5d4ef85bbfbf9b5ea5e002039ca41cc26f222ece8fb37316d9436cb914d7041ed51f1d5d3831b735ae2f0721020302030203020302030203020303ce8a414b8283b20263f621799a194ddf5d753bef21ab8253b41de0ba8adf661003a8e38716cdd8a09095a7036c686009bd8236b1c7eb9507540fb981baa9a8bc4b020302030203037ca01b97c87ac12c8995d3c80f7aab3313747ace5a829f08eb68381a5a9fc54003e5554fbb47341d48f82f64a26d175a7d3559378657e77cf2de2eff917b95be300203020303512c2cf0ab4340a1623bdddc301aa586932f9413ea9bf8c0f1849d2d70d5d0ff0375d0cc499c7f76c70939fd8d633c658747eebf0eb138c15d902c34f0de9098030203020303b78dcbd59a3668396357cbda038d7e5bc77aac4acdb3cd3a75e96eb05079a6bf03ceb3ed2850bca5df0bd69ce2e85e9daff43cdb4c79f58685340f521521a0943f0203020302030201034b33ab5a3b8d3b01c374c1d7fcfc714398b7f0704ba2f1ee757670269fd5a7f7020302020203070354000000000000000000000000000000005ca1ab1e582000000000000000000000000000000000000000000000000000000000000000004383b69e070354000000000000000000000000000000005ca1ab1e582010c18923d58801103b7e76ccd81e81a281713d174575a74b2ef0341f6b9a42fd5820b8b76bb549992d9bfc44e3b36d087a175b2e78b9584fc752eaa3013e0bdd31e8070354000000000000000000000000000000005ca1ab1e58209bd14ac8c1cf553e0ad3a2c109b9871eb74f3c116bf0bf492ef04d2983722555582090629dad0a40430445b7d2b25a8d19c5d7a929608ed7890877a499aaca01ca5002030315edee06840e36ef17d13817ab5475d12f7bd50113984febf31e2cd80c08952c03d360a7d78676862429feb7c95d052c1e63379b8ad3becf085a21baa353ab93d30203037a2fb952c2cf8e85d9706bcbcb5a69b83b13403b58f06b0767f4204acc5917930310de142eb3b2790cf1e3694b72eecc7e8ab3860f543c15cc24274ff69570f009020303879875563fe8a079ef71e84b6840b187c681499095de9d02d8b101c9dfcd111e0395e9fc3b000e49b65678f256d247786f72c91494c960d117b7668045c35502720203034c4d4621925c12b3878ebf267a1a013cc3d5675903cb0e22bc6d1df0bace3f8d03c092e19f1fd097b76813fc2412338735dab2f62302645b0e72d195b68a1e4d4702030350620914ec3787f2d03c118a874edb853c9678a3949ce426fc19489744df65e2033a2bcd06528de10b0bf7c316956d5af798ce85d8618011a8db4df56202c17f27020303c27ba5c9e177fdba8afc9cd524bb5616116bb12aac5aa30d1918e36228883fda03003a4d0233fc2ff4bfd5cb02b70ae195150d4d18b59449829e612204e831187b0203038cec528699f0b6819a574be7bea0d083f5999e462c6a464a37c582392140762a0393afd21f19e4329c0ef6b1b06baf963080c2980a73c5937cd6322ef7dc631dc00203038cf4931c97d6aa8c453db3175ebdf27d40e4e34b2b3ac67e8888dc34556a99b603cd716cb8821688b0df7e56b2c31036c17c53a5f6d50b50cfd4e68d30d2420120020303b81ba13ab693dd6dffd70ba32f7bd51fbd5ecd3f58bd8ee96d7b081dbe45efa803dff9ee8db1218deb4733e71215a2e1629d8c9f5e36bc0b8184d70f2ea6e8e01d0203031aafd4025804cbeabfe796224eda42a75577ec804c615abc88953b7c966766a4034baee3dbeedfb1b839869d087bbadb64bd8d4007cef5bfcd038c7f8436c4b7e5020303f659d8fb79866e5a2f9479b24ca74b34dae4e211e6a758e376a1407294fd840e032e9950f2c2283fc366c78f61d806a412a244bebf4dca45f250dceff31fd3a2a802030203020303375268372cd898f2295ec6c9a9838412658bf8a9ba5c309854a92dd747e4eb3c03bf0048ab25caf15956b958175c59038226d0331be1767f2c00ae19bc9f70f9ff020302030203030290f4c412920a6ea22d4ec8091a90d63fc62609d3e55e44da20097cdd8204430338962fdeb56eeda46eb38c254e32bd4fa863167913801664a58d773fa3a4882f02030203020303b83955a533913a8e816c0a9e001379dcbb9a89e48410b365841c552e93987a4a03e42aa480068387d975b85b52ab67acc0d5de816085765f419fec172afc69df34020302030203030e1f9af6f9a3833c51c53d2ee2b598421c0227dc651646350725e51762077ea3039ad12ef2e43458f28d5267d58f355ca92e3f625a595042518e0ccf8b0d4e96e002030203020302030203020303e8bb2dae757d043417195292bac773cda990500845f91a94d00179fe89525c3e039f7fc724f0bd8bd083a725fa7d2c8169bd8ca33d31c9146805940f0ee480c3dd02030203037d8dcb012bdde19a0dd178c1de91d21cc866a76b9b6315554fec4bc4f5daa7920383f4a4890e8cd73e6e32096e4b11c5c0c50991dff65297720ea9ab7b8ccf3ef302030203020303c998c4c602a03cfa0c92a1542165567f11d23f2ae5fb91d04e02292f8e297548039447848097d9500f21ebe819789f98461e01aff7cfcd442c8afe8e07b87a95690203020303f6441de6ba2bc5cc9ead4300519a94a14a80b85f2fb0fa41e2211d7c02af0e6703703a99e4c2133e89a6e892863f14f143cf5f2ad94bd527081c8be6143f14f3db020302030203031da626649ee092857f195eb0059639309d744972389a4e748c471f16b0fc3c2e03f4072cd036d5bbb777ad24fa0b1a235806ef25737404d7ce4f83babb76bf090802030203020303c7f6a9615846a2d627db4c940098479bce3c61c8fc1170d0b7f8c9ac2bec5ea4033664667108158e9377b49cf3632952090b6eab3ba6eaed4f48ca9d5beb273fd002010203070354000000000000000000000000000000005ca1ab1e5820eff9a5f21a1dc5ce907981aedce8e1f0d94116f871970a4c9488b2a6813ffd41582021bb230cc7b5a15416d28b65e85327b729b384a46e7d1208f17d1d74e498f445020102030203070354000000000000000000000000000000005ca1ab1e5820cfe58c94626d82e54c34444223348c504ae148f1e79868731da9b44fc91ddfd4582040fc722808ecb16a4f1cb2e145abfb2b8eb9d731283dbb46fe013c0e3441dfbc070354000000000000000000000000000000005ca1ab1e58200000000000000000000000000000000000000000000000000000000000000002446745baae070354000000000000000000000000000000005ca1ab1e5820bc32590bee71a54e9565024aca9df30307a01cf8f23561baed0ef54b30f5be68582007b7aa19b2ab0ca977cf557ea4cec4c0b84b00a9430cfe0323877011fa03269c020203e752c67cd3ffa4dacba7194a973b10998b056b5b936d17da2a8eae591a8e175e020303abdf2e1db4950317aadeff604ae51ac75e8281b1ea22e7f17349757c71dca21d03fd88dafa9a26e9c8f617b5de3e1a6021092dbff5c1fdb5d8efaeecb1f333565c020303b8a363b96519cb0eed132d807f6e42ea35f115584c443a74b14a19bbeac463d7038247bf369e033fcc19a5797960f1387f04f80cf396babac560060887288632db0203033497b9767463d12616a5b29b2d66156e49b3cccfe6598e2e73d90190e04a15120384203647fe683ea28ce78395875f0bc39f1fe1ce6c9670b8393161514dab4701020303be9a5bc3511c4f8466f2316f7d83370249d71f17357eda7cd7135b86b136090703f4ab8e9e9441ad40280807e7462de0147c3471983825b8f70b6066331bc7fdac020303fedefede445ae76cbb5f94df4e543537d9404b97450cef183b95f2fade25aa250384deb1e0e5121e700221d4f1ed13d488aaa4275c46416c2003d8d1eddfd78c180203035ed3f2ee894a97e0bd924d79ee091e8430799ba07601928b7912280a2260717b0365ca974c72e3e2db5912a9c87c5d5f84c2825a1f0196fa4793cee99d4d173351020303678452244e14f477cf17190203e3d2741cde4dada731914ad7987d94a95fe953030018accfc6cce1d6b0b884be4fdf54fc21d7f2b0f320711abea4aaf2dfe49d52020303a72a40bab31ce553d4d303b80f98837eb346dc83decd766ed763739ccaeb1d0f0334cd6263be1472af1b73f09f382ab57941ac5043ccd4feb8c4abeb0f2b0a869702030320b1e0149d703853b7ff9219e776215178aeed36a0c047635656c8a804e0f73f031e01e1d5be3de353277a132daf82f484854a9f96ba3555c42f03f63dac8a72db020302030341ad95a71f5d9ac2a4472b72437b529d9683cd3110874426bf5a3cf9fcb979a703a054c300828ecfa19bda2ca0f4d770134b6812dadef254990956f75f418010920203020303674f36f2a847c25e092483b595d6d69338bbf807516d5b424e3ab05fc95719cd031b713460225852cb9a5005429fdfdc44c8d71153e1aa04364570343a434c6388020302030325b7b7ced4578ad2120d4703c775f82d4fcf6ff0578d60d76b1b3d5bf982812e03ee4f0964b55a782cc4128ae4291d947dfd63300081d89319ddc357b6e9a7d365020302030360d8ba62d7544d9d354731fc286a41c33869212a45b7322d2c4e67504738e65103b0ae0178f65708516a57beaa38a2cd4d344fe8f3a217f1fe9c4cb2d41b2975b502030203020303b5419117efdf04c24efdb20fe6e1786d53529a5630a98108d2118c7dca7c136e03aaa09bc73d06dc35034f97fec917652f664c4d768d0c036b2539e661f2d8fc380203020302030203020303d51b1bcd3eab3b6a6c384a831ec8080cab63c1c44d898bd626248194376fe1de037ecc252f46692f6e8959f226a5e94a4ac314d38000dabd3c66c06489789651bc0203020303248599a8b4c29a8dfd17c29080088b4573842ac4a1fc2fb628f1f65350cbc2bb034f7ae2704632668f91a5605aa808807c7c83f999d35def3d40f72a825eb561ec020302030203020302030203020302030392af697495712b4013883b3f5ad2d370bdb93f0ed60416692b0267f10d9a3caa0386fa8ccd91ab622b232223f9a347f1785ca9c4b7323a2e0d19a7971c3afd63ff0203020303b4f12607fb8df583b854d7b235f4a64ccb2f4bc9819dc50f3a03ed0d4906910e038f64a125d14bb92752d65593faae8e41bb5e80e4f147b20f0c247078f6e7ca77070354000000000000000000000000000000005ca1ab1e58202d11035f2912c26c30c4f8957d3910a20622ea8709c8cd3e0ad87fa0f4460bbb5820c0bf0b2ab68768eaabe5fda7814227beaeaf4c4ee7e67f5d07aefaf5f0410ab80203034d5eb602925f13a2147a2c1439d43faa74e2561bb3d27811f02042466fb2804f035d9458bc537a1957fddbf6c5f13c6bfc9349abf1251df9d6dd48b5b574f6f48f020303bbf6401ad2a6b95a3e749f5b31224fc7fcdd083e7aeac9671ec3bebda312fe5c03393a914dd0b171b4cca2f5cef52cb4ed4b564278c0fb678e5e8f3d911b4addb302030356cdb16849ae7aff540b8724f73974149f71cd3f984360537159a273a5bfcc1d03791ad7bed137c9501bcee55dc6c9b030157a30c37fca14d39c25d9d5137ae88b02030392940198d6b1df58f0a6c3cc1da02efd9547043d626487166ec858a5aae7b61903efbf993292364275b60efda91d4c18f5a87549ebd407ba16763b1f0c6113a6cb0203039691d481d60a086435d9f914e8e2b5e5a68abfafb82dcc9d6de2176920c35ded03347f67f0fbbc63fa8a3b826c6491f42b13869a2abd2b6326d75d51cb30ea9cf1020303d822fd2ee43799eb3f714589ce7bea36550c8fe4a90b5a2aa68a95486490962c03718887bb3381bda095c52048955b7ce2e5b35e5bec11515e9fa86187fa9e0fa70203032d3bd6d97e29b994d2d73867b8e19f6b3b2a43d5992a6b9b3f350f8834c0da9a039f1aca3992b3c769a6735830359c9153c2daee33d937c9b903a05ed9ada8f5d0020303bddef4e591830295d3c1f6e27decf301748434c3df51aa8b451558ee7962deea03926ffe488854b96b623872e9a54c6e5bb737fa12f402bd8a2a79f34000ba21520203037b0ddd3f35449b7e435243005ad2a536fa28167cf7da21ecd2d4c3a55e6421a6030fd4916c5757cb6e137fac5d203ba3d5d50a5077a06e3804faa80e9783e2367d0203036fff8395e24c14d9e40936920b141c84e2127ed823a1625699eaebd1f26b69c703069db41eccbdb4aa227cb482a97d6b342d0413855bef3c9b432d74ef0be43e0b" +) diff --git a/smt/pkg/smt/witness_utils.go b/smt/pkg/smt/witness_utils.go new file mode 100644 index 00000000000..5aadf4d6cdf --- /dev/null +++ b/smt/pkg/smt/witness_utils.go @@ -0,0 +1,11 @@ +package smt + +import "fmt" + +func intArrayToString(a []int) string { + s := "" + for _, v := range a { + s += fmt.Sprintf("%d", v) + } + return s +} diff --git a/turbo/cli/default_flags.go b/turbo/cli/default_flags.go index 618a6c5bdc1..35a753021c2 100644 --- a/turbo/cli/default_flags.go +++ b/turbo/cli/default_flags.go @@ -291,5 +291,7 @@ var DefaultFlags = []cli.Flag{ &utils.InfoTreeUpdateInterval, &utils.SealBatchImmediatelyOnOverflow, &utils.MockWitnessGeneration, + &utils.WitnessCacheEnable, + &utils.WitnessCacheLimit, &utils.WitnessContractInclusion, } diff --git a/turbo/cli/flags_zkevm.go b/turbo/cli/flags_zkevm.go index 9aa4cecca5b..e71fb621ecc 100644 --- a/turbo/cli/flags_zkevm.go +++ b/turbo/cli/flags_zkevm.go @@ -131,6 +131,13 @@ func ApplyFlagsForZkConfig(ctx *cli.Context, cfg *ethconfig.Config) { badBatches = append(badBatches, val) } + // witness cache flags + // if dicabled, set limit to 0 and only check for it to be 0 or not + witnessCacheEnabled := ctx.Bool(utils.WitnessCacheEnable.Name) + witnessCacheLimit := ctx.Uint64(utils.WitnessCacheLimit.Name) + if !witnessCacheEnabled { + witnessCacheLimit = 0 + } var witnessInclusion []libcommon.Address for _, s := range strings.Split(ctx.String(utils.WitnessContractInclusion.Name), ",") { if s == "" { @@ -219,6 +226,7 @@ func ApplyFlagsForZkConfig(ctx *cli.Context, cfg *ethconfig.Config) { InfoTreeUpdateInterval: ctx.Duration(utils.InfoTreeUpdateInterval.Name), SealBatchImmediatelyOnOverflow: ctx.Bool(utils.SealBatchImmediatelyOnOverflow.Name), MockWitnessGeneration: ctx.Bool(utils.MockWitnessGeneration.Name), + WitnessCacheLimit: witnessCacheLimit, WitnessContractInclusion: witnessInclusion, } diff --git a/turbo/jsonrpc/zkevm_api.go b/turbo/jsonrpc/zkevm_api.go index 14cf4baaea5..dfd8814026c 100644 --- a/turbo/jsonrpc/zkevm_api.go +++ b/turbo/jsonrpc/zkevm_api.go @@ -9,7 +9,6 @@ import ( "github.com/ledgerwatch/erigon-lib/chain" "github.com/ledgerwatch/erigon-lib/common" - libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/log/v3" @@ -35,6 +34,7 @@ import ( "github.com/ledgerwatch/erigon/smt/pkg/smt" smtUtils "github.com/ledgerwatch/erigon/smt/pkg/utils" "github.com/ledgerwatch/erigon/turbo/rpchelper" + "github.com/ledgerwatch/erigon/turbo/trie" "github.com/ledgerwatch/erigon/zk/datastream/server" "github.com/ledgerwatch/erigon/zk/hermez_db" "github.com/ledgerwatch/erigon/zk/legacy_executor_verifier" @@ -44,7 +44,6 @@ import ( "github.com/ledgerwatch/erigon/zk/syncer" zktx "github.com/ledgerwatch/erigon/zk/tx" "github.com/ledgerwatch/erigon/zk/utils" - zkUtils "github.com/ledgerwatch/erigon/zk/utils" "github.com/ledgerwatch/erigon/zk/witness" "github.com/ledgerwatch/erigon/zkevm/hex" "github.com/ledgerwatch/erigon/zkevm/jsonrpc/client" @@ -832,7 +831,7 @@ func (api *ZkEvmAPIImpl) GetFullBlockByNumber(ctx context.Context, number rpc.Bl // GetFullBlockByHash returns a full block from the current canonical chain. If number is nil, the // latest known block is returned. -func (api *ZkEvmAPIImpl) GetFullBlockByHash(ctx context.Context, hash libcommon.Hash, fullTx bool) (types.Block, error) { +func (api *ZkEvmAPIImpl) GetFullBlockByHash(ctx context.Context, hash common.Hash, fullTx bool) (types.Block, error) { tx, err := api.db.BeginRo(ctx) if err != nil { return types.Block{}, err @@ -974,7 +973,6 @@ func (api *ZkEvmAPIImpl) GetBlockRangeWitness(ctx context.Context, startBlockNrO } func (api *ZkEvmAPIImpl) getBatchWitness(ctx context.Context, tx kv.Tx, batchNum uint64, debug bool, mode WitnessMode) (hexutility.Bytes, error) { - // limit in-flight requests by name semaphore := api.semaphores[getBatchWitness] if semaphore != nil { @@ -989,14 +987,44 @@ func (api *ZkEvmAPIImpl) getBatchWitness(ctx context.Context, tx kv.Tx, batchNum if api.ethApi.historyV3(tx) { return nil, fmt.Errorf("not supported by Erigon3") } - - generator, fullWitness, err := api.buildGenerator(ctx, tx, mode) + reader := hermez_db.NewHermezDbReader(tx) + badBatch, err := reader.GetInvalidBatch(batchNum) if err != nil { return nil, err } - return generator.GetWitnessByBatch(tx, ctx, batchNum, debug, fullWitness) + if !badBatch { + blockNumbers, err := reader.GetL2BlockNosByBatch(batchNum) + if err != nil { + return nil, err + } + if len(blockNumbers) == 0 { + return nil, fmt.Errorf("no blocks found for batch %d", batchNum) + } + var startBlock, endBlock uint64 + for _, blockNumber := range blockNumbers { + if startBlock == 0 || blockNumber < startBlock { + startBlock = blockNumber + } + if blockNumber > endBlock { + endBlock = blockNumber + } + } + + startBlockInt := rpc.BlockNumber(startBlock) + endBlockInt := rpc.BlockNumber(endBlock) + + startBlockRpc := rpc.BlockNumberOrHash{BlockNumber: &startBlockInt} + endBlockNrOrHash := rpc.BlockNumberOrHash{BlockNumber: &endBlockInt} + return api.getBlockRangeWitness(ctx, api.db, startBlockRpc, endBlockNrOrHash, debug, mode) + } else { + generator, fullWitness, err := api.buildGenerator(ctx, tx, mode) + if err != nil { + return nil, err + } + return generator.GetWitnessByBadBatch(tx, ctx, batchNum, debug, fullWitness) + } } func (api *ZkEvmAPIImpl) buildGenerator(ctx context.Context, tx kv.Tx, witnessMode WitnessMode) (*witness.Generator, bool, error) { @@ -1043,7 +1071,6 @@ func (api *ZkEvmAPIImpl) getBlockRangeWitness(ctx context.Context, db kv.RoDB, s } endBlockNr, _, _, err := rpchelper.GetCanonicalBlockNumber_zkevm(endBlockNrOrHash, tx, api.ethApi.filters) // DoCall cannot be executed on non-canonical blocks - if err != nil { return nil, err } @@ -1052,6 +1079,41 @@ func (api *ZkEvmAPIImpl) getBlockRangeWitness(ctx context.Context, db kv.RoDB, s return nil, fmt.Errorf("start block number must be less than or equal to end block number, start=%d end=%d", blockNr, endBlockNr) } + hermezDb := hermez_db.NewHermezDbReader(tx) + + // we only keep trimmed witnesses in the db + if witnessMode == WitnessModeTrimmed { + blockWitnesses := make([]*trie.Witness, 0, endBlockNr-blockNr+1) + //try to get them from the db, if all are available - do not unwind and generate + for blockNum := blockNr; blockNum <= endBlockNr; blockNum++ { + witnessBytes, err := hermezDb.GetWitnessCache(blockNum) + if err != nil { + return nil, err + } + + if len(witnessBytes) == 0 { + break + } + + blockWitness, err := witness.ParseWitnessFromBytes(witnessBytes, false) + if err != nil { + return nil, err + } + + blockWitnesses = append(blockWitnesses, blockWitness) + } + + if len(blockWitnesses) == int(endBlockNr-blockNr+1) { + // found all, calculate + baseWitness, err := witness.MergeWitnesses(ctx, blockWitnesses) + if err != nil { + return nil, err + } + + return witness.GetWitnessBytes(baseWitness, debug) + } + } + generator, fullWitness, err := api.buildGenerator(ctx, tx, witnessMode) if err != nil { return nil, err @@ -1296,11 +1358,6 @@ func getLastBlockInBatchNumber(tx kv.Tx, batchNumber uint64) (uint64, error) { return blocks[len(blocks)-1], nil } -func getAllBlocksInBatchNumber(tx kv.Tx, batchNumber uint64) ([]uint64, error) { - reader := hermez_db.NewHermezDbReader(tx) - return reader.GetL2BlockNosByBatch(batchNumber) -} - func getLatestBatchNumber(tx kv.Tx) (uint64, error) { c, err := tx.Cursor(hermez_db.BLOCKBATCHES) if err != nil { @@ -1374,68 +1431,6 @@ func getForkIntervals(tx kv.Tx) ([]rpc.ForkInterval, error) { return result, nil } -func convertTransactionsReceipts( - txs []eritypes.Transaction, - receipts eritypes.Receipts, - hermezReader hermez_db.HermezDbReader, - block eritypes.Block) ([]types.Transaction, error) { - if len(txs) != len(receipts) { - return nil, errors.New("transactions and receipts length mismatch") - } - - result := make([]types.Transaction, 0, len(txs)) - - for idx, tx := range txs { - effectiveGasPricePercentage, err := hermezReader.GetEffectiveGasPricePercentage(tx.Hash()) - if err != nil { - return nil, err - } - gasPrice := tx.GetPrice() - v, r, s := tx.RawSignatureValues() - var sender common.Address - - // TODO: senders! - - var receipt *types.Receipt - if len(receipts) > idx { - receipt = convertReceipt(receipts[idx], sender, tx.GetTo(), gasPrice, effectiveGasPricePercentage) - } - - bh := block.Hash() - blockNumber := block.NumberU64() - - tran := types.Transaction{ - Nonce: types.ArgUint64(tx.GetNonce()), - GasPrice: types.ArgBig(*gasPrice.ToBig()), - Gas: types.ArgUint64(tx.GetGas()), - To: tx.GetTo(), - Value: types.ArgBig(*tx.GetValue().ToBig()), - Input: tx.GetData(), - V: types.ArgBig(*v.ToBig()), - R: types.ArgBig(*r.ToBig()), - S: types.ArgBig(*s.ToBig()), - Hash: tx.Hash(), - From: sender, - BlockHash: &bh, - BlockNumber: types.ArgUint64Ptr(types.ArgUint64(blockNumber)), - TxIndex: types.ArgUint64Ptr(types.ArgUint64(idx)), - Type: types.ArgUint64(tx.Type()), - Receipt: receipt, - } - - cid := tx.GetChainID() - var cidAB *types.ArgBig - if cid.Cmp(uint256.NewInt(0)) != 0 { - cidAB = (*types.ArgBig)(cid.ToBig()) - tran.ChainID = cidAB - } - - result = append(result, tran) - } - - return result, nil -} - func convertBlockToRpcBlock( orig *eritypes.Block, receipts eritypes.Receipts, @@ -1653,7 +1648,7 @@ func (zkapi *ZkEvmAPIImpl) GetProof(ctx context.Context, address common.Address, batch := membatchwithdb.NewMemoryBatch(tx, api.dirs.Tmp, api.logger) defer batch.Rollback() - if err = zkUtils.PopulateMemoryMutationTables(batch); err != nil { + if err = utils.PopulateMemoryMutationTables(batch); err != nil { return nil, err } @@ -1713,13 +1708,12 @@ func (zkapi *ZkEvmAPIImpl) GetProof(ctx context.Context, address common.Address, plainState := state.NewPlainState(tx, blockNumber, systemcontracts.SystemContractCodeLookup[chainCfg.ChainName]) defer plainState.Close() - inclusion := make(map[libcommon.Address][]libcommon.Hash) + inclusion := make(map[common.Address][]common.Hash) for _, contract := range zkapi.config.WitnessContractInclusion { - err = plainState.ForEachStorage(contract, libcommon.Hash{}, func(key, secKey libcommon.Hash, value uint256.Int) bool { + if err = plainState.ForEachStorage(contract, common.Hash{}, func(key, secKey common.Hash, value uint256.Int) bool { inclusion[contract] = append(inclusion[contract], key) return false - }, math.MaxInt64) - if err != nil { + }, math.MaxInt64); err != nil { return nil, err } } @@ -1779,7 +1773,7 @@ func (zkapi *ZkEvmAPIImpl) GetProof(ctx context.Context, address common.Address, accProof := &accounts.SMTAccProofResult{ Address: address, Balance: (*hexutil.Big)(balance), - CodeHash: libcommon.BytesToHash(codeHash), + CodeHash: common.BytesToHash(codeHash), CodeLength: hexutil.Uint64(codeLength), Nonce: hexutil.Uint64(nonce), BalanceProof: balanceProofs, @@ -1921,7 +1915,7 @@ func (api *ZkEvmAPIImpl) GetRollupManagerAddress(ctx context.Context) (res json. return rollupManagerAddressJson, err } -func (api *ZkEvmAPIImpl) getInjectedBatchAccInputHashFromSequencer(rpcUrl string) (*libcommon.Hash, error) { +func (api *ZkEvmAPIImpl) getInjectedBatchAccInputHashFromSequencer(rpcUrl string) (*common.Hash, error) { res, err := client.JSONRPCCall(rpcUrl, "zkevm_getBatchByNumber", 1) if err != nil { return nil, err @@ -1948,7 +1942,7 @@ func (api *ZkEvmAPIImpl) getInjectedBatchAccInputHashFromSequencer(rpcUrl string return nil, fmt.Errorf("accInputHash is not a string") } - decoded := libcommon.HexToHash(hash) + decoded := common.HexToHash(hash) return &decoded, nil } diff --git a/turbo/stages/zk_stages.go b/turbo/stages/zk_stages.go index a585503c0e0..88d0deb1acb 100644 --- a/turbo/stages/zk_stages.go +++ b/turbo/stages/zk_stages.go @@ -80,6 +80,7 @@ func NewDefaultZkStages(ctx context.Context, ), stagedsync.StageHashStateCfg(db, dirs, cfg.HistoryV3, agg), zkStages.StageZkInterHashesCfg(db, true, true, false, dirs.Tmp, blockReader, controlServer.Hd, cfg.HistoryV3, agg, cfg.Zk), + zkStages.StageWitnessCfg(db, cfg.Zk, controlServer.ChainConfig, engine, blockReader, agg, cfg.HistoryV3, dirs, cfg.WitnessContractInclusion), stagedsync.StageHistoryCfg(db, cfg.Prune, dirs.Tmp), stagedsync.StageLogIndexCfg(db, cfg.Prune, dirs.Tmp, cfg.Genesis.Config.NoPruneContracts), stagedsync.StageCallTracesCfg(db, cfg.Prune, 0, dirs.Tmp), diff --git a/turbo/trie/witness.go b/turbo/trie/witness.go index 3f309be40e5..874fe5eb966 100644 --- a/turbo/trie/witness.go +++ b/turbo/trie/witness.go @@ -118,6 +118,8 @@ func NewWitnessFromReader(input io.Reader, trace bool) (*Witness, error) { op = &OperatorCode{} case OpBranch: op = &OperatorBranch{} + case OpSMTLeaf: + op = &OperatorSMTLeafValue{} case OpEmptyRoot: op = &OperatorEmptyRoot{} case OpExtension: @@ -173,81 +175,98 @@ func (w *Witness) WriteDiff(w2 *Witness, output io.Writer) { op = w.Operators[i] } if i >= len(w2.Operators) { - fmt.Fprintf(output, "unexpected o1[%d] = %T %v; o2[%d] = nil\n", i, op, op, i) + fmt.Fprintf(output, "missing in o2: o1[%d] = %T %v;\n", i, op, op) continue } + op2 := w2.Operators[i] switch o1 := op.(type) { case *OperatorBranch: - o2, ok := w2.Operators[i].(*OperatorBranch) + o2, ok := op2.(*OperatorBranch) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) - } - if o1.Mask != o2.Mask { - fmt.Fprintf(output, "o1[%d].Mask = %v; o2[%d].Mask = %v", i, o1.Mask, i, o2.Mask) + fmt.Fprintf(output, "OperatorBranch: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else if o1.Mask != o2.Mask { + fmt.Fprintf(output, "OperatorBranch: o1[%d].Mask = %v; o2[%d].Mask = %v", i, o1.Mask, i, o2.Mask) } case *OperatorHash: - o2, ok := w2.Operators[i].(*OperatorHash) + o2, ok := op2.(*OperatorHash) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) - } - if !bytes.Equal(o1.Hash.Bytes(), o2.Hash.Bytes()) { - fmt.Fprintf(output, "o1[%d].Hash = %s; o2[%d].Hash = %s\n", i, o1.Hash.Hex(), i, o2.Hash.Hex()) + fmt.Fprintf(output, "OperatorHash: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else if !bytes.Equal(o1.Hash.Bytes(), o2.Hash.Bytes()) { + fmt.Fprintf(output, "OperatorHash: o1[%d].Hash = %s; o2[%d].Hash = %s\n", i, o1.Hash.Hex(), i, o2.Hash.Hex()) } case *OperatorCode: - o2, ok := w2.Operators[i].(*OperatorCode) + o2, ok := op2.(*OperatorCode) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) - } - if !bytes.Equal(o1.Code, o2.Code) { - fmt.Fprintf(output, "o1[%d].Code = %x; o2[%d].Code = %x\n", i, o1.Code, i, o2.Code) + fmt.Fprintf(output, "OperatorCode: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else if !bytes.Equal(o1.Code, o2.Code) { + fmt.Fprintf(output, "OperatorCode: o1[%d].Code = %x; o2[%d].Code = %x\n", i, o1.Code, i, o2.Code) } case *OperatorEmptyRoot: - o2, ok := w2.Operators[i].(*OperatorEmptyRoot) + _, ok := op2.(*OperatorEmptyRoot) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) + fmt.Fprintf(output, "OperatorEmptyRoot: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) } case *OperatorExtension: - o2, ok := w2.Operators[i].(*OperatorExtension) + o2, ok := op2.(*OperatorExtension) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) - } - if !bytes.Equal(o1.Key, o2.Key) { - fmt.Fprintf(output, "extension o1[%d].Key = %x; o2[%d].Key = %x\n", i, o1.Key, i, o2.Key) + fmt.Fprintf(output, "OperatorExtension: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else if !bytes.Equal(o1.Key, o2.Key) { + fmt.Fprintf(output, "OperatorExtension: o1[%d].Key = %x; o2[%d].Key = %x\n", i, o1.Key, i, o2.Key) } case *OperatorLeafAccount: - o2, ok := w2.Operators[i].(*OperatorLeafAccount) + o2, ok := op2.(*OperatorLeafAccount) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) - } - if !bytes.Equal(o1.Key, o2.Key) { - fmt.Fprintf(output, "leafAcc o1[%d].Key = %x; o2[%d].Key = %x\n", i, o1.Key, i, o2.Key) - } - if o1.Nonce != o2.Nonce { - fmt.Fprintf(output, "leafAcc o1[%d].Nonce = %v; o2[%d].Nonce = %v\n", i, o1.Nonce, i, o2.Nonce) - } - if o1.Balance.String() != o2.Balance.String() { - fmt.Fprintf(output, "leafAcc o1[%d].Balance = %v; o2[%d].Balance = %v\n", i, o1.Balance.String(), i, o2.Balance.String()) - } - if o1.HasCode != o2.HasCode { - fmt.Fprintf(output, "leafAcc o1[%d].HasCode = %v; o2[%d].HasCode = %v\n", i, o1.HasCode, i, o2.HasCode) - } - if o1.HasStorage != o2.HasStorage { - fmt.Fprintf(output, "leafAcc o1[%d].HasStorage = %v; o2[%d].HasStorage = %v\n", i, o1.HasStorage, i, o2.HasStorage) + fmt.Fprintf(output, "OperatorLeafAccount: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else { + if !bytes.Equal(o1.Key, o2.Key) { + fmt.Fprintf(output, "OperatorLeafAccount: o1[%d].Key = %x; o2[%d].Key = %x\n", i, o1.Key, i, o2.Key) + } + if o1.Nonce != o2.Nonce { + fmt.Fprintf(output, "OperatorLeafAccount: o1[%d].Nonce = %v; o2[%d].Nonce = %v\n", i, o1.Nonce, i, o2.Nonce) + } + if o1.Balance.String() != o2.Balance.String() { + fmt.Fprintf(output, "OperatorLeafAccount: o1[%d].Balance = %v; o2[%d].Balance = %v\n", i, o1.Balance.String(), i, o2.Balance.String()) + } + if o1.HasCode != o2.HasCode { + fmt.Fprintf(output, "OperatorLeafAccount: o1[%d].HasCode = %v; o2[%d].HasCode = %v\n", i, o1.HasCode, i, o2.HasCode) + } + if o1.HasStorage != o2.HasStorage { + fmt.Fprintf(output, "OperatorLeafAccount: o1[%d].HasStorage = %v; o2[%d].HasStorage = %v\n", i, o1.HasStorage, i, o2.HasStorage) + } } case *OperatorLeafValue: - o2, ok := w2.Operators[i].(*OperatorLeafValue) + o2, ok := op2.(*OperatorLeafValue) if !ok { - fmt.Fprintf(output, "o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) + fmt.Fprintf(output, "OperatorLeafValue: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else { + if !bytes.Equal(o1.Key, o2.Key) { + fmt.Fprintf(output, "OperatorLeafValue: o1[%d].Key = %x; o2[%d].Key = %x\n", i, o1.Key, i, o2.Key) + } + if !bytes.Equal(o1.Value, o2.Value) { + fmt.Fprintf(output, "OperatorLeafValue: o1[%d].Value = %x; o2[%d].Value = %x\n", i, o1.Value, i, o2.Value) + } } - if !bytes.Equal(o1.Key, o2.Key) { - fmt.Fprintf(output, "leafVal o1[%d].Key = %x; o2[%d].Key = %x\n", i, o1.Key, i, o2.Key) - } - if !bytes.Equal(o1.Value, o2.Value) { - fmt.Fprintf(output, "leafVal o1[%d].Value = %x; o2[%d].Value = %x\n", i, o1.Value, i, o2.Value) + case *OperatorSMTLeafValue: + o2, ok := op2.(*OperatorSMTLeafValue) + if !ok { + fmt.Fprintf(output, "OperatorSMTLeafValue: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) + } else { + if !bytes.Equal(o1.Address, o2.Address) { + fmt.Fprintf(output, "OperatorSMTLeafValue: o1[%d].Address = %x; o2[%d].Address = %x\n", i, o1.Address, i, o2.Address) + } + if !bytes.Equal(o1.StorageKey, o2.StorageKey) { + fmt.Fprintf(output, "OperatorSMTLeafValue: o1[%d].StorageKey = %x; o2[%d].StorageKey = %x\n", i, o1.StorageKey, i, o2.StorageKey) + } + if !bytes.Equal(o1.Value, o2.Value) { + fmt.Fprintf(output, "OperatorSMTLeafValue: o1[%d].Value = %x; o2[%d].Value = %x\n", i, o1.Value, i, o2.Value) + } + if o1.NodeType != o2.NodeType { + fmt.Fprintf(output, "OperatorSMTLeafValue: o1[%d].NodeType = %d; o2[%d].NodeType = %d\n", i, o1.NodeType, i, o2.NodeType) + } } + default: - o2 := w2.Operators[i] - fmt.Fprintf(output, "unexpected o1[%d] = %T %+v; o2[%d] = %T %+v\n", i, o1, o1, i, o2, o2) + fmt.Fprintf(output, "unexpected operator: o1[%d] = %T; o2[%d] = %T\n", i, o1, i, op2) } } } diff --git a/zk/hermez_db/db.go b/zk/hermez_db/db.go index 01e8bdfe3e4..d2eb4961500 100644 --- a/zk/hermez_db/db.go +++ b/zk/hermez_db/db.go @@ -51,7 +51,8 @@ const FORK_HISTORY = "fork_history" // index const JUST_UNWOUND = "just_unwound" // batch number -> true const PLAIN_STATE_VERSION = "plain_state_version" // batch number -> true const ERIGON_VERSIONS = "erigon_versions" // erigon version -> timestamp of startup -const BATCH_ENDS = "batch_ends" // +const BATCH_ENDS = "batch_ends" // batch number -> true +const WITNESS_CACHE = "witness_cache" // block number -> witness for 1 block var HermezDbTables = []string{ L1VERIFICATIONS, @@ -88,6 +89,7 @@ var HermezDbTables = []string{ PLAIN_STATE_VERSION, ERIGON_VERSIONS, BATCH_ENDS, + WITNESS_CACHE, } type HermezDb struct { @@ -1887,3 +1889,20 @@ func (db *HermezDbReader) getForkIntervals(forkIdFilter *uint64) ([]types.ForkIn return forkIntervals, nil } + +func (db *HermezDb) WriteWitnessCache(blockNo uint64, witnessBytes []byte) error { + key := Uint64ToBytes(blockNo) + return db.tx.Put(WITNESS_CACHE, key, witnessBytes) +} + +func (db *HermezDbReader) GetWitnessCache(blockNo uint64) ([]byte, error) { + v, err := db.tx.GetOne(WITNESS_CACHE, Uint64ToBytes(blockNo)) + if err != nil { + return nil, err + } + return v, nil +} + +func (db *HermezDb) DeleteWitnessCaches(from, to uint64) error { + return db.deleteFromBucketWithUintKeysRange(WITNESS_CACHE, from, to) +} diff --git a/zk/l1_data/l1_decoder.go b/zk/l1_data/l1_decoder.go index 4427d9760fa..003e9d0ec5d 100644 --- a/zk/l1_data/l1_decoder.go +++ b/zk/l1_data/l1_decoder.go @@ -14,7 +14,6 @@ import ( "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/zk/contracts" "github.com/ledgerwatch/erigon/zk/da" - "github.com/ledgerwatch/erigon/zk/hermez_db" zktx "github.com/ledgerwatch/erigon/zk/tx" ) @@ -195,7 +194,12 @@ type DecodedL1Data struct { LimitTimestamp uint64 } -func BreakDownL1DataByBatch(batchNo uint64, forkId uint64, reader *hermez_db.HermezDbReader) (*DecodedL1Data, error) { +type l1DecoderHermezReader interface { + GetL1BatchData(batchNo uint64) ([]byte, error) + GetLastL1BatchData() (uint64, error) +} + +func BreakDownL1DataByBatch(batchNo uint64, forkId uint64, reader l1DecoderHermezReader) (*DecodedL1Data, error) { decoded := &DecodedL1Data{} // we expect that the batch we're going to load in next should be in the db already because of the l1 block sync // stage, if it is not there we need to panic as we're in a bad state diff --git a/zk/smt/changes_getter.go b/zk/smt/changes_getter.go new file mode 100644 index 00000000000..0e89700e14a --- /dev/null +++ b/zk/smt/changes_getter.go @@ -0,0 +1,200 @@ +package smt + +import ( + "errors" + "fmt" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/length" + "github.com/ledgerwatch/erigon-lib/kv" + + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/kv/dbutils" + "github.com/ledgerwatch/erigon/core/types/accounts" + + "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/core/systemcontracts" + "github.com/status-im/keycard-go/hexutils" +) + +var ( + ErrAlreadyOpened = errors.New("already opened") + ErrNotOpened = errors.New("not opened") +) + +type changesGetter struct { + tx kv.Tx + + ac kv.CursorDupSort + sc kv.CursorDupSort + psr *state.PlainState + currentPsr *state.PlainStateReader + + accChanges map[common.Address]*accounts.Account + codeChanges map[common.Address]string + storageChanges map[common.Address]map[string]string + + opened bool +} + +func NewChangesGetter(tx kv.Tx) *changesGetter { + return &changesGetter{ + tx: tx, + accChanges: make(map[common.Address]*accounts.Account), + codeChanges: make(map[common.Address]string), + storageChanges: make(map[common.Address]map[string]string), + } +} +func (cg *changesGetter) addDeletedAcc(addr common.Address) { + deletedAcc := new(accounts.Account) + deletedAcc.Balance = *uint256.NewInt(0) + deletedAcc.Nonce = 0 + cg.accChanges[addr] = deletedAcc +} + +func (cg *changesGetter) openChangesGetter(from uint64) error { + if cg.opened { + return ErrAlreadyOpened + } + + ac, err := cg.tx.CursorDupSort(kv.AccountChangeSet) + if err != nil { + return fmt.Errorf("CursorDupSort: %w", err) + } + + sc, err := cg.tx.CursorDupSort(kv.StorageChangeSet) + if err != nil { + return fmt.Errorf("CursorDupSort: %w", err) + } + + cg.ac = ac + cg.sc = sc + cg.psr = state.NewPlainState(cg.tx, from, systemcontracts.SystemContractCodeLookup["Hermez"]) + cg.currentPsr = state.NewPlainStateReader(cg.tx) + + cg.opened = true + + return nil +} + +func (cg *changesGetter) closeChangesGetter() { + if cg.ac != nil { + cg.ac.Close() + } + + if cg.sc != nil { + cg.sc.Close() + } + + if cg.psr != nil { + cg.psr.Close() + } +} + +func (cg *changesGetter) getChangesForBlock(blockNum uint64) error { + if !cg.opened { + return ErrNotOpened + } + + cg.psr.SetBlockNr(blockNum) + dupSortKey := dbutils.EncodeBlockNumber(blockNum) + + // collect changes to accounts and code + for _, v, err2 := cg.ac.SeekExact(dupSortKey); err2 == nil && v != nil; _, v, err2 = cg.ac.NextDup() { + if err := cg.setAccountChangesFromV(v); err != nil { + return fmt.Errorf("failed to get account changes: %w", err) + } + } + + if err := cg.tx.ForPrefix(kv.StorageChangeSet, dupSortKey, cg.setStorageChangesFromKv); err != nil { + return fmt.Errorf("failed to get storage changes: %w", err) + } + + return nil +} + +func (cg *changesGetter) setAccountChangesFromV(v []byte) error { + addr := common.BytesToAddress(v[:length.Addr]) + + // if the account was created in this changeset we should delete it + if len(v[length.Addr:]) == 0 { + cg.codeChanges[addr] = "" + cg.addDeletedAcc(addr) + return nil + } + + oldAcc, err := cg.psr.ReadAccountData(addr) + if err != nil { + return fmt.Errorf("ReadAccountData: %w", err) + } + + // currAcc at block we're unwinding from + currAcc, err := cg.currentPsr.ReadAccountData(addr) + if err != nil { + return fmt.Errorf("ReadAccountData: %w", err) + } + + if oldAcc.Incarnation > 0 { + if len(v) == 0 { // self-destructed + cg.addDeletedAcc(addr) + } else { + if currAcc.Incarnation > oldAcc.Incarnation { + cg.addDeletedAcc(addr) + } + } + } + + // store the account + cg.accChanges[addr] = oldAcc + + if oldAcc.CodeHash != currAcc.CodeHash { + hexcc, err := cg.getCodehashChanges(addr, oldAcc) + if err != nil { + return fmt.Errorf("getCodehashChanges: %w", err) + } + cg.codeChanges[addr] = hexcc + } + + return nil +} + +func (cg *changesGetter) getCodehashChanges(addr common.Address, oldAcc *accounts.Account) (string, error) { + cc, err := cg.currentPsr.ReadAccountCode(addr, oldAcc.Incarnation, oldAcc.CodeHash) + if err != nil { + return "", fmt.Errorf("ReadAccountCode: %w", err) + } + + ach := hexutils.BytesToHex(cc) + hexcc := "" + if len(ach) > 0 { + hexcc = "0x" + ach + } + + return hexcc, nil +} + +func (cg *changesGetter) setStorageChangesFromKv(sk, sv []byte) error { + changesetKey := sk[length.BlockNum:] + address, _ := dbutils.PlainParseStoragePrefix(changesetKey) + + sstorageKey := sv[:length.Hash] + stk := common.BytesToHash(sstorageKey) + + value := []byte{0} + if len(sv[length.Hash:]) != 0 { + value = sv[length.Hash:] + } + + stkk := fmt.Sprintf("0x%032x", stk) + v := fmt.Sprintf("0x%032x", common.BytesToHash(value)) + + m := make(map[string]string) + m[stkk] = v + + if cg.storageChanges[address] == nil { + cg.storageChanges[address] = make(map[string]string) + } + cg.storageChanges[address][stkk] = v + + return nil +} diff --git a/zk/smt/unwind_smt.go b/zk/smt/unwind_smt.go new file mode 100644 index 00000000000..e02203ce115 --- /dev/null +++ b/zk/smt/unwind_smt.go @@ -0,0 +1,91 @@ +package smt + +import ( + "context" + "fmt" + "math" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/kv" + db2 "github.com/ledgerwatch/erigon/smt/pkg/db" + + "github.com/ledgerwatch/erigon-lib/kv/membatchwithdb" + + "github.com/ledgerwatch/erigon/smt/pkg/smt" + "github.com/ledgerwatch/erigon/turbo/trie" + "github.com/ledgerwatch/erigon/zk" + "github.com/ledgerwatch/erigon/zkevm/log" +) + +func UnwindZkSMT(ctx context.Context, logPrefix string, from, to uint64, tx kv.RwTx, checkRoot bool, expectedRootHash *common.Hash, quiet bool) (common.Hash, error) { + if !quiet { + log.Info(fmt.Sprintf("[%s] Unwind trie hashes started", logPrefix)) + defer log.Info(fmt.Sprintf("[%s] Unwind ended", logPrefix)) + } + + eridb := db2.NewEriDb(tx) + eridb.RollbackBatch() + + dbSmt := smt.NewSMT(eridb, false) + + if !quiet { + log.Info(fmt.Sprintf("[%s]", logPrefix), "last root", common.BigToHash(dbSmt.LastRoot())) + } + + // only open the batch if tx is not already one + if _, ok := tx.(*membatchwithdb.MemoryMutation); !ok { + quit := make(chan struct{}) + eridb.OpenBatch(quit) + } + + changesGetter := NewChangesGetter(tx) + if err := changesGetter.openChangesGetter(from); err != nil { + return trie.EmptyRoot, fmt.Errorf("OpenChangesGetter: %w", err) + } + defer changesGetter.closeChangesGetter() + + total := uint64(math.Abs(float64(from) - float64(to) + 1)) + progressChan, stopPrinter := zk.ProgressPrinter(fmt.Sprintf("[%s] Progress unwinding", logPrefix), total, quiet) + defer stopPrinter() + + // walk backwards through the blocks, applying state changes, and deletes + // PlainState contains data AT the block + // History tables contain data BEFORE the block - so need a +1 offset + for i := from; i >= to+1; i-- { + select { + case <-ctx.Done(): + return trie.EmptyRoot, fmt.Errorf("context done") + default: + } + + if err := changesGetter.getChangesForBlock(i); err != nil { + return trie.EmptyRoot, fmt.Errorf("getChangesForBlock: %w", err) + } + + progressChan <- 1 + } + + stopPrinter() + + if _, _, err := dbSmt.SetStorage(ctx, logPrefix, changesGetter.accChanges, changesGetter.codeChanges, changesGetter.storageChanges); err != nil { + return trie.EmptyRoot, err + } + + lr := dbSmt.LastRoot() + + hash := common.BigToHash(lr) + if checkRoot && hash != *expectedRootHash { + log.Error("failed to verify hash") + return trie.EmptyRoot, fmt.Errorf("wrong trie root: %x, expected (from header): %x", hash, expectedRootHash) + } + + if !quiet { + log.Info(fmt.Sprintf("[%s] Trie root matches", logPrefix), "hash", hash.Hex()) + } + + if err := eridb.CommitBatch(); err != nil { + return trie.EmptyRoot, err + } + + return hash, nil +} diff --git a/zk/stages/stage_interhashes.go b/zk/stages/stage_interhashes.go index b4cd61c10d3..c2f708f6967 100644 --- a/zk/stages/stage_interhashes.go +++ b/zk/stages/stage_interhashes.go @@ -3,9 +3,7 @@ package stages import ( "fmt" - "github.com/holiman/uint256" "github.com/ledgerwatch/erigon-lib/common" - libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/length" "github.com/ledgerwatch/erigon-lib/kv" "github.com/ledgerwatch/erigon-lib/state" @@ -25,10 +23,7 @@ import ( "os" - "math" - "github.com/ledgerwatch/erigon-lib/kv/dbutils" - "github.com/ledgerwatch/erigon-lib/kv/membatchwithdb" "github.com/ledgerwatch/erigon/core/rawdb" "github.com/ledgerwatch/erigon/core/systemcontracts" "github.com/ledgerwatch/erigon/core/types/accounts" @@ -39,6 +34,7 @@ import ( "github.com/ledgerwatch/erigon/turbo/stages/headerdownload" "github.com/ledgerwatch/erigon/turbo/trie" "github.com/ledgerwatch/erigon/zk" + zkSmt "github.com/ledgerwatch/erigon/zk/smt" "github.com/status-im/keycard-go/hexutils" ) @@ -81,7 +77,7 @@ func StageZkInterHashesCfg( } } -func SpawnZkIntermediateHashesStage(s *stagedsync.StageState, u stagedsync.Unwinder, tx kv.RwTx, cfg ZkInterHashesCfg, ctx context.Context) (root libcommon.Hash, err error) { +func SpawnZkIntermediateHashesStage(s *stagedsync.StageState, u stagedsync.Unwinder, tx kv.RwTx, cfg ZkInterHashesCfg, ctx context.Context) (root common.Hash, err error) { logPrefix := s.LogPrefix() quit := ctx.Done() @@ -90,7 +86,7 @@ func SpawnZkIntermediateHashesStage(s *stagedsync.StageState, u stagedsync.Unwin useExternalTx := tx != nil if !useExternalTx { var err error - tx, err = cfg.db.BeginRw(context.Background()) + tx, err = cfg.db.BeginRw(ctx) if err != nil { return trie.EmptyRoot, err } @@ -195,7 +191,6 @@ func SpawnZkIntermediateHashesStage(s *stagedsync.StageState, u stagedsync.Unwin } func UnwindZkIntermediateHashesStage(u *stagedsync.UnwindState, s *stagedsync.StageState, tx kv.RwTx, cfg ZkInterHashesCfg, ctx context.Context, silent bool) (err error) { - quit := ctx.Done() useExternalTx := tx != nil if !useExternalTx { tx, err = cfg.db.BeginRw(ctx) @@ -219,12 +214,9 @@ func UnwindZkIntermediateHashesStage(u *stagedsync.UnwindState, s *stagedsync.St expectedRootHash = syncHeadHeader.Root } - root, err := unwindZkSMT(ctx, s.LogPrefix(), s.BlockNumber, u.UnwindPoint, tx, cfg.checkRoot, &expectedRootHash, silent, quit) - if err != nil { + if _, err = zkSmt.UnwindZkSMT(ctx, s.LogPrefix(), s.BlockNumber, u.UnwindPoint, tx, cfg.checkRoot, &expectedRootHash, silent); err != nil { return err } - _ = root - hermezDb := hermez_db.NewHermezDb(tx) if err := hermezDb.TruncateSmtDepths(u.UnwindPoint); err != nil { return err @@ -454,197 +446,6 @@ func zkIncrementIntermediateHashes(ctx context.Context, logPrefix string, s *sta return hash, nil } -func unwindZkSMT(ctx context.Context, logPrefix string, from, to uint64, db kv.RwTx, checkRoot bool, expectedRootHash *common.Hash, quiet bool, quit <-chan struct{}) (common.Hash, error) { - if !quiet { - log.Info(fmt.Sprintf("[%s] Unwind trie hashes started", logPrefix)) - defer log.Info(fmt.Sprintf("[%s] Unwind ended", logPrefix)) - } - - eridb := db2.NewEriDb(db) - dbSmt := smt.NewSMT(eridb, false) - - if !quiet { - log.Info(fmt.Sprintf("[%s]", logPrefix), "last root", common.BigToHash(dbSmt.LastRoot())) - } - - if quit == nil { - log.Warn("quit channel is nil, creating a new one") - quit = make(chan struct{}) - } - - // only open the batch if tx is not already one - if _, ok := db.(*membatchwithdb.MemoryMutation); !ok { - eridb.OpenBatch(quit) - } - - ac, err := db.CursorDupSort(kv.AccountChangeSet) - if err != nil { - return trie.EmptyRoot, err - } - defer ac.Close() - - sc, err := db.CursorDupSort(kv.StorageChangeSet) - if err != nil { - return trie.EmptyRoot, err - } - defer sc.Close() - - currentPsr := state2.NewPlainStateReader(db) - - total := uint64(math.Abs(float64(from) - float64(to) + 1)) - printerStopped := false - progressChan, stopPrinter := zk.ProgressPrinter(fmt.Sprintf("[%s] Progress unwinding", logPrefix), total, quiet) - defer func() { - if !printerStopped { - stopPrinter() - } - }() - - // walk backwards through the blocks, applying state changes, and deletes - // PlainState contains data AT the block - // History tables contain data BEFORE the block - so need a +1 offset - accChanges := make(map[common.Address]*accounts.Account) - codeChanges := make(map[common.Address]string) - storageChanges := make(map[common.Address]map[string]string) - - addDeletedAcc := func(addr common.Address) { - deletedAcc := new(accounts.Account) - deletedAcc.Balance = *uint256.NewInt(0) - deletedAcc.Nonce = 0 - accChanges[addr] = deletedAcc - } - - psr := state2.NewPlainState(db, from, systemcontracts.SystemContractCodeLookup["Hermez"]) - defer psr.Close() - - for i := from; i >= to+1; i-- { - select { - case <-ctx.Done(): - return trie.EmptyRoot, fmt.Errorf("[%s] Context done", logPrefix) - default: - } - - psr.SetBlockNr(i) - - dupSortKey := dbutils.EncodeBlockNumber(i) - - // collect changes to accounts and code - for _, v, err2 := ac.SeekExact(dupSortKey); err2 == nil && v != nil; _, v, err2 = ac.NextDup() { - - addr := common.BytesToAddress(v[:length.Addr]) - - // if the account was created in this changeset we should delete it - if len(v[length.Addr:]) == 0 { - codeChanges[addr] = "" - addDeletedAcc(addr) - continue - } - - oldAcc, err := psr.ReadAccountData(addr) - if err != nil { - return trie.EmptyRoot, err - } - - // currAcc at block we're unwinding from - currAcc, err := currentPsr.ReadAccountData(addr) - if err != nil { - return trie.EmptyRoot, err - } - - if oldAcc.Incarnation > 0 { - if len(v) == 0 { // self-destructed - addDeletedAcc(addr) - } else { - if currAcc.Incarnation > oldAcc.Incarnation { - addDeletedAcc(addr) - } - } - } - - // store the account - accChanges[addr] = oldAcc - - if oldAcc.CodeHash != currAcc.CodeHash { - cc, err := currentPsr.ReadAccountCode(addr, oldAcc.Incarnation, oldAcc.CodeHash) - if err != nil { - return trie.EmptyRoot, err - } - - ach := hexutils.BytesToHex(cc) - hexcc := "" - if len(ach) > 0 { - hexcc = "0x" + ach - } - codeChanges[addr] = hexcc - } - } - - err = db.ForPrefix(kv.StorageChangeSet, dupSortKey, func(sk, sv []byte) error { - changesetKey := sk[length.BlockNum:] - address, _ := dbutils.PlainParseStoragePrefix(changesetKey) - - sstorageKey := sv[:length.Hash] - stk := common.BytesToHash(sstorageKey) - - value := []byte{0} - if len(sv[length.Hash:]) != 0 { - value = sv[length.Hash:] - } - - stkk := fmt.Sprintf("0x%032x", stk) - v := fmt.Sprintf("0x%032x", common.BytesToHash(value)) - - m := make(map[string]string) - m[stkk] = v - - if storageChanges[address] == nil { - storageChanges[address] = make(map[string]string) - } - storageChanges[address][stkk] = v - return nil - }) - if err != nil { - return trie.EmptyRoot, err - } - - progressChan <- 1 - } - - stopPrinter() - printerStopped = true - - if _, _, err := dbSmt.SetStorage(ctx, logPrefix, accChanges, codeChanges, storageChanges); err != nil { - return trie.EmptyRoot, err - } - - if err := verifyLastHash(dbSmt, expectedRootHash, checkRoot, logPrefix, quiet); err != nil { - log.Error("failed to verify hash") - eridb.RollbackBatch() - return trie.EmptyRoot, err - } - - if err := eridb.CommitBatch(); err != nil { - return trie.EmptyRoot, err - } - - lr := dbSmt.LastRoot() - - hash := common.BigToHash(lr) - return hash, nil -} - -func verifyLastHash(dbSmt *smt.SMT, expectedRootHash *common.Hash, checkRoot bool, logPrefix string, quiet bool) error { - hash := common.BigToHash(dbSmt.LastRoot()) - - if checkRoot && hash != *expectedRootHash { - panic(fmt.Sprintf("[%s] Wrong trie root: %x, expected (from header): %x", logPrefix, hash, expectedRootHash)) - } - if !quiet { - log.Info(fmt.Sprintf("[%s] Trie root matches", logPrefix), "hash", hash.Hex()) - } - return nil -} - func processAccount(db smt.DB, a *accounts.Account, as map[string]string, inc uint64, psr *state2.PlainStateReader, addr common.Address, keys []utils.NodeKey) ([]utils.NodeKey, error) { // get the account balance and nonce keys, err := insertAccountStateToKV(db, keys, addr.String(), a.Balance.ToBig(), new(big.Int).SetUint64(a.Nonce)) diff --git a/zk/stages/stage_witness.go b/zk/stages/stage_witness.go new file mode 100644 index 00000000000..34f928ef6e7 --- /dev/null +++ b/zk/stages/stage_witness.go @@ -0,0 +1,327 @@ +package stages + +import ( + "context" + "fmt" + "time" + + "github.com/ledgerwatch/erigon-lib/chain" + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/datadir" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/kv/membatchwithdb" + eristate "github.com/ledgerwatch/erigon-lib/state" + "github.com/ledgerwatch/erigon/core" + "github.com/ledgerwatch/erigon/core/systemcontracts" + eritypes "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/core/vm" + zkUtils "github.com/ledgerwatch/erigon/zk/utils" + "github.com/ledgerwatch/erigon/zk/witness" + + "github.com/ledgerwatch/erigon/consensus" + "github.com/ledgerwatch/erigon/core/state" + "github.com/ledgerwatch/erigon/eth/stagedsync" + "github.com/ledgerwatch/erigon/eth/stagedsync/stages" + "github.com/ledgerwatch/erigon/turbo/services" + "github.com/ledgerwatch/erigon/zk/hermez_db" + "github.com/ledgerwatch/erigon/zk/sequencer" + + "github.com/ledgerwatch/erigon/core/rawdb" + "github.com/ledgerwatch/erigon/eth/ethconfig" + "github.com/ledgerwatch/log/v3" +) + +type WitnessDb interface { +} + +type WitnessCfg struct { + db kv.RwDB + zkCfg *ethconfig.Zk + chainConfig *chain.Config + engine consensus.Engine + blockReader services.FullBlockReader + agg *eristate.Aggregator + historyV3 bool + dirs datadir.Dirs + forcedContracs []common.Address +} + +func StageWitnessCfg(db kv.RwDB, zkCfg *ethconfig.Zk, chainConfig *chain.Config, engine consensus.Engine, blockReader services.FullBlockReader, agg *eristate.Aggregator, historyV3 bool, dirs datadir.Dirs, forcedContracs []common.Address) WitnessCfg { + cfg := WitnessCfg{ + db: db, + zkCfg: zkCfg, + chainConfig: chainConfig, + engine: engine, + blockReader: blockReader, + agg: agg, + historyV3: historyV3, + dirs: dirs, + forcedContracs: forcedContracs, + } + + return cfg +} + +// /////////////////////////////////////////// +// 1. Check to which block it should calculate witnesses +// 2. Unwind to that block +// 3. Calculate witnesses up to current executed block +// 4. Delete old block witnesses +// //////////////////////////////////////////// +func SpawnStageWitness( + s *stagedsync.StageState, + u stagedsync.Unwinder, + ctx context.Context, + tx kv.RwTx, + cfg WitnessCfg, +) error { + logPrefix := s.LogPrefix() + if cfg.zkCfg.WitnessCacheLimit == 0 { + log.Info(fmt.Sprintf("[%s] Skipping witness cache stage. Cache not set or limit is set to 0", logPrefix)) + return nil + } + log.Info(fmt.Sprintf("[%s] Starting witness cache stage", logPrefix)) + if sequencer.IsSequencer() { + log.Info(fmt.Sprintf("[%s] skipping -- sequencer", logPrefix)) + return nil + } + defer log.Info(fmt.Sprintf("[%s] Finished witness cache stage", logPrefix)) + + freshTx := false + if tx == nil { + freshTx = true + log.Debug(fmt.Sprintf("[%s] no tx provided, creating a new one", logPrefix)) + var err error + tx, err = cfg.db.BeginRw(ctx) + if err != nil { + return fmt.Errorf("cfg.db.BeginRw, %w", err) + } + defer tx.Rollback() + } + + stageWitnessProgressBlockNo, err := stages.GetStageProgress(tx, stages.Witness) + if err != nil { + return fmt.Errorf("GetStageProgress: %w", err) + } + + stageInterhashesProgressBlockNo, err := stages.GetStageProgress(tx, stages.IntermediateHashes) + if err != nil { + return fmt.Errorf("GetStageProgress: %w", err) + } + + if stageInterhashesProgressBlockNo <= stageWitnessProgressBlockNo { + log.Info(fmt.Sprintf("[%s] Skipping stage, no new blocks", logPrefix)) + return nil + } + + unwindPoint := stageWitnessProgressBlockNo + if stageInterhashesProgressBlockNo-cfg.zkCfg.WitnessCacheLimit > unwindPoint { + unwindPoint = stageInterhashesProgressBlockNo - cfg.zkCfg.WitnessCacheLimit + } + + //get unwind point to be end of previous batch + hermezDb := hermez_db.NewHermezDb(tx) + blocks, err := getBlocks(tx, unwindPoint, stageInterhashesProgressBlockNo) + if err != nil { + return fmt.Errorf("getBlocks: %w", err) + } + + // generator := witness.NewGenerator(cfg.dirs, cfg.historyV3, cfg.agg, cfg.blockReader, cfg.chainConfig, cfg.zkCfg, cfg.engine) + memTx := membatchwithdb.NewMemoryBatchWithSize(tx, cfg.dirs.Tmp, cfg.zkCfg.WitnessMemdbSize) + defer memTx.Rollback() + if err := zkUtils.PopulateMemoryMutationTables(memTx); err != nil { + return fmt.Errorf("PopulateMemoryMutationTables: %w", err) + } + memHermezDb := hermez_db.NewHermezDbReader(memTx) + + log.Info(fmt.Sprintf("[%s] Unwinding tree and hashess for witness generation", logPrefix), "from", unwindPoint, "to", stageInterhashesProgressBlockNo) + if err := witness.UnwindForWitness(ctx, memTx, unwindPoint, stageInterhashesProgressBlockNo, cfg.dirs, cfg.historyV3, cfg.agg); err != nil { + return fmt.Errorf("UnwindForWitness: %w", err) + } + log.Info(fmt.Sprintf("[%s] Unwind done", logPrefix)) + startBlock := blocks[0].NumberU64() + + prevHeader, err := cfg.blockReader.HeaderByNumber(ctx, tx, startBlock-1) + if err != nil { + return fmt.Errorf("blockReader.HeaderByNumber: %w", err) + } + + getHeader := func(hash common.Hash, number uint64) *eritypes.Header { + h, e := cfg.blockReader.Header(ctx, tx, hash, number) + if e != nil { + log.Error("getHeader error", "number", number, "hash", hash, "err", e) + } + return h + } + + reader := state.NewPlainState(tx, blocks[0].NumberU64(), systemcontracts.SystemContractCodeLookup[cfg.chainConfig.ChainName]) + defer reader.Close() + prevStateRoot := prevHeader.Root + + log.Info(fmt.Sprintf("[%s] Executing blocks and collecting witnesses", logPrefix), "from", startBlock, "to", stageInterhashesProgressBlockNo) + + now := time.Now() + for _, block := range blocks { + reader.SetBlockNr(block.NumberU64()) + tds := state.NewTrieDbState(prevHeader.Root, tx, startBlock-1, nil) + tds.SetResolveReads(true) + tds.StartNewBuffer() + tds.SetStateReader(reader) + + trieStateWriter := tds.NewTrieStateWriter() + if err := witness.PrepareGersForWitness(block, memHermezDb, tds, trieStateWriter); err != nil { + return fmt.Errorf("PrepareGersForWitness: %w", err) + } + + getHashFn := core.GetHashFn(block.Header(), getHeader) + + chainReader := stagedsync.NewChainReaderImpl(cfg.chainConfig, tx, nil, log.New()) + + vmConfig := vm.Config{} + if _, err = core.ExecuteBlockEphemerallyZk(cfg.chainConfig, &vmConfig, getHashFn, cfg.engine, block, tds, trieStateWriter, chainReader, nil, hermezDb, &prevStateRoot); err != nil { + return fmt.Errorf("ExecuteBlockEphemerallyZk: %w", err) + } + + prevStateRoot = block.Root() + + w, err := witness.BuildWitnessFromTrieDbState(ctx, memTx, tds, reader, cfg.forcedContracs, false) + if err != nil { + return fmt.Errorf("BuildWitnessFromTrieDbState: %w", err) + } + + bytes, err := witness.GetWitnessBytes(w, false) + if err != nil { + return fmt.Errorf("GetWitnessBytes: %w", err) + } + + if hermezDb.WriteWitnessCache(block.NumberU64(), bytes); err != nil { + return fmt.Errorf("WriteWitnessCache: %w", err) + } + if time.Since(now) > 10*time.Second { + log.Info(fmt.Sprintf("[%s] Executing blocks and collecting witnesses", logPrefix), "block", block.NumberU64()) + now = time.Now() + } + } + log.Info(fmt.Sprintf("[%s] Witnesses collected", logPrefix)) + + // delete cache for blocks lower than the limit + log.Info(fmt.Sprintf("[%s] Deleting old witness caches", logPrefix)) + if err := hermezDb.DeleteWitnessCaches(0, stageInterhashesProgressBlockNo-cfg.zkCfg.WitnessCacheLimit); err != nil { + return fmt.Errorf("DeleteWitnessCache: %w", err) + } + + if err := stages.SaveStageProgress(tx, stages.Witness, stageInterhashesProgressBlockNo); err != nil { + return fmt.Errorf("SaveStageProgress: %w", err) + } + + log.Info(fmt.Sprintf("[%s] Saving stage progress", logPrefix), "lastBlockNumber", stageInterhashesProgressBlockNo) + + if freshTx { + if err := tx.Commit(); err != nil { + return fmt.Errorf("tx.Commit: %w", err) + } + } + + return nil +} + +func getBlocks(tx kv.Tx, startBlock, endBlock uint64) (blocks []*eritypes.Block, err error) { + idx := 0 + blocks = make([]*eritypes.Block, endBlock-startBlock+1) + for blockNum := startBlock; blockNum <= endBlock; blockNum++ { + block, err := rawdb.ReadBlockByNumber(tx, blockNum) + if err != nil { + return nil, fmt.Errorf("ReadBlockByNumber: %w", err) + } + blocks[idx] = block + idx++ + } + + return blocks, nil +} + +func UnwindWitnessStage(u *stagedsync.UnwindState, tx kv.RwTx, cfg WitnessCfg, ctx context.Context) (err error) { + logPrefix := u.LogPrefix() + if cfg.zkCfg.WitnessCacheLimit == 0 { + log.Info(fmt.Sprintf("[%s] Skipping witness cache stage. Cache not set or limit is set to 0", logPrefix)) + return nil + } + useExternalTx := tx != nil + if !useExternalTx { + if tx, err = cfg.db.BeginRw(ctx); err != nil { + return fmt.Errorf("cfg.db.BeginRw: %w", err) + } + defer tx.Rollback() + } + + if cfg.zkCfg.WitnessCacheLimit == 0 { + log.Info(fmt.Sprintf("[%s] Skipping witness cache stage. Cache not set or limit is set to 0", logPrefix)) + return nil + } + + fromBlock := u.UnwindPoint + 1 + toBlock := u.CurrentBlockNumber + log.Info(fmt.Sprintf("[%s] Unwinding witness cache stage from block number", logPrefix), "fromBlock", fromBlock, "toBlock", toBlock) + defer log.Info(fmt.Sprintf("[%s] Unwinding witness cache complete", logPrefix)) + + hermezDb := hermez_db.NewHermezDb(tx) + if err := hermezDb.DeleteWitnessCaches(fromBlock, toBlock); err != nil { + return fmt.Errorf("DeleteWitnessCache: %w", err) + } + + if err := stages.SaveStageProgress(tx, stages.Witness, fromBlock); err != nil { + return fmt.Errorf("SaveStageProgress: %w", err) + } + + if err := u.Done(tx); err != nil { + return fmt.Errorf("u.Done: %w", err) + } + if !useExternalTx { + if err := tx.Commit(); err != nil { + return fmt.Errorf("tx.Commit: %w", err) + } + } + return nil +} + +func PruneWitnessStage(s *stagedsync.PruneState, tx kv.RwTx, cfg WitnessCfg, ctx context.Context) (err error) { + logPrefix := s.LogPrefix() + if cfg.zkCfg.WitnessCacheLimit == 0 { + log.Info(fmt.Sprintf("[%s] Skipping witness cache stage. Cache not set or limit is set to 0", logPrefix)) + return nil + } + useExternalTx := tx != nil + if !useExternalTx { + tx, err = cfg.db.BeginRw(ctx) + if err != nil { + return fmt.Errorf("cfg.db.BeginRw: %w", err) + } + defer tx.Rollback() + } + + log.Info(fmt.Sprintf("[%s] Pruning witnes caches...", logPrefix)) + defer log.Info(fmt.Sprintf("[%s] Pruning witnes caches complete", logPrefix)) + + hermezDb := hermez_db.NewHermezDb(tx) + + toBlock, err := stages.GetStageProgress(tx, stages.Witness) + if err != nil { + return fmt.Errorf("GetStageProgress: %w", err) + } + + if err := hermezDb.DeleteWitnessCaches(0, toBlock); err != nil { + return fmt.Errorf("DeleteWitnessCache: %w", err) + } + + log.Info(fmt.Sprintf("[%s] Saving stage progress", logPrefix), "stageProgress", 0) + if err := stages.SaveStageProgress(tx, stages.Witness, 0); err != nil { + return fmt.Errorf("SaveStageProgress: %v", err) + } + + if !useExternalTx { + if err := tx.Commit(); err != nil { + return fmt.Errorf("tx.Commit: %w", err) + } + } + return nil +} diff --git a/zk/stages/stages.go b/zk/stages/stages.go index 4ada15e99ec..3e0097dd642 100644 --- a/zk/stages/stages.go +++ b/zk/stages/stages.go @@ -233,6 +233,7 @@ func DefaultZkStages( exec stages.ExecuteBlockCfg, hashState stages.HashStateCfg, zkInterHashesCfg ZkInterHashesCfg, + stageWitnessCfg WitnessCfg, history stages.HistoryCfg, logIndex stages.LogIndexCfg, callTraces stages.CallTracesCfg, @@ -439,6 +440,20 @@ func DefaultZkStages( return nil }, }, + { + ID: stages2.Witness, + Description: "Generate witness caches for each block", + Disabled: false, + Forward: func(firstCycle bool, badBlockUnwind bool, s *stages.StageState, u stages.Unwinder, txc wrap.TxContainer, logger log.Logger) error { + return SpawnStageWitness(s, u, ctx, txc.Tx, stageWitnessCfg) + }, + Unwind: func(firstCycle bool, u *stages.UnwindState, s *stages.StageState, txc wrap.TxContainer, logger log.Logger) error { + return UnwindWitnessStage(u, txc.Tx, stageWitnessCfg, ctx) + }, + Prune: func(firstCycle bool, p *stages.PruneState, tx kv.RwTx, logger log.Logger) error { + return PruneWitnessStage(p, tx, stageWitnessCfg, ctx) + }, + }, { ID: stages2.Finish, Description: "Final: update current block for the RPC API", diff --git a/zk/witness/witness.go b/zk/witness/witness.go index 5ae7ac04bcf..66346367db4 100644 --- a/zk/witness/witness.go +++ b/zk/witness/witness.go @@ -1,15 +1,15 @@ package witness import ( - "bytes" "context" "errors" "fmt" "math/big" "time" + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/chain" - libcommon "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/datadir" "github.com/ledgerwatch/erigon-lib/kv" libstate "github.com/ledgerwatch/erigon-lib/state" @@ -23,20 +23,14 @@ import ( "github.com/ledgerwatch/erigon/eth/ethconfig" "github.com/ledgerwatch/erigon/eth/stagedsync" "github.com/ledgerwatch/erigon/eth/stagedsync/stages" - db2 "github.com/ledgerwatch/erigon/smt/pkg/db" - "github.com/ledgerwatch/erigon/smt/pkg/smt" "github.com/ledgerwatch/erigon/turbo/services" "github.com/ledgerwatch/erigon/turbo/trie" - dstypes "github.com/ledgerwatch/erigon/zk/datastream/types" "github.com/ledgerwatch/erigon/zk/hermez_db" "github.com/ledgerwatch/erigon/zk/l1_data" - zkStages "github.com/ledgerwatch/erigon/zk/stages" zkUtils "github.com/ledgerwatch/erigon/zk/utils" "github.com/ledgerwatch/log/v3" "github.com/ledgerwatch/erigon-lib/kv/membatchwithdb" - "github.com/holiman/uint256" - "math" ) var ( @@ -54,7 +48,7 @@ type Generator struct { chainCfg *chain.Config zkConfig *ethconfig.Zk engine consensus.EngineReader - forcedContracts []libcommon.Address + forcedContracts []common.Address } func NewGenerator( @@ -65,7 +59,7 @@ func NewGenerator( chainCfg *chain.Config, zkConfig *ethconfig.Zk, engine consensus.EngineReader, - forcedContracs []libcommon.Address, + forcedContracs []common.Address, ) *Generator { return &Generator{ dirs: dirs, @@ -79,80 +73,55 @@ func NewGenerator( } } -func (g *Generator) GetWitnessByBatch(tx kv.Tx, ctx context.Context, batchNum uint64, debug, witnessFull bool) (witness []byte, err error) { - t := zkUtils.StartTimer("witness", "getwitnessbybatch") +func (g *Generator) GetWitnessByBadBatch(tx kv.Tx, ctx context.Context, batchNum uint64, debug, witnessFull bool) (witness []byte, err error) { + t := zkUtils.StartTimer("witness", "getwitnessbybadbatch") defer t.LogTimer() reader := hermez_db.NewHermezDbReader(tx) - badBatch, err := reader.GetInvalidBatch(batchNum) + // we need the header of the block prior to this batch to build up the blocks + previousHeight, _, err := reader.GetHighestBlockInBatch(batchNum - 1) if err != nil { return nil, err } - if badBatch { - // we need the header of the block prior to this batch to build up the blocks - previousHeight, _, err := reader.GetHighestBlockInBatch(batchNum - 1) - if err != nil { - return nil, err - } - previousHeader := rawdb.ReadHeaderByNumber(tx, previousHeight) - if previousHeader == nil { - return nil, fmt.Errorf("failed to get header for block %d", previousHeight) - } + previousHeader := rawdb.ReadHeaderByNumber(tx, previousHeight) + if previousHeader == nil { + return nil, fmt.Errorf("failed to get header for block %d", previousHeight) + } - // 1. get l1 batch data for the bad batch - fork, err := reader.GetForkId(batchNum) - if err != nil { - return nil, err - } + // 1. get l1 batch data for the bad batch + fork, err := reader.GetForkId(batchNum) + if err != nil { + return nil, err + } - decoded, err := l1_data.BreakDownL1DataByBatch(batchNum, fork, reader) - if err != nil { - return nil, err - } + decoded, err := l1_data.BreakDownL1DataByBatch(batchNum, fork, reader) + if err != nil { + return nil, err + } - nextNum := previousHeader.Number.Uint64() - parentHash := previousHeader.Hash() - timestamp := previousHeader.Time - blocks := make([]*eritypes.Block, len(decoded.DecodedData)) - for i, d := range decoded.DecodedData { - timestamp += uint64(d.DeltaTimestamp) - nextNum++ - newHeader := &eritypes.Header{ - ParentHash: parentHash, - Coinbase: decoded.Coinbase, - Difficulty: new(big.Int).SetUint64(0), - Number: new(big.Int).SetUint64(nextNum), - GasLimit: zkUtils.GetBlockGasLimitForFork(fork), - Time: timestamp, - } - - parentHash = newHeader.Hash() - transactions := d.Transactions - block := eritypes.NewBlock(newHeader, transactions, nil, nil, nil) - blocks[i] = block + nextNum := previousHeader.Number.Uint64() + parentHash := previousHeader.Hash() + timestamp := previousHeader.Time + blocks := make([]*eritypes.Block, len(decoded.DecodedData)) + for i, d := range decoded.DecodedData { + timestamp += uint64(d.DeltaTimestamp) + nextNum++ + newHeader := &eritypes.Header{ + ParentHash: parentHash, + Coinbase: decoded.Coinbase, + Difficulty: new(big.Int).SetUint64(0), + Number: new(big.Int).SetUint64(nextNum), + GasLimit: zkUtils.GetBlockGasLimitForFork(fork), + Time: timestamp, } - return g.generateWitness(tx, ctx, batchNum, blocks, debug, witnessFull) - } else { - blockNumbers, err := reader.GetL2BlockNosByBatch(batchNum) - if err != nil { - return nil, err - } - if len(blockNumbers) == 0 { - return nil, fmt.Errorf("no blocks found for batch %d", batchNum) - } - blocks := make([]*eritypes.Block, len(blockNumbers)) - idx := 0 - for _, blockNum := range blockNumbers { - block, err := rawdb.ReadBlockByNumber(tx, blockNum) - if err != nil { - return nil, err - } - blocks[idx] = block - idx++ - } - return g.generateWitness(tx, ctx, batchNum, blocks, debug, witnessFull) + parentHash = newHeader.Hash() + transactions := d.Transactions + block := eritypes.NewBlock(newHeader, transactions, nil, nil, nil) + blocks[i] = block } + + return g.generateWitness(tx, ctx, batchNum, blocks, debug, witnessFull) } func (g *Generator) GetWitnessByBlockRange(tx kv.Tx, ctx context.Context, startBlock, endBlock uint64, debug, witnessFull bool) ([]byte, error) { @@ -164,9 +133,10 @@ func (g *Generator) GetWitnessByBlockRange(tx kv.Tx, ctx context.Context, startB } if endBlock == 0 { witness := trie.NewWitness([]trie.WitnessOperator{}) - return getWitnessBytes(witness, debug) + return GetWitnessBytes(witness, debug) } hermezDb := hermez_db.NewHermezDbReader(tx) + idx := 0 blocks := make([]*eritypes.Block, endBlock-startBlock+1) var firstBatch uint64 = 0 @@ -214,9 +184,9 @@ func (g *Generator) generateWitness(tx kv.Tx, ctx context.Context, batchNum uint return nil, fmt.Errorf("block number is in the future latest=%d requested=%d", latestBlock, endBlock) } - batch := membatchwithdb.NewMemoryBatchWithSize(tx, g.dirs.Tmp, g.zkConfig.WitnessMemdbSize) - defer batch.Rollback() - if err = zkUtils.PopulateMemoryMutationTables(batch); err != nil { + rwtx := membatchwithdb.NewMemoryBatchWithSize(tx, g.dirs.Tmp, g.zkConfig.WitnessMemdbSize) + defer rwtx.Rollback() + if err = zkUtils.PopulateMemoryMutationTables(rwtx); err != nil { return nil, err } @@ -230,21 +200,11 @@ func (g *Generator) generateWitness(tx kv.Tx, ctx context.Context, batchNum uint return nil, fmt.Errorf("requested block is too old, block must be within %d blocks of the head block number (currently %d)", maxGetProofRewindBlockCount, latestBlock) } - unwindState := &stagedsync.UnwindState{UnwindPoint: startBlock - 1} - stageState := &stagedsync.StageState{BlockNumber: latestBlock} - - hashStageCfg := stagedsync.StageHashStateCfg(nil, g.dirs, g.historyV3, g.agg) - if err := stagedsync.UnwindHashStateStage(unwindState, stageState, batch, hashStageCfg, ctx, log.New(), true); err != nil { - return nil, fmt.Errorf("unwind hash state: %w", err) + if err := UnwindForWitness(ctx, rwtx, startBlock, latestBlock, g.dirs, g.historyV3, g.agg); err != nil { + return nil, fmt.Errorf("UnwindForWitness: %w", err) } - interHashStageCfg := zkStages.StageZkInterHashesCfg(nil, true, true, false, g.dirs.Tmp, g.blockReader, nil, g.historyV3, g.agg, nil) - - if err = zkStages.UnwindZkIntermediateHashesStage(unwindState, stageState, batch, interHashStageCfg, ctx, true); err != nil { - return nil, fmt.Errorf("unwind intermediate hashes: %w", err) - } - - tx = batch + tx = rwtx } prevHeader, err := g.blockReader.HeaderByNumber(ctx, tx, startBlock-1) @@ -255,9 +215,9 @@ func (g *Generator) generateWitness(tx kv.Tx, ctx context.Context, batchNum uint tds := state.NewTrieDbState(prevHeader.Root, tx, startBlock-1, nil) tds.SetResolveReads(true) tds.StartNewBuffer() - trieStateWriter := tds.TrieStateWriter() + trieStateWriter := tds.NewTrieStateWriter() - getHeader := func(hash libcommon.Hash, number uint64) *eritypes.Header { + getHeader := func(hash common.Hash, number uint64) *eritypes.Header { h, e := g.blockReader.Header(ctx, tx, hash, number) if e != nil { log.Error("getHeader error", "number", number, "hash", hash, "err", e) @@ -278,48 +238,8 @@ func (g *Generator) generateWitness(tx kv.Tx, ctx context.Context, batchNum uint hermezDb := hermez_db.NewHermezDbReader(tx) - //[zkevm] get batches between last block and this one - // plus this blocks ger - lastBatchInserted, err := hermezDb.GetBatchNoByL2Block(blockNum - 1) - if err != nil { - return nil, fmt.Errorf("failed to get batch for block %d: %v", blockNum-1, err) - } - - currentBatch, err := hermezDb.GetBatchNoByL2Block(blockNum) - if err != nil { - return nil, fmt.Errorf("failed to get batch for block %d: %v", blockNum, err) - } - - gersInBetween, err := hermezDb.GetBatchGlobalExitRoots(lastBatchInserted, currentBatch) - if err != nil { - return nil, err - } - - var globalExitRoots []dstypes.GerUpdate - - if gersInBetween != nil { - globalExitRoots = append(globalExitRoots, *gersInBetween...) - } - - blockGer, err := hermezDb.GetBlockGlobalExitRoot(blockNum) - if err != nil { - return nil, err - } - emptyHash := libcommon.Hash{} - - if blockGer != emptyHash { - blockGerUpdate := dstypes.GerUpdate{ - GlobalExitRoot: blockGer, - Timestamp: block.Header().Time, - } - globalExitRoots = append(globalExitRoots, blockGerUpdate) - } - - for _, ger := range globalExitRoots { - // [zkevm] - add GER if there is one for this batch - if err := zkUtils.WriteGlobalExitRoot(tds, trieStateWriter, ger.GlobalExitRoot, ger.Timestamp); err != nil { - return nil, err - } + if err := PrepareGersForWitness(block, hermezDb, tds, trieStateWriter); err != nil { + return nil, fmt.Errorf("PrepareGersForWitness: %w", err) } engine, ok := g.engine.(consensus.Engine) @@ -328,60 +248,24 @@ func (g *Generator) generateWitness(tx kv.Tx, ctx context.Context, batchNum uint return nil, fmt.Errorf("engine is not consensus.Engine") } - vmConfig := vm.Config{} - getHashFn := core.GetHashFn(block.Header(), getHeader) chainReader := stagedsync.NewChainReaderImpl(g.chainCfg, tx, nil, log.New()) - _, err = core.ExecuteBlockEphemerallyZk(g.chainCfg, &vmConfig, getHashFn, engine, block, tds, trieStateWriter, chainReader, nil, hermezDb, &prevStateRoot) - if err != nil { - return nil, err + vmConfig := vm.Config{} + if _, err = core.ExecuteBlockEphemerallyZk(g.chainCfg, &vmConfig, getHashFn, engine, block, tds, trieStateWriter, chainReader, nil, hermezDb, &prevStateRoot); err != nil { + return nil, fmt.Errorf("ExecuteBlockEphemerallyZk: %w", err) } prevStateRoot = block.Root() } - inclusion := make(map[libcommon.Address][]libcommon.Hash) - for _, contract := range g.forcedContracts { - err = reader.ForEachStorage(contract, libcommon.Hash{}, func(key, secKey libcommon.Hash, value uint256.Int) bool { - inclusion[contract] = append(inclusion[contract], key) - return false - }, math.MaxInt64) - if err != nil { - return nil, err - } - } - - var rl trie.RetainDecider - // if full is true, we will send all the nodes to the witness - rl = &trie.AlwaysTrueRetainDecider{} - - if !witnessFull { - rl, err = tds.ResolveSMTRetainList(inclusion) - if err != nil { - return nil, err - } - } - - eridb := db2.NewEriDb(batch) - smtTrie := smt.NewSMT(eridb, false) - - witness, err := smt.BuildWitness(smtTrie, rl, ctx) + witness, err := BuildWitnessFromTrieDbState(ctx, rwtx, tds, reader, g.forcedContracts, witnessFull) if err != nil { - return nil, fmt.Errorf("build witness: %v", err) + return nil, fmt.Errorf("BuildWitnessFromTrieDbState: %w", err) } - return getWitnessBytes(witness, debug) -} - -func getWitnessBytes(witness *trie.Witness, debug bool) ([]byte, error) { - var buf bytes.Buffer - _, err := witness.WriteInto(&buf, debug) - if err != nil { - return nil, err - } - return buf.Bytes(), nil + return GetWitnessBytes(witness, debug) } func (g *Generator) generateMockWitness(batchNum uint64, blocks []*eritypes.Block, debug bool) ([]byte, error) { diff --git a/zk/witness/witness_merge_test_data.go b/zk/witness/witness_merge_test_data.go new file mode 100644 index 00000000000..1bfe7b9cd14 --- /dev/null +++ b/zk/witness/witness_merge_test_data.go @@ -0,0 +1,8 @@ +package witness + +var ( + witness1 = "01020302030203020302030203034b4c181607792b3c46ea253af79666ab9bbfa3d29e8855be6c4e045b3424f6a503fdb52981685167cdab219ae57b3c5869e539e89eb29845d6406b3229247e982e020302030203020302030203020303dc378377acad40e16af2de6482d7a60c1e5f087d067fc716c2485742ac2e29330339535728bf0c5d72ec789110ff3691dfb9cf434399ad849a86ca6725977d3e4f0203020303481a1fc812bcc98ce37225fff9f28a6d8d0ea5c63aeda93b031e8e4603cc8e7c032952530fef71561f9028c37b944df439c0d2968c4f7e247a2ad12dd4969ffc8302030203031ce6733d3a496a34cb114cad924070b0dfad8ff6891f629ed2ae31326540fe120345057d6cbecce08aeecc475c91403549f4fe82bdb953895bdeded2fae6f8688a020302030203020303c4ac3ac799860160a30a3304b765c2c90bc414edc3739a5d098bb7e18009548a039042e98ef239f418f2bf7ad10868e1fa7d0f644458488adf684313dc3f683a5202030203020303949f805ade2be05694c8011fa17fab3646a43f38f96d868386f0ba9558ba5f960302aabd9fbeceb9711f46d634513830181412c8405aea579f470a19b477d090140203020303db978a462b93b2efa3aa3da09e03370b570db692c6d361d52ae1051bdb26a3a903916d67432c505e1dc33f3617e0743d761aba44785726309191e79cb18b666e7402030203033edca13bcadc1db9305f3b15322cc6d774682fffdfe2b509f81d00b16ce2dcd003dc94780e238944094e7856154e6d3e54fec28293a9a70eaf1cc2a81e874e22170203020302010203070354000000000000000000000000000000005ca1ab1e5820e72de8a1b9696dd30f7886b15c4cc9234d52c6b41b9c33e2baaf8d88fc5b7c9f5820f8fb80310ac041e7a5e79c138d7261cda5d8a988dc9268b5a8dc5318fb610a90070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000358206e82d18bde430935057c321f6c30812e0eae2122da6af753e25974c92f0d7b50020303c6cbb686c9d7a94f49dfbee076ae1b87f1c9bb5f33b7c98a71816c33b4731a3b037514c2021b2bb805e2a6060b265dd53c069a4587e19cd7d1af99d0e9c3d0e550020303784570292bffbc3ee00153e5806a317459fed4c1d84de0515dcefc177404865003f08c92f6786e67148e8fb2bcd4eb813a665e16a270b475605d1d84b587450ff102030344c5e2a1775873020ab4e5a588e95d6702878cd46012682196dc39738fd8780703a6d5ee17fe3be7e20050e4e66c54b5188febbdd3615f832c35b073078258b214020303a42b38dcef18f890c02cdb90473211c95582727b83af287cbfc8a3f10e29649103380623684a9b3b341e01ee65908a6aac96fdf1444ca255b9dd5193537d58709b020303476c478891a8f8d905ebf9e5c031ba1020ca1436538bf9b97c6eaa1b9512da97038c77314895fccd4edafbfd73b531f4dec6f4671b6acde83926907ab376982f310203036416706411fa678c78f77dbfb609d65f63d6b04a8aae3fae4cad23419f6e738b03b6ec59ff099f23c5a528e805fbd9457736b100ea0e96390eb536046b88da3db102030334468b79fd36c8bc812c6613d176983aa4be53642e7e56421faa4ef25031fc73032869ca46586018725007aac483055d85131fcc4432c9a72175a8c6263b65c1ed020303676f9f98ef2cdc44ec8d98d0153be2aeb90b08386286887c94567950df1216440385bdebccb7559d68f55e26ba0980bcf7120609c7bb43cfc1f701e92f670ac1280203031117969a5ad58cb9a441ddd498cf3bebc13ab5aea1ceb29ddb1a226c5343c6e703425597c542fab13f686a7053f6c1e2635a729f8d9da4c01d763ffe9965ddd63402030345f2b9e446c9e743f6899409a4567a9b7f8770f711d39e39773d8173c4ea3a0c03cbc17bc3c54426fc8cf2b13b1ddb800509579856ce251beae01d924a92a8edb8020302030203030560b956a67a313d6d8939eed4cd80cc385eb49f7b6dd269ccde33a145f1216e037b3e0569695b777df45db97a41b025b57c680ad61231b61225fc7825824c4c0502030203033f1ce9dde58980c5bc34a88467f3b8cfd334dab19f28050acc53f33aab0b366f036092ba2243e1d8e20c2aa4ba0aee9ca063e8e8e6da493269065c227232020a590203020303921f9061d1b4082d20b7f9df21566609ca6dc64cd0ffac2625e9ff3090ac73570371757934d2c7d4dfe9b7b1e5c71fe18b66cf56c540c3d04310873976f79ef9f602030203020303e8e045e00ad70f879f31e498fe49aa2fd4c81849b6c55dd98391681351aac7df036e509788bd99ed4034d5fa3901bbda4cb6f94d709b2d54eca545336569791f36020302030310125b6177d5086fcdca8e0607e49e6cb21bebc95404329c9769a7d3ed59e2c4034e9acfa4214d459d1c81a304d862b2dbd4d832e71ab851656bfcc0e9c5b3e6f60203020303ad656bdacec77a2e7e591bddde7b2c7ab9b928945ee65898ff35900e25f0c21f03e47d9766945c4649bd44422f5fa779e92267d76ce44f396ef0b672215e43ce7802030203020303d3858acf0781afe0adae49a25d1724b36c9989179cc884b9a9c6481f89e57706031da6fb50879ede58d816d1624292f0c60a8133bcbc671dd92d0f9cb8d50fc803020302030317221db9e049aebabc83cefc3ebe7040ec1e82022d104d2c78f796753f76f0120352124f3ffee53f7e0f9a0068d5c0b0abfca5aaa148371c91e2e81df5fba6f8bf0203020303e00ce2232f3e208dcf050f887d02c7b91170c4b98e1d098ec5238bb3d387a41e038a0379dab30ce84865bb4f0834a9c3fd7bb2da17994abf03e89fd8d754bf7aab0203020302030333f5853903cb36caedffa12d0aa0a01c39e91309629a97dddafa8da4f738fb3e038e1dc6012aecd5998053b5878c6e3a398c8a286c7ad4dc0b55f043e4b4210950020302030317e041d91830fe8051fc73631cfd56519fc5bb88b5369298da9807b40d93733703dc7745bf8dfa058bcf1328f79efc9441cf5ad5fb5763c75bdebf9492f88a6c8302030203020303bf9a2780e63ac26ffca6df07f182db72e3e65802b277ea6d40057c383d5a37e3036c1548eb1c956ece0a876fff4463cc3f2e9c3b6eef88379f07e6d71013eb4aad020302030202020103c2eebac9e260dad1f051fa75846558dcc0ed120d1a7699fd1d6b739a3419015b020103b9d64db96b69b035a74a90069691afa2b34a705f3ad3aa82f3757c7a6f2a9f17070354000000000000000000000000000000005ca1ab1e58207af492bfc857210d76ff8398a35a942af892e866b1f4c241746b3ee89ca002595820f889596e5b3d4dbe5bcab5cde2af26d3ad8d88bc086e8b4f929885f33f6eec77020303cd3edcf79c7e0e0d6b87ae523729611faeda92e77bbb59f739e9a6127d890cdf03009a5c01c3cb27d2f1ffbd3fd77ff38f66991f64174f5df1411ea21ae2e22f250203035334694d8b56a2f5e0d0ded81283e7f36b3da6fbf2e2d1b469c008d7414296be03953388b0cacc587c5ca452ba8e96a9071958ef94439690bc14866f556a35ebc1020303ac2aae05bc7a68d238e9a9bbd2d5d07a001f8f3651bb25f5a6d6dcbb155569090335b6f55bf3d56419cbc3a45d4fa6bed330d9e0391f8806c97a7aa4149d06725b0203033dfe4a2f0555ff318ad12e49515e712f339134af0237edaef08553d9d67e260b039cd50a46feb34ab47c24391a2579e601956897ad6299bd14a4c8d9628a37d46e02030348f01fedf98979a5fb3df07daded956331fa6a02f697dfe29dd26e71111de5540387829f9a96ed82303e86550747e311d5dbfe94cc71113600595360abb512cb7b02030203020302030203020303eac48d9dbf7d162797293e0acd54382d4fd53e80e29c9c43c51dafb05c0880060306b13c75c66a6e267236b6579bcca576ff889e323ac6ffd0ee317e07623a3866020302030203020302030352e23af8570aeca858a6aa5bd20d2c63a92eb08529a9e6e5fb245aa72c5b72ce0334d7cfef6cb28d62f63cf907e3273d76a8bb858423c6ef446b056fb4f06210e002030203020302030315bf4cd3a7f33296bb4778e216bd18adacf25c97f8f4df9e1052dcba7b6edf2203b3637d0b1cf58c5272f28f8354a764d3cd48ff7c04f807237da8f4a1e2ef5db5020302030203020302030203020303c018dfe606efd5d17f3d45e91d33d3d7ef57d92a1d509291b1556bbb7e78dd0803b08ff5bb304aa8741af608415c541440edcd98bbc0fc849fe2a89c2c783341d502030203031e0eb0ac5664b1d0267d3c51dd66d1828c0d01a0903d599a317e4578926d4e3503330e2ccc546a3db52e796943aa8960d6d483a3b941ae0caa21cc7b9f7a3c2bbc070354a40d5f56745a118d0906a34e69aec8c0db1cb8fa582000000000000000000000000000000000000000000000000000000000000000015820421c2cc0dce9b0fbdb85cbe43bd6c2a1af5a6f5da756cdb8b6f2bb948e3a90da020303ac874a6acbf6de628134cd74ad9f336206e7aadb1ef09456b6267a770612485703ef323528b761720ce04927a54b81025a935f070420d655217e40eb2e084bd170020303a48199a63a429cf44fb39fdbb73098a49dc171e03d32800f483780adb9aa06580388796e8ab2076fc77f00a5096317ceff8a54da310b014a0310504bcd76f8b8da020303f8698a6f24140e0e37f49032fb2da6db2c8bcaea8961a6e1976baded0d9a8bd80371b277886f0d14b6f82cfd063ecddab10fb5da5e0666e040992469d09a6bc8b0020303dee2b54587eeb7db2fe9ef0dc262b6ae679a5bfff89c8f403d181a1d79107b1d032aff27f522ef5fd88213c3865e01c7b4c1720d56778d1bd0e48e6a86fb3b07970203037d0d29240ad72800831a91d8e54019da745c6c6a630a625167723ace857bbb810305edd49a8cfb1eea157734968e95e8b8620c474c3cfc6f3285d3dad36893114302030349b1bd34664838889a2133d716143cb8707a15745738917bfbbeecbe871e6e90035ba74ef0008ce80ac6d199cc4d217aaa9b8a5fd58f2d329aba4e061c16d99b620203030d600bfcd6581d405aaed26aa7cee976fbb2bb9c1c1390bd3eb14cf5f661022303acf3369b84f876dc556ed93718d616864020b1969d24170f4970ddbd944e1bd9020303d5bb847f016f33d8cac460756aad70173d8d6e37c37d1b69e1b1b45c52e5996103c3777105bd00820b49c89486e7589f0ccd0244ab6fd4b1409ba86dece7506f9102030356b2929dbde358b52b652bc842c7a42aea162f0d79bd7d653b5cfee34e9f0e6c03656a686adb3bff7a9d8841d3e296b0dc61c389b399677222ebbd70cf0c19e70a020303e5bf4a0779ccfa6d42a01e532bb6120b168699bfd3f4f44a62780481d5f86588036efb82ef530fb604bdff43bf1ad1a7dde41522bf8a7f5e724dd3074562b0c0ef020303036a50ac7a6e425842820d2a4e07a80f416706903e9d88b5824559515a901aa80303e3c58c1dfb4f5a5a5d1180dd010ceb33a42a0ff7cab200ced5562202261aa0020302030385d60697f5b4482fcbecfaf5f53111681c9b48ed7bbd2cdb1a257bb7f26db9d103ae21f016eadf6448b913ba498fe3d678b8bcdf9569b026053de69bd24563ef0202030203032fd50f1a5b8eddbd5ccb90e37d9c190092927af9b26a1cf8b4576d7982476fb603436882f441f09768b000722da7ec7c74b6f0252c24e16b9e6461ce4f4eeb791d02030203034eb00b994d3a8d439f47981f68baf7fb0f0e88e2167243c6b005de3c48b5c3ec03ac5626fd3f4030d088d0f41834de11510b59739353238241138d70bd8e05c22e02030203030a51165872abbe7260a6777cbbd2f6d81dfcd07c1b7c0783659bf8e8ca8e77b9032f78c81c54fd31d1a25214fa464424ae6e6399f15c1bd8987825f1f0d0dfccde020302030203020303215472473dede3eebcfdd93b1ee898e4d6cf33261a1fba12ff77dff2fb8a0f27037938ac733af661730414e88da9633c04a8914c9ae4263a4f8cea7066e6cefb840203020302030203034d6713bc006056011f31ac6f935e71e33ab8045353e9e138ec9743e8574a8d2f03fcaee2f22e1561702d029c465b755ff5491e4114264dfdf16fe9efd34864a83802030203037cc278f9b41fd17fb5eb3c839a725fcd1ef6000189fcebcb4214303f45dcd2d60386c3bc64da300f1a87efa2eb2724553e41348057fc99d5c23b5b20e216fde46d020302030376bf2ddfddca9910df80bb0785add76937d1e90e029c02b04c0cf421622a232803ba2219bc37e93a89b0effdfc3601f58645c1cb7e818f2254c8fd16fea4ba84440203020303fdf1c4799edef5fe2960f9148627fff521e591247a224eb8d05eae3f51675b560372adafa8e298a14d0da0a71e645a12a23def78db8e81f7a68ef92aac7d5700b40203020303f5a794d38718b283b47993f3bbcd67c76f84c47fcf2d35373fcb7f8a0f43a06b03a14b12d1f03790ac75797e463a8a7edcfb2bc80b65a7dc8d1b15d00cefb315d5020302010312f8462573dc83d436d2498e68019babdcc911f482e1e00c1b3fda70e1a166e40203020303adcfa2154e38e2cdbafd5d56bdaa5dca90a5bfb9c36bfbe140bb31ec0e66716503b5aaf1a6fa2e80ad8f4e49c51808d2898fd74f539ec5de974b57c27466f5e7490203070354000000000000000000000000000000005ca1ab1e58205956a0b12f607189a063054545ab26ce76ea5eb4c9bc1e8d8161646c93ac66515820da6aba51eaf87e14a7585e52e23cc0b789c61b3e808d2aef704ae932bb2ab49d070354ee5a4826068c5326a7f06fd6c7cbf816f096846c5820701c251f0448beefca8b47dce2e42f136d224b8e89e4900d24681e46a70e7448510237426c03237429400000000067445fb8020303dd24d6adc0d7b321eb19905b22f1780707b0d7e30026716c3b0d7ea311cbfeab03e498dca26358e5fd56e5464288da82073a17cbbd112e322488c12bff1661b49b020303413600069144f3379227184b3d365f22778695ad2b812ffe56bdec80df882877033b1e22049401430c9208943101b5ef3e70d99c9853e08591c4729e0f31f4bf56020303ffce2337b88b26e7b0582d1679484fa995b68c0418d72f650531db342e25f12e03493c1bb3f993e9aa63e2736b9e0826f1309ed298bd95bfc169f89b6a62cbed420203031bacbf380b1eafbec9c534577f8972d28087bc6e94bc276ec91e66a11396f07903bb137addf6042ee1a1eb0170ac09f0a092b2f7682f718d5986152d56d192b347020303b89984a9ec10a5bc5835efef55fbf26f3477d21372a55ae4abd26c55ee5e323d035ab47c29775484efde5ad8cfb1a399e9008bcb66f6cd77f28c255980633aeb5d0203037902d8528b89dce0e6a41ff89888121f42520936f3684bdc8481094f1e046b4f03cedf898a501b7bc036d92797f971bf9caa1028994a8d6b15ceb79e4ca532e7cc02030203020303a366f69c8b19d47be34a2a6333298d705692f65daf3fba95d6f48b9676b6cd3b0351f190ff80b28f339034b6be161060cbe4837cf22e0c01b3d5a77b8f349c4f1d02030203038d8eae2b45a21838dbf9f517dae99ff0bac7a25d4756a7a3315c43cfa7dbfb9803785e2e17b8cdb9628ca4c2f963eb5722918462cf75f91dd6fd00ae84d17ba2a90203020302030312a3a949b95a27ae6f73e9d879bc9c9c6eb6757f1c20ee76d1f52e1d4c9ec4eb03d38f8911a661255b0ebcabbadd44e38903841386863c97499f3e57a06bc5c3e702030203020303763e3e4c8cc4a4b30afaaae229ff20ac282d74c923a88be140293d62b2d812bb03b4b4e3386c676de1012a2bdced3714094e57803a98920b0eefe63e186abdd4d902030203032ee550fc2b119e46e3338c971e6f44ea838020e442fce0c4a34b359306a00379038c72343f5e2ac968c7f1edfd71f18128db6b52aa476fbec372eaa58a2acf45220203020303221a1371f01a251478f2a6673db891a3c412d954dc9e741ea2bfd249abf428bf0325059126652b0c2c46d78a02eba6c4df473b674ed378b17827c634bd119f5422020302030203020303313abcaaf43f5d42589a57c6fc0bec04526b43a3dc139415af1de50f8846c004037ee72e1eb97ffd7dfe0c7d40b575103edd3e62c030b86362c41630c6e97bf6bf020302030203020303508d990b34daf5a3f925c435daac3d293f6d861094cc2d343a92c62428fa66da032f8b40a9211667e9c44328d6440091ecb3a46bc15832f7d7cdfa8ec130b527fc0203020303f993f7eae6e45a6f8557c6c5d0e912cb41b71d2bf37f38affc0b2d8e054193220315eeb3ab754628ce727cd0b7028ff8ed3291de7566b99066e127185d043f595702030203032f2c132f32f21e267ab64271e8f2c0c39fedbcc509c4589616cffec21d7332eb03839857347599c19c43a0acfe53e1bb5bbe0d68ddb49cee05f1b24c5acac24a150203020303dcd0869ad1107856680f6bf164623fc709d26d1a0842bb9c60a383f255c0ec2403c92cb1692742c4e2b6a91d13c3b371a9dccd29f898d8f6457ad052b1da9efcf6020302030203031408a1feb1c4cefd2e71c1d7ce58e6b4c2d139d48c67037d40dc0d60390af539039c51675ab13cc260ab6875b12824ed60903c1755add14024e27508ac0a3b9d81020102030376fdbe16ba7e2f048d9c311cb1f99291b4f624717ddd7e9f2aa653099d19314f032ebe85ea3fef7c7033338d1ed98e187eddf75dff4772a23e19392ce61690f77f020303f901f2ba5a7a95db9ea7106268f17f341206944377d1f006921211069cf8a0a103f43daf24401f9ed2d0691570a8ccdcd016c90b722786ff590276f7cd5933ff3d02030378ab72606d2d32782ceccc9c11af9496f599dec259281c01f0c18a3b875518ed0355b4984426bd4db31ca5d70798a18280a4d319786bd897a29365d2db7489b32d02030335706adc0febe81255c960be521ae4c7a6201b2db502fb7016a5d4d9ba36c58803ef3e9f16053b7f799f207451eb3403eb95301e9c9e721dfde0c41ebd8362485c0203033b59831b753c1ca3ed58d3293aab0099027f87ff97f3f7e92d9dfb095839497a03821fc506f41f2a0bcce20367ebd6ae4b461e110e1788d190416c8345ec72c364020303cf6d91b6b57705a8f02a367997e807f49dba00a5bb3e8d0de25eacad5486b88f03abc64c2300b90b30ae3b11fb71095675d1a62860a6471a1a2defcf624b8bb4d4020303be890b95c3a4c5c381f1a00d6d98da4cd8467e002746a8c52f2564e41319d3780394b620da3f2c277f0d4a70c7a54a7245503ed2e808bf722cce0b503e242ae7d10203039f6bac7e82bf632c8b003eed17f050a49d2ea83b6a93e09295b3b3c51c55ada6038d01937127f83a85e3e655363f467385226f7e406409528791f6e2375184ef5e02030203020303e2ba22bcf2fd6923a2ffd1ae073bcffad33e81f4a7cb9cab82e130c63a213b6e031dd2e6a82a0638b027a1f15eac2bceca26ef1519de70dc99bd5275791bab4bb0020302030203031d0be4b4d178c76d39a7689aaa3a9866e63b999a2d11dbec2f04787c714dabbe03e5880788e24aeb6314512538d4cf7382b37132d4d2870122f47de8ac0d09eb020203020303b9af076d8b0e683e730de94273fbcdb5d2ac9f29273a9ffb38875892722f439903e22b2cbffaa7b1ed370a3d8b87199e1f1485703145dd3de0945cede9629702600203020303a019468f5d28919dfcc2d7bfd844492f2ab1df6400a17627b31c29ea02d583f5038dd13cd4ecf8c4151cebaf6e2637913a2310a81d4ecbd5f5fd2f4a4c315558ac0203020303167bb488d1aff473f1027bdeadb8e0e7a439f6a589c78caae1a3d045e78da60303ddda65ddb3f7e0fe430faaeb49419075391fd2559659f2ba88d3655454e079e802030203020302030203020302030203037a46bc17ebfbc47f6d99661de00074c9958e0f7fd66df7c77c236b89b165472e034b58bfe7c7506d2891367c270ca350269dfc0a08b7466ec2496c6330dd602bb302030203039b58b0df7fae59a4cef25184d849214bc145cda115b2c0dfd85fd470ecdea70f0330923d4d299efbc138d4442519d30cd33a7827557231388b81a6ea9c65eabe6f0203020303af3fee608c2e8e5a30ffc6345d86ec1b2d55f10e518b4da5e8eb59abde07b59803c2016682405d3a953eba254601d5fc0b8966a33efaa51918a4a41b8e0acbeb4602030203034b1387aa6d0ab944e2ec65ce38c8643a0ddfca5c3059718f398dee501291569603528cbab25216c4397a402fcb572f0b512a773dfeafa59e401989a4da13406bfe02030203070354000000000000000000000000000000005ca1ab1e582054b6c4d9862a1658dedebe99a0f61d94c5d1515fd031d0dfe9ebce6a1454f5c658203f14693500ccd0260659fd9eaf69570edc0504867134ac88f871d91d388b63690203070354914e7547b9051ea6226c30495190a2efa15930c95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927548382be7cc5c2cd8b14f44108444ced6745c5fecb02030311ab6695ec969171698c1f56d4c05373d8505a2c7299fb05cda1d4351e22bfa403478b94ae515fbd01728835b532c7c45ccc78d200d3d004da6917337e139eb729020303ffd14369e7c7f7aec3a890a20234885f2c9fb9802ec318d8434ebcd58a696153030ddae742090ea458c3f232dc894bd8cd3378b4b4590a0523e09a44e0439fe0db020303b1688d8c7806365d931579ccac8dbf7a8d7705ac393159dfd9c0395ab7b5ca5b036a6c978a565b15267de4330de7b6166014082043c5cc80370953767ac501ccf2020303f181e47adf88e965d55e1153d76b731c261ad7d7720823919fc11d98bc144d2a03c480f344ef22a4532900fb9d7cb9d8b5ce1e4f11a231e682142f9ffe1962807d0203032db40fdeb2c5256d5a237b6134f844646b325bfc12c687916327e21a65b1ae6a03c73ddb4116e07a00066b925a207dda51fbbfadce21a7459c6c2ae7f598721089020303e81fa28f73bf124de71b54c67334292e397e000428de699c89947d793cacb9da03173e567d72ac2c265860d9103e791fdfe3cad72a9a1dae15d9bec6687eb506d702030305a683365eb32bb92967becff0dba79d1c23ff75b2fc3d40f9a1573b993747b703b8b1075b12927a8f483dc7b802c96483206f98c640e49e22d4b426f9a9eb750f0203031276db0802c8235f9f248bbafaa6cbabb75baead95ede989894ea6d8585c3c8703527ea0179a8814d423775e1f381cc8eee0797216d71c79729ab186714e4daf3702030330b1e1f7a1f7dcbf5cd00932de20748e546bc1a8da9381fa3d5066f3c02b61da033f7308aca0fa70a938e45539d5dcd1864bc233ef232c6d38fa1dd331e536a400020303ad8fe61eca50a88286f382461ecaa93dc71e9aed12e91a2e9930325e5ffd1d7903fd046a02679f734a91031aacb4194ada537220167cfa68306b651433026e6478020302030203020303b7e72973952f51f913dc6818649ddb3c5619982f21e56347003ebe3b3788eadb0384757ebf158021f4bfc0d9a1bf844d13747328fd367727cb0a2d9b7c91926c400203020303593dd6ef2d4c6f8ab3253bec454072a6cf779b5acd194d43cf4d30191d4b24fe03d80a7ee4528b16cb482fd73c259b2e6e4fde5d5d31be6b97703fbbb17c3e61d20203020303992d90fe15b918f58e8dac35e96d0ebf33834ccacc8a69b6a075b263d0df655e0301b8df4b987fcf3a98000ca00d3191fd2292dc9210d7f1ab382035b2e2d02be9020302030328797f5226ad9a63c859dc61073e8ef33fe15094e61db64bcde0379f055f733403b50fe3e685c2e442a3a81715f64a840afaf1f81b49ed21b3fc2ead0620f6caae020302030203020303189a1bc58c5621e4845025a9c534fb9ad2bb2f5be276faee403d59266561d652038325fb098a4b3a402690994212511e710d20cb7966fb26b3687fea719eca217a0203020303ca11813aa459d051b0411eeddd18070506e8fe2054a2e22a763b05454e87cefd03b2cb46d28f3bcf15305b0654ca442442420ccc1b28e44e2e2c84498571b5375a02030203039385ca432e99a05cca8aa7fe5868222cdb6c928c8bbdd7eb13c22c5abe1b11cd03e8cb7cbe434eae4b8b7910183b3b006a1b3df70ae7b30248fef24d64a004c3c90203020302030203035fb731b403c8979aa552e74b3534a247c638547dc7c957467a4b08855b29b74703d49a5d90635d403354f849daf9976a4f4dfd7dab5517b254638eb893511ebcaa02030203032fddd404fe9317d561378c78f3afbe75e18c27face10d4e6ea03fc2888b22e33033c8c390d481f51cf6b43c22677a971beae0e62e8b2ecfdaaed05b48ac0f60294020302030203020302030203070054000000000000000000000000000000005ca1ab1e45e8d4a5100002010341305ecddd1b56329ac9f09a1235eec6ce6be69492a9788db13e9187dc21e9dc020303fb1c6d1aa6d3f3bef7a0bf4130218b3b168f9447e69ebcd3b68c2b2f41d9b2ef03652ba6f9b69aee3d28404079416c2f8fba4078d66b558c7a8d9615cfe7d3bd30020303d9f042d0d2f152e24d8cde02d3a7d7a1fa234efc5dc259572d412a2e607215ba03c5b76ff595e1d74a22eb44a5aed94f3225b6126c2c28ef04bb75e1d3804925ad02030314a2b125da4db5ba673cd5c0aaae8c5bf0857fd45728b868cff3f40eaf9f82790393e93c4f4b58f6f9d397d136319a29aa6b691b652651513bfc2297107379ce62020303f00359907dd68b2ae8e2d252d3313f3ba2bba16d21995333b2162b24c9bbeac4036435af585f0f75e60d362629108f6768756f7b39f1c70ab7f79e6b4e1bd9f08f020303929e2f8eb833089a3773b497247338865ef336de61e7da4a362eb3e5d5601a7203323197b010e3205d910c230463758f39cd6c01258db0a11b9b47f4c278db0494020303ab4bdc2dbea0c00b12cedf9e968135b62101bc1e20e270a1f694ae6a4686627c03140686262c769436fdaece3afe58e8a4423cbf381295a85237e52fac66c57879020303295e1973d07a067f281e3337e756bacf10dcc295f7074564874ea4401eb2a4e503cfec4348d3a697dd4f1835bc31c2615f56f92a02c1935cceec2501c12b8628f10203033892c29a2de6aee7888c2448fdbb3252d32b426bf74edf79223e4ee886fc0f6b03ef287d8ccaa574ebdac646e6d35bfb3ce52b00eda1e671d7d7bbf31bd59ff7ee020303c58f22b2dc782f914b31e3b87185b727a0bd2e2dcc41481e31ab1b26f222fdf703f0dcf8a2ce85de4d96bdc4c1a9c52a7ec54cc771750f0ed7d6c1113b93df65ce02030203039a7c26055306c8884baf96dccb2e3bb3cb30deceafdc73491bbdf0333400efc0036ee70bfe41de62ab49a9a63ca415bb881a92980f87fc044f2f5ae2e84185dfea0203020303c4332d86dee9e03fbda2dc0eb81cb20a6f6a20c7df95090f09e47d8e7efa1d7b03a698f30a106768bc9d451fd96a6808beb2b799deec6423688d02a9ba34b4af280203020302030203020303398dee7348cac5f07e4865c2049207722ec9572e2ae69b21a8cbd1c053c44a0e03612d7861c014aed261de20fd1109fc86ae090eb2c37a02b8a6072bba1c77c8b50203020302030203020302030203031f28ae8c421086878704ec730445ebf2ff23d186ffed24802f0ae24259c8d21403a8e38716cdd8a09095a7036c686009bd8236b1c7eb9507540fb981baa9a8bc4b020302030203030fe638892efa1dbdc2881def87e77dbbba95d91c8debdd9b242bbf0745455a7403e5554fbb47341d48f82f64a26d175a7d3559378657e77cf2de2eff917b95be300203020303512c2cf0ab4340a1623bdddc301aa586932f9413ea9bf8c0f1849d2d70d5d0ff0375d0cc499c7f76c70939fd8d633c658747eebf0eb138c15d902c34f0de9098030203020303b78dcbd59a3668396357cbda038d7e5bc77aac4acdb3cd3a75e96eb05079a6bf03ceb3ed2850bca5df0bd69ce2e85e9daff43cdb4c79f58685340f521521a0943f0203020302030201034b33ab5a3b8d3b01c374c1d7fcfc714398b7f0704ba2f1ee757670269fd5a7f7020302020203070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000043840c77070354000000000000000000000000000000005ca1ab1e582010c18923d58801103b7e76ccd81e81a281713d174575a74b2ef0341f6b9a42fd5820b8b76bb549992d9bfc44e3b36d087a175b2e78b9584fc752eaa3013e0bdd31e8070354000000000000000000000000000000005ca1ab1e58209bd14ac8c1cf553e0ad3a2c109b9871eb74f3c116bf0bf492ef04d2983722555582090629dad0a40430445b7d2b25a8d19c5d7a929608ed7890877a499aaca01ca5002030315edee06840e36ef17d13817ab5475d12f7bd50113984febf31e2cd80c08952c03d360a7d78676862429feb7c95d052c1e63379b8ad3becf085a21baa353ab93d30203037a2fb952c2cf8e85d9706bcbcb5a69b83b13403b58f06b0767f4204acc5917930310de142eb3b2790cf1e3694b72eecc7e8ab3860f543c15cc24274ff69570f009020303879875563fe8a079ef71e84b6840b187c681499095de9d02d8b101c9dfcd111e0395e9fc3b000e49b65678f256d247786f72c91494c960d117b7668045c35502720203033d99bf4088229f273fa4910aad8f0aae6f7de8fd1832ddd14c8fa3d083aac51603b40316099ecb013c6dcc6ac2a3e831521afa35ea0ee52485c2e8cd40bd81fd870203030b576686ff79ae37ff5ae2d4240131369472a24104dcabaaf3c348da66a638bf03dddfa8283748687718b9672b0a69a6b7758ce10eff383d83986c1a2aca2910e002030313ac1b1da5a5a4232e4e2b766aaba01f45f9444b926476f01882e30d4cc6ae1a0323f57d2012e1874436ddc007ea8bc6dcbeae6e0dac6fd044c8375d2fe593904502030381ee4d8ef714022c3c5fad435af845d213cb988ef7561ddf65929553b70dd69a03178f9fbf18b1d12feb522330c82fe96d15bc4964e1c1053093c4903149652e6b02030323cdd6298c89fc39f87595dedfd8bae3ae7a40b66f312333394482169297dc8d033517a6ff26c035b9f822da8d2abd642c858696e0d970b1026cb524cb0844195a02030391dd21f4c52970493d439537192b245ccd2e4e3e8e16d90dc74e417718b12f9103d56b5ff3ad5ab9205b2d6f9c508e744643224a7ebca8c1a4aea71f01e48b186b02030304375ae3a357e874c2a10fe3596adee75d0ccb96e63838d8db70c9e402663e9903bd8d2e9ed97a66281cbb0733a92dcc92158740088acc7a9c834d8204c0acc1da0203033c9cd711a378c8153572663cfc686ea0324eaabf0feca614928eab900755299f030bdcf7033e475ad4a147377e1bb9ed8619b0c88b728f7935ecbe7bcd2fa82c7c0203020302030313f878d66e026ade0b2e4ffec8b114291f4d832aae729e6da9fe98f316651f67031b597cff5ad0e3ec8b6baa5f15993d6e950c3cf473b0c796f339d7e9e28da24002030203020303ac0a0e8df4bc9026b7b241c34d72dce10c8424eacea17d1670103c8ded2446be03f7a62663d338b5b7e9219d01266b1772ca3720daf925bd302b7dafcf8abebcba0203020302030366c76d1cd3e3136e15f9f29f3dcc4bded2c760f251e06403ea022bf5d67ae2d503c574a334d06a611fa0340a1213e317efb125ace4eda7a487ea00075e9a6b67a902030203020303ed1e8a1502eb462bb7f836f6d72486908e1b2cce7cb00e387cc1aadc827874d103bdbfb8f970bcc72256ac4e1eca0809217c625c6412289a6dc5dff7c91454436602030203020302030203020303e8bb2dae757d043417195292bac773cda990500845f91a94d00179fe89525c3e03e385244b15001ddd43b0180922bbccf4040a86bd116ade66fc3aced03dffecff02030203037d8dcb012bdde19a0dd178c1de91d21cc866a76b9b6315554fec4bc4f5daa79203bc46b61f50585799762df053271c52844c6fe83156fde628c8bc4369c4fed18202030203020303c998c4c602a03cfa0c92a1542165567f11d23f2ae5fb91d04e02292f8e297548039447848097d9500f21ebe819789f98461e01aff7cfcd442c8afe8e07b87a95690203020303f6441de6ba2bc5cc9ead4300519a94a14a80b85f2fb0fa41e2211d7c02af0e6703703a99e4c2133e89a6e892863f14f143cf5f2ad94bd527081c8be6143f14f3db020302030203031da626649ee092857f195eb0059639309d744972389a4e748c471f16b0fc3c2e03f4072cd036d5bbb777ad24fa0b1a235806ef25737404d7ce4f83babb76bf090802030203020303c7f6a9615846a2d627db4c940098479bce3c61c8fc1170d0b7f8c9ac2bec5ea4033664667108158e9377b49cf3632952090b6eab3ba6eaed4f48ca9d5beb273fd002010203070354000000000000000000000000000000005ca1ab1e5820eff9a5f21a1dc5ce907981aedce8e1f0d94116f871970a4c9488b2a6813ffd41582021bb230cc7b5a15416d28b65e85327b729b384a46e7d1208f17d1d74e498f445020102030203070354000000000000000000000000000000005ca1ab1e5820cfe58c94626d82e54c34444223348c504ae148f1e79868731da9b44fc91ddfd4582040fc722808ecb16a4f1cb2e145abfb2b8eb9d731283dbb46fe013c0e3441dfbc070354000000000000000000000000000000005ca1ab1e58200000000000000000000000000000000000000000000000000000000000000002446746e71f070354000000000000000000000000000000005ca1ab1e5820bc32590bee71a54e9565024aca9df30307a01cf8f23561baed0ef54b30f5be68582007b7aa19b2ab0ca977cf557ea4cec4c0b84b00a9430cfe0323877011fa03269c020203e752c67cd3ffa4dacba7194a973b10998b056b5b936d17da2a8eae591a8e175e020303abdf2e1db4950317aadeff604ae51ac75e8281b1ea22e7f17349757c71dca21d03fd88dafa9a26e9c8f617b5de3e1a6021092dbff5c1fdb5d8efaeecb1f333565c020303b8a363b96519cb0eed132d807f6e42ea35f115584c443a74b14a19bbeac463d7038247bf369e033fcc19a5797960f1387f04f80cf396babac560060887288632db0203032a893ec5bee53177a40777945189164675444d0087d703c8129196df58b4ffd10384203647fe683ea28ce78395875f0bc39f1fe1ce6c9670b8393161514dab47010203039cd9d80315aa2688e25cdcc210d01a64e57404ec24bd81538fcfd3880c7a1485031ced4693c4d71b2e97ea6287a2d22ed1af991abfe52dd764bfcdb56f3084e85e0203039067a4614e2e410a883b1cf0ccfdc298c978614ca1a472330e5d63e1ea9ae095035bfc8cc6e977317e3dea3bdea3406975ae2384e72f6e5e09ebc3ff358e4d9725020303f30c3bcd31fed704d2c67d83ece97bb8fc518746b11b291f9ff5c12ea436f92703800f22b2fc6b77bdb96880866086a8f4d621ef386020c90fe2a678b1bc3a063d02030203035d2afadc42d28ae8d74c7b5f96e56bcaecc01423bc9555ef9d9686271ddd238b033852af41b0f8f922418b3f525cd77f039ce8a0e41034e8a9c51de2daf331a7cc02030203020303dedf2c8185299a3cdcf5805fd538243eafabea31d99f30e0c56119453cbe0595035fd0c51fc95c362deb97f4d34a367c56d9c3bae67f33a75541d47299bf8c85d002030203033a34a2ec21ba01bdffa3e14bdc6234b1177f58fb0f8d20ded1e0d337abc9097303f2a2ca0856cfc4409a556f408436e6112049837ca240449b521ce77ded9bbb4502030203020302030355b79241b57362ed5a29e40e42647066862077123d3363d2776ae9a5235aa625031a0d841893cc3c11eefec6fcff6687e1f2d52c667b72e9896d185cfac2d52f200203020303267a5ba100569955e1248dd2758afbe9cabcc9fb5256aeadf2d9de2bd50fa9d3031c3657155c2172893ad0ceacfd6dbaac96e7450dd3572516816664bbad57307e0203020303bfdb95766031cea080daeba2879e20c2c9212e98699aa1a9ddd0f35b3f4f14d1031cb570e01fa4fd83227e9e7423cedcb4d1f2aa49a4b379bfb5532267cb41d1ed0203020303d26a86e0cde80dcb3dddc5689ee7aff7cc4aa63c69a65db071604f2d22821f5003453e11710c67ffb8aee8ecd4e3d9e482a3c3b6473055a8fda9141761be2a2cfd0203020303eed4e48df11288a42497f24b09a60c194737347e0f0324ace547906015c46763030f3541edd630e75e0ecfad8204446c4b04e707f29a911034b0d990df202058b6020302030357f21f30a7d272dc3b763a0ba582826c2888cd791ea5cfebf8c6eeba97688cff03942b80bd4855b40d077eb25e2677767cd9e3e32548b948133c53d5cfd98fb4120201020303039a912ac6df3a5af5d7cdbebd9c86dfc4d667901d38d17f5e265b4ec92851a3039a13ede5f8fe8fc936a9c053045c21b7cfac59232ed14acebe5a1270684c7ba402030366f89b9e4af2d9333431a7252441386158c0cd4f1416c432bbfeddeaf9a94fd303ea0e7f59ba22f8e1c16d8662786956816da4d6c44b3af63dbaeff9fa26ff58a8020303087927425293ead337b03b12cc3be21e324869c328321da791feace40848626c0320fde6ec582d5275f6c1b21b4ad7130f8e54c52d05483ef9effefa3cae9eaf51020303dd266e9e532095a3ef2479e8543f52ee9386405aadc619a2e962ad2f4ca7940003015c36f881ff87d7cdce55b31157699432c553b1c2be328b4b041688853ec960020303d58b82e1f5dc744c3e99a29dae08c0cacdd92b28e0966a5fb3b143479649353e0381584029a53e6c7f0dee68619e681482b9e36e43858e57bacb3554d7af2a8ad1020303f6ca9ca2515d3662f23cde1e54e67e0817607d0b9f501818a528ca1b43ffcce603bd381317706701d336e83e27c1cd699d0b616b349b0e28de4cd010cfec1a2bad0203020303af2d5e74e0ba57395bd0c11be5508a506eee906defac2ac84fba6ce7b577205703dddb21150e7c057c4df77ad73836cefe1e746adc52dfe903bcb543bea8eba9d502030203036cb57c550ffabdb39fe5531fac6c603b79b2551bfac7e208e7a1b1628607ff9303f46bdcac887fc8561c752bc45e1c98389a6a35cc0572575245a8f2ae513bea3f02030203035dff75d9bd1da1247aa0fc644e204d8fe7a916636d465210ba9a828a93bd8fde03f50e7e2741b63ce73e98ef6a769fa9339d941cf993b7e4b26305f22e9f18bc560203020303ec8a5f20ba3d3385c3ce7cd26702f5e40a4432f72ac566a3df649c1af87741fb036a000d8ceda0fcfe3ba4e6ae633e3abbd3deee0db83107e5ce0e0469b26e7324020302030203036058e9f8cd448caadf126fd3b7d50fbbdd8e2f7a8de9160a484ad79f8829bf5a03be9a1646b44327a504c96d0b2ac009d73adb23ba21ba3df5a5dfff32b74403680203020302030203020303ebee5c234bc2f660a9b3efe1bd2fb7d340182d904429b1f2a4e89bb51b1c47c903e51438724a9cf3725c22e07d59ba15acf0bbf473b37744164f122ac475ec42d20203020303bf9c131a0283cc46ca74d21b68d0b3a62d131dc9f4787ab60772569aaba63fd703f011de292bb236c3b08513f7b82ab7d311d0f80d4d3e173c2f8445028ed1cbf8020302030203020302030203020302030392af697495712b4013883b3f5ad2d370bdb93f0ed60416692b0267f10d9a3caa0386fa8ccd91ab622b232223f9a347f1785ca9c4b7323a2e0d19a7971c3afd63ff0203020303b4f12607fb8df583b854d7b235f4a64ccb2f4bc9819dc50f3a03ed0d4906910e038f64a125d14bb92752d65593faae8e41bb5e80e4f147b20f0c247078f6e7ca77070354000000000000000000000000000000005ca1ab1e58202d11035f2912c26c30c4f8957d3910a20622ea8709c8cd3e0ad87fa0f4460bbb5820c0bf0b2ab68768eaabe5fda7814227beaeaf4c4ee7e67f5d07aefaf5f0410ab80203034d5eb602925f13a2147a2c1439d43faa74e2561bb3d27811f02042466fb2804f035d9458bc537a1957fddbf6c5f13c6bfc9349abf1251df9d6dd48b5b574f6f48f020303bbf6401ad2a6b95a3e749f5b31224fc7fcdd083e7aeac9671ec3bebda312fe5c03393a914dd0b171b4cca2f5cef52cb4ed4b564278c0fb678e5e8f3d911b4addb302030356cdb16849ae7aff540b8724f73974149f71cd3f984360537159a273a5bfcc1d03791ad7bed137c9501bcee55dc6c9b030157a30c37fca14d39c25d9d5137ae88b020303e43916580d350f4da396c5763989f003085f6c468cf815846a95571702f1f53903e88243a0e60743a8285f13df8159992bd95c7f9546a8b5ef0ea2166fa211b8f70203039691d481d60a086435d9f914e8e2b5e5a68abfafb82dcc9d6de2176920c35ded03347f67f0fbbc63fa8a3b826c6491f42b13869a2abd2b6326d75d51cb30ea9cf1020303a06d3787a49c8745205aae2c80c6aed35adaa5a8e829f8ce8c29d55ffe8cadef032b843523c93d41eee41def0561be9ad7414c5bd9591d8e3723fcb0aea6170c72020303e56edd97325fff9e9a09d439d966a37ab63cdb3a3328b157445b60c3b91a86aa0381354b5bad8afeb2c183556c5f20e5d25c565cb8a738add05fc71bfb086737a102030301fa96c592fe444b2504b86acb3efb7befb3e241223f2d697c162be93668231d037f5346f59d4e0e4737f7b5cdde5494c43dcf2b583098022afa1d40024d434625020303299100220dba6b0afe91d1fb4a5c16f6cdc90da62bd73bd75b66063366a950f90315d7adf6a555d635edb76f96c7aeed7b5e3990ab1d13e0b01acd386ddeb43e0e0203034a527f4391b236f6ed15aeb5eb8839bca31aceadf3b8b5b7f5208d22f6a01b8903ecb9612fb023bcc161bfacadd2003a53d264c5555c4d65107fa01d984fc66017" + witness2 = "01020302030203020302030203034b4c181607792b3c46ea253af79666ab9bbfa3d29e8855be6c4e045b3424f6a503fdb52981685167cdab219ae57b3c5869e539e89eb29845d6406b3229247e982e020302030203020302030203020303dc378377acad40e16af2de6482d7a60c1e5f087d067fc716c2485742ac2e29330339535728bf0c5d72ec789110ff3691dfb9cf434399ad849a86ca6725977d3e4f0203020303481a1fc812bcc98ce37225fff9f28a6d8d0ea5c63aeda93b031e8e4603cc8e7c032952530fef71561f9028c37b944df439c0d2968c4f7e247a2ad12dd4969ffc8302030203031ce6733d3a496a34cb114cad924070b0dfad8ff6891f629ed2ae31326540fe120345057d6cbecce08aeecc475c91403549f4fe82bdb953895bdeded2fae6f8688a020302030203020303c4ac3ac799860160a30a3304b765c2c90bc414edc3739a5d098bb7e18009548a039042e98ef239f418f2bf7ad10868e1fa7d0f644458488adf684313dc3f683a5202030203020303949f805ade2be05694c8011fa17fab3646a43f38f96d868386f0ba9558ba5f960302aabd9fbeceb9711f46d634513830181412c8405aea579f470a19b477d090140203020303db978a462b93b2efa3aa3da09e03370b570db692c6d361d52ae1051bdb26a3a903916d67432c505e1dc33f3617e0743d761aba44785726309191e79cb18b666e7402030203033edca13bcadc1db9305f3b15322cc6d774682fffdfe2b509f81d00b16ce2dcd003dc94780e238944094e7856154e6d3e54fec28293a9a70eaf1cc2a81e874e22170203020302010203070354000000000000000000000000000000005ca1ab1e5820e72de8a1b9696dd30f7886b15c4cc9234d52c6b41b9c33e2baaf8d88fc5b7c9f5820f8fb80310ac041e7a5e79c138d7261cda5d8a988dc9268b5a8dc5318fb610a90070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000358206e82d18bde430935057c321f6c30812e0eae2122da6af753e25974c92f0d7b50020303c6cbb686c9d7a94f49dfbee076ae1b87f1c9bb5f33b7c98a71816c33b4731a3b037514c2021b2bb805e2a6060b265dd53c069a4587e19cd7d1af99d0e9c3d0e550020303784570292bffbc3ee00153e5806a317459fed4c1d84de0515dcefc177404865003f08c92f6786e67148e8fb2bcd4eb813a665e16a270b475605d1d84b587450ff102030344c5e2a1775873020ab4e5a588e95d6702878cd46012682196dc39738fd8780703a6d5ee17fe3be7e20050e4e66c54b5188febbdd3615f832c35b073078258b214020303a42b38dcef18f890c02cdb90473211c95582727b83af287cbfc8a3f10e29649103380623684a9b3b341e01ee65908a6aac96fdf1444ca255b9dd5193537d58709b020303476c478891a8f8d905ebf9e5c031ba1020ca1436538bf9b97c6eaa1b9512da97038c77314895fccd4edafbfd73b531f4dec6f4671b6acde83926907ab376982f310203036416706411fa678c78f77dbfb609d65f63d6b04a8aae3fae4cad23419f6e738b03b6ec59ff099f23c5a528e805fbd9457736b100ea0e96390eb536046b88da3db102030334468b79fd36c8bc812c6613d176983aa4be53642e7e56421faa4ef25031fc73032869ca46586018725007aac483055d85131fcc4432c9a72175a8c6263b65c1ed020303676f9f98ef2cdc44ec8d98d0153be2aeb90b08386286887c94567950df1216440385bdebccb7559d68f55e26ba0980bcf7120609c7bb43cfc1f701e92f670ac1280203031117969a5ad58cb9a441ddd498cf3bebc13ab5aea1ceb29ddb1a226c5343c6e703425597c542fab13f686a7053f6c1e2635a729f8d9da4c01d763ffe9965ddd63402030345f2b9e446c9e743f6899409a4567a9b7f8770f711d39e39773d8173c4ea3a0c03cbc17bc3c54426fc8cf2b13b1ddb800509579856ce251beae01d924a92a8edb8020302030203030560b956a67a313d6d8939eed4cd80cc385eb49f7b6dd269ccde33a145f1216e037b3e0569695b777df45db97a41b025b57c680ad61231b61225fc7825824c4c0502030203033f1ce9dde58980c5bc34a88467f3b8cfd334dab19f28050acc53f33aab0b366f036092ba2243e1d8e20c2aa4ba0aee9ca063e8e8e6da493269065c227232020a590203020303921f9061d1b4082d20b7f9df21566609ca6dc64cd0ffac2625e9ff3090ac73570371757934d2c7d4dfe9b7b1e5c71fe18b66cf56c540c3d04310873976f79ef9f602030203020303e8e045e00ad70f879f31e498fe49aa2fd4c81849b6c55dd98391681351aac7df036e509788bd99ed4034d5fa3901bbda4cb6f94d709b2d54eca545336569791f36020302030310125b6177d5086fcdca8e0607e49e6cb21bebc95404329c9769a7d3ed59e2c4034e9acfa4214d459d1c81a304d862b2dbd4d832e71ab851656bfcc0e9c5b3e6f60203020303ad656bdacec77a2e7e591bddde7b2c7ab9b928945ee65898ff35900e25f0c21f03e47d9766945c4649bd44422f5fa779e92267d76ce44f396ef0b672215e43ce7802030203020303d3858acf0781afe0adae49a25d1724b36c9989179cc884b9a9c6481f89e57706031da6fb50879ede58d816d1624292f0c60a8133bcbc671dd92d0f9cb8d50fc803020302030317221db9e049aebabc83cefc3ebe7040ec1e82022d104d2c78f796753f76f0120352124f3ffee53f7e0f9a0068d5c0b0abfca5aaa148371c91e2e81df5fba6f8bf0203020303e00ce2232f3e208dcf050f887d02c7b91170c4b98e1d098ec5238bb3d387a41e038a0379dab30ce84865bb4f0834a9c3fd7bb2da17994abf03e89fd8d754bf7aab0203020302030333f5853903cb36caedffa12d0aa0a01c39e91309629a97dddafa8da4f738fb3e038e1dc6012aecd5998053b5878c6e3a398c8a286c7ad4dc0b55f043e4b4210950020302030317e041d91830fe8051fc73631cfd56519fc5bb88b5369298da9807b40d93733703dc7745bf8dfa058bcf1328f79efc9441cf5ad5fb5763c75bdebf9492f88a6c8302030203020303bf9a2780e63ac26ffca6df07f182db72e3e65802b277ea6d40057c383d5a37e3036c1548eb1c956ece0a876fff4463cc3f2e9c3b6eef88379f07e6d71013eb4aad020302030202020103c2eebac9e260dad1f051fa75846558dcc0ed120d1a7699fd1d6b739a3419015b020103b9d64db96b69b035a74a90069691afa2b34a705f3ad3aa82f3757c7a6f2a9f17070354000000000000000000000000000000005ca1ab1e58207af492bfc857210d76ff8398a35a942af892e866b1f4c241746b3ee89ca002595820f889596e5b3d4dbe5bcab5cde2af26d3ad8d88bc086e8b4f929885f33f6eec77020303cd3edcf79c7e0e0d6b87ae523729611faeda92e77bbb59f739e9a6127d890cdf03009a5c01c3cb27d2f1ffbd3fd77ff38f66991f64174f5df1411ea21ae2e22f250203035334694d8b56a2f5e0d0ded81283e7f36b3da6fbf2e2d1b469c008d7414296be03953388b0cacc587c5ca452ba8e96a9071958ef94439690bc14866f556a35ebc1020303ac2aae05bc7a68d238e9a9bbd2d5d07a001f8f3651bb25f5a6d6dcbb155569090335b6f55bf3d56419cbc3a45d4fa6bed330d9e0391f8806c97a7aa4149d06725b0203033dfe4a2f0555ff318ad12e49515e712f339134af0237edaef08553d9d67e260b039cd50a46feb34ab47c24391a2579e601956897ad6299bd14a4c8d9628a37d46e02030348f01fedf98979a5fb3df07daded956331fa6a02f697dfe29dd26e71111de5540387829f9a96ed82303e86550747e311d5dbfe94cc71113600595360abb512cb7b02030203020302030203020303eac48d9dbf7d162797293e0acd54382d4fd53e80e29c9c43c51dafb05c0880060306b13c75c66a6e267236b6579bcca576ff889e323ac6ffd0ee317e07623a3866020302030203020302030352e23af8570aeca858a6aa5bd20d2c63a92eb08529a9e6e5fb245aa72c5b72ce0334d7cfef6cb28d62f63cf907e3273d76a8bb858423c6ef446b056fb4f06210e002030203020302030315bf4cd3a7f33296bb4778e216bd18adacf25c97f8f4df9e1052dcba7b6edf2203b3637d0b1cf58c5272f28f8354a764d3cd48ff7c04f807237da8f4a1e2ef5db5020302030203020302030203020303c018dfe606efd5d17f3d45e91d33d3d7ef57d92a1d509291b1556bbb7e78dd0803b08ff5bb304aa8741af608415c541440edcd98bbc0fc849fe2a89c2c783341d502030203031e0eb0ac5664b1d0267d3c51dd66d1828c0d01a0903d599a317e4578926d4e3503330e2ccc546a3db52e796943aa8960d6d483a3b941ae0caa21cc7b9f7a3c2bbc070354a40d5f56745a118d0906a34e69aec8c0db1cb8fa582000000000000000000000000000000000000000000000000000000000000000015820421c2cc0dce9b0fbdb85cbe43bd6c2a1af5a6f5da756cdb8b6f2bb948e3a90da020303ac874a6acbf6de628134cd74ad9f336206e7aadb1ef09456b6267a770612485703ef323528b761720ce04927a54b81025a935f070420d655217e40eb2e084bd170020303a48199a63a429cf44fb39fdbb73098a49dc171e03d32800f483780adb9aa06580388796e8ab2076fc77f00a5096317ceff8a54da310b014a0310504bcd76f8b8da020303f8698a6f24140e0e37f49032fb2da6db2c8bcaea8961a6e1976baded0d9a8bd80371b277886f0d14b6f82cfd063ecddab10fb5da5e0666e040992469d09a6bc8b0020303dee2b54587eeb7db2fe9ef0dc262b6ae679a5bfff89c8f403d181a1d79107b1d032aff27f522ef5fd88213c3865e01c7b4c1720d56778d1bd0e48e6a86fb3b07970203037d0d29240ad72800831a91d8e54019da745c6c6a630a625167723ace857bbb810305edd49a8cfb1eea157734968e95e8b8620c474c3cfc6f3285d3dad36893114302030349b1bd34664838889a2133d716143cb8707a15745738917bfbbeecbe871e6e90035ba74ef0008ce80ac6d199cc4d217aaa9b8a5fd58f2d329aba4e061c16d99b620203030d600bfcd6581d405aaed26aa7cee976fbb2bb9c1c1390bd3eb14cf5f661022303acf3369b84f876dc556ed93718d616864020b1969d24170f4970ddbd944e1bd9020303d5bb847f016f33d8cac460756aad70173d8d6e37c37d1b69e1b1b45c52e5996103c3777105bd00820b49c89486e7589f0ccd0244ab6fd4b1409ba86dece7506f9102030356b2929dbde358b52b652bc842c7a42aea162f0d79bd7d653b5cfee34e9f0e6c03656a686adb3bff7a9d8841d3e296b0dc61c389b399677222ebbd70cf0c19e70a020303e5bf4a0779ccfa6d42a01e532bb6120b168699bfd3f4f44a62780481d5f86588036efb82ef530fb604bdff43bf1ad1a7dde41522bf8a7f5e724dd3074562b0c0ef020303036a50ac7a6e425842820d2a4e07a80f416706903e9d88b5824559515a901aa80303e3c58c1dfb4f5a5a5d1180dd010ceb33a42a0ff7cab200ced5562202261aa0020302030385d60697f5b4482fcbecfaf5f53111681c9b48ed7bbd2cdb1a257bb7f26db9d103ae21f016eadf6448b913ba498fe3d678b8bcdf9569b026053de69bd24563ef0202030203032fd50f1a5b8eddbd5ccb90e37d9c190092927af9b26a1cf8b4576d7982476fb603436882f441f09768b000722da7ec7c74b6f0252c24e16b9e6461ce4f4eeb791d02030203034eb00b994d3a8d439f47981f68baf7fb0f0e88e2167243c6b005de3c48b5c3ec03ac5626fd3f4030d088d0f41834de11510b59739353238241138d70bd8e05c22e02030203030a51165872abbe7260a6777cbbd2f6d81dfcd07c1b7c0783659bf8e8ca8e77b9032f78c81c54fd31d1a25214fa464424ae6e6399f15c1bd8987825f1f0d0dfccde020302030203020303215472473dede3eebcfdd93b1ee898e4d6cf33261a1fba12ff77dff2fb8a0f27037938ac733af661730414e88da9633c04a8914c9ae4263a4f8cea7066e6cefb840203020302030203034d6713bc006056011f31ac6f935e71e33ab8045353e9e138ec9743e8574a8d2f03fcaee2f22e1561702d029c465b755ff5491e4114264dfdf16fe9efd34864a83802030203037cc278f9b41fd17fb5eb3c839a725fcd1ef6000189fcebcb4214303f45dcd2d60386c3bc64da300f1a87efa2eb2724553e41348057fc99d5c23b5b20e216fde46d020302030376bf2ddfddca9910df80bb0785add76937d1e90e029c02b04c0cf421622a232803ba2219bc37e93a89b0effdfc3601f58645c1cb7e818f2254c8fd16fea4ba84440203020303fdf1c4799edef5fe2960f9148627fff521e591247a224eb8d05eae3f51675b560372adafa8e298a14d0da0a71e645a12a23def78db8e81f7a68ef92aac7d5700b40203020303f5a794d38718b283b47993f3bbcd67c76f84c47fcf2d35373fcb7f8a0f43a06b03a14b12d1f03790ac75797e463a8a7edcfb2bc80b65a7dc8d1b15d00cefb315d5020302010312f8462573dc83d436d2498e68019babdcc911f482e1e00c1b3fda70e1a166e40203020303adcfa2154e38e2cdbafd5d56bdaa5dca90a5bfb9c36bfbe140bb31ec0e66716503b5aaf1a6fa2e80ad8f4e49c51808d2898fd74f539ec5de974b57c27466f5e7490203070354000000000000000000000000000000005ca1ab1e58205956a0b12f607189a063054545ab26ce76ea5eb4c9bc1e8d8161646c93ac66515820da6aba51eaf87e14a7585e52e23cc0b789c61b3e808d2aef704ae932bb2ab49d070354ee5a4826068c5326a7f06fd6c7cbf816f096846c5820701c251f0448beefca8b47dce2e42f136d224b8e89e4900d24681e46a70e7448510237426c03237429400000000067445fb8020303dd24d6adc0d7b321eb19905b22f1780707b0d7e30026716c3b0d7ea311cbfeab03e498dca26358e5fd56e5464288da82073a17cbbd112e322488c12bff1661b49b020303413600069144f3379227184b3d365f22778695ad2b812ffe56bdec80df882877033b1e22049401430c9208943101b5ef3e70d99c9853e08591c4729e0f31f4bf56020303ffce2337b88b26e7b0582d1679484fa995b68c0418d72f650531db342e25f12e03493c1bb3f993e9aa63e2736b9e0826f1309ed298bd95bfc169f89b6a62cbed420203031bacbf380b1eafbec9c534577f8972d28087bc6e94bc276ec91e66a11396f07903bb137addf6042ee1a1eb0170ac09f0a092b2f7682f718d5986152d56d192b347020303b89984a9ec10a5bc5835efef55fbf26f3477d21372a55ae4abd26c55ee5e323d035ab47c29775484efde5ad8cfb1a399e9008bcb66f6cd77f28c255980633aeb5d0203037902d8528b89dce0e6a41ff89888121f42520936f3684bdc8481094f1e046b4f03cedf898a501b7bc036d92797f971bf9caa1028994a8d6b15ceb79e4ca532e7cc02030203020303a366f69c8b19d47be34a2a6333298d705692f65daf3fba95d6f48b9676b6cd3b0351f190ff80b28f339034b6be161060cbe4837cf22e0c01b3d5a77b8f349c4f1d02030203038d8eae2b45a21838dbf9f517dae99ff0bac7a25d4756a7a3315c43cfa7dbfb9803785e2e17b8cdb9628ca4c2f963eb5722918462cf75f91dd6fd00ae84d17ba2a90203020302030312a3a949b95a27ae6f73e9d879bc9c9c6eb6757f1c20ee76d1f52e1d4c9ec4eb03d38f8911a661255b0ebcabbadd44e38903841386863c97499f3e57a06bc5c3e702030203020303763e3e4c8cc4a4b30afaaae229ff20ac282d74c923a88be140293d62b2d812bb03b4b4e3386c676de1012a2bdced3714094e57803a98920b0eefe63e186abdd4d902030203032ee550fc2b119e46e3338c971e6f44ea838020e442fce0c4a34b359306a00379038c72343f5e2ac968c7f1edfd71f18128db6b52aa476fbec372eaa58a2acf45220203020303221a1371f01a251478f2a6673db891a3c412d954dc9e741ea2bfd249abf428bf0325059126652b0c2c46d78a02eba6c4df473b674ed378b17827c634bd119f5422020302030203020303313abcaaf43f5d42589a57c6fc0bec04526b43a3dc139415af1de50f8846c004037ee72e1eb97ffd7dfe0c7d40b575103edd3e62c030b86362c41630c6e97bf6bf020302030203020303508d990b34daf5a3f925c435daac3d293f6d861094cc2d343a92c62428fa66da032f8b40a9211667e9c44328d6440091ecb3a46bc15832f7d7cdfa8ec130b527fc0203020303f993f7eae6e45a6f8557c6c5d0e912cb41b71d2bf37f38affc0b2d8e054193220315eeb3ab754628ce727cd0b7028ff8ed3291de7566b99066e127185d043f595702030203032f2c132f32f21e267ab64271e8f2c0c39fedbcc509c4589616cffec21d7332eb03839857347599c19c43a0acfe53e1bb5bbe0d68ddb49cee05f1b24c5acac24a150203020303dcd0869ad1107856680f6bf164623fc709d26d1a0842bb9c60a383f255c0ec2403c92cb1692742c4e2b6a91d13c3b371a9dccd29f898d8f6457ad052b1da9efcf6020302030203031408a1feb1c4cefd2e71c1d7ce58e6b4c2d139d48c67037d40dc0d60390af539039c51675ab13cc260ab6875b12824ed60903c1755add14024e27508ac0a3b9d81020102030376fdbe16ba7e2f048d9c311cb1f99291b4f624717ddd7e9f2aa653099d19314f032ebe85ea3fef7c7033338d1ed98e187eddf75dff4772a23e19392ce61690f77f020303f901f2ba5a7a95db9ea7106268f17f341206944377d1f006921211069cf8a0a103f43daf24401f9ed2d0691570a8ccdcd016c90b722786ff590276f7cd5933ff3d02030378ab72606d2d32782ceccc9c11af9496f599dec259281c01f0c18a3b875518ed0355b4984426bd4db31ca5d70798a18280a4d319786bd897a29365d2db7489b32d02030335706adc0febe81255c960be521ae4c7a6201b2db502fb7016a5d4d9ba36c58803ef3e9f16053b7f799f207451eb3403eb95301e9c9e721dfde0c41ebd8362485c0203033b59831b753c1ca3ed58d3293aab0099027f87ff97f3f7e92d9dfb095839497a03821fc506f41f2a0bcce20367ebd6ae4b461e110e1788d190416c8345ec72c364020303cf6d91b6b57705a8f02a367997e807f49dba00a5bb3e8d0de25eacad5486b88f03abc64c2300b90b30ae3b11fb71095675d1a62860a6471a1a2defcf624b8bb4d4020303be890b95c3a4c5c381f1a00d6d98da4cd8467e002746a8c52f2564e41319d3780394b620da3f2c277f0d4a70c7a54a7245503ed2e808bf722cce0b503e242ae7d10203039f6bac7e82bf632c8b003eed17f050a49d2ea83b6a93e09295b3b3c51c55ada6038d01937127f83a85e3e655363f467385226f7e406409528791f6e2375184ef5e02030203020303e2ba22bcf2fd6923a2ffd1ae073bcffad33e81f4a7cb9cab82e130c63a213b6e031dd2e6a82a0638b027a1f15eac2bceca26ef1519de70dc99bd5275791bab4bb0020302030203031d0be4b4d178c76d39a7689aaa3a9866e63b999a2d11dbec2f04787c714dabbe03e5880788e24aeb6314512538d4cf7382b37132d4d2870122f47de8ac0d09eb020203020303b9af076d8b0e683e730de94273fbcdb5d2ac9f29273a9ffb38875892722f439903e22b2cbffaa7b1ed370a3d8b87199e1f1485703145dd3de0945cede9629702600203020303a019468f5d28919dfcc2d7bfd844492f2ab1df6400a17627b31c29ea02d583f5038dd13cd4ecf8c4151cebaf6e2637913a2310a81d4ecbd5f5fd2f4a4c315558ac0203020303167bb488d1aff473f1027bdeadb8e0e7a439f6a589c78caae1a3d045e78da60303ddda65ddb3f7e0fe430faaeb49419075391fd2559659f2ba88d3655454e079e802030203020302030203020302030203037a46bc17ebfbc47f6d99661de00074c9958e0f7fd66df7c77c236b89b165472e034b58bfe7c7506d2891367c270ca350269dfc0a08b7466ec2496c6330dd602bb302030203039b58b0df7fae59a4cef25184d849214bc145cda115b2c0dfd85fd470ecdea70f0330923d4d299efbc138d4442519d30cd33a7827557231388b81a6ea9c65eabe6f0203020303af3fee608c2e8e5a30ffc6345d86ec1b2d55f10e518b4da5e8eb59abde07b59803c2016682405d3a953eba254601d5fc0b8966a33efaa51918a4a41b8e0acbeb4602030203034b1387aa6d0ab944e2ec65ce38c8643a0ddfca5c3059718f398dee501291569603528cbab25216c4397a402fcb572f0b512a773dfeafa59e401989a4da13406bfe02030203070354000000000000000000000000000000005ca1ab1e582054b6c4d9862a1658dedebe99a0f61d94c5d1515fd031d0dfe9ebce6a1454f5c658203f14693500ccd0260659fd9eaf69570edc0504867134ac88f871d91d388b63690203070354914e7547b9051ea6226c30495190a2efa15930c95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927548382be7cc5c2cd8b14f44108444ced6745c5fecb02030311ab6695ec969171698c1f56d4c05373d8505a2c7299fb05cda1d4351e22bfa403478b94ae515fbd01728835b532c7c45ccc78d200d3d004da6917337e139eb729020303ffd14369e7c7f7aec3a890a20234885f2c9fb9802ec318d8434ebcd58a696153030ddae742090ea458c3f232dc894bd8cd3378b4b4590a0523e09a44e0439fe0db020303b1688d8c7806365d931579ccac8dbf7a8d7705ac393159dfd9c0395ab7b5ca5b036a6c978a565b15267de4330de7b6166014082043c5cc80370953767ac501ccf2020303f181e47adf88e965d55e1153d76b731c261ad7d7720823919fc11d98bc144d2a03c480f344ef22a4532900fb9d7cb9d8b5ce1e4f11a231e682142f9ffe1962807d0203032db40fdeb2c5256d5a237b6134f844646b325bfc12c687916327e21a65b1ae6a03c73ddb4116e07a00066b925a207dda51fbbfadce21a7459c6c2ae7f598721089020303e81fa28f73bf124de71b54c67334292e397e000428de699c89947d793cacb9da03173e567d72ac2c265860d9103e791fdfe3cad72a9a1dae15d9bec6687eb506d702030305a683365eb32bb92967becff0dba79d1c23ff75b2fc3d40f9a1573b993747b703b8b1075b12927a8f483dc7b802c96483206f98c640e49e22d4b426f9a9eb750f0203031276db0802c8235f9f248bbafaa6cbabb75baead95ede989894ea6d8585c3c8703527ea0179a8814d423775e1f381cc8eee0797216d71c79729ab186714e4daf3702030330b1e1f7a1f7dcbf5cd00932de20748e546bc1a8da9381fa3d5066f3c02b61da033f7308aca0fa70a938e45539d5dcd1864bc233ef232c6d38fa1dd331e536a400020303ad8fe61eca50a88286f382461ecaa93dc71e9aed12e91a2e9930325e5ffd1d7903fd046a02679f734a91031aacb4194ada537220167cfa68306b651433026e6478020302030203020303b7e72973952f51f913dc6818649ddb3c5619982f21e56347003ebe3b3788eadb0384757ebf158021f4bfc0d9a1bf844d13747328fd367727cb0a2d9b7c91926c400203020303593dd6ef2d4c6f8ab3253bec454072a6cf779b5acd194d43cf4d30191d4b24fe03d80a7ee4528b16cb482fd73c259b2e6e4fde5d5d31be6b97703fbbb17c3e61d20203020303992d90fe15b918f58e8dac35e96d0ebf33834ccacc8a69b6a075b263d0df655e0301b8df4b987fcf3a98000ca00d3191fd2292dc9210d7f1ab382035b2e2d02be9020302030328797f5226ad9a63c859dc61073e8ef33fe15094e61db64bcde0379f055f733403b50fe3e685c2e442a3a81715f64a840afaf1f81b49ed21b3fc2ead0620f6caae020302030203020303189a1bc58c5621e4845025a9c534fb9ad2bb2f5be276faee403d59266561d652038325fb098a4b3a402690994212511e710d20cb7966fb26b3687fea719eca217a0203020303ca11813aa459d051b0411eeddd18070506e8fe2054a2e22a763b05454e87cefd03b2cb46d28f3bcf15305b0654ca442442420ccc1b28e44e2e2c84498571b5375a02030203039385ca432e99a05cca8aa7fe5868222cdb6c928c8bbdd7eb13c22c5abe1b11cd03e8cb7cbe434eae4b8b7910183b3b006a1b3df70ae7b30248fef24d64a004c3c90203020302030203035fb731b403c8979aa552e74b3534a247c638547dc7c957467a4b08855b29b74703d49a5d90635d403354f849daf9976a4f4dfd7dab5517b254638eb893511ebcaa02030203032fddd404fe9317d561378c78f3afbe75e18c27face10d4e6ea03fc2888b22e33033c8c390d481f51cf6b43c22677a971beae0e62e8b2ecfdaaed05b48ac0f60294020302030203020302030203070054000000000000000000000000000000005ca1ab1e45e8d4a5100002010341305ecddd1b56329ac9f09a1235eec6ce6be69492a9788db13e9187dc21e9dc020303fb1c6d1aa6d3f3bef7a0bf4130218b3b168f9447e69ebcd3b68c2b2f41d9b2ef03652ba6f9b69aee3d28404079416c2f8fba4078d66b558c7a8d9615cfe7d3bd30020303d9f042d0d2f152e24d8cde02d3a7d7a1fa234efc5dc259572d412a2e607215ba03c5b76ff595e1d74a22eb44a5aed94f3225b6126c2c28ef04bb75e1d3804925ad02030314a2b125da4db5ba673cd5c0aaae8c5bf0857fd45728b868cff3f40eaf9f82790393e93c4f4b58f6f9d397d136319a29aa6b691b652651513bfc2297107379ce62020303f00359907dd68b2ae8e2d252d3313f3ba2bba16d21995333b2162b24c9bbeac4036435af585f0f75e60d362629108f6768756f7b39f1c70ab7f79e6b4e1bd9f08f020303929e2f8eb833089a3773b497247338865ef336de61e7da4a362eb3e5d5601a7203323197b010e3205d910c230463758f39cd6c01258db0a11b9b47f4c278db0494020303ab4bdc2dbea0c00b12cedf9e968135b62101bc1e20e270a1f694ae6a4686627c03140686262c769436fdaece3afe58e8a4423cbf381295a85237e52fac66c57879020303295e1973d07a067f281e3337e756bacf10dcc295f7074564874ea4401eb2a4e503cfec4348d3a697dd4f1835bc31c2615f56f92a02c1935cceec2501c12b8628f10203033892c29a2de6aee7888c2448fdbb3252d32b426bf74edf79223e4ee886fc0f6b03ef287d8ccaa574ebdac646e6d35bfb3ce52b00eda1e671d7d7bbf31bd59ff7ee020303c58f22b2dc782f914b31e3b87185b727a0bd2e2dcc41481e31ab1b26f222fdf703f0dcf8a2ce85de4d96bdc4c1a9c52a7ec54cc771750f0ed7d6c1113b93df65ce02030203039a7c26055306c8884baf96dccb2e3bb3cb30deceafdc73491bbdf0333400efc0036ee70bfe41de62ab49a9a63ca415bb881a92980f87fc044f2f5ae2e84185dfea0203020303c4332d86dee9e03fbda2dc0eb81cb20a6f6a20c7df95090f09e47d8e7efa1d7b03a698f30a106768bc9d451fd96a6808beb2b799deec6423688d02a9ba34b4af280203020302030203020303398dee7348cac5f07e4865c2049207722ec9572e2ae69b21a8cbd1c053c44a0e03612d7861c014aed261de20fd1109fc86ae090eb2c37a02b8a6072bba1c77c8b50203020302030203020302030203031f28ae8c421086878704ec730445ebf2ff23d186ffed24802f0ae24259c8d21403a8e38716cdd8a09095a7036c686009bd8236b1c7eb9507540fb981baa9a8bc4b020302030203030fe638892efa1dbdc2881def87e77dbbba95d91c8debdd9b242bbf0745455a7403e5554fbb47341d48f82f64a26d175a7d3559378657e77cf2de2eff917b95be300203020303512c2cf0ab4340a1623bdddc301aa586932f9413ea9bf8c0f1849d2d70d5d0ff0375d0cc499c7f76c70939fd8d633c658747eebf0eb138c15d902c34f0de9098030203020303b78dcbd59a3668396357cbda038d7e5bc77aac4acdb3cd3a75e96eb05079a6bf03ceb3ed2850bca5df0bd69ce2e85e9daff43cdb4c79f58685340f521521a0943f0203020302030201034b33ab5a3b8d3b01c374c1d7fcfc714398b7f0704ba2f1ee757670269fd5a7f7020302020203070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000043840c77070354000000000000000000000000000000005ca1ab1e582010c18923d58801103b7e76ccd81e81a281713d174575a74b2ef0341f6b9a42fd5820b8b76bb549992d9bfc44e3b36d087a175b2e78b9584fc752eaa3013e0bdd31e8070354000000000000000000000000000000005ca1ab1e58209bd14ac8c1cf553e0ad3a2c109b9871eb74f3c116bf0bf492ef04d2983722555582090629dad0a40430445b7d2b25a8d19c5d7a929608ed7890877a499aaca01ca5002030315edee06840e36ef17d13817ab5475d12f7bd50113984febf31e2cd80c08952c03d360a7d78676862429feb7c95d052c1e63379b8ad3becf085a21baa353ab93d30203037a2fb952c2cf8e85d9706bcbcb5a69b83b13403b58f06b0767f4204acc5917930310de142eb3b2790cf1e3694b72eecc7e8ab3860f543c15cc24274ff69570f009020303879875563fe8a079ef71e84b6840b187c681499095de9d02d8b101c9dfcd111e0395e9fc3b000e49b65678f256d247786f72c91494c960d117b7668045c35502720203033d99bf4088229f273fa4910aad8f0aae6f7de8fd1832ddd14c8fa3d083aac51603b40316099ecb013c6dcc6ac2a3e831521afa35ea0ee52485c2e8cd40bd81fd870203030b576686ff79ae37ff5ae2d4240131369472a24104dcabaaf3c348da66a638bf03dddfa8283748687718b9672b0a69a6b7758ce10eff383d83986c1a2aca2910e002030313ac1b1da5a5a4232e4e2b766aaba01f45f9444b926476f01882e30d4cc6ae1a0323f57d2012e1874436ddc007ea8bc6dcbeae6e0dac6fd044c8375d2fe593904502030381ee4d8ef714022c3c5fad435af845d213cb988ef7561ddf65929553b70dd69a03178f9fbf18b1d12feb522330c82fe96d15bc4964e1c1053093c4903149652e6b02030323cdd6298c89fc39f87595dedfd8bae3ae7a40b66f312333394482169297dc8d033517a6ff26c035b9f822da8d2abd642c858696e0d970b1026cb524cb0844195a02030391dd21f4c52970493d439537192b245ccd2e4e3e8e16d90dc74e417718b12f9103d56b5ff3ad5ab9205b2d6f9c508e744643224a7ebca8c1a4aea71f01e48b186b02030304375ae3a357e874c2a10fe3596adee75d0ccb96e63838d8db70c9e402663e9903bd8d2e9ed97a66281cbb0733a92dcc92158740088acc7a9c834d8204c0acc1da0203033c9cd711a378c8153572663cfc686ea0324eaabf0feca614928eab900755299f030bdcf7033e475ad4a147377e1bb9ed8619b0c88b728f7935ecbe7bcd2fa82c7c02030203020302030344d578674b6be511832af115d2669571dda0918b6cc734aa4acda37260458f3303fa95439e418988542df6cc8a12cd2e59ddd44643f035364c14b146d8665ab538020302030203034f9b9d5ccea861bd0aa15a5fb7fecc1a6d49b66bc7eb1e8905701e3e5728957003a6bfd6ce49840bddcf6502294487dcf1e2b5d6b06100a0b1259dbe8c8bd8e44f0203020303dc08ac42d157ac7d835fabb64048b54993bf6636eff62863d99d2c8905f1e6050362a972a91cfac6bfbaf2c40c724f947a405ce6e647aac0a61ea8f467a49b41cc020302030203020303a4be360a2b33a98faf83a47c1611d2114b590f1a626d48f700e1b0d8361f65f6030e4a6c2e589051b01393778e2bd41764d047b0394238c514e3cff1bcd9f17fde0203020303a19150f49f5fa1e3a3e09c7b8e3a80ad9f901086b2acacc8a5712c945ab79d3903374e7d15b75adda866c38fbbe1cb5bcad247ad095de306706d40855b922df14f020302030203020302030354772bf7e2a00683456b752e674df624b9b8419fd126964d66a82f8ba678977a03dd8f48954ed2bb272c5b94a49d1ef09d545062536065580bbd306776bc135f8e02030203032108ea8ac4227399387099ff7faacb8c1e424f5543edb67d7d8ed0f04a4e0dfb0392659304959ceea896f45666a76214b0f96c0d0ac9ddb78a96f9a0271e7b579a02030203020303870c4f9820964a725c45a91364107661534dff05c30e966b1946f2157844ec0603bf64c46a8bfb74f75acb660d0a43078c21cdab2627c014fd463a56ad85cb7e6a020302030203034b81bf62e5171445bc7bb3e154c4236543feb39907364512e7f8bf3010d0bcd103c1e217970454c195c8cefeedb6eb556772703cdfcbb9473b1251407e3af45d4d0203020203a8dd420db1a92952522be68028b8762b9c2c45f11efe01d4e2b2a17a8aeca76202020203037c03317c701ee7c858e7c429134f07bc4f3bb40047681a2995924386b065a44003eeb2124d66ad9fe030707b71b337ead87239fbfec018f78a36cf83ffe6c1f3090203034479d72706bfadbfc681e4b1e0c17fd702e94ff5cce085697fa4915b9ddf8e5503978f813e60f47989d365c08ad74b7b5697ac63a4d729225fef5cbbf858dd9e360203031e3fe72c68bad17795f3ab1c89427a9db9297c750e25a03f4d5cc7f4300ccf25033477174075c81e1ea46067ae9766ac42b6e37b0122ca757914f2d38d5a5b0fd90203037c82934570e0e51dadfe294202f68ff1baa30ec7f3d972fd309af51bb73233b003c73c4ff799c5d7f7900bab9bed27acfd777778f080034d266e4b3a8cb275180e0203032ec060cb265b14a46177f0b9263af186c22d8fad7466efd3dda1a76839916f720322d842fbac43297665301e5a01f595b5961a7617045e6f90903794e64ae970f3020303bd26ad01b4a6d5fc9578bb889728e38b0cd1929f289dd0733beea3122035d8050305574e7ff67c46b4d58103152ffd94f950e5bf9a67a405de972849bfaa7a335e0203033c9f565b7511511ebda8b766512d87d572c4958008f933b4e55604a5f3c36e82036a24bb5153ae46e102a28f022b5305705a84d70a4d2d5b399a09ae90bec4c86d020303ca003945b6df159b5c900df34d54d18e81551ef946e6ec76aa5105912bd41228031937941108c7513a3bcf7e078b1b35a9816cf095dc7413079922c0eef235cd950203032c581d00b2b34c68be72f5453d8d67f30797a26d4b0df66f004fc0075cc8eb1003e71d380a7d686d28aca8fa3508c37b30fb5e30bcd348e19dfa6b547f2fda4fb602030203020303ac0a0e8df4bc9026b7b241c34d72dce10c8424eacea17d1670103c8ded2446be03f7a62663d338b5b7e9219d01266b1772ca3720daf925bd302b7dafcf8abebcba0203020302030366c76d1cd3e3136e15f9f29f3dcc4bded2c760f251e06403ea022bf5d67ae2d503c574a334d06a611fa0340a1213e317efb125ace4eda7a487ea00075e9a6b67a902030203020303ed1e8a1502eb462bb7f836f6d72486908e1b2cce7cb00e387cc1aadc827874d103bdbfb8f970bcc72256ac4e1eca0809217c625c6412289a6dc5dff7c91454436602030203020302030203020303e8bb2dae757d043417195292bac773cda990500845f91a94d00179fe89525c3e03e385244b15001ddd43b0180922bbccf4040a86bd116ade66fc3aced03dffecff02030203037d8dcb012bdde19a0dd178c1de91d21cc866a76b9b6315554fec4bc4f5daa79203bc46b61f50585799762df053271c52844c6fe83156fde628c8bc4369c4fed18202030203020303c998c4c602a03cfa0c92a1542165567f11d23f2ae5fb91d04e02292f8e297548039447848097d9500f21ebe819789f98461e01aff7cfcd442c8afe8e07b87a95690203020303f6441de6ba2bc5cc9ead4300519a94a14a80b85f2fb0fa41e2211d7c02af0e6703703a99e4c2133e89a6e892863f14f143cf5f2ad94bd527081c8be6143f14f3db020302030203031da626649ee092857f195eb0059639309d744972389a4e748c471f16b0fc3c2e03f4072cd036d5bbb777ad24fa0b1a235806ef25737404d7ce4f83babb76bf090802030203020303c7f6a9615846a2d627db4c940098479bce3c61c8fc1170d0b7f8c9ac2bec5ea4033664667108158e9377b49cf3632952090b6eab3ba6eaed4f48ca9d5beb273fd002010203070354000000000000000000000000000000005ca1ab1e5820eff9a5f21a1dc5ce907981aedce8e1f0d94116f871970a4c9488b2a6813ffd41582021bb230cc7b5a15416d28b65e85327b729b384a46e7d1208f17d1d74e498f445020102030203070354000000000000000000000000000000005ca1ab1e5820cfe58c94626d82e54c34444223348c504ae148f1e79868731da9b44fc91ddfd4582040fc722808ecb16a4f1cb2e145abfb2b8eb9d731283dbb46fe013c0e3441dfbc070354000000000000000000000000000000005ca1ab1e58200000000000000000000000000000000000000000000000000000000000000002446746e71f070354000000000000000000000000000000005ca1ab1e5820bc32590bee71a54e9565024aca9df30307a01cf8f23561baed0ef54b30f5be68582007b7aa19b2ab0ca977cf557ea4cec4c0b84b00a9430cfe0323877011fa03269c020203e752c67cd3ffa4dacba7194a973b10998b056b5b936d17da2a8eae591a8e175e020303abdf2e1db4950317aadeff604ae51ac75e8281b1ea22e7f17349757c71dca21d03fd88dafa9a26e9c8f617b5de3e1a6021092dbff5c1fdb5d8efaeecb1f333565c020303b8a363b96519cb0eed132d807f6e42ea35f115584c443a74b14a19bbeac463d7038247bf369e033fcc19a5797960f1387f04f80cf396babac560060887288632db0203032a893ec5bee53177a40777945189164675444d0087d703c8129196df58b4ffd10384203647fe683ea28ce78395875f0bc39f1fe1ce6c9670b8393161514dab47010203039cd9d80315aa2688e25cdcc210d01a64e57404ec24bd81538fcfd3880c7a1485031ced4693c4d71b2e97ea6287a2d22ed1af991abfe52dd764bfcdb56f3084e85e0203039067a4614e2e410a883b1cf0ccfdc298c978614ca1a472330e5d63e1ea9ae095035bfc8cc6e977317e3dea3bdea3406975ae2384e72f6e5e09ebc3ff358e4d9725020303f30c3bcd31fed704d2c67d83ece97bb8fc518746b11b291f9ff5c12ea436f92703800f22b2fc6b77bdb96880866086a8f4d621ef386020c90fe2a678b1bc3a063d020303fb752c12ae75e534126c45ee4aaa0e80c44afa5f5ac85f491d47c6c232479cb203f4091664c7e58a48ec6c8343fd713184f2195f17153a9b10439f3aa99461a425020303d58b82e1f5dc744c3e99a29dae08c0cacdd92b28e0966a5fb3b143479649353e0381584029a53e6c7f0dee68619e681482b9e36e43858e57bacb3554d7af2a8ad1020303f6ca9ca2515d3662f23cde1e54e67e0817607d0b9f501818a528ca1b43ffcce603bd381317706701d336e83e27c1cd699d0b616b349b0e28de4cd010cfec1a2bad0203020303af2d5e74e0ba57395bd0c11be5508a506eee906defac2ac84fba6ce7b577205703dddb21150e7c057c4df77ad73836cefe1e746adc52dfe903bcb543bea8eba9d502030203036cb57c550ffabdb39fe5531fac6c603b79b2551bfac7e208e7a1b1628607ff9303f46bdcac887fc8561c752bc45e1c98389a6a35cc0572575245a8f2ae513bea3f02030203035dff75d9bd1da1247aa0fc644e204d8fe7a916636d465210ba9a828a93bd8fde03f50e7e2741b63ce73e98ef6a769fa9339d941cf993b7e4b26305f22e9f18bc560203020303ec8a5f20ba3d3385c3ce7cd26702f5e40a4432f72ac566a3df649c1af87741fb036a000d8ceda0fcfe3ba4e6ae633e3abbd3deee0db83107e5ce0e0469b26e7324020302030203036058e9f8cd448caadf126fd3b7d50fbbdd8e2f7a8de9160a484ad79f8829bf5a03be9a1646b44327a504c96d0b2ac009d73adb23ba21ba3df5a5dfff32b74403680203020302030203020303ebee5c234bc2f660a9b3efe1bd2fb7d340182d904429b1f2a4e89bb51b1c47c903e51438724a9cf3725c22e07d59ba15acf0bbf473b37744164f122ac475ec42d20203020303bf9c131a0283cc46ca74d21b68d0b3a62d131dc9f4787ab60772569aaba63fd703f011de292bb236c3b08513f7b82ab7d311d0f80d4d3e173c2f8445028ed1cbf8020302030203020302030203020302030392af697495712b4013883b3f5ad2d370bdb93f0ed60416692b0267f10d9a3caa0386fa8ccd91ab622b232223f9a347f1785ca9c4b7323a2e0d19a7971c3afd63ff0203020303b4f12607fb8df583b854d7b235f4a64ccb2f4bc9819dc50f3a03ed0d4906910e038f64a125d14bb92752d65593faae8e41bb5e80e4f147b20f0c247078f6e7ca77070354000000000000000000000000000000005ca1ab1e58202d11035f2912c26c30c4f8957d3910a20622ea8709c8cd3e0ad87fa0f4460bbb5820c0bf0b2ab68768eaabe5fda7814227beaeaf4c4ee7e67f5d07aefaf5f0410ab80203034d5eb602925f13a2147a2c1439d43faa74e2561bb3d27811f02042466fb2804f035d9458bc537a1957fddbf6c5f13c6bfc9349abf1251df9d6dd48b5b574f6f48f020303bbf6401ad2a6b95a3e749f5b31224fc7fcdd083e7aeac9671ec3bebda312fe5c03393a914dd0b171b4cca2f5cef52cb4ed4b564278c0fb678e5e8f3d911b4addb302030356cdb16849ae7aff540b8724f73974149f71cd3f984360537159a273a5bfcc1d03791ad7bed137c9501bcee55dc6c9b030157a30c37fca14d39c25d9d5137ae88b020303e43916580d350f4da396c5763989f003085f6c468cf815846a95571702f1f53903e88243a0e60743a8285f13df8159992bd95c7f9546a8b5ef0ea2166fa211b8f70203039691d481d60a086435d9f914e8e2b5e5a68abfafb82dcc9d6de2176920c35ded03347f67f0fbbc63fa8a3b826c6491f42b13869a2abd2b6326d75d51cb30ea9cf1020303a06d3787a49c8745205aae2c80c6aed35adaa5a8e829f8ce8c29d55ffe8cadef032b843523c93d41eee41def0561be9ad7414c5bd9591d8e3723fcb0aea6170c72020303e56edd97325fff9e9a09d439d966a37ab63cdb3a3328b157445b60c3b91a86aa0381354b5bad8afeb2c183556c5f20e5d25c565cb8a738add05fc71bfb086737a102030301fa96c592fe444b2504b86acb3efb7befb3e241223f2d697c162be93668231d037f5346f59d4e0e4737f7b5cdde5494c43dcf2b583098022afa1d40024d434625020303299100220dba6b0afe91d1fb4a5c16f6cdc90da62bd73bd75b66063366a950f90315d7adf6a555d635edb76f96c7aeed7b5e3990ab1d13e0b01acd386ddeb43e0e0203034a527f4391b236f6ed15aeb5eb8839bca31aceadf3b8b5b7f5208d22f6a01b8903ecb9612fb023bcc161bfacadd2003a53d264c5555c4d65107fa01d984fc66017" + + resultWitness = "01020302030203020302030203034b4c181607792b3c46ea253af79666ab9bbfa3d29e8855be6c4e045b3424f6a503fdb52981685167cdab219ae57b3c5869e539e89eb29845d6406b3229247e982e020302030203020302030203020303dc378377acad40e16af2de6482d7a60c1e5f087d067fc716c2485742ac2e29330339535728bf0c5d72ec789110ff3691dfb9cf434399ad849a86ca6725977d3e4f0203020303481a1fc812bcc98ce37225fff9f28a6d8d0ea5c63aeda93b031e8e4603cc8e7c032952530fef71561f9028c37b944df439c0d2968c4f7e247a2ad12dd4969ffc8302030203031ce6733d3a496a34cb114cad924070b0dfad8ff6891f629ed2ae31326540fe120345057d6cbecce08aeecc475c91403549f4fe82bdb953895bdeded2fae6f8688a020302030203020303c4ac3ac799860160a30a3304b765c2c90bc414edc3739a5d098bb7e18009548a039042e98ef239f418f2bf7ad10868e1fa7d0f644458488adf684313dc3f683a5202030203020303949f805ade2be05694c8011fa17fab3646a43f38f96d868386f0ba9558ba5f960302aabd9fbeceb9711f46d634513830181412c8405aea579f470a19b477d090140203020303db978a462b93b2efa3aa3da09e03370b570db692c6d361d52ae1051bdb26a3a903916d67432c505e1dc33f3617e0743d761aba44785726309191e79cb18b666e7402030203033edca13bcadc1db9305f3b15322cc6d774682fffdfe2b509f81d00b16ce2dcd003dc94780e238944094e7856154e6d3e54fec28293a9a70eaf1cc2a81e874e22170203020302010203070354000000000000000000000000000000005ca1ab1e5820e72de8a1b9696dd30f7886b15c4cc9234d52c6b41b9c33e2baaf8d88fc5b7c9f5820f8fb80310ac041e7a5e79c138d7261cda5d8a988dc9268b5a8dc5318fb610a90070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000358206e82d18bde430935057c321f6c30812e0eae2122da6af753e25974c92f0d7b50020303c6cbb686c9d7a94f49dfbee076ae1b87f1c9bb5f33b7c98a71816c33b4731a3b037514c2021b2bb805e2a6060b265dd53c069a4587e19cd7d1af99d0e9c3d0e550020303784570292bffbc3ee00153e5806a317459fed4c1d84de0515dcefc177404865003f08c92f6786e67148e8fb2bcd4eb813a665e16a270b475605d1d84b587450ff102030344c5e2a1775873020ab4e5a588e95d6702878cd46012682196dc39738fd8780703a6d5ee17fe3be7e20050e4e66c54b5188febbdd3615f832c35b073078258b214020303a42b38dcef18f890c02cdb90473211c95582727b83af287cbfc8a3f10e29649103380623684a9b3b341e01ee65908a6aac96fdf1444ca255b9dd5193537d58709b020303476c478891a8f8d905ebf9e5c031ba1020ca1436538bf9b97c6eaa1b9512da97038c77314895fccd4edafbfd73b531f4dec6f4671b6acde83926907ab376982f310203036416706411fa678c78f77dbfb609d65f63d6b04a8aae3fae4cad23419f6e738b03b6ec59ff099f23c5a528e805fbd9457736b100ea0e96390eb536046b88da3db102030334468b79fd36c8bc812c6613d176983aa4be53642e7e56421faa4ef25031fc73032869ca46586018725007aac483055d85131fcc4432c9a72175a8c6263b65c1ed020303676f9f98ef2cdc44ec8d98d0153be2aeb90b08386286887c94567950df1216440385bdebccb7559d68f55e26ba0980bcf7120609c7bb43cfc1f701e92f670ac1280203031117969a5ad58cb9a441ddd498cf3bebc13ab5aea1ceb29ddb1a226c5343c6e703425597c542fab13f686a7053f6c1e2635a729f8d9da4c01d763ffe9965ddd63402030345f2b9e446c9e743f6899409a4567a9b7f8770f711d39e39773d8173c4ea3a0c03cbc17bc3c54426fc8cf2b13b1ddb800509579856ce251beae01d924a92a8edb8020302030203030560b956a67a313d6d8939eed4cd80cc385eb49f7b6dd269ccde33a145f1216e037b3e0569695b777df45db97a41b025b57c680ad61231b61225fc7825824c4c0502030203033f1ce9dde58980c5bc34a88467f3b8cfd334dab19f28050acc53f33aab0b366f036092ba2243e1d8e20c2aa4ba0aee9ca063e8e8e6da493269065c227232020a590203020303921f9061d1b4082d20b7f9df21566609ca6dc64cd0ffac2625e9ff3090ac73570371757934d2c7d4dfe9b7b1e5c71fe18b66cf56c540c3d04310873976f79ef9f602030203020303e8e045e00ad70f879f31e498fe49aa2fd4c81849b6c55dd98391681351aac7df036e509788bd99ed4034d5fa3901bbda4cb6f94d709b2d54eca545336569791f36020302030310125b6177d5086fcdca8e0607e49e6cb21bebc95404329c9769a7d3ed59e2c4034e9acfa4214d459d1c81a304d862b2dbd4d832e71ab851656bfcc0e9c5b3e6f60203020303ad656bdacec77a2e7e591bddde7b2c7ab9b928945ee65898ff35900e25f0c21f03e47d9766945c4649bd44422f5fa779e92267d76ce44f396ef0b672215e43ce7802030203020303d3858acf0781afe0adae49a25d1724b36c9989179cc884b9a9c6481f89e57706031da6fb50879ede58d816d1624292f0c60a8133bcbc671dd92d0f9cb8d50fc803020302030317221db9e049aebabc83cefc3ebe7040ec1e82022d104d2c78f796753f76f0120352124f3ffee53f7e0f9a0068d5c0b0abfca5aaa148371c91e2e81df5fba6f8bf0203020303e00ce2232f3e208dcf050f887d02c7b91170c4b98e1d098ec5238bb3d387a41e038a0379dab30ce84865bb4f0834a9c3fd7bb2da17994abf03e89fd8d754bf7aab0203020302030333f5853903cb36caedffa12d0aa0a01c39e91309629a97dddafa8da4f738fb3e038e1dc6012aecd5998053b5878c6e3a398c8a286c7ad4dc0b55f043e4b4210950020302030317e041d91830fe8051fc73631cfd56519fc5bb88b5369298da9807b40d93733703dc7745bf8dfa058bcf1328f79efc9441cf5ad5fb5763c75bdebf9492f88a6c8302030203020303bf9a2780e63ac26ffca6df07f182db72e3e65802b277ea6d40057c383d5a37e3036c1548eb1c956ece0a876fff4463cc3f2e9c3b6eef88379f07e6d71013eb4aad020302030202020103c2eebac9e260dad1f051fa75846558dcc0ed120d1a7699fd1d6b739a3419015b020103b9d64db96b69b035a74a90069691afa2b34a705f3ad3aa82f3757c7a6f2a9f17070354000000000000000000000000000000005ca1ab1e58207af492bfc857210d76ff8398a35a942af892e866b1f4c241746b3ee89ca002595820f889596e5b3d4dbe5bcab5cde2af26d3ad8d88bc086e8b4f929885f33f6eec77020303cd3edcf79c7e0e0d6b87ae523729611faeda92e77bbb59f739e9a6127d890cdf03009a5c01c3cb27d2f1ffbd3fd77ff38f66991f64174f5df1411ea21ae2e22f250203035334694d8b56a2f5e0d0ded81283e7f36b3da6fbf2e2d1b469c008d7414296be03953388b0cacc587c5ca452ba8e96a9071958ef94439690bc14866f556a35ebc1020303ac2aae05bc7a68d238e9a9bbd2d5d07a001f8f3651bb25f5a6d6dcbb155569090335b6f55bf3d56419cbc3a45d4fa6bed330d9e0391f8806c97a7aa4149d06725b0203033dfe4a2f0555ff318ad12e49515e712f339134af0237edaef08553d9d67e260b039cd50a46feb34ab47c24391a2579e601956897ad6299bd14a4c8d9628a37d46e02030348f01fedf98979a5fb3df07daded956331fa6a02f697dfe29dd26e71111de5540387829f9a96ed82303e86550747e311d5dbfe94cc71113600595360abb512cb7b02030203020302030203020303eac48d9dbf7d162797293e0acd54382d4fd53e80e29c9c43c51dafb05c0880060306b13c75c66a6e267236b6579bcca576ff889e323ac6ffd0ee317e07623a3866020302030203020302030352e23af8570aeca858a6aa5bd20d2c63a92eb08529a9e6e5fb245aa72c5b72ce0334d7cfef6cb28d62f63cf907e3273d76a8bb858423c6ef446b056fb4f06210e002030203020302030315bf4cd3a7f33296bb4778e216bd18adacf25c97f8f4df9e1052dcba7b6edf2203b3637d0b1cf58c5272f28f8354a764d3cd48ff7c04f807237da8f4a1e2ef5db5020302030203020302030203020303c018dfe606efd5d17f3d45e91d33d3d7ef57d92a1d509291b1556bbb7e78dd0803b08ff5bb304aa8741af608415c541440edcd98bbc0fc849fe2a89c2c783341d502030203031e0eb0ac5664b1d0267d3c51dd66d1828c0d01a0903d599a317e4578926d4e3503330e2ccc546a3db52e796943aa8960d6d483a3b941ae0caa21cc7b9f7a3c2bbc070354a40d5f56745a118d0906a34e69aec8c0db1cb8fa582000000000000000000000000000000000000000000000000000000000000000015820421c2cc0dce9b0fbdb85cbe43bd6c2a1af5a6f5da756cdb8b6f2bb948e3a90da020303ac874a6acbf6de628134cd74ad9f336206e7aadb1ef09456b6267a770612485703ef323528b761720ce04927a54b81025a935f070420d655217e40eb2e084bd170020303a48199a63a429cf44fb39fdbb73098a49dc171e03d32800f483780adb9aa06580388796e8ab2076fc77f00a5096317ceff8a54da310b014a0310504bcd76f8b8da020303f8698a6f24140e0e37f49032fb2da6db2c8bcaea8961a6e1976baded0d9a8bd80371b277886f0d14b6f82cfd063ecddab10fb5da5e0666e040992469d09a6bc8b0020303dee2b54587eeb7db2fe9ef0dc262b6ae679a5bfff89c8f403d181a1d79107b1d032aff27f522ef5fd88213c3865e01c7b4c1720d56778d1bd0e48e6a86fb3b07970203037d0d29240ad72800831a91d8e54019da745c6c6a630a625167723ace857bbb810305edd49a8cfb1eea157734968e95e8b8620c474c3cfc6f3285d3dad36893114302030349b1bd34664838889a2133d716143cb8707a15745738917bfbbeecbe871e6e90035ba74ef0008ce80ac6d199cc4d217aaa9b8a5fd58f2d329aba4e061c16d99b620203030d600bfcd6581d405aaed26aa7cee976fbb2bb9c1c1390bd3eb14cf5f661022303acf3369b84f876dc556ed93718d616864020b1969d24170f4970ddbd944e1bd9020303d5bb847f016f33d8cac460756aad70173d8d6e37c37d1b69e1b1b45c52e5996103c3777105bd00820b49c89486e7589f0ccd0244ab6fd4b1409ba86dece7506f9102030356b2929dbde358b52b652bc842c7a42aea162f0d79bd7d653b5cfee34e9f0e6c03656a686adb3bff7a9d8841d3e296b0dc61c389b399677222ebbd70cf0c19e70a020303e5bf4a0779ccfa6d42a01e532bb6120b168699bfd3f4f44a62780481d5f86588036efb82ef530fb604bdff43bf1ad1a7dde41522bf8a7f5e724dd3074562b0c0ef020303036a50ac7a6e425842820d2a4e07a80f416706903e9d88b5824559515a901aa80303e3c58c1dfb4f5a5a5d1180dd010ceb33a42a0ff7cab200ced5562202261aa0020302030385d60697f5b4482fcbecfaf5f53111681c9b48ed7bbd2cdb1a257bb7f26db9d103ae21f016eadf6448b913ba498fe3d678b8bcdf9569b026053de69bd24563ef0202030203032fd50f1a5b8eddbd5ccb90e37d9c190092927af9b26a1cf8b4576d7982476fb603436882f441f09768b000722da7ec7c74b6f0252c24e16b9e6461ce4f4eeb791d02030203034eb00b994d3a8d439f47981f68baf7fb0f0e88e2167243c6b005de3c48b5c3ec03ac5626fd3f4030d088d0f41834de11510b59739353238241138d70bd8e05c22e02030203030a51165872abbe7260a6777cbbd2f6d81dfcd07c1b7c0783659bf8e8ca8e77b9032f78c81c54fd31d1a25214fa464424ae6e6399f15c1bd8987825f1f0d0dfccde020302030203020303215472473dede3eebcfdd93b1ee898e4d6cf33261a1fba12ff77dff2fb8a0f27037938ac733af661730414e88da9633c04a8914c9ae4263a4f8cea7066e6cefb840203020302030203034d6713bc006056011f31ac6f935e71e33ab8045353e9e138ec9743e8574a8d2f03fcaee2f22e1561702d029c465b755ff5491e4114264dfdf16fe9efd34864a83802030203037cc278f9b41fd17fb5eb3c839a725fcd1ef6000189fcebcb4214303f45dcd2d60386c3bc64da300f1a87efa2eb2724553e41348057fc99d5c23b5b20e216fde46d020302030376bf2ddfddca9910df80bb0785add76937d1e90e029c02b04c0cf421622a232803ba2219bc37e93a89b0effdfc3601f58645c1cb7e818f2254c8fd16fea4ba84440203020303fdf1c4799edef5fe2960f9148627fff521e591247a224eb8d05eae3f51675b560372adafa8e298a14d0da0a71e645a12a23def78db8e81f7a68ef92aac7d5700b40203020303f5a794d38718b283b47993f3bbcd67c76f84c47fcf2d35373fcb7f8a0f43a06b03a14b12d1f03790ac75797e463a8a7edcfb2bc80b65a7dc8d1b15d00cefb315d5020302010312f8462573dc83d436d2498e68019babdcc911f482e1e00c1b3fda70e1a166e40203020303adcfa2154e38e2cdbafd5d56bdaa5dca90a5bfb9c36bfbe140bb31ec0e66716503b5aaf1a6fa2e80ad8f4e49c51808d2898fd74f539ec5de974b57c27466f5e7490203070354000000000000000000000000000000005ca1ab1e58205956a0b12f607189a063054545ab26ce76ea5eb4c9bc1e8d8161646c93ac66515820da6aba51eaf87e14a7585e52e23cc0b789c61b3e808d2aef704ae932bb2ab49d070354ee5a4826068c5326a7f06fd6c7cbf816f096846c5820701c251f0448beefca8b47dce2e42f136d224b8e89e4900d24681e46a70e7448510237426c03237429400000000067445fb8020303dd24d6adc0d7b321eb19905b22f1780707b0d7e30026716c3b0d7ea311cbfeab03e498dca26358e5fd56e5464288da82073a17cbbd112e322488c12bff1661b49b020303413600069144f3379227184b3d365f22778695ad2b812ffe56bdec80df882877033b1e22049401430c9208943101b5ef3e70d99c9853e08591c4729e0f31f4bf56020303ffce2337b88b26e7b0582d1679484fa995b68c0418d72f650531db342e25f12e03493c1bb3f993e9aa63e2736b9e0826f1309ed298bd95bfc169f89b6a62cbed420203031bacbf380b1eafbec9c534577f8972d28087bc6e94bc276ec91e66a11396f07903bb137addf6042ee1a1eb0170ac09f0a092b2f7682f718d5986152d56d192b347020303b89984a9ec10a5bc5835efef55fbf26f3477d21372a55ae4abd26c55ee5e323d035ab47c29775484efde5ad8cfb1a399e9008bcb66f6cd77f28c255980633aeb5d0203037902d8528b89dce0e6a41ff89888121f42520936f3684bdc8481094f1e046b4f03cedf898a501b7bc036d92797f971bf9caa1028994a8d6b15ceb79e4ca532e7cc02030203020303a366f69c8b19d47be34a2a6333298d705692f65daf3fba95d6f48b9676b6cd3b0351f190ff80b28f339034b6be161060cbe4837cf22e0c01b3d5a77b8f349c4f1d02030203038d8eae2b45a21838dbf9f517dae99ff0bac7a25d4756a7a3315c43cfa7dbfb9803785e2e17b8cdb9628ca4c2f963eb5722918462cf75f91dd6fd00ae84d17ba2a90203020302030312a3a949b95a27ae6f73e9d879bc9c9c6eb6757f1c20ee76d1f52e1d4c9ec4eb03d38f8911a661255b0ebcabbadd44e38903841386863c97499f3e57a06bc5c3e702030203020303763e3e4c8cc4a4b30afaaae229ff20ac282d74c923a88be140293d62b2d812bb03b4b4e3386c676de1012a2bdced3714094e57803a98920b0eefe63e186abdd4d902030203032ee550fc2b119e46e3338c971e6f44ea838020e442fce0c4a34b359306a00379038c72343f5e2ac968c7f1edfd71f18128db6b52aa476fbec372eaa58a2acf45220203020303221a1371f01a251478f2a6673db891a3c412d954dc9e741ea2bfd249abf428bf0325059126652b0c2c46d78a02eba6c4df473b674ed378b17827c634bd119f5422020302030203020303313abcaaf43f5d42589a57c6fc0bec04526b43a3dc139415af1de50f8846c004037ee72e1eb97ffd7dfe0c7d40b575103edd3e62c030b86362c41630c6e97bf6bf020302030203020303508d990b34daf5a3f925c435daac3d293f6d861094cc2d343a92c62428fa66da032f8b40a9211667e9c44328d6440091ecb3a46bc15832f7d7cdfa8ec130b527fc0203020303f993f7eae6e45a6f8557c6c5d0e912cb41b71d2bf37f38affc0b2d8e054193220315eeb3ab754628ce727cd0b7028ff8ed3291de7566b99066e127185d043f595702030203032f2c132f32f21e267ab64271e8f2c0c39fedbcc509c4589616cffec21d7332eb03839857347599c19c43a0acfe53e1bb5bbe0d68ddb49cee05f1b24c5acac24a150203020303dcd0869ad1107856680f6bf164623fc709d26d1a0842bb9c60a383f255c0ec2403c92cb1692742c4e2b6a91d13c3b371a9dccd29f898d8f6457ad052b1da9efcf6020302030203031408a1feb1c4cefd2e71c1d7ce58e6b4c2d139d48c67037d40dc0d60390af539039c51675ab13cc260ab6875b12824ed60903c1755add14024e27508ac0a3b9d81020102030376fdbe16ba7e2f048d9c311cb1f99291b4f624717ddd7e9f2aa653099d19314f032ebe85ea3fef7c7033338d1ed98e187eddf75dff4772a23e19392ce61690f77f020303f901f2ba5a7a95db9ea7106268f17f341206944377d1f006921211069cf8a0a103f43daf24401f9ed2d0691570a8ccdcd016c90b722786ff590276f7cd5933ff3d02030378ab72606d2d32782ceccc9c11af9496f599dec259281c01f0c18a3b875518ed0355b4984426bd4db31ca5d70798a18280a4d319786bd897a29365d2db7489b32d02030335706adc0febe81255c960be521ae4c7a6201b2db502fb7016a5d4d9ba36c58803ef3e9f16053b7f799f207451eb3403eb95301e9c9e721dfde0c41ebd8362485c0203033b59831b753c1ca3ed58d3293aab0099027f87ff97f3f7e92d9dfb095839497a03821fc506f41f2a0bcce20367ebd6ae4b461e110e1788d190416c8345ec72c364020303cf6d91b6b57705a8f02a367997e807f49dba00a5bb3e8d0de25eacad5486b88f03abc64c2300b90b30ae3b11fb71095675d1a62860a6471a1a2defcf624b8bb4d4020303be890b95c3a4c5c381f1a00d6d98da4cd8467e002746a8c52f2564e41319d3780394b620da3f2c277f0d4a70c7a54a7245503ed2e808bf722cce0b503e242ae7d10203039f6bac7e82bf632c8b003eed17f050a49d2ea83b6a93e09295b3b3c51c55ada6038d01937127f83a85e3e655363f467385226f7e406409528791f6e2375184ef5e02030203020303e2ba22bcf2fd6923a2ffd1ae073bcffad33e81f4a7cb9cab82e130c63a213b6e031dd2e6a82a0638b027a1f15eac2bceca26ef1519de70dc99bd5275791bab4bb0020302030203031d0be4b4d178c76d39a7689aaa3a9866e63b999a2d11dbec2f04787c714dabbe03e5880788e24aeb6314512538d4cf7382b37132d4d2870122f47de8ac0d09eb020203020303b9af076d8b0e683e730de94273fbcdb5d2ac9f29273a9ffb38875892722f439903e22b2cbffaa7b1ed370a3d8b87199e1f1485703145dd3de0945cede9629702600203020303a019468f5d28919dfcc2d7bfd844492f2ab1df6400a17627b31c29ea02d583f5038dd13cd4ecf8c4151cebaf6e2637913a2310a81d4ecbd5f5fd2f4a4c315558ac0203020303167bb488d1aff473f1027bdeadb8e0e7a439f6a589c78caae1a3d045e78da60303ddda65ddb3f7e0fe430faaeb49419075391fd2559659f2ba88d3655454e079e802030203020302030203020302030203037a46bc17ebfbc47f6d99661de00074c9958e0f7fd66df7c77c236b89b165472e034b58bfe7c7506d2891367c270ca350269dfc0a08b7466ec2496c6330dd602bb302030203039b58b0df7fae59a4cef25184d849214bc145cda115b2c0dfd85fd470ecdea70f0330923d4d299efbc138d4442519d30cd33a7827557231388b81a6ea9c65eabe6f0203020303af3fee608c2e8e5a30ffc6345d86ec1b2d55f10e518b4da5e8eb59abde07b59803c2016682405d3a953eba254601d5fc0b8966a33efaa51918a4a41b8e0acbeb4602030203034b1387aa6d0ab944e2ec65ce38c8643a0ddfca5c3059718f398dee501291569603528cbab25216c4397a402fcb572f0b512a773dfeafa59e401989a4da13406bfe02030203070354000000000000000000000000000000005ca1ab1e582054b6c4d9862a1658dedebe99a0f61d94c5d1515fd031d0dfe9ebce6a1454f5c658203f14693500ccd0260659fd9eaf69570edc0504867134ac88f871d91d388b63690203070354914e7547b9051ea6226c30495190a2efa15930c95820ffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927548382be7cc5c2cd8b14f44108444ced6745c5fecb02030311ab6695ec969171698c1f56d4c05373d8505a2c7299fb05cda1d4351e22bfa403478b94ae515fbd01728835b532c7c45ccc78d200d3d004da6917337e139eb729020303ffd14369e7c7f7aec3a890a20234885f2c9fb9802ec318d8434ebcd58a696153030ddae742090ea458c3f232dc894bd8cd3378b4b4590a0523e09a44e0439fe0db020303b1688d8c7806365d931579ccac8dbf7a8d7705ac393159dfd9c0395ab7b5ca5b036a6c978a565b15267de4330de7b6166014082043c5cc80370953767ac501ccf2020303f181e47adf88e965d55e1153d76b731c261ad7d7720823919fc11d98bc144d2a03c480f344ef22a4532900fb9d7cb9d8b5ce1e4f11a231e682142f9ffe1962807d0203032db40fdeb2c5256d5a237b6134f844646b325bfc12c687916327e21a65b1ae6a03c73ddb4116e07a00066b925a207dda51fbbfadce21a7459c6c2ae7f598721089020303e81fa28f73bf124de71b54c67334292e397e000428de699c89947d793cacb9da03173e567d72ac2c265860d9103e791fdfe3cad72a9a1dae15d9bec6687eb506d702030305a683365eb32bb92967becff0dba79d1c23ff75b2fc3d40f9a1573b993747b703b8b1075b12927a8f483dc7b802c96483206f98c640e49e22d4b426f9a9eb750f0203031276db0802c8235f9f248bbafaa6cbabb75baead95ede989894ea6d8585c3c8703527ea0179a8814d423775e1f381cc8eee0797216d71c79729ab186714e4daf3702030330b1e1f7a1f7dcbf5cd00932de20748e546bc1a8da9381fa3d5066f3c02b61da033f7308aca0fa70a938e45539d5dcd1864bc233ef232c6d38fa1dd331e536a400020303ad8fe61eca50a88286f382461ecaa93dc71e9aed12e91a2e9930325e5ffd1d7903fd046a02679f734a91031aacb4194ada537220167cfa68306b651433026e6478020302030203020303b7e72973952f51f913dc6818649ddb3c5619982f21e56347003ebe3b3788eadb0384757ebf158021f4bfc0d9a1bf844d13747328fd367727cb0a2d9b7c91926c400203020303593dd6ef2d4c6f8ab3253bec454072a6cf779b5acd194d43cf4d30191d4b24fe03d80a7ee4528b16cb482fd73c259b2e6e4fde5d5d31be6b97703fbbb17c3e61d20203020303992d90fe15b918f58e8dac35e96d0ebf33834ccacc8a69b6a075b263d0df655e0301b8df4b987fcf3a98000ca00d3191fd2292dc9210d7f1ab382035b2e2d02be9020302030328797f5226ad9a63c859dc61073e8ef33fe15094e61db64bcde0379f055f733403b50fe3e685c2e442a3a81715f64a840afaf1f81b49ed21b3fc2ead0620f6caae020302030203020303189a1bc58c5621e4845025a9c534fb9ad2bb2f5be276faee403d59266561d652038325fb098a4b3a402690994212511e710d20cb7966fb26b3687fea719eca217a0203020303ca11813aa459d051b0411eeddd18070506e8fe2054a2e22a763b05454e87cefd03b2cb46d28f3bcf15305b0654ca442442420ccc1b28e44e2e2c84498571b5375a02030203039385ca432e99a05cca8aa7fe5868222cdb6c928c8bbdd7eb13c22c5abe1b11cd03e8cb7cbe434eae4b8b7910183b3b006a1b3df70ae7b30248fef24d64a004c3c90203020302030203035fb731b403c8979aa552e74b3534a247c638547dc7c957467a4b08855b29b74703d49a5d90635d403354f849daf9976a4f4dfd7dab5517b254638eb893511ebcaa02030203032fddd404fe9317d561378c78f3afbe75e18c27face10d4e6ea03fc2888b22e33033c8c390d481f51cf6b43c22677a971beae0e62e8b2ecfdaaed05b48ac0f60294020302030203020302030203070054000000000000000000000000000000005ca1ab1e45e8d4a5100002010341305ecddd1b56329ac9f09a1235eec6ce6be69492a9788db13e9187dc21e9dc020303fb1c6d1aa6d3f3bef7a0bf4130218b3b168f9447e69ebcd3b68c2b2f41d9b2ef03652ba6f9b69aee3d28404079416c2f8fba4078d66b558c7a8d9615cfe7d3bd30020303d9f042d0d2f152e24d8cde02d3a7d7a1fa234efc5dc259572d412a2e607215ba03c5b76ff595e1d74a22eb44a5aed94f3225b6126c2c28ef04bb75e1d3804925ad02030314a2b125da4db5ba673cd5c0aaae8c5bf0857fd45728b868cff3f40eaf9f82790393e93c4f4b58f6f9d397d136319a29aa6b691b652651513bfc2297107379ce62020303f00359907dd68b2ae8e2d252d3313f3ba2bba16d21995333b2162b24c9bbeac4036435af585f0f75e60d362629108f6768756f7b39f1c70ab7f79e6b4e1bd9f08f020303929e2f8eb833089a3773b497247338865ef336de61e7da4a362eb3e5d5601a7203323197b010e3205d910c230463758f39cd6c01258db0a11b9b47f4c278db0494020303ab4bdc2dbea0c00b12cedf9e968135b62101bc1e20e270a1f694ae6a4686627c03140686262c769436fdaece3afe58e8a4423cbf381295a85237e52fac66c57879020303295e1973d07a067f281e3337e756bacf10dcc295f7074564874ea4401eb2a4e503cfec4348d3a697dd4f1835bc31c2615f56f92a02c1935cceec2501c12b8628f10203033892c29a2de6aee7888c2448fdbb3252d32b426bf74edf79223e4ee886fc0f6b03ef287d8ccaa574ebdac646e6d35bfb3ce52b00eda1e671d7d7bbf31bd59ff7ee020303c58f22b2dc782f914b31e3b87185b727a0bd2e2dcc41481e31ab1b26f222fdf703f0dcf8a2ce85de4d96bdc4c1a9c52a7ec54cc771750f0ed7d6c1113b93df65ce02030203039a7c26055306c8884baf96dccb2e3bb3cb30deceafdc73491bbdf0333400efc0036ee70bfe41de62ab49a9a63ca415bb881a92980f87fc044f2f5ae2e84185dfea0203020303c4332d86dee9e03fbda2dc0eb81cb20a6f6a20c7df95090f09e47d8e7efa1d7b03a698f30a106768bc9d451fd96a6808beb2b799deec6423688d02a9ba34b4af280203020302030203020303398dee7348cac5f07e4865c2049207722ec9572e2ae69b21a8cbd1c053c44a0e03612d7861c014aed261de20fd1109fc86ae090eb2c37a02b8a6072bba1c77c8b50203020302030203020302030203031f28ae8c421086878704ec730445ebf2ff23d186ffed24802f0ae24259c8d21403a8e38716cdd8a09095a7036c686009bd8236b1c7eb9507540fb981baa9a8bc4b020302030203030fe638892efa1dbdc2881def87e77dbbba95d91c8debdd9b242bbf0745455a7403e5554fbb47341d48f82f64a26d175a7d3559378657e77cf2de2eff917b95be300203020303512c2cf0ab4340a1623bdddc301aa586932f9413ea9bf8c0f1849d2d70d5d0ff0375d0cc499c7f76c70939fd8d633c658747eebf0eb138c15d902c34f0de9098030203020303b78dcbd59a3668396357cbda038d7e5bc77aac4acdb3cd3a75e96eb05079a6bf03ceb3ed2850bca5df0bd69ce2e85e9daff43cdb4c79f58685340f521521a0943f0203020302030201034b33ab5a3b8d3b01c374c1d7fcfc714398b7f0704ba2f1ee757670269fd5a7f7020302020203070354000000000000000000000000000000005ca1ab1e5820000000000000000000000000000000000000000000000000000000000000000043840c77070354000000000000000000000000000000005ca1ab1e582010c18923d58801103b7e76ccd81e81a281713d174575a74b2ef0341f6b9a42fd5820b8b76bb549992d9bfc44e3b36d087a175b2e78b9584fc752eaa3013e0bdd31e8070354000000000000000000000000000000005ca1ab1e58209bd14ac8c1cf553e0ad3a2c109b9871eb74f3c116bf0bf492ef04d2983722555582090629dad0a40430445b7d2b25a8d19c5d7a929608ed7890877a499aaca01ca5002030315edee06840e36ef17d13817ab5475d12f7bd50113984febf31e2cd80c08952c03d360a7d78676862429feb7c95d052c1e63379b8ad3becf085a21baa353ab93d30203037a2fb952c2cf8e85d9706bcbcb5a69b83b13403b58f06b0767f4204acc5917930310de142eb3b2790cf1e3694b72eecc7e8ab3860f543c15cc24274ff69570f009020303879875563fe8a079ef71e84b6840b187c681499095de9d02d8b101c9dfcd111e0395e9fc3b000e49b65678f256d247786f72c91494c960d117b7668045c35502720203033d99bf4088229f273fa4910aad8f0aae6f7de8fd1832ddd14c8fa3d083aac51603b40316099ecb013c6dcc6ac2a3e831521afa35ea0ee52485c2e8cd40bd81fd870203030b576686ff79ae37ff5ae2d4240131369472a24104dcabaaf3c348da66a638bf03dddfa8283748687718b9672b0a69a6b7758ce10eff383d83986c1a2aca2910e002030313ac1b1da5a5a4232e4e2b766aaba01f45f9444b926476f01882e30d4cc6ae1a0323f57d2012e1874436ddc007ea8bc6dcbeae6e0dac6fd044c8375d2fe593904502030381ee4d8ef714022c3c5fad435af845d213cb988ef7561ddf65929553b70dd69a03178f9fbf18b1d12feb522330c82fe96d15bc4964e1c1053093c4903149652e6b02030323cdd6298c89fc39f87595dedfd8bae3ae7a40b66f312333394482169297dc8d033517a6ff26c035b9f822da8d2abd642c858696e0d970b1026cb524cb0844195a02030391dd21f4c52970493d439537192b245ccd2e4e3e8e16d90dc74e417718b12f9103d56b5ff3ad5ab9205b2d6f9c508e744643224a7ebca8c1a4aea71f01e48b186b02030304375ae3a357e874c2a10fe3596adee75d0ccb96e63838d8db70c9e402663e9903bd8d2e9ed97a66281cbb0733a92dcc92158740088acc7a9c834d8204c0acc1da0203033c9cd711a378c8153572663cfc686ea0324eaabf0feca614928eab900755299f030bdcf7033e475ad4a147377e1bb9ed8619b0c88b728f7935ecbe7bcd2fa82c7c02030203020302030344d578674b6be511832af115d2669571dda0918b6cc734aa4acda37260458f3303fa95439e418988542df6cc8a12cd2e59ddd44643f035364c14b146d8665ab538020302030203034f9b9d5ccea861bd0aa15a5fb7fecc1a6d49b66bc7eb1e8905701e3e5728957003a6bfd6ce49840bddcf6502294487dcf1e2b5d6b06100a0b1259dbe8c8bd8e44f0203020303dc08ac42d157ac7d835fabb64048b54993bf6636eff62863d99d2c8905f1e6050362a972a91cfac6bfbaf2c40c724f947a405ce6e647aac0a61ea8f467a49b41cc020302030203020303a4be360a2b33a98faf83a47c1611d2114b590f1a626d48f700e1b0d8361f65f6030e4a6c2e589051b01393778e2bd41764d047b0394238c514e3cff1bcd9f17fde0203020303a19150f49f5fa1e3a3e09c7b8e3a80ad9f901086b2acacc8a5712c945ab79d3903374e7d15b75adda866c38fbbe1cb5bcad247ad095de306706d40855b922df14f020302030203020302030354772bf7e2a00683456b752e674df624b9b8419fd126964d66a82f8ba678977a03dd8f48954ed2bb272c5b94a49d1ef09d545062536065580bbd306776bc135f8e02030203032108ea8ac4227399387099ff7faacb8c1e424f5543edb67d7d8ed0f04a4e0dfb0392659304959ceea896f45666a76214b0f96c0d0ac9ddb78a96f9a0271e7b579a02030203020303870c4f9820964a725c45a91364107661534dff05c30e966b1946f2157844ec0603bf64c46a8bfb74f75acb660d0a43078c21cdab2627c014fd463a56ad85cb7e6a020302030203034b81bf62e5171445bc7bb3e154c4236543feb39907364512e7f8bf3010d0bcd103c1e217970454c195c8cefeedb6eb556772703cdfcbb9473b1251407e3af45d4d0203020203a8dd420db1a92952522be68028b8762b9c2c45f11efe01d4e2b2a17a8aeca76202020203037c03317c701ee7c858e7c429134f07bc4f3bb40047681a2995924386b065a44003eeb2124d66ad9fe030707b71b337ead87239fbfec018f78a36cf83ffe6c1f3090203034479d72706bfadbfc681e4b1e0c17fd702e94ff5cce085697fa4915b9ddf8e5503978f813e60f47989d365c08ad74b7b5697ac63a4d729225fef5cbbf858dd9e360203031e3fe72c68bad17795f3ab1c89427a9db9297c750e25a03f4d5cc7f4300ccf25033477174075c81e1ea46067ae9766ac42b6e37b0122ca757914f2d38d5a5b0fd90203037c82934570e0e51dadfe294202f68ff1baa30ec7f3d972fd309af51bb73233b003c73c4ff799c5d7f7900bab9bed27acfd777778f080034d266e4b3a8cb275180e0203032ec060cb265b14a46177f0b9263af186c22d8fad7466efd3dda1a76839916f720322d842fbac43297665301e5a01f595b5961a7617045e6f90903794e64ae970f3020303bd26ad01b4a6d5fc9578bb889728e38b0cd1929f289dd0733beea3122035d8050305574e7ff67c46b4d58103152ffd94f950e5bf9a67a405de972849bfaa7a335e0203033c9f565b7511511ebda8b766512d87d572c4958008f933b4e55604a5f3c36e82036a24bb5153ae46e102a28f022b5305705a84d70a4d2d5b399a09ae90bec4c86d020303ca003945b6df159b5c900df34d54d18e81551ef946e6ec76aa5105912bd41228031937941108c7513a3bcf7e078b1b35a9816cf095dc7413079922c0eef235cd950203032c581d00b2b34c68be72f5453d8d67f30797a26d4b0df66f004fc0075cc8eb1003e71d380a7d686d28aca8fa3508c37b30fb5e30bcd348e19dfa6b547f2fda4fb602030203020303ac0a0e8df4bc9026b7b241c34d72dce10c8424eacea17d1670103c8ded2446be03f7a62663d338b5b7e9219d01266b1772ca3720daf925bd302b7dafcf8abebcba0203020302030366c76d1cd3e3136e15f9f29f3dcc4bded2c760f251e06403ea022bf5d67ae2d503c574a334d06a611fa0340a1213e317efb125ace4eda7a487ea00075e9a6b67a902030203020303ed1e8a1502eb462bb7f836f6d72486908e1b2cce7cb00e387cc1aadc827874d103bdbfb8f970bcc72256ac4e1eca0809217c625c6412289a6dc5dff7c91454436602030203020302030203020303e8bb2dae757d043417195292bac773cda990500845f91a94d00179fe89525c3e03e385244b15001ddd43b0180922bbccf4040a86bd116ade66fc3aced03dffecff02030203037d8dcb012bdde19a0dd178c1de91d21cc866a76b9b6315554fec4bc4f5daa79203bc46b61f50585799762df053271c52844c6fe83156fde628c8bc4369c4fed18202030203020303c998c4c602a03cfa0c92a1542165567f11d23f2ae5fb91d04e02292f8e297548039447848097d9500f21ebe819789f98461e01aff7cfcd442c8afe8e07b87a95690203020303f6441de6ba2bc5cc9ead4300519a94a14a80b85f2fb0fa41e2211d7c02af0e6703703a99e4c2133e89a6e892863f14f143cf5f2ad94bd527081c8be6143f14f3db020302030203031da626649ee092857f195eb0059639309d744972389a4e748c471f16b0fc3c2e03f4072cd036d5bbb777ad24fa0b1a235806ef25737404d7ce4f83babb76bf090802030203020303c7f6a9615846a2d627db4c940098479bce3c61c8fc1170d0b7f8c9ac2bec5ea4033664667108158e9377b49cf3632952090b6eab3ba6eaed4f48ca9d5beb273fd002010203070354000000000000000000000000000000005ca1ab1e5820eff9a5f21a1dc5ce907981aedce8e1f0d94116f871970a4c9488b2a6813ffd41582021bb230cc7b5a15416d28b65e85327b729b384a46e7d1208f17d1d74e498f445020102030203070354000000000000000000000000000000005ca1ab1e5820cfe58c94626d82e54c34444223348c504ae148f1e79868731da9b44fc91ddfd4582040fc722808ecb16a4f1cb2e145abfb2b8eb9d731283dbb46fe013c0e3441dfbc070354000000000000000000000000000000005ca1ab1e58200000000000000000000000000000000000000000000000000000000000000002446746e71f070354000000000000000000000000000000005ca1ab1e5820bc32590bee71a54e9565024aca9df30307a01cf8f23561baed0ef54b30f5be68582007b7aa19b2ab0ca977cf557ea4cec4c0b84b00a9430cfe0323877011fa03269c020203e752c67cd3ffa4dacba7194a973b10998b056b5b936d17da2a8eae591a8e175e020303abdf2e1db4950317aadeff604ae51ac75e8281b1ea22e7f17349757c71dca21d03fd88dafa9a26e9c8f617b5de3e1a6021092dbff5c1fdb5d8efaeecb1f333565c020303b8a363b96519cb0eed132d807f6e42ea35f115584c443a74b14a19bbeac463d7038247bf369e033fcc19a5797960f1387f04f80cf396babac560060887288632db0203032a893ec5bee53177a40777945189164675444d0087d703c8129196df58b4ffd10384203647fe683ea28ce78395875f0bc39f1fe1ce6c9670b8393161514dab47010203039cd9d80315aa2688e25cdcc210d01a64e57404ec24bd81538fcfd3880c7a1485031ced4693c4d71b2e97ea6287a2d22ed1af991abfe52dd764bfcdb56f3084e85e0203039067a4614e2e410a883b1cf0ccfdc298c978614ca1a472330e5d63e1ea9ae095035bfc8cc6e977317e3dea3bdea3406975ae2384e72f6e5e09ebc3ff358e4d9725020303f30c3bcd31fed704d2c67d83ece97bb8fc518746b11b291f9ff5c12ea436f92703800f22b2fc6b77bdb96880866086a8f4d621ef386020c90fe2a678b1bc3a063d02030203035d2afadc42d28ae8d74c7b5f96e56bcaecc01423bc9555ef9d9686271ddd238b033852af41b0f8f922418b3f525cd77f039ce8a0e41034e8a9c51de2daf331a7cc02030203020303dedf2c8185299a3cdcf5805fd538243eafabea31d99f30e0c56119453cbe0595035fd0c51fc95c362deb97f4d34a367c56d9c3bae67f33a75541d47299bf8c85d002030203033a34a2ec21ba01bdffa3e14bdc6234b1177f58fb0f8d20ded1e0d337abc9097303f2a2ca0856cfc4409a556f408436e6112049837ca240449b521ce77ded9bbb4502030203020302030355b79241b57362ed5a29e40e42647066862077123d3363d2776ae9a5235aa625031a0d841893cc3c11eefec6fcff6687e1f2d52c667b72e9896d185cfac2d52f200203020303267a5ba100569955e1248dd2758afbe9cabcc9fb5256aeadf2d9de2bd50fa9d3031c3657155c2172893ad0ceacfd6dbaac96e7450dd3572516816664bbad57307e0203020303bfdb95766031cea080daeba2879e20c2c9212e98699aa1a9ddd0f35b3f4f14d1031cb570e01fa4fd83227e9e7423cedcb4d1f2aa49a4b379bfb5532267cb41d1ed0203020303d26a86e0cde80dcb3dddc5689ee7aff7cc4aa63c69a65db071604f2d22821f5003453e11710c67ffb8aee8ecd4e3d9e482a3c3b6473055a8fda9141761be2a2cfd0203020303eed4e48df11288a42497f24b09a60c194737347e0f0324ace547906015c46763030f3541edd630e75e0ecfad8204446c4b04e707f29a911034b0d990df202058b6020302030357f21f30a7d272dc3b763a0ba582826c2888cd791ea5cfebf8c6eeba97688cff03942b80bd4855b40d077eb25e2677767cd9e3e32548b948133c53d5cfd98fb4120201020303039a912ac6df3a5af5d7cdbebd9c86dfc4d667901d38d17f5e265b4ec92851a3039a13ede5f8fe8fc936a9c053045c21b7cfac59232ed14acebe5a1270684c7ba402030366f89b9e4af2d9333431a7252441386158c0cd4f1416c432bbfeddeaf9a94fd303ea0e7f59ba22f8e1c16d8662786956816da4d6c44b3af63dbaeff9fa26ff58a8020303087927425293ead337b03b12cc3be21e324869c328321da791feace40848626c0320fde6ec582d5275f6c1b21b4ad7130f8e54c52d05483ef9effefa3cae9eaf51020303dd266e9e532095a3ef2479e8543f52ee9386405aadc619a2e962ad2f4ca7940003015c36f881ff87d7cdce55b31157699432c553b1c2be328b4b041688853ec960020303d58b82e1f5dc744c3e99a29dae08c0cacdd92b28e0966a5fb3b143479649353e0381584029a53e6c7f0dee68619e681482b9e36e43858e57bacb3554d7af2a8ad1020303f6ca9ca2515d3662f23cde1e54e67e0817607d0b9f501818a528ca1b43ffcce603bd381317706701d336e83e27c1cd699d0b616b349b0e28de4cd010cfec1a2bad0203020303af2d5e74e0ba57395bd0c11be5508a506eee906defac2ac84fba6ce7b577205703dddb21150e7c057c4df77ad73836cefe1e746adc52dfe903bcb543bea8eba9d502030203036cb57c550ffabdb39fe5531fac6c603b79b2551bfac7e208e7a1b1628607ff9303f46bdcac887fc8561c752bc45e1c98389a6a35cc0572575245a8f2ae513bea3f02030203035dff75d9bd1da1247aa0fc644e204d8fe7a916636d465210ba9a828a93bd8fde03f50e7e2741b63ce73e98ef6a769fa9339d941cf993b7e4b26305f22e9f18bc560203020303ec8a5f20ba3d3385c3ce7cd26702f5e40a4432f72ac566a3df649c1af87741fb036a000d8ceda0fcfe3ba4e6ae633e3abbd3deee0db83107e5ce0e0469b26e7324020302030203036058e9f8cd448caadf126fd3b7d50fbbdd8e2f7a8de9160a484ad79f8829bf5a03be9a1646b44327a504c96d0b2ac009d73adb23ba21ba3df5a5dfff32b74403680203020302030203020303ebee5c234bc2f660a9b3efe1bd2fb7d340182d904429b1f2a4e89bb51b1c47c903e51438724a9cf3725c22e07d59ba15acf0bbf473b37744164f122ac475ec42d20203020303bf9c131a0283cc46ca74d21b68d0b3a62d131dc9f4787ab60772569aaba63fd703f011de292bb236c3b08513f7b82ab7d311d0f80d4d3e173c2f8445028ed1cbf8020302030203020302030203020302030392af697495712b4013883b3f5ad2d370bdb93f0ed60416692b0267f10d9a3caa0386fa8ccd91ab622b232223f9a347f1785ca9c4b7323a2e0d19a7971c3afd63ff0203020303b4f12607fb8df583b854d7b235f4a64ccb2f4bc9819dc50f3a03ed0d4906910e038f64a125d14bb92752d65593faae8e41bb5e80e4f147b20f0c247078f6e7ca77070354000000000000000000000000000000005ca1ab1e58202d11035f2912c26c30c4f8957d3910a20622ea8709c8cd3e0ad87fa0f4460bbb5820c0bf0b2ab68768eaabe5fda7814227beaeaf4c4ee7e67f5d07aefaf5f0410ab80203034d5eb602925f13a2147a2c1439d43faa74e2561bb3d27811f02042466fb2804f035d9458bc537a1957fddbf6c5f13c6bfc9349abf1251df9d6dd48b5b574f6f48f020303bbf6401ad2a6b95a3e749f5b31224fc7fcdd083e7aeac9671ec3bebda312fe5c03393a914dd0b171b4cca2f5cef52cb4ed4b564278c0fb678e5e8f3d911b4addb302030356cdb16849ae7aff540b8724f73974149f71cd3f984360537159a273a5bfcc1d03791ad7bed137c9501bcee55dc6c9b030157a30c37fca14d39c25d9d5137ae88b020303e43916580d350f4da396c5763989f003085f6c468cf815846a95571702f1f53903e88243a0e60743a8285f13df8159992bd95c7f9546a8b5ef0ea2166fa211b8f70203039691d481d60a086435d9f914e8e2b5e5a68abfafb82dcc9d6de2176920c35ded03347f67f0fbbc63fa8a3b826c6491f42b13869a2abd2b6326d75d51cb30ea9cf1020303a06d3787a49c8745205aae2c80c6aed35adaa5a8e829f8ce8c29d55ffe8cadef032b843523c93d41eee41def0561be9ad7414c5bd9591d8e3723fcb0aea6170c72020303e56edd97325fff9e9a09d439d966a37ab63cdb3a3328b157445b60c3b91a86aa0381354b5bad8afeb2c183556c5f20e5d25c565cb8a738add05fc71bfb086737a102030301fa96c592fe444b2504b86acb3efb7befb3e241223f2d697c162be93668231d037f5346f59d4e0e4737f7b5cdde5494c43dcf2b583098022afa1d40024d434625020303299100220dba6b0afe91d1fb4a5c16f6cdc90da62bd73bd75b66063366a950f90315d7adf6a555d635edb76f96c7aeed7b5e3990ab1d13e0b01acd386ddeb43e0e0203034a527f4391b236f6ed15aeb5eb8839bca31aceadf3b8b5b7f5208d22f6a01b8903ecb9612fb023bcc161bfacadd2003a53d264c5555c4d65107fa01d984fc66017" +) diff --git a/zk/witness/witness_utils.go b/zk/witness/witness_utils.go new file mode 100644 index 00000000000..ce63342148e --- /dev/null +++ b/zk/witness/witness_utils.go @@ -0,0 +1,199 @@ +package witness + +import ( + "bytes" + "context" + "errors" + "fmt" + "math" + + "github.com/holiman/uint256" + coreState "github.com/ledgerwatch/erigon/core/state" + db2 "github.com/ledgerwatch/erigon/smt/pkg/db" + "github.com/ledgerwatch/erigon/smt/pkg/smt" + "github.com/ledgerwatch/erigon/turbo/trie" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/datadir" + "github.com/ledgerwatch/erigon-lib/kv" + "github.com/ledgerwatch/erigon-lib/state" + corestate "github.com/ledgerwatch/erigon/core/state" + + "github.com/ledgerwatch/erigon/core/rawdb" + eritypes "github.com/ledgerwatch/erigon/core/types" + "github.com/ledgerwatch/erigon/eth/stagedsync" + dstypes "github.com/ledgerwatch/erigon/zk/datastream/types" + zkSmt "github.com/ledgerwatch/erigon/zk/smt" + zkUtils "github.com/ledgerwatch/erigon/zk/utils" + "github.com/ledgerwatch/log/v3" +) + +var ( + ErrNoWitnesses = errors.New("witness count is 0") +) + +func UnwindForWitness(ctx context.Context, tx kv.RwTx, startBlock, latestBlock uint64, dirs datadir.Dirs, historyV3 bool, agg *state.Aggregator) (err error) { + unwindState := &stagedsync.UnwindState{UnwindPoint: startBlock - 1} + stageState := &stagedsync.StageState{BlockNumber: latestBlock} + + hashStageCfg := stagedsync.StageHashStateCfg(nil, dirs, historyV3, agg) + if err := stagedsync.UnwindHashStateStage(unwindState, stageState, tx, hashStageCfg, ctx, log.New(), true); err != nil { + return fmt.Errorf("UnwindHashStateStage: %w", err) + } + + var expectedRootHash common.Hash + syncHeadHeader, err := rawdb.ReadHeaderByNumber_zkevm(tx, unwindState.UnwindPoint) + if err != nil { + return fmt.Errorf("ReadHeaderByNumber_zkevm for block %d: %v", unwindState.UnwindPoint, err) + } + + if syncHeadHeader == nil { + log.Warn("header not found for block number", "block", unwindState.UnwindPoint) + } else { + expectedRootHash = syncHeadHeader.Root + } + + if _, err := zkSmt.UnwindZkSMT(ctx, "api.generateWitness", stageState.BlockNumber, unwindState.UnwindPoint, tx, true, &expectedRootHash, true); err != nil { + return fmt.Errorf("UnwindZkSMT: %w", err) + } + + return nil +} + +type gerForWitnessDb interface { + GetBatchNoByL2Block(blockNum uint64) (uint64, error) + GetBatchGlobalExitRoots(lastBatch, currentBatch uint64) (*[]dstypes.GerUpdate, error) + GetBlockGlobalExitRoot(blockNum uint64) (common.Hash, error) +} + +func PrepareGersForWitness(block *eritypes.Block, db gerForWitnessDb, tds *coreState.TrieDbState, trieStateWriter *coreState.TrieStateWriter) error { + blockNum := block.NumberU64() + //[zkevm] get batches between last block and this one + // plus this blocks ger + lastBatchInserted, err := db.GetBatchNoByL2Block(blockNum - 1) + if err != nil { + return fmt.Errorf("GetBatchNoByL2Block for block %d: %w", blockNum-1, err) + } + + currentBatch, err := db.GetBatchNoByL2Block(blockNum) + if err != nil { + return fmt.Errorf("GetBatchNoByL2Block for block %d: %v", blockNum, err) + } + + gersInBetween, err := db.GetBatchGlobalExitRoots(lastBatchInserted, currentBatch) + if err != nil { + return fmt.Errorf("GetBatchGlobalExitRoots for block %d: %v", blockNum, err) + } + + var globalExitRoots []dstypes.GerUpdate + + if gersInBetween != nil { + globalExitRoots = append(globalExitRoots, *gersInBetween...) + } + + blockGer, err := db.GetBlockGlobalExitRoot(blockNum) + if err != nil { + return fmt.Errorf("GetBlockGlobalExitRoot for block %d: %v", blockNum, err) + } + emptyHash := common.Hash{} + + if blockGer != emptyHash { + blockGerUpdate := dstypes.GerUpdate{ + GlobalExitRoot: blockGer, + Timestamp: block.Header().Time, + } + globalExitRoots = append(globalExitRoots, blockGerUpdate) + } + + for _, ger := range globalExitRoots { + // [zkevm] - add GER if there is one for this batch + if err := zkUtils.WriteGlobalExitRoot(tds, trieStateWriter, ger.GlobalExitRoot, ger.Timestamp); err != nil { + return fmt.Errorf("WriteGlobalExitRoot: %w", err) + } + } + + return nil +} + +type trieDbState interface { + ResolveSMTRetainList(inclusion map[common.Address][]common.Hash) (*trie.RetainList, error) +} + +func BuildWitnessFromTrieDbState(ctx context.Context, tx kv.Tx, tds trieDbState, reader *corestate.PlainState, forcedContracts []common.Address, witnessFull bool) (witness *trie.Witness, err error) { + var rl trie.RetainDecider + // if full is true, we will send all the nodes to the witness + rl = &trie.AlwaysTrueRetainDecider{} + + if !witnessFull { + inclusion := make(map[common.Address][]common.Hash) + for _, contract := range forcedContracts { + err = reader.ForEachStorage(contract, common.Hash{}, func(key, secKey common.Hash, value uint256.Int) bool { + inclusion[contract] = append(inclusion[contract], key) + return false + }, math.MaxInt64) + if err != nil { + return nil, err + } + } + + rl, err = tds.ResolveSMTRetainList(inclusion) + if err != nil { + return nil, err + } + } + + eridb := db2.NewRoEriDb(tx) + smtTrie := smt.NewRoSMT(eridb) + + if witness, err = smtTrie.BuildWitness(rl, ctx); err != nil { + return nil, fmt.Errorf("BuildWitness: %w", err) + } + + return +} + +func GetWitnessBytes(witness *trie.Witness, debug bool) ([]byte, error) { + var buf bytes.Buffer + if _, err := witness.WriteInto(&buf, debug); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func ParseWitnessFromBytes(input []byte, trace bool) (*trie.Witness, error) { + return trie.NewWitnessFromReader(bytes.NewReader(input), trace) +} + +// merges witnesses into one +// corresponds to a witness built on a range of blocks +// input witnesses should be ordered by consequent blocks +// it replaces values from 2,3,4 into the first witness +func MergeWitnesses(ctx context.Context, witnesses []*trie.Witness) (*trie.Witness, error) { + if len(witnesses) == 0 { + return nil, ErrNoWitnesses + } + + if len(witnesses) == 1 { + return witnesses[0], nil + } + + baseSmt, err := smt.BuildSMTFromWitness(witnesses[0]) + if err != nil { + return nil, fmt.Errorf("BuildSMTfromWitness: %w", err) + } + for i := 1; i < len(witnesses); i++ { + if err := smt.AddWitnessToSMT(baseSmt, witnesses[i]); err != nil { + return nil, fmt.Errorf("AddWitnessToSMT: %w", err) + } + } + + // if full is true, we will send all the nodes to the witness + rl := &trie.AlwaysTrueRetainDecider{} + + witness, err := baseSmt.BuildWitness(rl, ctx) + if err != nil { + return nil, fmt.Errorf("BuildWitness: %w", err) + } + + return witness, nil +} diff --git a/zk/witness/witness_utils_test.go b/zk/witness/witness_utils_test.go new file mode 100644 index 00000000000..5e14e1abbe7 --- /dev/null +++ b/zk/witness/witness_utils_test.go @@ -0,0 +1,203 @@ +package witness + +import ( + "bytes" + "context" + "encoding/binary" + "encoding/hex" + "fmt" + "math/big" + "math/rand" + "testing" + + "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon/crypto" + "github.com/ledgerwatch/erigon/smt/pkg/smt" + "github.com/ledgerwatch/erigon/turbo/trie" + "github.com/status-im/keycard-go/hexutils" + "github.com/stretchr/testify/assert" +) + +func TestMergeWitnesses(t *testing.T) { + smt1 := smt.NewSMT(nil, false) + smt2 := smt.NewSMT(nil, false) + smtFull := smt.NewSMT(nil, false) + + random := rand.New(rand.NewSource(0)) + + numberOfAccounts := 500 + + for i := 0; i < numberOfAccounts; i++ { + a := getAddressForIndex(i) + addressBytes := crypto.Keccak256(a[:]) + address := common.BytesToAddress(addressBytes).String() + balance := new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) + nonce := new(big.Int).Rand(random, new(big.Int).Exp(common.Big2, common.Big256, nil)) + bytecode := "afafaf" + contractStorage := make(map[string]string) + for j := 0; j < 10; j++ { + storageKey := genRandomByteArrayOfLen(32) + storageValue := genRandomByteArrayOfLen(32) + contractStorage[common.BytesToHash(storageKey).Hex()] = common.BytesToHash(storageValue).Hex() + } + var smtPart *smt.SMT + + if i&1 == 0 { + smtPart = smt1 + } else { + smtPart = smt2 + } + + if _, err := smtPart.SetAccountBalance(address, balance); err != nil { + t.Error(err) + return + } + if _, err := smtPart.SetAccountNonce(address, nonce); err != nil { + t.Error(err) + return + } + if err := smtPart.SetContractBytecode(address, bytecode); err != nil { + t.Error(err) + return + } + if err := smtPart.Db.AddCode(hexutils.HexToBytes(bytecode)); err != nil { + t.Error(err) + return + } + if _, err := smtPart.SetContractStorage(address, contractStorage, nil); err != nil { + t.Error(err) + return + } + + if _, err := smtFull.SetAccountBalance(address, balance); err != nil { + t.Error(err) + return + } + if _, err := smtFull.SetAccountNonce(address, nonce); err != nil { + t.Error(err) + return + } + if err := smtFull.SetContractBytecode(address, bytecode); err != nil { + t.Error(err) + return + } + if err := smtFull.Db.AddCode(hexutils.HexToBytes(bytecode)); err != nil { + t.Error(err) + return + } + if _, err := smtFull.SetContractStorage(address, contractStorage, nil); err != nil { + t.Error(err) + return + } + } + + rl1 := &trie.AlwaysTrueRetainDecider{} + rl2 := &trie.AlwaysTrueRetainDecider{} + rlFull := &trie.AlwaysTrueRetainDecider{} + witness1, err := smt1.BuildWitness(rl1, context.Background()) + if err != nil { + t.Error(err) + return + } + + witness2, err := smt2.BuildWitness(rl2, context.Background()) + if err != nil { + t.Error(err) + return + } + + witnessFull, err := smtFull.BuildWitness(rlFull, context.Background()) + if err != nil { + t.Error(err) + return + } + mergedWitness, err := MergeWitnesses(context.Background(), []*trie.Witness{witness1, witness2}) + assert.Nil(t, err, "should successfully merge witnesses") + + //create writer + var buff bytes.Buffer + mergedWitness.WriteDiff(witnessFull, &buff) + diff := buff.String() + assert.Equal(t, 0, len(diff), "witnesses should be equal") + if len(diff) > 0 { + fmt.Println(diff) + } +} + +func getAddressForIndex(index int) [20]byte { + var address [20]byte + binary.BigEndian.PutUint32(address[:], uint32(index)) + return address +} + +func genRandomByteArrayOfLen(length uint) []byte { + array := make([]byte, length) + for i := uint(0); i < length; i++ { + array[i] = byte(rand.Intn(256)) + } + return array +} + +func TestMergeRealWitnesses(t *testing.T) { + witnessBytes1, err := hex.DecodeString(witness1) + assert.NoError(t, err, "error decoding witness1") + witnessBytes2, err := hex.DecodeString(witness2) + assert.NoError(t, err, "error decoding witness2") + expectedWitnessBytes, err := hex.DecodeString(resultWitness) + assert.NoError(t, err, "error decoding expectedWitness") + + blockWitness1, err := ParseWitnessFromBytes(witnessBytes1, false) + assert.NoError(t, err, "error parsing witness1") + blockWitness2, err := ParseWitnessFromBytes(witnessBytes2, false) + assert.NoError(t, err, "error parsing witness2") + expectedWitness, err := ParseWitnessFromBytes(expectedWitnessBytes, false) + assert.NoError(t, err, "error parsing expectedWitness") + + mergedWitness, err := MergeWitnesses(context.Background(), []*trie.Witness{blockWitness1, blockWitness2}) + assert.NoError(t, err, "error merging witnesses") + + //create writer + var buff bytes.Buffer + expectedWitness.WriteDiff(mergedWitness, &buff) + diff := buff.String() + if len(diff) > 0 { + fmt.Println(diff) + } + assert.Equal(t, 0, len(diff), "witnesses should be equal") +} + +func TestMergeWitnessesWithHashNodes(t *testing.T) { + smt1 := smt.NewSMT(nil, false) + smt2 := smt.NewSMT(nil, false) + smtFull := smt.NewSMT(nil, false) + + _, err := smt1.InsertHashNode([]int{0, 0, 0}, new(big.Int).SetUint64(1)) + assert.NoError(t, err, "error inserting hash node") + _, err = smt2.InsertHashNode([]int{0, 0}, new(big.Int).SetUint64(2)) + assert.NoError(t, err, "error inserting hash node") + _, err = smtFull.InsertHashNode([]int{0, 0, 0}, new(big.Int).SetUint64(1)) + assert.NoError(t, err, "error inserting hash node") + + // get witnesses + rl1 := &trie.AlwaysTrueRetainDecider{} + rl2 := &trie.AlwaysTrueRetainDecider{} + rlFull := &trie.AlwaysTrueRetainDecider{} + blockWitness1, err := smt1.BuildWitness(rl1, context.Background()) + assert.NoError(t, err, "error building witness") + blockWitness2, err := smt2.BuildWitness(rl2, context.Background()) + assert.NoError(t, err, "error building witness") + expectedWitness, err := smtFull.BuildWitness(rlFull, context.Background()) + assert.NoError(t, err, "error building witness") + + mergedWitness, err := MergeWitnesses(context.Background(), []*trie.Witness{blockWitness1, blockWitness2}) + assert.NoError(t, err, "error merging witnesses") + + //create writer + var buff bytes.Buffer + expectedWitness.WriteDiff(mergedWitness, &buff) + diff := buff.String() + if len(diff) > 0 { + fmt.Println(diff) + } + assert.Equal(t, 0, len(diff), "witnesses should be equal") +}