diff --git a/.env b/.env deleted file mode 100644 index 31ac7d6..0000000 --- a/.env +++ /dev/null @@ -1,4 +0,0 @@ -DB_USER=salka1988 -DB_PASS=let-me-in -DB_NAME=graph-node -NODE_ID=default \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index cab53a5..99cef55 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,4 +1,4 @@ -name: Build ad publish Docker image +name: Build and publish Docker image on: push: @@ -28,5 +28,6 @@ jobs: with: username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - image: ghcr.io/fuellabs/fuel-graph-node - dockerfile: fuel-graph-node/Dockerfile \ No newline at end of file + image: ghcr.io/fuellabs/fuel-subgraph + dockerfile: firehose/Dockerfile + context: firehose \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3f4072b..400f53b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,10 @@ version: "3" services: firehose: build: - context: ./firehose + context: firehose + dockerfile: Dockerfile +# image: 'ghcr.io/fuellabs/fuel-subgraph:cc2be21' +# platform: linux/amd64 ports: - "10015:10015" - "10016:10016" diff --git a/docker-graph-config.toml b/docker-graph-config.toml deleted file mode 100644 index 3d00768..0000000 --- a/docker-graph-config.toml +++ /dev/null @@ -1,20 +0,0 @@ -[deployment] -[[deployment.rule]] -shards = "primary" -indexers = ["default"] - -[store] -[store.primary] -connection = "postgresql://salka1988:let-me-in@postgres:5432/graph-node" -pool_size = 10 - -[chains] -ingestor = "block_ingestor_node" - -[chains.fuelnet] -shard = "primary" -protocol = "fuel" -provider = [ - { label = "fuel", details = { type = "firehose", url = "http://firehose:10015" } }, -] - diff --git a/firehose/Dockerfile b/firehose/Dockerfile index cc23731..2590da0 100644 --- a/firehose/Dockerfile +++ b/firehose/Dockerfile @@ -1,31 +1,36 @@ -# extractor - +# Stage 1: Use cargo-chef to generate a recipe FROM lukemathwalker/cargo-chef:latest-rust-1.76-slim AS chef - WORKDIR /app +# Stage 2: Planner FROM chef AS planner -COPY ./firehose-extract/ . +COPY ./firehose-extract/ /app/ RUN cargo chef prepare --recipe-path recipe.json +# Stage 3: Extract builder FROM chef AS extract_builder RUN apt update && apt install -y protobuf-compiler COPY --from=planner /app/recipe.json recipe.json -# Build dependencies - this is the caching Docker layer! RUN cargo chef cook --release --recipe-path recipe.json -# Build application -COPY ./firehose-extract/ . +COPY ./firehose-extract/ /app/ RUN cargo build --release -# firefuel -FROM golang:1.22.1 AS builder -COPY ./firehose-fuel/ . -RUN GOBIN=/ go install ./cmd/firefuel - +# Stage 4: Go build +FROM golang:1.22-alpine as go_build +COPY ./firehose-core/ /app/firehose-core/ +COPY ./substreams/ /app/substreams/ +WORKDIR /app/firehose-core +RUN go mod download +ARG VERSION="dev" +RUN apk --no-cache add git +RUN go build -v -ldflags "-X main.version=${VERSION}" -o /app/firehose-core/firecore ./cmd/firecore + +# Stage 5: Final stage FROM debian COPY --from=extract_builder /app/target/release/firehose-extract /app/firehose-extract -COPY --from=builder firefuel /app/firefuel - -COPY ./firehose-fuel/devel/fuelfire/bootstrap.sh /app/start.sh +COPY --from=go_build /app/firehose-core/firecore /app/firecore +COPY --from=go_build /app/firehose-core/devel/standard/bootstrap.sh /app/start.sh +RUN chmod +x /app/start.sh -ENTRYPOINT ["/app/start.sh"] +# Additional setup if needed +ENTRYPOINT [ "/app/start.sh" ] diff --git a/firehose/firehose-core/.github/workflows/docker.yml b/firehose/firehose-core/.github/workflows/docker.yml new file mode 100644 index 0000000..271595b --- /dev/null +++ b/firehose/firehose-core/.github/workflows/docker.yml @@ -0,0 +1,82 @@ +name: Build docker image + +on: + push: + tags: + - "v*" + branches: + - "develop" + - "feature/*" + workflow_dispatch: + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-20.04 + + permissions: + contents: read + packages: write + + outputs: + tags: ${{ steps.meta.outputs.tags }} + + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-go@v5 + with: + go-version: '1.22.x' + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Get go pseudohash + id: extract_pseudohash + shell: bash + run: | + echo "GO_PSEUDOHASH=$(go list -f '{{.Version}}' -m github.com/streamingfast/firehose-core@${{ github.sha }})" | tee $GITHUB_ENV + + - name: Generate docker tags/labels from github build context + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=tag + type=sha,prefix=,enable=true + type=raw,value=${{ env.GO_PSEUDOHASH }} + type=raw,enable=${{ github.ref == 'refs/heads/develop' }},value=develop + flavor: | + latest=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + build-args: | + VERSION=${{ github.event.ref }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + + slack-notifications: + if: ${{ !startsWith(github.ref, 'refs/tags/') && github.event_name != 'workflow_dispatch' }} + needs: [ build ] + runs-on: ubuntu-20.04 + steps: + - name: Slack notification + env: + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + uses: Ilshidur/action-slack@2.0.2 + with: + args: | + :done: *${{ github.repository }}* Success building docker image from ${{ github.ref_type }} _${{ github.ref_name }}_ (${{ github.actor }}) :sparkling_heart: ```${{ join(needs.build.outputs.tags, ' ') }}``` diff --git a/firehose/firehose-core/.gitignore b/firehose/firehose-core/.gitignore new file mode 100644 index 0000000..85d77f0 --- /dev/null +++ b/firehose/firehose-core/.gitignore @@ -0,0 +1,7 @@ +.idea +/build +/dist +.envrc +.env +.DS_Store +firehose-data* \ No newline at end of file diff --git a/firehose/firehose-core/.sfreleaser b/firehose/firehose-core/.sfreleaser new file mode 100644 index 0000000..a98efb7 --- /dev/null +++ b/firehose/firehose-core/.sfreleaser @@ -0,0 +1,6 @@ +global: + binary: firecore + language: golang + variant: application +release: + goreleaser-docker-image: goreleaser/goreleaser-cross:v1.22 diff --git a/firehose/firehose-core/CHANGELOG.md b/firehose/firehose-core/CHANGELOG.md new file mode 100644 index 0000000..feea602 --- /dev/null +++ b/firehose/firehose-core/CHANGELOG.md @@ -0,0 +1,546 @@ +# Change log + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this +project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). See [MAINTAINERS.md](./MAINTAINERS.md) +for instructions to keep up to date. + +Operators, you should copy/paste content of this content straight to your project. It is written and meant to be copied over to your project. + +If you were at `firehose-core` version `1.0.0` and are bumping to `1.1.0`, you should copy the content between those 2 version to your own repository, replacing placeholder value `fire{chain}` with your chain's own binary. + +## Unreleased + +* bump go-generics to v3.4.0 + +## v1.3.5 + +### Substreams fixes + +* fix a possible panic() when an request is interrupted during the file loading phase of a squashing operation. +* fix a rare possibility of stalling if only some fullkv stores caches were deleted, but further segments were still present. +* fix stats counters for store operations time + +## v1.3.4 + +* add `DefaultBlockType` into `firehose.Chain` struct, enabling default block type setting for known chain + +### substreams + +* bumped to v1.5.3 +* add `--block-type` flag that can be specified when creating substreams tier1. If not specified, tier1 will auto-detect block type from source. +* fix memory leak on substreams execution (by bumping wazero dependency) +* prevent substreams-tier1 stopping if blocktype auto-detection times out +* fix missing error handling when writing output data to files. This could result in tier1 request just "hanging" waiting for the file never produced by tier2. +* fix handling of dstore error in tier1 'execout walker' causing stalling issues on S3 or on unexpected storage errors +* increase number of retries on storage when writing states or execouts (5 -> 10) +* prevent slow squashing when loading each segment from full KV store (can happen when a stage contains multiple stores) + +## v1.3.3 + +### substreams + +* Fix a context leak causing tier1 responses to slow down progressively + +## v1.3.2 + +### substreams + +* fix another panic on substreams-tier2 service +* fix thread leak in metering affecting substreams +* revert a substreams scheduler optimisation that causes slow restarts when close to head +* add substreams_tier2_active_requests and substreams_tier2_request_counter prometheus metrics + +## v1.3.1 + +* fix panic on substreams-tier2 service + +## v1.3.0 + +### Substreams + +* Substreams bumped to @v1.5.0: See https://github.com/streamingfast/substreams/releases/tag/v1.5.0 for details. + +#### Chain-agnostic tier2 + +* A single substreams-tier2 instance can now serve requests for multiple chains or networks. All network-specific parameters are now passed from Tier1 to Tier2 in the internal ProcessRange request. +* This allows you to better use your computing resources by pooling all the networks together. + +> [!IMPORTANT] +> Since the `tier2` services will now get the network information from the `tier1` request, you must make sure that the file paths and network addresses will be the same for both tiers. +> ex: if `--common-merged-blocks-store-url=/data/merged` is set on tier1, make sure the merged blocks are also available from tier2 under the path `/data/merged`. +> The flags `--substreams-state-store-url`, `--substreams-state-store-default-tag` and `--common-merged-blocks-store-url` are now ignored on tier2. The flag `--common-first-streamable-block` should be set to 0 to accommodate every chain. + +> [!TIP] +> The cached 'partial' files no longer contain the "trace ID" in their filename, preventing accumulation of "unsquashed" partial store files. The system will delete files under '{modulehash}/state' named in this format`{blocknumber}-{blocknumber}.{hexadecimal}.partial.zst` when it runs into them. + +#### Performance improvements + +* All module outputs are now cached. (previously, only the last module was cached, along with the "store snapshots", to allow parallel processing). +* Tier2 will now read back mapper outputs (if they exist) to prevent running them again. Additionally, it will not read back the full blocks if its inputs can be satisfied from existing cached mapper outputs. +* Tier2 will skip processing completely if it's processing the last stage and the `output_module` is a mapper that has already been processed (ex: when multiple requests are indexing the same data at the same time) +* Tier2 will skip processing completely if it's processing a stage where all the stores and outputs have been processed and cached. +* Scheduler modification: a stage now waits for the previous stage to have completed the same segment before running, to take advantage of the cached intermediate layers. +* Improved file listing performance for Google Storage backends by 25% + +> [!TIP] +* Concurrent requests on the same module hashes may benefit from the other requests' work to a certain extent (up to 75%) -- The very first request does most of the work for the other ones. + +> [!TIP] +> More caches will increase disk usage and there is no automatic removal of old module caches. The operator is responsible for deleting old module caches. + +> [!TIP] +> The cached 'partial' files no longer contain the "trace ID" in their filename, preventing accumulation of "unsquashed" partial store files. +> The system will delete files under '{modulehash}/state' named in this format`{blocknumber}-{blocknumber}.{hexadecimal}.partial.zst` when it runs into them. + +#### Metrics + +* Readiness metric for Substreams tier1 app is now named `substreams_tier1` (was mistakenly called `firehose` before). +* Added back readiness metric for Substreams tiere app (named `substreams_tier2`). +* Added metric `substreams_tier1_active_worker_requests` which gives the number of active Substreams worker requests a tier1 app is currently doing against tier2 nodes. +* Added metric `substreams_tier1_worker_request_counter` which gives the total Substreams worker requests a tier1 app made against tier2 nodes. + +### Flags + +* Added `--merger-delete-threads` to customize the number of threads the merger will use to delete files. It's recommended to increase this when using Ceph as S3 storage provider to 25 or higher (due to performance issues with deletes the merger might otherwise not be able to delete one-block files fast enough). +* Added `--substreams-tier2-max-concurrent-requests` to limit the number of concurrent requests to the tier2 substreams service. + +## v1.2.5 + +* Fixed `tools check merged-blocks` default range when `-r ` is not provided to now be `[0, +∞]` (was previously `[HEAD, +∞]`). + +* Fixed `tools check merged-blocks` to be able to run without a block range provided. + +* Added API Key authentication to `client.NewFirehoseFetchClient` and `client.NewFirehoseClient`. + + > [!NOTE] + > If you were using `github.com/streamingfast/firehose-core/firehose/client.NewFirehoseFetchClient` or `github.com/streamingfast/firehose-core/firehose/client.NewFirehoseStreamClient`, this will be a minor breaking change, refer to [upgrade notes](./UPDATE.md#v125) for details if it affects you. + +* Fixed `tools check merged-blocks` examples using block range (range should be specified as `[]?:[]`). + +* Added `--substreams-tier2-max-concurrent-requests` to limit the number of concurrent requests to the tier2 Substreams service. + +## v1.2.4 + +### Substreams improvements + +* Performance: prevent reprocessing jobs when there is only a mapper in production mode and everything is already cached +* Performance: prevent "UpdateStats" from running too often and stalling other operations when running with a high parallel jobs count +* Performance: fixed bug in scheduler ramp-up function sometimes waiting before raising the number of workers +* Added the output module's hash to the "incoming request" log + +### Reader node and Beacon blocks +* The `reader-node-bootstrap-url` gained the ability to be bootstrapped from a `bash` script. + + If the bootstrap URL is of the form `bash:///?`, the bash script at + `` will be executed. The script is going to receive in environment variables the resolved + reader node variables in the form of `READER_NODE_`. The fully resolved node arguments + (from `reader-node-arguments`) are passed as args to the bash script. The query parameters accepted are: + + - `arg=` | Pass as extra argument to the script, prepended to the list of resolved node arguments + - `env=%3d` | Pass as extra environment variable as `=` with key being upper-cased (multiple(s) allowed) + - `env_=` | Pass as extra environment variable as `=` with key being upper-cased (multiple(s) allowed) + - `cwd=` | Change the working directory to `` before running the script + - `interpreter=` | Use `` as the interpreter to run the script + - `interpreter_arg=` | Pass `` as arguments to the interpreter before the script path (multiple(s) allowed) + + > [!NOTE] + > The `bash:///` script support is currently experimental and might change in upcoming releases, the behavior changes will be + clearly documented here. + +* The `reader-node-bootstrap-url` gained the ability to be bootstrapped from a pre-made archive file ending with `tar.zst` or `tar.zstd`. + +* The `reader-node-bootstrap-data-url` is now added automatically if `firecore.Chain#ReaderNodeBootstrapperFactory` is `non-nil`. + + If the bootstrap URL ends with `tar.zst` or `tar.zstd`, the archive is read and extracted into the + `reader-node-data-dir` location. The archive is expected to contain the full content of the 'reader-node-data-dir' + and is expanded as is. + +* Added `Beacon` to known list of Block model. + +## v1.2.3 + +* Fix marshalling of blocks to JSON in tools like `firehose-client` and `print merged-blocks` + +## v1.2.2 + +### Auth and metering + +* Add missing metering events for `sf.firehose.v2.Fetch/Block` responses. +* Changed default polling interval in 'continuous authentication' from 10s to 60s, added 'interval' query param to URL. + +### Substreams + +* Fixed bug in scheduler ramp-up function sometimes waiting before raising the number of workers +* Fixed load-balancing from tier1 to tier2 when using dns:/// (round-robin policy was not set correctly) +* Added `trace_id` in grpc authentication calls +* Bumped connect-go library to new "connectrpc.com/connect" location + +## v1.2.1 + +### Fixed + +* Fixed `tools firehose-client` which was broken because of bad flag handling + +### Added + +* Added `--api-key-env-var` flag to firehose-clients, which allows you to pass your API Key from an environment variable (HTTP header `x-api-key`) instead of a JWT (`Authorization: bearer`), where supported. + +## v1.2.0 + +* Poller is now fetching blocks in an optimized way, it will fetch several blocks at once and then process them. + +* Poller is now handling skipped blocks, it will fetch the next blocks until it find a none skipped block. + +* Poller now has default retry value of infinite. + +* Compare tool is now using dynamic protobuf unmarshaler, it will be able to compare any block type. + +* Print tool is now using dynamic protobuf unmarshaler, it will be able to print any block type. + +* Print tool is encoding bytes in base64 by default, it can be changed to hex or base58 by using parameter `bytes-encoding`. + +* Added 'x-trace-id' header to auth requests when using --common-auth-plugin=grpc + +* Fixed Substreams scheduler sometimes taking a long time to spawn more than a single worker. + +* Added ACCEPT_SOLANA_LEGACY_BLOCK_FORMAT env var to allow special tweak operations + +## v1.1.3 + +* Removed useless chainLatestFinalizeBlock from blockPoller initialization + +## v1.1.2 + +* Added `Arweave` to known list of Block model. + +## v1.1.1 + +* Added `FORCE_FINALITY_AFTER_BLOCKS` environment variable to override block finality information at the reader/poller level. This allows an operator to pretend that finality is still progressing, N blocks behind HEAD, in the case where a beacon chain fails to do so and is intended as a workaround for deprecated chains like Goerli. + +## v1.1.0 + +* Updated `substreams` and `dgrpc` to latest versions to reduce logging. + +* Tools printing Firehose `Block` model to JSON now have `--proto-paths` take higher precedence over well-known types and even the chain itself, the order is `--proto-paths` > `chain` > `well-known` (so `well-known` is lookup last). + +* The `tools print one-block` now works correctly on blocks generated by omni-chain `firecore` binary. + +* Tools printing Firehose `Block` model to JSON now have `--proto-paths` take higher precedence over well-known types and even the chain itself, the order is `--proto-paths` > `chain` > `well-known` (so `well-known` is lookup last). + +* The `tools print one-block` now works correctly on blocks generated by omni-chain `firecore` binary. + +* The various health endpoint now sets `Content-Type: application/json` header prior sending back their response to the client. + +* The `firehose`, `substreams-tier1` and `substream-tier2` health endpoint now respects the `common-system-shutdown-signal-delay` configuration value meaning that the health endpoint will return `false` now if `SIGINT` has been received but we are still in the shutdown unready period defined by the config value. If you use some sort of load balancer, you should make sure they are configured to use the health endpoint and you should `common-system-shutdown-signal-delay` to something like `15s`. + +* The `firecore.ConsoleReader` gained the ability to print stats as it ingest blocks. + +* The `firecore.ConsoleReader` has been made stricter by ensuring Firehose chain exchange protocol is respected. + +* Changed `reader` logger back to `reader-node` to fit with the app's name which is `reader-node`. + +* Fix `-c ""` not working properly when no arguments are present when invoking `start` command. + +* Fix `tools compare-blocks` that would fail on new format. + +* Fix `substreams` to correctly delete `.partial` files when serving a request that is not on a boundary. + +* Add Antelope types to the blockchain's known types. + +## v1.0.0 + +This is a major release. + +### Operators + +> [!IMPORTANT] +> When upgrading your stack to firehose-core v1.0.0, be sure to upgrade all components simultaneously because the block encapsulation format has changed. +> Blocks that are merged using the new merger will not be readable by previous versions. + +### Added + +* New binary `firecore` which can run all firehose components (`reader`, `reader-stdin`, `merger`, `relayer`, `firehose`, `substreams-tier1|2`) in a chain-agnostic way. This is not mandatory (it can still be used as a library) but strongly suggested when possible. + +* Current Limitations on Ethereum: + - The firecore `firehose` app does not support transforms (filters, header-only --for graph-node compatibility--) so you will want to continue running this app from `fireeth` + - The firecore `substreams` apps do not support eth_calls so you will want to continue running them from `fireeth` + - The firecore `reader` does not support the block format output by the current geth firehose instrumentation, so you will want to continue running it from `fireeth` + +* New BlockPoller library to facilitate the implementation of rpc-poller-based chains, taking care of managing reorgs + +* Considering that firehose-core is chain-agnostic, it's not aware of the different of the different block types. To be able to use tools around block decoding/printing, + there are two ways to provide the type definition: + 1. the 'protoregistry' package contains well-known block type definitions (ethereum, near, solana, bitcoin...), you won't need to provide anything in those cases. + 2. for other types, you can provide additional protobuf files using `--proto-path` flag + +### Changed + +* Merged blocks storage format has been changed. Current blocks will continue to be decoded, but new merged blocks will not be readable by previous software versions. +* The code from the following repositories have been merged into this repo. They will soon be archived. + * github.com/streamingfast/node-manager + * github.com/streamingfast/merger + * github.com/streamingfast/relayer + * github.com/streamingfast/firehose + * github.com/streamingfast/index-builder + +## v0.2.4 + +* Fixed SF_TRACING feature (regression broke the ability to specify a tracing endpoint) +* Firehose connections rate-limiting will now force a delay of between 1 and 4 seconds (random value) before refusing a connection when under heavy load +* Fixed substreams GRPC/Connect error codes not propagating correctly + +## v0.2.3 + +### Fixed + + * fixed typo in `check-merged-blocks` preventing its proper display of missing ranges + +## v0.2.2 + +### Added + +* Firehose logs now include auth information (userID, keyID, realIP) along with blocks + egress bytes sent. + +### Fixed + +* Filesource validation of block order in merged-blocks now works correctly when using indexes in firehose `Blocks` queries + +### Removed + +* Flag `substreams-rpc-endpoints` removed, this was present by mistake and unused actually. +* Flag `substreams-rpc-cache-store-url` removed, this was present by mistake and unused actually. +* Flag `substreams-rpc-cache-chunk-size` removed, this was present by mistake and unused actually. + +## v0.2.1 + +### Operators + +> [!IMPORTANT] +> We have had reports of older versions of this software creating corrupted merged-blocks-files (with duplicate or extra out-of-bound blocks) +> This release adds additional validation of merged-blocks to prevent serving duplicate blocks from the firehose or substreams service. +> This may cause service outage if you have produced those blocks or downloaded them from another party who was affected by this bug. + +* Find the affected files by running the following command (can be run multiple times in parallel, over smaller ranges) + +``` +tools check merged-blocks-batch +``` + +* If you see any affected range, produce fixed merged-blocks files with the following command, on each range: + +``` +tools fix-bloated-merged-blocks : +``` + +* Copy the merged-blocks files created in output-store over to the your merged-blocks-store, replacing the corrupted files. + +### Removed +* Removed the `--dedupe-blocks` flag on `tools download-from-firehose` as it can create confusion and more issues. + +### Fixed +* Bumped `bstream`: the `filesource` will now refuse to read blocks from a merged-files if they are not ordered or if there are any duplicate. +* The command `tools download-from-firehose` will now fail if it is being served blocks "out of order", to prevent any corrupted merged-blocks from being created. +* The command `tools print merged-blocks` did not print the whole merged-blocks file, the arguments were confusing: now it will parse as a uint64. +* The command `tools unmerge-blocks` did not cover the whole given range, now fixed + +### Added +* Added the command `tools fix-bloated-merged-blocks` to try to fix merged-blocks that contain duplicates and blocks outside of their range. +* Command `tools print one-block and merged-blocks` now supports a new `--output-format` `jsonl` format. +Bytes data can now printed as hex or base58 string instead of base64 string. + +### Changed +* Changed `tools check merged-blocks-batch` argument syntax: the output-to-store is now optional. + +## v0.2.0 + +### Fixed + +* Fixed a few false positives on `tools check merged-blocks-batch` +* Fixed `tools print merged-blocks` to print correctly a single block if specified. + +### Removed + +* **Breaking** The `reader-node-log-to-zap` flag has been removed. This was a source of confusion for operators reporting Firehose on bugs because the node's logs where merged within normal Firehose on logs and it was not super obvious. + + Now, logs from the node will be printed to `stdout` unformatted exactly like presented by the chain. Filtering of such logs must now be delegated to the node's implementation and how it deals depends on the node's binary. Refer to it to determine how you can tweak the logging verbosity emitted by the node. + +### Added + +* Added support `-o jsonl` in `tools print merged-blocks` and `tools print one-block`. +* Added support for block range in `tools print merged-blocks`. + + > [!NOTE] + > For now the range is restricted to a single "merged-blocks" file! + +* Added retry loop for merger when walking one block files. Some use-cases where the bundle reader was sending files too fast and the merger was not waiting to accumulate enough files to start bundling merged files +* Added `--dedupe-blocks` flag on `tools download-from-firehose` to ensure no duplicate blocks end up in download merged-blocks (should not be needed in normal operations) + +## v0.1.12 + +* Added `tools check merged-blocks-batch` to simplify checking blocks continuity in batched mode, writing results to a store +* Bumped substreams to `v1.1.20` with a fix for some minor bug fixes related to start block processing + +## v0.1.11 + +* Bumped `substreams` to `v1.1.18` with a regression fix for when a Substreams has a start block in the reversible segment. + +## v0.1.10 + +### Added + +The `--common-auth-plugin` got back the ability to use `secret://?[user_id=]&[api_key_id=]` in which case request are authenticated based on the `Authorization: Bearer ` and continue only if ` == `. + +### Changed +* Bumped `substreams` to `v1.1.17` with provider new metrics `substreams_active_requests` and `substreams_counter` + +## v0.1.9 + +### Changed + +* Bumped `susbtreams` to `v1.1.14` to fix bugs with start blocks, where Substreams would fail if the start block was before the first block of the chain, or if the start block was a block that was not yet produced by the chain. +* Improved error message when referenced config file is not found, removed hard-coded mention of `fireacme`. + +## v0.1.8 + +### Fixed + +* More tolerant retry/timeouts on filesource (prevent "Context Deadline Exceeded") + +## v0.1.7 + +### Operators + +> [!IMPORTANT] +> The Substreams service exposed from this version will send progress messages that cannot be decoded by Substreams clients prior to v1.1.12. +> Streaming of the actual data will not be affected. Clients will need to be upgraded to properly decode the new progress messages. + +### Changed + +* Bumped substreams to `v1.1.12` to support the new progress message format. Progression now relates to **stages** instead of modules. You can get stage information using the `substreams info` command starting at version `v1.1.12`. +* Bumped supervisor buffer size to 100Mb +* Substreams bumped: better "Progress" messages + +### Added + +* Added new templating option to `reader-node-arguments`, specifically `{start-block-num}` (maps to configuration value `reader-node-start-block-num`) and `{stop-block-num}` (maps to value of configuration value `reader-node-stop-block-num`) + +### Changed + +* The `reader-node` is now able to read Firehose node protocol line up to 100 MiB in raw size (previously the limit was 50 MiB). + +### Removed + +* Removed `--substreams-tier1-request-stats` and `--substreams-tier1-request-stats` (Substreams request-stats are now always sent to clients) + +## v0.1.6 + +### Fixed + +* Fixed bug where `null` dmetering plugin was not able to be registered. + +## v0.1.5 + +### Fixed + +* Fixed dmetering bug where events where dropped, when channel got saturated + +### Changed + +* `fire{chain} tools check forks` now sorts forks by block number from ascending order (so that line you see is the current highest fork). +* `fire{chain} tools check forks --after-block` can now be used to show only forks after a certain block number. +* bump `firehose`, `dmetering` and `bstream` dependencies in order to get latest fixes to meter live blocks. + +## v0.1.4 + +This release bumps Substreams to [v1.1.10](https://github.com/streamingfast/substreams/releases/tag/v1.1.10). + +### Fixed + +* Fixed jobs that would hang when flags `--substreams-state-bundle-size` and `--substreams-tier1-subrequests-size` had different values. The latter flag has been completely **removed**, subrequests will be bound to the state bundle size. + +### Added + +* Added support for *continuous authentication* via the grpc auth plugin (allowing cutoff triggered by the auth system). + +## v0.1.3 + +This release bumps Substreams to [v1.1.9](https://github.com/streamingfast/substreams/releases/tag/v1.1.9). + +### Highlights + +#### Substreams Scheduler Improvements for Parallel Processing + +The `substreams` scheduler has been improved to reduce the number of required jobs for parallel processing. This affects `backprocessing` (preparing the states of modules up to a "start-block") and `forward processing` (preparing the states and the outputs to speed up streaming in production-mode). + +Jobs on `tier2` workers are now divided in "stages", each stage generating the partial states for all the modules that have the same dependencies. A `substreams` that has a single store won't be affected, but one that has 3 top-level stores, which used to run 3 jobs for every segment now only runs a single job per segment to get all the states ready. + + +#### Substreams State Store Selection + +The `substreams` server now accepts `X-Sf-Substreams-Cache-Tag` header to select which Substreams state store URL should be used by the request. When performing a Substreams request, the servers will optionally pick the state store based on the header. This enable consumers to stay on the same cache version when the operators needs to bump the data version (reasons for this could be a bug in Substreams software that caused some cached data to be corrupted on invalid). + +To benefit from this, operators that have a version currently in their state store URL should move the version part from `--substreams-state-store-url` to the new flag `--substreams-state-store-default-tag`. For example if today you have in your config: + +```yaml +start: + ... + flags: + substreams-state-store-url: ///v3 +``` + +You should convert to: + +```yaml +start: + ... + flags: + substreams-state-store-url: // + substreams-state-store-default-tag: v3 +``` + +### Operators Upgrade + +The app `substreams-tier1` and `substreams-tier2` should be upgraded concurrently. Some calls will fail while versions are misaligned. + +### Backend Changes + +* Authentication plugin `trust` can now specify an exclusive list of `allowed` headers (all lowercase), ex: `trust://?allowed=x-sf-user-id,x-sf-api-key-id,x-real-ip,x-sf-substreams-cache-tag` + +* The `tier2` app no longer uses the `common-auth-plugin`, `trust` will always be used, so that `tier1` can pass down its headers (ex: `X-Sf-Substreams-Cache-Tag`). + +## v0.1.2 + +#### Operator Changes + +* Added `fire{chain} tools check forks [--min-depth=]` that reads forked blocks you have and prints resolved longest forks you have seen. The command works for any chain, here a sample output: + + ```log + ... + + Fork Depth 3 + #45236230 [ea33194e0a9bb1d8 <= 164aa1b9c8a02af0 (on chain)] + #45236231 [f7d2dc3fbdd0699c <= ea33194e0a9bb1d8] + #45236232 [ed588cca9b1db391 <= f7d2dc3fbdd0699c] + + Fork Depth 2 + #45236023 [b6b1c68c30b61166 <= 60083a796a079409 (on chain)] + #45236024 [6d64aec1aece4a43 <= b6b1c68c30b61166] + + ... + ``` + +* The `fire{chain} tools` commands and sub-commands have better rendering `--help` by hidden not needed global flags with long description. + +## v0.1.1 + +#### Operator Changes + +* Added missing `--substreams-tier2-request-stats` request debugging flag. + +* Added missing Firehose rate limiting options flags, `--firehose-rate-limit-bucket-size` and `--firehose-rate-limit-bucket-fill-rate` to manage concurrent connection attempts to Firehose, check `fire{chain} start --help` for details. + +## v0.1.0 + +#### Backend Changes + +* Fixed Substreams accepted block which was not working properly. diff --git a/firehose/firehose-core/CONTRIBUTING.md b/firehose/firehose-core/CONTRIBUTING.md new file mode 100644 index 0000000..bae2498 --- /dev/null +++ b/firehose/firehose-core/CONTRIBUTING.md @@ -0,0 +1,127 @@ +# Contributing + +Interested in contributing? That's awesome! + +## Reporting An Issue + +If you're about to raise an issue because you think you've found a problem, or you'd like to make a request for a new feature in the codebase, or any other reason... please read this first. + +The GitHub issue tracker is the preferred channel for [bug reports](#bug-reports), [feature requests](#feature-requests), and [submitting pull requests](#submitting-pull-requests), but please respect the following restrictions: + +* Please **search for existing issues**. Help us keep duplicate issues to a minimum by checking to see if someone has already reported your problem or requested your idea. + +* Please **be civil**. Keep the discussion on topic and respect the opinions of others. See also our [Contributor Code of Conduct](#conduct). + +### Bug Reports + +A bug is a _demonstrable problem_ that is caused by the code in the repository. Good bug reports are extremely helpful - thank you! + +Guidelines for bug reports: + +1. **Use the GitHub issue search** — check if the issue has already been reported. +1. **Check if the issue has been fixed** — closed issues in the current milestone or try to reproduce it using the latest `develop` branch. + +A good bug report shouldn't leave others needing to chase you up for more information. Be sure to include the details of your environment and relevant tests that demonstrate the failure. + +### Feature Requests + +Feature requests are welcome. Before you submit one be sure to have: + +1. **Use the GitHub search** and check the feature hasn't already been requested. +1. Take a moment to think about whether your idea fits with the scope and aims of the project. +1. Remember, it's up to *you* to make a strong case to convince the project's leaders of the merits of this feature. Please provide as much detail and context as possible, this means explaining the use case and why it is likely to be common. + +### Change Requests + +Change requests cover both architectural and functional changes. If you have an idea for a new or different dependency, a refactor, or an improvement to a feature, etc - please be sure to: + +1. **Use the GitHub search** and check someone else didn't get there first +1. Take a moment to think about the best way to make a case for, and explain what you're thinking. Are you sure this shouldn't really be + a [bug report](#bug-reports) or a [feature request](#feature-requests)? Is it really one idea or is it many? What's the context? What problem are you solving? Why is what you are suggesting better than what's already there? + +## Working on the Code + +Code contributions are welcome and encouraged! Please follow these guidelines when submitting code: + +### Branching Conventions + +In all repositories: + +- **develop** is the development branch. All work on the next release happens here so you should generally branch off `develop`. Do **not** use this branch for a production site. +- **master** contains the latest releases. This branch may be used in production. Do **not** use this branch to work on the source code. +- **feature/something** contains feature branches where collaboration can exist, and is sync (rebased or merges) with `develop` from time to time. +- **hotfix/something** for hotfixes. + +This generally reflects the seminal _git flow_. + +### Submitting Pull Requests + +Pull requests are awesome. If you're looking to raise a PR for something which doesn't have an open issue, please think carefully about [raising an issue](#reporting-an-issue) which your PR can close, especially if you're fixing a bug. This makes it more likely that there will be enough information available for your PR to be properly tested and merged. + +### Testing and Quality Assurance + +Never underestimate just how useful quality assurance is. If you're looking to get involved with the code base and don't know where to start, checking out and testing a pull request is one of the most useful things you could do. + +Essentially, [check out the latest develop branch](#working-on-the-code), take it for a spin, and if you find anything odd, please follow the [bug report guidelines](#bug-reports) and let us know! + +## Code of Conduct + +While contributing, please be respectful and constructive, so that participation in our project is a positive experience for everyone. + +Examples of behavior that contributes to creating a positive environment include: +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior include: +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others’ private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Contributor License & Acknowledgments + +Whenever you make a contribution to this project, you license your contribution under the same terms as set out in [LICENSE](./LICENSE), and you represent and warrant that you have the right to license your contribution under those terms. Whenever you make a contribution to this project, you also certify in the terms of the Developer’s Certificate of Origin set out below: + +``` +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. +1 Letterman Drive +Suite D4700 +San Francisco, CA, 94129 + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` diff --git a/firehose/firehose-core/Dockerfile b/firehose/firehose-core/Dockerfile new file mode 100644 index 0000000..3f03af0 --- /dev/null +++ b/firehose/firehose-core/Dockerfile @@ -0,0 +1,28 @@ +FROM golang:1.22-alpine as build +WORKDIR /app + +COPY go.mod go.sum ./ +RUN go mod download + +COPY . ./ + +ARG VERSION="dev" +RUN apk --no-cache add git +RUN go build -v -ldflags "-X main.version=${VERSION}" ./cmd/firecore + +#### + +FROM alpine:3 + + +RUN apk --no-cache add \ + ca-certificates htop iotop sysstat \ + strace lsof curl jq tzdata + +RUN mkdir -p /app/ && curl -Lo /app/grpc_health_probe https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.4.12/grpc_health_probe-linux-amd64 && chmod +x /app/grpc_health_probe + +WORKDIR /app + +COPY --from=build /app/firecore /app/firecore + +ENTRYPOINT [ "/app/firecore" ] diff --git a/firehose/firehose-fuel/LICENSE b/firehose/firehose-core/LICENSE similarity index 100% rename from firehose/firehose-fuel/LICENSE rename to firehose/firehose-core/LICENSE diff --git a/firehose/firehose-core/README.md b/firehose/firehose-core/README.md new file mode 100644 index 0000000..55e702d --- /dev/null +++ b/firehose/firehose-core/README.md @@ -0,0 +1,36 @@ +## Firehose Integrators Tool Kit + +This repository contains all the code that is required to maintain the Golang part of the Firehose stack for chain integrators. This repository can be seen as an Integrator Tool Kit for developers maintaining Firehose for a specific chain. It's essentially a chain-agnostic shared library that is used to avoid duplication across all projects and ease maintenance work for the teams. It contains **no** chain-specific code and everything that is chain-specific must be provided. + +> **Note** This repository is **only** useful for maintainers of `firehose-` repositories and new integrators looking to integrate Firehose into a new chain. If you are a developer using Firehose or Substreams technology, this repository is not for you. + +### Philosophy + +Firehose maintenance cost comes from two sides. First, there is the chain integration that needs to be maintained. This is done within the chain's code directly by the chain's core developers. The second side of things is the maintenance of the Golang part of the Firehose stack. + +Each chain creates its own Firehose Golang repository named `firehose-`. [Firehose-acme repository](https://github.com/streamingfast/firehose-core/firehose-acme) acts as an example of this. Firehose is composed of multiple smaller components that can be run independently and each of them has a set of CLI flags and other configuration parameters. + +The initial "Acme" template we had contained a lot of boilerplate code to properly configure and run the Firehose Golang stack. This meant that if we needed to add a new feature that required a new flag or change a flag default value or any kind of improvements, chain integrators that were maintaining their `firehose-` repository were in the obligation of tracking changes made in `firehose-acme` and apply those back on their repository by hand. + +This was true also for continuously tracking updates to the various small libraries that form the Firehose stack. With Firehose starting to get more and more streamlined across different chains, that was a recipe for a maintenance hell for every chain integration. + +This repository aims at solving this maintenance burden by acting as a facade for all the Golang code required to have a functional and up-to-date Firehose stack. This way, we maintain the `firehose-core` project, adding/changing/removing flags, bumping dependencies, and adding new features, while you, as a maintainer of `firehose-` repository, simply need to track `firehose-core` for new releases and bump a single dependency to be up to date with the latest changes. + +### Changelog + +The [CHANGELOG.md](./CHANGELOG.md) of this project is written in such way so that you can copy-paste recent changes straight into your own release notes so that operators that are using your `firehose-` repository are made aware of deprecation notes, removal, changes and other important elements. + +Maintainers, you should copy/paste content of this content straight to your project. It is written and meant to be copied over to your project. If you were at `firehose-core` version `1.0.0` and are bumping to `1.1.0`, you should copy the content between those 2 version to your own repository, replacing placeholder value `fire{chain}` with your chain's own binary. + +The bash command `awk '/## v0.1.11/,/## v0.1.8/' CHANGELOG.md | grep -v '## v0.1.8'` to obtain the content between 2 versions. You can then merged the different `Added`, `Changed`, `Removed` and others into single merged section. + +> [!NOTE] +> At some point will provide a tool to easily generate the change for you so you can simply copy it over. + +### Update + +When bumping `firehose-core` to a breaking version, details of such upgrade will be described in [UPDATE.md](./UPDATE.md). Breaking version can be be noticed currently if the minor version is bumped up, for example going from v0.1.11 to v0.2.0 introduces some breaking changes. Once we will release the very first major version 1, breaking changes will be when going from v0.y.z to v1.0.0. + +### Build & CI + +The build and CI files are maintained for now in https://github.com/streamingfast/firehose-acme directly and should be updated manually from time to time from there. diff --git a/firehose/firehose-core/UPDATE.md b/firehose/firehose-core/UPDATE.md new file mode 100644 index 0000000..e2bb681 --- /dev/null +++ b/firehose/firehose-core/UPDATE.md @@ -0,0 +1,111 @@ +# Update + +## v1.2.5 + +If you were using `github.com/streamingfast/firehose-core/firehose/client.NewFirehoseFetchClient` or `github.com/streamingfast/firehose-core/firehose/client.NewFirehoseClient`, this will be a minor breaking change, the constructor now returns `callOpts` which should be passed to all request you make, see usage in [resp, err := firehoseClient.Block(ctx, req, requestInfo.GRPCCallOpts...)](https://github.com/streamingfast/firehose-core/blob/6c20f33f74be1abe58fbded1c178223702dcd6dd/cmd/tools/firehose/single_block_client.go#L74-L75) and populating `requestInfo.GRPCCallOpts` from `NewFirehoseFetchClient` function returns can be seen [here](https://github.com/streamingfast/firehose-core/blob/develop/cmd/tools/firehose/firehose.go#L71). + +## v1.0.0 + +No changes was required. + +## v0.2.0 + +This includes changes that are needed to upgrade to version v0.2.0. + +- The `nodemanager.GetLogLevelFunc` global override has been removed. If you had a global assignment of the form `nodemanager.GetLogLevelFunc = ...` you must removed it now. There is no replacement has the functionality this was used for has been removed. See the [version v0.2.0](./CHANGELOG.md#v020) section of the CHANGELOG for further details about the removal. + +- The `firecore.Config#ReaderNodeBootstrapperFactory` has change signature from `func(cmd *cobra.Command, nodeDataDir string) (operator.Bootstrapper, error)` to ` ReaderNodeBootstrapperFactory func(ctx context.Context, logger *zap.Logger, cmd *cobra.Command, resolvedNodeArguments []string, resolver ReaderNodeArgumentResolver) (operator.Bootstrapper, error)`. The new signature provides more data than before namely you get a `context.Context` for long running operation, the `logger` instance for this app if you need to log stuff, the `cmd` if you need to extract flags, the currently fully resolved arguments via `resolvedNodeArguments` and also a `resolver` function so you can perform further replacement on your own custom flags if needed. If you were using `nodeDataDir` value before, it's easy to get it back with `nodeDataDir := resolver("{node-data-dir}")`. + + From: + + ```go + func newReaderNodeBootstrapper(cmd *cobra.Command, nodeDataDir string) (operator.Bootstrapper, error) { + ... + } + ``` + + To + + ```go + func newReaderNodeBootstrapper(ctx context.Context, + logger *zap.Logger, + cmd *cobra.Command, + resolvedNodeArguments []string, + resolver firecore.ReaderNodeArgumentResolver, + ) (operator.Bootstrapper, error) { + nodeDataDir := resolver("{node-data-dir}") + + ... + } + ``` + +- The `firecore.Block` has a new method that you **must** implement: `GetFirehoseBlockVersion() int32`. Implementing like: + + ```go + func (b *Block) GetFirehoseBlockVersion() int32 { + return N + } + ``` + + Where `N` is the value your have currently as `ProtocolVersion` in your `Chain` config. You should **not** try to use `Chain.ProtocolVersion` directly and you must instead "hard-code" it for now. Those two values were tied together in `firecore <= v0.1.z` which was wrong so since they don't represent the same thing (one is used for the low-level file format `dbin` while the other is used in `bstream.PayloadVersion`). + +- The `firecore.Block` interface method `GetFirehoseBlockLIBNum` has been removed from the core interface `firecore.Block` and has been moved instead to an optional interface `firecore.BlockLIBNumDerivable`. This was needed because not all chain convey the LIBNum within the `Block` model itself. This is not a breaking change and requires no change on your part. This was done to accomodate Ethereum and probably others. + +- The `firecore.NewGenericBlockEncoder(protocolVersion int32)` has been renamed and changed signature, you should now use simply `firecore.NewBlockEncoder()` + +- The way tools transform flags are registered changed to become more generic. The `firecore.ToolsConfig#TransformFlags` changed it's definition completely. If you had `firecore.Config#Tools { TransformFlags: ... }` defined, you must change a bit how you define it. You will now manually define the flags and the parser will change to parse all of the flags instead of one flag at a time. + +Here the example that was applied to `firehose-near` `firecore.Chain` config: + + From: + + ```go + TransformFlags: map[string]*firecore.TransformFlag{ + "receipt-account-filters": { + Description: "Comma-separated accounts to use as filter/index. If it contains a colon (:), it will be interpreted as : (each of which can be empty, ex: 'hello:' or ':world')", + Parser: parseReceiptAccountFilters, + }, + }, + ``` + + To: + + ```go + TransformFlags: &firecore.TransformFlags{ + Register: func(flags *pflag.FlagSet) { + flags.String("receipt-account-filters", "", "Comma-separated accounts to use as filter/index. If it contains a colon (:), it will be interpreted as : (each of which can be empty, ex: 'hello:' or ':world')") + }, + + Parse: parseReceiptAccountFilters, + }, + ``` + + > [!NOTE] + > Notice the rename from `Parser` to `Parse`! + + And the `parseReceiptAccountFilters` needs some update too: + + From: + + ```go + func parseReceiptAccountFilters(in string) (*anypb.Any, error) { + ... + + return anypb.New(filters) + } + ``` + + To: + + ```go + func parseReceiptAccountFilters(cmd *cobra.Command, logger *zap.Logger) ([]*anypb.Any, error) { + in := sflags.MustGetString(cmd, "receipt-filter-accounts") + + ... + + any, err := anypb.New(filters) + // Deal with error + + return []*anypb.Any{any}, err + } + ``` \ No newline at end of file diff --git a/firehose/firehose-core/battlefield/battlefield.go b/firehose/firehose-core/battlefield/battlefield.go new file mode 100644 index 0000000..5f5bc18 --- /dev/null +++ b/firehose/firehose-core/battlefield/battlefield.go @@ -0,0 +1,32 @@ +package battlefield + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + . "github.com/streamingfast/cli" +) + +func BattlefieldCmd(binaryName string, config *Config) cli.CommandOption { + return Group( + "battlefield", + "Battlefield regression tests commands", + Command( + runE, + "run", + "Run battlefield regression test suite against oracle", + RangeArgs(0, 1), + ), + ) +} + +func runE(cmd *cobra.Command, args []string) error { + variant := "" + if len(args) > 0 { + variant = args[0] + } + + fmt.Println("Variant", variant) + return nil +} diff --git a/firehose/firehose-core/battlefield/compare_blocks.go b/firehose/firehose-core/battlefield/compare_blocks.go new file mode 100644 index 0000000..7624e6a --- /dev/null +++ b/firehose/firehose-core/battlefield/compare_blocks.go @@ -0,0 +1,80 @@ +package battlefield + +import ( + "encoding/json" + "fmt" + "os" + "os/exec" + + "github.com/streamingfast/cli" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +func CompareBlockFiles(referenceBlockFile, otherBlockFile string, processFileContent func(cntA, cntB []byte) (interface{}, interface{}, error), logger *zap.Logger) (bool, error) { + logger.Info("comparing block files", + zap.String("reference_block_file", referenceBlockFile), + zap.String("other_block_file", otherBlockFile), + ) + + refCnt, err := os.ReadFile(referenceBlockFile) + if err != nil { + return false, fmt.Errorf("unable to read block file %q: %w", referenceBlockFile, err) + } + + otherCnt, err := os.ReadFile(otherBlockFile) + if err != nil { + return false, fmt.Errorf("unable to read block file %q: %w", otherBlockFile, err) + } + + var refBlocksJsonInterface, otherBlocksJsonInterface interface{} + if processFileContent == nil { + if err = json.Unmarshal(refCnt, &refBlocksJsonInterface); err != nil { + return false, fmt.Errorf("unable to unmarshal block %q: %w", referenceBlockFile, err) + } + + if err = json.Unmarshal(otherCnt, &otherBlocksJsonInterface); err != nil { + return false, fmt.Errorf("unable to unmarshal block %q: %w", otherBlockFile, err) + } + } else { + refBlocksJsonInterface, otherBlocksJsonInterface, err = processFileContent(refCnt, otherCnt) + if err != nil { + return false, fmt.Errorf("failed to process blocks content file: %w", err) + } + } + + if assert.ObjectsAreEqualValues(refBlocksJsonInterface, otherBlocksJsonInterface) { + fmt.Println("Files are equal, all good") + return true, nil + } + + useBash := true + command := fmt.Sprintf("diff -C 5 \"%s\" \"%s\" | less", referenceBlockFile, otherBlockFile) + if os.Getenv("DIFF_EDITOR") != "" { + command = fmt.Sprintf("%s \"%s\" \"%s\"", os.Getenv("DIFF_EDITOR"), referenceBlockFile, otherBlockFile) + } + + showDiff, wasAnswered := cli.AskConfirmation(`File %q and %q differs, do you want to see the difference now`, referenceBlockFile, otherBlockFile) + if wasAnswered && showDiff { + diffCmd := exec.Command(command) + if useBash { + diffCmd = exec.Command("bash", "-c", command) + } + + diffCmd.Stdout = os.Stdout + diffCmd.Stderr = os.Stderr + + if err := diffCmd.Run(); err != nil { + return false, fmt.Errorf("diff command failed to run properly") + } + + fmt.Println("You can run the following command to see it manually later:") + } else { + fmt.Println("Not showing diff between files, run the following command to see it manually:") + } + + fmt.Println() + fmt.Printf(" %s\n", command) + fmt.Println("") + return false, nil +} diff --git a/firehose/firehose-core/battlefield/config.go b/firehose/firehose-core/battlefield/config.go new file mode 100644 index 0000000..67ec8bd --- /dev/null +++ b/firehose/firehose-core/battlefield/config.go @@ -0,0 +1,4 @@ +package battlefield + +type Config struct { +} diff --git a/firehose/firehose-core/blockpoller/cursor.go b/firehose/firehose-core/blockpoller/cursor.go new file mode 100644 index 0000000..a76f3dc --- /dev/null +++ b/firehose/firehose-core/blockpoller/cursor.go @@ -0,0 +1,74 @@ +package blockpoller + +import ( + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "go.uber.org/zap" +) + +type State string + +const ( + ContinuousSegState State = "CONTINUOUS" + IncompleteSegState State = "INCOMPLETE" +) + +func (s State) String() string { + return string(s) +} + +type cursor struct { + currentBlk bstream.BlockRef + currentIncompleteSeg *bstream.BasicBlockRef + state State + logger *zap.Logger +} + +func (s *cursor) addBlk(blk *pbbstream.Block, blockSeen bool, parentSeen bool) { + blkRef := blk.AsRef() + logger := s.logger.With( + zap.Stringer("blk", blkRef), + zap.Stringer("parent_blk", blk.PreviousRef()), + zap.Bool("seen_blk", blockSeen), + zap.Bool("seen_parent", parentSeen), + zap.Stringer("previous_state", s.state), + ) + if s.currentIncompleteSeg != nil { + logger = logger.With(zap.Stringer("current_incomplete_seg", *s.currentIncompleteSeg)) + } else { + logger = logger.With(zap.String("current_incomplete_seg", "none")) + + } + + if s.state == IncompleteSegState && blockSeen && parentSeen { + // if we are checking an incomplete segement, and we get a block that is already in the forkdb + // and whose parent is also in the forkdb, then we are back on a continuous segment + s.state = ContinuousSegState + } + s.currentBlk = blkRef + logger.Debug("received block", zap.Stringer("current_state", s.state)) +} + +func (s *cursor) getBlkSegmentNum() bstream.BlockRef { + if s.state == IncompleteSegState { + if s.currentIncompleteSeg == nil { + panic("current incomplete segment is nil, when cursor is incomplete segment, this should never happen") + } + return *s.currentIncompleteSeg + } + return s.currentBlk +} + +func (s *cursor) blkIsConnectedToLib() { + s.state = ContinuousSegState + s.currentIncompleteSeg = nil +} + +func (s *cursor) blkIsNotConnectedToLib() { + if s.state != IncompleteSegState { + s.state = IncompleteSegState + // we don't want to point the current blk since that will change + v := bstream.NewBlockRef(s.currentBlk.ID(), s.currentBlk.Num()) + s.currentIncompleteSeg = &v + } +} diff --git a/firehose/firehose-core/blockpoller/fetcher.go b/firehose/firehose-core/blockpoller/fetcher.go new file mode 100644 index 0000000..46d9c90 --- /dev/null +++ b/firehose/firehose-core/blockpoller/fetcher.go @@ -0,0 +1,12 @@ +package blockpoller + +import ( + "context" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" +) + +type BlockFetcher interface { + IsBlockAvailable(requestedSlot uint64) bool + Fetch(ctx context.Context, blkNum uint64) (b *pbbstream.Block, skipped bool, err error) +} diff --git a/firehose/firehose-core/blockpoller/handler.go b/firehose/firehose-core/blockpoller/handler.go new file mode 100644 index 0000000..a39ecce --- /dev/null +++ b/firehose/firehose-core/blockpoller/handler.go @@ -0,0 +1,57 @@ +package blockpoller + +import ( + "encoding/base64" + "fmt" + "strings" + "sync" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" +) + +type BlockHandler interface { + Init() + Handle(blk *pbbstream.Block) error +} + +var _ BlockHandler = (*FireBlockHandler)(nil) + +type FireBlockHandler struct { + blockTypeURL string + init sync.Once +} + +func NewFireBlockHandler(blockTypeURL string) *FireBlockHandler { + return &FireBlockHandler{ + blockTypeURL: clean(blockTypeURL), + } +} + +func (f *FireBlockHandler) Init() { + fmt.Println("FIRE INIT 3.0", f.blockTypeURL) +} + +func (f *FireBlockHandler) Handle(b *pbbstream.Block) error { + typeURL := clean(b.Payload.TypeUrl) + if typeURL != f.blockTypeURL { + return fmt.Errorf("block type url %q does not match expected type %q", typeURL, f.blockTypeURL) + } + + blockLine := fmt.Sprintf( + "FIRE BLOCK %d %s %d %s %d %d %s", + b.Number, + b.Id, + b.ParentNum, + b.ParentId, + b.LibNum, + b.Timestamp.AsTime().UnixNano(), + base64.StdEncoding.EncodeToString(b.Payload.Value), + ) + + fmt.Println(blockLine) + return nil +} + +func clean(in string) string { + return strings.Replace(in, "type.googleapis.com/", "", 1) +} diff --git a/firehose/firehose-core/blockpoller/handler_test.go b/firehose/firehose-core/blockpoller/handler_test.go new file mode 100644 index 0000000..81f3843 --- /dev/null +++ b/firehose/firehose-core/blockpoller/handler_test.go @@ -0,0 +1,24 @@ +package blockpoller + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFireBlockHandler_clean(t *testing.T) { + tests := []struct { + in string + expect string + }{ + {"type.googleapis.com/sf.bstream.v2.Block", "sf.bstream.v2.Block"}, + {"sf.bstream.v2.Block", "sf.bstream.v2.Block"}, + } + + for _, test := range tests { + t.Run(test.in, func(t *testing.T) { + assert.Equal(t, test.expect, clean(test.in)) + }) + } + +} diff --git a/firehose/firehose-core/blockpoller/init_test.go b/firehose/firehose-core/blockpoller/init_test.go new file mode 100644 index 0000000..75b5545 --- /dev/null +++ b/firehose/firehose-core/blockpoller/init_test.go @@ -0,0 +1,123 @@ +package blockpoller + +import ( + "context" + "fmt" + "testing" + "time" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/derr" + "github.com/streamingfast/logging" + "github.com/stretchr/testify/assert" + "github.com/test-go/testify/require" + "go.uber.org/zap/zapcore" +) + +var logger, tracer = logging.PackageLogger("forkhandler", "github.com/streamingfast/firehose-core/forkhandler.test") + +func init() { + logging.InstantiateLoggers(logging.WithDefaultLevel(zapcore.DebugLevel)) +} + +var TestErrCompleteDone = fmt.Errorf("test complete done") + +type TestBlock struct { + expect *pbbstream.Block + send *pbbstream.Block +} + +var _ BlockFetcher = &TestBlockFetcher{} + +type TestBlockFetcher struct { + t *testing.T + blocks []*TestBlock + idx uint64 + completed bool +} + +func newTestBlockFetcher(t *testing.T, blocks []*TestBlock) *TestBlockFetcher { + return &TestBlockFetcher{ + t: t, + blocks: blocks, + } +} + +func (b *TestBlockFetcher) PollingInterval() time.Duration { + return 0 +} + +func (b *TestBlockFetcher) IsBlockAvailable(requestedSlot uint64) bool { + return true +} + +func (b *TestBlockFetcher) Fetch(_ context.Context, blkNum uint64) (*pbbstream.Block, bool, error) { + if len(b.blocks) == 0 { + assert.Fail(b.t, fmt.Sprintf("should not have fetched block %d", blkNum)) + } + + if b.idx >= uint64(len(b.blocks)) { + return nil, false, derr.NewFatalError(TestErrCompleteDone) + } + + if blkNum != b.blocks[b.idx].expect.Number { + assert.Fail(b.t, fmt.Sprintf("expected to fetch block %d, got %d", b.blocks[b.idx].expect.Number, blkNum)) + } + + blkToSend := b.blocks[b.idx].send + b.idx++ + return blkToSend, false, nil +} + +func (b *TestBlockFetcher) check(t *testing.T) { + t.Helper() + require.Equal(b.t, uint64(len(b.blocks)), b.idx, "we should have fetched all %d blocks, only fired %d blocks", len(b.blocks), b.idx) +} + +var _ BlockHandler = &TestBlockFinalizer{} + +type TestBlockFinalizer struct { + t *testing.T + fireBlocks []*pbbstream.Block + idx uint64 +} + +func newTestBlockFinalizer(t *testing.T, fireBlocks []*pbbstream.Block) *TestBlockFinalizer { + return &TestBlockFinalizer{ + t: t, + fireBlocks: fireBlocks, + } +} + +func (t *TestBlockFinalizer) Init() { + //TODO implement me + panic("implement me") +} + +func (t *TestBlockFinalizer) Handle(blk *pbbstream.Block) error { + if len(t.fireBlocks) == 0 { + assert.Fail(t.t, fmt.Sprintf("should not have fired block %s", blk.AsRef())) + } + + if t.idx >= uint64(len(t.fireBlocks)) { + return TestErrCompleteDone + } + + if blk.Number != t.fireBlocks[t.idx].Number { + assert.Fail(t.t, fmt.Sprintf("expected to fetch block %d, got %d", t.fireBlocks[t.idx].Number, blk.Number)) + } + t.idx++ + return nil +} + +func (b *TestBlockFinalizer) check(t *testing.T) { + t.Helper() + require.Equal(b.t, uint64(len(b.fireBlocks)), b.idx, "we should have fired all %d blocks, only fired %d blocks", len(b.fireBlocks), b.idx) +} + +var _ BlockHandler = &TestNoopBlockFinalizer{} + +type TestNoopBlockFinalizer struct{} + +func (t *TestNoopBlockFinalizer) Init() {} +func (t *TestNoopBlockFinalizer) Handle(blk *pbbstream.Block) error { return nil } diff --git a/firehose/firehose-core/blockpoller/options.go b/firehose/firehose-core/blockpoller/options.go new file mode 100644 index 0000000..057512d --- /dev/null +++ b/firehose/firehose-core/blockpoller/options.go @@ -0,0 +1,31 @@ +package blockpoller + +import "go.uber.org/zap" + +type Option func(*BlockPoller) + +func WithBlockFetchRetryCount(v uint64) Option { + return func(p *BlockPoller) { + p.fetchBlockRetryCount = v + } +} + +func WithStoringState(stateStorePath string) Option { + return func(p *BlockPoller) { + p.stateStorePath = stateStorePath + } +} + +// IgnoreCursor ensures the poller will ignore the cursor and start from the startBlockNum +// the cursor will still be saved as the poller progresses +func IgnoreCursor() Option { + return func(p *BlockPoller) { + p.ignoreCursor = true + } +} + +func WithLogger(logger *zap.Logger) Option { + return func(p *BlockPoller) { + p.logger = logger + } +} diff --git a/firehose/firehose-core/blockpoller/poller.go b/firehose/firehose-core/blockpoller/poller.go new file mode 100644 index 0000000..08198f2 --- /dev/null +++ b/firehose/firehose-core/blockpoller/poller.go @@ -0,0 +1,355 @@ +package blockpoller + +import ( + "context" + "fmt" + "math" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/derr" + "github.com/streamingfast/dhammer" + "github.com/streamingfast/firehose-core/internal/utils" + "github.com/streamingfast/shutter" + "go.uber.org/zap" +) + +type block struct { + *pbbstream.Block + fired bool +} + +func newBlock(block2 *pbbstream.Block) *block { + return &block{block2, false} +} + +type BlockPoller struct { + *shutter.Shutter + startBlockNumGate uint64 + fetchBlockRetryCount uint64 + stateStorePath string + ignoreCursor bool + forceFinalityAfterBlocks *uint64 + + blockFetcher BlockFetcher + blockHandler BlockHandler + forkDB *forkable.ForkDB + + logger *zap.Logger + + optimisticallyPolledBlocks map[uint64]*BlockItem +} + +func New( + blockFetcher BlockFetcher, + blockHandler BlockHandler, + opts ...Option, +) *BlockPoller { + + b := &BlockPoller{ + Shutter: shutter.New(), + blockFetcher: blockFetcher, + blockHandler: blockHandler, + fetchBlockRetryCount: math.MaxUint64, + logger: zap.NewNop(), + forceFinalityAfterBlocks: utils.GetEnvForceFinalityAfterBlocks(), + } + + for _, opt := range opts { + opt(b) + } + + return b +} + +func (p *BlockPoller) Run(ctx context.Context, startBlockNum uint64, blockFetchBatchSize int) error { + p.startBlockNumGate = startBlockNum + p.logger.Info("starting poller", + zap.Uint64("start_block_num", startBlockNum), + zap.Uint64("block_fetch_batch_size", uint64(blockFetchBatchSize)), + ) + p.blockHandler.Init() + + for { + startBlock, skip, err := p.blockFetcher.Fetch(ctx, startBlockNum) + if err != nil { + return fmt.Errorf("unable to fetch start block %d: %w", startBlockNum, err) + } + if skip { + startBlockNum++ + continue + } + return p.run(startBlock.AsRef(), blockFetchBatchSize) + } +} + +func (p *BlockPoller) run(resolvedStartBlock bstream.BlockRef, numberOfBlockToFetch int) (err error) { + p.forkDB, resolvedStartBlock, err = initState(resolvedStartBlock, p.stateStorePath, p.ignoreCursor, p.logger) + if err != nil { + return fmt.Errorf("unable to initialize cursor: %w", err) + } + + currentCursor := &cursor{state: ContinuousSegState, logger: p.logger} + blockToFetch := resolvedStartBlock.Num() + var hashToFetch *string + for { + if p.IsTerminating() { + p.logger.Info("block poller is terminating") + } + + p.logger.Info("about to fetch block", zap.Uint64("block_to_fetch", blockToFetch)) + var fetchedBlock *pbbstream.Block + if hashToFetch != nil { + fetchedBlock, err = p.fetchBlockWithHash(blockToFetch, *hashToFetch) + } else { + fetchedBlock, err = p.fetchBlock(blockToFetch, numberOfBlockToFetch) + } + + if err != nil { + return fmt.Errorf("unable to fetch block %d: %w", blockToFetch, err) + } + + blockToFetch, hashToFetch, err = p.processBlock(currentCursor, fetchedBlock) + if err != nil { + return fmt.Errorf("unable to fetch block %d: %w", blockToFetch, err) + } + + if p.IsTerminating() { + p.logger.Info("block poller is terminating") + } + } +} + +func (p *BlockPoller) processBlock(currentState *cursor, block *pbbstream.Block) (uint64, *string, error) { + p.logger.Info("processing block", zap.Stringer("block", block.AsRef()), zap.Uint64("lib_num", block.LibNum)) + if block.Number < p.forkDB.LIBNum() { + panic(fmt.Errorf("unexpected error block %d is below the current LIB num %d. There should be no re-org above the current LIB num", block.Number, p.forkDB.LIBNum())) + } + + // On the first run, we will fetch the blk for the `startBlockRef`, since we have a `Ref` it stands + // to reason that we may already have the block. We could potentially optimize this + + seenBlk, seenParent := p.forkDB.AddLink(block.AsRef(), block.ParentId, newBlock(block)) + + currentState.addBlk(block, seenBlk, seenParent) + + blkCompleteSegNum := currentState.getBlkSegmentNum() + completeSegment, reachLib := p.forkDB.CompleteSegment(blkCompleteSegNum) + p.logger.Debug("checked if block is complete segment", + zap.Uint64("blk_num", blkCompleteSegNum.Num()), + zap.Int("segment_len", len(completeSegment)), + zap.Bool("reached_lib", reachLib), + ) + + if reachLib { + currentState.blkIsConnectedToLib() + err := p.fireCompleteSegment(completeSegment) + if err != nil { + return 0, nil, fmt.Errorf("firing complete segment: %w", err) + } + + // since the block is linkable to the current lib + // we can safely set the new lib to the current block's Lib + // the assumption here is that teh Lib the Block we received from the block fetcher ir ALWAYS CORRECT + p.logger.Debug("setting lib", zap.Stringer("blk", block.AsRef()), zap.Uint64("lib_num", block.LibNum)) + p.forkDB.SetLIB(block.AsRef(), block.LibNum) + p.forkDB.PurgeBeforeLIB(0) + + err = p.saveState(completeSegment) + if err != nil { + return 0, nil, fmt.Errorf("saving state: %w", err) + } + + nextBlockNum := nextBlkInSeg(completeSegment) + return nextBlockNum, nil, nil + } + + currentState.blkIsNotConnectedToLib() + + prevBlockNum, prevBlockHash := prevBlockInSegment(completeSegment) + return prevBlockNum, prevBlockHash, nil +} + +type BlockItem struct { + blockNumber uint64 + block *pbbstream.Block + skipped bool +} + +func (p *BlockPoller) loadNextBlocks(requestedBlock uint64, numberOfBlockToFetch int) error { + p.optimisticallyPolledBlocks = map[uint64]*BlockItem{} + + nailer := dhammer.NewNailer(10, func(ctx context.Context, blockToFetch uint64) (*BlockItem, error) { + var blockItem *BlockItem + err := derr.Retry(p.fetchBlockRetryCount, func(ctx context.Context) error { + b, skip, err := p.blockFetcher.Fetch(ctx, blockToFetch) + if err != nil { + return fmt.Errorf("unable to fetch block %d: %w", blockToFetch, err) + } + if skip { + blockItem = &BlockItem{ + blockNumber: blockToFetch, + block: nil, + skipped: true, + } + return nil + } + //todo: add block to cache + blockItem = &BlockItem{ + blockNumber: blockToFetch, + block: b, + skipped: false, + } + return nil + + }) + + if err != nil { + return nil, fmt.Errorf("failed to fetch block with retries %d: %w", blockToFetch, err) + } + + return blockItem, err + }) + + ctx := context.Background() + nailer.Start(ctx) + + done := make(chan interface{}, 1) + go func() { + for blockItem := range nailer.Out { + p.optimisticallyPolledBlocks[blockItem.blockNumber] = blockItem + } + + close(done) + }() + + didTriggerFetch := false + for i := 0; i < numberOfBlockToFetch; i++ { + b := requestedBlock + uint64(i) + + //only fetch block if it is available on chain + if p.blockFetcher.IsBlockAvailable(b) { + p.logger.Info("optimistically fetching block", zap.Uint64("block_num", b)) + didTriggerFetch = true + nailer.Push(ctx, b) + } else { + //if this block is not available, we can assume that the next blocks are not available as well + break + } + } + + if !didTriggerFetch { + //if we did not trigger any fetch, we fetch the requested block + // Fetcher should return the block when available (this will be a blocking call until the block is available) + nailer.Push(ctx, requestedBlock) + } + + nailer.Close() + + <-done + + if nailer.Err() != nil { + return fmt.Errorf("failed optimistically fetch blocks starting at %d: %w", requestedBlock, nailer.Err()) + } + + return nil +} + +func (p *BlockPoller) fetchBlock(blockNumber uint64, numberOfBlockToFetch int) (*pbbstream.Block, error) { + for { + blockItem, found := p.optimisticallyPolledBlocks[blockNumber] + if !found { + err := p.loadNextBlocks(blockNumber, numberOfBlockToFetch) + if err != nil { + return nil, fmt.Errorf("failed to load next blocks: %w", err) + } + continue //that will retry the current block after loading the more blocks + } + if blockItem.skipped { + blockNumber++ + continue + } + + p.logger.Info("block was optimistically polled", zap.Uint64("block_num", blockNumber)) + return blockItem.block, nil + } +} + +func (p *BlockPoller) fetchBlockWithHash(blkNum uint64, hash string) (*pbbstream.Block, error) { + p.logger.Info("fetching block with hash", zap.Uint64("block_num", blkNum), zap.String("hash", hash)) + _ = hash //todo: hash will be used to fetch block from cache + + p.optimisticallyPolledBlocks = map[uint64]*BlockItem{} + + var out *pbbstream.Block + var skipped bool + err := derr.Retry(p.fetchBlockRetryCount, func(ctx context.Context) error { + //todo: get block from cache + var fetchErr error + out, skipped, fetchErr = p.blockFetcher.Fetch(ctx, blkNum) + if fetchErr != nil { + return fmt.Errorf("unable to fetch block %d: %w", blkNum, fetchErr) + } + if skipped { + return nil + } + return nil + }) + + if err != nil { + return nil, fmt.Errorf("failed to fetch block with retries %d: %w", blkNum, err) + } + + if skipped { + return nil, fmt.Errorf("block %d was skipped and sould not have been requested", blkNum) + } + + if p.forceFinalityAfterBlocks != nil { + utils.TweakBlockFinality(out, *p.forceFinalityAfterBlocks) + } + + return out, nil +} + +func (p *BlockPoller) fireCompleteSegment(blocks []*forkable.Block) error { + for _, blk := range blocks { + b := blk.Object.(*block) + if _, err := p.fire(b); err != nil { + return fmt.Errorf("fireing block %d (%qs) %w", blk.BlockNum, blk.BlockID, err) + } + } + return nil +} + +func (p *BlockPoller) fire(blk *block) (bool, error) { + if blk.fired { + return false, nil + } + + if blk.Number < p.startBlockNumGate { + return false, nil + } + + if err := p.blockHandler.Handle(blk.Block); err != nil { + return false, err + } + + blk.fired = true + return true, nil +} + +func nextBlkInSeg(blocks []*forkable.Block) uint64 { + if len(blocks) == 0 { + panic(fmt.Errorf("the blocks segments should never be empty")) + } + return blocks[len(blocks)-1].BlockNum + 1 +} + +func prevBlockInSegment(blocks []*forkable.Block) (uint64, *string) { + if len(blocks) == 0 { + panic(fmt.Errorf("the blocks segments should never be empty")) + } + blockObject := blocks[0].Object.(*block) + return blockObject.ParentNum, &blockObject.ParentId +} diff --git a/firehose/firehose-core/blockpoller/poller_test.go b/firehose/firehose-core/blockpoller/poller_test.go new file mode 100644 index 0000000..fbb0406 --- /dev/null +++ b/firehose/firehose-core/blockpoller/poller_test.go @@ -0,0 +1,256 @@ +package blockpoller + +import ( + "errors" + "fmt" + "strconv" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/stretchr/testify/assert" +) + +func TestForkHandler_run(t *testing.T) { + tests := []struct { + name string + startBlock bstream.BlockRef + blocks []*TestBlock + expectFireBlock []*pbbstream.Block + }{ + { + name: "start block 0", + startBlock: blk("0a", "", 0).AsRef(), + blocks: []*TestBlock{ + tb("0a", "", 0), + tb("1a", "0a", 0), + tb("2a", "1a", 0), + }, + expectFireBlock: []*pbbstream.Block{ + blk("0a", "", 0), + blk("1a", "0a", 0), + blk("2a", "1a", 0), + }, + }, + { + name: "Fork 1", + startBlock: blk("100a", "99a", 100).AsRef(), + blocks: []*TestBlock{ + tb("100a", "99a", 100), + tb("101a", "100a", 100), + tb("102a", "101a", 100), + tb("103a", "102a", 100), + tb("104b", "103b", 100), + tb("103a", "102a", 100), + tb("104a", "103a", 100), + tb("105b", "104b", 100), + tb("103b", "102b", 100), + tb("102b", "101a", 100), + tb("106a", "105a", 100), + tb("105a", "104a", 100), + }, + expectFireBlock: []*pbbstream.Block{ + blk("100a", "99a", 100), + blk("101a", "100a", 100), + blk("102a", "101a", 100), + blk("103a", "102a", 100), + blk("104a", "103a", 100), + blk("102b", "101a", 100), + blk("103b", "102b", 100), + blk("104b", "103b", 100), + blk("105b", "104b", 100), + blk("105a", "104a", 100), + blk("106a", "105a", 100), + }, + }, + { + name: "Fork 2", + startBlock: blk("100a", "99a", 100).AsRef(), + blocks: []*TestBlock{ + tb("100a", "99a", 100), + tb("101a", "100a", 100), + tb("102a", "101a", 100), + tb("103a", "102a", 100), + tb("104b", "103b", 100), + tb("103a", "102a", 100), + tb("104a", "103a", 100), + tb("105b", "104b", 100), + tb("103b", "102b", 100), + tb("102a", "101a", 100), + tb("103a", "104a", 100), + tb("104a", "105a", 100), + tb("105a", "104a", 100), + }, + expectFireBlock: []*pbbstream.Block{ + blk("100a", "99a", 100), + blk("101a", "100a", 100), + blk("102a", "101a", 100), + blk("103a", "102a", 100), + blk("104a", "103a", 100), + blk("105a", "104a", 100), + }, + }, + { + name: "with lib advancing", + startBlock: blk("100a", "99a", 100).AsRef(), + blocks: []*TestBlock{ + tb("100a", "99a", 100), + tb("101a", "100a", 100), + tb("102a", "101a", 100), + tb("103a", "102a", 101), + tb("104b", "103b", 101), + tb("103a", "102a", 101), + tb("104a", "103a", 101), + tb("105b", "104b", 101), + tb("103b", "102b", 101), + tb("102a", "101a", 101), + tb("103a", "104a", 101), + tb("104a", "105a", 101), + tb("105a", "104a", 101), + }, + expectFireBlock: []*pbbstream.Block{ + blk("100a", "99a", 100), + blk("101a", "100a", 100), + blk("102a", "101a", 100), + blk("103a", "102a", 100), + blk("104a", "103a", 100), + blk("105a", "104a", 100), + }, + }, + { + name: "with skipping blocks", + startBlock: blk("100a", "99a", 100).AsRef(), + blocks: []*TestBlock{ + tb("100a", "99a", 100), + tb("101a", "100a", 100), + tb("102a", "101a", 100), + tb("103a", "102a", 101), + tb("104b", "103b", 101), + tb("103a", "102a", 101), + tb("104a", "103a", 101), + tb("105b", "104b", 101), + tb("103b", "102b", 101), + tb("102a", "101a", 101), + tb("103a", "104a", 101), + tb("104a", "105a", 101), + tb("105a", "104a", 101), + }, + expectFireBlock: []*pbbstream.Block{ + blk("100a", "99a", 100), + blk("101a", "100a", 100), + blk("102a", "101a", 100), + blk("103a", "102a", 100), + blk("104a", "103a", 100), + blk("105a", "104a", 100), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + + blockFetcher := newTestBlockFetcher(t, tt.blocks) + blockFinalizer := newTestBlockFinalizer(t, tt.expectFireBlock) + + f := New(blockFetcher, blockFinalizer) + f.forkDB = forkable.NewForkDB() + + err := f.run(tt.startBlock, 1) + if !errors.Is(err, TestErrCompleteDone) { + require.NoError(t, err) + } + + blockFetcher.check(t) + blockFinalizer.check(t) + + }) + } +} + +func TestForkHandler_fire(t *testing.T) { + tests := []struct { + name string + block *block + startBlockNum uint64 + expect bool + }{ + { + name: "greater then start block", + block: &block{blk("100a", "99a", 98), false}, + startBlockNum: 98, + expect: true, + }, + { + name: "on then start block", + block: &block{blk("100a", "99a", 98), false}, + startBlockNum: 100, + expect: true, + }, + { + name: "less then start block", + block: &block{blk("100a", "99a", 98), false}, + startBlockNum: 101, + expect: false, + }, + { + name: "already fired", + block: &block{blk("100a", "99a", 98), true}, + startBlockNum: 98, + expect: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + poller := &BlockPoller{startBlockNumGate: test.startBlockNum, blockHandler: &TestNoopBlockFinalizer{}} + ok, err := poller.fire(test.block) + require.NoError(t, err) + assert.Equal(t, test.expect, ok) + }) + } + +} + +func tb(id, prev string, libNum uint64) *TestBlock { + return &TestBlock{ + expect: blk(id, prev, libNum), + send: blk(id, prev, libNum), + } +} + +func blk(id, prev string, libNum uint64) *pbbstream.Block { + return &pbbstream.Block{ + Number: blockNum(id), + Id: id, + ParentId: prev, + LibNum: libNum, + ParentNum: blockNum(prev), + } +} + +func forkBlk(id string) *forkable.Block { + return &forkable.Block{ + BlockID: id, + BlockNum: blockNum(id), + Object: &block{ + Block: &pbbstream.Block{ + Number: blockNum(id), + Id: id, + }, + }, + } +} + +func blockNum(blockID string) uint64 { + b := blockID + if len(blockID) < 8 { // shorter version, like 8a for 00000008a + b = fmt.Sprintf("%09s", blockID) + } + bin, err := strconv.ParseUint(b[:8], 10, 64) + if err != nil { + panic(err) + } + return bin +} diff --git a/firehose/firehose-core/blockpoller/state_file.go b/firehose/firehose-core/blockpoller/state_file.go new file mode 100644 index 0000000..a5b0ef8 --- /dev/null +++ b/firehose/firehose-core/blockpoller/state_file.go @@ -0,0 +1,142 @@ +package blockpoller + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" + "go.uber.org/zap" +) + +type blockRef struct { + Id string `json:"id"` + Num uint64 `json:"num"` +} + +type blockRefWithPrev struct { + blockRef + PrevBlockId string `json:"previous_ref_id"` +} + +func (b blockRef) String() string { + return fmt.Sprintf("%d (%s)", b.Num, b.Id) +} + +type stateFile struct { + Lib blockRef + LastFiredBlock blockRefWithPrev + Blocks []blockRefWithPrev +} + +func getState(stateStorePath string) (*stateFile, error) { + if stateStorePath == "" { + return nil, fmt.Errorf("no cursor store path set") + } + + filepath := filepath.Join(stateStorePath, "cursor.json") + file, err := os.Open(filepath) + if err != nil { + return nil, fmt.Errorf("unable to open cursor file %s: %w", filepath, err) + } + sf := stateFile{} + decoder := json.NewDecoder(file) + if err := decoder.Decode(&sf); err != nil { + return nil, fmt.Errorf("feailed to decode cursor file %s: %w", filepath, err) + } + return &sf, nil +} + +func (p *BlockPoller) saveState(blocks []*forkable.Block) error { + p.logger.Debug("saving cursor", zap.String("state_store_path", p.stateStorePath)) + if p.stateStorePath == "" { + return nil + } + + lastFiredBlock := blocks[len(blocks)-1] + + sf := stateFile{ + Lib: blockRef{p.forkDB.LIBID(), p.forkDB.LIBNum()}, + LastFiredBlock: blockRefWithPrev{blockRef{lastFiredBlock.BlockID, lastFiredBlock.BlockNum}, lastFiredBlock.PreviousBlockID}, + } + + for _, blk := range blocks { + sf.Blocks = append(sf.Blocks, blockRefWithPrev{blockRef{blk.BlockID, blk.BlockNum}, blk.PreviousBlockID}) + } + + cnt, err := json.Marshal(sf) + if err != nil { + return fmt.Errorf("unable to marshal stateFile: %w", err) + } + + err = os.MkdirAll(p.stateStorePath, os.ModePerm) + if err != nil { + return fmt.Errorf("making state store path: %w", err) + } + fpath := filepath.Join(p.stateStorePath, "cursor.json") + + if err := os.WriteFile(fpath, cnt, 0666); err != nil { + return fmt.Errorf("unable to open cursor file %s: %w", fpath, err) + } + + p.logger.Info("saved cursor", + zap.Reflect("filepath", fpath), + zap.Stringer("last_fired_block", sf.LastFiredBlock), + zap.Stringer("lib", sf.Lib), + zap.Int("block_count", len(sf.Blocks)), + ) + return nil +} + +func initState(resolvedStartBlock bstream.BlockRef, stateStorePath string, ignoreCursor bool, logger *zap.Logger) (*forkable.ForkDB, bstream.BlockRef, error) { + forkDB := forkable.NewForkDB(forkable.ForkDBWithLogger(logger)) + + useStartBlockFunc := func() (*forkable.ForkDB, bstream.BlockRef, error) { + forkDB.InitLIB(resolvedStartBlock) + return forkDB, resolvedStartBlock, nil + } + + if ignoreCursor { + logger.Info("ignorign cursor", + zap.Stringer("start_block", resolvedStartBlock), + zap.Stringer("lib", resolvedStartBlock), + ) + return useStartBlockFunc() + } + + sf, err := getState(stateStorePath) + if err != nil { + logger.Warn("unable to load cursor file, initializing a new forkdb", + zap.Stringer("start_block", resolvedStartBlock), + zap.Stringer("lib", resolvedStartBlock), + zap.Error(err), + ) + return useStartBlockFunc() + } + + forkDB.InitLIB(bstream.NewBlockRef(sf.Lib.Id, sf.Lib.Num)) + + for _, blk := range sf.Blocks { + b := &block{ + Block: &pbbstream.Block{ + Number: blk.Num, + Id: blk.Id, + ParentId: blk.PrevBlockId, + }, + fired: true, + } + forkDB.AddLink(bstream.NewBlockRef(blk.Id, blk.Num), blk.PrevBlockId, b) + } + + logger.Info("loaded cursor", + zap.Stringer("start_block", sf.LastFiredBlock), + zap.Stringer("lib", sf.Lib), + zap.Int("block_count", len(sf.Blocks)), + ) + + return forkDB, bstream.NewBlockRef(sf.LastFiredBlock.Id, sf.LastFiredBlock.Num), nil +} diff --git a/firehose/firehose-core/blockpoller/state_file_test.go b/firehose/firehose-core/blockpoller/state_file_test.go new file mode 100644 index 0000000..3e410c8 --- /dev/null +++ b/firehose/firehose-core/blockpoller/state_file_test.go @@ -0,0 +1,111 @@ +package blockpoller + +import ( + "os" + "path/filepath" + "testing" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +func TestFireBlockFinalizer_state(t *testing.T) { + dirName, err := os.MkdirTemp("", "fblk") + require.NoError(t, err) + defer os.Remove(dirName) + + fk := forkable.NewForkDB() + // simulating a flow where the lib libmoves + fk.SetLIB(bstream.NewBlockRef("100a", 100), 100) + fk.AddLink(bstream.NewBlockRef("101a", 101), "100a", &block{Block: blk("101a", "100a", 100)}) + fk.AddLink(bstream.NewBlockRef("102a", 102), "101a", &block{Block: blk("102a", "101a", 100)}) + fk.AddLink(bstream.NewBlockRef("103a", 103), "102a", &block{Block: blk("103a", "102a", 100)}) + fk.SetLIB(bstream.NewBlockRef("103a", 103), 101) + fk.AddLink(bstream.NewBlockRef("104b", 104), "103b", &block{Block: blk("104b", "103b", 101)}) + fk.AddLink(bstream.NewBlockRef("103a", 103), "102a", &block{Block: blk("103a", "102a", 101)}) + fk.AddLink(bstream.NewBlockRef("104a", 104), "103a", &block{Block: blk("104a", "103a", 101)}) + fk.AddLink(bstream.NewBlockRef("105b", 105), "104b", &block{Block: blk("105b", "104b", 101)}) + fk.AddLink(bstream.NewBlockRef("103b", 103), "102b", &block{Block: blk("103b", "102b", 101)}) + fk.AddLink(bstream.NewBlockRef("102b", 102), "101a", &block{Block: blk("102b", "101a", 101)}) + fk.AddLink(bstream.NewBlockRef("106a", 106), "105a", &block{Block: blk("106a", "105a", 101)}) + fk.AddLink(bstream.NewBlockRef("105a", 105), "104a", &block{Block: blk("105a", "104a", 101)}) + expectedBlocks, reachedLib := fk.CompleteSegment(blk("105a", "104a", 101).AsRef()) + // simulate firing the blocks + for _, blk := range expectedBlocks { + blk.Object.(*block).fired = true + } + assert.True(t, reachedLib) + require.Equal(t, 5, len(expectedBlocks)) + + expectedStateFileCnt := `{"Lib":{"id":"101a","num":101},"LastFiredBlock":{"id":"105a","num":105,"previous_ref_id":"104a"},"Blocks":[{"id":"101a","num":101,"previous_ref_id":"100a"},{"id":"102a","num":102,"previous_ref_id":"101a"},{"id":"103a","num":103,"previous_ref_id":"102a"},{"id":"104a","num":104,"previous_ref_id":"103a"},{"id":"105a","num":105,"previous_ref_id":"104a"}]}` + + poller := &BlockPoller{ + stateStorePath: dirName, + forkDB: fk, + logger: zap.NewNop(), + } + require.NoError(t, poller.saveState(expectedBlocks)) + + filePath := filepath.Join(dirName, "cursor.json") + cnt, err := os.ReadFile(filePath) + require.NoError(t, err) + assert.Equal(t, expectedStateFileCnt, string(cnt)) + + forkDB, startBlock, err := initState(bstream.NewBlockRef("60a", 60), dirName, false, zap.NewNop()) + require.NoError(t, err) + + blocks, reachedLib := forkDB.CompleteSegment(bstream.NewBlockRef("105a", 105)) + assert.True(t, reachedLib) + assertForkableBlocks(t, expectedBlocks, blocks) + assert.Equal(t, bstream.NewBlockRef("105a", 105), startBlock) + assert.Equal(t, "101a", forkDB.LIBID()) + assert.Equal(t, uint64(101), forkDB.LIBNum()) +} + +func TestFireBlockFinalizer_noSstate(t *testing.T) { + dirName, err := os.MkdirTemp("", "fblk") + require.NoError(t, err) + defer os.Remove(dirName) + + forkDB, startBlock, err := initState(bstream.NewBlockRef("60a", 60), dirName, false, logger) + require.NoError(t, err) + + blocks, reachedLib := forkDB.CompleteSegment(bstream.NewBlockRef("60a", 60)) + assert.True(t, reachedLib) + require.Equal(t, 0, len(blocks)) + + blocks, reachedLib = forkDB.CompleteSegment(bstream.NewBlockRef("105a", 105)) + assert.False(t, reachedLib) + require.Equal(t, 0, len(blocks)) + + assert.Equal(t, bstream.NewBlockRef("60a", 60), startBlock) +} + +func assertForkableBlocks(t *testing.T, expected, actual []*forkable.Block) { + t.Helper() + + require.Equal(t, len(expected), len(actual)) + for idx, expect := range expected { + assertForkableBlock(t, expect, actual[idx]) + } +} + +func assertForkableBlock(t *testing.T, expected, actual *forkable.Block) { + t.Helper() + assert.Equal(t, expected.BlockID, actual.BlockID) + assert.Equal(t, expected.BlockNum, actual.BlockNum) + assert.Equal(t, expected.PreviousBlockID, actual.PreviousBlockID) + + expectedBlock, ok := expected.Object.(*block) + require.True(t, ok) + actualBlock, ok := actual.Object.(*block) + require.True(t, ok) + + assert.Equal(t, expectedBlock.fired, actualBlock.fired) + assert.Equal(t, expectedBlock.Block.Id, actualBlock.Block.Id) + assert.Equal(t, expectedBlock.Block.Number, actualBlock.Block.Number) + assert.Equal(t, expectedBlock.Block.ParentId, actualBlock.Block.ParentId) +} diff --git a/firehose/firehose-core/chain.go b/firehose/firehose-core/chain.go new file mode 100644 index 0000000..789c6e5 --- /dev/null +++ b/firehose/firehose-core/chain.go @@ -0,0 +1,354 @@ +package firecore + +import ( + "context" + "fmt" + "runtime/debug" + "strings" + + "github.com/streamingfast/substreams/wasm" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/firehose-core/node-manager/mindreader" + "github.com/streamingfast/firehose-core/node-manager/operator" + "github.com/streamingfast/logging" + "go.uber.org/multierr" + "go.uber.org/zap" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/known/anypb" +) + +// SanitizeBlockForCompareFunc takes a chain agnostic [block] and transforms it in-place, removing fields +// that should not be compared. +type SanitizeBlockForCompareFunc func(block *pbbstream.Block) *pbbstream.Block + +// Chain is the omni config object for configuring your chain specific information. It contains various +// fields that are used everywhere to properly configure the `firehose-` binary. +// +// Each field is documented about where it's used. Throughtout the different [Chain] option, +// we will use `Acme` as the chain's name placeholder, replace it with your chain name. +type Chain[B Block] struct { + // ShortName is the short name for your Firehose on and is usually how + // your chain's name is represented as a diminitutive. If your chain's name is already + // short, we suggest to keep [ShortName] and [LongName] the same. + // + // As an example, Firehose on Ethereum [ShortName] is `eth` while Firehose on NEAR + // short name is `near`. + // + // The [ShortName] **must** be non-empty, lower cased and must **not** contain any spaces. + ShortName string + + // LongName is the full name of your chain and the case sensitivy of this value is respected. + // It is used in description of command and some logging output. + // + // The [LongName] **must** be non-empty. + LongName string + + // ExecutableName is the name of the binary that is used to launch a syncing full node for this chain. For example, + // on Ethereum, the binary by default is `geth`. This is used by the `reader-node` app to specify the + // `reader-node-binary-name` flag. + // + // The [ExecutableName] **must** be non-empty. + ExecutableName string + + // FullyQualifiedModule is the Go module of your actual `firehose-` repository and should + // correspond to the `module` line of the `go.mod` file found at the root of your **own** `firehose-` + // repository. The value can be seen using `head -1 go.mod | sed 's/module //'`. + // + // The [FullyQualifiedModule] **must** be non-empty. + FullyQualifiedModule string + + // Version represents the actual version for your Firehose on . It should be injected + // via and `ldflags` through your `main` package. + // + // The [Version] **must** be non-empty. + Version string + + // FirstStreamableBlock represents the block number of the first block that is streamable using Firehose, + // for example on Ethereum it's set to `0`, the genesis block's number while on Antelope it's + // set to 2 (genesis block is 1 there but our instrumentation on this chain instruments + // only from block #2). + // + // This value is actually the default value of the `--common-first-streamable-block` flag and + // all later usages are done using the flag's value and not this value. + // + // So this value is actually dynamic and can be changed at runtime using the + // `--common-first-streamable-block`. + // + // The [FirstStreamableBlock] should be defined but the default 0 value is good enough + // for most chains. + FirstStreamableBlock uint64 + + // BlockFactory is a factory function that returns a new instance of your chain's Block. + // This new instance is usually used within `firecore` to unmarshal some bytes into your + // chain's specific block model and return a [proto.Message] fully instantiated. + // + // The [BlockFactory] **must** be non-nil and must return a non-nil [proto.Message]. + BlockFactory func() Block + + // ConsoleReaderFactory is the function that should return the `ConsoleReader` that knowns + // how to transform your your chain specific Firehose instrumentation logs into the proper + // Block model of your chain. + // + // The [ConsoleReaderFactory] **must** be non-nil and must return a non-nil [mindreader.ConsolerReader] or an error. + ConsoleReaderFactory func(lines chan string, blockEncoder BlockEncoder, logger *zap.Logger, tracer logging.Tracer) (mindreader.ConsolerReader, error) + + // BlockIndexerFactories defines the set of indexes built out of Firehose blocks to be served by Firehose + // as custom filters. + // + // The [BlockIndexerFactories] is optional. If set, each key must be assigned to a non-nil [BlockIndexerFactory]. For now, + // a single factory can be specified per chain. We use a map to allow for multiple factories in the future. + // + // If there is no indexer factories defined, the `index-builder` app will be disabled for this chain. + // + // The [BlockIndexerFactories] is optional. + BlockIndexerFactories map[string]BlockIndexerFactory[B] + + // BlockTransformerFactories defines the set of transformer that will be enabled when the client request Firehose + // blocks. + // + // The [BlockTransformerFactories] is optional. If set, each key must be assigned to a non-nil + // [BlockTransformerFactory]. Multiple transformers can be defined. + // + // The [BlockTransformerFactories] is optional. + BlockTransformerFactories map[protoreflect.FullName]BlockTransformerFactory + + // RegisterExtraStartFlags is a function that is called by the `reader-node` app to allow your chain + // to register extra custom arguments. This function is called after the common flags are registered. + // + // The [RegisterExtraStartFlags] function is optional and not called if nil. + RegisterExtraStartFlags func(flags *pflag.FlagSet) + + // ReaderNodeBootstrapperFactory enables the `reader-node` app to have a custom bootstrapper for your chain. + // By default, no specialized bootstrapper is defined. + // + // If this is set, the `reader-node` app will use the one bootstrapper returned by this function. The function + // will receive the `start` command where flags are defined as well as the node's absolute data directory as an + // argument. + ReaderNodeBootstrapperFactory func( + ctx context.Context, + logger *zap.Logger, + cmd *cobra.Command, + resolvedNodeArguments []string, + resolver ReaderNodeArgumentResolver, + ) (operator.Bootstrapper, error) + + // Tools aggregate together all configuration options required for the various `fire tools` + // to work properly for example to print block using chain specific information. + // + // The [Tools] element is optional and if not provided, sane defaults will be used. + Tools *ToolsConfig[B] + + // BlockEncoder is the cached block encoder object that should be used for this chain. Populate + // when Init() is called will be `nil` prior to that. + // + // When you need to encode your chain specific block like `pbeth.Block` into a `bstream.Block` you + // should use this encoder: + // + // bstreamBlock, err := chain.BlockEncoder.Encode(block) + // + BlockEncoder BlockEncoder + + DefaultBlockType string + + RegisterSubstreamsExtensions func() (wasm.WASMExtensioner, error) +} + +type ToolsConfig[B Block] struct { + // SanitizeBlockForCompare is a function that takes a chain agnostic [block] and transforms it in-place, removing fields + // that should not be compared. + // + // The [SanitizeBlockForCompare] is optional, if nil, no-op sanitizer be used. + SanitizeBlockForCompare SanitizeBlockForCompareFunc + + // RegisterExtraCmd enables you to register extra commands to the `fire tools` group. + // The callback function is called with the `toolsCmd` command that is the root command of the `fire tools` + // as well as the chain, the root logger and root tracer for tools. + // + // You are responsible of calling `toolsCmd.AddCommand` to register your extra commands. + // + // The [RegisterExtraCmd] function is optional and not called if nil. + RegisterExtraCmd func(chain *Chain[B], toolsCmd *cobra.Command, zlog *zap.Logger, tracer logging.Tracer) error + + // TransformFlags specify chain specific transforms flags (and parsing of those flag's value). The flags defined + // in there are added to all Firehose-client like tools commannd (`tools firehose-client`, `tools firehose-prometheus-exporter`, etc.) + // automatically. + // + // Refer to the TransformFlags for further details on how respect the contract of this field. + // + // The [TransformFlags] is optional. + TransformFlags *TransformFlags + + // MergedBlockUpgrader when define enables for your chain to upgrade between different versions of "merged-blocks". + // It happens from time to time that a data bug is found in the way merged blocks and it's possible to fix it by + // applying a transformation to the block. This is what this function is for. + // + // When defined, a new tools `fire tools upgrade-merged-blocks` is added. This command will enable operators + // to upgrade from one version to another of the merged blocks. + // + // The [MergedBlockUpgrader] is optional and not specifying it disables command `fire tools upgrade-merged-blocks`. + MergedBlockUpgrader func(block *pbbstream.Block) (*pbbstream.Block, error) +} + +// GetSanitizeBlockForCompare returns the [SanitizeBlockForCompare] value if defined, otherwise a no-op sanitizer. +func (t *ToolsConfig[B]) GetSanitizeBlockForCompare() SanitizeBlockForCompareFunc { + if t == nil || t.SanitizeBlockForCompare == nil { + return func(block *pbbstream.Block) *pbbstream.Block { return block } + } + + return t.SanitizeBlockForCompare +} + +type TransformFlags struct { + // Register is a function that will be called when we need to register the flags for the transforms. + // You received the command's flag set and you are responsible of registering the flags. + Register func(flags *pflag.FlagSet) + + // Parse is a function that will be called when we need to extract the transforms out of the flags. + // You received the command and the logger and you are responsible of parsing the flags and returning + // the transforms. + // + // Flags can be obtain with `sflags.MustGetString(cmd, "")` and you will obtain the value. + Parse func(cmd *cobra.Command, logger *zap.Logger) ([]*anypb.Any, error) +} + +// Validate normalizes some aspect of the [Chain] values (spaces trimming essentially) and validates the chain +// by accumulating error an panic if all the error found along the way. +func (c *Chain[B]) Validate() { + c.ShortName = strings.ToLower(strings.TrimSpace(c.ShortName)) + c.LongName = strings.TrimSpace(c.LongName) + c.ExecutableName = strings.TrimSpace(c.ExecutableName) + + var err error + + if c.ShortName == "" { + err = multierr.Append(err, fmt.Errorf("field 'ShortName' must be non-empty")) + } + + if strings.Contains(c.ShortName, " ") { + err = multierr.Append(err, fmt.Errorf("field 'ShortName' must not contain any space(s)")) + } + + if c.LongName == "" { + err = multierr.Append(err, fmt.Errorf("field 'LongName' must be non-empty")) + } + + if c.ExecutableName == "" && !UnsafeAllowExecutableNameToBeEmpty { + err = multierr.Append(err, fmt.Errorf("field 'ExecutableName' must be non-empty")) + } + + if c.FullyQualifiedModule == "" { + err = multierr.Append(err, fmt.Errorf("field 'FullyQualifiedModule' must be non-empty")) + } + + if c.Version == "" { + err = multierr.Append(err, fmt.Errorf("field 'Version' must be non-empty")) + } + + if c.BlockFactory == nil { + err = multierr.Append(err, fmt.Errorf("field 'BlockFactory' must be non-nil")) + } else if c.BlockFactory() == nil { + err = multierr.Append(err, fmt.Errorf("field 'BlockFactory' must not produce nil blocks")) + } + + if c.ConsoleReaderFactory == nil { + err = multierr.Append(err, fmt.Errorf("field 'ConsoleReaderFactory' must be non-nil")) + } + + if len(c.BlockIndexerFactories) > 1 { + err = multierr.Append(err, fmt.Errorf("field 'BlockIndexerFactories' must have at most one element")) + } + + for key, indexerFactory := range c.BlockIndexerFactories { + if indexerFactory == nil { + err = multierr.Append(err, fmt.Errorf("entry %q for field 'BlockIndexerFactories' must be non-nil", key)) + } + } + + for key, transformerFactory := range c.BlockTransformerFactories { + if transformerFactory == nil { + err = multierr.Append(err, fmt.Errorf("entry %q for field 'BlockTransformerFactories' must be non-nil", key)) + } + } + + errors := multierr.Errors(err) + if len(errors) > 0 { + errorLines := make([]string, len(errors)) + for i, err := range errors { + errorLines[i] = fmt.Sprintf("- %s", err) + } + + panic(fmt.Sprintf("firecore.Chain is invalid:\n%s", strings.Join(errorLines, "\n"))) + } +} + +// Init is called when the chain is first loaded to initialize the `bstream` +// library with the chain specific configuration. +// +// This must called only once per chain per process. +// +// **Caveats** Two chain in the same Go binary will not work today as `bstream` uses global +// variables to store configuration which presents multiple chain to exist in the same process. +func (c *Chain[B]) Init() { + c.BlockEncoder = NewBlockEncoder() + + if c.ReaderNodeBootstrapperFactory == nil { + c.ReaderNodeBootstrapperFactory = DefaultReaderNodeBootstrapper(noOpReaderNodeBootstrapperFactory) + } +} + +// BinaryName represents the binary name for your Firehose on is the [ShortName] +// lowered appended to 'fire' prefix to before for example `fireacme`. +func (c *Chain[B]) BinaryName() string { + return "fire" + strings.ToLower(c.ShortName) +} + +// RootLoggerPackageID is the `packageID` value when instantiating the root logger on the chain +// that is used by CLI command and other +func (c *Chain[B]) RootLoggerPackageID() string { + return c.LoggerPackageID(fmt.Sprintf("cmd/%s/cli", c.BinaryName())) +} + +// LoggerPackageID computes a logger `packageID` value for a specific sub-package. +func (c *Chain[B]) LoggerPackageID(subPackage string) string { + return fmt.Sprintf("%s/%s", c.FullyQualifiedModule, subPackage) +} + +// VersionString computes the version string that will be display when calling `firexxx --version` +// and extract build information from Git via Golang `debug.ReadBuildInfo`. +func (c *Chain[B]) VersionString() string { + info, ok := debug.ReadBuildInfo() + if !ok { + panic("we should have been able to retrieve info from 'runtime/debug#ReadBuildInfo'") + } + + commit := findSetting("vcs.revision", info.Settings) + date := findSetting("vcs.time", info.Settings) + + var labels []string + if len(commit) >= 7 { + labels = append(labels, fmt.Sprintf("Commit %s", commit[0:7])) + } + + if date != "" { + labels = append(labels, fmt.Sprintf("Built %s", date)) + } + + if len(labels) == 0 { + return c.Version + } + + return fmt.Sprintf("%s (%s)", c.Version, strings.Join(labels, ", ")) +} + +func findSetting(key string, settings []debug.BuildSetting) (value string) { + for _, setting := range settings { + if setting.Key == key { + return setting.Value + } + } + + return "" +} diff --git a/firehose/firehose-core/cmd/apps/firehose.go b/firehose/firehose-core/cmd/apps/firehose.go new file mode 100644 index 0000000..4585299 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/firehose.go @@ -0,0 +1,107 @@ +package apps + +import ( + "fmt" + "net/url" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/bstream/transform" + "github.com/streamingfast/dauth" + discoveryservice "github.com/streamingfast/dgrpc/server/discovery-service" + "github.com/streamingfast/dmetrics" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/firehose/app/firehose" + "github.com/streamingfast/firehose-core/firehose/server" + "github.com/streamingfast/firehose-core/launcher" + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +var metricset = dmetrics.NewSet() +var headBlockNumMetric = metricset.NewHeadBlockNumber("firehose") +var headTimeDriftmetric = metricset.NewHeadTimeDrift("firehose") + +func RegisterFirehoseApp[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) { + appLogger, appTracer := logging.PackageLogger("firehose", "firehose") + + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "firehose", + Title: "Block Firehose", + Description: "Provides on-demand filtered blocks, depends on common-merged-blocks-store-url and common-live-blocks-addr", + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("firehose-grpc-listen-addr", firecore.FirehoseGRPCServingAddr, "Address on which the firehose will listen") + cmd.Flags().String("firehose-discovery-service-url", "", "Url to configure the gRPC discovery service") //traffic-director://xds?vpc_network=vpc-global&use_xds_reds=true + cmd.Flags().Int("firehose-rate-limit-bucket-size", -1, "Rate limit bucket size (default: no rate limit)") + cmd.Flags().Duration("firehose-rate-limit-bucket-fill-rate", 10*time.Second, "Rate limit bucket refill rate (default: 10s)") + + return nil + }, + + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + authenticator, err := dauth.New(viper.GetString("common-auth-plugin"), appLogger) + if err != nil { + return nil, fmt.Errorf("unable to initialize authenticator: %w", err) + } + + mergedBlocksStoreURL, oneBlocksStoreURL, forkedBlocksStoreURL, err := firecore.GetCommonStoresURLs(runtime.AbsDataDir) + if err != nil { + return nil, err + } + + rawServiceDiscoveryURL := viper.GetString("firehose-discovery-service-url") + var serviceDiscoveryURL *url.URL + if rawServiceDiscoveryURL != "" { + serviceDiscoveryURL, err = url.Parse(rawServiceDiscoveryURL) + if err != nil { + return nil, fmt.Errorf("unable to parse discovery service url: %w", err) + } + err = discoveryservice.Bootstrap(serviceDiscoveryURL) + if err != nil { + return nil, fmt.Errorf("unable to bootstrap discovery service: %w", err) + } + } + + indexStore, possibleIndexSizes, err := firecore.GetIndexStore(runtime.AbsDataDir) + if err != nil { + return nil, fmt.Errorf("unable to initialize indexes: %w", err) + } + + registry := transform.NewRegistry() + for _, factory := range chain.BlockTransformerFactories { + transformer, err := factory(indexStore, possibleIndexSizes) + if err != nil { + return nil, fmt.Errorf("unable to create transformer: %w", err) + } + + registry.Register(transformer) + } + + var serverOptions []server.Option + + limiterSize := viper.GetInt("firehose-rate-limit-bucket-size") + limiterRefillRate := viper.GetDuration("firehose-rate-limit-bucket-fill-rate") + if limiterSize > 0 { + serverOptions = append(serverOptions, server.WithLeakyBucketLimiter(limiterSize, limiterRefillRate)) + } + + return firehose.New(appLogger, appTracer, &firehose.Config{ + MergedBlocksStoreURL: mergedBlocksStoreURL, + OneBlocksStoreURL: oneBlocksStoreURL, + ForkedBlocksStoreURL: forkedBlocksStoreURL, + BlockStreamAddr: viper.GetString("common-live-blocks-addr"), + GRPCListenAddr: viper.GetString("firehose-grpc-listen-addr"), + GRPCShutdownGracePeriod: 1 * time.Second, + ServiceDiscoveryURL: serviceDiscoveryURL, + ServerOptions: serverOptions, + }, &firehose.Modules{ + Authenticator: authenticator, + HeadTimeDriftMetric: headTimeDriftmetric, + HeadBlockNumberMetric: headBlockNumMetric, + TransformRegistry: registry, + CheckPendingShutdown: runtime.IsPendingShutdown, + }), nil + }, + }) +} diff --git a/firehose/firehose-core/cmd/apps/index_builder.go b/firehose/firehose-core/cmd/apps/index_builder.go new file mode 100644 index 0000000..8e4c350 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/index_builder.go @@ -0,0 +1,102 @@ +package apps + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + bstransform "github.com/streamingfast/bstream/transform" + firecore "github.com/streamingfast/firehose-core" + index_builder "github.com/streamingfast/firehose-core/index-builder/app/index-builder" + "github.com/streamingfast/firehose-core/launcher" + "go.uber.org/zap" +) + +func RegisterIndexBuilderApp[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) { + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "index-builder", + Title: "Index Builder", + Description: "App the builds indexes out of Firehose blocks", + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("index-builder-grpc-listen-addr", firecore.IndexBuilderServiceAddr, "Address to listen for grpc-based healthz check") + cmd.Flags().Uint64("index-builder-index-size", 10000, "Size of index bundles that will be created") + cmd.Flags().Uint64("index-builder-start-block", 0, "Block number to start indexing") + cmd.Flags().Uint64("index-builder-stop-block", 0, "Block number to stop indexing") + return nil + }, + InitFunc: func(runtime *launcher.Runtime) error { + return nil + }, + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + mergedBlocksStoreURL, _, _, err := firecore.GetCommonStoresURLs(runtime.AbsDataDir) + if err != nil { + return nil, err + } + + indexStore, lookupIdxSizes, err := firecore.GetIndexStore(runtime.AbsDataDir) + if err != nil { + return nil, err + } + + // Chain must have been validated, so if we are here, it's because there is + // exactly one index in BlockIndexerFactories map. + indexShortName, indexerFactory, found := getMapFirst(chain.BlockIndexerFactories) + if !found { + return nil, fmt.Errorf("no indexer factory found but one should be defined at this point") + } + + startBlockResolver := func(ctx context.Context) (uint64, error) { + select { + case <-ctx.Done(): + return 0, ctx.Err() + default: + } + + startBlockNum := bstransform.FindNextUnindexed( + ctx, + viper.GetUint64("index-builder-start-block"), + lookupIdxSizes, + indexShortName, + indexStore, + ) + + return startBlockNum, nil + } + stopBlockNum := viper.GetUint64("index-builder-stop-block") + + indexer, err := indexerFactory(indexStore, viper.GetUint64("index-builder-index-size")) + if err != nil { + return nil, fmt.Errorf("unable to create indexer: %w", err) + } + + handler := bstream.HandlerFunc(func(blk *pbbstream.Block, _ interface{}) error { + var b = chain.BlockFactory() + if err := blk.Payload.UnmarshalTo(b); err != nil { + return err + } + return indexer.ProcessBlock(any(b).(B)) + }) + + app := index_builder.New(&index_builder.Config{ + BlockHandler: handler, + StartBlockResolver: startBlockResolver, + EndBlock: stopBlockNum, + MergedBlocksStoreURL: mergedBlocksStoreURL, + GRPCListenAddr: viper.GetString("index-builder-grpc-listen-addr"), + }) + + return app, nil + }, + }) +} + +func getMapFirst[K comparable, V any](m map[K]V) (k K, v V, found bool) { + for k := range m { + return k, m[k], true + } + + return k, v, false +} diff --git a/firehose/firehose-core/cmd/apps/merger.go b/firehose/firehose-core/cmd/apps/merger.go new file mode 100644 index 0000000..e84fd29 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/merger.go @@ -0,0 +1,48 @@ +package apps + +import ( + "time" + + firecore "github.com/streamingfast/firehose-core" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/firehose-core/launcher" + "github.com/streamingfast/firehose-core/merger/app/merger" + "go.uber.org/zap" +) + +func RegisterMergerApp(rootLog *zap.Logger) { + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "merger", + Title: "Merger", + Description: "Produces merged block files from single-block files", + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("merger-grpc-listen-addr", firecore.MergerServingAddr, "Address to listen for incoming gRPC requests") + cmd.Flags().Uint64("merger-prune-forked-blocks-after", 50000, "Number of blocks that must pass before we delete old forks (one-block-files lingering)") + cmd.Flags().Uint64("merger-stop-block", 0, "If non-zero, merger will trigger shutdown when blocks have been merged up to this block") + cmd.Flags().Duration("merger-time-between-store-lookups", 1*time.Second, "Delay between source store polling (should be higher for remote storage)") + cmd.Flags().Duration("merger-time-between-store-pruning", time.Minute, "Delay between source store pruning loops") + cmd.Flags().Int("merger-delete-threads", 8, "Number of threads for deleting files in parallel (increase this in case the merger isn't able to keep up with deleting one-block files).") + return nil + }, + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + mergedBlocksStoreURL, oneBlocksStoreURL, forkedBlocksStoreURL, err := firecore.GetCommonStoresURLs(runtime.AbsDataDir) + if err != nil { + return nil, err + } + + return merger.New(&merger.Config{ + GRPCListenAddr: viper.GetString("merger-grpc-listen-addr"), + PruneForkedBlocksAfter: viper.GetUint64("merger-prune-forked-blocks-after"), + StorageOneBlockFilesPath: oneBlocksStoreURL, + StorageMergedBlocksFilesPath: mergedBlocksStoreURL, + StorageForkedBlocksFilesPath: forkedBlocksStoreURL, + StopBlock: viper.GetUint64("merger-stop-block"), + TimeBetweenPruning: viper.GetDuration("merger-time-between-store-pruning"), + TimeBetweenPolling: viper.GetDuration("merger-time-between-store-lookups"), + FilesDeleteThreads: viper.GetInt("merger-delete-threads"), + }), nil + }, + }) +} diff --git a/firehose/firehose-core/cmd/apps/reader_node.go b/firehose/firehose-core/cmd/apps/reader_node.go new file mode 100644 index 0000000..7b2d18f --- /dev/null +++ b/firehose/firehose-core/cmd/apps/reader_node.go @@ -0,0 +1,260 @@ +package apps + +import ( + "context" + "fmt" + "os" + "regexp" + "time" + + "github.com/kballard/go-shellquote" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/bstream/blockstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/cli" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/launcher" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + nodeManagerApp "github.com/streamingfast/firehose-core/node-manager/app/node_manager" + "github.com/streamingfast/firehose-core/node-manager/metrics" + reader "github.com/streamingfast/firehose-core/node-manager/mindreader" + "github.com/streamingfast/firehose-core/node-manager/operator" + sv "github.com/streamingfast/firehose-core/superviser" + "github.com/streamingfast/logging" + pbheadinfo "github.com/streamingfast/pbgo/sf/headinfo/v1" + "github.com/streamingfast/snapshotter" + "go.uber.org/zap" + "google.golang.org/grpc" +) + +func RegisterReaderNodeApp[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) { + appLogger, appTracer := logging.PackageLogger("reader-node", "reader-node") + + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "reader-node", + Title: fmt.Sprintf("%s Reader Node", chain.LongName), + Description: fmt.Sprintf("%s node with built-in operational manager", chain.LongName), + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("reader-node-path", chain.ExecutableName, cli.FlagDescription(` + Process that will be invoked to sync the chain, can be a full path or just the binary's name, in which case the binary is + searched for paths listed by the PATH environment variable (following operating system rules around PATH handling). + `)) + cmd.Flags().String("reader-node-data-dir", "{data-dir}/reader/data", "Directory for node data") + cmd.Flags().Bool("reader-node-debug-firehose-logs", false, "[DEV] Prints firehose instrumentation logs to standard output, should be use for debugging purposes only") + cmd.Flags().String("reader-node-manager-api-addr", firecore.ReaderNodeManagerAPIAddr, "Acme node manager API address") + cmd.Flags().Duration("reader-node-readiness-max-latency", 30*time.Second, "Determine the maximum head block latency at which the instance will be determined healthy. Some chains have more regular block production than others.") + cmd.Flags().String("reader-node-arguments", "", string(cli.Description(` + Defines the node arguments that will be passed to the node on execution. Supports templating, where we will replace certain sub-string with the appropriate value + + {data-dir} The current data-dir path defined by the flag 'data-dir' + {node-data-dir} The node data dir path defined by the flag 'reader-node-data-dir' + {hostname} The machine's hostname + {start-block-num} The resolved start block number defined by the flag 'reader-node-start-block-num' (can be overwritten) + {stop-block-num} The stop block number defined by the flag 'reader-node-stop-block-num' + + Example: 'run blockchain -start {start-block-num} -end {stop-block-num}' may yield 'run blockchain -start 200 -end 500' + `))) + cmd.Flags().StringSlice("reader-node-backups", []string{}, "Repeatable, space-separated key=values definitions for backups. Example: 'type=gke-pvc-snapshot prefix= tag=v1 freq-blocks=1000 freq-time= project=myproj'") + cmd.Flags().String("reader-node-grpc-listen-addr", firecore.ReaderNodeGRPCAddr, "The gRPC listening address to use for serving real-time blocks") + cmd.Flags().Bool("reader-node-discard-after-stop-num", false, "Ignore remaining blocks being processed after stop num (only useful if we discard the reader data after reprocessing a chunk of blocks)") + cmd.Flags().String("reader-node-working-dir", "{data-dir}/reader/work", "Path where reader will stores its files") + cmd.Flags().Uint("reader-node-start-block-num", 0, "Blocks that were produced with smaller block number then the given block num are skipped") + cmd.Flags().Uint("reader-node-stop-block-num", 0, "Shutdown reader when we the following 'stop-block-num' has been reached, inclusively.") + cmd.Flags().Int("reader-node-blocks-chan-capacity", 100, "Capacity of the channel holding blocks read by the reader. Process will shutdown reader-node if the channel gets over 90% of that capacity to prevent horrible consequences. Raise this number when processing tiny blocks very quickly") + cmd.Flags().String("reader-node-one-block-suffix", "default", cli.FlagDescription(` + Unique identifier for reader, so that it can produce 'oneblock files' in the same store as another instance without competing + for writes. You should set this flag if you have multiple reader running, each one should get a unique identifier, the + hostname value is a good value to use. + `)) + return nil + }, + InitFunc: func(runtime *launcher.Runtime) error { + return nil + }, + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + sfDataDir := runtime.AbsDataDir + + nodePath := viper.GetString("reader-node-path") + if nodePath == "" { + return nil, fmt.Errorf("the configuration value 'reader-node-path' cannot be empty, it must points to a Firehose aware blockchain node client or a Firehose aware poller, don't forget to set the 'reader-node-arguments' flag to pass the appropriate arguments to the node") + } + + nodeDataDir := firecore.MustReplaceDataDir(sfDataDir, viper.GetString("reader-node-data-dir")) + + readinessMaxLatency := viper.GetDuration("reader-node-readiness-max-latency") + debugFirehose := viper.GetBool("reader-node-debug-firehose-logs") + shutdownDelay := viper.GetDuration("common-system-shutdown-signal-delay") // we reuse this global value + httpAddr := viper.GetString("reader-node-manager-api-addr") + backupConfigs := viper.GetStringSlice("reader-node-backups") + + backupModules, backupSchedules, err := operator.ParseBackupConfigs(appLogger, backupConfigs, map[string]operator.BackupModuleFactory{ + "gke-pvc-snapshot": gkeSnapshotterFactory, + }) + if err != nil { + return nil, fmt.Errorf("parse backup configs: %w", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), 4*time.Minute) + defer cancel() + + userDefined := viper.IsSet("reader-node-start-block-num") + startBlockNum := viper.GetUint64("reader-node-start-block-num") + firstStreamableBlock := viper.GetUint64("common-first-streamable-block") + + resolveStartBlockNum := startBlockNum + if !userDefined { + resolveStartBlockNum, err = firecore.UnsafeResolveReaderNodeStartBlock(ctx, startBlockNum, firstStreamableBlock, runtime, rootLog) + if err != nil { + return nil, fmt.Errorf("resolve start block: %w", err) + } + + } + + stopBlockNum := viper.GetUint64("reader-node-stop-block-num") + + hostname, _ := os.Hostname() + nodeArgumentResolver := createNodeArgumentsResolver(sfDataDir, nodeDataDir, hostname, resolveStartBlockNum, stopBlockNum) + + nodeArguments, err := buildNodeArguments(viper.GetString("reader-node-arguments"), nodeArgumentResolver) + if err != nil { + return nil, fmt.Errorf("cannot split 'reader-node-arguments' value: %w", err) + } + + headBlockTimeDrift := metrics.NewHeadBlockTimeDrift("reader-node") + headBlockNumber := metrics.NewHeadBlockNumber("reader-node") + appReadiness := metrics.NewAppReadiness("reader-node") + + metricsAndReadinessManager := nodeManager.NewMetricsAndReadinessManager( + headBlockTimeDrift, + headBlockNumber, + appReadiness, + readinessMaxLatency, + ) + + superviser := sv.SupervisorFactory(chain.ExecutableName, nodePath, nodeArguments, appLogger) + superviser.RegisterLogPlugin(sv.NewNodeLogPlugin(debugFirehose)) + + var bootstrapper operator.Bootstrapper + if chain.ReaderNodeBootstrapperFactory != nil { + bootstrapper, err = chain.ReaderNodeBootstrapperFactory(StartCmd.Context(), appLogger, StartCmd, nodeArguments, nodeArgumentResolver) + if err != nil { + return nil, fmt.Errorf("new bootstrapper: %w", err) + } + } + + chainOperator, err := operator.New( + appLogger, + superviser, + metricsAndReadinessManager, + &operator.Options{ + ShutdownDelay: shutdownDelay, + EnableSupervisorMonitoring: true, + Bootstrapper: bootstrapper, + }) + if err != nil { + return nil, fmt.Errorf("unable to create chain operator: %w", err) + } + + for name, mod := range backupModules { + appLogger.Info("registering backup module", zap.String("name", name), zap.Any("module", mod)) + err := chainOperator.RegisterBackupModule(name, mod) + if err != nil { + return nil, fmt.Errorf("unable to register backup module %s: %w", name, err) + } + + appLogger.Info("backup module registered", zap.String("name", name), zap.Any("module", mod)) + } + + for _, sched := range backupSchedules { + chainOperator.RegisterBackupSchedule(sched) + } + + blockStreamServer := blockstream.NewUnmanagedServer(blockstream.ServerOptionWithLogger(appLogger)) + oneBlocksStoreURL := firecore.MustReplaceDataDir(sfDataDir, viper.GetString("common-one-block-store-url")) + workingDir := firecore.MustReplaceDataDir(sfDataDir, viper.GetString("reader-node-working-dir")) + gprcListenAddr := viper.GetString("reader-node-grpc-listen-addr") + oneBlockFileSuffix := viper.GetString("reader-node-one-block-suffix") + blocksChanCapacity := viper.GetInt("reader-node-blocks-chan-capacity") + + readerPlugin, err := reader.NewMindReaderPlugin( + oneBlocksStoreURL, + workingDir, + func(lines chan string) (reader.ConsolerReader, error) { + return chain.ConsoleReaderFactory(lines, chain.BlockEncoder, appLogger, appTracer) + }, + resolveStartBlockNum, + stopBlockNum, + blocksChanCapacity, + metricsAndReadinessManager.UpdateHeadBlock, + func(error) { + chainOperator.Shutdown(nil) + }, + oneBlockFileSuffix, + blockStreamServer, + appLogger, + appTracer, + ) + if err != nil { + return nil, fmt.Errorf("new reader plugin: %w", err) + } + + superviser.RegisterLogPlugin(readerPlugin) + + return nodeManagerApp.New(&nodeManagerApp.Config{ + HTTPAddr: httpAddr, + GRPCAddr: gprcListenAddr, + }, &nodeManagerApp.Modules{ + Operator: chainOperator, + MindreaderPlugin: readerPlugin, + MetricsAndReadinessManager: metricsAndReadinessManager, + RegisterGRPCService: func(server grpc.ServiceRegistrar) error { + pbheadinfo.RegisterHeadInfoServer(server, blockStreamServer) + pbbstream.RegisterBlockStreamServer(server, blockStreamServer) + + return nil + }, + }, appLogger), nil + }, + }) +} + +var variablesRegex = regexp.MustCompile(`\{(data-dir|node-data-dir|hostname|start-block-num|stop-block-num)\}`) + +// buildNodeArguments will resolve and split the given string into arguments, replacing the variables with the appropriate values. +// +// We are using a function for testing purposes, so that we can test arguments resolving and splitting correctly. +func buildNodeArguments(in string, resolver firecore.ReaderNodeArgumentResolver) ([]string, error) { + // Split arguments according to standard shell rules + nodeArguments, err := shellquote.Split(resolver(in)) + if err != nil { + return nil, fmt.Errorf("cannot split 'reader-node-arguments' value: %w", err) + } + + return nodeArguments, nil +} + +func createNodeArgumentsResolver(dataDir, nodeDataDir, hostname string, startBlockNum, stopBlockNum uint64) firecore.ReaderNodeArgumentResolver { + return func(in string) string { + return variablesRegex.ReplaceAllStringFunc(in, func(match string) string { + switch match { + case "{data-dir}": + return dataDir + case "{node-data-dir}": + return nodeDataDir + case "{hostname}": + return hostname + case "{start-block-num}": + return fmt.Sprintf("%d", startBlockNum) + case "{stop-block-num}": + return fmt.Sprintf("%d", stopBlockNum) + default: + return fmt.Sprintf("", match) + } + }) + } +} + +func gkeSnapshotterFactory(conf operator.BackupModuleConfig) (operator.BackupModule, error) { + return snapshotter.NewGKEPVCSnapshotter(conf) +} diff --git a/firehose/firehose-core/cmd/apps/reader_node_stdin.go b/firehose/firehose-core/cmd/apps/reader_node_stdin.go new file mode 100644 index 0000000..0b05308 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/reader_node_stdin.go @@ -0,0 +1,65 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apps + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/launcher" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + nodeReaderStdinApp "github.com/streamingfast/firehose-core/node-manager/app/node_reader_stdin" + "github.com/streamingfast/firehose-core/node-manager/metrics" + "github.com/streamingfast/firehose-core/node-manager/mindreader" + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +func RegisterReaderNodeStdinApp[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) { + appLogger, appTracer := logging.PackageLogger("reader-node-stdin", chain.LoggerPackageID("reader-node-stdin")) + + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "reader-node-stdin", + Title: "Reader Node (stdin)", + Description: "Blocks reading node, unmanaged, reads Firehose logs from standard input and transform them into Firehose chain specific blocks", + RegisterFlags: func(cmd *cobra.Command) error { return nil }, + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + sfDataDir := runtime.AbsDataDir + archiveStoreURL := firecore.MustReplaceDataDir(sfDataDir, viper.GetString("common-one-block-store-url")) + consoleReaderFactory := func(lines chan string) (mindreader.ConsolerReader, error) { + return chain.ConsoleReaderFactory(lines, chain.BlockEncoder, appLogger, appTracer) + } + + metricID := "reader-node-stdin" + headBlockTimeDrift := metrics.NewHeadBlockTimeDrift(metricID) + headBlockNumber := metrics.NewHeadBlockNumber(metricID) + appReadiness := metrics.NewAppReadiness(metricID) + metricsAndReadinessManager := nodeManager.NewMetricsAndReadinessManager(headBlockTimeDrift, headBlockNumber, appReadiness, viper.GetDuration("reader-node-readiness-max-latency")) + + return nodeReaderStdinApp.New(&nodeReaderStdinApp.Config{ + GRPCAddr: viper.GetString("reader-node-grpc-listen-addr"), + OneBlocksStoreURL: archiveStoreURL, + MindReadBlocksChanCapacity: viper.GetInt("reader-node-blocks-chan-capacity"), + StartBlockNum: viper.GetUint64("reader-node-start-block-num"), + StopBlockNum: viper.GetUint64("reader-node-stop-block-num"), + WorkingDir: firecore.MustReplaceDataDir(sfDataDir, viper.GetString("reader-node-working-dir")), + OneBlockSuffix: viper.GetString("reader-node-one-block-suffix"), + }, &nodeReaderStdinApp.Modules{ + ConsoleReaderFactory: consoleReaderFactory, + MetricsAndReadinessManager: metricsAndReadinessManager, + }, appLogger, appTracer), nil + }, + }) +} diff --git a/firehose/firehose-core/cmd/apps/reader_node_test.go b/firehose/firehose-core/cmd/apps/reader_node_test.go new file mode 100644 index 0000000..95f1888 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/reader_node_test.go @@ -0,0 +1,50 @@ +package apps + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_buildNodeArguments(t *testing.T) { + dataDir := "/data" + nodeDataDir := "/data/node" + hostname := "host" + + tests := []struct { + name string + args string + want []string + startBlockNum uint64 + stopBlockNum uint64 + assertion require.ErrorAssertionFunc + }{ + {"no variables", "arg1 arg2", []string{"arg1", "arg2"}, 10, 20, require.NoError}, + {"variable data-dir", "{data-dir} arg2", []string{"/data", "arg2"}, 10, 20, require.NoError}, + {"variable node-data-dir", "{node-data-dir} arg2", []string{"/data/node", "arg2"}, 10, 20, require.NoError}, + {"variable hostname", "{hostname} arg2", []string{"host", "arg2"}, 10, 20, require.NoError}, + {"variable start block num", "{start-block-num} arg2", []string{"10", "arg2"}, 10, 20, require.NoError}, + {"variable stop block num", "{stop-block-num} arg2", []string{"20", "arg2"}, 10, 20, require.NoError}, + {"variable data-dir double quotes", `"{hostname} with spaces" arg2`, []string{"host with spaces", "arg2"}, 10, 20, require.NoError}, + {"variable all", `--home="{data-dir}" --data={node-data-dir} --id={hostname} --other --start={start-block-num} -stop {stop-block-num} --foo`, []string{ + "--home=/data", + "--data=/data/node", + "--id=host", + "--other", + "--start=10", + "-stop", + "20", + "--foo", + }, 10, 20, require.NoError}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + resolver := createNodeArgumentsResolver(dataDir, nodeDataDir, hostname, tt.startBlockNum, tt.stopBlockNum) + args, err := buildNodeArguments(tt.args, resolver) + tt.assertion(t, err) + + assert.Equal(t, tt.want, args) + }) + } +} diff --git a/firehose/firehose-core/cmd/apps/relayer.go b/firehose/firehose-core/cmd/apps/relayer.go new file mode 100644 index 0000000..3ef9284 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/relayer.go @@ -0,0 +1,36 @@ +package apps + +import ( + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/launcher" + "github.com/streamingfast/firehose-core/relayer/app/relayer" + "go.uber.org/zap" +) + +func RegisterRelayerApp(rootLog *zap.Logger) { + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "relayer", + Title: "Relayer", + Description: "Serves blocks as a stream, with a buffer", + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("relayer-grpc-listen-addr", firecore.RelayerServingAddr, "Address to listen for incoming gRPC requests") + cmd.Flags().StringSlice("relayer-source", []string{firecore.ReaderNodeGRPCAddr}, "List of live sources (reader(s)) to connect to for live block feeds (repeat flag as needed)") + cmd.Flags().Duration("relayer-max-source-latency", 999999*time.Hour, "Max latency tolerated to connect to a source. A performance optimization for when you have redundant sources and some may not have caught up") + return nil + }, + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + sfDataDir := runtime.AbsDataDir + + return relayer.New(&relayer.Config{ + SourcesAddr: viper.GetStringSlice("relayer-source"), + OneBlocksURL: firecore.MustReplaceDataDir(sfDataDir, viper.GetString("common-one-block-store-url")), + GRPCListenAddr: viper.GetString("relayer-grpc-listen-addr"), + MaxSourceLatency: viper.GetDuration("relayer-max-source-latency"), + }), nil + }, + }) +} diff --git a/firehose/firehose-core/cmd/apps/start.go b/firehose/firehose-core/cmd/apps/start.go new file mode 100644 index 0000000..74e1efc --- /dev/null +++ b/firehose/firehose-core/cmd/apps/start.go @@ -0,0 +1,133 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apps + +import ( + "context" + "fmt" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/dmetering" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/launcher" + tracing "github.com/streamingfast/sf-tracing" + "go.uber.org/zap" + "golang.org/x/exp/slices" +) + +var StartCmd = &cobra.Command{Use: "start", Args: cobra.ArbitraryArgs} + +func ConfigureStartCmd[B firecore.Block](chain *firecore.Chain[B], binaryName string, rootLog *zap.Logger) { + StartCmd.Short = fmt.Sprintf("Starts `%s` services all at once", binaryName) + StartCmd.RunE = func(cmd *cobra.Command, args []string) (err error) { + cmd.SilenceUsage = true + + dataDir := sflags.MustGetString(cmd, "data-dir") + rootLog.Debug(fmt.Sprintf("%s binary started", binaryName), zap.String("data_dir", dataDir)) + + configFile := sflags.MustGetString(cmd, "config-file") + rootLog.Info(fmt.Sprintf("starting Firehose on %s with config file '%s'", chain.LongName, configFile)) + + err = start(cmd, dataDir, args, rootLog) + if err != nil { + return fmt.Errorf("unable to launch: %w", err) + } + + rootLog.Info("terminated") + return + } +} + +func start(cmd *cobra.Command, dataDir string, args []string, rootLog *zap.Logger) (err error) { + dataDirAbs, err := filepath.Abs(dataDir) + if err != nil { + return fmt.Errorf("unable to setup directory structure: %w", err) + } + + err = firecore.MakeDirs([]string{dataDirAbs}) + if err != nil { + return err + } + + bstream.GetProtocolFirstStreamableBlock = sflags.MustGetUint64(cmd, "common-first-streamable-block") + + err = bstream.ValidateRegistry() + if err != nil { + return fmt.Errorf("protocol specific hooks not configured correctly: %w", err) + } + + eventEmitter, err := dmetering.New(sflags.MustGetString(cmd, "common-metering-plugin"), rootLog) + if err != nil { + return fmt.Errorf("unable to initialize dmetering: %w", err) + } + defer func() { + eventEmitter.Shutdown(nil) + }() + dmetering.SetDefaultEmitter(eventEmitter) + + launch := launcher.NewLauncher(rootLog, dataDirAbs) + rootLog.Debug("launcher created") + + runByDefault := func(app string) bool { + appsNotRunningByDefault := []string{"reader-node-stdin"} + return !slices.Contains(appsNotRunningByDefault, app) + } + + apps := launcher.ParseAppsFromArgs(args, runByDefault) + if len(args) == 0 && launcher.Config != nil && launcher.Config["start"] != nil { + apps = launcher.ParseAppsFromArgs(launcher.Config["start"].Args, runByDefault) + } + + serviceName := "firecore" + if len(apps) == 1 { + serviceName = serviceName + "/" + apps[0] + } + if err := tracing.SetupOpenTelemetry(context.Background(), serviceName); err != nil { + return err + } + + rootLog.Info(fmt.Sprintf("launching applications: %s", strings.Join(apps, ","))) + if err = launch.Launch(apps); err != nil { + return err + } + + signalHandler, hasBeenSignaled, _ := cli.SetupSignalHandler(sflags.MustGetDuration(cmd, "common-system-shutdown-signal-delay"), rootLog) + + // We need to pass the signal handler so that runtime.IsPendingShutdown() is properly + // linked to the signal handler, otherwise, it will always return false. + launch.SwitchHasBeenSignaledAtomic(hasBeenSignaled) + + select { + case <-signalHandler: + rootLog.Info("received termination signal, quitting") + go launch.Close() + case appID := <-launch.Terminating(): + if launch.Err() == nil { + rootLog.Info(fmt.Sprintf("application %s triggered a clean shutdown, quitting", appID)) + } else { + rootLog.Info(fmt.Sprintf("application %s shutdown unexpectedly, quitting", appID)) + err = launch.Err() + } + } + + launch.WaitForTermination() + + return +} diff --git a/firehose/firehose-core/cmd/apps/substreams_common.go b/firehose/firehose-core/cmd/apps/substreams_common.go new file mode 100644 index 0000000..2f02f3d --- /dev/null +++ b/firehose/firehose-core/cmd/apps/substreams_common.go @@ -0,0 +1,17 @@ +package apps + +import ( + "sync" + + "github.com/spf13/cobra" +) + +var registerSSOnce sync.Once + +func registerCommonSubstreamsFlags(cmd *cobra.Command) { + registerSSOnce.Do(func() { + cmd.Flags().Uint64("substreams-state-bundle-size", uint64(1_000), "Interval in blocks at which to save store snapshots and output caches") + cmd.Flags().String("substreams-state-store-url", "{sf-data-dir}/localdata", "where substreams state data are stored") + cmd.Flags().String("substreams-state-store-default-tag", "", "If non-empty, will be appended to {substreams-state-store-url} (ex: 'v1'). Can be overriden per-request with 'X-Sf-Substreams-Cache-Tag' header") + }) +} diff --git a/firehose/firehose-core/cmd/apps/substreams_tier1.go b/firehose/firehose-core/cmd/apps/substreams_tier1.go new file mode 100644 index 0000000..62e39c4 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/substreams_tier1.go @@ -0,0 +1,156 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apps + +import ( + "fmt" + "net/url" + "os" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/streamingfast/dauth" + discoveryservice "github.com/streamingfast/dgrpc/server/discovery-service" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/launcher" + "github.com/streamingfast/logging" + app "github.com/streamingfast/substreams/app" + "github.com/streamingfast/substreams/wasm" + "go.uber.org/zap" +) + +var ss1HeadBlockNumMetric = metricset.NewHeadBlockNumber("substreams-tier1") +var ss1HeadTimeDriftmetric = metricset.NewHeadTimeDrift("substreams-tier1") + +func RegisterSubstreamsTier1App[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) { + appLogger, _ := logging.PackageLogger("substreams-tier1", "github.com/streamingfast/firehose-core/firehose-ethereum/substreams-tier1") + + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "substreams-tier1", + Title: "Substreams tier1 server", + Description: "Provides a substreams grpc endpoint", + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("substreams-tier1-grpc-listen-addr", firecore.SubstreamsTier1GRPCServingAddr, "Address on which the Substreams tier1 will listen, listen by default in plain text, appending a '*' to the end of the address make it listen in snake-oil (inscure) TLS") + cmd.Flags().String("substreams-tier1-subrequests-endpoint", firecore.SubstreamsTier2GRPCServingAddr, "Address on which the Substreans tier1 can reach the tier2") + // communication with tier2 + cmd.Flags().String("substreams-tier1-discovery-service-url", "", "URL to configure the grpc discovery service, used for communication with tier2") //traffic-director://xds?vpc_network=vpc-global&use_xds_reds=true + cmd.Flags().Bool("substreams-tier1-subrequests-insecure", false, "Connect to tier2 without checking certificate validity") + cmd.Flags().Bool("substreams-tier1-subrequests-plaintext", true, "Connect to tier2 without client in plaintext mode") + cmd.Flags().Int("substreams-tier1-max-subrequests", 4, "number of parallel subrequests that the tier1 can make to the tier2 per request") + cmd.Flags().String("block-type", "", "Block type to use for the substreams tier1 (Ex: sf.ethereum.type.v2.Block)") + + // all substreams + registerCommonSubstreamsFlags(cmd) + return nil + }, + + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + blockstreamAddr := viper.GetString("common-live-blocks-addr") + + authenticator, err := dauth.New(viper.GetString("common-auth-plugin"), appLogger) + if err != nil { + return nil, fmt.Errorf("unable to initialize dauth: %w", err) + } + + mergedBlocksStoreURL, oneBlocksStoreURL, forkedBlocksStoreURL, err := firecore.GetCommonStoresURLs(runtime.AbsDataDir) + if err != nil { + return nil, err + } + + sfDataDir := runtime.AbsDataDir + + rawServiceDiscoveryURL := viper.GetString("substreams-tier1-discovery-service-url") + grpcListenAddr := viper.GetString("substreams-tier1-grpc-listen-addr") + + stateStoreURL := firecore.MustReplaceDataDir(sfDataDir, viper.GetString("substreams-state-store-url")) + stateStoreDefaultTag := viper.GetString("substreams-state-store-default-tag") + + stateBundleSize := viper.GetUint64("substreams-state-bundle-size") + + subrequestsEndpoint := viper.GetString("substreams-tier1-subrequests-endpoint") + subrequestsInsecure := viper.GetBool("substreams-tier1-subrequests-insecure") + subrequestsPlaintext := viper.GetBool("substreams-tier1-subrequests-plaintext") + maxSubrequests := viper.GetUint64("substreams-tier1-max-subrequests") + + var blockType string + if chain.DefaultBlockType != "" { + blockType = chain.DefaultBlockType + } + + blockTypeFromFlag := viper.GetString("block-type") + + if blockTypeFromFlag != "" { + blockType = blockTypeFromFlag + } + + tracing := os.Getenv("SUBSTREAMS_TRACING") == "modules_exec" + + var serviceDiscoveryURL *url.URL + if rawServiceDiscoveryURL != "" { + serviceDiscoveryURL, err = url.Parse(rawServiceDiscoveryURL) + if err != nil { + return nil, fmt.Errorf("unable to parse discovery service url: %w", err) + } + err = discoveryservice.Bootstrap(serviceDiscoveryURL) + if err != nil { + return nil, fmt.Errorf("unable to bootstrap discovery service: %w", err) + } + } + + var wasmExtensions wasm.WASMExtensioner + if chain.RegisterSubstreamsExtensions != nil { + exts, err := chain.RegisterSubstreamsExtensions() + if err != nil { + return nil, fmt.Errorf("substreams extensions: %w", err) + } + wasmExtensions = exts + } + + meteringConfig := viper.GetString("common-metering-plugin") + + return app.NewTier1(appLogger, + &app.Tier1Config{ + MeteringConfig: meteringConfig, + + MergedBlocksStoreURL: mergedBlocksStoreURL, + OneBlocksStoreURL: oneBlocksStoreURL, + ForkedBlocksStoreURL: forkedBlocksStoreURL, + BlockStreamAddr: blockstreamAddr, + + StateStoreURL: stateStoreURL, + StateStoreDefaultTag: stateStoreDefaultTag, + StateBundleSize: stateBundleSize, + MaxSubrequests: maxSubrequests, + SubrequestsEndpoint: subrequestsEndpoint, + SubrequestsInsecure: subrequestsInsecure, + SubrequestsPlaintext: subrequestsPlaintext, + BlockType: blockType, + WASMExtensions: wasmExtensions, + + Tracing: tracing, + + GRPCListenAddr: grpcListenAddr, + GRPCShutdownGracePeriod: time.Second, + ServiceDiscoveryURL: serviceDiscoveryURL, + }, &app.Tier1Modules{ + Authenticator: authenticator, + HeadTimeDriftMetric: ss1HeadTimeDriftmetric, + HeadBlockNumberMetric: ss1HeadBlockNumMetric, + CheckPendingShutDown: runtime.IsPendingShutdown, + }), nil + }, + }) +} diff --git a/firehose/firehose-core/cmd/apps/substreams_tier2.go b/firehose/firehose-core/cmd/apps/substreams_tier2.go new file mode 100644 index 0000000..4b37508 --- /dev/null +++ b/firehose/firehose-core/cmd/apps/substreams_tier2.go @@ -0,0 +1,98 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package apps + +import ( + "fmt" + "net/url" + "os" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + discoveryservice "github.com/streamingfast/dgrpc/server/discovery-service" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/launcher" + "github.com/streamingfast/logging" + "github.com/streamingfast/substreams/app" + "github.com/streamingfast/substreams/wasm" + "go.uber.org/zap" +) + +var ss2HeadBlockNumMetric = metricset.NewHeadBlockNumber("substreams-tier2") +var ss2HeadTimeDriftmetric = metricset.NewHeadTimeDrift("substreams-tier2") + +func RegisterSubstreamsTier2App[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) { + appLogger, _ := logging.PackageLogger("substreams-tier2", "github.com/streamingfast/firehose-core/firehose-ethereum/substreams-tier2") + + launcher.RegisterApp(rootLog, &launcher.AppDef{ + ID: "substreams-tier2", + Title: "Substreams tier2 server", + Description: "Provides a substreams grpc endpoint", + RegisterFlags: func(cmd *cobra.Command) error { + cmd.Flags().String("substreams-tier2-grpc-listen-addr", firecore.SubstreamsTier2GRPCServingAddr, "Address on which the substreams tier2 will listen. Default is plain-text, appending a '*' to the end to jkkkj") + cmd.Flags().String("substreams-tier2-discovery-service-url", "", "URL to advertise presence to the grpc discovery service") //traffic-director://xds?vpc_network=vpc-global&use_xds_reds=true + cmd.Flags().Uint64("substreams-tier2-max-concurrent-requests", 0, "Maximum number of concurrent requests allowed on the server. When the tier2 service hits this limit, it will set itself as 'Not Ready' until requests are processed. Default 0 (no limit)") + + // all substreams + registerCommonSubstreamsFlags(cmd) + return nil + }, + + FactoryFunc: func(runtime *launcher.Runtime) (launcher.App, error) { + rawServiceDiscoveryURL := viper.GetString("substreams-tier2-discovery-service-url") + grpcListenAddr := viper.GetString("substreams-tier2-grpc-listen-addr") + + maximumConcurrentRequests := viper.GetUint64("substreams-tier2-max-concurrent-requests") + + tracing := os.Getenv("SUBSTREAMS_TRACING") == "modules_exec" + + var serviceDiscoveryURL *url.URL + if rawServiceDiscoveryURL != "" { + var err error + svcURL, err := url.Parse(rawServiceDiscoveryURL) + if err != nil { + return nil, fmt.Errorf("unable to parse discovery service url: %w", err) + } + err = discoveryservice.Bootstrap(svcURL) + if err != nil { + return nil, fmt.Errorf("unable to bootstrap discovery service: %w", err) + } + serviceDiscoveryURL = svcURL + } + + var wasmExtensions wasm.WASMExtensioner + if chain.RegisterSubstreamsExtensions != nil { + exts, err := chain.RegisterSubstreamsExtensions() + if err != nil { + return nil, fmt.Errorf("substreams extensions: %w", err) + } + wasmExtensions = exts + } + + return app.NewTier2(appLogger, + &app.Tier2Config{ + Tracing: tracing, + + GRPCListenAddr: grpcListenAddr, + ServiceDiscoveryURL: serviceDiscoveryURL, + WASMExtensions: wasmExtensions, + + MaximumConcurrentRequests: maximumConcurrentRequests, + }, &app.Tier2Modules{ + CheckPendingShutDown: runtime.IsPendingShutdown, + }), nil + }, + }) +} diff --git a/firehose/firehose-core/cmd/firecore/main.go b/firehose/firehose-core/cmd/firecore/main.go new file mode 100644 index 0000000..65d5472 --- /dev/null +++ b/firehose/firehose-core/cmd/firecore/main.go @@ -0,0 +1,27 @@ +package main + +import ( + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + firecore "github.com/streamingfast/firehose-core" + fhCMD "github.com/streamingfast/firehose-core/cmd" +) + +func main() { + firecore.UnsafeRunningFromFirecore = true + firecore.UnsafeAllowExecutableNameToBeEmpty = true + + fhCMD.Main(&firecore.Chain[*pbbstream.Block]{ + ShortName: "core", + LongName: "CORE", //only used to compose cmd title and description + FullyQualifiedModule: "github.com/streamingfast/firehose-core", + Version: version, + FirstStreamableBlock: 1, + BlockFactory: func() firecore.Block { return new(pbbstream.Block) }, + ConsoleReaderFactory: firecore.NewConsoleReader, + Tools: &firecore.ToolsConfig[*pbbstream.Block]{}, + DefaultBlockType: "sf.fuel.type.v1.Block", + }) +} + +// Version value, injected via go build `ldflags` at build time, **must** not be removed or inlined +var version = "dev" diff --git a/firehose/firehose-core/cmd/main.go b/firehose/firehose-core/cmd/main.go new file mode 100644 index 0000000..60de5e7 --- /dev/null +++ b/firehose/firehose-core/cmd/main.go @@ -0,0 +1,235 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + dauthgrpc "github.com/streamingfast/dauth/grpc" + dauthnull "github.com/streamingfast/dauth/null" + dauthsecret "github.com/streamingfast/dauth/secret" + dauthtrust "github.com/streamingfast/dauth/trust" + "github.com/streamingfast/dmetering" + dmeteringgrpc "github.com/streamingfast/dmetering/grpc" + dmeteringlogger "github.com/streamingfast/dmetering/logger" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/cmd/apps" + "github.com/streamingfast/firehose-core/cmd/tools" + "github.com/streamingfast/firehose-core/launcher" + + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +var rootCmd = &cobra.Command{} +var rootLog *zap.Logger +var rootTracer logging.Tracer + +// Main is the main entry point that configures everything and should be called from your Go +// 'main' entrypoint directly. +func Main[B firecore.Block](chain *firecore.Chain[B]) { + dauthgrpc.Register() + dauthnull.Register() + dauthsecret.Register() + dauthtrust.Register() + dmeteringgrpc.Register() + dmeteringlogger.Register() + dmetering.RegisterNull() + + chain.Validate() + chain.Init() + + binaryName := chain.BinaryName() + rootLog, rootTracer = logging.RootLogger(binaryName, chain.RootLoggerPackageID()) + + cobra.OnInitialize(func() { + cli.ConfigureViperForCommand(rootCmd, strings.ToUpper(binaryName)) + + // Compatibility to fetch `viper.GetXXX(....)` without `start-` prefix for flags on startCmd + apps.StartCmd.LocalFlags().VisitAll(func(flag *pflag.Flag) { + viper.BindPFlag(flag.Name, flag) + viper.BindEnv(sflags.MustGetViperKeyFromFlag(flag), strings.ToUpper(binaryName+"_"+strings.ReplaceAll(flag.Name, "-", "_"))) + }) + }) + + rootCmd.Use = binaryName + rootCmd.Short = fmt.Sprintf("Firehose on %s", chain.LongName) + rootCmd.Version = chain.VersionString() + + rootCmd.AddCommand(apps.StartCmd) + rootCmd.AddCommand(tools.ToolsCmd) + + (func(flags *pflag.FlagSet) { + flags.StringP("data-dir", "d", "./firehose-data", "Path to data storage for all components of the Firehose stack") + flags.StringP("config-file", "c", "./firehose.yaml", "Configuration file to use. No config file loaded if set to an empty string.") + + flags.String("log-format", "text", "Format for logging to stdout. Either 'text' or 'stackdriver'") + flags.Bool("log-to-file", true, "Also write logs to {data-dir}/firehose.log.json ") + flags.String("log-level-switcher-listen-addr", "localhost:1065", cli.FlagDescription(` + If non-empty, a JSON based HTTP server will listen on this address to let you switch the default logging level + of all registered loggers to a different one on the fly. This enables switching to debug level on + a live running production instance. Use 'curl -XPUT -d '{"level":"debug","inputs":"*"} http://localhost:1065' to + switch the level for all loggers. Each logger (even in transitive dependencies, at least those part of the core + StreamingFast's Firehose) are registered using two identifiers, the overarching component usually all loggers in a + library uses the same component name like 'bstream' or 'merger', and a fully qualified ID which is usually the Go + package fully qualified name in which the logger is defined. The 'inputs' can be either one or many component's name + like 'bstream|merger|firehose' or a regex that is matched against the fully qualified name. If there is a match for a + given logger, it will change its level to the one specified in 'level' field. The valid levels are 'trace', 'debug', + 'info', 'warn', 'error', 'panic'. Can be used to silence loggers by using 'panic' (well, technically it's not a full + silence but almost), or make them more verbose and change it back later. + `)) + flags.CountP("log-verbosity", "v", "Enables verbose output (-vvvv for max verbosity)") + + flags.String("metrics-listen-addr", ":9102", "If non-empty, the process will listen on this address to server the Prometheus metrics collected by the components.") + flags.String("pprof-listen-addr", "localhost:6060", "If non-empty, the process will listen on this address for pprof analysis (see https://golang.org/pkg/net/http/pprof/)") + flags.Duration("startup-delay", 0, cli.FlagDescription(` + Delay before launching the components defined in config file or via the command line arguments. This can be used to perform + maintenance operations on a running container or pod prior it will actually start processing. Useful for example to clear + a persistent disks of its content before starting, cleary cached content to try to resolve bugs, etc. + `)) + })(rootCmd.PersistentFlags()) + + registerCommonFlags(chain) + apps.RegisterReaderNodeApp(chain, rootLog) + apps.RegisterReaderNodeStdinApp(chain, rootLog) + apps.RegisterMergerApp(rootLog) + apps.RegisterRelayerApp(rootLog) + apps.RegisterFirehoseApp(chain, rootLog) + apps.RegisterSubstreamsTier1App(chain, rootLog) + apps.RegisterSubstreamsTier2App(chain, rootLog) + + if len(chain.BlockIndexerFactories) > 0 { + apps.RegisterIndexBuilderApp(chain, rootLog) + } + + startFlags := apps.StartCmd.Flags() + + if chain.RegisterExtraStartFlags != nil { + chain.RegisterExtraStartFlags(startFlags) + } + + if chain.ReaderNodeBootstrapperFactory != nil && startFlags.Lookup("reader-node-bootstrap-data-url") == nil { + startFlags.String("reader-node-bootstrap-data-url", "", firecore.DefaultReaderNodeBootstrapDataURLFlagDescription()) + } + + apps.ConfigureStartCmd(chain, binaryName, rootLog) + + if err := tools.ConfigureToolsCmd(chain, rootLog, rootTracer); err != nil { + exitWithError("registering tools command", err) + } + + if err := launcher.RegisterFlags(rootLog, apps.StartCmd); err != nil { + exitWithError("registering application flags", err) + } + + var availableCmds []string + for app := range launcher.AppRegistry { + availableCmds = append(availableCmds, app) + } + + apps.StartCmd.SetHelpTemplate(fmt.Sprintf(startCmdHelpTemplate, strings.Join(availableCmds, "\n "))) + apps.StartCmd.Example = fmt.Sprintf("%s start reader-node", binaryName) + + rootCmd.PersistentPreRunE = func(cmd *cobra.Command, _ []string) error { + if err := setupCmd(cmd, chain.BinaryName()); err != nil { + return err + } + + startupDelay := viper.GetDuration("global-startup-delay") + if startupDelay > 0 { + rootLog.Info("sleeping before starting apps", zap.Duration("delay", startupDelay)) + time.Sleep(startupDelay) + } + + return nil + } + + if err := rootCmd.Execute(); err != nil { + exitWithError("failed to run", err) + } +} + +func exitWithError(message string, err error) { + rootLog.Error(message, zap.Error(err)) + rootLog.Sync() + os.Exit(1) +} + +func registerCommonFlags[B firecore.Block](chain *firecore.Chain[B]) { + launcher.RegisterCommonFlags = func(_ *zap.Logger, cmd *cobra.Command) error { + // Common stores configuration flags + cmd.Flags().String("common-one-block-store-url", firecore.OneBlockStoreURL, "[COMMON] Store URL to read/write one-block files") + cmd.Flags().String("common-merged-blocks-store-url", firecore.MergedBlocksStoreURL, "[COMMON] Store URL where to read/write merged blocks.") + cmd.Flags().String("common-forked-blocks-store-url", firecore.ForkedBlocksStoreURL, "[COMMON] Store URL where to read/write forked block files that we want to keep.") + cmd.Flags().String("common-live-blocks-addr", firecore.RelayerServingAddr, "[COMMON] gRPC endpoint to get real-time blocks.") + + cmd.Flags().String("common-index-store-url", firecore.IndexStoreURL, "[COMMON] Store URL where to read/write index files (if used on the chain).") + cmd.Flags().IntSlice("common-index-block-sizes", []int{100000, 10000, 1000, 100}, "Index bundle sizes that that are considered valid when looking for block indexes") + + cmd.Flags().Bool("common-blocks-cache-enabled", false, cli.FlagDescription(` + [COMMON] Use a disk cache to store the blocks data to disk and instead of keeping it in RAM. By enabling this, block's Protobuf content, in bytes, + is kept on file system instead of RAM. This is done as soon the block is downloaded from storage. This is a tradeoff between RAM and Disk, if you + are going to serve only a handful of concurrent requests, it's suggested to keep is disabled, if you encounter heavy RAM consumption issue, specially + by the firehose component, it's definitely a good idea to enable it and configure it properly through the other 'common-blocks-cache-...' flags. The cache is + split in two portions, one keeping N total bytes of blocks of the most recently used blocks and the other one keeping the N earliest blocks as + requested by the various consumers of the cache. + `)) + cmd.Flags().String("common-blocks-cache-dir", firecore.BlocksCacheDirectory, cli.FlagDescription(` + [COMMON] Blocks cache directory where all the block's bytes will be cached to disk instead of being kept in RAM. + This should be a disk that persists across restarts of the Firehose component to reduce the the strain on the disk + when restarting and streams reconnects. The size of disk must at least big (with a 10%% buffer) in bytes as the sum of flags' + value for 'common-blocks-cache-max-recent-entry-bytes' and 'common-blocks-cache-max-entry-by-age-bytes'. + `)) + cmd.Flags().Int("common-blocks-cache-max-recent-entry-bytes", 21474836480, cli.FlagDescription(` + [COMMON] Blocks cache max size in bytes of the most recently used blocks, after the limit is reached, blocks are evicted from the cache. + `)) + cmd.Flags().Int("common-blocks-cache-max-entry-by-age-bytes", 21474836480, cli.FlagDescription(` + [COMMON] Blocks cache max size in bytes of the earliest used blocks, after the limit is reached, blocks are evicted from the cache. + `)) + + cmd.Flags().Int("common-first-streamable-block", int(chain.FirstStreamableBlock), "[COMMON] First streamable block of the chain") + + // Authentication, metering and rate limiter plugins + cmd.Flags().String("common-auth-plugin", "null://", "[COMMON] Auth plugin URI, see streamingfast/dauth repository") + cmd.Flags().String("common-metering-plugin", "null://", "[COMMON] Metering plugin URI, see streamingfast/dmetering repository") + + // System Behavior + cmd.Flags().Uint64("common-auto-mem-limit-percent", 0, "[COMMON] Automatically sets GOMEMLIMIT to a percentage of memory limit from cgroup (useful for container environments)") + cmd.Flags().Bool("common-auto-max-procs", false, "[COMMON] Automatically sets GOMAXPROCS to max cpu available from cgroup (useful for container environments)") + cmd.Flags().Duration("common-system-shutdown-signal-delay", 0, cli.FlagDescription(` + [COMMON] Add a delay between receiving SIGTERM signal and shutting down apps. + Apps will respond negatively to /healthz during this period + `)) + return nil + } +} + +var startCmdHelpTemplate = `Usage:{{if .Runnable}} + {{.UseLine}}{{end}} [all|command1 [command2...]]{{if gt (len .Aliases) 0}} + +Aliases: + {{.NameAndAliases}}{{end}}{{if .HasExample}} + +Examples: + {{.Example}}{{end}} + +Available Commands: + %s{{if .HasAvailableLocalFlags}} + +Flags: +{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} + +Global Flags: +{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} + +Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} + {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} + +Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} +` diff --git a/firehose/firehose-core/cmd/setup.go b/firehose/firehose-core/cmd/setup.go new file mode 100644 index 0000000..a609703 --- /dev/null +++ b/firehose/firehose-core/cmd/setup.go @@ -0,0 +1,148 @@ +package cmd + +import ( + "fmt" + _ "net/http/pprof" + "os" + "strings" + + "github.com/streamingfast/firehose-core/cmd/apps" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/firehose-core/launcher" +) + +func setupCmd(cmd *cobra.Command, binaryName string) error { + cmd.SilenceUsage = true + + cmds := extractCmd(cmd) + subCommand := cmds[len(cmds)-1] + + forceConfigOn := []*cobra.Command{apps.StartCmd} + logToFileOn := []*cobra.Command{apps.StartCmd} + + if configFile := viper.GetString("global-config-file"); configFile != "" { + exists, err := fileExists(configFile) + if err != nil { + return fmt.Errorf("unable to check if config file exists: %w", err) + } + + if !exists && isMatchingCommand(cmds, forceConfigOn) { + return fmt.Errorf("unable to find config file %q", configFile) + } + + if exists { + if err := launcher.LoadConfigFile(configFile); err != nil { + return fmt.Errorf("unable to read config file %q: %w", configFile, err) + } + } + } + + startFlagByName := getStartFlags() + + subconf := launcher.Config[subCommand] + if subconf != nil { + for k, v := range subconf.Flags { + flag, found := startFlagByName[k] + if !found { + return fmt.Errorf("invalid flag %s in config file under command %s", k, subCommand) + } + + viper.SetDefault(flag.viperKey, v) + + // For root command, we want to keep compatibility for `viper.GetXXX("global-")` to work with config loaded value + if strings.HasPrefix(flag.viperKey, "global.") { + viper.SetDefault(strings.Replace(flag.viperKey, "global.", "global-", 1), v) + } + + // For 'start' command, we want to keep compatibility for `viper.GetXXX("")` to work with config loaded value + if strings.HasPrefix(flag.viperKey, "start.") { + viper.SetDefault(strings.TrimPrefix(flag.viperKey, "start."), v) + } + } + } + + launcher.SetupLogger(rootLog, &launcher.LoggingOptions{ + WorkingDir: viper.GetString("global-data-dir"), + // We add +1 so our default verbosity is to show all packages in INFO mode + Verbosity: viper.GetInt("global-log-verbosity") + 1, + LogFormat: viper.GetString("global-log-format"), + LogToFile: isMatchingCommand(cmds, logToFileOn) && viper.GetBool("global-log-to-file"), + LogListenAddr: viper.GetString("global-log-level-switcher-listen-addr"), + LogToStderr: true, + }) + + launcher.SetupTracing(binaryName) + launcher.SetupAnalyticsMetrics(rootLog, viper.GetString("global-metrics-listen-addr"), viper.GetString("global-pprof-listen-addr")) + launcher.SetAutoMemoryLimit(viper.GetUint64("common-auto-mem-limit-percent"), rootLog) + + if viper.GetBool("common-auto-max-procs") { + launcher.SetAutoMaxProcs(rootLog) + } + + return nil +} + +func isMatchingCommand(cmds []string, runSetupOn []*cobra.Command) bool { + for _, c := range runSetupOn { + baseChunks := extractCmd(c) + if strings.Join(cmds, ".") == strings.Join(baseChunks, ".") { + return true + } + } + return false +} + +func extractCmd(cmd *cobra.Command) []string { + cmds := []string{} + for { + if cmd == nil { + break + } + cmds = append(cmds, cmd.Use) + cmd = cmd.Parent() + } + + out := make([]string, len(cmds)) + + for itr, v := range cmds { + newIndex := len(cmds) - 1 - itr + out[newIndex] = v + } + return out +} + +func fileExists(file string) (bool, error) { + stat, err := os.Stat(file) + if os.IsNotExist(err) { + return false, nil + } + + if err != nil { + return false, err + } + + return !stat.IsDir(), nil +} + +type flagInfo struct { + originalName string + viperKey string +} + +func getStartFlags() (byName map[string]*flagInfo) { + byName = make(map[string]*flagInfo) + + rootCmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) { + byName[flag.Name] = &flagInfo{flag.Name, sflags.MustGetViperKeyFromFlag(flag)} + }) + + apps.StartCmd.LocalFlags().VisitAll(func(flag *pflag.Flag) { + byName[flag.Name] = &flagInfo{flag.Name, sflags.MustGetViperKeyFromFlag(flag)} + }) + + return byName +} diff --git a/firehose/firehose-core/cmd/tools/check/blocks.go b/firehose/firehose-core/cmd/tools/check/blocks.go new file mode 100644 index 0000000..33904eb --- /dev/null +++ b/firehose/firehose-core/cmd/tools/check/blocks.go @@ -0,0 +1,310 @@ +package check + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "regexp" + "strconv" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + print2 "github.com/streamingfast/firehose-core/cmd/tools/print" + "github.com/streamingfast/firehose-core/types" + "go.uber.org/zap" +) + +var numberRegex = regexp.MustCompile(`(\d{10})`) + +type PrintDetails uint8 + +const ( + PrintNoDetails PrintDetails = iota + PrintStats + PrintFull +) + +func CheckMergedBlocks[B firecore.Block](ctx context.Context, chain *firecore.Chain[B], logger *zap.Logger, storeURL string, fileBlockSize uint64, blockRange types.BlockRange, printDetails PrintDetails) error { + readAllBlocks := printDetails != PrintNoDetails + fmt.Printf("Checking block holes on %s\n", storeURL) + if readAllBlocks { + fmt.Println("Detailed printing requested: All block files will be read and checked for continuity. This may take a while...") + } + + var expected uint64 + var count int + var highestBlockSeen uint64 + lowestBlockSeen := firecore.MaxUint64 + + holeFound := false + expected = types.RoundToBundleStartBlock(uint64(blockRange.Start), fileBlockSize) + currentStartBlk := uint64(blockRange.Start) + + blocksStore, err := dstore.NewDBinStore(storeURL) + if err != nil { + return err + } + + walkPrefix := WalkBlockPrefix(blockRange, fileBlockSize) + + tfdb := &trackedForkDB{ + fdb: forkable.NewForkDB(), + } + + logger.Debug("walking merged blocks", zap.Stringer("block_range", blockRange), zap.String("walk_prefix", walkPrefix)) + err = blocksStore.Walk(ctx, walkPrefix, func(filename string) error { + match := numberRegex.FindStringSubmatch(filename) + if match == nil { + return nil + } + + logger.Debug("received merged blocks", zap.String("filename", filename)) + + count++ + baseNum, _ := strconv.ParseUint(match[1], 10, 32) + if baseNum+uint64(fileBlockSize)-1 < uint64(blockRange.Start) { + logger.Debug("base num lower then block range start, quitting", zap.Uint64("base_num", baseNum), zap.Int64("starting_at", blockRange.Start)) + return nil + } + + if baseNum != expected { + // There is no previous valid block range if we are at the ever first seen file + if count > 1 { + fmt.Printf("✅ Range %s\n", types.NewClosedRange(int64(currentStartBlk), uint64(types.RoundToBundleEndBlock(expected-fileBlockSize, fileBlockSize)))) + } + + // Otherwise, we do not follow last seen element (previous is `100 - 199` but we are `299 - 300`) + missingRange := types.NewClosedRange(int64(expected), types.RoundToBundleEndBlock(baseNum-fileBlockSize, fileBlockSize)) + fmt.Printf("❌ Range %s (Missing, [%s])\n", missingRange, missingRange.ReprocRange()) + currentStartBlk = baseNum + + holeFound = true + } + expected = baseNum + fileBlockSize + + if readAllBlocks { + lowestBlockSegment, highestBlockSegment := validateBlockSegment(ctx, chain, blocksStore, filename, fileBlockSize, blockRange, printDetails, tfdb) + if lowestBlockSegment < lowestBlockSeen { + lowestBlockSeen = lowestBlockSegment + } + if highestBlockSegment > highestBlockSeen { + highestBlockSeen = highestBlockSegment + } + } else { + if baseNum < lowestBlockSeen { + lowestBlockSeen = baseNum + } + if baseNum+fileBlockSize > highestBlockSeen { + highestBlockSeen = baseNum + fileBlockSize + } + } + + if count%10000 == 0 { + fmt.Printf("✅ Range %s\n", types.NewClosedRange(int64(currentStartBlk), types.RoundToBundleEndBlock(baseNum, fileBlockSize))) + currentStartBlk = baseNum + fileBlockSize + } + + if blockRange.IsClosed() && types.RoundToBundleEndBlock(baseNum, fileBlockSize) >= *blockRange.Stop-1 { + return dstore.StopIteration + } + + return nil + }) + + if err != nil { + return err + } + + logger.Debug("checking incomplete range", + zap.Stringer("range", blockRange), + zap.Bool("range_unbounded", blockRange.IsOpen()), + zap.Uint64("lowest_block_seen", lowestBlockSeen), + zap.Uint64("highest_block_seen", highestBlockSeen), + ) + if tfdb.lastLinkedBlock != nil && tfdb.lastLinkedBlock.Number < highestBlockSeen { + fmt.Printf("🔶 Range %s has issues with forks, last linkable block number: %d\n", types.NewClosedRange(int64(currentStartBlk), uint64(highestBlockSeen)), tfdb.lastLinkedBlock.Number) + } else { + fmt.Printf("✅ Range %s\n", types.NewClosedRange(int64(currentStartBlk), uint64(highestBlockSeen))) + } + + fmt.Println() + fmt.Println("Summary:") + + if blockRange.IsClosed() && + (highestBlockSeen < uint64(*blockRange.Stop-1) || + (lowestBlockSeen > uint64(blockRange.Start) && lowestBlockSeen > bstream.GetProtocolFirstStreamableBlock)) { + fmt.Printf("> 🔶 Incomplete range %s, started at block %s and stopped at block: %s\n", blockRange, types.PrettyBlockNum(lowestBlockSeen), types.PrettyBlockNum(highestBlockSeen)) + } + + if holeFound { + fmt.Printf("> 🆘 Holes found!\n") + } else { + fmt.Printf("> 🆗 No hole found\n") + } + + return nil +} + +type trackedForkDB struct { + fdb *forkable.ForkDB + firstUnlinkableBlock *pbbstream.Block + lastLinkedBlock *pbbstream.Block + unlinkableSegmentCount int +} + +func validateBlockSegment[B firecore.Block]( + ctx context.Context, + chain *firecore.Chain[B], + store dstore.Store, + segment string, + fileBlockSize uint64, + blockRange types.BlockRange, + printDetails PrintDetails, + tfdb *trackedForkDB, +) (lowestBlockSeen, highestBlockSeen uint64) { + lowestBlockSeen = firecore.MaxUint64 + reader, err := store.OpenObject(ctx, segment) + if err != nil { + fmt.Printf("❌ Unable to read blocks segment %s: %s\n", segment, err) + return + } + defer reader.Close() + + readerFactory, err := bstream.NewDBinBlockReader(reader) + if err != nil { + fmt.Printf("❌ Unable to read blocks segment %s: %s\n", segment, err) + return + } + + seenBlockCount := 0 + for { + block, err := readerFactory.Read() + if block != nil { + if block.Number < uint64(blockRange.Start) { + continue + } + + if blockRange.IsClosed() && block.Number > *blockRange.Stop { + return + } + + if block.Number < lowestBlockSeen { + lowestBlockSeen = block.Number + } + if block.Number > highestBlockSeen { + highestBlockSeen = block.Number + } + + if !tfdb.fdb.HasLIB() { + tfdb.fdb.InitLIB(block.AsRef()) + } + + tfdb.fdb.AddLink(block.AsRef(), block.ParentId, nil) + revSeg, _ := tfdb.fdb.ReversibleSegment(block.AsRef()) + if revSeg == nil { + tfdb.unlinkableSegmentCount++ + if tfdb.firstUnlinkableBlock == nil { + tfdb.firstUnlinkableBlock = block + } + + // TODO: this print should be under a 'check forkable' flag? + fmt.Printf("🔶 Block #%d is not linkable at this point\n", block.Number) + + if tfdb.unlinkableSegmentCount > 99 && tfdb.unlinkableSegmentCount%100 == 0 { + // TODO: this print should be under a 'check forkable' flag? + fmt.Printf("❌ Large gap of %d unlinkable blocks found in chain. Last linked block: %d, first Unlinkable block: %d. \n", tfdb.unlinkableSegmentCount, tfdb.lastLinkedBlock.Number, tfdb.firstUnlinkableBlock.Number) + } + } else { + tfdb.lastLinkedBlock = block + tfdb.unlinkableSegmentCount = 0 + tfdb.firstUnlinkableBlock = nil + tfdb.fdb.SetLIB(block.AsRef(), block.LibNum) + if tfdb.fdb.HasLIB() { + tfdb.fdb.PurgeBeforeLIB(0) + } + } + seenBlockCount++ + + if printDetails == PrintStats { + err := print2.PrintBStreamBlock(block, false, os.Stdout) + if err != nil { + fmt.Printf("❌ Unable to print block %s: %s\n", block.AsRef(), err) + continue + } + } + + if printDetails == PrintFull { + var b = chain.BlockFactory() + + if _, ok := b.(*pbbstream.Block); ok { + //todo: implements when buf registry available ... + panic("printing full block is not supported for pbbstream.Block") + } + + if err := block.Payload.UnmarshalTo(b); err != nil { + fmt.Printf("❌ Unable unmarshall block %s: %s\n", block.AsRef(), err) + break + } + + out, err := json.MarshalIndent(b, "", " ") + + if err != nil { + fmt.Printf("❌ Unable to print full block %s: %s\n", block.AsRef(), err) + continue + } + + fmt.Println(string(out)) + } + + continue + } + + if block == nil && err == io.EOF { + if seenBlockCount < expectedBlockCount(segment, fileBlockSize) { + fmt.Printf("🔶 Segment %s contained only %d blocks (< 100), this can happen on some chains\n", segment, seenBlockCount) + } + + return + } + + if err != nil { + fmt.Printf("❌ Unable to read all blocks from segment %s after reading %d blocks: %s\n", segment, seenBlockCount, err) + return + } + } + return +} + +func WalkBlockPrefix(blockRange types.BlockRange, fileBlockSize uint64) string { + if blockRange.IsOpen() { + return "" + } + + startString := fmt.Sprintf("%010d", types.RoundToBundleStartBlock(uint64(blockRange.Start), fileBlockSize)) + endString := fmt.Sprintf("%010d", types.RoundToBundleEndBlock(*blockRange.Stop-1, fileBlockSize)+1) + + offset := 0 + for i := 0; i < len(startString); i++ { + if startString[i] != endString[i] { + return string(startString[0:i]) + } + + offset++ + } + + // At this point, the two strings are equal, to return the string + return startString +} + +func expectedBlockCount(segment string, fileBlockSize uint64) int { + if segment == "0000000000" { + return int(fileBlockSize - bstream.GetProtocolFirstStreamableBlock) + } + + return int(fileBlockSize) +} diff --git a/firehose/firehose-core/cmd/tools/check/check.go b/firehose/firehose-core/cmd/tools/check/check.go new file mode 100644 index 0000000..3ee204c --- /dev/null +++ b/firehose/firehose-core/cmd/tools/check/check.go @@ -0,0 +1,201 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package check + +import ( + "fmt" + "slices" + "strings" + + "github.com/dustin/go-humanize" + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/types" + "go.uber.org/zap" + "golang.org/x/exp/maps" +) + +func NewCheckCommand[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) *cobra.Command { + + toolsCheckCmd := &cobra.Command{Use: "check", Short: "Various checks for deployment, data integrity & debugging"} + + toolsCheckForksCmd := &cobra.Command{ + Use: "forks ", + Short: "Reads all forked blocks you have and print longest linkable segments for each fork", + Args: cobra.ExactArgs(1), + } + + var ( + toolsCheckMergedBlocksCmd = &cobra.Command{ + // TODO: Not sure, it's now a required thing, but we could probably use the same logic as `start` + // and avoid altogether passing the args. If this would also load the config and everything else, + // that would be much more seamless! + Use: "merged-blocks ", + Short: "Checks for any holes in merged blocks as well as ensuring merged blocks integrity", + Args: cobra.ExactArgs(1), + } + ) + + toolsCheckCmd.AddCommand(newCheckMergedBlockBatchCmd()) + toolsCheckCmd.AddCommand(toolsCheckForksCmd) + toolsCheckCmd.AddCommand(toolsCheckMergedBlocksCmd) + + toolsCheckCmd.PersistentFlags().StringP("range", "r", "", "Block range to use for the check") + + toolsCheckMergedBlocksCmd.Flags().BoolP("print-stats", "s", false, "Natively decode each block in the segment and print statistics about it, ensuring it contains the required blocks") + toolsCheckMergedBlocksCmd.Flags().BoolP("print-full", "f", false, "Natively decode each block and print the full JSON representation of the block, should be used with a small range only if you don't want to be overwhelmed") + + toolsCheckForksCmd.Flags().Uint64("min-depth", 1, "Only show forks that are at least this deep") + toolsCheckForksCmd.Flags().Uint64("after-block", 0, "Only show forks that happened after this block number, if value is not 0") + + toolsCheckMergedBlocksCmd.RunE = createToolsCheckMergedBlocksE(chain, rootLog) + toolsCheckMergedBlocksCmd.Example = firecore.ExamplePrefixed(chain, "tools check merged-blocks", ` + "./sf-data/storage/merged-blocks" + "gs:////" -s + "s3:////" -f + "az:////" -r ":1_000_000" + "az:////" -r "100_000:1_000_000" + `) + + toolsCheckForksCmd.RunE = toolsCheckForksE + + return toolsCheckCmd +} + +func createToolsCheckMergedBlocksE[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + storeURL := args[0] + fileBlockSize := uint64(100) + + blockRange, err := types.GetBlockRangeFromFlagDefault(cmd, "range", types.NewOpenRange(0)) + if err != nil { + return err + } + + printDetails := PrintNoDetails + if sflags.MustGetBool(cmd, "print-stats") { + printDetails = PrintStats + } + + if sflags.MustGetBool(cmd, "print-full") { + printDetails = PrintFull + } + + return CheckMergedBlocks(cmd.Context(), chain, rootLog, storeURL, fileBlockSize, blockRange, printDetails) + } +} + +func toolsCheckForksE(cmd *cobra.Command, args []string) error { + storeURL := args[0] + + blocksStore, err := dstore.NewDBinStore(storeURL) + cli.NoError(err, "unable to create blocks store") + + oneBlockFiles := []*bstream.OneBlockFile{} + oneBlockFilesByID := map[string]*bstream.OneBlockFile{} + err = blocksStore.Walk(cmd.Context(), "", func(filename string) error { + file, err := bstream.NewOneBlockFile(filename) + cli.NoError(err, "unable to parse block filename %q", filename) + + oneBlockFiles = append(oneBlockFiles, file) + oneBlockFilesByID[file.ID] = file + return nil + }) + cli.NoError(err, "unable to walk blocks store") + + if len(oneBlockFiles) == 0 { + fmt.Println("No forked blocks found") + } + + parentOf := map[string]*bstream.OneBlockFile{} + for _, file := range oneBlockFiles { + parentOf[file.ID] = oneBlockFilesByID[file.PreviousID] + } + + // All forks indexed by their lowest height parent, list is always sorted by height + links := map[string][]*bstream.OneBlockFile{} + for _, file := range oneBlockFiles { + links[file.ID] = []*bstream.OneBlockFile{file} + } + + for { + changed := false + for root, chain := range links { + if parent := parentOf[root]; parent != nil { + delete(links, root) + links[parent.ID] = append([]*bstream.OneBlockFile{parent}, chain...) + changed = true + } + + } + if !changed { + break + } + } + + sortedKeys := maps.Keys(links) + slices.SortFunc(sortedKeys, func(a, b string) int { + if links[a][0].Num < links[b][0].Num { + return -1 + } + + if links[a][0].Num == links[b][0].Num { + return 0 + } + + return 1 + }) + + minDepth := sflags.MustGetInt(cmd, "min-depth") + afterBlock := sflags.MustGetUint64(cmd, "after-block") + + for _, key := range sortedKeys { + link := links[key] + + if len(link) < int(minDepth) { + continue + } + + if afterBlock != 0 && link[0].Num <= afterBlock { + continue + } + + chain := make([]string, len(link)) + for i, segment := range link { + spaces := strings.Repeat(" ", (i)+2) + + canonical := "" + if i == 0 { + canonical = " (on chain)" + } + + chain[i] = fmt.Sprintf(spaces+"#%d [%s <= %s%s]", blockNumber(segment.Num), segment.ID, segment.PreviousID, canonical) + } + + fmt.Printf("Fork Depth %d\n%s\n\n", len(link), strings.Join(chain, "\n")) + } + + return nil +} + +type blockNumber uint64 + +func (b blockNumber) String() string { + return fmt.Sprintf("#%s", humanize.Comma(int64(b))) +} diff --git a/firehose/firehose-core/cmd/tools/check/merged_batch.go b/firehose/firehose-core/cmd/tools/check/merged_batch.go new file mode 100644 index 0000000..309d364 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/check/merged_batch.go @@ -0,0 +1,202 @@ +package check + +import ( + "context" + "fmt" + "io" + "strconv" + "strings" + + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + "github.com/streamingfast/firehose-core/types" +) + +type blockRef struct { + hash string + num uint64 +} + +func (b *blockRef) reset() { + b.hash = "" + b.num = 0 +} + +func (b *blockRef) set(hash string, num uint64) { + b.hash = hash + b.num = num +} + +func (b *blockRef) isUnset() bool { + return b.hash == "" && b.num == 0 +} + +// CheckMergedBlocksBatch will write a list of base-block-numbers to a store, for merged-blocks-files that are broken or missing +// broken merged-blocks-files are the ones that contain "empty" blocks (no ID) or unlinkable blocks +// there could be false positives on unlinkable blocks, though +// output files are like this: 0000123100.broken 0000123500.missing +func CheckMergedBlocksBatch( + ctx context.Context, + sourceStoreURL string, + destStoreURL string, + fileBlockSize uint64, + blockRange types.BlockRange, +) error { + if !blockRange.IsResolved() { + return fmt.Errorf("check merged blocks can only work with fully resolved range, got %s", blockRange) + } + + expected := types.RoundToBundleStartBlock(uint64(blockRange.Start), fileBlockSize) + fileBlockSize64 := uint64(fileBlockSize) + + blocksStore, err := dstore.NewDBinStore(sourceStoreURL) + if err != nil { + return err + } + var destStore dstore.Store + if destStoreURL != "" { + destStore, err = dstore.NewSimpleStore(destStoreURL) + if err != nil { + return err + } + } + + var firstFilename = fmt.Sprintf("%010d", types.RoundToBundleStartBlock(uint64(blockRange.Start), fileBlockSize)) + + lastSeenBlock := &blockRef{} + + var lastFilename string + err = blocksStore.WalkFrom(ctx, "", firstFilename, func(filename string) error { + if strings.HasSuffix(filename, ".tmp") { + return nil + } + match := numberRegex.FindStringSubmatch(filename) + if match == nil { + return nil + } + + // should not happen with firstFilename, but leaving in case + baseNum, _ := strconv.ParseUint(match[1], 10, 32) + if baseNum+uint64(fileBlockSize)-1 < uint64(blockRange.Start) { + return nil + } + + if baseNum < uint64(expected) { + return fmt.Errorf("unhandled error: found base number %d below expected %d", baseNum, expected) + } + for expected < baseNum { + fmt.Printf("missing file %q\n", filename) + if destStore != nil { + outputFile := fmt.Sprintf("%010d.missing", expected) + destStore.WriteObject(ctx, outputFile, strings.NewReader("")) + } + expected += fileBlockSize64 + } + + broken, details, err := checkMergedBlockFileBroken(ctx, blocksStore, filename, lastSeenBlock) + if broken { + if lastSeenBlock.isUnset() { + fmt.Printf("found broken file %q, %s\n", filename, details) + if destStore != nil { + outputFile := fmt.Sprintf("%010d.broken", baseNum) + destStore.WriteObject(ctx, outputFile, strings.NewReader("")) + } + } else { + brokenSince := types.RoundToBundleStartBlock(uint64(lastSeenBlock.num+1), 100) + for i := brokenSince; i <= baseNum; i += fileBlockSize64 { + fmt.Printf("found broken file %q, %s\n", filename, details) + if destStore != nil { + outputFile := fmt.Sprintf("%010d.broken", i) + err := destStore.WriteObject(ctx, outputFile, strings.NewReader("")) + if err != nil { + return fmt.Errorf("unable to write broken file %q: %w", outputFile, err) + } + } + } + } + lastSeenBlock.reset() + } + lastFilename = filename + + if err != nil { + return err + } + + if blockRange.IsClosed() && types.RoundToBundleEndBlock(baseNum, fileBlockSize) >= *blockRange.Stop-1 { + return dstore.StopIteration + } + expected = baseNum + fileBlockSize64 + + return nil + }) + fmt.Println("last file processed:", lastFilename) + if err != nil { + return err + } + + return nil +} + +var printCounter = 0 + +func checkMergedBlockFileBroken( + ctx context.Context, + store dstore.Store, + filename string, + lastSeenBlock *blockRef, +) (broken bool, details string, err error) { + if printCounter%100 == 0 { + fmt.Println("checking", filename, "... (printing 1/100)") + } + printCounter++ + + reader, err := store.OpenObject(ctx, filename) + if err != nil { + return true, "", err + } + defer reader.Close() + + readerFactory, err := bstream.NewDBinBlockReader(reader) + if err != nil { + return true, "", err + } + + for { + var block *pbbstream.Block + block, err = readerFactory.Read() + + if block == nil { + if err == io.EOF { + err = nil + } + return + } + if err != nil { + return + } + + if block.Id == "" { + broken = true + details = "read block with no ID" + return + } + + if lastSeenBlock.isUnset() { + fakePreviousNum := block.Number + if fakePreviousNum != 0 { + fakePreviousNum -= 1 + } + lastSeenBlock.set(block.ParentId, fakePreviousNum) + } + if block.ParentId != lastSeenBlock.hash { + if block.Id == lastSeenBlock.hash && block.Number == lastSeenBlock.num { + continue + } + details = fmt.Sprintf("broken on block %d: expecting %q, got %q", block.Number, lastSeenBlock.hash, block.ParentId) + broken = true + return + } + lastSeenBlock.set(block.Id, block.Number) + } +} diff --git a/firehose/firehose-core/cmd/tools/check/mergedbatch.go b/firehose/firehose-core/cmd/tools/check/mergedbatch.go new file mode 100644 index 0000000..3b484eb --- /dev/null +++ b/firehose/firehose-core/cmd/tools/check/mergedbatch.go @@ -0,0 +1,57 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package check + +import ( + "strconv" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/firehose-core/types" +) + +func newCheckMergedBlockBatchCmd() *cobra.Command { + var toolsCheckMergedBlocksBatchCmd = &cobra.Command{ + Use: "merged-blocks-batch ", + Short: "Checks for any missing, disordered or duplicate blocks in merged blocks files", + Args: cobra.ExactArgs(3), + RunE: checkMergedBlocksBatchRunE, + } + toolsCheckMergedBlocksBatchCmd.PersistentFlags().String("output-to-store", "", "If non-empty, an empty file called .broken will be created for every problematic merged-blocks-file. This is a convenient way to gather the results from multiple parallel processes.") + return toolsCheckMergedBlocksBatchCmd + +} + +func checkMergedBlocksBatchRunE(cmd *cobra.Command, args []string) error { + storeURL := args[0] + start, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return err + } + stop, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return err + } + fileBlockSize := uint64(100) + + blockRange := types.BlockRange{ + Start: int64(start), + Stop: &stop, + } + + resultsStoreURL := sflags.MustGetString(cmd, "output-to-store") + + return CheckMergedBlocksBatch(cmd.Context(), storeURL, resultsStoreURL, fileBlockSize, blockRange) +} diff --git a/firehose/firehose-core/cmd/tools/compare/tools_compare_blocks.go b/firehose/firehose-core/cmd/tools/compare/tools_compare_blocks.go new file mode 100644 index 0000000..063a116 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/compare/tools_compare_blocks.go @@ -0,0 +1,397 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package compare + +import ( + "context" + "fmt" + "io" + "reflect" + "strconv" + "sync" + + jd "github.com/josephburnett/jd/lib" + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/cmd/tools/check" + "github.com/streamingfast/firehose-core/json" + fcproto "github.com/streamingfast/firehose-core/proto" + "github.com/streamingfast/firehose-core/types" + "go.uber.org/multierr" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/dynamicpb" +) + +type BlockDifferences struct { + BlockNumber uint64 + Differences []string +} + +func NewToolsCompareBlocksCmd[B firecore.Block](chain *firecore.Chain[B]) *cobra.Command { + cmd := &cobra.Command{ + Use: "compare-blocks []", + Short: "Checks for any differences between two block stores between a specified range. (To compare the likeness of two block ranges, for example)", + Long: cli.Dedent(` + The 'compare-blocks' takes in two paths to stores of merged blocks and a range specifying the blocks you + want to compare, written as: ':'. It will output the status of the likeness of every + 100,000 blocks, on completion, or on encountering a difference. Increments that contain a difference will + be communicated as well as the blocks within that contain differences. Increments that do not have any + differences will be outputted as identical. + + After passing through the blocks, it will output instructions on how to locate a specific difference + based on the blocks that were given. This is done by applying the '--diff' flag before your args. + + Commands inputted with '--diff' will display the blocks that have differences, as well as the + difference. + `), + Args: cobra.ExactArgs(3), + RunE: runCompareBlocksE(chain), + Example: firecore.ExamplePrefixed(chain, "tools compare-blocks", ` + # Run over full block range + reference_store/ current_store/ 0:16000000 + + # Run over specific block range, displaying differences in blocks + --diff reference_store/ current_store/ 100:200 + `), + } + + flags := cmd.PersistentFlags() + flags.Bool("diff", false, "When activated, difference is displayed for each block with a difference") + flags.String("bytes-encoding", "hex", "Encoding for bytes fields, either 'hex' or 'base58'") + flags.Bool("include-unknown-fields", false, "When activated, the 'unknown fields' in the protobuf message will also be compared. These would not generate any difference when unmarshalled with the current protobuf definition.") + flags.StringSlice("proto-paths", []string{""}, "Paths to proto files to use for dynamic decoding of blocks") + + return cmd +} + +func runCompareBlocksE[B firecore.Block](chain *firecore.Chain[B]) firecore.CommandExecutor { + + return func(cmd *cobra.Command, args []string) error { + displayDiff := sflags.MustGetBool(cmd, "diff") + includeUnknownFields := sflags.MustGetBool(cmd, "include-unknown-fields") + protoPaths := sflags.MustGetStringSlice(cmd, "proto-paths") + bytesEncoding := sflags.MustGetString(cmd, "bytes-encoding") + segmentSize := uint64(100000) + warnAboutExtraBlocks := sync.Once{} + ctx := cmd.Context() + blockRange, err := types.GetBlockRangeFromArg(args[2]) + if err != nil { + return fmt.Errorf("parsing range: %w", err) + } + + if !blockRange.IsResolved() { + return fmt.Errorf("invalid block range, you must provide a closed range fully resolved (no negative value)") + } + + stopBlock := blockRange.GetStopBlockOr(firecore.MaxUint64) + + // Create stores + storeReference, err := dstore.NewDBinStore(args[0]) + if err != nil { + return fmt.Errorf("unable to create store at path %q: %w", args[0], err) + } + storeCurrent, err := dstore.NewDBinStore(args[1]) + if err != nil { + return fmt.Errorf("unable to create store at path %q: %w", args[1], err) + } + + segments, err := blockRange.Split(segmentSize, types.EndBoundaryExclusive) + if err != nil { + return fmt.Errorf("unable to split blockrage in segments: %w", err) + } + processState := &state{ + segments: segments, + } + + registry, err := fcproto.NewRegistry(nil, protoPaths...) + if err != nil { + return fmt.Errorf("creating registry: %w", err) + } + + sanitizer := chain.Tools.GetSanitizeBlockForCompare() + + err = storeReference.Walk(ctx, check.WalkBlockPrefix(blockRange, 100), func(filename string) (err error) { + fileStartBlock, err := strconv.Atoi(filename) + if err != nil { + return fmt.Errorf("parsing filename: %w", err) + } + + // If reached end of range + if stopBlock <= uint64(fileStartBlock) { + return dstore.StopIteration + } + + if blockRange.Contains(uint64(fileStartBlock), types.EndBoundaryExclusive) { + var wg sync.WaitGroup + var bundleErrLock sync.Mutex + var bundleReadErr error + var referenceBlockHashes []string + var referenceBlocks map[string]*dynamicpb.Message + var referenceBlocksNum map[string]uint64 + var currentBlocks map[string]*dynamicpb.Message + + wg.Add(1) + go func() { + defer wg.Done() + referenceBlockHashes, referenceBlocks, referenceBlocksNum, err = readBundle( + ctx, + filename, + storeReference, + uint64(fileStartBlock), + stopBlock, + &warnAboutExtraBlocks, + sanitizer, + registry, + ) + if err != nil { + bundleErrLock.Lock() + bundleReadErr = multierr.Append(bundleReadErr, err) + bundleErrLock.Unlock() + } + }() + + wg.Add(1) + go func() { + defer wg.Done() + _, currentBlocks, _, err = readBundle(ctx, + filename, + storeCurrent, + uint64(fileStartBlock), + stopBlock, + &warnAboutExtraBlocks, + sanitizer, + registry, + ) + if err != nil { + bundleErrLock.Lock() + bundleReadErr = multierr.Append(bundleReadErr, err) + bundleErrLock.Unlock() + } + }() + wg.Wait() + if bundleReadErr != nil { + return fmt.Errorf("reading bundles: %w", bundleReadErr) + } + + outLock := sync.Mutex{} + for _, referenceBlockHash := range referenceBlockHashes { + wg.Add(1) + go func(hash string) { + defer wg.Done() + referenceBlock := referenceBlocks[hash] + currentBlock, existsInCurrent := currentBlocks[hash] + referenceBlockNum := referenceBlocksNum[hash] + + var isDifferent bool + if existsInCurrent { + var differences []string + differences = Compare(referenceBlock, currentBlock, includeUnknownFields, registry, bytesEncoding) + + isDifferent = len(differences) > 0 + + if isDifferent { + outLock.Lock() + fmt.Printf("- Block %d is different\n", referenceBlockNum) + if displayDiff { + for _, diff := range differences { + fmt.Println(" · ", diff) + } + } + outLock.Unlock() + } + } + processState.process(referenceBlockNum, isDifferent, !existsInCurrent) + }(referenceBlockHash) + wg.Wait() + } + } + return nil + }) + if err != nil { + return fmt.Errorf("walking files: %w", err) + } + processState.print() + + return nil + } +} + +func readBundle( + ctx context.Context, + filename string, + store dstore.Store, + fileStartBlock, + stopBlock uint64, + warnAboutExtraBlocks *sync.Once, + sanitizer firecore.SanitizeBlockForCompareFunc, + registry *fcproto.Registry, +) ([]string, map[string]*dynamicpb.Message, map[string]uint64, error) { + fileReader, err := store.OpenObject(ctx, filename) + + if err != nil { + return nil, nil, nil, fmt.Errorf("creating reader: %w", err) + } + + blockReader, err := bstream.NewDBinBlockReader(fileReader) + if err != nil { + return nil, nil, nil, fmt.Errorf("creating block reader: %w", err) + } + + var blockHashes []string + blocksMap := make(map[string]*dynamicpb.Message) + blockNumMap := make(map[string]uint64) + for { + curBlock, err := blockReader.Read() + if err == io.EOF { + break + } + if err != nil { + return nil, nil, nil, fmt.Errorf("reading blocks: %w", err) + } + if curBlock.Number >= stopBlock { + break + } + if curBlock.Number < fileStartBlock { + warnAboutExtraBlocks.Do(func() { + fmt.Printf("Warn: Bundle file %s contains block %d, preceding its start_block. This 'feature' is not used anymore and extra blocks like this one will be ignored during compare\n", store.ObjectURL(filename), curBlock.Number) + }) + continue + } + + if sanitizer != nil { + curBlock = sanitizer(curBlock) + } + + curBlockPB, err := registry.Unmarshal(curBlock.Payload) + + if err != nil { + return nil, nil, nil, fmt.Errorf("unmarshalling block: %w", err) + } + blockHashes = append(blockHashes, curBlock.Id) + blockNumMap[curBlock.Id] = curBlock.Number + blocksMap[curBlock.Id] = curBlockPB + } + + return blockHashes, blocksMap, blockNumMap, nil +} + +type state struct { + segments []types.BlockRange + currentSegmentIdx int + blocksCountedInThisSegment int + differencesFound int + missingBlocks int + totalBlocksCounted int +} + +func (s *state) process(blockNum uint64, isDifferent bool, isMissing bool) { + if !s.segments[s.currentSegmentIdx].Contains(blockNum, types.EndBoundaryExclusive) { // moving forward + s.print() + for i := s.currentSegmentIdx; i < len(s.segments); i++ { + if s.segments[i].Contains(blockNum, types.EndBoundaryExclusive) { + s.currentSegmentIdx = i + s.totalBlocksCounted += s.blocksCountedInThisSegment + s.differencesFound = 0 + s.missingBlocks = 0 + s.blocksCountedInThisSegment = 0 + } + } + } + + s.totalBlocksCounted++ + if isMissing { + s.missingBlocks++ + } else if isDifferent { + s.differencesFound++ + } + +} + +func (s *state) print() { + endBlock := fmt.Sprintf("%d", s.segments[s.currentSegmentIdx].GetStopBlockOr(firecore.MaxUint64)) + + if s.totalBlocksCounted == 0 { + fmt.Printf("✖ No blocks were found at all for segment %d - %s\n", s.segments[s.currentSegmentIdx].Start, endBlock) + return + } + + if s.differencesFound == 0 && s.missingBlocks == 0 { + fmt.Printf("✓ Segment %d - %s has no differences (%d blocks counted)\n", s.segments[s.currentSegmentIdx].Start, endBlock, s.totalBlocksCounted) + return + } + + if s.differencesFound == 0 && s.missingBlocks == 0 { + fmt.Printf("✓~ Segment %d - %s has no differences but does have %d missing blocks (%d blocks counted)\n", s.segments[s.currentSegmentIdx].Start, endBlock, s.missingBlocks, s.totalBlocksCounted) + return + } + + fmt.Printf("✖ Segment %d - %s has %d different blocks and %d missing blocks (%d blocks counted)\n", s.segments[s.currentSegmentIdx].Start, endBlock, s.differencesFound, s.missingBlocks, s.totalBlocksCounted) +} + +func Compare(reference proto.Message, current proto.Message, includeUnknownFields bool, registry *fcproto.Registry, bytesEncoding string) (differences []string) { + if reference == nil && current == nil { + return nil + } + if reflect.TypeOf(reference).Kind() == reflect.Ptr && reference == current { + return nil + } + + referenceMsg := reference.ProtoReflect() + currentMsg := current.ProtoReflect() + if referenceMsg.IsValid() && !currentMsg.IsValid() { + return []string{fmt.Sprintf("reference block is valid protobuf message, but current block is invalid")} + } + if !referenceMsg.IsValid() && currentMsg.IsValid() { + return []string{fmt.Sprintf("reference block is invalid protobuf message, but current block is valid")} + } + + //todo: check if there is a equals that do not compare unknown fields + if !proto.Equal(reference, current) { + var opts []json.MarshallerOption + if !includeUnknownFields { + opts = append(opts, json.WithoutUnknownFields()) + } + + if bytesEncoding == "base58" { + opts = append(opts, json.WithBytesEncoderFunc(json.ToBase58)) + } + + encoder := json.NewMarshaller(registry, opts...) + + referenceAsJSON, err := encoder.MarshalToString(reference) + cli.NoError(err, "marshal JSON reference") + + currentAsJSON, err := encoder.MarshalToString(current) + cli.NoError(err, "marshal JSON current") + + r, err := jd.ReadJsonString(referenceAsJSON) + cli.NoError(err, "read JSON reference") + + c, err := jd.ReadJsonString(currentAsJSON) + cli.NoError(err, "read JSON current") + + if diff := r.Diff(c).Render(); diff != "" { + + differences = append(differences, diff) + } + + } + + return differences +} diff --git a/firehose/firehose-core/cmd/tools/firehose/client.go b/firehose/firehose-core/cmd/tools/firehose/client.go new file mode 100644 index 0000000..ad973d3 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/firehose/client.go @@ -0,0 +1,135 @@ +package firehose + +import ( + "context" + "fmt" + "io" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli/sflags" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/cmd/tools/print" + "github.com/streamingfast/firehose-core/types" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" +) + +func NewToolsFirehoseClientCmd[B firecore.Block](chain *firecore.Chain[B], logger *zap.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "firehose-client ", + Short: "Connects to a Firehose endpoint over gRPC and print block stream as JSON to terminal", + Args: cobra.ExactArgs(2), + RunE: getFirehoseClientE(chain, logger), + } + + addFirehoseStreamClientFlagsToSet(cmd.Flags(), chain) + + cmd.Flags().StringSlice("proto-paths", []string{""}, "Paths to proto files to use for dynamic decoding of blocks") + cmd.Flags().Bool("final-blocks-only", false, "Only ask for final blocks") + cmd.Flags().Bool("print-cursor-only", false, "Skip block decoding, only print the step cursor (useful for performance testing)") + cmd.Flags().String("bytes-encoding", "hex", "Encoding for bytes fields, either 'hex' or 'base58'") + + return cmd +} + +type respChan struct { + ch chan string +} + +func getFirehoseClientE[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + firehoseClient, connClose, requestInfo, err := getFirehoseStreamClientFromCmd(cmd, rootLog, args[0], chain) + if err != nil { + return err + } + defer connClose() + + blockRange, err := types.GetBlockRangeFromArg(args[1]) + if err != nil { + return fmt.Errorf("invalid range %q: %w", args[1], err) + } + + printCursorOnly := sflags.MustGetBool(cmd, "print-cursor-only") + + request := &pbfirehose.Request{ + StartBlockNum: blockRange.Start, + StopBlockNum: blockRange.GetStopBlockOr(0), + Transforms: requestInfo.Transforms, + FinalBlocksOnly: requestInfo.FinalBlocksOnly, + Cursor: requestInfo.Cursor, + } + + stream, err := firehoseClient.Blocks(ctx, request, requestInfo.GRPCCallOpts...) + if err != nil { + return fmt.Errorf("unable to start blocks stream: %w", err) + } + + meta, err := stream.Header() + if err != nil { + rootLog.Warn("cannot read header") + } else { + if hosts := meta.Get("hostname"); len(hosts) != 0 { + rootLog = rootLog.With(zap.String("remote_hostname", hosts[0])) + } + } + rootLog.Info("connected") + + resps := make(chan *respChan, 10) + allDone := make(chan bool) + + if !printCursorOnly { + // print the responses linearly + go func() { + for resp := range resps { + line := <-resp.ch + fmt.Println(line) + } + close(allDone) + }() + } + + jencoder, err := print.SetupJsonMarshaller(cmd, chain.BlockFactory().ProtoReflect().Descriptor().ParentFile()) + if err != nil { + return fmt.Errorf("unable to create json encoder: %w", err) + } + + for { + response, err := stream.Recv() + if err != nil { + if err == io.EOF { + break + } + return fmt.Errorf("stream error while receiving: %w", err) + } + + if printCursorOnly { + fmt.Printf("%s - %s\n", response.Step.String(), response.Cursor) + continue + } + + resp := &respChan{ + ch: make(chan string), + } + resps <- resp + + // async process the response + go func() { + line, err := jencoder.MarshalToString(response) + if err != nil { + rootLog.Error("marshalling to string", zap.Error(err)) + } + + resp.ch <- line + }() + } + if printCursorOnly { + return nil + } + + close(resps) + <-allDone + return nil + } +} diff --git a/firehose/firehose-core/cmd/tools/firehose/firehose.go b/firehose/firehose-core/cmd/tools/firehose/firehose.go new file mode 100644 index 0000000..6cfdb4b --- /dev/null +++ b/firehose/firehose-core/cmd/tools/firehose/firehose.go @@ -0,0 +1,126 @@ +package firehose + +import ( + "fmt" + "os" + + "github.com/mostynb/go-grpc-compression/zstd" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/streamingfast/cli/sflags" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/firehose/client" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" + "google.golang.org/grpc" + "google.golang.org/grpc/encoding/gzip" + "google.golang.org/protobuf/types/known/anypb" +) + +type firehoseRequestInfo struct { + GRPCCallOpts []grpc.CallOption + Cursor string + FinalBlocksOnly bool + Transforms []*anypb.Any +} + +func getFirehoseFetchClientFromCmd[B firecore.Block](cmd *cobra.Command, logger *zap.Logger, endpoint string, chain *firecore.Chain[B]) ( + firehoseClient pbfirehose.FetchClient, + connClose func() error, + requestInfo *firehoseRequestInfo, + err error, +) { + return getFirehoseClientFromCmd[B, pbfirehose.FetchClient](cmd, logger, "fetch-client", endpoint, chain) +} + +func getFirehoseStreamClientFromCmd[B firecore.Block](cmd *cobra.Command, logger *zap.Logger, endpoint string, chain *firecore.Chain[B]) ( + firehoseClient pbfirehose.StreamClient, + connClose func() error, + requestInfo *firehoseRequestInfo, + err error, +) { + return getFirehoseClientFromCmd[B, pbfirehose.StreamClient](cmd, logger, "stream-client", endpoint, chain) +} + +func getFirehoseClientFromCmd[B firecore.Block, C any](cmd *cobra.Command, logger *zap.Logger, kind string, endpoint string, chain *firecore.Chain[B]) ( + firehoseClient C, + connClose func() error, + requestInfo *firehoseRequestInfo, + err error, +) { + requestInfo = &firehoseRequestInfo{} + + jwt := os.Getenv(sflags.MustGetString(cmd, "api-token-env-var")) + apiKey := os.Getenv(sflags.MustGetString(cmd, "api-key-env-var")) + + plaintext := sflags.MustGetBool(cmd, "plaintext") + insecure := sflags.MustGetBool(cmd, "insecure") + + if sflags.FlagDefined(cmd, "cursor") { + requestInfo.Cursor = sflags.MustGetString(cmd, "cursor") + } + + if sflags.FlagDefined(cmd, "final-blocks-only") { + requestInfo.FinalBlocksOnly = sflags.MustGetBool(cmd, "final-blocks-only") + } + + var rawClient any + if kind == "stream-client" { + rawClient, connClose, requestInfo.GRPCCallOpts, err = client.NewFirehoseClient(endpoint, jwt, apiKey, insecure, plaintext) + } else if kind == "fetch-client" { + rawClient, connClose, requestInfo.GRPCCallOpts, err = client.NewFirehoseFetchClient(endpoint, jwt, apiKey, insecure, plaintext) + } else { + panic(fmt.Errorf("unsupported Firehose client kind: %s", kind)) + } + + if err != nil { + return firehoseClient, nil, nil, err + } + + firehoseClient = rawClient.(C) + + compression := sflags.MustGetString(cmd, "compression") + var compressor grpc.CallOption + switch compression { + case "gzip": + compressor = grpc.UseCompressor(gzip.Name) + case "zstd": + compressor = grpc.UseCompressor(zstd.Name) + case "none": + // Valid value but nothing to do + default: + return firehoseClient, nil, nil, fmt.Errorf("invalid value for compression: only 'gzip', 'zstd' or 'none' are accepted") + + } + + if compressor != nil { + requestInfo.GRPCCallOpts = append(requestInfo.GRPCCallOpts, compressor) + } + + if chain.Tools.TransformFlags != nil { + requestInfo.Transforms, err = chain.Tools.TransformFlags.Parse(cmd, logger) + } + + if err != nil { + return firehoseClient, nil, nil, fmt.Errorf("unable to parse transforms flags: %w", err) + } + + return +} + +func addFirehoseStreamClientFlagsToSet[B firecore.Block](flags *pflag.FlagSet, chain *firecore.Chain[B]) { + addFirehoseFetchClientFlagsToSet(flags, chain) + + flags.String("cursor", "", "Use this cursor with the request to resume your stream at the following block pointed by the cursor") +} + +func addFirehoseFetchClientFlagsToSet[B firecore.Block](flags *pflag.FlagSet, chain *firecore.Chain[B]) { + flags.StringP("api-token-env-var", "a", "FIREHOSE_API_TOKEN", "Look for a JWT in this environment variable to authenticate against endpoint (alternative to api-key-env-var)") + flags.String("api-key-env-var", "FIREHOSE_API_KEY", "Look for an API key directly in this environment variable to authenticate against endpoint (alternative to api-token-env-var)") + flags.String("compression", "none", "The HTTP compression: use either 'none', 'gzip' or 'zstd'") + flags.BoolP("plaintext", "p", false, "Use plaintext connection to Firehose") + flags.BoolP("insecure", "k", false, "Use SSL connection to Firehose but skip SSL certificate validation") + if chain.Tools.TransformFlags != nil { + chain.Tools.TransformFlags.Register(flags) + } +} diff --git a/firehose/firehose-core/cmd/tools/firehose/prometheus_exporter.go b/firehose/firehose-core/cmd/tools/firehose/prometheus_exporter.go new file mode 100644 index 0000000..0c96b01 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/firehose/prometheus_exporter.go @@ -0,0 +1,122 @@ +package firehose + +import ( + "context" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/logging" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" +) + +var status = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "firehose_healthcheck_status", Help: "Either 1 for successful firehose request, or 0 for failure"}, []string{"endpoint"}) +var propagationDelay = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "firehose_healthcheck_block_delay", Help: "Delay between block time and propagation to firehose clients"}, []string{"endpoint"}) + +var lastBlockLock sync.Mutex +var lastBlockReceived time.Time +var driftSec = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "firehose_healthcheck_drift", Help: "Time since the most recent block received (seconds)"}, []string{"endpoint"}) + +// You should add your custom 'transforms' flags to this command in your init(), then parse them in transformsSetter +func NewToolsFirehosePrometheusExporterCmd[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger, tracer logging.Tracer) *cobra.Command { + cmd := &cobra.Command{ + Use: "firehose-prometheus-exporter ", + Short: "stream blocks near the chain HEAD and report to prometheus", + Args: cobra.ExactArgs(1), + RunE: runPrometheusExporterE(chain, zlog, tracer), + } + + addFirehoseStreamClientFlagsToSet(cmd.Flags(), chain) + + return cmd +} + +func runPrometheusExporterE[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger, tracer logging.Tracer) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + endpoint := args[0] + start := int64(-1) + stop := uint64(0) + + firehoseClient, connClose, requestInfo, err := getFirehoseStreamClientFromCmd(cmd, zlog, endpoint, chain) + if err != nil { + return err + } + defer connClose() + + request := &pbfirehose.Request{ + StartBlockNum: start, + StopBlockNum: stop, + Transforms: requestInfo.Transforms, + FinalBlocksOnly: true, + Cursor: requestInfo.Cursor, + } + + prometheus.MustRegister(status) + prometheus.MustRegister(propagationDelay) + prometheus.MustRegister(driftSec) + + // update the drift based on last time + go func() { + for { + time.Sleep(500 * time.Millisecond) + lastBlockLock.Lock() + driftSec.With(prometheus.Labels{"endpoint": endpoint}).Set(time.Since(lastBlockReceived).Seconds()) + lastBlockLock.Unlock() + } + }() + + var sleepTime time.Duration + for { + time.Sleep(sleepTime) + sleepTime = time.Second * 3 + stream, err := firehoseClient.Blocks(ctx, request, requestInfo.GRPCCallOpts...) + if err != nil { + zlog.Error("connecting", zap.Error(err)) + markFailure(endpoint) + continue + } + + zlog.Info("connected") + + for { + response, err := stream.Recv() + if err != nil { + zlog.Error("got error from stream", zap.Error(err)) + markFailure(endpoint) + break + } + + if cursor, err := bstream.CursorFromOpaque(response.Cursor); err == nil { + zlog.Info("Got block", zap.String("block", cursor.Block.ID()), zap.Uint64("block_num", cursor.Block.Num())) + + lastBlockLock.Lock() + lastBlockReceived = time.Now() + lastBlockLock.Unlock() + markSuccess(endpoint) + } + } + + } + + // serve := http.Server{Handler: handler, Addr: addr} + // if err := serve.ListenAndServe(); err != nil { + // zlog.Error("can't listen on the metrics endpoint", zap.Error(err)) + // return err + // } + // return nil + } +} + +func markSuccess(endpoint string) { + status.With(prometheus.Labels{"endpoint": endpoint}).Set(1) +} + +func markFailure(endpoint string) { + status.With(prometheus.Labels{"endpoint": endpoint}).Set(0) +} diff --git a/firehose/firehose-core/cmd/tools/firehose/single_block_client.go b/firehose/firehose-core/cmd/tools/firehose/single_block_client.go new file mode 100644 index 0000000..b6b0a5a --- /dev/null +++ b/firehose/firehose-core/cmd/tools/firehose/single_block_client.go @@ -0,0 +1,86 @@ +package firehose + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/spf13/cobra" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/jsonpb" + "github.com/streamingfast/logging" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" +) + +// You should add your custom 'transforms' flags to this command in your init(), then parse them in transformsSetter +func NewToolsFirehoseSingleBlockClientCmd[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger, tracer logging.Tracer) *cobra.Command { + cmd := &cobra.Command{ + Use: "firehose-single-block-client {endpoint} {block_num|block_num:block_id|cursor}", + Short: "fetch a single block from firehose and print as JSON", + Args: cobra.ExactArgs(2), + RunE: getFirehoseSingleBlockClientE(chain, zlog, tracer), + Example: firecore.ExamplePrefixed(chain, "tools ", ` + firehose-single-block-client --compression=gzip my.firehose.endpoint:443 2344:0x32d8e8d98a798da98d6as9d69899as86s9898d8ss8d87 + `), + } + + addFirehoseFetchClientFlagsToSet(cmd.Flags(), chain) + + return cmd +} + +func getFirehoseSingleBlockClientE[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger, tracer logging.Tracer) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + endpoint := args[0] + firehoseClient, connClose, requestInfo, err := getFirehoseFetchClientFromCmd(cmd, zlog, endpoint, chain) + if err != nil { + return err + } + defer connClose() + + req := &pbfirehose.SingleBlockRequest{} + + ref := args[1] + if num, err := strconv.ParseUint(ref, 10, 64); err == nil { + req.Reference = &pbfirehose.SingleBlockRequest_BlockNumber_{ + BlockNumber: &pbfirehose.SingleBlockRequest_BlockNumber{ + Num: num, + }, + } + } else if parts := strings.Split(ref, ":"); len(parts) == 2 { + num, err := strconv.ParseUint(parts[0], 10, 64) + if err != nil { + return fmt.Errorf("invalid block reference, cannot decode first part as block_num: %s, %w", ref, err) + } + req.Reference = &pbfirehose.SingleBlockRequest_BlockHashAndNumber_{ + BlockHashAndNumber: &pbfirehose.SingleBlockRequest_BlockHashAndNumber{ + Num: num, + Hash: parts[1], + }, + } + + } else { + req.Reference = &pbfirehose.SingleBlockRequest_Cursor_{ + Cursor: &pbfirehose.SingleBlockRequest_Cursor{ + Cursor: ref, + }, + } + } + + resp, err := firehoseClient.Block(ctx, req, requestInfo.GRPCCallOpts...) + if err != nil { + return err + } + + line, err := jsonpb.MarshalToString(resp) + if err != nil { + return err + } + fmt.Println(line) + return nil + } +} diff --git a/firehose/firehose-core/cmd/tools/firehose/tools_download_from_firehose.go b/firehose/firehose-core/cmd/tools/firehose/tools_download_from_firehose.go new file mode 100644 index 0000000..558a76f --- /dev/null +++ b/firehose/firehose-core/cmd/tools/firehose/tools_download_from_firehose.go @@ -0,0 +1,152 @@ +package firehose + +import ( + "context" + "fmt" + + "io" + "strconv" + "time" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +func NewToolsDownloadFromFirehoseCmd[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "download-from-firehose ", + Short: "Download blocks from Firehose and save them to merged-blocks", + Args: cobra.ExactArgs(4), + RunE: createToolsDownloadFromFirehoseE(chain, zlog), + Example: firecore.ExamplePrefixed(chain, "tools download-from-firehose", ` + # Adjust based on your actual network + mainnet.eth.streamingfast.io:443 1000:2000 ./output_dir + `), + } + + addFirehoseStreamClientFlagsToSet(cmd.Flags(), chain) + + return cmd +} + +func createToolsDownloadFromFirehoseE[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + + if _, ok := chain.BlockFactory().(*pbbstream.Block); ok { + //todo: fix this with buf registry + return fmt.Errorf("this tool only works with blocks that are not of type *pbbstream.Block") + } + + endpoint := args[0] + startBlock, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return fmt.Errorf("parsing start block num: %w", err) + } + stopBlock, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return fmt.Errorf("parsing stop block num: %w", err) + } + destFolder := args[3] + + firehoseClient, connClose, requestInfo, err := getFirehoseStreamClientFromCmd(cmd, zlog, endpoint, chain) + if err != nil { + return err + } + defer connClose() + + var retryDelay = time.Second * 4 + + store, err := dstore.NewDBinStore(destFolder) + if err != nil { + return err + } + + mergeWriter := &firecore.MergedBlocksWriter{ + Store: store, + TweakBlock: func(b *pbbstream.Block) (*pbbstream.Block, error) { return b, nil }, + Logger: zlog, + } + + approximateLIBWarningIssued := false + var lastBlockID string + var lastBlockNum uint64 + for { + + request := &pbfirehose.Request{ + StartBlockNum: int64(startBlock), + StopBlockNum: stopBlock, + FinalBlocksOnly: true, + Cursor: requestInfo.Cursor, + } + + stream, err := firehoseClient.Blocks(ctx, request, requestInfo.GRPCCallOpts...) + if err != nil { + return fmt.Errorf("unable to start blocks stream: %w", err) + } + + for { + response, err := stream.Recv() + if err != nil { + if err == io.EOF { + return nil + } + + zlog.Error("stream encountered a remote error, going to retry", + zap.Duration("retry_delay", retryDelay), + zap.Error(err), + ) + <-time.After(retryDelay) + break + } + + block := chain.BlockFactory() + if err := anypb.UnmarshalTo(response.Block, block, proto.UnmarshalOptions{}); err != nil { + return fmt.Errorf("unmarshal response block: %w", err) + } + + if _, ok := block.(firecore.BlockLIBNumDerivable); !ok { + // We must wrap the block in a BlockEnveloppe and "provide" the LIB number as itself minus 1 since + // there is nothing we can do more here to obtain the value sadly. For chain where the LIB can be + // derived from the Block itself, this code does **not** run (so it will have the correct value) + if !approximateLIBWarningIssued { + approximateLIBWarningIssued = true + zlog.Warn("LIB number is approximated, it is not provided by the chain's Block model so we msut set it to block number minus 1 (which is kinda ok because only final blocks are retrieved in this download tool)") + } + + number := block.GetFirehoseBlockNumber() + libNum := number - 1 + if number <= bstream.GetProtocolFirstStreamableBlock { + libNum = number + } + + block = firecore.BlockEnveloppe{ + Block: block, + LIBNum: libNum, + } + } + + blk, err := chain.BlockEncoder.Encode(block) + if err != nil { + return fmt.Errorf("error decoding response to bstream block: %w", err) + } + if lastBlockID != "" && blk.ParentId != lastBlockID { + return fmt.Errorf("got an invalid sequence of blocks: block %q has previousId %s, previous block %d had ID %q, this endpoint is serving blocks out of order", blk.String(), blk.ParentId, lastBlockNum, lastBlockID) + } + lastBlockID = blk.Id + lastBlockNum = blk.Number + + if err := mergeWriter.ProcessBlock(blk, nil); err != nil { + return fmt.Errorf("write to blockwriter: %w", err) + } + } + } + } +} diff --git a/firehose/firehose-core/cmd/tools/fix/legacy_2_block_any.go b/firehose/firehose-core/cmd/tools/fix/legacy_2_block_any.go new file mode 100644 index 0000000..eeee8e7 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/fix/legacy_2_block_any.go @@ -0,0 +1,131 @@ +package fix + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/cmd/tools/check" + "github.com/streamingfast/firehose-core/types" + "go.uber.org/zap" +) + +func NewLegacy2BlockAby[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger) *cobra.Command { + return &cobra.Command{ + Use: "legacy_2_block_any []", + Short: "converts merged blocks from legacy format to block any format", + Args: cobra.ExactArgs(3), + RunE: runLegacy2BlockAnyE(zlog), + } +} + +func runLegacy2BlockAnyE(zlog *zap.Logger) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + srcStore, err := dstore.NewDBinStore(args[0]) + if err != nil { + return fmt.Errorf("unable to create source store: %w", err) + } + + destStore, err := dstore.NewDBinStore(args[1]) + if err != nil { + return fmt.Errorf("unable to create destination store: %w", err) + } + + blockRange, err := types.GetBlockRangeFromArg(args[2]) + if err != nil { + return fmt.Errorf("parsing block range: %w", err) + } + + err = srcStore.Walk(ctx, check.WalkBlockPrefix(blockRange, 100), func(filename string) error { + zlog.Debug("checking merged block file", zap.String("filename", filename)) + + startBlock := firecore.MustParseUint64(filename) + + if startBlock > uint64(blockRange.GetStopBlockOr(firecore.MaxUint64)) { + zlog.Debug("skipping merged block file", zap.String("reason", "past stop block"), zap.String("filename", filename)) + return dstore.StopIteration + } + + if startBlock+100 < uint64(blockRange.Start) { + zlog.Debug("skipping merged block file", zap.String("reason", "before start block"), zap.String("filename", filename)) + return nil + } + + rc, err := srcStore.OpenObject(ctx, filename) + if err != nil { + return fmt.Errorf("failed to open %s: %w", filename, err) + } + defer rc.Close() + + br, err := bstream.NewDBinBlockReader(rc) + if err != nil { + return fmt.Errorf("creating block reader: %w", err) + } + + mergeWriter := &firecore.MergedBlocksWriter{ + Store: destStore, + TweakBlock: func(b *pbbstream.Block) (*pbbstream.Block, error) { + + return b, nil + }, + Logger: zlog, + } + + seen := make(map[string]bool) + + var lastBlockID string + var lastBlockNum uint64 + + // iterate through the blocks in the file + for { + block, err := br.Read() + if err == io.EOF { + break + } + + if block.Number < startBlock { + continue + } + + if block.Number > blockRange.GetStopBlockOr(firecore.MaxUint64) { + break + } + + if seen[block.Id] { + zlog.Info("skipping seen block (source merged-blocks had duplicates, skipping)", zap.String("id", block.Id), zap.Uint64("num", block.Number)) + continue + } + + if lastBlockID != "" && block.ParentId != lastBlockID { + return fmt.Errorf("got an invalid sequence of blocks: block %q has previousId %s, previous block %d had ID %q, this endpoint is serving blocks out of order", block.String(), block.ParentId, lastBlockNum, lastBlockID) + } + lastBlockID = block.Id + lastBlockNum = block.Number + + seen[block.Id] = true + + if err := mergeWriter.ProcessBlock(block, nil); err != nil { + return fmt.Errorf("write to blockwriter: %w", err) + } + } + + return nil + }) + + if err == io.EOF { + return nil + } + + if err != nil { + return err + } + + return nil + } +} diff --git a/firehose/firehose-core/cmd/tools/fix/tools_fix_bloated_merged_blocks.go b/firehose/firehose-core/cmd/tools/fix/tools_fix_bloated_merged_blocks.go new file mode 100644 index 0000000..0642988 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/fix/tools_fix_bloated_merged_blocks.go @@ -0,0 +1,128 @@ +package fix + +import ( + "fmt" + "io" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/cmd/tools/check" + "github.com/streamingfast/firehose-core/types" + "go.uber.org/zap" +) + +func NewToolsFixBloatedMergedBlocks[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger) *cobra.Command { + return &cobra.Command{ + Use: "fix-bloated-merged-blocks []", + Short: "Fixes 'corrupted' merged-blocks that contain extraneous or duplicate blocks. Some older versions of the merger may have produce such bloated merged-blocks. All merged-blocks files in given range will be rewritten, regardless of if they were corrupted.", + Args: cobra.ExactArgs(3), + RunE: runFixBloatedMergedBlocksE(zlog), + } +} + +func runFixBloatedMergedBlocksE(zlog *zap.Logger) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + srcStore, err := dstore.NewDBinStore(args[0]) + if err != nil { + return fmt.Errorf("unable to create source store: %w", err) + } + + destStore, err := dstore.NewDBinStore(args[1]) + if err != nil { + return fmt.Errorf("unable to create destination store: %w", err) + } + + blockRange, err := types.GetBlockRangeFromArg(args[2]) + if err != nil { + return fmt.Errorf("parsing block range: %w", err) + } + + err = srcStore.Walk(ctx, check.WalkBlockPrefix(blockRange, 100), func(filename string) error { + zlog.Debug("checking merged block file", zap.String("filename", filename)) + + startBlock := firecore.MustParseUint64(filename) + + if startBlock > uint64(blockRange.GetStopBlockOr(firecore.MaxUint64)) { + zlog.Debug("skipping merged block file", zap.String("reason", "past stop block"), zap.String("filename", filename)) + return dstore.StopIteration + } + + if startBlock+100 < uint64(blockRange.Start) { + zlog.Debug("skipping merged block file", zap.String("reason", "before start block"), zap.String("filename", filename)) + return nil + } + + rc, err := srcStore.OpenObject(ctx, filename) + if err != nil { + return fmt.Errorf("failed to open %s: %w", filename, err) + } + defer rc.Close() + + br, err := bstream.NewDBinBlockReader(rc) + if err != nil { + return fmt.Errorf("creating block reader: %w", err) + } + + mergeWriter := &firecore.MergedBlocksWriter{ + Store: destStore, + TweakBlock: func(b *pbbstream.Block) (*pbbstream.Block, error) { return b, nil }, + Logger: zlog, + } + + seen := make(map[string]bool) + + var lastBlockID string + var lastBlockNum uint64 + + // iterate through the blocks in the file + for { + block, err := br.Read() + if err == io.EOF { + break + } + + if block.Number < startBlock { + continue + } + + if block.Number > blockRange.GetStopBlockOr(firecore.MaxUint64) { + break + } + + if seen[block.Id] { + zlog.Info("skipping seen block (source merged-blocks had duplicates, skipping)", zap.String("id", block.Id), zap.Uint64("num", block.Number)) + continue + } + + if lastBlockID != "" && block.ParentId != lastBlockID { + return fmt.Errorf("got an invalid sequence of blocks: block %q has previousId %s, previous block %d had ID %q, this endpoint is serving blocks out of order", block.String(), block.ParentId, lastBlockNum, lastBlockID) + } + lastBlockID = block.Id + lastBlockNum = block.Number + + seen[block.Id] = true + + if err := mergeWriter.ProcessBlock(block, nil); err != nil { + return fmt.Errorf("write to blockwriter: %w", err) + } + } + + return nil + }) + + if err == io.EOF { + return nil + } + + if err != nil { + return err + } + + return nil + } +} diff --git a/firehose/firehose-core/cmd/tools/mergeblock/tools_merge_blocks.go b/firehose/firehose-core/cmd/tools/mergeblock/tools_merge_blocks.go new file mode 100644 index 0000000..29a2965 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/mergeblock/tools_merge_blocks.go @@ -0,0 +1,129 @@ +package mergeblock + +import ( + "errors" + "fmt" + "io" + "strconv" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + + "github.com/spf13/cobra" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "go.uber.org/zap" +) + +func NewToolsMergeBlocksCmd[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger) *cobra.Command { + cmd := &cobra.Command{ + Use: "merge-blocks ", + Short: "Merges one-block files into merged-block file", + Args: cobra.ExactArgs(3), + RunE: runMergeBlocksE(zlog), + } + + cmd.Flags().String("force-block-type", "", "When set, will force the block type to the given value.") + + return cmd +} + +func runMergeBlocksE(zlog *zap.Logger) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + srcStore, err := dstore.NewDBinStore(args[0]) + if err != nil { + return fmt.Errorf("unable to create source store: %w", err) + } + + destStore, err := dstore.NewDBinStore(args[1]) + if err != nil { + return fmt.Errorf("unable to create destination store: %w", err) + } + + lowBundary, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return fmt.Errorf("converting low bundary string to uint64: %w", err) + } + + mergeWriter := &firecore.MergedBlocksWriter{ + Store: destStore, + LowBlockNum: lowBundary, + StopBlockNum: 0, + Logger: zlog, + Cmd: cmd, + } + + zlog.Info("starting block merger process", zap.String("source", args[0]), zap.String("dest", args[1])) + + var lastFilename string + var blockCount int + var previousBlockNumber uint64 + err = srcStore.WalkFrom(ctx, "", fmt.Sprintf("%010d", lowBundary), func(filename string) error { + var currentBlockNumber uint64 + currentBlockNumber, _, _, _, _, err = bstream.ParseFilename(filename) + if err != nil { + return fmt.Errorf("parsing filename %s: %w", filename, err) + } + + if previousBlockNumber == currentBlockNumber { + zlog.Warn("skipping duplicate block", zap.String("filename", filename)) + return nil + } + + if currentBlockNumber > lowBundary+100 { + return dstore.StopIteration + } + + var fileReader io.Reader + fileReader, err = srcStore.OpenObject(ctx, filename) + if err != nil { + return fmt.Errorf("creating reader: %w", err) + } + + var blockReader *bstream.DBinBlockReader + blockReader, err = bstream.NewDBinBlockReader(fileReader) + if err != nil { + return fmt.Errorf("creating block reader: %w", err) + } + + var currentBlock *pbbstream.Block + currentBlock, err = blockReader.Read() + if err != nil { + return fmt.Errorf("reading block: %w", err) + } + + if err = mergeWriter.ProcessBlock(currentBlock, nil); err != nil { + return fmt.Errorf("processing block: %w", err) + } + + lastFilename = filename + blockCount += 1 + + previousBlockNumber = currentBlockNumber + return nil + }) + + mergeWriter.Logger = mergeWriter.Logger.With(zap.String("last_filename", lastFilename), zap.Int("block_count", blockCount)) + if err != nil { + if errors.Is(err, dstore.StopIteration) { + err = mergeWriter.WriteBundle() + if err != nil { + return fmt.Errorf("writing bundle: %w", err) + } + fmt.Println("done") + } + return fmt.Errorf("walking source store: %w", err) + } + + err = mergeWriter.WriteBundle() + if err != nil { + return fmt.Errorf("writing bundle: %w", err) + } + + return nil + } + +} diff --git a/firehose/firehose-core/cmd/tools/mergeblock/tools_unmerge_blocks.go b/firehose/firehose-core/cmd/tools/mergeblock/tools_unmerge_blocks.go new file mode 100644 index 0000000..dbe79b0 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/mergeblock/tools_unmerge_blocks.go @@ -0,0 +1,134 @@ +package mergeblock + +import ( + "fmt" + "io" + + "github.com/streamingfast/firehose-core/cmd/tools/check" + "github.com/streamingfast/firehose-core/types" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "go.uber.org/zap" +) + +func NewToolsUnmergeBlocksCmd[B firecore.Block](chain *firecore.Chain[B], zlog *zap.Logger) *cobra.Command { + return &cobra.Command{ + Use: "unmerge-blocks []", + Short: "Unmerges merged block files into one-block-files", + Args: cobra.ExactArgs(3), + RunE: runUnmergeBlocksE(zlog), + } +} + +func runUnmergeBlocksE(zlog *zap.Logger) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + srcStore, err := dstore.NewDBinStore(args[0]) + if err != nil { + return fmt.Errorf("unable to create source store: %w", err) + } + + destStore, err := dstore.NewDBinStore(args[1]) + if err != nil { + return fmt.Errorf("unable to create destination store: %w", err) + } + + blockRange, err := types.GetBlockRangeFromArg(args[2]) + if err != nil { + return fmt.Errorf("parsing block range: %w", err) + } + + err = srcStore.Walk(ctx, check.WalkBlockPrefix(blockRange, 100), func(filename string) error { + zlog.Debug("checking merged block file", zap.String("filename", filename)) + + startBlock := firecore.MustParseUint64(filename) + + if startBlock > uint64(blockRange.GetStopBlockOr(firecore.MaxUint64)) { + zlog.Debug("skipping merged block file", zap.String("reason", "past stop block"), zap.String("filename", filename)) + return dstore.StopIteration + } + + if startBlock+100 < uint64(blockRange.Start) { + zlog.Debug("skipping merged block file", zap.String("reason", "before start block"), zap.String("filename", filename)) + return nil + } + + rc, err := srcStore.OpenObject(ctx, filename) + if err != nil { + return fmt.Errorf("failed to open %s: %w", filename, err) + } + defer rc.Close() + + br, err := bstream.NewDBinBlockReader(rc) + if err != nil { + return fmt.Errorf("creating block reader: %w", err) + } + + // iterate through the blocks in the file + for { + block, err := br.Read() + if err == io.EOF { + break + } + + if block.Number < uint64(blockRange.Start) { + continue + } + + if block.Number > blockRange.GetStopBlockOr(firecore.MaxUint64) { + break + } + + oneblockFilename := bstream.BlockFileNameWithSuffix(block, "extracted") + zlog.Debug("writing block", zap.Uint64("block_num", block.Number), zap.String("filename", oneblockFilename)) + + pr, pw := io.Pipe() + + //write block data to pipe, and then close to signal end of data + go func(block *pbbstream.Block) { + var err error + defer func() { + pw.CloseWithError(err) + }() + + bw, err := bstream.NewDBinBlockWriter(pw) + if err != nil { + zlog.Error("creating block writer", zap.Error(err)) + return + } + + err = bw.Write(block) + if err != nil { + zlog.Error("writing block", zap.Error(err)) + return + } + }(block) + + //read block data from pipe and write block data to dest store + err = destStore.WriteObject(ctx, oneblockFilename, pr) + if err != nil { + return fmt.Errorf("writing block %d to %s: %w", block.Number, oneblockFilename, err) + } + + zlog.Info("wrote block", zap.Uint64("block_num", block.Number), zap.String("filename", oneblockFilename)) + } + + return nil + }) + + if err == io.EOF { + return nil + } + + if err != nil { + return err + } + + return nil + } +} diff --git a/firehose/firehose-core/cmd/tools/mergeblock/tools_upgrade_merged_blocks.go b/firehose/firehose-core/cmd/tools/mergeblock/tools_upgrade_merged_blocks.go new file mode 100644 index 0000000..ad476a6 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/mergeblock/tools_upgrade_merged_blocks.go @@ -0,0 +1,67 @@ +package mergeblock + +import ( + "context" + "errors" + "fmt" + "io" + "strconv" + + "github.com/spf13/cobra" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/bstream/stream" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "go.uber.org/zap" +) + +func NewToolsUpgradeMergedBlocksCmd[B firecore.Block](chain *firecore.Chain[B], rootLog *zap.Logger) *cobra.Command { + return &cobra.Command{ + Use: "upgrade-merged-blocks ", + Short: "From a merged-blocks source, rewrite blocks to a new merged-blocks destination, while applying all possible upgrades", + Args: cobra.ExactArgs(4), + RunE: getMergedBlockUpgrader(chain.Tools.MergedBlockUpgrader, rootLog), + } +} + +func getMergedBlockUpgrader(tweakFunc func(block *pbbstream.Block) (*pbbstream.Block, error), rootLog *zap.Logger) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + source := args[0] + sourceStore, err := dstore.NewDBinStore(source) + if err != nil { + return fmt.Errorf("reading source store: %w", err) + } + + dest := args[1] + destStore, err := dstore.NewStore(dest, "dbin.zst", "zstd", true) + if err != nil { + return fmt.Errorf("reading destination store: %w", err) + } + + start, err := strconv.ParseUint(args[2], 10, 64) + if err != nil { + return fmt.Errorf("parsing start block num: %w", err) + } + stop, err := strconv.ParseUint(args[3], 10, 64) + if err != nil { + return fmt.Errorf("parsing stop block num: %w", err) + } + + rootLog.Info("starting block upgrader process", zap.Uint64("start", start), zap.Uint64("stop", stop), zap.String("source", source), zap.String("dest", dest)) + writer := &firecore.MergedBlocksWriter{ + Cmd: cmd, + Store: destStore, + LowBlockNum: firecore.LowBoundary(start), + StopBlockNum: stop, + TweakBlock: tweakFunc, + } + stream := stream.New(nil, sourceStore, nil, int64(start), writer, stream.WithFinalBlocksOnly()) + + err = stream.Run(context.Background()) + if errors.Is(err, io.EOF) { + rootLog.Info("Complete!") + return nil + } + return err + } +} diff --git a/firehose/firehose-core/cmd/tools/print/tools_print.go b/firehose/firehose-core/cmd/tools/print/tools_print.go new file mode 100644 index 0000000..69bf43f --- /dev/null +++ b/firehose/firehose-core/cmd/tools/print/tools_print.go @@ -0,0 +1,285 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package print + +import ( + "fmt" + "io" + "os" + "strconv" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + fcjson "github.com/streamingfast/firehose-core/json" + "github.com/streamingfast/firehose-core/proto" + "github.com/streamingfast/firehose-core/types" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func NewToolsPrintCmd[B firecore.Block](chain *firecore.Chain[B]) *cobra.Command { + toolsPrintCmd := &cobra.Command{ + Use: "print", + Short: "Prints of one block or merged blocks file", + } + + toolsPrintOneBlockCmd := &cobra.Command{ + Use: "one-block ", + Short: "Prints a block from a one-block file", + Args: cobra.ExactArgs(2), + } + + toolsPrintMergedBlocksCmd := &cobra.Command{ + Use: "merged-blocks ", + Short: "Prints the content summary of a merged blocks file.", + Args: cobra.ExactArgs(2), + } + + toolsPrintCmd.AddCommand(toolsPrintOneBlockCmd) + toolsPrintCmd.AddCommand(toolsPrintMergedBlocksCmd) + + toolsPrintCmd.PersistentFlags().StringP("output", "o", "text", "Output mode for block printing, either 'text', 'json' or 'jsonl'") + toolsPrintCmd.PersistentFlags().String("bytes-encoding", "hex", "Encoding for bytes fields, either 'hex' or 'base58'") + toolsPrintCmd.PersistentFlags().StringSlice("proto-paths", []string{""}, "Paths to proto files to use for dynamic decoding of blocks") + toolsPrintCmd.PersistentFlags().Bool("transactions", false, "When in 'text' output mode, also print transactions summary") + + toolsPrintOneBlockCmd.RunE = createToolsPrintOneBlockE(chain) + toolsPrintMergedBlocksCmd.RunE = createToolsPrintMergedBlocksE(chain) + + return toolsPrintCmd +} + +func createToolsPrintMergedBlocksE[B firecore.Block](chain *firecore.Chain[B]) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + outputMode, err := toolsPrintCmdGetOutputMode(cmd) + if err != nil { + return fmt.Errorf("invalid 'output' flag: %w", err) + } + + printTransactions := sflags.MustGetBool(cmd, "transactions") + + storeURL := args[0] + store, err := dstore.NewDBinStore(storeURL) + if err != nil { + return fmt.Errorf("unable to create store at path %q: %w", store, err) + } + + startBlock, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return fmt.Errorf("invalid base block %q: %w", args[1], err) + } + blockBoundary := types.RoundToBundleStartBlock(startBlock, 100) + + filename := fmt.Sprintf("%010d", blockBoundary) + reader, err := store.OpenObject(ctx, filename) + if err != nil { + fmt.Printf("❌ Unable to read blocks filename %s: %s\n", filename, err) + return err + } + defer reader.Close() + + readerFactory, err := bstream.NewDBinBlockReader(reader) + if err != nil { + fmt.Printf("❌ Unable to read blocks filename %s: %s\n", filename, err) + return err + } + + jencoder, err := SetupJsonMarshaller(cmd, chain.BlockFactory().ProtoReflect().Descriptor().ParentFile()) + if err != nil { + return fmt.Errorf("unable to create json encoder: %w", err) + } + + seenBlockCount := 0 + for { + block, err := readerFactory.Read() + if err != nil { + if err == io.EOF { + fmt.Fprintf(os.Stderr, "Total blocks: %d\n", seenBlockCount) + return nil + } + return fmt.Errorf("error receiving blocks: %w", err) + } + + seenBlockCount++ + + if err := displayBlock(block, chain, outputMode, printTransactions, jencoder); err != nil { + // Error is ready to be passed to the user as-is + return err + } + } + } +} + +func createToolsPrintOneBlockE[B firecore.Block](chain *firecore.Chain[B]) firecore.CommandExecutor { + return func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + outputMode, err := toolsPrintCmdGetOutputMode(cmd) + if err != nil { + return fmt.Errorf("invalid 'output' flag: %w", err) + } + + printTransactions := sflags.MustGetBool(cmd, "transactions") + + jencoder, err := SetupJsonMarshaller(cmd, chain.BlockFactory().ProtoReflect().Descriptor().ParentFile()) + if err != nil { + return fmt.Errorf("unable to create json encoder: %w", err) + } + + storeURL := args[0] + store, err := dstore.NewDBinStore(storeURL) + if err != nil { + return fmt.Errorf("unable to create store at path %q: %w", store, err) + } + + blockNum, err := strconv.ParseUint(args[1], 10, 64) + if err != nil { + return fmt.Errorf("unable to parse block number %q: %w", args[1], err) + } + + var files []string + filePrefix := fmt.Sprintf("%010d", blockNum) + err = store.Walk(ctx, filePrefix, func(filename string) (err error) { + files = append(files, filename) + return nil + }) + if err != nil { + return fmt.Errorf("unable to find on block files: %w", err) + } + + for _, filepath := range files { + reader, err := store.OpenObject(ctx, filepath) + if err != nil { + fmt.Printf("❌ Unable to read block filename %s: %s\n", filepath, err) + return err + } + defer reader.Close() + + readerFactory, err := bstream.NewDBinBlockReader(reader) + if err != nil { + fmt.Printf("❌ Unable to read blocks filename %s: %s\n", filepath, err) + return err + } + + block, err := readerFactory.Read() + if err != nil { + if err == io.EOF { + break + } + return fmt.Errorf("reading block: %w", err) + } + + if err := displayBlock(block, chain, outputMode, printTransactions, jencoder); err != nil { + // Error is ready to be passed to the user as-is + return err + } + } + return nil + } +} + +//go:generate go-enum -f=$GOFILE --marshal --names --nocase + +type PrintOutputMode uint + +func toolsPrintCmdGetOutputMode(cmd *cobra.Command) (PrintOutputMode, error) { + outputModeRaw := sflags.MustGetString(cmd, "output") + + var out PrintOutputMode + if err := out.UnmarshalText([]byte(outputModeRaw)); err != nil { + return out, fmt.Errorf("invalid value %q: %w", outputModeRaw, err) + } + + return out, nil +} + +func displayBlock[B firecore.Block](pbBlock *pbbstream.Block, chain *firecore.Chain[B], outputMode PrintOutputMode, printTransactions bool, encoder *fcjson.Marshaller) error { + if pbBlock == nil { + return fmt.Errorf("block is nil") + } + + if outputMode == PrintOutputModeText { + if err := PrintBStreamBlock(pbBlock, printTransactions, os.Stdout); err != nil { + return fmt.Errorf("pbBlock text printing: %w", err) + } + return nil + } + + if !firecore.UnsafeRunningFromFirecore { + // since we are running via the chain specific binary (i.e. fireeth) we can use a BlockFactory + marshallableBlock := chain.BlockFactory() + + if err := pbBlock.Payload.UnmarshalTo(marshallableBlock); err != nil { + return fmt.Errorf("pbBlock payload unmarshal: %w", err) + } + + err := encoder.Marshal(marshallableBlock) + if err != nil { + return fmt.Errorf("pbBlock JSON printing: json marshal: %w", err) + } + return nil + } + + // since we are running directly the firecore binary we will *NOT* use the BlockFactory + err := encoder.Marshal(pbBlock.Payload) + if err != nil { + return fmt.Errorf("marshalling block to json: %w", err) + } + + return nil +} + +func PrintBStreamBlock(b *pbbstream.Block, printTransactions bool, out io.Writer) error { + _, err := out.Write( + []byte( + fmt.Sprintf( + "Block #%d (%s)\n", + b.Number, + b.Id, + ), + ), + ) + if err != nil { + return fmt.Errorf("writing block: %w", err) + } + + if printTransactions { + if _, err = out.Write([]byte("warning: transaction printing not supported by bstream block")); err != nil { + return fmt.Errorf("writing transaction support warning: %w", err) + } + } + + return nil +} + +func SetupJsonMarshaller(cmd *cobra.Command, chainFileDescriptor protoreflect.FileDescriptor) (*fcjson.Marshaller, error) { + registry, err := proto.NewRegistry(chainFileDescriptor, sflags.MustGetStringSlice(cmd, "proto-paths")...) + if err != nil { + return nil, fmt.Errorf("new registry: %w", err) + } + + var options []fcjson.MarshallerOption + bytesEncoding := sflags.MustGetString(cmd, "bytes-encoding") + if bytesEncoding == "base58" { + options = append(options, fcjson.WithBytesEncoderFunc(fcjson.ToBase58)) + } + + return fcjson.NewMarshaller(registry, options...), nil +} diff --git a/firehose/firehose-core/cmd/tools/print/tools_print_enum.go b/firehose/firehose-core/cmd/tools/print/tools_print_enum.go new file mode 100644 index 0000000..1c70e04 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/print/tools_print_enum.go @@ -0,0 +1,96 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: +// Revision: +// Build Date: +// Built By: + +package print + +import ( + "fmt" + "strings" +) + +const ( + // PrintOutputModeText is a PrintOutputMode of type Text. + PrintOutputModeText PrintOutputMode = iota + // PrintOutputModeJSON is a PrintOutputMode of type JSON. + PrintOutputModeJSON + // PrintOutputModeJSONL is a PrintOutputMode of type JSONL. + PrintOutputModeJSONL +) + +var ErrInvalidPrintOutputMode = fmt.Errorf("not a valid PrintOutputMode, try [%s]", strings.Join(_PrintOutputModeNames, ", ")) + +const _PrintOutputModeName = "TextJSONJSONL" + +var _PrintOutputModeNames = []string{ + _PrintOutputModeName[0:4], + _PrintOutputModeName[4:8], + _PrintOutputModeName[8:13], +} + +// PrintOutputModeNames returns a list of possible string values of PrintOutputMode. +func PrintOutputModeNames() []string { + tmp := make([]string, len(_PrintOutputModeNames)) + copy(tmp, _PrintOutputModeNames) + return tmp +} + +var _PrintOutputModeMap = map[PrintOutputMode]string{ + PrintOutputModeText: _PrintOutputModeName[0:4], + PrintOutputModeJSON: _PrintOutputModeName[4:8], + PrintOutputModeJSONL: _PrintOutputModeName[8:13], +} + +// String implements the Stringer interface. +func (x PrintOutputMode) String() string { + if str, ok := _PrintOutputModeMap[x]; ok { + return str + } + return fmt.Sprintf("PrintOutputMode(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x PrintOutputMode) IsValid() bool { + _, ok := _PrintOutputModeMap[x] + return ok +} + +var _PrintOutputModeValue = map[string]PrintOutputMode{ + _PrintOutputModeName[0:4]: PrintOutputModeText, + strings.ToLower(_PrintOutputModeName[0:4]): PrintOutputModeText, + _PrintOutputModeName[4:8]: PrintOutputModeJSON, + strings.ToLower(_PrintOutputModeName[4:8]): PrintOutputModeJSON, + _PrintOutputModeName[8:13]: PrintOutputModeJSONL, + strings.ToLower(_PrintOutputModeName[8:13]): PrintOutputModeJSONL, +} + +// ParsePrintOutputMode attempts to convert a string to a PrintOutputMode. +func ParsePrintOutputMode(name string) (PrintOutputMode, error) { + if x, ok := _PrintOutputModeValue[name]; ok { + return x, nil + } + // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. + if x, ok := _PrintOutputModeValue[strings.ToLower(name)]; ok { + return x, nil + } + return PrintOutputMode(0), fmt.Errorf("%s is %w", name, ErrInvalidPrintOutputMode) +} + +// MarshalText implements the text marshaller method. +func (x PrintOutputMode) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *PrintOutputMode) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParsePrintOutputMode(name) + if err != nil { + return err + } + *x = tmp + return nil +} diff --git a/firehose/firehose-core/cmd/tools/tools.go b/firehose/firehose-core/cmd/tools/tools.go new file mode 100644 index 0000000..9a096d5 --- /dev/null +++ b/firehose/firehose-core/cmd/tools/tools.go @@ -0,0 +1,72 @@ +// Copyright 2021 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package tools + +import ( + "fmt" + + "github.com/spf13/cobra" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/cmd/tools/check" + "github.com/streamingfast/firehose-core/cmd/tools/compare" + "github.com/streamingfast/firehose-core/cmd/tools/firehose" + "github.com/streamingfast/firehose-core/cmd/tools/fix" + "github.com/streamingfast/firehose-core/cmd/tools/mergeblock" + print2 "github.com/streamingfast/firehose-core/cmd/tools/print" + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +var ToolsCmd = &cobra.Command{Use: "tools", Short: "Developer tools for operators and developers"} + +func ConfigureToolsCmd[B firecore.Block]( + chain *firecore.Chain[B], + logger *zap.Logger, + tracer logging.Tracer, +) error { + + ToolsCmd.AddCommand(check.NewCheckCommand(chain, logger)) + ToolsCmd.AddCommand(print2.NewToolsPrintCmd(chain)) + + ToolsCmd.AddCommand(compare.NewToolsCompareBlocksCmd(chain)) + ToolsCmd.AddCommand(firehose.NewToolsDownloadFromFirehoseCmd(chain, logger)) + ToolsCmd.AddCommand(firehose.NewToolsFirehoseClientCmd(chain, logger)) + ToolsCmd.AddCommand(firehose.NewToolsFirehoseSingleBlockClientCmd(chain, logger, tracer)) + ToolsCmd.AddCommand(firehose.NewToolsFirehosePrometheusExporterCmd(chain, logger, tracer)) + ToolsCmd.AddCommand(mergeblock.NewToolsUnmergeBlocksCmd(chain, logger)) + ToolsCmd.AddCommand(mergeblock.NewToolsMergeBlocksCmd(chain, logger)) + ToolsCmd.AddCommand(fix.NewToolsFixBloatedMergedBlocks(chain, logger)) + + if chain.Tools.MergedBlockUpgrader != nil { + ToolsCmd.AddCommand(mergeblock.NewToolsUpgradeMergedBlocksCmd(chain, logger)) + } + + if chain.Tools.RegisterExtraCmd != nil { + if err := chain.Tools.RegisterExtraCmd(chain, ToolsCmd, logger, tracer); err != nil { + return fmt.Errorf("registering extra tools command: %w", err) + } + } + + var walkCmd func(node *cobra.Command) + walkCmd = func(node *cobra.Command) { + firecore.HideGlobalFlagsOnChildCmd(node) + for _, child := range node.Commands() { + walkCmd(child) + } + } + walkCmd(ToolsCmd) + + return nil +} diff --git a/firehose/firehose-core/config/examples/merge-accumulated-one-blocks.yaml b/firehose/firehose-core/config/examples/merge-accumulated-one-blocks.yaml new file mode 100644 index 0000000..b7ecce9 --- /dev/null +++ b/firehose/firehose-core/config/examples/merge-accumulated-one-blocks.yaml @@ -0,0 +1,36 @@ +start: + args: + - merger + flags: + log-to-file: false + data-dir: /data//firehose-data + + # Use the first rounded block number as the starting point from the one-blocks file you + # want to merge. For example, if you have in your one-blocks folder (in this config + # it would be /data//firehose-data/storage/one-blocks) the files: + # - 0005222014-a4f7d8b748525f5f-9c0632eec4c64c8b-5221815-default.dbin.zst + # - 0005222015-ba4f7d8b748525f5f-ac0632eec4c64c8b-5221817-default.dbin.zst + # - 0005222016-c4f7d8b748525f5f-ac0632eec4c64c8b-5221817-default.dbin.zst + # - ... + # + # Then the upper 100s rounded block number is 5222100 so you would use + # the config value 'common-first-streamable-block: 5222100'. + # + # If the block you have is exactly 5222100, then you should use 5222100 as + # the starting point. + # + # You can use `find /data//firehose-data/storage/one-blocks -name "*.zst" | sort | head -1` + # (or your appropriate 'common-one-block-store-url' value) to find the first block + # you have. The block number is the first value in the file name (the others are + # block's hash, last irreversible block's hash, last irreversible block's num). + # + # The range before the staring point will need to be reprocessed based on + # the chain's recovery mechanism which is out of scope here. + common-first-streamable-block: 100 + + # You should set this to the last block number you have in your one-blocks folder + # rounded down to 100s. + # + # For example, if you have in your one-blocks folder last block 8725, + # then you should use 8700 as the stop block. + merger-stop-block: 8700 diff --git a/firehose/firehose-core/consolereader.go b/firehose/firehose-core/consolereader.go new file mode 100644 index 0000000..210aec6 --- /dev/null +++ b/firehose/firehose-core/consolereader.go @@ -0,0 +1,263 @@ +package firecore + +import ( + "encoding/hex" + "fmt" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/firehose-core/node-manager/mindreader" + "github.com/streamingfast/firehose-core/pb/sf/fuel/type/v1" + "github.com/streamingfast/logging" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" + "io" + "os" + "strings" + "sync" + "time" +) + +const FirePrefix = "FIRE " +const FirePrefixLen = len(FirePrefix) +const FuelProtoPrefix = "PROTO " +const FuelProtoPrefixLen = len(FuelProtoPrefix) + +type ParsingStats struct { +} + +type ConsoleReader struct { + lines chan string + done chan interface{} + closeOnce sync.Once + logger *zap.Logger + tracer logging.Tracer + + // Parsing context + readerProtocolVersion string + protoMessageType string + lastBlock bstream.BlockRef + lastParentBlock bstream.BlockRef + lastBlockTimestamp time.Time + + lib uint64 + + blockRate *dmetrics.AvgRatePromCounter +} + +func NewConsoleReader(lines chan string, blockEncoder BlockEncoder, logger *zap.Logger, tracer logging.Tracer) (mindreader.ConsolerReader, error) { + reader := newConsoleReader(lines, logger, tracer) + + delayBetweenStats := 30 * time.Second + if tracer.Enabled() { + delayBetweenStats = 5 * time.Second + } + + go func() { + defer reader.blockRate.Stop() + + for { + select { + case <-reader.done: + return + case <-time.After(delayBetweenStats): + reader.printStats() + } + } + }() + + return reader, nil +} + +func newConsoleReader(lines chan string, logger *zap.Logger, tracer logging.Tracer) *ConsoleReader { + return &ConsoleReader{ + lines: lines, + done: make(chan interface{}), + logger: logger, + tracer: tracer, + protoMessageType: "sf.fuel.type.v1.Block", + blockRate: dmetrics.MustNewAvgRateFromPromCounter(ConsoleReaderBlockReadCount, 1*time.Second, 30*time.Second, "blocks"), + } +} + +func (r *ConsoleReader) Done() <-chan interface{} { + return r.done +} + +func (r *ConsoleReader) Close() error { + r.closeOnce.Do(func() { + r.blockRate.SyncNow() + r.printStats() + + r.logger.Info("console reader done") + close(r.done) + }) + + return nil +} + +type blockRefView struct { + ref bstream.BlockRef +} + +func (v blockRefView) String() string { + if v.ref == nil { + return "" + } + + return v.ref.String() +} + +type blockRefViewTimestamp struct { + ref bstream.BlockRef + timestamp time.Time +} + +func (v blockRefViewTimestamp) String() string { + return fmt.Sprintf("%s @ %s", blockRefView{v.ref}, v.timestamp.Local().Format(time.RFC822Z)) +} + +func (r *ConsoleReader) printStats() { + r.logger.Info("console reader stats", + zap.Stringer("block_rate", r.blockRate), + zap.Stringer("last_block", blockRefViewTimestamp{r.lastBlock, r.lastBlockTimestamp}), + zap.Stringer("last_parent_block", blockRefView{r.lastParentBlock}), + zap.Uint64("lib", r.lib), + ) +} + +func (r *ConsoleReader) ReadBlock() (out *pbbstream.Block, err error) { + out, err = r.next() + if err != nil { + return nil, err + } + + err = writeUint32ToFile("last_height.txt", out.GetFirehoseBlockNumber()) + if err != nil { + return nil, err + } + + return out, nil +} + +func (r *ConsoleReader) next() (out *pbbstream.Block, err error) { + for line := range r.lines { + if !strings.HasPrefix(line, FirePrefix) { + continue + } + + line = line[FirePrefixLen:] + + switch { + case strings.HasPrefix(line, FuelProtoPrefix): + out, err = r.readFuelProto(line[FuelProtoPrefixLen:]) + + default: + if r.tracer.Enabled() { + r.logger.Debug("skipping unknown Firehose log line", zap.String("line", line)) + } + continue + } + + if err != nil { + chunks := strings.SplitN(line, " ", 2) + return nil, fmt.Errorf("%s: %s (line %q)", chunks[0], err, line) + } + + if out != nil { + return out, nil + } + } + + r.Close() + + return nil, io.EOF +} + +func (r *ConsoleReader) readFuelProto(params string) (*pbbstream.Block, error) { + + fuelBlock := &pbfuel.Block{} + + out, err := hex.DecodeString(params) + if err != nil { + return nil, fmt.Errorf("read block %d: invalid string value: %w. Unable to decode the string", err) + } + + if err := proto.Unmarshal(out, fuelBlock); err != nil { + return nil, fmt.Errorf("read block %d: invalid proto: %w") + } + if err != nil { + return nil, fmt.Errorf("unable to unmarshal decoded string: %w", err) + } + + blockPayload := &anypb.Any{ + TypeUrl: r.protoMessageType, + Value: out, + } + + block := &pbbstream.Block{ + Id: fuelBlock.GetFirehoseBlockID(), + Number: fuelBlock.GetFirehoseBlockNumber(), + ParentId: fuelBlock.GetFirehoseBlockParentID(), + ParentNum: fuelBlock.GetFirehoseBlockParentNumber(), + Timestamp: timestamppb.New(fuelBlock.GetFirehoseBlockTime()), + LibNum: fuelBlock.GetFirehoseBlockLIBNum(), + Payload: blockPayload, + } + + ConsoleReaderBlockReadCount.Inc() + r.lastBlock = bstream.NewBlockRef(fuelBlock.GetFirehoseBlockID(), fuelBlock.GetFirehoseBlockNumber()) + r.lastParentBlock = bstream.NewBlockRef(fuelBlock.GetFirehoseBlockParentID(), fuelBlock.GetFirehoseBlockParentNumber()) + r.lastBlockTimestamp = fuelBlock.GetFirehoseBlockTime() + r.lib = fuelBlock.GetFirehoseBlockLIBNum() + + return block, nil +} + +func (r *ConsoleReader) setProtoMessageType(typeURL string) { + if strings.HasPrefix(typeURL, "type.googleapis.com/") { + r.protoMessageType = typeURL + return + } + + if strings.Contains(typeURL, "/") { + panic(fmt.Sprintf("invalid type url %q, expecting type.googleapis.com/", typeURL)) + } + + r.protoMessageType = "type.googleapis.com/" + typeURL +} + +// splitInBoundedChunks splits the line in `count` chunks and returns the slice `chunks[1:count]` (so exclusive end), +// but will accumulate all trailing chunks within the last (for free-form strings, or JSON objects) +func splitInBoundedChunks(line string, count int) ([]string, error) { + chunks := strings.SplitN(line, " ", count) + if len(chunks) != count { + return nil, fmt.Errorf("%d fields required but found %d fields for line %q", count, len(chunks), line) + } + + return chunks, nil +} + +func writeUint32ToFile(filePath string, num uint64) error { + file, err := os.Create(filePath) + + if err != nil { + return fmt.Errorf("error creating file: %w", err) + } + + defer func() { + if closeErr := file.Close(); closeErr != nil && err == nil { + err = fmt.Errorf("error closing file: %w", closeErr) + } + }() + + _, writeErr := fmt.Fprint(file, num) + + if writeErr != nil { + return fmt.Errorf("error writing to file: %w", writeErr) + } + + return nil +} diff --git a/firehose/firehose-core/consolereader_test.go b/firehose/firehose-core/consolereader_test.go new file mode 100644 index 0000000..cb525c0 --- /dev/null +++ b/firehose/firehose-core/consolereader_test.go @@ -0,0 +1,89 @@ +package firecore + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "testing" + "time" + + "github.com/streamingfast/firehose-core/test" + "github.com/streamingfast/logging" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/anypb" +) + +var zlogTest, tracerTest = logging.PackageLogger("test", "github.com/streamingfast/firehose-core/firecore") + +func Test_Ctx_readBlock(t *testing.T) { + reader := &ConsoleReader{ + logger: zlogTest, + tracer: tracerTest, + + readerProtocolVersion: "1.0", + protoMessageType: "type.googleapis.com/sf.ethereum.type.v2.Block", + } + + blockHash := "d2836a703a02f3ca2a13f05efe26fc48c6fa0db0d754a49e56b066d3b7d54659" + blockHashBytes, err := hex.DecodeString(blockHash) + blockNumber := uint64(18571000) + + parentHash := "55de88c909fa368ae1e93b6b8ffb3fbb12e64aefec1d4a1fcc27ae7633de2f81" + parentBlockNumber := 18570999 + + libNumber := 18570800 + + pbBlock := test.Block{ + Hash: blockHashBytes, + Number: blockNumber, + } + + anypbBlock, err := anypb.New(&pbBlock) + + require.NoError(t, err) + nowNano := time.Now().UnixNano() + line := fmt.Sprintf( + "%d %s %d %s %d %d %s", + blockNumber, + blockHash, + parentBlockNumber, + parentHash, + libNumber, + nowNano, + base64.StdEncoding.EncodeToString(anypbBlock.Value), + ) + + block := reader.readBlock(line) + require.NoError(t, err) + + require.Equal(t, blockNumber, block.Number) + require.Equal(t, blockHash, block.Id) + require.Equal(t, parentHash, block.ParentId) + require.Equal(t, uint64(libNumber), block.LibNum) + require.Equal(t, int32(time.Unix(0, nowNano).Nanosecond()), block.Timestamp.Nanos) + + require.NoError(t, err) + require.Equal(t, anypbBlock.GetValue(), block.Payload.Value) + +} + +func Test_GetNext(t *testing.T) { + lines := make(chan string, 2) + reader := newConsoleReader(lines, zlogTest, tracerTest) + + initLine := "FIRE INIT 1.0 sf.ethereum.type.v2.Block" + blockLine := "FIRE BLOCK 18571000 d2836a703a02f3ca2a13f05efe26fc48c6fa0db0d754a49e56b066d3b7d54659 18570999 55de88c909fa368ae1e93b6b8ffb3fbb12e64aefec1d4a1fcc27ae7633de2f81 18570800 1699992393935935000 Ci10eXBlLmdvb2dsZWFwaXMuY29tL3NmLmV0aGVyZXVtLnR5cGUudjIuQmxvY2sSJxIg0oNqcDoC88oqE/Be/ib8SMb6DbDXVKSeVrBm07fVRlkY+L3tCA==" + + lines <- initLine + lines <- blockLine + close(lines) + + block, err := reader.ReadBlock() + require.NoError(t, err) + + require.Equal(t, uint64(18571000), block.Number) + require.Equal(t, "d2836a703a02f3ca2a13f05efe26fc48c6fa0db0d754a49e56b066d3b7d54659", block.Id) + require.Equal(t, "55de88c909fa368ae1e93b6b8ffb3fbb12e64aefec1d4a1fcc27ae7633de2f81", block.ParentId) + require.Equal(t, uint64(18570800), block.LibNum) + require.Equal(t, int32(time.Unix(0, 1699992393935935000).Nanosecond()), block.Timestamp.Nanos) +} diff --git a/firehose/firehose-core/constants.go b/firehose/firehose-core/constants.go new file mode 100644 index 0000000..026e4e1 --- /dev/null +++ b/firehose/firehose-core/constants.go @@ -0,0 +1,26 @@ +package firecore + +// Those are `var` and globally available so that some chains to keep backward-compatibility can +// change them. This is not advertised and should **not** be used by new chain. +var ( + MaxUint64 = ^uint64(0) + // Common ports + MetricsListenAddr string = ":9102" + + // Firehose chain specific port + IndexBuilderServiceAddr string = ":10009" + ReaderNodeGRPCAddr string = ":10010" + ReaderNodeManagerAPIAddr string = ":10011" + MergerServingAddr string = ":10012" + RelayerServingAddr string = ":10014" + FirehoseGRPCServingAddr string = ":10015" + SubstreamsTier1GRPCServingAddr string = ":10016" + SubstreamsTier2GRPCServingAddr string = ":10017" + + // Data storage default locations + BlocksCacheDirectory string = "file://{data-dir}/storage/blocks-cache" + MergedBlocksStoreURL string = "file://{data-dir}/storage/merged-blocks" + OneBlockStoreURL string = "file://{data-dir}/storage/one-blocks" + ForkedBlocksStoreURL string = "file://{data-dir}/storage/forked-blocks" + IndexStoreURL string = "file://{data-dir}/storage/index" +) diff --git a/firehose/firehose-core/devel/firecore b/firehose/firehose-core/devel/firecore new file mode 100755 index 0000000..7411e78 --- /dev/null +++ b/firehose/firehose-core/devel/firecore @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" + +active_pid= +binary=firecore + +main() { + set -e + + version="unknown" + if [[ -f .version ]]; then + version=`cat .version` + fi + + pushd "$ROOT" &> /dev/null + go install -ldflags "-X main.Version=$version" ./cmd/$binary + popd &> /dev/null + + if [[ $KILL_AFTER != "" ]]; then + "`go env GOPATH`/bin/$binary" "$@" & + active_pid=$! + + sleep $KILL_AFTER + kill -s TERM $active_pid &> /dev/null || true + else + exec "`go env GOPATH`/bin/$binary" "$@" + fi +} + +main "$@" diff --git a/firehose/firehose-core/devel/standard/bootstrap.sh b/firehose/firehose-core/devel/standard/bootstrap.sh new file mode 100644 index 0000000..9f36263 --- /dev/null +++ b/firehose/firehose-core/devel/standard/bootstrap.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash + +set -e + +FIREHOSE_EXTRACT_BIN="/app/firehose-extract" +FIREFUEL_BIN="/app/firecore" +STORAGE_DIR="/data/storage_dir" +CHAIN_ID="$1" + +COMMON_LIVE_BLOCKS_ADDR="$(hostname -I | awk '{print $1}'):10024" + +if [ -z "$CHAIN_ID" ]; then + echo "Usage: $0 CHAIN_ID" + return 1 +fi + +HEIGHT_FILE="$STORAGE_DIR/last_height.txt" + +if [[ -f "$HEIGHT_FILE" ]]; then + LAST_HEIGHT=$(($(cat "$HEIGHT_FILE") - 1)) +else + LAST_HEIGHT=0 +fi + +TEMPFILE="$(mktemp)" + +cat <"$TEMPFILE" +start: + args: + - reader-node + - merger + - relayer + - firehose + - substreams-tier1 + - substreams-tier2 + flags: + common-first-streamable-block: 1 + reader-node-path: "$FIREHOSE_EXTRACT_BIN" + reader-node-arguments: $CHAIN_ID $LAST_HEIGHT + + common-live-blocks-addr: "$COMMON_LIVE_BLOCKS_ADDR" + reader-node-grpc-listen-addr: "$COMMON_LIVE_BLOCKS_ADDR" + +END + +cd "$STORAGE_DIR" +exec "$FIREFUEL_BIN" -c "$TEMPFILE" start + diff --git a/firehose/firehose-core/devel/standard/start.sh b/firehose/firehose-core/devel/standard/start.sh new file mode 100755 index 0000000..26ba980 --- /dev/null +++ b/firehose/firehose-core/devel/standard/start.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +clean= +firecore="$ROOT/../firecore" + +main() { + pushd "$ROOT" &> /dev/null + + while getopts "hc" opt; do + case $opt in + h) usage && exit 0;; + c) clean=true;; + \?) usage_error "Invalid option: -$OPTARG";; + esac + done + shift $((OPTIND-1)) + [[ $1 = "--" ]] && shift + + set -e + + if [[ $clean == "true" ]]; then + rm -rf firehose-data &> /dev/null || true + fi + + exec $firecore -c $(basename $ROOT).yaml start "$@" +} + +usage_error() { + message="$1" + exit_code="$2" + + echo "ERROR: $message" + echo "" + usage + exit ${exit_code:-1} +} + +usage() { + echo "usage: start.sh [-c]" + echo "" + echo "Start $(basename $ROOT) environment." + echo "" + echo "Options" + echo " -c Clean actual data directory first" +} + +main "$@" \ No newline at end of file diff --git a/firehose/firehose-core/devel/standard_local/bootstrap.sh b/firehose/firehose-core/devel/standard_local/bootstrap.sh new file mode 100755 index 0000000..cf8e2a4 --- /dev/null +++ b/firehose/firehose-core/devel/standard_local/bootstrap.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash + +set -e + +CHAIN_ID="beta-5.fuel.network" + +if [ -z "$CHAIN_ID" ]; then + echo "Usage: $0 CHAIN_ID" + return 1 +fi + +HEIGHT_FILE="last_height.txt" + +if [[ -f "$HEIGHT_FILE" ]]; then + LAST_HEIGHT=$(($(cat "$HEIGHT_FILE") - 1)) +else + LAST_HEIGHT=0 +fi + +cat <"standard_local.yaml" +start: + args: + - reader-node + - merger + - relayer + - firehose + - substreams-tier1 + - substreams-tier2 + flags: + + common-first-streamable-block: 1 + reader-node-path: "../../../firehose-extract/target/debug/firehose-extract" + reader-node-arguments: $CHAIN_ID $LAST_HEIGHT + + common-live-blocks-addr: "localhost:10024" + reader-node-grpc-listen-addr: "localhost:10024" + +END + + diff --git a/firehose/firehose-core/devel/standard_local/start.sh b/firehose/firehose-core/devel/standard_local/start.sh new file mode 100755 index 0000000..581dfb0 --- /dev/null +++ b/firehose/firehose-core/devel/standard_local/start.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +clean= +firecore="$ROOT/../firecore" +bootstrap="$ROOT/bootstrap.sh" + + +main() { + pushd "$ROOT" &> /dev/null + + echo $ROOT + + while getopts "hc" opt; do + case $opt in + h) usage && exit 0;; + c) clean=true;; + \?) usage_error "Invalid option: -$OPTARG";; + esac + done + shift $((OPTIND-1)) + [[ $1 = "--" ]] && shift + + set -e + + $bootstrap "$@" + + if [[ $clean == "true" ]]; then + rm -rf firehose-data &> /dev/null || true + fi + + exec $firecore -c $(basename $ROOT).yaml start "$@" +} + +usage_error() { + message="$1" + exit_code="$2" + + echo "ERROR: $message" + echo "" + usage + exit ${exit_code:-1} +} + +usage() { + echo "usage: start.sh [-c]" + echo "" + echo "Start $(basename $ROOT) environment." + echo "" + echo "Options" + echo " -c Clean actual data directory first" +} + +main "$@" \ No newline at end of file diff --git a/firehose/firehose-core/firehose/app/firehose/app.go b/firehose/firehose-core/firehose/app/firehose/app.go new file mode 100644 index 0000000..2fb83b7 --- /dev/null +++ b/firehose/firehose-core/firehose/app/firehose/app.go @@ -0,0 +1,225 @@ +// Copyright 2020 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package firehose + +import ( + "context" + "fmt" + "net/url" + "time" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/blockstream" + "github.com/streamingfast/bstream/hub" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/bstream/transform" + "github.com/streamingfast/dauth" + dgrpcserver "github.com/streamingfast/dgrpc/server" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/dstore" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/firehose" + "github.com/streamingfast/firehose-core/firehose/metrics" + "github.com/streamingfast/firehose-core/firehose/server" + "github.com/streamingfast/logging" + "github.com/streamingfast/shutter" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +type Config struct { + MergedBlocksStoreURL string + OneBlocksStoreURL string + ForkedBlocksStoreURL string + BlockStreamAddr string // gRPC endpoint to get real-time blocks, can be "" in which live streams is disabled + GRPCListenAddr string // gRPC address where this app will listen to + GRPCShutdownGracePeriod time.Duration // The duration we allow for gRPC connections to terminate gracefully prior forcing shutdown + ServiceDiscoveryURL *url.URL + ServerOptions []server.Option `json:"-"` +} + +type RegisterServiceExtensionFunc func(server dgrpcserver.Server, + mergedBlocksStore dstore.Store, + forkedBlocksStore dstore.Store, // this can be nil here + forkableHub *hub.ForkableHub, + logger *zap.Logger) + +type Modules struct { + // Required dependencies + Authenticator dauth.Authenticator + HeadTimeDriftMetric *dmetrics.HeadTimeDrift + HeadBlockNumberMetric *dmetrics.HeadBlockNum + TransformRegistry *transform.Registry + RegisterServiceExtension RegisterServiceExtensionFunc + CheckPendingShutdown func() bool +} + +type App struct { + *shutter.Shutter + config *Config + modules *Modules + logger *zap.Logger + tracer logging.Tracer + isReady *atomic.Bool +} + +func New(logger *zap.Logger, tracer logging.Tracer, config *Config, modules *Modules) *App { + return &App{ + Shutter: shutter.New(), + config: config, + modules: modules, + logger: logger, + tracer: tracer, + + isReady: atomic.NewBool(false), + } +} + +func (a *App) Run() error { + dmetrics.Register(metrics.Metricset) + + a.logger.Info("running firehose", zap.Reflect("config", a.config)) + if err := a.config.Validate(); err != nil { + return fmt.Errorf("invalid app config: %w", err) + } + + mergedBlocksStore, err := dstore.NewDBinStore(a.config.MergedBlocksStoreURL) + if err != nil { + return fmt.Errorf("failed setting up block store from url %q: %w", a.config.MergedBlocksStoreURL, err) + } + + oneBlocksStore, err := dstore.NewDBinStore(a.config.OneBlocksStoreURL) + if err != nil { + return fmt.Errorf("failed setting up block store from url %q: %w", a.config.OneBlocksStoreURL, err) + } + + // set to empty store interface if URL is "" + var forkedBlocksStore dstore.Store + if a.config.ForkedBlocksStoreURL != "" { + forkedBlocksStore, err = dstore.NewDBinStore(a.config.ForkedBlocksStoreURL) + if err != nil { + return fmt.Errorf("failed setting up block store from url %q: %w", a.config.ForkedBlocksStoreURL, err) + } + } + + withLive := a.config.BlockStreamAddr != "" + + var forkableHub *hub.ForkableHub + + if withLive { + liveSourceFactory := bstream.SourceFactory(func(h bstream.Handler) bstream.Source { + + return blockstream.NewSource( + context.Background(), + a.config.BlockStreamAddr, + 2, + bstream.HandlerFunc(func(blk *pbbstream.Block, obj interface{}) error { + a.modules.HeadBlockNumberMetric.SetUint64(blk.Number) + a.modules.HeadTimeDriftMetric.SetBlockTime(blk.Time()) + return h.ProcessBlock(blk, obj) + }), + blockstream.WithRequester("firehose"), + ) + }) + + oneBlocksSourceFactory := bstream.SourceFromNumFactoryWithSkipFunc(func(num uint64, h bstream.Handler, skipFunc func(string) bool) bstream.Source { + src, err := bstream.NewOneBlocksSource(num, oneBlocksStore, h, bstream.OneBlocksSourceWithSkipperFunc(skipFunc)) + if err != nil { + return nil + } + return src + }) + + forkableHub = hub.NewForkableHub(liveSourceFactory, oneBlocksSourceFactory, 500) + forkableHub.OnTerminated(a.Shutdown) + + go forkableHub.Run() + } + + streamFactory := firecore.NewStreamFactory( + mergedBlocksStore, + forkedBlocksStore, + forkableHub, + a.modules.TransformRegistry, + ) + + blockGetter := firehose.NewBlockGetter(mergedBlocksStore, forkedBlocksStore, forkableHub) + + firehoseServer := server.New( + a.modules.TransformRegistry, + streamFactory, + blockGetter, + a.logger, + a.modules.Authenticator, + a.IsReady, + a.config.GRPCListenAddr, + a.config.ServiceDiscoveryURL, + a.config.ServerOptions..., + ) + + a.OnTerminating(func(_ error) { + firehoseServer.Shutdown(a.config.GRPCShutdownGracePeriod) + }) + firehoseServer.OnTerminated(a.Shutdown) + + if a.modules.RegisterServiceExtension != nil { + a.modules.RegisterServiceExtension( + firehoseServer.Server, + mergedBlocksStore, + forkedBlocksStore, + forkableHub, + a.logger) + } + + go func() { + if withLive { + a.logger.Info("waiting until hub is real-time synced") + select { + case <-forkableHub.Ready: + metrics.AppReadiness.SetReady() + case <-a.Terminating(): + return + } + } + + a.logger.Info("launching gRPC firehoseServer", zap.Bool("live_support", withLive)) + a.isReady.CAS(false, true) + firehoseServer.Launch() + }() + + return nil +} + +// IsReady return `true` if the apps is ready to accept requests, `false` is returned +// otherwise. +func (a *App) IsReady(ctx context.Context) bool { + if a.IsTerminating() { + return false + } + if a.modules.CheckPendingShutdown != nil && a.modules.CheckPendingShutdown() { + return false + } + if !a.modules.Authenticator.Ready(ctx) { + return false + } + + return a.isReady.Load() +} + +// Validate inspects itself to determine if the current config is valid according to +// Firehose rules. +func (config *Config) Validate() error { + return nil +} diff --git a/firehose/firehose-core/firehose/block_getter.go b/firehose/firehose-core/firehose/block_getter.go new file mode 100644 index 0000000..6ac564b --- /dev/null +++ b/firehose/firehose-core/firehose/block_getter.go @@ -0,0 +1,109 @@ +package firehose + +import ( + "context" + "errors" + "fmt" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/hub" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/derr" + "github.com/streamingfast/dmetering" + "github.com/streamingfast/dstore" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +type BlockGetter struct { + mergedBlocksStore dstore.Store + forkedBlocksStore dstore.Store + hub *hub.ForkableHub +} + +func NewBlockGetter( + mergedBlocksStore dstore.Store, + forkedBlocksStore dstore.Store, + hub *hub.ForkableHub, +) *BlockGetter { + return &BlockGetter{ + mergedBlocksStore: mergedBlocksStore, + forkedBlocksStore: forkedBlocksStore, + hub: hub, + } +} + +func (g *BlockGetter) Get( + ctx context.Context, + num uint64, + id string, + logger *zap.Logger) (out *pbbstream.Block, err error) { + + id = bstream.NormalizeBlockID(id) + reqLogger := logger.With( + zap.Uint64("num", num), + zap.String("id", id), + ) + + // check for block in live segment: Hub + if g.hub != nil && num > g.hub.LowestBlockNum() { + if blk := g.hub.GetBlock(num, id); blk != nil { + reqLogger.Info("single block request", zap.String("source", "hub"), zap.Bool("found", true)) + return blk, nil + } + reqLogger.Info("single block request", zap.String("source", "hub"), zap.Bool("found", false)) + return nil, status.Error(codes.NotFound, "live block not found in hub") + } + + mergedBlocksStore := g.mergedBlocksStore + if clonable, ok := mergedBlocksStore.(dstore.Clonable); ok { + var err error + mergedBlocksStore, err = clonable.Clone(ctx) + if err != nil { + return nil, err + } + mergedBlocksStore.SetMeter(dmetering.GetBytesMeter(ctx)) + } + + // check for block in mergedBlocksStore + err = derr.RetryContext(ctx, 3, func(ctx context.Context) error { + blk, err := bstream.FetchBlockFromMergedBlocksStore(ctx, num, mergedBlocksStore) + if err != nil { + if errors.Is(err, dstore.ErrNotFound) { + return derr.NewFatalError(err) + } + return err + } + if id == "" || blk.Id == id { + reqLogger.Info("single block request", zap.String("source", "merged_blocks"), zap.Bool("found", true)) + out = blk + return nil + } + return derr.NewFatalError(fmt.Errorf("wrong block: found %s, expecting %s", blk.Id, id)) + }) + if out != nil { + return out, nil + } + + // check for block in forkedBlocksStore + if g.forkedBlocksStore != nil { + forkedBlocksStore := g.forkedBlocksStore + if clonable, ok := forkedBlocksStore.(dstore.Clonable); ok { + var err error + forkedBlocksStore, err = clonable.Clone(ctx) + if err != nil { + return nil, err + } + forkedBlocksStore.SetMeter(dmetering.GetBytesMeter(ctx)) + } + + if blk, _ := bstream.FetchBlockFromOneBlockStore(ctx, num, id, forkedBlocksStore); blk != nil { + reqLogger.Info("single block request", zap.String("source", "forked_blocks"), zap.Bool("found", true)) + return blk, nil + } + } + + reqLogger.Info("single block request", zap.Bool("found", false), zap.Error(err)) + return nil, status.Error(codes.NotFound, "block not found in files") +} diff --git a/firehose/firehose-core/firehose/client/client.go b/firehose/firehose-core/firehose/client/client.go new file mode 100644 index 0000000..64c4fd4 --- /dev/null +++ b/firehose/firehose-core/firehose/client/client.go @@ -0,0 +1,112 @@ +package client + +import ( + "context" + "crypto/tls" + "fmt" + + "github.com/streamingfast/dgrpc" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "golang.org/x/oauth2" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials/oauth" + "google.golang.org/grpc/metadata" +) + +// firehoseClient, closeFunc, grpcCallOpts, err := NewFirehoseClient(endpoint, jwt, insecure, plaintext) +// defer closeFunc() +// stream, err := firehoseClient.Blocks(context.Background(), request, grpcCallOpts...) +func NewFirehoseClient(endpoint, jwt, apiKey string, useInsecureTSLConnection, usePlainTextConnection bool) (cli pbfirehose.StreamClient, closeFunc func() error, callOpts []grpc.CallOption, err error) { + + if useInsecureTSLConnection && usePlainTextConnection { + return nil, nil, nil, fmt.Errorf("option --insecure and --plaintext are mutually exclusive, they cannot be both specified at the same time") + } + + var dialOptions []grpc.DialOption + switch { + case usePlainTextConnection: + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + + case useInsecureTSLConnection: + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} + } + + conn, err := dgrpc.NewExternalClient(endpoint, dialOptions...) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to create external gRPC client: %w", err) + } + closeFunc = conn.Close + cli = pbfirehose.NewStreamClient(conn) + + if !usePlainTextConnection { + if jwt != "" { + credentials := oauth.NewOauthAccess(&oauth2.Token{AccessToken: jwt, TokenType: "Bearer"}) + callOpts = append(callOpts, grpc.PerRPCCredentials(credentials)) + } else if apiKey != "" { + callOpts = append(callOpts, grpc.PerRPCCredentials(&ApiKeyAuth{ApiKey: apiKey})) + } + } + + return +} + +type ApiKeyAuth struct { + ApiKey string +} + +func (a *ApiKeyAuth) GetRequestMetadata(ctx context.Context, uri ...string) (out map[string]string, err error) { + md, ok := metadata.FromOutgoingContext(ctx) + if !ok { + md = metadata.New(nil) + } + out = make(map[string]string) + for k, v := range md { + if len(v) != 0 { + out[k] = v[0] + } + } + if a.ApiKey != "" { + out["x-api-key"] = a.ApiKey + } + return +} + +func (a *ApiKeyAuth) RequireTransportSecurity() bool { + return true +} + +func NewFirehoseFetchClient(endpoint, jwt, apiKey string, useInsecureTSLConnection, usePlainTextConnection bool) (cli pbfirehose.FetchClient, closeFunc func() error, callOpts []grpc.CallOption, err error) { + + if useInsecureTSLConnection && usePlainTextConnection { + return nil, nil, nil, fmt.Errorf("option --insecure and --plaintext are mutually exclusive, they cannot be both specified at the same time") + } + + var dialOptions []grpc.DialOption + switch { + case usePlainTextConnection: + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + + case useInsecureTSLConnection: + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} + } + + if !usePlainTextConnection { + if jwt != "" { + credentials := oauth.NewOauthAccess(&oauth2.Token{AccessToken: jwt, TokenType: "Bearer"}) + callOpts = append(callOpts, grpc.PerRPCCredentials(credentials)) + } else if apiKey != "" { + callOpts = append(callOpts, grpc.PerRPCCredentials(&ApiKeyAuth{ApiKey: apiKey})) + } + } + + conn, err := dgrpc.NewExternalClient(endpoint, dialOptions...) + if err != nil { + return nil, nil, nil, fmt.Errorf("unable to create external gRPC client: %w", err) + } + closeFunc = conn.Close + cli = pbfirehose.NewFetchClient(conn) + + return +} diff --git a/firehose/firehose-core/firehose/init_test.go b/firehose/firehose-core/firehose/init_test.go new file mode 100644 index 0000000..b35a7c3 --- /dev/null +++ b/firehose/firehose-core/firehose/init_test.go @@ -0,0 +1,9 @@ +package firehose + +import ( + "github.com/streamingfast/logging" +) + +func init() { + logging.InstantiateLoggers() +} diff --git a/firehose/firehose-core/firehose/metrics/metrics.go b/firehose/firehose-core/firehose/metrics/metrics.go new file mode 100644 index 0000000..ac1c146 --- /dev/null +++ b/firehose/firehose-core/firehose/metrics/metrics.go @@ -0,0 +1,17 @@ +package metrics + +import ( + "github.com/streamingfast/dmetrics" +) + +var Metricset = dmetrics.NewSet() + +var AppReadiness = Metricset.NewAppReadiness("firehose") +var ActiveRequests = Metricset.NewGauge("firehose_active_requests", "Number of active requests") +var RequestCounter = Metricset.NewCounter("firehose_requests_counter", "Request count") + +var ActiveSubstreams = Metricset.NewGauge("firehose_active_substreams", "Number of active substreams requests") +var SubstreamsCounter = Metricset.NewCounter("firehose_substreams_counter", "Substreams requests count") + +// var CurrentListeners = Metricset.NewGaugeVec("current_listeners", []string{"req_type"}, "...") +// var TimedOutPushingTrxCount = Metricset.NewCounterVec("something", []string{"guarantee"}, "Number of requests for push_transaction timed out while submitting") diff --git a/firehose/firehose-core/firehose/rate/limiter.go b/firehose/firehose-core/firehose/rate/limiter.go new file mode 100644 index 0000000..4ff4e7b --- /dev/null +++ b/firehose/firehose-core/firehose/rate/limiter.go @@ -0,0 +1,71 @@ +package rate + +import ( + "context" + "fmt" + "time" +) + +type Limiter interface { + Take(ctx context.Context, id string, method string) (allow bool) + Return() + String() string +} + +type token bool + +type leakyBucketLimiter struct { + tokens chan token + + dripInterval time.Duration +} + +func NewLeakyBucketLimiter(size int, dripInterval time.Duration) Limiter { + tks := make(chan token, size) + for i := 0; i < size; i++ { + tks <- token(true) + } + + go func() { + for { + select { + case <-time.After(dripInterval): + select { + case tks <- token(true): + // + default: + // + } + } + } + }() + + return &leakyBucketLimiter{ + tokens: tks, + dripInterval: dripInterval, + } +} + +func (l *leakyBucketLimiter) Take(ctx context.Context, id string, method string) (allow bool) { + select { + case <-l.tokens: + return true + case <-ctx.Done(): + return false + default: + return false + } +} + +func (l *leakyBucketLimiter) Return() { + select { + case l.tokens <- token(true): + // + default: + // + } +} + +func (l *leakyBucketLimiter) String() string { + return fmt.Sprintf("leaky-bucket-limiter(len=%d, cap=%d, drip-interval=%s)", len(l.tokens), cap(l.tokens), l.dripInterval) +} diff --git a/firehose/firehose-core/firehose/server/blocks.go b/firehose/firehose-core/firehose/server/blocks.go new file mode 100644 index 0000000..e7c9a72 --- /dev/null +++ b/firehose/firehose-core/firehose/server/blocks.go @@ -0,0 +1,328 @@ +package server + +import ( + "context" + "errors" + "fmt" + "math/rand" + "os" + "time" + + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/bstream/stream" + "github.com/streamingfast/dauth" + "github.com/streamingfast/dmetering" + "github.com/streamingfast/firehose-core/firehose/metrics" + "github.com/streamingfast/logging" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" +) + +func (s *Server) Block(ctx context.Context, request *pbfirehose.SingleBlockRequest) (*pbfirehose.SingleBlockResponse, error) { + var blockNum uint64 + var blockHash string + switch ref := request.Reference.(type) { + case *pbfirehose.SingleBlockRequest_BlockHashAndNumber_: + blockNum = ref.BlockHashAndNumber.Num + blockHash = ref.BlockHashAndNumber.Hash + case *pbfirehose.SingleBlockRequest_Cursor_: + cur, err := bstream.CursorFromOpaque(ref.Cursor.Cursor) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + blockNum = cur.Block.Num() + blockHash = cur.Block.ID() + case *pbfirehose.SingleBlockRequest_BlockNumber_: + blockNum = ref.BlockNumber.Num + } + + ctx = dmetering.WithBytesMeter(ctx) + blk, err := s.blockGetter.Get(ctx, blockNum, blockHash, s.logger) + if err != nil { + if _, ok := status.FromError(err); ok { + return nil, err + } + return nil, status.Error(codes.Internal, err.Error()) + } + if blk == nil { + return nil, status.Errorf(codes.NotFound, "block %s not found", bstream.NewBlockRef(blockHash, blockNum)) + } + + resp := &pbfirehose.SingleBlockResponse{ + Block: blk.Payload, + } + + ////////////////////////////////////////////////////////////////////// + meter := dmetering.GetBytesMeter(ctx) + bytesRead := meter.BytesReadDelta() + bytesWritten := meter.BytesWrittenDelta() + size := proto.Size(resp) + + auth := dauth.FromContext(ctx) + event := dmetering.Event{ + UserID: auth.UserID(), + ApiKeyID: auth.APIKeyID(), + IpAddress: auth.RealIP(), + Meta: auth.Meta(), + Endpoint: "sf.firehose.v2.Firehose/Block", + Metrics: map[string]float64{ + "egress_bytes": float64(size), + "written_bytes": float64(bytesWritten), + "read_bytes": float64(bytesRead), + "block_count": 1, + }, + Timestamp: time.Now(), + } + dmetering.Emit(ctx, event) + ////////////////////////////////////////////////////////////////////// + + return resp, nil +} + +func (s *Server) Blocks(request *pbfirehose.Request, streamSrv pbfirehose.Stream_BlocksServer) error { + ctx := streamSrv.Context() + metrics.RequestCounter.Inc() + + logger := logging.Logger(ctx, s.logger) + + if s.rateLimiter != nil { + rlCtx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + if allow := s.rateLimiter.Take(rlCtx, "", "Blocks"); !allow { + jitterDelay := time.Duration(rand.Intn(3000) + 1000) // force a minimal backoff + <-time.After(time.Millisecond * jitterDelay) + return status.Error(codes.Unavailable, "rate limit exceeded") + } else { + defer s.rateLimiter.Return() + } + } + + metrics.ActiveRequests.Inc() + defer metrics.ActiveRequests.Dec() + + if os.Getenv("FIREHOSE_SEND_HOSTNAME") != "" { + hostname, err := os.Hostname() + if err != nil { + hostname = "unknown" + logger.Warn("cannot determine hostname, using 'unknown'", zap.Error(err)) + } + md := metadata.New(map[string]string{"hostname": hostname}) + if err := streamSrv.SendHeader(md); err != nil { + logger.Warn("cannot send metadata header", zap.Error(err)) + } + } + + isLiveBlock := func(step pbfirehose.ForkStep) bool { + if step == pbfirehose.ForkStep_STEP_NEW { + return true + } + + return false + } + + var blockCount uint64 + handlerFunc := bstream.HandlerFunc(func(block *pbbstream.Block, obj interface{}) error { + blockCount++ + cursorable := obj.(bstream.Cursorable) + cursor := cursorable.Cursor() + + stepable := obj.(bstream.Stepable) + step := stepable.Step() + + wrapped := obj.(bstream.ObjectWrapper) + obj = wrapped.WrappedObject() + if obj == nil { + obj = block.Payload + } + + protoStep, skip := stepToProto(step, request.FinalBlocksOnly) + if skip { + return nil + } + + resp := &pbfirehose.Response{ + Step: protoStep, + Cursor: cursor.ToOpaque(), + } + + switch v := obj.(type) { + case *anypb.Any: + resp.Block = v + break + case proto.Message: + cnt, err := anypb.New(v) + if err != nil { + return fmt.Errorf("to any: %w", err) + } + resp.Block = cnt + default: + // this can be the out + return fmt.Errorf("unknown object type %t, cannot marshal to protobuf Any", v) + } + + if s.postHookFunc != nil { + s.postHookFunc(ctx, resp) + } + start := time.Now() + err := streamSrv.Send(resp) + if err != nil { + logger.Info("stream send error", zap.Uint64("block_num", block.Number), zap.String("block_id", block.Id), zap.Error(err)) + return NewErrSendBlock(err) + } + + if isLiveBlock(protoStep) { + dmetering.GetBytesMeter(ctx).AddBytesRead(len(block.Payload.Value)) + } + + level := zap.DebugLevel + if block.Number%200 == 0 { + level = zap.InfoLevel + } + + logger.Check(level, "stream sent block").Write(zap.Uint64("block_num", block.Number), zap.String("block_id", block.Id), zap.Duration("duration", time.Since(start))) + + return nil + }) + + if s.transformRegistry != nil { + passthroughTr, err := s.transformRegistry.PassthroughFromTransforms(request.Transforms) + if err != nil { + return status.Errorf(codes.Internal, "unable to create pre-proc function: %s", err) + } + + if passthroughTr != nil { + metrics.ActiveSubstreams.Inc() + defer metrics.ActiveSubstreams.Dec() + metrics.SubstreamsCounter.Inc() + outputFunc := func(cursor *bstream.Cursor, message *anypb.Any) error { + var blocknum uint64 + var opaqueCursor string + var outStep pbfirehose.ForkStep + if cursor != nil { + blocknum = cursor.Block.Num() + opaqueCursor = cursor.ToOpaque() + + protoStep, skip := stepToProto(cursor.Step, request.FinalBlocksOnly) + if skip { + return nil + } + outStep = protoStep + } + resp := &pbfirehose.Response{ + Step: outStep, + Cursor: opaqueCursor, + Block: message, + } + if s.postHookFunc != nil { + s.postHookFunc(ctx, resp) + } + start := time.Now() + err := streamSrv.Send(resp) + if err != nil { + logger.Info("stream send error from transform", zap.Uint64("blocknum", blocknum), zap.Error(err)) + return NewErrSendBlock(err) + } + + level := zap.DebugLevel + if blocknum%200 == 0 { + level = zap.InfoLevel + } + logger.Check(level, "stream sent message from transform").Write(zap.Uint64("blocknum", blocknum), zap.Duration("duration", time.Since(start))) + return nil + } + request.Transforms = nil + + return passthroughTr.Run(ctx, request, s.streamFactory.New, outputFunc) + // --> will want to start a few firehose instances,sources, manage them, process them... + // --> I give them an output func to print back to the user with the request + // --> I could HERE give him the + } + } else if len(request.Transforms) > 0 { + return status.Errorf(codes.Unimplemented, "no transforms registry configured within this instance") + } + + ctx = s.initFunc(ctx, request) + str, err := s.streamFactory.New(ctx, handlerFunc, request, logger) + if err != nil { + return err + } + + err = str.Run(ctx) + meter := getRequestMeter(ctx) + + fields := []zap.Field{ + zap.Uint64("block_sent", meter.blocks), + zap.Int("egress_bytes", meter.egressBytes), + zap.Error(err), + } + + auth := dauth.FromContext(ctx) + if auth != nil { + fields = append(fields, + zap.String("api_key_id", auth.APIKeyID()), + zap.String("user_id", auth.UserID()), + zap.String("real_ip", auth.RealIP()), + ) + } + logger.Info("firehose process completed", fields...) + if err != nil { + if errors.Is(err, stream.ErrStopBlockReached) { + logger.Info("stream of blocks reached end block") + return nil + } + + if errors.Is(err, context.Canceled) { + if ctx.Err() != context.Canceled { + logger.Debug("stream of blocks ended with context canceled, but our own context was not canceled", zap.Error(err)) + } + return status.Error(codes.Canceled, "source canceled") + } + + if errors.Is(err, context.DeadlineExceeded) { + logger.Info("stream of blocks ended with context deadline exceeded", zap.Error(err)) + return status.Error(codes.DeadlineExceeded, "source deadline exceeded") + } + + var errInvalidArg *stream.ErrInvalidArg + if errors.As(err, &errInvalidArg) { + return status.Error(codes.InvalidArgument, errInvalidArg.Error()) + } + + var errSendBlock *ErrSendBlock + if errors.As(err, &errSendBlock) { + logger.Info("unable to send block probably due to client disconnecting", zap.Error(errSendBlock.inner)) + return status.Error(codes.Unavailable, errSendBlock.inner.Error()) + } + + logger.Info("unexpected stream of blocks termination", zap.Error(err)) + return status.Errorf(codes.Internal, "unexpected stream termination") + } + + logger.Error("source is not expected to terminate gracefully, should stop at block or continue forever") + return status.Error(codes.Internal, "unexpected stream completion") + +} + +func stepToProto(step bstream.StepType, finalBlocksOnly bool) (outStep pbfirehose.ForkStep, skip bool) { + if finalBlocksOnly { + if step.Matches(bstream.StepIrreversible) { + return pbfirehose.ForkStep_STEP_FINAL, false + } + return 0, true + } + + if step.Matches(bstream.StepNew) { + return pbfirehose.ForkStep_STEP_NEW, false + } + if step.Matches(bstream.StepUndo) { + return pbfirehose.ForkStep_STEP_UNDO, false + } + return 0, true // simply skip irreversible or stalled here +} diff --git a/firehose/firehose-core/firehose/server/errors.go b/firehose/firehose-core/firehose/server/errors.go new file mode 100644 index 0000000..2264e5e --- /dev/null +++ b/firehose/firehose-core/firehose/server/errors.go @@ -0,0 +1,19 @@ +package server + +import ( + "fmt" +) + +type ErrSendBlock struct { + inner error +} + +func NewErrSendBlock(inner error) ErrSendBlock { + return ErrSendBlock{ + inner: inner, + } +} + +func (e ErrSendBlock) Error() string { + return fmt.Sprintf("send error: %s", e.inner) +} diff --git a/firehose/firehose-core/firehose/server/local.go b/firehose/firehose-core/firehose/server/local.go new file mode 100644 index 0000000..43f0db2 --- /dev/null +++ b/firehose/firehose-core/firehose/server/local.go @@ -0,0 +1,77 @@ +package server + +import ( + "context" + + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type BlocksPipe struct { + //grpc.ServerStream + grpc.ClientStream + ctx context.Context + pipeChan chan *pbfirehose.Response + err error +} + +func (p *BlocksPipe) SendHeader(metadata.MD) error { + return nil +} +func (p *BlocksPipe) SetHeader(metadata.MD) error { + return nil +} +func (p *BlocksPipe) SetTrailer(metadata.MD) { + return +} + +func (p *BlocksPipe) Context() context.Context { + return p.ctx +} + +func (p *BlocksPipe) Send(resp *pbfirehose.Response) error { + select { + case <-p.ctx.Done(): + return p.ctx.Err() + case p.pipeChan <- resp: + } + return nil +} + +func (p *BlocksPipe) Recv() (*pbfirehose.Response, error) { + select { + case resp, ok := <-p.pipeChan: + if !ok { + return resp, p.err + } + return resp, nil + case <-p.ctx.Done(): + select { + // ensure we empty the pipeChan + case resp, ok := <-p.pipeChan: + if !ok { + return resp, p.err + } + return resp, nil + default: + return nil, p.err + } + } +} + +func (s *Server) BlocksFromLocal(ctx context.Context, req *pbfirehose.Request) pbfirehose.Stream_BlocksClient { + cctx, cancel := context.WithCancel(ctx) + + pipe := &BlocksPipe{ + ctx: cctx, + pipeChan: make(chan *pbfirehose.Response), + } + go func() { + err := s.Blocks(req, pipe) + pipe.err = err + cancel() + }() + + return pipe +} diff --git a/firehose/firehose-core/firehose/server/server.go b/firehose/firehose-core/firehose/server/server.go new file mode 100644 index 0000000..0a9a208 --- /dev/null +++ b/firehose/firehose-core/firehose/server/server.go @@ -0,0 +1,184 @@ +package server + +import ( + "context" + "net/url" + "strings" + "time" + + _ "github.com/mostynb/go-grpc-compression/zstd" + "github.com/streamingfast/bstream/transform" + "github.com/streamingfast/dauth" + dauthgrpc "github.com/streamingfast/dauth/middleware/grpc" + dgrpcserver "github.com/streamingfast/dgrpc/server" + "github.com/streamingfast/dgrpc/server/factory" + "github.com/streamingfast/dmetering" + "github.com/streamingfast/dmetrics" + firecore "github.com/streamingfast/firehose-core" + "github.com/streamingfast/firehose-core/firehose" + "github.com/streamingfast/firehose-core/firehose/rate" + pbfirehoseV1 "github.com/streamingfast/pbgo/sf/firehose/v1" + pbfirehoseV2 "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.opentelemetry.io/otel" + "go.uber.org/zap" + "google.golang.org/grpc" + _ "google.golang.org/grpc/encoding/gzip" + "google.golang.org/protobuf/proto" +) + +type Server struct { + streamFactory *firecore.StreamFactory + transformRegistry *transform.Registry + blockGetter *firehose.BlockGetter + + initFunc func(context.Context, *pbfirehoseV2.Request) context.Context + postHookFunc func(context.Context, *pbfirehoseV2.Response) + + dgrpcserver.Server + listenAddr string + healthListenAddr string + logger *zap.Logger + metrics dmetrics.Set + + rateLimiter rate.Limiter +} + +type Option func(*Server) + +func WithLeakyBucketLimiter(size int, dripRate time.Duration) Option { + return func(s *Server) { + s.rateLimiter = rate.NewLeakyBucketLimiter(size, dripRate) + } +} + +func New( + transformRegistry *transform.Registry, + streamFactory *firecore.StreamFactory, + blockGetter *firehose.BlockGetter, + logger *zap.Logger, + authenticator dauth.Authenticator, + isReady func(context.Context) bool, + listenAddr string, + serviceDiscoveryURL *url.URL, + opts ...Option, +) *Server { + initFunc := func(ctx context.Context, _ *pbfirehoseV2.Request) context.Context { + ////////////////////////////////////////////////////////////////////// + ctx = dmetering.WithBytesMeter(ctx) + ctx = withRequestMeter(ctx) + return ctx + ////////////////////////////////////////////////////////////////////// + } + + postHookFunc := func(ctx context.Context, response *pbfirehoseV2.Response) { + ////////////////////////////////////////////////////////////////////// + meter := dmetering.GetBytesMeter(ctx) + bytesRead := meter.BytesReadDelta() + bytesWritten := meter.BytesWrittenDelta() + size := proto.Size(response) + + auth := dauth.FromContext(ctx) + event := dmetering.Event{ + UserID: auth.UserID(), + ApiKeyID: auth.APIKeyID(), + IpAddress: auth.RealIP(), + Meta: auth.Meta(), + Endpoint: "sf.firehose.v2.Firehose/Blocks", + Metrics: map[string]float64{ + "egress_bytes": float64(size), + "written_bytes": float64(bytesWritten), + "read_bytes": float64(bytesRead), + "block_count": 1, + }, + Timestamp: time.Now(), + } + + requestMeter := getRequestMeter(ctx) + requestMeter.blocks++ + requestMeter.egressBytes += size + dmetering.Emit(ctx, event) + ////////////////////////////////////////////////////////////////////// + } + + tracerProvider := otel.GetTracerProvider() + options := []dgrpcserver.Option{ + dgrpcserver.WithLogger(logger), + dgrpcserver.WithHealthCheck(dgrpcserver.HealthCheckOverGRPC|dgrpcserver.HealthCheckOverHTTP, createHealthCheck(isReady)), + dgrpcserver.WithPostUnaryInterceptor(otelgrpc.UnaryServerInterceptor(otelgrpc.WithTracerProvider(tracerProvider))), + dgrpcserver.WithPostStreamInterceptor(otelgrpc.StreamServerInterceptor(otelgrpc.WithTracerProvider(tracerProvider))), + dgrpcserver.WithGRPCServerOptions(grpc.MaxRecvMsgSize(25 * 1024 * 1024)), + dgrpcserver.WithPostUnaryInterceptor(dauthgrpc.UnaryAuthChecker(authenticator, logger)), + dgrpcserver.WithPostStreamInterceptor(dauthgrpc.StreamAuthChecker(authenticator, logger)), + } + + if serviceDiscoveryURL != nil { + options = append(options, dgrpcserver.WithServiceDiscoveryURL(serviceDiscoveryURL)) + } + + if strings.Contains(listenAddr, "*") { + options = append(options, dgrpcserver.WithInsecureServer()) + } else { + options = append(options, dgrpcserver.WithPlainTextServer()) + } + + grpcServer := factory.ServerFromOptions(options...) + + s := &Server{ + Server: grpcServer, + transformRegistry: transformRegistry, + blockGetter: blockGetter, + streamFactory: streamFactory, + listenAddr: strings.ReplaceAll(listenAddr, "*", ""), + initFunc: initFunc, + postHookFunc: postHookFunc, + logger: logger, + } + + logger.Info("registering grpc services") + grpcServer.RegisterService(func(gs grpc.ServiceRegistrar) { + if blockGetter != nil { + pbfirehoseV2.RegisterFetchServer(gs, s) + } + pbfirehoseV2.RegisterStreamServer(gs, s) + pbfirehoseV1.RegisterStreamServer(gs, NewFirehoseProxyV1ToV2(s)) // compatibility with firehose + }) + + for _, opt := range opts { + opt(s) + } + + return s +} + +func (s *Server) Launch() { + s.Server.Launch(s.listenAddr) +} + +func createHealthCheck(isReady func(ctx context.Context) bool) dgrpcserver.HealthCheck { + return func(ctx context.Context) (bool, interface{}, error) { + return isReady(ctx), nil, nil + } +} + +type key int + +var requestMeterKey key + +type requestMeter struct { + blocks uint64 + egressBytes int +} + +func getRequestMeter(ctx context.Context) *requestMeter { + if rm, ok := ctx.Value(requestMeterKey).(*requestMeter); ok { + return rm + } + return &requestMeter{} // not so useful but won't break tests +} +func withRequestMeter(ctx context.Context) context.Context { + if _, ok := ctx.Value(requestMeterKey).(*requestMeter); ok { + return ctx + } + return context.WithValue(ctx, requestMeterKey, &requestMeter{}) +} diff --git a/firehose/firehose-core/firehose/server/v1proxy.go b/firehose/firehose-core/firehose/server/v1proxy.go new file mode 100644 index 0000000..a6724d5 --- /dev/null +++ b/firehose/firehose-core/firehose/server/v1proxy.go @@ -0,0 +1,86 @@ +package server + +import ( + "fmt" + + pbfirehoseV1 "github.com/streamingfast/pbgo/sf/firehose/v1" + pbfirehoseV2 "github.com/streamingfast/pbgo/sf/firehose/v2" + "google.golang.org/grpc" +) + +type FirehoseProxyV1ToV2 struct { + server *Server +} + +func NewFirehoseProxyV1ToV2(server *Server) *FirehoseProxyV1ToV2 { + return &FirehoseProxyV1ToV2{ + server: server, + } +} + +func (s *FirehoseProxyV1ToV2) Blocks(req *pbfirehoseV1.Request, streamSrv pbfirehoseV1.Stream_BlocksServer) error { + + var finalBlocksOnly bool + var validSteps bool + var withUndo bool + switch len(req.ForkSteps) { + case 1: + if req.ForkSteps[0] == pbfirehoseV1.ForkStep_STEP_IRREVERSIBLE { + finalBlocksOnly = true + validSteps = true + } + if req.ForkSteps[0] == pbfirehoseV1.ForkStep_STEP_NEW { + validSteps = true + } + case 2: + if (req.ForkSteps[0] == pbfirehoseV1.ForkStep_STEP_NEW && req.ForkSteps[1] == pbfirehoseV1.ForkStep_STEP_UNDO) || + (req.ForkSteps[1] == pbfirehoseV1.ForkStep_STEP_NEW && req.ForkSteps[0] == pbfirehoseV1.ForkStep_STEP_UNDO) { + validSteps = true + withUndo = true + } else if req.ForkSteps[0] == pbfirehoseV1.ForkStep_STEP_NEW && req.ForkSteps[1] == pbfirehoseV1.ForkStep_STEP_IRREVERSIBLE { + validSteps = true + // compatibility hack. you won't receive IRREVERSIBLE here + } + } + if !validSteps { + return fmt.Errorf("invalid parameter for ForkSteps: this server implements firehose v2 operation and only supports [NEW,UNDO] or [IRREVERSIBLE]") + } + + reqV2 := &pbfirehoseV2.Request{ + StartBlockNum: req.StartBlockNum, + Cursor: req.StartCursor, + StopBlockNum: req.StopBlockNum, + FinalBlocksOnly: finalBlocksOnly, + Transforms: req.Transforms, + } + + wrapper := streamWrapper{ServerStream: streamSrv, next: streamSrv, withUndo: withUndo} + + return s.server.Blocks(reqV2, wrapper) +} + +type streamWrapper struct { + grpc.ServerStream + next pbfirehoseV1.Stream_BlocksServer + withUndo bool +} + +func (w streamWrapper) Send(response *pbfirehoseV2.Response) error { + return w.next.Send(&pbfirehoseV1.Response{ + Block: response.Block, + Step: convertForkStep(response.Step), + Cursor: response.Cursor, + }) +} + +func convertForkStep(in pbfirehoseV2.ForkStep) pbfirehoseV1.ForkStep { + switch in { + case pbfirehoseV2.ForkStep_STEP_FINAL: + return pbfirehoseV1.ForkStep_STEP_IRREVERSIBLE + case pbfirehoseV2.ForkStep_STEP_NEW: + return pbfirehoseV1.ForkStep_STEP_NEW + case pbfirehoseV2.ForkStep_STEP_UNDO: + return pbfirehoseV1.ForkStep_STEP_UNDO + } + return pbfirehoseV1.ForkStep_STEP_UNKNOWN +} diff --git a/firehose/firehose-core/firehose/tests/integration_test.go b/firehose/firehose-core/firehose/tests/integration_test.go new file mode 100644 index 0000000..1e42891 --- /dev/null +++ b/firehose/firehose-core/firehose/tests/integration_test.go @@ -0,0 +1,236 @@ +package firehose + +//import ( +// "context" +// "encoding/json" +// "fmt" +// "testing" +// "time" +// +// "github.com/alicebob/miniredis/v2/server" +// "github.com/streamingfast/bstream" +// "github.com/streamingfast/dstore" +// pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" +// pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v1" +// "github.com/stretchr/testify/assert" +// "github.com/stretchr/testify/require" +// "go.uber.org/zap" +// "google.golang.org/protobuf/proto" +//) +// +//func TestFullFlow(t *testing.T) { +// +// stepNew := pbfirehose.ForkStep_STEP_NEW +// stepIrr := pbfirehose.ForkStep_STEP_IRREVERSIBLE +// stepUndo := pbfirehose.ForkStep_STEP_UNDO +// _ = stepUndo +// +// type expectedResp struct { +// num uint64 +// id string +// step pbfirehose.ForkStep +// } +// +// tests := []struct { +// name string +// files map[int][]byte +// irreversibleBlocksIndexes map[int]map[int]string +// startBlockNum uint64 +// stopBlockNum uint64 +// cursor *bstream.LastFiredBlock +// expectedResponses []expectedResp +// }{ +// { +// "scenario 1 -- irreversible index, no cursor", +// map[int][]byte{ +// 0: testBlocks( +// 4, "4a", "3a", 0, +// 6, "6a", "4a", 0, +// ), +// 100: testBlocks( +// 100, "100a", "6a", 6, +// 102, "102a", "100a", 6, +// 103, "103a", "102a", 100, // moves LIB from 6 to 100 +// ), +// 200: testBlocks( +// 204, "204b", "103a", 102, // moves LIB from 100 to 102 +// 205, "205b", "103b", 100, //unlinkable +// ), +// }, +// map[int]map[int]string{ +// 0: { +// 4: "4a", +// 6: "6a", +// }, +// 200: { // this hould not be used +// 204: "204a", +// 206: "206a", +// }, +// }, +// 5, +// 0, +// nil, +// []expectedResp{ +// {6, "6a", stepNew}, +// {6, "6a", stepIrr}, +// {100, "100a", stepNew}, +// {102, "102a", stepNew}, +// {103, "103a", stepNew}, +// {100, "100a", stepIrr}, +// {204, "204b", stepNew}, +// {102, "102a", stepIrr}, +// }, +// }, +// { +// "scenario 2 -- no irreversible index, start->stop with some libs", +// map[int][]byte{ +// 0: testBlocks( +// 4, "4a", "3a", 0, +// 6, "6a", "4a", 4, +// ), +// 100: testBlocks( +// 100, "100a", "6a", 6, +// 102, "102a", "100a", 6, +// 103, "103a", "102a", 100, // triggers StepIrr +// 104, "104a", "103a", 100, // after stop block +// ), +// }, +// nil, +// 6, +// 103, +// nil, +// []expectedResp{ +// {6, "6a", stepNew}, +// {100, "100a", stepNew}, +// {6, "6a", stepIrr}, +// {102, "102a", stepNew}, +// {103, "103a", stepNew}, +// }, +// }, +// } +// +// for _, c := range tests { +// t.Run(c.name, func(t *testing.T) { +// +// logger := zap.NewNop() +// bs := dstore.NewMockStore(nil) +// for i, data := range c.files { +// bs.SetFile(base(i), data) +// } +// +// irrStore := getIrrStore(c.irreversibleBlocksIndexes) +// +// // fake block decoder func to return pbbstream.Block +// bstream.GetBlockDecoder = bstream.BlockDecoderFunc(func(blk *pbbstream.Block) (interface{}, error) { +// block := new(pbbstream.Block) +// block.Number = blk.Number +// block.Id = blk.Id +// block.PreviousId = blk.PreviousId +// return block, nil +// }) +// +// tracker := bstream.NewTracker(0) // 0 value not used +// fmt.Println(bstream.GetProtocolFirstStreamableBlock) +// tracker.AddResolver(bstream.OffsetStartBlockResolver(200)) +// +// i := NewStreamFactory( +// []dstore.Store{bs}, +// irrStore, +// []uint64{10000, 1000, 100}, +// nil, +// nil, +// tracker, +// ) +// +// s := server.NewServer( +// logger, +// nil, +// i, +// ) +// +// ctx, cancelCtx := context.WithCancel(context.Background()) +// defer cancelCtx() +// localClient := s.BlocksFromLocal(ctx, &pbfirehose.Request{ +// StartBlockNum: int64(c.startBlockNum), +// StopBlockNum: c.stopBlockNum, +// }) +// +// for _, r := range c.expectedResponses { +// resp, err := localClient.Recv() +// require.NotNil(t, resp) +// require.NoError(t, err) +// +// fmt.Println(resp.LastFiredBlock) +// cursor, err := bstream.CursorFromOpaque(resp.LastFiredBlock) +// require.NoError(t, err, "cursor sent from firehose should always be valid") +// require.False(t, cursor.IsEmpty()) +// +// b := &pbbstream.Block{} +// err = proto.Unmarshal(resp.Block.Value, b) +// require.NoError(t, err) +// +// require.Equal(t, r.num, b.Number) +// require.Equal(t, r.id, b.Id) +// require.Equal(t, r.step, resp.Step) +// } +// +// // catchExtraBlock +// moreChan := make(chan *pbbstream.Block) +// go func() { +// resp, err := localClient.Recv() +// require.NoError(t, err) +// if resp == nil { +// return +// } +// +// b := &pbbstream.Block{} +// err = proto.Unmarshal(resp.Block.Value, b) +// require.NoError(t, err) +// moreChan <- b +// }() +// +// select { +// case resp := <-moreChan: +// assert.Falsef(t, true, "an extra block was seen: %s", resp.String()) +// case <-time.After(time.Millisecond * 50): +// } +// +// }) +// } +// +//} +// +//func base(in int) string { +// return fmt.Sprintf("%010d", in) +//} +// +//func testBlocks(in ...interface{}) (out []byte) { +// var blks []bstream.ParsableTestBlock +// for i := 0; i < len(in); i += 4 { +// blks = append(blks, bstream.ParsableTestBlock{ +// Number: uint64(in[i].(int)), +// ID: in[i+1].(string), +// PreviousID: in[i+2].(string), +// LIBNum: uint64(in[i+3].(int)), +// }) +// } +// +// for _, blk := range blks { +// b, err := json.Marshal(blk) +// if err != nil { +// panic(err) +// } +// out = append(out, b...) +// out = append(out, '\n') +// } +// return +//} +// +//func getIrrStore(irrBlkIdxs map[int]map[int]string) (irrStore *dstore.MockStore) { +// irrStore = dstore.NewMockStore(nil) +// for j, n := range irrBlkIdxs { +// filename, cnt := bstream.TestIrrBlocksIdx(j, 100, n) +// irrStore.SetFile(filename, cnt) +// } +// return +//} diff --git a/firehose/firehose-core/firehose/tests/stream_blocks_test.go b/firehose/firehose-core/firehose/tests/stream_blocks_test.go new file mode 100644 index 0000000..3e2dd0a --- /dev/null +++ b/firehose/firehose-core/firehose/tests/stream_blocks_test.go @@ -0,0 +1,103 @@ +package firehose + +//import ( +// "context" +// "strings" +// "testing" +// +// pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v1" +// +// "github.com/streamingfast/bstream" +// "github.com/streamingfast/dstore" +// pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" +// "github.com/stretchr/testify/assert" +// "github.com/stretchr/testify/require" +// "go.uber.org/zap" +// "google.golang.org/protobuf/proto" +//) +// +//func TestLocalBlocks(t *testing.T) { +// +// store := dstore.NewMockStore(nil) +// idxStore := dstore.NewMockStore(nil) +// blocksStores := []dstore.Store{store} +// logger := zap.NewNop() +// +// i := NewStreamFactory( +// blocksStores, +// idxStore, +// []uint64{10000, 1000, 100}, +// nil, +// nil, +// nil, +// ) +// +// s := NewServer( +// logger, +// nil, +// i, +// ) +// +// // fake block decoder func to return bstream.Block +// bstream.GetBlockDecoder = bstream.BlockDecoderFunc(func(blk *pbbstream.Block) (interface{}, error) { +// block := new(pbbstream.Block) +// block.Number = blk.Number +// block.Id = blk.Id +// block.PreviousId = blk.PreviousId +// return block, nil +// }) +// +// blocks := strings.Join([]string{ +// bstream.TestJSONBlockWithLIBNum("00000002a", "00000001a", 1), +// bstream.TestJSONBlockWithLIBNum("00000003a", "00000002a", 2), +// bstream.TestJSONBlockWithLIBNum("00000004a", "00000003a", 3), // last one closes on endblock +// }, "\n") +// +// store.SetFile("0000000000", []byte(blocks)) +// +// localClient := s.BlocksFromLocal(context.Background(), &pbfirehose.Request{ +// StartBlockNum: 2, +// StopBlockNum: 4, +// }) +// +// // ---- +// blk, err := localClient.Recv() +// require.NoError(t, err) +// b := &pbbstream.Block{} +// err = proto.Unmarshal(blk.Block.Value, b) +// require.NoError(t, err) +// require.Equal(t, uint64(2), b.Number) +// require.Equal(t, blk.Step, pbfirehose.ForkStep_STEP_NEW) +// +// // ---- +// blk, err = localClient.Recv() +// require.NoError(t, err) +// b = &pbbstream.Block{} +// err = proto.Unmarshal(blk.Block.Value, b) +// require.NoError(t, err) +// assert.Equal(t, uint64(3), b.Number) +// assert.Equal(t, blk.Step, pbfirehose.ForkStep_STEP_NEW) +// +// // ---- +// blk, err = localClient.Recv() +// require.NoError(t, err) +// b = &pbbstream.Block{} +// err = proto.Unmarshal(blk.Block.Value, b) +// require.NoError(t, err) +// assert.Equal(t, uint64(2), b.Number) +// assert.Equal(t, blk.Step, pbfirehose.ForkStep_STEP_IRREVERSIBLE) +// +// // ---- +// blk, err = localClient.Recv() +// require.NoError(t, err) +// b = &pbbstream.Block{} +// err = proto.Unmarshal(blk.Block.Value, b) +// require.NoError(t, err) +// assert.Equal(t, uint64(4), b.Number) +// assert.Equal(t, blk.Step, pbfirehose.ForkStep_STEP_NEW) +// +// // ---- +// blk, err = localClient.Recv() +// require.NoError(t, err) +// require.Nil(t, blk) +//} diff --git a/firehose/firehose-core/flags.go b/firehose/firehose-core/flags.go new file mode 100644 index 0000000..8b34415 --- /dev/null +++ b/firehose/firehose-core/flags.go @@ -0,0 +1,22 @@ +package firecore + +import "github.com/spf13/cobra" + +// globalFlagsHiddenOnChildCmd represents the list of global flags that should be hidden on child commands +var globalFlagsHiddenOnChildCmd = []string{ + "log-level-switcher-listen-addr", + "metrics-listen-addr", + "pprof-listen-addr", + "startup-delay", +} + +func HideGlobalFlagsOnChildCmd(cmd *cobra.Command) { + actual := cmd.HelpFunc() + cmd.SetHelpFunc(func(command *cobra.Command, strings []string) { + for _, flag := range globalFlagsHiddenOnChildCmd { + command.Flags().MarkHidden(flag) + } + + actual(command, strings) + }) +} diff --git a/firehose/firehose-core/go.mod b/firehose/firehose-core/go.mod new file mode 100644 index 0000000..e9c677f --- /dev/null +++ b/firehose/firehose-core/go.mod @@ -0,0 +1,233 @@ +module github.com/streamingfast/firehose-core + +go 1.22 + +require ( + buf.build/gen/go/bufbuild/reflect/connectrpc/go v1.12.0-20230822193137-310c9c4845dd.1 + buf.build/gen/go/bufbuild/reflect/protocolbuffers/go v1.31.0-20230822193137-310c9c4845dd.2 + connectrpc.com/connect v1.15.0 + github.com/ShinyTrinkets/overseer v0.3.0 + github.com/dustin/go-humanize v1.0.1 + github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d + github.com/iancoleman/strcase v0.3.0 + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 + github.com/mostynb/go-grpc-compression v1.1.17 + github.com/prometheus/client_golang v1.16.0 + github.com/spf13/cobra v1.7.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.15.0 + github.com/streamingfast/bstream v0.0.2-0.20240228193450-5200ecab8050 + github.com/streamingfast/cli v0.0.4-0.20230825151644-8cc84512cd80 + github.com/streamingfast/dauth v0.0.0-20240222213226-519afc16cf84 + github.com/streamingfast/dbin v0.9.1-0.20231117225723-59790c798e2c + github.com/streamingfast/derr v0.0.0-20230515163924-8570aaa43fe1 + github.com/streamingfast/dgrpc v0.0.0-20240222213940-b9f324ff4d5c + github.com/streamingfast/dhammer v0.0.0-20230125192823-c34bbd561bd4 + github.com/streamingfast/dmetering v0.0.0-20240403142935-dc8bb3bb32c3 + github.com/streamingfast/dmetrics v0.0.0-20230919161904-206fa8ebd545 + github.com/streamingfast/dstore v0.1.1-0.20240325191553-bcce8892a9bb + github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0 + github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 + github.com/streamingfast/pbgo v0.0.6-0.20240131193313-6b88bc7139db + github.com/streamingfast/snapshotter v0.0.0-20230316190750-5bcadfde44d0 + github.com/streamingfast/substreams v1.5.3-0.20240405214647-fa91cf14ac98 + github.com/stretchr/testify v1.8.4 + github.com/test-go/testify v1.1.4 + go.uber.org/multierr v1.10.0 + go.uber.org/zap v1.26.0 + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 + google.golang.org/grpc v1.63.2 + google.golang.org/protobuf v1.33.0 +) + +require ( + connectrpc.com/grpchealth v1.3.0 // indirect + connectrpc.com/grpcreflect v1.2.0 // indirect + connectrpc.com/otelconnect v0.7.0 // indirect + github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/google/s2a-go v0.1.7 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/sercand/kuberesolver/v5 v5.1.1 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect +) + +require ( + cloud.google.com/go v0.112.0 // indirect + cloud.google.com/go/compute v1.24.0 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go/monitoring v1.18.0 // indirect + cloud.google.com/go/storage v1.38.0 // indirect + cloud.google.com/go/trace v1.10.5 // indirect + contrib.go.opencensus.io/exporter/stackdriver v0.13.10 // indirect + contrib.go.opencensus.io/exporter/zipkin v0.1.1 // indirect + github.com/Azure/azure-pipeline-go v0.2.3 // indirect + github.com/Azure/azure-storage-blob-go v0.14.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.32.3 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.15.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.39.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.0.0-20221018185641-36f91511cfd7 // indirect + github.com/KimMachineGun/automemlimit v0.2.4 + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/RoaringBitmap/roaring v1.9.1 // indirect + github.com/ShinyTrinkets/meta-logger v0.2.0 // indirect + github.com/abourget/llerrgroup v0.2.0 + github.com/aws/aws-sdk-go v1.44.325 // indirect + github.com/benbjohnson/clock v1.3.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bits-and-blooms/bitset v1.12.0 // indirect + github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d // indirect + github.com/bobg/go-generics/v2 v2.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/chzyer/readline v1.5.0 // indirect + github.com/cilium/ebpf v0.4.0 // indirect + github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa // indirect + github.com/containerd/cgroups v1.0.4 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/envoyproxy/go-control-plane v0.12.0 // indirect + github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.21.1 // indirect + github.com/godbus/dbus/v5 v5.1.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect + github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/gorilla/mux v1.8.0 + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/ipfs/boxo v0.8.0 // indirect + github.com/ipfs/go-cid v0.4.0 // indirect + github.com/ipfs/go-ipfs-api v0.6.0 // indirect + github.com/jhump/protoreflect v1.14.0 + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/josephburnett/jd v1.7.1 + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.6 // indirect + github.com/klauspost/cpuid/v2 v2.2.3 // indirect + github.com/libp2p/go-buffer-pool v0.1.0 // indirect + github.com/libp2p/go-flow-metrics v0.1.0 // indirect + github.com/libp2p/go-libp2p v0.26.3 // indirect + github.com/lithammer/dedent v1.1.0 // indirect + github.com/logrusorgru/aurora v2.0.3+incompatible // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/manifoldco/promptui v0.9.0 // indirect + github.com/mattn/go-ieproxy v0.0.1 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mr-tron/base58 v1.2.0 + github.com/mschoch/smat v0.2.0 // indirect + github.com/multiformats/go-base32 v0.1.0 // indirect + github.com/multiformats/go-base36 v0.2.0 // indirect + github.com/multiformats/go-multiaddr v0.8.0 // indirect + github.com/multiformats/go-multibase v0.1.1 // indirect + github.com/multiformats/go-multicodec v0.8.1 // indirect + github.com/multiformats/go-multihash v0.2.1 // indirect + github.com/multiformats/go-multistream v0.4.1 // indirect + github.com/multiformats/go-varint v0.0.7 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/opencontainers/runtime-spec v1.0.2 // indirect + github.com/openzipkin/zipkin-go v0.4.2 // indirect + github.com/paulbellamy/ratecounter v0.2.0 // indirect + github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.5.0 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.0 // indirect + github.com/rs/cors v1.10.0 // indirect + github.com/schollz/closestmatch v2.1.0+incompatible // indirect + github.com/sethvargo/go-retry v0.2.3 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spaolacci/murmur3 v1.1.0 // indirect + github.com/spf13/afero v1.10.0 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/streamingfast/dtracing v0.0.0-20220305214756-b5c0e8699839 + github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308 // indirect + github.com/streamingfast/sf-tracing v0.0.0-20240209202324-9daa52c71a52 + github.com/streamingfast/shutter v1.5.0 + github.com/subosito/gotenv v1.4.2 // indirect + github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf // indirect + github.com/tetratelabs/wazero v1.7.0 // indirect + github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c // indirect + github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 // indirect + go.opencensus.io v0.24.0 + go.opentelemetry.io/contrib/detectors/gcp v1.9.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 + go.opentelemetry.io/otel v1.24.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 // indirect + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.23.1 // indirect + go.opentelemetry.io/otel/exporters/zipkin v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.24.0 // indirect + go.opentelemetry.io/otel/sdk v1.23.1 // indirect + go.opentelemetry.io/otel/trace v1.24.0 // indirect + go.opentelemetry.io/proto/otlp v1.1.0 // indirect + go.uber.org/atomic v1.10.0 + go.uber.org/automaxprocs v1.5.1 + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/oauth2 v0.18.0 + golang.org/x/sync v0.6.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect + google.golang.org/api v0.172.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/olivere/elastic.v3 v3.0.75 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.25.0 // indirect + k8s.io/apimachinery v0.25.0 // indirect + k8s.io/client-go v0.25.0 // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect + lukechampine.com/blake3 v1.1.7 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.2.0 // indirect +) + +replace ( + github.com/ShinyTrinkets/overseer => github.com/streamingfast/overseer v0.2.1-0.20210326144022-ee491780e3ef + github.com/bytecodealliance/wasmtime-go/v4 => github.com/streamingfast/wasmtime-go/v4 v4.0.0-freemem3 + github.com/jhump/protoreflect => github.com/streamingfast/protoreflect v0.0.0-20231205191344-4b629d20ce8d + github.com/streamingfast/substreams => ../substreams +) diff --git a/firehose/firehose-core/go.sum b/firehose/firehose-core/go.sum new file mode 100644 index 0000000..b3f9eaf --- /dev/null +++ b/firehose/firehose-core/go.sum @@ -0,0 +1,1225 @@ +buf.build/gen/go/bufbuild/reflect/connectrpc/go v1.12.0-20230822193137-310c9c4845dd.1 h1:xnX/gxrGjg0FKB/YLHwCLRPdGoVOblm3s9MZa1oNFIw= +buf.build/gen/go/bufbuild/reflect/connectrpc/go v1.12.0-20230822193137-310c9c4845dd.1/go.mod h1:ru4ObfnijLo+YjfhJFd5Xjljz+d8M+QD+ZZLn4zz6lw= +buf.build/gen/go/bufbuild/reflect/protocolbuffers/go v1.31.0-20230822193137-310c9c4845dd.2 h1:RF8bm8mWobc2HVWCrr5PUlCQcpfsrzL/dcydKLmVC7Y= +buf.build/gen/go/bufbuild/reflect/protocolbuffers/go v1.31.0-20230822193137-310c9c4845dd.2/go.mod h1:3JED1QGgFgqC45IIPkydCq6dIcQKfG6/Ghf0RfKr2Ok= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM= +cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= +cloud.google.com/go/compute v1.24.0/go.mod h1:kw1/T+h/+tK2LJK0wiPPx1intgdAM3j/g3hFDlscY40= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= +cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= +cloud.google.com/go/logging v1.9.0/go.mod h1:1Io0vnZv4onoUnsVUQY3HZ3Igb1nBchky0A0y7BBBhE= +cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4= +cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= +cloud.google.com/go/monitoring v1.18.0/go.mod h1:c92vVBCeq/OB4Ioyo+NbN2U7tlg5ZH41PZcdvfc+Lcg= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= +cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= +cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= +cloud.google.com/go/trace v1.10.5/go.mod h1:9hjCV1nGBCtXbAE4YK7OqJ8pmPYSxPA0I67JwRd5s3M= +connectrpc.com/connect v1.15.0 h1:lFdeCbZrVVDydAqwr4xGV2y+ULn+0Z73s5JBj2LikWo= +connectrpc.com/connect v1.15.0/go.mod h1:bQmjpDY8xItMnttnurVgOkHUBMRT9cpsNi2O4AjKhmA= +connectrpc.com/grpchealth v1.3.0 h1:FA3OIwAvuMokQIXQrY5LbIy8IenftksTP/lG4PbYN+E= +connectrpc.com/grpchealth v1.3.0/go.mod h1:3vpqmX25/ir0gVgW6RdnCPPZRcR6HvqtXX5RNPmDXHM= +connectrpc.com/grpcreflect v1.2.0 h1:Q6og1S7HinmtbEuBvARLNwYmTbhEGRpHDhqrPNlmK+U= +connectrpc.com/grpcreflect v1.2.0/go.mod h1:nwSOKmE8nU5u/CidgHtPYk1PFI3U9ignz7iDMxOYkSY= +connectrpc.com/otelconnect v0.7.0 h1:ZH55ZZtcJOTKWWLy3qmL4Pam4RzRWBJFOqTPyAqCXkY= +connectrpc.com/otelconnect v0.7.0/go.mod h1:Bt2ivBymHZHqxvo4HkJ0EwHuUzQN6k2l0oH+mp/8nwc= +contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= +contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys= +contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8= +contrib.go.opencensus.io/exporter/zipkin v0.1.1 h1:PR+1zWqY8ceXs1qDQQIlgXe+sdiwCf0n32bH4+Epk8g= +contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= +github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= +github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.32.3 h1:fiyErF/p5fz79DvMCca9ayvYiWYsFP1oJbskt9fjo8I= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.32.3/go.mod h1:s7Gpwj0tk7XnVCm4BQEmx/mbS36SuTCY/vMB2SNxe8o= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.15.0 h1:5uR5WqunMUqN5Z+USN/N25aM7zWd0JUCIfz1B/w0HtA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.15.0/go.mod h1:LTScD9l1w6+z1IB3FKtXxS4oenRlIJQQrIiV/Iq1Bsw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.39.0 h1:RDD62LpQbuv4rpLOm0w1zlLIcIo7k+zi3EZV5nVyAo8= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.39.0/go.mod h1:PV+bUv9S+/W9PmZECvnC39uIEYnDL9veytwZrMqPexc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.39.0 h1:uZvy89rOd+9ryIir65RO7BmKYxQ9uBbFcnNcslu6RIM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.39.0/go.mod h1:lz6DEePTxmjvYMtusOoS3qDAErC0STi/wmvqJucKY28= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.0.0-20221018185641-36f91511cfd7 h1:4cXY9jZO7UoRYKyD+CssnBlwn2HTeUzCQ1b44PJijzc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.0.0-20221018185641-36f91511cfd7/go.mod h1:FwtSi1M0P8cuMlHxVso1vcivukprUr1bBwf15CRypOI= +github.com/KimMachineGun/automemlimit v0.2.4 h1:GBty8TK8k0aJer1Pq5/3Vdt2ef+YpLhcqNo+PSD5CoI= +github.com/KimMachineGun/automemlimit v0.2.4/go.mod h1:38QAnnnNhnFuAIW3+aPlaVUHqzE9buJYZK3m/jsra8E= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/RoaringBitmap/roaring v1.9.1 h1:LXcSqGGGMKm+KAzUyWn7ZeREqoOkoMX+KwLOK1thc4I= +github.com/RoaringBitmap/roaring v1.9.1/go.mod h1:6AXUsoIEzDTFFQCe1RbGA6uFONMhvejWj5rqITANK90= +github.com/ShinyTrinkets/meta-logger v0.2.0 h1:oR533+wuhSJ+vLsnSq1CBSGQygNv8nDsvuRUVcOls0g= +github.com/ShinyTrinkets/meta-logger v0.2.0/go.mod h1:cY1KnpPfpLIopR+arZXHYVrVGO6AETrhi3HmRGFjU+U= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/abourget/llerrgroup v0.2.0 h1:2nPXy6Owo/KOKDQYvjMmS8rsjtitvuP2OEGrqgpj428= +github.com/abourget/llerrgroup v0.2.0/go.mod h1:QukSa1Sim/0R4aRlWdiBdAy+0i1PBfOd1WHpfYM1ngA= +github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.44.325 h1:jF/L99fJSq/BfiLmUOflO/aM+LwcqBm0Fe/qTK5xxuI= +github.com/aws/aws-sdk-go v1.44.325/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/azer/is-terminal v1.0.0 h1:COvj8jmg2xMz0CqHn4Uu8X1m7Dmzmu0CpciBaLtJQBg= +github.com/azer/is-terminal v1.0.0/go.mod h1:5geuIpRQvdv6g/Q1MwXHbmNUlFLg8QcheGk4dZOmxQU= +github.com/azer/logger v1.0.0 h1:3T4BnTLyndJWHajOyECt2kAhnvP30KCrVAkYcMjHrXk= +github.com/azer/logger v1.0.0/go.mod h1:iaDID7UeBTyUh31bjGFlLkr87k23z/mHMMLzt6YQQHU= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.12.0 h1:U/q1fAF7xXRhFCrhROzIfffYnu+dlS38vCZtmFVPHmA= +github.com/bits-and-blooms/bitset v1.12.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= +github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d h1:fSlGu5ePbkjBidXuj2O5j9EcYrVB5Cr6/wdkYyDgxZk= +github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d/go.mod h1:yCBkgASmKHgUOFjK9h1sOytUVgA+JkQjqj3xYP4AdWY= +github.com/bobg/go-generics/v2 v2.1.1 h1:4rN9upY6Xm4TASSMeH+NzUghgO4h/SbNrQphIjRd/R0= +github.com/bobg/go-generics/v2 v2.1.1/go.mod h1:iPMSRVFlzkJSYOCXQ0n92RA3Vxw0RBv2E8j9ZODXgHk= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.4.0 h1:QlHdikaxALkqWasW8hAC1mfR0jdmvbfaBdBPFmRSglA= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= +github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= +github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= +github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI= +github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d h1:zqfo2jECgX5eYQseB/X+uV4Y5ocGOG/vG/LTztUCyPA= +github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= +github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= +github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec h1:fR20TYVVwhK4O7r7y+McjRYyaTH6/vjwJOajE+XhlzM= +github.com/google/pprof v0.0.0-20221203041831-ce31453925ec/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= +github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs= +github.com/ipfs/boxo v0.8.0/go.mod h1:RIsi4CnTyQ7AUsNn5gXljJYZlQrHBMnJp94p73liFiA= +github.com/ipfs/go-cid v0.4.0 h1:a4pdZq0sx6ZSxbCizebnKiMCx/xI/aBBFlB73IgH4rA= +github.com/ipfs/go-cid v0.4.0/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= +github.com/ipfs/go-ipfs-api v0.6.0 h1:JARgG0VTbjyVhO5ZfesnbXv9wTcMvoKRBLF1SzJqzmg= +github.com/ipfs/go-ipfs-api v0.6.0/go.mod h1:iDC2VMwN9LUpQV/GzEeZ2zNqd8NUdRmWcFM+K/6odf0= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josephburnett/jd v1.7.1 h1:oXBPMS+SNnILTMGj1fWLK9pexpeJUXtbVFfRku/PjBU= +github.com/josephburnett/jd v1.7.1/go.mod h1:R8ZnZnLt2D4rhW4NvBc/USTo6mzyNT6fYNIIWOJA9GY= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= +github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= +github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= +github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= +github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= +github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mostynb/go-grpc-compression v1.1.17 h1:N9t6taOJN3mNTTi0wDf4e3lp/G/ON1TP67Pn0vTUA9I= +github.com/mostynb/go-grpc-compression v1.1.17/go.mod h1:FUSBr0QjKqQgoDG/e0yiqlR6aqyXC39+g/hFLDfSsEY= +github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= +github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= +github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= +github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= +github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= +github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= +github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= +github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= +github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= +github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= +github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= +github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= +github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= +github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= +github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= +github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= +github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= +github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= +github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= +github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA= +github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY= +github.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs= +github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= +github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= +github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= +github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= +github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= +github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk= +github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rs/cors v1.10.0 h1:62NOS1h+r8p1mW6FM0FSB0exioXLhd/sh15KpjWBZ+8= +github.com/rs/cors v1.10.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aeprPTHb6yY= +github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= +github.com/sethvargo/go-retry v0.2.3 h1:oYlgvIvsju3jNbottWABtbnoLC+GDtLdBHxKWxQm/iU= +github.com/sethvargo/go-retry v0.2.3/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= +github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= +github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= +github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streamingfast/bstream v0.0.2-0.20240228193450-5200ecab8050 h1:S+1qRKKco3h+7q1voMHPDvX7gewJebs0ZG7aS0XKvZk= +github.com/streamingfast/bstream v0.0.2-0.20240228193450-5200ecab8050/go.mod h1:08GVb+DXyz6jVNIsbf+2zlaC81UeEGu5o1h49KrSR3Y= +github.com/streamingfast/cli v0.0.4-0.20230825151644-8cc84512cd80 h1:UxJUTcEVkdZy8N77E3exz0iNlgQuxl4m220GPvzdZ2s= +github.com/streamingfast/cli v0.0.4-0.20230825151644-8cc84512cd80/go.mod h1:QxjVH73Lkqk+mP8bndvhMuQDUINfkgsYhdCH/5TJFKI= +github.com/streamingfast/dauth v0.0.0-20240222213226-519afc16cf84 h1:yCvuNcwQ21J4Ua6YrAmHDBx3bjK04y+ssEYBe65BXRU= +github.com/streamingfast/dauth v0.0.0-20240222213226-519afc16cf84/go.mod h1:cwfI5vaMd+CiwZIL0H0JdP5UDWCZOVFz/ex3L0+o/j4= +github.com/streamingfast/dbin v0.9.1-0.20231117225723-59790c798e2c h1:6WjE2yInE+5jnI7cmCcxOiGZiEs2FQm9Zsg2a9Ivp0Q= +github.com/streamingfast/dbin v0.9.1-0.20231117225723-59790c798e2c/go.mod h1:dbfiy9ORrL8c6ldSq+L0H9pg8TOqqu/FsghsgUEWK54= +github.com/streamingfast/derr v0.0.0-20230515163924-8570aaa43fe1 h1:xJB7rXnOHLesosMjfwWsEL2i/40mFSkzenEb3M0qTyM= +github.com/streamingfast/derr v0.0.0-20230515163924-8570aaa43fe1/go.mod h1:QSm/AfaDsE0k1xBYi0lW580YJ/WDV/FKZI628tkZR0Y= +github.com/streamingfast/dgrpc v0.0.0-20240222213940-b9f324ff4d5c h1:hn5ZPKGtgscYTjz4J+mDEpdWyhnid9B12zqrl+vIRYM= +github.com/streamingfast/dgrpc v0.0.0-20240222213940-b9f324ff4d5c/go.mod h1:EPtUX/vhRphE37Zo6sDcgD/S3sm5YqXHhxAgzS6Ebwo= +github.com/streamingfast/dhammer v0.0.0-20230125192823-c34bbd561bd4 h1:HKi8AIkLBzxZWmbCRUo1RxoOLK33iXO6gZprfsE9rf4= +github.com/streamingfast/dhammer v0.0.0-20230125192823-c34bbd561bd4/go.mod h1:ehPytv7E4rI65iLcrwTes4rNGGqPPiugnH+20nDQyp4= +github.com/streamingfast/dmetering v0.0.0-20240403142935-dc8bb3bb32c3 h1:u3C2jzTc7d58PvVjlZew4HmZ1g1xr9yWBd8eWjmQNig= +github.com/streamingfast/dmetering v0.0.0-20240403142935-dc8bb3bb32c3/go.mod h1:UqWuX3REU/IInBUaymFN2eLjuvz+/0SsoUFjeQlLNyI= +github.com/streamingfast/dmetrics v0.0.0-20230919161904-206fa8ebd545 h1:SUl04bZKGAv207lp7/6CHOJIRpjUKunwItrno3K463Y= +github.com/streamingfast/dmetrics v0.0.0-20230919161904-206fa8ebd545/go.mod h1:JbxEDbzWRG1dHdNIPrYfuPllEkktZMgm40AwVIBENcw= +github.com/streamingfast/dstore v0.1.1-0.20240325191553-bcce8892a9bb h1:tmu8wGiSTzdqk2CnPnI7GywKwepGieqNOQDRKKSiVJg= +github.com/streamingfast/dstore v0.1.1-0.20240325191553-bcce8892a9bb/go.mod h1:kNzxgv2MzYFn2T4kelBVpGp/yP/1njtr3+csWuqxK3w= +github.com/streamingfast/dtracing v0.0.0-20220305214756-b5c0e8699839 h1:K6mJPvh1jAL+/gBS7Bh9jyzWaTib6N47m06gZOTUPwQ= +github.com/streamingfast/dtracing v0.0.0-20220305214756-b5c0e8699839/go.mod h1:huOJyjMYS6K8upTuxDxaNd+emD65RrXoVBvh8f1/7Ns= +github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0 h1:g8eEYbFSykyzIyuxNMmHEUGGUvJE0ivmqZagLDK42gw= +github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0/go.mod h1:cTNObq2Uofb330y05JbbZZ6RwE6QUXw5iVcHk1Fx3fk= +github.com/streamingfast/logging v0.0.0-20210811175431-f3b44b61606a/go.mod h1:4GdqELhZOXj4xwc4IaBmzofzdErGynnaSzuzxy0ZIBo= +github.com/streamingfast/logging v0.0.0-20220304183711-ddba33d79e27/go.mod h1:4GdqELhZOXj4xwc4IaBmzofzdErGynnaSzuzxy0ZIBo= +github.com/streamingfast/logging v0.0.0-20220304214715-bc750a74b424/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= +github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo= +github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= +github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308 h1:xlWSfi1BoPfsHtPb0VEHGUcAdBF208LUiFCwfaVPfLA= +github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308/go.mod h1:K1p8Bj/wG34KJvYzPUqtzpndffmpkrVY11u2hkyxCWQ= +github.com/streamingfast/overseer v0.2.1-0.20210326144022-ee491780e3ef h1:9IVFHRsqvI+vKJwgF1OMV6L55jHbaV/ZLoU4IAG/dME= +github.com/streamingfast/overseer v0.2.1-0.20210326144022-ee491780e3ef/go.mod h1:cq8CvbZ3ioFmGrHokSAJalS0lC+pVXLKhITScItUGXY= +github.com/streamingfast/pbgo v0.0.6-0.20240131193313-6b88bc7139db h1:c39xMBgmHgbx1e+cP8KJZ2ziWh9VsjY5C0vDZiytYtw= +github.com/streamingfast/pbgo v0.0.6-0.20240131193313-6b88bc7139db/go.mod h1:eDQjKBYg9BWE2BTaV3UZeLZ5xw05+ywA9RCFTmM1w5Y= +github.com/streamingfast/protoreflect v0.0.0-20231205191344-4b629d20ce8d h1:33VIARqUqBUKXJcuQoOS1rVSms54tgxhhNCmrLptpLg= +github.com/streamingfast/protoreflect v0.0.0-20231205191344-4b629d20ce8d/go.mod h1:aBJivEdekmFWYSQ29EE/fN9IanJWJXbtjy3ky0XD/jE= +github.com/streamingfast/sf-tracing v0.0.0-20240209202324-9daa52c71a52 h1:D9M3b2mTrvnvjGpFFd/JqZ/GSPoUrWU2zrtRpDOyqao= +github.com/streamingfast/sf-tracing v0.0.0-20240209202324-9daa52c71a52/go.mod h1:VRhdIrTjQSsc9cryNR18HTS32rgrHxQYwmoUOSEhpFA= +github.com/streamingfast/shutter v1.5.0 h1:NpzDYzj0HVpSiDJVO/FFSL6QIK/YKOxY0gJAtyaTOgs= +github.com/streamingfast/shutter v1.5.0/go.mod h1:B/T6efqdeMGbGwjzPS1ToXzYZI4kDzI5/u4I+7qbjY8= +github.com/streamingfast/snapshotter v0.0.0-20230316190750-5bcadfde44d0 h1:Y15G1Z4fpEdm2b+/70owI7TLuXadlqBtGM7rk4Hxrzk= +github.com/streamingfast/snapshotter v0.0.0-20230316190750-5bcadfde44d0/go.mod h1:/Rnz2TJvaShjUct0scZ9kKV2Jr9/+KBAoWy4UMYxgv4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= +github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= +github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= +github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= +github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= +github.com/tetratelabs/wazero v1.7.0 h1:jg5qPydno59wqjpGrHph81lbtHzTrWzwwtD4cD88+hQ= +github.com/tetratelabs/wazero v1.7.0/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= +github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= +github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c h1:GGsyl0dZ2jJgVT+VvWBf/cNijrHRhkrTjkmp5wg7li0= +github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8= +github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/detectors/gcp v1.9.0 h1:en6EnI47A3nrVtKCIgwFS5SUAhYW8LHn4Rkmm6HGhzg= +go.opentelemetry.io/contrib/detectors/gcp v1.9.0/go.mod h1:OqG0FEnmWeJWYVrEovaHXHXY4bVTnp/WfTzhwrsGWlw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw= +go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= +go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1 h1:o8iWeVFa1BcLtVEV0LzrCxV2/55tB3xLxADr6Kyoey4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.23.1/go.mod h1:SEVfdK4IoBnbT2FXNM/k8yC08MrfbhWk3U4ljM8B3HE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1 h1:p3A5+f5l9e/kuEBwLOrnpkIDHQFlHmbiVxMURWRK6gQ= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.23.1/go.mod h1:OClrnXUjBqQbInvjJFjYSnMxBSCXBF8r3b34WqjiIrQ= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.23.1 h1:IqmsDcJnxQSs6W+1TMSqpYO7VY4ZuEKJGYlSBPUlT1s= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.23.1/go.mod h1:VMZ84RYOd4Lrp0+09mckDvqBj2PXWDwOFaxb1P5uO8g= +go.opentelemetry.io/otel/exporters/zipkin v1.23.1 h1:goka4KdsPPpHHQnzp1/XE1wVpk2oQO9RXCOH4MZWSyg= +go.opentelemetry.io/otel/exporters/zipkin v1.23.1/go.mod h1:KXTI1fJdTqRrQlIYgdmF4MnyAbHFWg1z320eOpL53qA= +go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= +go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.opentelemetry.io/otel/sdk v1.23.1 h1:O7JmZw0h76if63LQdsBMKQDWNb5oEcOThG9IrxscV+E= +go.opentelemetry.io/otel/sdk v1.23.1/go.mod h1:LzdEVR5am1uKOOwfBWFef2DCi1nu3SA8XQxx2IerWFk= +go.opentelemetry.io/otel/sdk/metric v1.19.0 h1:EJoTO5qysMsYCa+w4UghwFV/ptQgqSL/8Ni+hx+8i1k= +go.opentelemetry.io/otel/sdk/metric v1.19.0/go.mod h1:XjG0jQyFJrv2PbMvwND7LwCEhsJzCzV5210euduKcKY= +go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= +go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI= +go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= +go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= +go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI= +golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.172.0 h1:/1OcMZGPmW1rX2LCu2CmGUD1KXK1+pfzxotxyRUCCdk= +google.golang.org/api v0.172.0/go.mod h1:+fJZq6QXWfa9pXhnIzsjx4yI22d4aI9ZpLb58gvXjis= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ= +google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= +google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/olivere/elastic.v3 v3.0.75 h1:u3B8p1VlHF3yNLVOlhIWFT3F1ICcHfM5V6FFJe6pPSo= +gopkg.in/olivere/elastic.v3 v3.0.75/go.mod h1:yDEuSnrM51Pc8dM5ov7U8aI/ToR3PG0llA8aRv2qmw0= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= +k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= +k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= +k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= +k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= +k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= +lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/firehose/firehose-core/index-builder/app/index-builder/app.go b/firehose/firehose-core/index-builder/app/index-builder/app.go new file mode 100644 index 0000000..dbfd634 --- /dev/null +++ b/firehose/firehose-core/index-builder/app/index-builder/app.go @@ -0,0 +1,97 @@ +package index_builder + +import ( + "context" + "fmt" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dgrpc" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/dstore" + index_builder "github.com/streamingfast/firehose-core/index-builder" + "github.com/streamingfast/firehose-core/index-builder/metrics" + "github.com/streamingfast/shutter" + "go.uber.org/zap" + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +type Config struct { + BlockHandler bstream.Handler + StartBlockResolver func(ctx context.Context) (uint64, error) + EndBlock uint64 + MergedBlocksStoreURL string + ForkedBlocksStoreURL string + GRPCListenAddr string +} + +type App struct { + *shutter.Shutter + config *Config + readinessProbe pbhealth.HealthClient +} + +func New(config *Config) *App { + return &App{ + Shutter: shutter.New(), + config: config, + } +} + +func (a *App) Run() error { + blockStore, err := dstore.NewDBinStore(a.config.MergedBlocksStoreURL) + if err != nil { + return err + } + + ctx, cancel := context.WithCancel(context.Background()) + a.OnTerminating(func(error) { + cancel() + }) + + startBlock, err := a.config.StartBlockResolver(ctx) + if err != nil { + return fmt.Errorf("resolve start block: %w", err) + } + + indexBuilder := index_builder.NewIndexBuilder( + zlog, + a.config.BlockHandler, + startBlock, + a.config.EndBlock, + blockStore, + ) + + gs, err := dgrpc.NewInternalClient(a.config.GRPCListenAddr) + if err != nil { + return fmt.Errorf("cannot create readiness probe") + } + a.readinessProbe = pbhealth.NewHealthClient(gs) + + dmetrics.Register(metrics.MetricSet) + + a.OnTerminating(indexBuilder.Shutdown) + indexBuilder.OnTerminated(a.Shutdown) + + go indexBuilder.Launch() + + zlog.Info("index builder running") + return nil +} + +func (a *App) IsReady() bool { + if a.readinessProbe == nil { + return false + } + + resp, err := a.readinessProbe.Check(context.Background(), &pbhealth.HealthCheckRequest{}) + if err != nil { + zlog.Info("index-builder readiness probe error", zap.Error(err)) + return false + } + + if resp.Status == pbhealth.HealthCheckResponse_SERVING { + return true + } + + return false +} diff --git a/firehose/firehose-core/index-builder/app/index-builder/logging.go b/firehose/firehose-core/index-builder/app/index-builder/logging.go new file mode 100644 index 0000000..d24e498 --- /dev/null +++ b/firehose/firehose-core/index-builder/app/index-builder/logging.go @@ -0,0 +1,7 @@ +package index_builder + +import ( + "github.com/streamingfast/logging" +) + +var zlog, tracer = logging.PackageLogger("index-builder", "github.com/streamingfast/firehose-core/index-builder/app/index-builder") diff --git a/firehose/firehose-core/index-builder/healthz.go b/firehose/firehose-core/index-builder/healthz.go new file mode 100644 index 0000000..63b69e0 --- /dev/null +++ b/firehose/firehose-core/index-builder/healthz.go @@ -0,0 +1,42 @@ +package index_builder + +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import ( + "context" + + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +// Check is basic GRPC Healthcheck +func (app *IndexBuilder) Check(ctx context.Context, in *pbhealth.HealthCheckRequest) (*pbhealth.HealthCheckResponse, error) { + status := pbhealth.HealthCheckResponse_SERVING + return &pbhealth.HealthCheckResponse{ + Status: status, + }, nil +} + +// Watch is basic GRPC Healthcheck as a stream +func (app *IndexBuilder) Watch(req *pbhealth.HealthCheckRequest, stream pbhealth.Health_WatchServer) error { + err := stream.Send(&pbhealth.HealthCheckResponse{ + Status: pbhealth.HealthCheckResponse_SERVING, + }) + if err != nil { + return err + } + + <-stream.Context().Done() + return nil +} diff --git a/firehose/firehose-core/index-builder/index-builder.go b/firehose/firehose-core/index-builder/index-builder.go new file mode 100644 index 0000000..0a11b6a --- /dev/null +++ b/firehose/firehose-core/index-builder/index-builder.go @@ -0,0 +1,96 @@ +package index_builder + +import ( + "context" + "errors" + "fmt" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + firecore "github.com/streamingfast/firehose-core" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/stream" + "github.com/streamingfast/dstore" + "github.com/streamingfast/firehose-core/index-builder/metrics" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "github.com/streamingfast/shutter" + "go.uber.org/zap" +) + +type IndexBuilder struct { + *shutter.Shutter + logger *zap.Logger + + startBlockNum uint64 + stopBlockNum uint64 + + handler bstream.Handler + + blocksStore dstore.Store +} + +func NewIndexBuilder(logger *zap.Logger, handler bstream.Handler, startBlockNum, stopBlockNum uint64, blockStore dstore.Store) *IndexBuilder { + return &IndexBuilder{ + Shutter: shutter.New(), + startBlockNum: startBlockNum, + stopBlockNum: stopBlockNum, + handler: handler, + blocksStore: blockStore, + + logger: logger, + } +} + +func (app *IndexBuilder) Launch() { + err := app.launch() + if errors.Is(err, stream.ErrStopBlockReached) { + app.logger.Info("index builder reached stop block", zap.Uint64("stop_block_num", app.stopBlockNum)) + err = nil + } + app.logger.Info("index builder exited", zap.Error(err)) + app.Shutdown(err) +} + +func (app *IndexBuilder) launch() error { + startBlockNum := app.startBlockNum + stopBlockNum := app.stopBlockNum + + streamFactory := firecore.NewStreamFactory( + app.blocksStore, + nil, + nil, + nil, + ) + ctx := context.Background() + + req := &pbfirehose.Request{ + StartBlockNum: int64(startBlockNum), + StopBlockNum: stopBlockNum, + FinalBlocksOnly: true, + } + + handlerFunc := func(block *pbbstream.Block, obj interface{}) error { + app.logger.Debug("handling block", zap.Uint64("block_num", block.Number)) + + metrics.HeadBlockNumber.SetUint64(block.Number) + metrics.HeadBlockTimeDrift.SetBlockTime(block.Time()) + metrics.AppReadiness.SetReady() + + app.logger.Debug("updated head block metrics", zap.Uint64("block_num", block.Number), zap.Time("block_time", block.Time())) + + return app.handler.ProcessBlock(block, obj) + } + + stream, err := streamFactory.New( + ctx, + bstream.HandlerFunc(handlerFunc), + req, + app.logger, + ) + + if err != nil { + return fmt.Errorf("getting firehose stream: %w", err) + } + + return stream.Run(ctx) +} diff --git a/firehose/firehose-core/index-builder/metrics/metrics.go b/firehose/firehose-core/index-builder/metrics/metrics.go new file mode 100644 index 0000000..d691c4d --- /dev/null +++ b/firehose/firehose-core/index-builder/metrics/metrics.go @@ -0,0 +1,9 @@ +package metrics + +import "github.com/streamingfast/dmetrics" + +var MetricSet = dmetrics.NewSet() + +var HeadBlockTimeDrift = MetricSet.NewHeadTimeDrift("block-indexer") +var HeadBlockNumber = MetricSet.NewHeadBlockNumber("block-indexer") +var AppReadiness = MetricSet.NewAppReadiness("block-indexer") diff --git a/firehose/firehose-core/internal/utils/utils.go b/firehose/firehose-core/internal/utils/utils.go new file mode 100644 index 0000000..c0416f1 --- /dev/null +++ b/firehose/firehose-core/internal/utils/utils.go @@ -0,0 +1,30 @@ +package utils + +import ( + "os" + "strconv" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" +) + +func GetEnvForceFinalityAfterBlocks() *uint64 { + if fin := os.Getenv("FORCE_FINALITY_AFTER_BLOCKS"); fin != "" { + if fin64, err := strconv.ParseInt(fin, 10, 64); err == nil { + finu64 := uint64(fin64) + return &finu64 + } + } + return nil +} + +func TweakBlockFinality(blk *pbbstream.Block, maxDistanceToBlock uint64) { + if blk.LibNum > blk.Number { + panic("libnum cannot be greater than block number") + } + if blk.Number < maxDistanceToBlock { + return // prevent uin64 underflow at the beginning of the chain + } + if (blk.Number - blk.LibNum) >= maxDistanceToBlock { + blk.LibNum = blk.Number - maxDistanceToBlock // force finality + } +} diff --git a/firehose/firehose-core/internal/utils/utils_test.go b/firehose/firehose-core/internal/utils/utils_test.go new file mode 100644 index 0000000..bc10a68 --- /dev/null +++ b/firehose/firehose-core/internal/utils/utils_test.go @@ -0,0 +1,76 @@ +package utils + +import ( + "fmt" + "os" + "strconv" + "testing" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/stretchr/testify/assert" +) + +func TestGetEnvForceFinalityAfterBlocks(t *testing.T) { + // Set up test case + expected := uint64(10) + os.Setenv("FORCE_FINALITY_AFTER_BLOCKS", strconv.FormatUint(expected, 10)) + defer os.Unsetenv("FORCE_FINALITY_AFTER_BLOCKS") + + // Call the function + result := GetEnvForceFinalityAfterBlocks() + + // Check the result + if result == nil { + t.Errorf("Expected non-nil result, got nil") + } else if *result != expected { + t.Errorf("Expected %d, got %d", expected, *result) + } +} +func TestTweakBlockFinality(t *testing.T) { + // Define test cases + testCases := []struct { + blk *pbbstream.Block + maxDistanceToBlock uint64 + expectedLibNum uint64 + }{ + { + blk: &pbbstream.Block{ + Number: 100, + LibNum: 80, + }, + maxDistanceToBlock: 10, + expectedLibNum: 90, + }, + { + blk: &pbbstream.Block{ + Number: 100, + LibNum: 80, + }, + maxDistanceToBlock: 200, + expectedLibNum: 80, + }, + { + blk: &pbbstream.Block{ + Number: 100, + LibNum: 0, + }, + maxDistanceToBlock: 200, + expectedLibNum: 0, + }, + { + blk: &pbbstream.Block{ + Number: 100, + LibNum: 0, + }, + maxDistanceToBlock: 10, + expectedLibNum: 90, + }, + } + + for i, tc := range testCases { + t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { + TweakBlockFinality(tc.blk, tc.maxDistanceToBlock) + assert.Equal(t, tc.expectedLibNum, tc.blk.LibNum) + }) + } +} diff --git a/firehose/firehose-core/json/marshallers.go b/firehose/firehose-core/json/marshallers.go new file mode 100644 index 0000000..daaf399 --- /dev/null +++ b/firehose/firehose-core/json/marshallers.go @@ -0,0 +1,194 @@ +package json + +import ( + "bytes" + "encoding/hex" + "fmt" + "os" + + "slices" + + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" + "github.com/mr-tron/base58" + "github.com/streamingfast/firehose-core/proto" + "google.golang.org/protobuf/encoding/protowire" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/dynamicpb" + "google.golang.org/protobuf/types/known/anypb" +) + +type kvList []*kv +type kv struct { + key string + value any +} + +type Marshaller struct { + marshallers *json.Marshalers + includeUnknownFields bool + registry *proto.Registry + bytesEncoderFunc func(encoder *jsontext.Encoder, t []byte, options json.Options) error +} + +type MarshallerOption func(*Marshaller) + +func WithoutUnknownFields() MarshallerOption { + return func(e *Marshaller) { + e.includeUnknownFields = false + } +} +func WithBytesEncoderFunc(f func(encoder *jsontext.Encoder, t []byte, options json.Options) error) MarshallerOption { + return func(e *Marshaller) { + e.bytesEncoderFunc = f + } +} + +func NewMarshaller(registry *proto.Registry, options ...MarshallerOption) *Marshaller { + m := &Marshaller{ + includeUnknownFields: true, + registry: registry, + bytesEncoderFunc: ToHex, + } + + for _, opt := range options { + opt(m) + } + + m.marshallers = json.NewMarshalers( + json.MarshalFuncV2(m.anypb), + json.MarshalFuncV2(m.dynamicpbMessage), + json.MarshalFuncV2(m.encodeKVList), + json.MarshalFuncV2(m.bytesEncoderFunc), + ) + + return m +} + +func (m *Marshaller) Marshal(in any) error { + err := json.MarshalEncode(jsontext.NewEncoder(os.Stdout), in, json.WithMarshalers(m.marshallers)) + if err != nil { + return fmt.Errorf("marshalling and encoding block to json: %w", err) + } + return nil +} + +func (m *Marshaller) MarshalToString(in any) (string, error) { + buf := bytes.NewBuffer(nil) + if err := json.MarshalEncode(jsontext.NewEncoder(buf), in, json.WithMarshalers(m.marshallers)); err != nil { + return "", err + } + return buf.String(), nil + +} + +func (m *Marshaller) anypb(encoder *jsontext.Encoder, t *anypb.Any, options json.Options) error { + msg, err := m.registry.Unmarshal(t) + if err != nil { + return fmt.Errorf("unmarshalling proto any: %w", err) + } + + cnt, err := json.Marshal(msg, json.WithMarshalers(m.marshallers)) + if err != nil { + return fmt.Errorf("json marshalling proto any: %w", err) + } + return encoder.WriteValue(cnt) +} + +func (m *Marshaller) encodeKVList(encoder *jsontext.Encoder, t kvList, options json.Options) error { + if err := encoder.WriteToken(jsontext.ObjectStart); err != nil { + return err + } + for _, kv := range t { + if err := encoder.WriteToken(jsontext.String(kv.key)); err != nil { + return err + } + + cnt, err := json.Marshal(kv.value, json.WithMarshalers(m.marshallers)) + if err != nil { + return fmt.Errorf("json marshalling of value : %w", err) + } + + if err := encoder.WriteValue(cnt); err != nil { + return err + } + } + return encoder.WriteToken(jsontext.ObjectEnd) +} + +func (m *Marshaller) dynamicpbMessage(encoder *jsontext.Encoder, msg *dynamicpb.Message, options json.Options) error { + var kvl kvList + + if m.includeUnknownFields { + x := msg.GetUnknown() + fieldNumber, ofType, l := protowire.ConsumeField(x) + if l > 0 { + var unknownValue []byte + unknownValue = x[:l] + kvl = append(kvl, &kv{ + key: fmt.Sprintf("__unknown_fields_%d_with_type_%d__", fieldNumber, ofType), + value: hex.EncodeToString(unknownValue), + }) + } + } + + msg.Range(func(fd protoreflect.FieldDescriptor, v protoreflect.Value) bool { + if fd.IsMap() { + out := map[string]interface{}{} + v.Map().Range(func(k protoreflect.MapKey, v2 protoreflect.Value) bool { + key := k.String() + out[key] = v2.Interface() + return true + }) + kvl = append(kvl, &kv{ + key: string(fd.Name()), + value: out, + }) + return true + } + + if fd.IsList() { + out := make([]any, v.List().Len()) + for i := 0; i < v.List().Len(); i++ { + out[i] = v.List().Get(i).Interface() + } + kvl = append(kvl, &kv{ + key: string(fd.Name()), + value: out, + }) + return true + } + kvl = append(kvl, &kv{ + key: string(fd.Name()), + value: v.Interface(), + }) + + return true + }) + + slices.SortFunc(kvl, func(a, b *kv) int { + if a.key < b.key { + return -1 + } + + if a.key == b.key { + return 0 + } + + return 1 + }) + + cnt, err := json.Marshal(kvl, json.WithMarshalers(m.marshallers)) + if err != nil { + return fmt.Errorf("json marshalling proto any: %w", err) + } + return encoder.WriteValue(cnt) +} + +func ToBase58(encoder *jsontext.Encoder, t []byte, options json.Options) error { + return encoder.WriteToken(jsontext.String(base58.Encode(t))) +} + +func ToHex(encoder *jsontext.Encoder, t []byte, options json.Options) error { + return encoder.WriteToken(jsontext.String(hex.EncodeToString(t))) +} diff --git a/firehose/firehose-core/launcher/app.go b/firehose/firehose-core/launcher/app.go new file mode 100644 index 0000000..c2cff51 --- /dev/null +++ b/firehose/firehose-core/launcher/app.go @@ -0,0 +1,46 @@ +package launcher + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +type AppDef struct { + ID string + Title string + Description string + RegisterFlags func(cmd *cobra.Command) error + InitFunc func(runtime *Runtime) error + FactoryFunc func(runtime *Runtime) (App, error) +} + +func (a *AppDef) String() string { + return fmt.Sprintf("%s (%s)", a.ID, a.Title) +} + +type App interface { + Terminating() <-chan struct{} + Terminated() <-chan struct{} + Shutdown(err error) + Err() error + Run() error +} + +//go:generate go-enum -f=$GOFILE --marshal --names + +// ENUM( +// +// NotFound +// Created +// Running +// Warning +// Stopped +// +// ) +type AppStatus uint + +type AppInfo struct { + ID string + Status AppStatus +} diff --git a/firehose/firehose-core/launcher/app_enum.go b/firehose/firehose-core/launcher/app_enum.go new file mode 100644 index 0000000..2a626f1 --- /dev/null +++ b/firehose/firehose-core/launcher/app_enum.go @@ -0,0 +1,90 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: +// Revision: +// Build Date: +// Built By: + +package launcher + +import ( + "fmt" + "strings" +) + +const ( + // AppStatusNotFound is a AppStatus of type NotFound. + AppStatusNotFound AppStatus = iota + // AppStatusCreated is a AppStatus of type Created. + AppStatusCreated + // AppStatusRunning is a AppStatus of type Running. + AppStatusRunning + // AppStatusWarning is a AppStatus of type Warning. + AppStatusWarning + // AppStatusStopped is a AppStatus of type Stopped. + AppStatusStopped +) + +const _AppStatusName = "NotFoundCreatedRunningWarningStopped" + +var _AppStatusNames = []string{ + _AppStatusName[0:8], + _AppStatusName[8:15], + _AppStatusName[15:22], + _AppStatusName[22:29], + _AppStatusName[29:36], +} + +// AppStatusNames returns a list of possible string values of AppStatus. +func AppStatusNames() []string { + tmp := make([]string, len(_AppStatusNames)) + copy(tmp, _AppStatusNames) + return tmp +} + +var _AppStatusMap = map[AppStatus]string{ + AppStatusNotFound: _AppStatusName[0:8], + AppStatusCreated: _AppStatusName[8:15], + AppStatusRunning: _AppStatusName[15:22], + AppStatusWarning: _AppStatusName[22:29], + AppStatusStopped: _AppStatusName[29:36], +} + +// String implements the Stringer interface. +func (x AppStatus) String() string { + if str, ok := _AppStatusMap[x]; ok { + return str + } + return fmt.Sprintf("AppStatus(%d)", x) +} + +var _AppStatusValue = map[string]AppStatus{ + _AppStatusName[0:8]: AppStatusNotFound, + _AppStatusName[8:15]: AppStatusCreated, + _AppStatusName[15:22]: AppStatusRunning, + _AppStatusName[22:29]: AppStatusWarning, + _AppStatusName[29:36]: AppStatusStopped, +} + +// ParseAppStatus attempts to convert a string to a AppStatus +func ParseAppStatus(name string) (AppStatus, error) { + if x, ok := _AppStatusValue[name]; ok { + return x, nil + } + return AppStatus(0), fmt.Errorf("%s is not a valid AppStatus, try [%s]", name, strings.Join(_AppStatusNames, ", ")) +} + +// MarshalText implements the text marshaller method +func (x AppStatus) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method +func (x *AppStatus) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseAppStatus(name) + if err != nil { + return err + } + *x = tmp + return nil +} diff --git a/firehose/firehose-core/launcher/config.go b/firehose/firehose-core/launcher/config.go new file mode 100644 index 0000000..6f7871e --- /dev/null +++ b/firehose/firehose-core/launcher/config.go @@ -0,0 +1,32 @@ +package launcher + +import ( + "fmt" + "os" + + "gopkg.in/yaml.v2" +) + +var Config map[string]*CommandConfig + +type CommandConfig struct { + Args []string `json:"args"` + Flags map[string]string `json:"flags"` +} + +// Load reads a YAML config, and sets the global DfuseConfig variable +// Use the raw JSON form to provide to the +// different plugins and apps for them to load their config. +func LoadConfigFile(filename string) (err error) { + yamlBytes, err := os.ReadFile(filename) + if err != nil { + return err + } + + err = yaml.Unmarshal(yamlBytes, &Config) + if err != nil { + return fmt.Errorf("reading json: %s", err) + } + + return nil +} diff --git a/firehose/firehose-core/launcher/init_test.go b/firehose/firehose-core/launcher/init_test.go new file mode 100644 index 0000000..90c832b --- /dev/null +++ b/firehose/firehose-core/launcher/init_test.go @@ -0,0 +1,9 @@ +package launcher + +import "github.com/streamingfast/logging" + +var zlog, _ = logging.PackageLogger("launcher", "github.com/streamingfast/firehose-core/launcher") + +func init() { + logging.InstantiateLoggers() +} diff --git a/firehose/firehose-core/launcher/launcher.go b/firehose/firehose-core/launcher/launcher.go new file mode 100644 index 0000000..4c8f086 --- /dev/null +++ b/firehose/firehose-core/launcher/launcher.go @@ -0,0 +1,319 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package launcher + +import ( + "fmt" + "runtime/debug" + "sync" + "time" + + "github.com/streamingfast/shutter" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +type Launcher struct { + shutter *shutter.Shutter + + runtime *Runtime + apps map[string]App + + appStatus map[string]AppStatus + appStatusSubscriptions []*subscription + appStatusLock sync.RWMutex + + shutdownDoOnce sync.Once + firstShutdownAppID string + hasBeenSignaled *atomic.Bool + + logger *zap.Logger +} + +func NewLauncher(logger *zap.Logger, absDataDir string) *Launcher { + l := &Launcher{ + shutter: shutter.New(), + apps: make(map[string]App), + appStatus: make(map[string]AppStatus), + hasBeenSignaled: new(atomic.Bool), + logger: logger, + } + + l.runtime = &Runtime{ + AbsDataDir: absDataDir, + IsPendingShutdown: func() bool { + return l.hasBeenSignaled.Load() + }, + } + + return l +} + +func (l *Launcher) SwitchHasBeenSignaledAtomic(other *atomic.Bool) { + l.hasBeenSignaled = other +} + +func (l *Launcher) Close() { + l.shutter.Shutdown(nil) +} + +func (l *Launcher) Launch(appNames []string) error { + if len(appNames) == 0 { + return fmt.Errorf("no apps specified") + } + // This is done first as a sanity check so we don't launch anything if something is misconfigured + for _, appID := range appNames { + appDef, found := AppRegistry[appID] + if !found { + return fmt.Errorf("cannot launch un-registered application %q", appID) + } + + if appDef.InitFunc != nil { + l.logger.Debug("initialize application", zap.String("app", appID)) + err := appDef.InitFunc(l.runtime) + if err != nil { + return fmt.Errorf("unable to initialize app %q: %w", appID, err) + } + } + } + + for _, appID := range appNames { + appDef := AppRegistry[appID] + + l.StoreAndStreamAppStatus(appID, AppStatusCreated) + l.logger.Debug("creating application", zap.String("app", appID)) + + // We wrap FactoryFunc inside a func to be able to recover from panic + app, err := func() (app App, err error) { + defer func() { + // If err is not nil, it means FactoryFunc finished, so handle possible recover only if err == nil + if err == nil { + err = recoverToError(recover()) + } + }() + + return appDef.FactoryFunc(l.runtime) + }() + + if err != nil { + return fmt.Errorf("unable to create app %q: %w", appID, err) + } + + l.shutter.OnTerminating(func(err error) { + go app.Shutdown(err) + }) + + l.apps[appDef.ID] = app + } + + for appID, app := range l.apps { + if l.shutter.IsTerminating() { + break + } + + // run + go func(appID string, app App) { + defer (func() { + // Don't be fooled, this will work only for this very goroutine and its + // execution. If the app launches other goroutines and one of those fails, this + // recovery will not be able to recover them and the whole program will panic + // without ever reaching this point here. + l.shutdownIfRecoveringFromPanic(appID, recover()) + })() + + l.logger.Debug("launching app", zap.String("app", appID)) + err := app.Run() + if err != nil { + l.shutdownDueToApp(appID, err) + } + }(appID, app) + } + + for appID, app := range l.apps { + if l.shutter.IsTerminating() { + break + } + + // watch for shutdown + go func(appID string, app App) { + select { + case <-app.Terminating(): + l.shutdownDueToApp(appID, app.Err()) + case <-l.shutter.Terminating(): + } + }(appID, app) + } + + return nil +} + +func (l *Launcher) Terminating() <-chan string { + + ch := make(chan string, 1) + + go func() { + <-l.shutter.Terminating() + ch <- l.firstShutdownAppID + }() + + return ch +} + +func (l *Launcher) Err() error { + return l.shutter.Err() +} + +// shutdownDueToApp initiates a launcher shutdown process recording the app that initially triggered +// the shutdown and calling the launcher `Shutdown` method with the error. The `err` can be `nil` +// in which case we assume a clean shutdown. Otherwise, we assume a fatal error shutdown and log +// the fatal error. +func (l *Launcher) shutdownDueToApp(appID string, err error) { + l.shutdownDoOnce.Do(func() { // pretty printing of error causing dfuse shutdown + l.firstShutdownAppID = appID + + if err != nil { + l.FatalAppError(appID, err) + } else { + l.logger.Info(fmt.Sprintf("app %s triggered clean shutdown", appID)) + } + }) + + l.StoreAndStreamAppStatus(appID, AppStatusStopped) + l.shutter.Shutdown(err) +} + +func (l *Launcher) FatalAppError(app string, err error) { + msg := fmt.Sprintf("\n################################################################\n"+ + "Fatal error in app %s:\n\n%s"+ + "\n################################################################\n", app, err) + + l.logger.Error(msg) +} + +// shutdownIfRecoveringFromPanic is called with the result of `recover()` call in a `defer` +// to handle any panic and shutdowns down the whole launcher if any recovered error was encountered. +func (l *Launcher) shutdownIfRecoveringFromPanic(appID string, recovered interface{}) (shuttindDown bool) { + if recovered == nil { + return false + } + + err := fmt.Errorf("app %q panicked", appID) + switch v := recovered.(type) { + case error: + err = fmt.Errorf("%s: %w\n%s", err.Error(), v, string(debug.Stack())) + default: + err = fmt.Errorf("%s: %s\n%s", err.Error(), v, string(debug.Stack())) + } + + l.shutdownDueToApp(appID, err) + + return true +} + +func recoverToError(recovered interface{}) (err error) { + if recovered == nil { + return nil + } + + if v, ok := recovered.(error); ok { + return v + } + + return fmt.Errorf("%s", recovered) +} + +func (l *Launcher) StoreAndStreamAppStatus(appID string, status AppStatus) { + l.appStatusLock.Lock() + defer l.appStatusLock.Unlock() + + l.appStatus[appID] = status + + appInfo := &AppInfo{ + ID: appID, + Status: status, + } + + for _, sub := range l.appStatusSubscriptions { + sub.Push(appInfo) + } +} + +func (l *Launcher) GetAppStatus(appID string) AppStatus { + l.appStatusLock.RLock() + defer l.appStatusLock.RUnlock() + + if v, found := l.appStatus[appID]; found { + return v + } + + return AppStatusNotFound +} + +func (l *Launcher) GetAppIDs() (resp []string) { + for appID := range l.apps { + resp = append(resp, string(appID)) + } + return resp +} + +func (l *Launcher) WaitForTermination() { + l.logger.Info("waiting for all apps termination...") + now := time.Now() + for appID, app := range l.apps { + innerFor: + for { + select { + case <-app.Terminated(): + l.logger.Debug("app terminated", zap.String("app_id", appID)) + break innerFor + case <-time.After(1500 * time.Millisecond): + l.logger.Info(fmt.Sprintf("still waiting for app %q ... %v", appID, time.Since(now).Round(100*time.Millisecond))) + } + } + } + l.logger.Info("all apps terminated gracefully") +} + +func (l *Launcher) SubscribeAppStatus() *subscription { + chanSize := 500 + sub := newSubscription(l.logger, chanSize) + + l.appStatusLock.Lock() + defer l.appStatusLock.Unlock() + + l.appStatusSubscriptions = append(l.appStatusSubscriptions, sub) + + l.logger.Debug("app status subscribed") + return sub +} + +func (l *Launcher) UnsubscribeAppStatus(sub *subscription) { + if sub == nil { + return + } + + l.appStatusLock.Lock() + defer l.appStatusLock.Unlock() + + var filtered []*subscription + for _, candidate := range l.appStatusSubscriptions { + // Pointer address comparison + if candidate != sub { + filtered = append(filtered, candidate) + } + } + + l.appStatusSubscriptions = filtered +} diff --git a/firehose/firehose-core/launcher/logging.go b/firehose/firehose-core/launcher/logging.go new file mode 100644 index 0000000..90bca97 --- /dev/null +++ b/firehose/firehose-core/launcher/logging.go @@ -0,0 +1,73 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package launcher + +import ( + "path/filepath" + + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +type LoggingOptions struct { + WorkingDir string // the folder where the data will be stored, in our case will be used to store the logger + Verbosity int // verbosity level + LogFormat string // specifies the log format + LogToFile bool // specifies if we should store the logs on disk + LogListenAddr string // address that listens to change the logs + LogToStderr bool // determines if the standard console logger should log to Stderr (defaults is to log in Stdout) +} + +func SetupLogger(rootLogger *zap.Logger, opts *LoggingOptions) { + options := []logging.InstantiateOption{ + logging.WithLogLevelSwitcherServerAutoStart(), + logging.WithDefaultSpec(defaultSpecForVerbosity(opts.Verbosity)...), + logging.WithConsoleToStdout(), + } + + if opts.LogToStderr { + options = append(options, logging.WithConsoleToStderr()) + } + + if opts.LogListenAddr != "" { + options = append(options, logging.WithLogLevelSwitcherServerListeningAddress(opts.LogListenAddr)) + } + + if opts.LogFormat == "stackdriver" || opts.LogFormat == "json" { + options = append(options, logging.WithProductionLogger()) + } + + if opts.LogToFile { + options = append(options, logging.WithOutputToFile(filepath.Join(opts.WorkingDir, "app.log.json"))) + } + + logging.InstantiateLoggers(options...) + + // Hijack standard Golang `log` and redirect it to our common logger + zap.RedirectStdLogAt(rootLogger, zap.DebugLevel) +} + +func defaultSpecForVerbosity(verbosity int) []string { + switch verbosity { + case 0: + return nil + + case 1: + return []string{"*=info"} + + default: + return []string{"*=debug"} + } +} diff --git a/firehose/firehose-core/launcher/readiness.go b/firehose/firehose-core/launcher/readiness.go new file mode 100644 index 0000000..1711de0 --- /dev/null +++ b/firehose/firehose-core/launcher/readiness.go @@ -0,0 +1,56 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package launcher + +import ( + "sync" + + "go.uber.org/zap" +) + +type subscription struct { + IncomingAppInfo chan *AppInfo + Closed bool + QuitOnce sync.Once + + logger *zap.Logger +} + +func newSubscription(logger *zap.Logger, chanSize int) (out *subscription) { + return &subscription{ + IncomingAppInfo: make(chan *AppInfo, chanSize), + } +} + +func (s *subscription) Push(app *AppInfo) { + if s.Closed { + return + } + + s.logger.Debug("pushing app readiness state to subscriber", + zap.Reflect("response", app), + ) + if len(s.IncomingAppInfo) == cap(s.IncomingAppInfo) { + s.QuitOnce.Do(func() { + s.logger.Debug("reach max buffer size for readiness stream, closing channel") + close(s.IncomingAppInfo) + s.Closed = true + }) + return + } + + // Clean up + s.IncomingAppInfo <- app +} diff --git a/firehose/firehose-core/launcher/registry.go b/firehose/firehose-core/launcher/registry.go new file mode 100644 index 0000000..554aab9 --- /dev/null +++ b/firehose/firehose-core/launcher/registry.go @@ -0,0 +1,81 @@ +package launcher + +import ( + "sort" + "strings" + + "github.com/spf13/cobra" + "go.uber.org/zap" +) + +var AppRegistry = map[string]*AppDef{} + +func RegisterApp(logger *zap.Logger, appDef *AppDef) { + logger.Debug("registering app", zap.Stringer("app", appDef)) + AppRegistry[appDef.ID] = appDef +} + +var RegisterCommonFlags func(logger *zap.Logger, cmd *cobra.Command) error + +func RegisterFlags(logger *zap.Logger, cmd *cobra.Command) error { + for _, appDef := range AppRegistry { + logger.Debug("trying to register flags", zap.String("app_id", appDef.ID)) + if appDef.RegisterFlags != nil { + logger.Debug("found non nil flags, registering", zap.String("app_id", appDef.ID)) + err := appDef.RegisterFlags(cmd) + if err != nil { + return err + } + } + } + + if RegisterCommonFlags != nil { + if err := RegisterCommonFlags(logger, cmd); err != nil { + return err + } + } + + return nil +} + +func ParseAppsFromArgs(args []string, runByDefault func(string) bool) (apps []string) { + if len(args) == 0 { + return ParseAppsFromArgs([]string{"all"}, runByDefault) + } + + for _, arg := range args { + chunks := strings.Split(arg, ",") + for _, app := range chunks { + app = strings.TrimSpace(app) + if app == "all" { + for app := range AppRegistry { + if !runByDefault(app) { + continue + } + apps = append(apps, app) + } + } else { + if strings.HasPrefix(app, "-") { + removeApp := app[1:] + apps = removeElement(apps, removeApp) + } else { + apps = append(apps, app) + } + } + + } + } + + sort.Strings(apps) + + return +} + +func removeElement(lst []string, el string) (out []string) { + for _, l := range lst { + if l != el { + out = append(out, l) + } + } + return +} diff --git a/firehose/firehose-core/launcher/registry_test.go b/firehose/firehose-core/launcher/registry_test.go new file mode 100644 index 0000000..0d74a8b --- /dev/null +++ b/firehose/firehose-core/launcher/registry_test.go @@ -0,0 +1,58 @@ +package launcher + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseFromArgs(t *testing.T) { + tests := []struct { + name string + input []string + expect []string + }{ + { + input: []string{"all,-app2"}, + expect: []string{"app1", "app3"}, + }, + { + input: []string{"all", " -app2 "}, + expect: []string{"app1", "app3"}, + }, + { + input: []string{"all"}, + expect: []string{"app1", "app2", "app3"}, + }, + { + input: []string{" app1", " app2"}, + expect: []string{"app1", "app2"}, + }, + { + input: []string{" app1, app2"}, + expect: []string{"app1", "app2"}, + }, + { + input: []string{"app2", "appnodefault"}, + expect: []string{"app2", "appnodefault"}, + }, + } + + RegisterApp(zlog, &AppDef{ID: "app1"}) + RegisterApp(zlog, &AppDef{ID: "app2"}) + RegisterApp(zlog, &AppDef{ID: "app3"}) + RegisterApp(zlog, &AppDef{ID: "appnodefault"}) + + for idx, test := range tests { + t.Run(fmt.Sprintf("%d", idx), func(t *testing.T) { + res := ParseAppsFromArgs(test.input, func(app string) bool { + if app == "appnodefault" { + return false + } + return true + }) + assert.Equal(t, test.expect, res) + }) + } +} diff --git a/firehose/firehose-core/launcher/runtime.go b/firehose/firehose-core/launcher/runtime.go new file mode 100644 index 0000000..a5df1b3 --- /dev/null +++ b/firehose/firehose-core/launcher/runtime.go @@ -0,0 +1,10 @@ +package launcher + +type Runtime struct { + AbsDataDir string + + // IsPendingShutdown is a function that is going to return true as soon as the initial SIGINT signal is + // received which can be used to turn a healthz monitor as unhealthy so that a load balancer can + // remove the node from the pool and has 'common-system-shutdown-signal-delay' to do it. + IsPendingShutdown func() bool +} diff --git a/firehose/firehose-core/launcher/setup.go b/firehose/firehose-core/launcher/setup.go new file mode 100644 index 0000000..86be009 --- /dev/null +++ b/firehose/firehose-core/launcher/setup.go @@ -0,0 +1,135 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package launcher + +import ( + "fmt" + "net/http" + _ "net/http/pprof" + "syscall" + + "github.com/KimMachineGun/automemlimit/memlimit" + "go.uber.org/automaxprocs/maxprocs" + + "github.com/streamingfast/dmetrics" + "go.uber.org/zap" +) + +func SetupAnalyticsMetrics(logger *zap.Logger, metricsListenAddr string, pprofListenAddr string) { + if metricsListenAddr != "" { + go dmetrics.Serve(metricsListenAddr) + } + + if err := SetMaxOpenFilesLimit(logger, goodEnoughMaxOpenFilesLimit, osxStockMaxOpenFilesLimit); err != nil { + logger.Warn("unable to adjust ulimit max open files value, it might causes problem along the road", zap.Error(err)) + } + + if pprofListenAddr != "" { + go func() { + err := http.ListenAndServe(pprofListenAddr, nil) + if err != nil { + logger.Debug("unable to start profiling server", zap.Error(err), zap.String("listen_addr", pprofListenAddr)) + } + }() + } +} + +const goodEnoughMaxOpenFilesLimit uint64 = 1000000 +const osxStockMaxOpenFilesLimit uint64 = 24576 + +func SetAutoMemoryLimit(limit uint64, logger *zap.Logger) error { + if limit != 0 { + if limit > 100 { + return fmt.Errorf("cannot set common-auto-mem-limit-percent above 100") + } + logger.Info("setting GOMEMLIMIT relative to available memory", zap.Uint64("percent", limit)) + memlimit.SetGoMemLimit(float64(limit) / 100) + } + return nil +} + +func SetAutoMaxProcs(logger *zap.Logger) { + logger.Info("aligning GO max procs to available CPU threads according to cgroup limits") + maxprocs.Set() +} + +func SetMaxOpenFilesLimit(logger *zap.Logger, goodEnoughMaxOpenFiles, osxStockMaxOpenFiles uint64) error { + maxOpenFilesLimit, err := getMaxOpenFilesLimit() + if err != nil { + return err + } + + logger.Debug("ulimit max open files before adjustment", zap.Uint64("current_value", maxOpenFilesLimit)) + if maxOpenFilesLimit >= goodEnoughMaxOpenFiles { + logger.Debug("no need to update ulimit as it's already higher than our good enough value", zap.Uint64("good_enough_value", goodEnoughMaxOpenFiles)) + return nil + } + + // We first try to set the value to our good enough value. It might or might not + // work depending if the user permits the operation and if on OS X, the maximal + // value possible as been increased (https://superuser.com/a/514049/459230). + // + // If our first try didn't work, let's try with a small value that should fit + // most stock OS X value. This should probably be done only for OS X, other OSes + // should probably even try a higher value than the minimal OS X value first. + // + // We might need conditional compilation units here to make the logic easier. + err = trySetMaxOpenFilesLimit(goodEnoughMaxOpenFiles) + if err != nil { + logger.Debug("unable to use our good enough ulimit max open files value, going to try with something lower", zap.Error(err)) + } else { + return logValueAfterAdjustment(logger) + } + + err = trySetMaxOpenFilesLimit(osxStockMaxOpenFiles) + if err != nil { + return fmt.Errorf("cannot set ulimit max open files: %w", err) + } + + return logValueAfterAdjustment(logger) +} + +func trySetMaxOpenFilesLimit(value uint64) error { + err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{ + Cur: value, + Max: value, + }) + + if err != nil { + return fmt.Errorf("cannot set ulimit max open files: %w", err) + } + + return nil +} + +func getMaxOpenFilesLimit() (uint64, error) { + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return 0, fmt.Errorf("cannot get ulimit max open files value: %w", err) + } + + return rLimit.Cur, nil +} + +func logValueAfterAdjustment(logger *zap.Logger) error { + maxOpenFilesLimit, err := getMaxOpenFilesLimit() + if err != nil { + return err + } + + logger.Debug("ulimit max open files after adjustment", zap.Uint64("current_value", maxOpenFilesLimit)) + return nil +} diff --git a/firehose/firehose-core/launcher/tracing.go b/firehose/firehose-core/launcher/tracing.go new file mode 100644 index 0000000..fec971a --- /dev/null +++ b/firehose/firehose-core/launcher/tracing.go @@ -0,0 +1,26 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package launcher + +import ( + "github.com/streamingfast/derr" + "github.com/streamingfast/dtracing" + "go.opencensus.io/trace" +) + +func SetupTracing(name string) { + err := dtracing.SetupTracing(name, trace.ProbabilitySampler(1/8.0)) + derr.Check("unable to setup tracing correctly", err) +} diff --git a/firehose/firehose-core/merged_blocks_writer.go b/firehose/firehose-core/merged_blocks_writer.go new file mode 100644 index 0000000..d49d6df --- /dev/null +++ b/firehose/firehose-core/merged_blocks_writer.go @@ -0,0 +1,113 @@ +package firecore + +import ( + "context" + "fmt" + "io" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + "go.uber.org/zap" +) + +type MergedBlocksWriter struct { + Store dstore.Store + LowBlockNum uint64 + StopBlockNum uint64 + + blocks []*pbbstream.Block + Logger *zap.Logger + Cmd *cobra.Command + + TweakBlock func(*pbbstream.Block) (*pbbstream.Block, error) +} + +func (w *MergedBlocksWriter) ProcessBlock(blk *pbbstream.Block, obj interface{}) error { + if w.TweakBlock != nil { + b, err := w.TweakBlock(blk) + if err != nil { + return fmt.Errorf("tweaking block: %w", err) + } + blk = b + } + + if w.LowBlockNum == 0 && blk.Number > 99 { // initial block + if blk.Number%100 != 0 && blk.Number != bstream.GetProtocolFirstStreamableBlock { + return fmt.Errorf("received unexpected block %s (not a boundary, not the first streamable block %d)", blk, bstream.GetProtocolFirstStreamableBlock) + } + w.LowBlockNum = LowBoundary(blk.Number) + w.Logger.Debug("setting initial boundary to %d upon seeing block %s", zap.Uint64("low_boundary", w.LowBlockNum), zap.Uint64("blk_num", blk.Number)) + } + + if blk.Number > w.LowBlockNum+99 { + w.Logger.Debug("bundling because we saw block %s from next bundle (%d was not seen, it must not exist on this chain)", zap.Uint64("blk_num", blk.Number), zap.Uint64("last_bundle_block", w.LowBlockNum+99)) + if err := w.WriteBundle(); err != nil { + return err + } + } + + if w.StopBlockNum > 0 && blk.Number >= w.StopBlockNum { + return io.EOF + } + + w.blocks = append(w.blocks, blk) + + if blk.Number == w.LowBlockNum+99 { + w.Logger.Debug("bundling on last bundle block", zap.Uint64("last_bundle_block", w.LowBlockNum+99)) + if err := w.WriteBundle(); err != nil { + return err + } + return nil + } + + return nil +} + +func (w *MergedBlocksWriter) WriteBundle() error { + file := filename(w.LowBlockNum) + w.Logger.Info("writing merged file to store (suffix: .dbin.zst)", zap.String("filename", file), zap.Uint64("lowBlockNum", w.LowBlockNum)) + + if len(w.blocks) == 0 { + return fmt.Errorf("no blocks to write to bundle") + } + + pr, pw := io.Pipe() + + go func() { + var err error + defer func() { + pw.CloseWithError(err) + }() + + blockWriter, err := bstream.NewDBinBlockWriter(pw) + if err != nil { + return + } + + for _, blk := range w.blocks { + err = blockWriter.Write(blk) + if err != nil { + return + } + } + }() + + err := w.Store.WriteObject(context.Background(), file, pr) + if err != nil { + w.Logger.Error("writing to store", zap.Error(err)) + } + + w.LowBlockNum += 100 + w.blocks = nil + + return err +} +func filename(num uint64) string { + return fmt.Sprintf("%010d", num) +} + +func LowBoundary(i uint64) uint64 { + return i - (i % 100) +} diff --git a/firehose/firehose-core/merger/app/merger/app.go b/firehose/firehose-core/merger/app/merger/app.go new file mode 100644 index 0000000..cc9fb31 --- /dev/null +++ b/firehose/firehose-core/merger/app/merger/app.go @@ -0,0 +1,143 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "context" + "fmt" + "time" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dgrpc" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/dstore" + "github.com/streamingfast/firehose-core/merger" + "github.com/streamingfast/firehose-core/merger/metrics" + "github.com/streamingfast/shutter" + "go.uber.org/zap" + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +type Config struct { + StorageOneBlockFilesPath string + StorageMergedBlocksFilesPath string + StorageForkedBlocksFilesPath string + + FilesDeleteThreads int + + GRPCListenAddr string + + PruneForkedBlocksAfter uint64 + + TimeBetweenPruning time.Duration + TimeBetweenPolling time.Duration + StopBlock uint64 +} + +type App struct { + *shutter.Shutter + config *Config + readinessProbe pbhealth.HealthClient +} + +func New(config *Config) *App { + return &App{ + Shutter: shutter.New(), + config: config, + } +} + +func (a *App) Run() error { + zlog.Info("running merger", zap.Reflect("config", a.config)) + + dmetrics.Register(metrics.MetricSet) + + oneBlockStoreStore, err := dstore.NewDBinStore(a.config.StorageOneBlockFilesPath) + if err != nil { + return fmt.Errorf("failed to init source archive store: %w", err) + } + + mergedBlocksStore, err := dstore.NewDBinStore(a.config.StorageMergedBlocksFilesPath) + if err != nil { + return fmt.Errorf("failed to init destination archive store: %w", err) + } + + var forkedBlocksStore dstore.Store + if a.config.StorageForkedBlocksFilesPath != "" { + forkedBlocksStore, err = dstore.NewDBinStore(a.config.StorageForkedBlocksFilesPath) + if err != nil { + return fmt.Errorf("failed to init destination archive store: %w", err) + } + } + + bundleSize := uint64(100) + + // we are setting the backoff here for dstoreIO + io := merger.NewDStoreIO( + zlog, + tracer, + oneBlockStoreStore, + mergedBlocksStore, + forkedBlocksStore, + 5, + 500*time.Millisecond, + bundleSize, + a.config.FilesDeleteThreads) + + m := merger.NewMerger( + zlog, + a.config.GRPCListenAddr, + io, + bstream.GetProtocolFirstStreamableBlock, + bundleSize, + a.config.PruneForkedBlocksAfter, + a.config.TimeBetweenPruning, + a.config.TimeBetweenPolling, + a.config.StopBlock, + ) + zlog.Info("merger initiated") + + gs, err := dgrpc.NewInternalClient(a.config.GRPCListenAddr) + if err != nil { + return fmt.Errorf("cannot create readiness probe") + } + a.readinessProbe = pbhealth.NewHealthClient(gs) + + a.OnTerminating(m.Shutdown) + m.OnTerminated(a.Shutdown) + + go m.Run() + + zlog.Info("merger running") + return nil +} + +func (a *App) IsReady() bool { + if a.readinessProbe == nil { + return false + } + + resp, err := a.readinessProbe.Check(context.Background(), &pbhealth.HealthCheckRequest{}) + if err != nil { + zlog.Info("merger readiness probe error", zap.Error(err)) + return false + } + + if resp.Status == pbhealth.HealthCheckResponse_SERVING { + return true + } + + return false +} diff --git a/firehose/firehose-core/merger/app/merger/logging.go b/firehose/firehose-core/merger/app/merger/logging.go new file mode 100644 index 0000000..c0ee237 --- /dev/null +++ b/firehose/firehose-core/merger/app/merger/logging.go @@ -0,0 +1,21 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "github.com/streamingfast/logging" +) + +var zlog, tracer = logging.PackageLogger("merger", "github.com/streamingfast/firehose-core/merger/app/merger") diff --git a/firehose/firehose-core/merger/bundler.go b/firehose/firehose-core/merger/bundler.go new file mode 100644 index 0000000..058ee1c --- /dev/null +++ b/firehose/firehose-core/merger/bundler.go @@ -0,0 +1,251 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "sync" + "time" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/forkable" + "github.com/streamingfast/firehose-core/merger/metrics" + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +var ErrStopBlockReached = errors.New("stop block reached") +var ErrFirstBlockAfterInitialStreamableBlock = errors.New("received first block after inital streamable block") + +type Bundler struct { + sync.Mutex + + io IOInterface + + baseBlockNum uint64 + + bundleSize uint64 + bundleError chan error + inProcess sync.Mutex + stopBlock uint64 + enforceNextBlockOnBoundary bool + firstStreamableBlock uint64 + + seenBlockFiles map[string]*bstream.OneBlockFile + irreversibleBlocks []*bstream.OneBlockFile + forkable *forkable.Forkable + + logger *zap.Logger +} + +var logger, _ = logging.PackageLogger("merger", "github.com/streamingfast/firehose-core/merger/bundler") + +func NewBundler(startBlock, stopBlock, firstStreamableBlock, bundleSize uint64, io IOInterface) *Bundler { + b := &Bundler{ + bundleSize: bundleSize, + io: io, + bundleError: make(chan error, 1), + firstStreamableBlock: firstStreamableBlock, + stopBlock: stopBlock, + seenBlockFiles: make(map[string]*bstream.OneBlockFile), + logger: logger, + } + b.Reset(toBaseNum(startBlock, bundleSize), nil) + return b +} + +// BaseBlockNum can be called from a different thread +func (b *Bundler) BaseBlockNum() uint64 { + b.inProcess.Lock() + defer b.inProcess.Unlock() + // while inProcess is locked, all blocks below b.baseBlockNum are actually merged + return b.baseBlockNum +} + +func (b *Bundler) HandleBlockFile(obf *bstream.OneBlockFile) error { + b.seenBlockFiles[obf.CanonicalName] = obf + return b.forkable.ProcessBlock(obf.ToBstreamBlock(), obf) // forkable will call our own b.ProcessBlock() on irreversible blocks only +} + +func (b *Bundler) forkedBlocksInCurrentBundle() (out []*bstream.OneBlockFile) { + highBoundary := b.baseBlockNum + b.bundleSize + + // remove irreversible blocks from map (they will be merged and deleted soon) + for _, block := range b.irreversibleBlocks { + delete(b.seenBlockFiles, block.CanonicalName) + } + + // identify and then delete remaining blocks from map, return them as forks + for name, block := range b.seenBlockFiles { + if block.Num < b.baseBlockNum { + delete(b.seenBlockFiles, name) // too old, just cleaning up the map of lingering old blocks + } + if block.Num < highBoundary { + out = append(out, block) + delete(b.seenBlockFiles, name) + } + } + return +} + +func (b *Bundler) Reset(nextBase uint64, lib bstream.BlockRef) { + options := []forkable.Option{ + forkable.WithFilters(bstream.StepIrreversible), + forkable.HoldBlocksUntilLIB(), + forkable.WithWarnOnUnlinkableBlocks(100), // don't warn too soon, sometimes oneBlockFiles are uploaded out of order from mindreader (on remote I/O) + } + if lib != nil { + options = append(options, forkable.WithInclusiveLIB(lib)) + b.enforceNextBlockOnBoundary = false // we don't need to check first block because we know it will be linked to lib + } else { + b.enforceNextBlockOnBoundary = true + } + b.forkable = forkable.New(b, options...) + + b.Lock() + b.baseBlockNum = nextBase + b.irreversibleBlocks = nil + b.Unlock() +} + +func readBlockTime(data []byte) (time.Time, error) { + reader := bytes.NewReader(data) + blockReader, err := bstream.NewDBinBlockReader(reader) + if err != nil { + return time.Time{}, fmt.Errorf("unable to create block reader: %w", err) + } + blk, err := blockReader.Read() + if err != nil && err != io.EOF { + return time.Time{}, fmt.Errorf("block reader failed: %w", err) + } + return blk.Time(), nil +} + +func (b *Bundler) ProcessBlock(_ *pbbstream.Block, obj interface{}) error { + obf := obj.(bstream.ObjectWrapper).WrappedObject().(*bstream.OneBlockFile) + if obf.Num < b.baseBlockNum { + // we may be receiving an inclusive LIB just before our bundle, ignore it + return nil + } + + if b.enforceNextBlockOnBoundary { + if obf.Num != b.baseBlockNum && obf.Num != b.firstStreamableBlock { + //{"severity":"ERROR","timestamp":"2023-11-07T12:28:34.735713163-05:00","logger":"merger","message":"expecting to start at block `base_block_num` but got block `block_num` (and we have no previous blockID to align with..). First streamable block is configured to be: `first_streamable_block`", + //"base_block_num":22207900, + //"block_num":22208900, + //"first_streamable_block":22207900, + //"logging.googleapis.com/labels":{},"serviceContext":{"service":"unknown"}} + b.logger.Error( + "expecting to start at block `base_block_num` but got block `block_num` (and we have no previous blockID to align with..). First streamable block is configured to be: `first_streamable_block`", + zap.Uint64("base_block_num", b.baseBlockNum), + zap.Uint64("block_num", obf.Num), + zap.Uint64("first_streamable_block", b.firstStreamableBlock), + ) + return ErrFirstBlockAfterInitialStreamableBlock + } + b.enforceNextBlockOnBoundary = false + } + + if obf.Num < b.baseBlockNum+b.bundleSize { + b.Lock() + metrics.AppReadiness.SetReady() + b.irreversibleBlocks = append(b.irreversibleBlocks, obf) + metrics.HeadBlockNumber.SetUint64(obf.Num) + go func() { + // this pre-downloads the data + data, err := obf.Data(context.Background(), b.io.DownloadOneBlockFile) + if err != nil { + return + } + // now that we have the data, might as well read the block time for metrics + if time, err := readBlockTime(data); err == nil { + metrics.HeadBlockTimeDrift.SetBlockTime(time) + } + }() + b.Unlock() + return nil + } + + select { + case err := <-b.bundleError: + return err + default: + } + + forkedBlocks := b.forkedBlocksInCurrentBundle() + blocksToBundle := b.irreversibleBlocks + baseBlockNum := b.baseBlockNum + b.inProcess.Lock() + go func() { + defer b.inProcess.Unlock() + if err := b.io.MergeAndStore(context.Background(), baseBlockNum, blocksToBundle); err != nil { + b.bundleError <- err + return + } + if forkableIO, ok := b.io.(ForkAwareIOInterface); ok { + forkableIO.MoveForkedBlocks(context.Background(), forkedBlocks) + } + // we do not delete bundled blocks here, they get pruned later. keeping the blocks from the last bundle is useful for bootstrapping + }() + + b.Lock() + // we keep the last block of the bundle, only deleting it on next merge, to facilitate joining to one-block-filled hub + lastBlock := b.irreversibleBlocks[len(b.irreversibleBlocks)-1] + b.irreversibleBlocks = []*bstream.OneBlockFile{lastBlock, obf} + b.baseBlockNum += b.bundleSize + for obf.Num > b.baseBlockNum+b.bundleSize { // skip more merged-block-files + b.inProcess.Lock() + if err := b.io.MergeAndStore(context.Background(), b.baseBlockNum, []*bstream.OneBlockFile{lastBlock}); err != nil { // lastBlock will be excluded from bundle but is useful to bundler + return err + } + b.inProcess.Unlock() + b.baseBlockNum += b.bundleSize + } + b.Unlock() + + if b.stopBlock != 0 && b.baseBlockNum >= b.stopBlock { + return ErrStopBlockReached + } + + return nil +} + +// String can be called from a different thread +func (b *Bundler) String() string { + b.Lock() + defer b.Unlock() + + var firstBlock, lastBlock string + length := len(b.irreversibleBlocks) + if length != 0 { + firstBlock = b.irreversibleBlocks[0].String() + lastBlock = b.irreversibleBlocks[length-1].String() + } + + return fmt.Sprintf( + "bundle_size: %d, base_block_num: %d, first_block: %s, last_block: %s, length: %d", + b.bundleSize, + b.baseBlockNum, + firstBlock, + lastBlock, + length, + ) +} diff --git a/firehose/firehose-core/merger/bundler_test.go b/firehose/firehose-core/merger/bundler_test.go new file mode 100644 index 0000000..327762d --- /dev/null +++ b/firehose/firehose-core/merger/bundler_test.go @@ -0,0 +1,226 @@ +package merger + +import ( + // "context" + //"fmt" + + "context" + "testing" + + // "time" + + // "github.com/streamingfast/bstream" + //"github.com/streamingfast/firehose-core/merger/bundle" + "github.com/streamingfast/bstream" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setPbBlock(obf *bstream.OneBlockFile) { + //pbb := &pbbstream.Block{ + // Number: obf.Num, + //} + //out, err := proto.Marshal(pbb) + //if err != nil { + // panic(err) + //} + //obf.MemoizeData = out +} + +var block98 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000098-0000000000000098a-0000000000000097a-96-suffix") + setPbBlock(obf) + return obf +} +var block99 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000099-0000000000000099a-0000000000000098a-97-suffix") + setPbBlock(obf) + return obf + +} +var block100 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000100-0000000000000100a-0000000000000099a-98-suffix") + setPbBlock(obf) + return obf +} +var block101 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000101-0000000000000101a-0000000000000100a-99-suffix") + setPbBlock(obf) + return obf +} +var block102Final100 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000102-0000000000000102a-0000000000000101a-100-suffix") + setPbBlock(obf) + return obf +} +var block103Final101 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000103-0000000000000103a-0000000000000102a-101-suffix") + setPbBlock(obf) + return obf +} +var block104Final102 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000104-0000000000000104a-0000000000000103a-102-suffix") + setPbBlock(obf) + return obf +} +var block105Final103 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000105-0000000000000105a-0000000000000104a-103-suffix") + setPbBlock(obf) + return obf +} +var block106Final104 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000106-0000000000000106a-0000000000000105a-104-suffix") + setPbBlock(obf) + return obf +} + +var block507Final106 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000507-0000000000000507a-0000000000000106a-106-suffix") + setPbBlock(obf) + return obf +} +var block608Final507 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000608-0000000000000608a-0000000000000507a-507-suffix") + setPbBlock(obf) + return obf +} +var block609Final608 = func() *bstream.OneBlockFile { + obf := bstream.MustNewOneBlockFile("0000000609-0000000000000609a-0000000000000608a-608-suffix") + setPbBlock(obf) + return obf +} + +func TestNewBundler(t *testing.T) { + b := NewBundler(100, 200, 2, 100, nil) + require.NotNil(t, b) + assert.EqualValues(t, 100, b.bundleSize) + assert.EqualValues(t, 200, b.stopBlock) + assert.NotNil(t, b.bundleError) + assert.NotNil(t, b.seenBlockFiles) +} + +func TestBundlerReset(t *testing.T) { + b := NewBundler(100, 200, 2, 2, nil) // merge every 2 blocks + + b.irreversibleBlocks = []*bstream.OneBlockFile{block100(), block101()} + b.Reset(102, block100().ToBstreamBlock().AsRef()) + assert.Nil(t, b.irreversibleBlocks) + assert.EqualValues(t, 102, b.baseBlockNum) + +} + +func TestBundlerMergeKeepOne(t *testing.T) { + + tests := []struct { + name string + inBlocks []*bstream.OneBlockFile + mergeSize uint64 + expectRemaining []*bstream.OneBlockFile + expectBase uint64 + expectMerged []uint64 + }{ + { + name: "vanilla", + inBlocks: []*bstream.OneBlockFile{ + block100(), + block101(), + block102Final100(), + block103Final101(), + block104Final102(), + }, + mergeSize: 2, + expectRemaining: []*bstream.OneBlockFile{ + block101(), + block102Final100(), + }, + expectBase: 102, + expectMerged: []uint64{100}, + }, + { + name: "vanilla_plus_one", + inBlocks: []*bstream.OneBlockFile{ + block100(), + block101(), + block102Final100(), + block103Final101(), + block104Final102(), + block105Final103(), + }, + mergeSize: 2, + expectRemaining: []*bstream.OneBlockFile{ + block101(), + block102Final100(), + block103Final101(), + }, + expectBase: 102, + expectMerged: []uint64{100}, + }, + { + name: "twoMerges", + inBlocks: []*bstream.OneBlockFile{ + block100(), + block101(), + block102Final100(), + block103Final101(), + block104Final102(), + block105Final103(), + block106Final104(), + }, + mergeSize: 2, + expectRemaining: []*bstream.OneBlockFile{ + block103Final101(), + block104Final102(), + }, + expectBase: 104, + expectMerged: []uint64{100, 102}, + }, + { + name: "big_hole", + inBlocks: []*bstream.OneBlockFile{ + block100(), + block101(), + block102Final100(), + block103Final101(), + block104Final102(), + block105Final103(), + block106Final104(), + block507Final106(), + block608Final507(), + block609Final608(), + }, + mergeSize: 100, + expectRemaining: []*bstream.OneBlockFile{ + block507Final106(), // last from bundle 500 + block608Final507(), // the only irreversible block from current bundle + }, + expectBase: 600, + expectMerged: []uint64{100, 200, 300, 400, 500}, + }, + } + + for _, c := range tests { + + t.Run(c.name, func(t *testing.T) { + var merged []uint64 + b := NewBundler(100, 700, 2, c.mergeSize, &TestMergerIO{ + MergeAndStoreFunc: func(_ context.Context, inclusiveLowerBlock uint64, _ []*bstream.OneBlockFile) (err error) { + merged = append(merged, inclusiveLowerBlock) + return nil + }, + }) // merge every 2 blocks + b.irreversibleBlocks = []*bstream.OneBlockFile{block100(), block101()} + + for _, blk := range c.inBlocks { + require.NoError(t, b.HandleBlockFile(blk)) + } + + // wait for MergeAndStore + b.inProcess.Lock() + b.inProcess.Unlock() + + assert.Equal(t, c.expectMerged, merged) + assert.Equal(t, c.expectRemaining, b.irreversibleBlocks) + assert.Equal(t, int(c.expectBase), int(b.baseBlockNum)) + }) + } +} diff --git a/firehose/firehose-core/merger/bundlereader.go b/firehose/firehose-core/merger/bundlereader.go new file mode 100644 index 0000000..e2ec0ba --- /dev/null +++ b/firehose/firehose-core/merger/bundlereader.go @@ -0,0 +1,126 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "bytes" + "context" + "fmt" + "io" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dbin" + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +type BundleReader struct { + ctx context.Context + readBuffer []byte + readBufferOffset int + oneBlockDataChan chan []byte + errChan chan error + + logger *zap.Logger + header *dbin.Header + headerLength int +} + +func NewBundleReader(ctx context.Context, logger *zap.Logger, tracer logging.Tracer, oneBlockFiles []*bstream.OneBlockFile, anyOneBlockFile *bstream.OneBlockFile, oneBlockDownloader bstream.OneBlockDownloaderFunc) (*BundleReader, error) { + r := &BundleReader{ + ctx: ctx, + logger: logger, + oneBlockDataChan: make(chan []byte, 1), + errChan: make(chan error, 1), + } + + data, err := anyOneBlockFile.Data(ctx, oneBlockDownloader) + if err != nil { + return nil, fmt.Errorf("cannot read one_block_file to get header: %w", err) + } + + dbinReader, err := bstream.NewDBinBlockReader(bytes.NewReader(data)) + if err != nil { + return nil, fmt.Errorf("creating block reader: %w", err) + } + + r.header = dbinReader.Header + r.headerLength = len(r.header.RawBytes) + + if len(data) < r.headerLength { + return nil, fmt.Errorf("one-block-file corrupt: expected header size of %d, but file size is only %d bytes", r.headerLength, len(data)) + } + r.readBuffer = data[:r.headerLength] + + go r.downloadAll(oneBlockFiles, oneBlockDownloader) + return r, nil +} + +// downloadAll does not work in parallel: for performance, the oneBlockFiles' data should already have been memoized by calling Data() on them. +func (r *BundleReader) downloadAll(oneBlockFiles []*bstream.OneBlockFile, oneBlockDownloader bstream.OneBlockDownloaderFunc) { + defer close(r.oneBlockDataChan) + for _, oneBlockFile := range oneBlockFiles { + data, err := oneBlockFile.Data(r.ctx, oneBlockDownloader) + if err != nil { + r.errChan <- err + return + } + r.oneBlockDataChan <- data + } +} + +func (r *BundleReader) Read(p []byte) (bytesRead int, err error) { + if r.readBuffer == nil { + if err := r.fillBuffer(); err != nil { + return 0, err + } + } + + bytesRead = copy(p, r.readBuffer[r.readBufferOffset:]) + r.readBufferOffset += bytesRead + if r.readBufferOffset >= len(r.readBuffer) { + r.readBuffer = nil + } + + return bytesRead, nil +} + +func (r *BundleReader) fillBuffer() error { + var data []byte + select { + case d, ok := <-r.oneBlockDataChan: + if !ok { + return io.EOF + } + data = d + case err := <-r.errChan: + return err + case <-r.ctx.Done(): + return nil + } + + if len(data) == 0 { + r.readBuffer = nil + return fmt.Errorf("one-block-file corrupt: empty data") + } + + if len(data) < r.headerLength { + return fmt.Errorf("one-block-file corrupt: expected header size of %d, but file size is only %d bytes", r.headerLength, len(data)) + } + data = data[r.headerLength:] + r.readBuffer = data + r.readBufferOffset = 0 + return nil +} diff --git a/firehose/firehose-core/merger/bundlereader_test.go b/firehose/firehose-core/merger/bundlereader_test.go new file mode 100644 index 0000000..a67ef37 --- /dev/null +++ b/firehose/firehose-core/merger/bundlereader_test.go @@ -0,0 +1,288 @@ +package merger + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "path" + "testing" + "time" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dbin" + "github.com/stretchr/testify/require" +) + +func TestBundleReader_ReadSimpleFiles(t *testing.T) { + bundle := NewTestBundle() + + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, bundle[0], nil) + require.NoError(t, err) + + r1 := make([]byte, len(testOneBlockHeader)) + read, err := r.Read(r1) + require.NoError(t, err, "reading header") + require.Equal(t, string(testOneBlockHeader), string(r1)) + + r1 = make([]byte, 2) + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 2, read) + require.Equal(t, []byte{0x1, 0x2}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 2, read) + require.Equal(t, []byte{0x3, 0x4}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 2, read) + require.Equal(t, []byte{0x5, 0x6}, r1) + + read, err = r.Read(r1) + require.Equal(t, 0, read) + require.Equal(t, io.EOF, err) +} + +func TestBundleReader_ReadByChunk(t *testing.T) { + bundle := NewTestBundle() + + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, bundle[0], nil) + require.NoError(t, err) + + r1 := make([]byte, len(testOneBlockHeader)) + read, err := r.Read(r1) + require.NoError(t, err, "reading header") + require.Equal(t, string(testOneBlockHeader), string(r1)) + + r1 = make([]byte, 1) + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 1, read) + require.Equal(t, []byte{0x1}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 1, read) + require.Equal(t, []byte{0x2}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 1, read) + require.Equal(t, []byte{0x3}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 1, read) + require.Equal(t, []byte{0x4}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 1, read) + require.Equal(t, []byte{0x5}, r1) + + read, err = r.Read(r1) + require.NoError(t, err) + require.Equal(t, 1, read) + require.Equal(t, []byte{0x6}, r1) + + _, err = r.Read(r1) + require.Equal(t, err, io.EOF) +} + +func TestBundleReader_Read_Then_Read_Block(t *testing.T) { + bundle := []*bstream.OneBlockFile{ + NewTestOneBlockFileFromFile(t, "0000000001-20150730T152628.0-13406cb6-b1cb8fa3.dbin"), + NewTestOneBlockFileFromFile(t, "0000000002-20150730T152657.0-044698c9-13406cb6.dbin"), + NewTestOneBlockFileFromFile(t, "0000000003-20150730T152728.0-a88cf741-044698c9.dbin"), + } + + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, bundle[0], nil) + require.NoError(t, err) + allBlockData, err := ioutil.ReadAll(r) + require.NoError(t, err) + dbinReader := dbin.NewReader(bytes.NewReader(allBlockData)) + + //Reader header once + _, err = dbinReader.ReadHeader() + + //Block 1 + require.NoError(t, err) + b1, err := dbinReader.ReadMessage() + require.NoError(t, err) + require.Equal(t, b1, bundle[0].MemoizeData[14:]) + + //Block 2 + require.NoError(t, err) + b2, err := dbinReader.ReadMessage() + require.NoError(t, err) + require.Equal(t, b2, bundle[1].MemoizeData[14:]) + + //Block 3 + require.NoError(t, err) + b3, err := dbinReader.ReadMessage() + require.NoError(t, err) + require.Equal(t, b3, bundle[2].MemoizeData[14:]) +} + +func TestBundleReader_Read_DownloadOneBlockFileError(t *testing.T) { + bundle := NewBundleNoMemoize() + anyOB := &bstream.OneBlockFile{ + CanonicalName: "header", + MemoizeData: testOneBlockHeader, + } + + downloadOneBlockFile := func(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + return nil, fmt.Errorf("some error") + } + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, anyOB, downloadOneBlockFile) + require.NoError(t, err) + + r1 := make([]byte, len(testOneBlockHeader)) + read, err := r.Read(r1) + require.NoError(t, err, "reading header") + require.Equal(t, string(testOneBlockHeader), string(r1)) + + read, err = r.Read(r1) + require.Equal(t, 0, read) + require.Errorf(t, err, "some error") +} + +func TestBundleReader_Read_DownloadOneBlockFileCorrupt(t *testing.T) { + + bundle := NewBundleNoMemoize() + + downloadOneBlockFile := func(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + return []byte{0xAB, 0xCD, 0xEF}, nil // shorter than header length + } + + _, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, bundle[0], downloadOneBlockFile) + require.Error(t, err) +} + +func TestBundleReader_Read_DownloadOneBlockFileZeroLength(t *testing.T) { + bundle := NewBundleNoMemoize() + + anyBlockFile := &bstream.OneBlockFile{ + CanonicalName: "header", + MemoizeData: testOneBlockHeader, + } + + downloadOneBlockFile := func(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + return []byte{}, nil + } + + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, anyBlockFile, downloadOneBlockFile) + require.NoError(t, err) + + r1 := make([]byte, len(testOneBlockHeader)) + read, err := r.Read(r1) + require.NoError(t, err, "reading header") + require.Equal(t, string(testOneBlockHeader), string(r1)) + + r1 = make([]byte, 4) + read, err = r.Read(r1) + require.Equal(t, read, 0) + require.Error(t, err, "EOF expected") +} + +func TestBundleReader_Read_ReadBufferNotNil(t *testing.T) { + bundle := NewBundleNoMemoize() + + anyBlockFile := &bstream.OneBlockFile{ + CanonicalName: "header", + MemoizeData: testOneBlockHeader, + } + + downloadOneBlockFile := func(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + return nil, fmt.Errorf("some error") + } + + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, anyBlockFile, downloadOneBlockFile) + require.NoError(t, err) + r.readBuffer = []byte{0xAB, 0xCD} + r1 := make([]byte, 4) + + read, err := r.Read(r1) + require.Equal(t, read, 2) + require.Nil(t, err) +} + +func TestBundleReader_Read_EmptyListOfOneBlockFiles(t *testing.T) { + bundle := NewBundleNoMemoize() + + anyBlockFile := &bstream.OneBlockFile{ + CanonicalName: "header", + MemoizeData: testOneBlockHeader, + } + + downloadOneBlockFile := func(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + return nil, fmt.Errorf("some error") + } + + r, err := NewBundleReader(context.Background(), testLogger, testTracer, bundle, anyBlockFile, downloadOneBlockFile) + require.NoError(t, err) + + r1 := make([]byte, len(testOneBlockHeader)) + read, err := r.Read(r1) + require.NoError(t, err, "reading header") + require.Equal(t, string(testOneBlockHeader), string(r1)) + + r1 = make([]byte, 4) + read, err = r.Read(r1) + require.Equal(t, 0, read) + require.Errorf(t, err, "EOF") +} + +func NewTestOneBlockFileFromFile(t *testing.T, fileName string) *bstream.OneBlockFile { + t.Helper() + data, err := ioutil.ReadFile(path.Join("test_data", fileName)) + require.NoError(t, err) + time.Sleep(1 * time.Millisecond) + return &bstream.OneBlockFile{ + CanonicalName: fileName, + Filenames: map[string]bool{fileName: true}, + ID: "", + Num: 0, + PreviousID: "", + MemoizeData: data, + } +} + +var testOneBlockHeader = []byte("dbin\x00tes\x00\x00") + +func NewTestBundle() []*bstream.OneBlockFile { + + o1 := &bstream.OneBlockFile{ + CanonicalName: "o1", + MemoizeData: append(testOneBlockHeader, []byte{0x1, 0x2}...), + } + o2 := &bstream.OneBlockFile{ + CanonicalName: "o2", + MemoizeData: append(testOneBlockHeader, []byte{0x3, 0x4}...), + } + o3 := &bstream.OneBlockFile{ + CanonicalName: "o3", + MemoizeData: append(testOneBlockHeader, []byte{0x5, 0x6}...), + } + return []*bstream.OneBlockFile{o1, o2, o3} +} + +func NewBundleNoMemoize() []*bstream.OneBlockFile { + o1 := &bstream.OneBlockFile{ + CanonicalName: "o1", + MemoizeData: []byte{}, + } + o2 := &bstream.OneBlockFile{ + CanonicalName: "o2", + MemoizeData: []byte{}, + } + o3 := &bstream.OneBlockFile{ + CanonicalName: "o3", + MemoizeData: []byte{}, + } + return []*bstream.OneBlockFile{o1, o2, o3} +} diff --git a/firehose/firehose-core/merger/consts.go b/firehose/firehose-core/merger/consts.go new file mode 100644 index 0000000..585cc91 --- /dev/null +++ b/firehose/firehose-core/merger/consts.go @@ -0,0 +1,24 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import "time" + +var ListFilesTimeout = 10 * time.Minute +var WriteObjectTimeout = 5 * time.Minute +var GetObjectTimeout = 5 * time.Minute +var DeleteObjectTimeout = 5 * time.Minute + +const ParallelOneBlockDownload = 2 diff --git a/firehose/firehose-core/merger/healthz.go b/firehose/firehose-core/merger/healthz.go new file mode 100644 index 0000000..cc50dfd --- /dev/null +++ b/firehose/firehose-core/merger/healthz.go @@ -0,0 +1,43 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "context" + + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +// Check is basic GRPC Healthcheck +func (m *Merger) Check(ctx context.Context, in *pbhealth.HealthCheckRequest) (*pbhealth.HealthCheckResponse, error) { + status := pbhealth.HealthCheckResponse_SERVING + return &pbhealth.HealthCheckResponse{ + Status: status, + }, nil +} + +// Watch is basic GRPC Healthcheck as a stream +func (m *Merger) Watch(req *pbhealth.HealthCheckRequest, stream pbhealth.Health_WatchServer) error { + err := stream.Send(&pbhealth.HealthCheckResponse{ + Status: pbhealth.HealthCheckResponse_SERVING, + }) + if err != nil { + return err + } + + // The merger is always serving, so just want until this stream is canceled out + <-stream.Context().Done() + return nil +} diff --git a/firehose/firehose-core/merger/healthz_test.go b/firehose/firehose-core/merger/healthz_test.go new file mode 100644 index 0000000..697c536 --- /dev/null +++ b/firehose/firehose-core/merger/healthz_test.go @@ -0,0 +1,32 @@ +package merger + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +func TestHealthz_Check(t *testing.T) { + ctx := context.Background() + m := NewMerger( + testLogger, + "6969", + nil, + 1, + 100, + 100, + time.Second, + time.Second, + 0, + ) + request := &pbhealth.HealthCheckRequest{} + resp, err := m.Check(ctx, request) + if err != nil { + panic(err) + } + + require.Equal(t, resp.Status, pbhealth.HealthCheckResponse_SERVING) +} diff --git a/firehose/firehose-core/merger/init_test.go b/firehose/firehose-core/merger/init_test.go new file mode 100644 index 0000000..e6f09a1 --- /dev/null +++ b/firehose/firehose-core/merger/init_test.go @@ -0,0 +1,25 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "github.com/streamingfast/logging" +) + +var testLogger, testTracer = logging.PackageLogger("merger", "github.com/streamingfast/firehose-core/merger_tests") + +func init() { + logging.InstantiateLoggers() +} diff --git a/firehose/firehose-core/merger/merger.go b/firehose/firehose-core/merger/merger.go new file mode 100644 index 0000000..8181dce --- /dev/null +++ b/firehose/firehose-core/merger/merger.go @@ -0,0 +1,244 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "context" + "errors" + "time" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/shutter" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Merger struct { + *shutter.Shutter + grpcListenAddr string + + io IOInterface + firstStreamableBlock uint64 + logger *zap.Logger + + timeBetweenPolling time.Duration + + timeBetweenPruning time.Duration + pruningDistanceToLIB uint64 + + bundler *Bundler +} + +func NewMerger( + logger *zap.Logger, + grpcListenAddr string, + io IOInterface, + + firstStreamableBlock uint64, + bundleSize uint64, + pruningDistanceToLIB uint64, + timeBetweenPruning time.Duration, + timeBetweenPolling time.Duration, + stopBlock uint64, +) *Merger { + m := &Merger{ + Shutter: shutter.New(), + bundler: NewBundler(firstStreamableBlock, stopBlock, firstStreamableBlock, bundleSize, io), + grpcListenAddr: grpcListenAddr, + io: io, + firstStreamableBlock: firstStreamableBlock, + pruningDistanceToLIB: pruningDistanceToLIB, + timeBetweenPolling: timeBetweenPolling, + timeBetweenPruning: timeBetweenPruning, + logger: logger, + } + m.OnTerminating(func(_ error) { m.bundler.inProcess.Lock(); m.bundler.inProcess.Unlock() }) // finish bundle that may be merging async + + return m +} + +func (m *Merger) Run() { + m.logger.Info("starting merger") + + m.startGRPCServer() + + m.startOldFilesPruner() + m.startForkedBlocksPruner() + + err := m.run() + if err != nil { + m.logger.Error("merger returned error", zap.Error(err)) + } + m.Shutdown(err) +} + +func (m *Merger) startForkedBlocksPruner() { + forkableIO, ok := m.io.(ForkAwareIOInterface) + if !ok { + return + } + m.logger.Info("starting pruning of forked files", + zap.Uint64("pruning_distance_to_lib", m.pruningDistanceToLIB), + zap.Duration("time_between_pruning", m.timeBetweenPruning), + ) + + go func() { + delay := m.timeBetweenPruning // do not start pruning immediately + for { + time.Sleep(delay) + now := time.Now() + + pruningTarget := m.pruningTarget(m.pruningDistanceToLIB) + forkableIO.DeleteForkedBlocksAsync(bstream.GetProtocolFirstStreamableBlock, pruningTarget) + + if spentTime := time.Since(now); spentTime < m.timeBetweenPruning { + delay = m.timeBetweenPruning - spentTime + } + } + }() + +} + +func (m *Merger) startOldFilesPruner() { + m.logger.Info("starting pruning of unused (old) one-block-files", + zap.Uint64("pruning_distance_to_lib", m.bundler.bundleSize), + zap.Duration("time_between_pruning", m.timeBetweenPruning), + ) + go func() { + delay := m.timeBetweenPruning // do not start pruning immediately + + unfinishedDelay := time.Second * 5 + if unfinishedDelay > delay { + unfinishedDelay = delay / 2 + } + + ctx := context.Background() + for { + time.Sleep(delay) + + var toDelete []*bstream.OneBlockFile + + pruningTarget := m.pruningTarget(m.bundler.bundleSize) + if pruningTarget == 0 { + m.logger.Debug("skipping file deletion until we have a pruning target") + continue + } + + delay = m.timeBetweenPruning + err := m.io.WalkOneBlockFiles(ctx, m.firstStreamableBlock, func(obf *bstream.OneBlockFile) error { + if obf.Num < pruningTarget { + toDelete = append(toDelete, obf) + } + if len(toDelete) >= DefaultFilesDeleteBatchSize { + delay = unfinishedDelay + return ErrStopBlockReached + } + return nil + }) + if err != nil && !errors.Is(err, ErrStopBlockReached) { + m.logger.Warn("error while walking oneBlockFiles", zap.Error(err)) + } + + m.io.DeleteAsync(toDelete) + } + }() +} + +func (m *Merger) pruningTarget(distance uint64) uint64 { + bundlerBase := m.bundler.BaseBlockNum() + if distance > bundlerBase { + return 0 + } + + return bundlerBase - distance +} + +func (m *Merger) run() error { + ctx := context.Background() + + var holeFoundLogged bool + for { + now := time.Now() + if m.IsTerminating() { + return nil + } + + base, lib, err := m.io.NextBundle(ctx, m.bundler.baseBlockNum) + if err != nil { + if errors.Is(err, ErrHoleFound) { + if holeFoundLogged { + m.logger.Debug("found hole in merged files. this is not normal behavior unless reprocessing batches", zap.Error(err)) + } else { + holeFoundLogged = true + m.logger.Warn("found hole in merged files (next occurrence will show up as Debug)", zap.Error(err)) + } + } else { + return err + } + } + + if m.bundler.stopBlock != 0 && base > m.bundler.stopBlock { + if err == ErrStopBlockReached { + m.logger.Info("stop block reached") + return nil + } + } + + if base > m.bundler.baseBlockNum { + logFields := []zapcore.Field{ + zap.Uint64("previous_base_block_num", m.bundler.baseBlockNum), + zap.Uint64("new_base_block_num", base), + } + if lib != nil { + logFields = append(logFields, zap.Stringer("lib", lib)) + } + m.logger.Info("resetting bundler base block num", logFields...) + m.bundler.Reset(base, lib) + } + + var walkErr error + retryErr := Retry(m.logger, 12, 5*time.Second, func() error { + err = m.io.WalkOneBlockFiles(ctx, m.bundler.baseBlockNum, func(obf *bstream.OneBlockFile) error { + return m.bundler.HandleBlockFile(obf) + }) + + if err == ErrFirstBlockAfterInitialStreamableBlock { + m.bundler.Reset(base, lib) + return err + } + + if err != nil { + walkErr = err + } + return nil + }) + + if retryErr != nil { + return retryErr + } + + if walkErr != nil { + if walkErr == ErrStopBlockReached { + m.logger.Info("stop block reached") + return nil + } + return walkErr + } + + if spentTime := time.Since(now); spentTime < m.timeBetweenPolling { + time.Sleep(m.timeBetweenPolling - spentTime) + } + } +} diff --git a/firehose/firehose-core/merger/merger_io.go b/firehose/firehose-core/merger/merger_io.go new file mode 100644 index 0000000..19a4f16 --- /dev/null +++ b/firehose/firehose-core/merger/merger_io.go @@ -0,0 +1,442 @@ +package merger + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "sort" + "strconv" + "strings" + "sync" + "time" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dstore" + "github.com/streamingfast/firehose-core/merger/metrics" + "github.com/streamingfast/logging" + "go.uber.org/zap" +) + +var ErrHoleFound = errors.New("hole found in merged files") +var DefaultFilesDeleteBatchSize = 10000 + +type IOInterface interface { + + // NextBundle will read through consecutive merged blocks, starting at `lowestBaseBlock`, and return the next bundle that needs to be created + // If it finds an existing merged file at `lowestBaseBlock`, it will read the last one and include the lastIrreversibleBlock so you can bootstrap your forkdb from there + NextBundle(ctx context.Context, lowestBaseBlock uint64) (baseBlock uint64, lastIrreversibleBlock bstream.BlockRef, err error) + + // WalkOneBlockFiles calls your function for each oneBlockFile it reads, starting at the inclusiveLowerBlock. Useful to feed a block source + WalkOneBlockFiles(ctx context.Context, inclusiveLowerBlock uint64, callback func(*bstream.OneBlockFile) error) error + + // MergeAndStore writes a merged file from a list of oneBlockFiles + MergeAndStore(ctx context.Context, inclusiveLowerBlock uint64, oneBlockFiles []*bstream.OneBlockFile) (err error) + + // DownloadOneBlockFile will get you the data from the file + DownloadOneBlockFile(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) + + // DeleteAsync should be able to delete large quantities of oneBlockFiles from storage without ever blocking + DeleteAsync(oneBlockFiles []*bstream.OneBlockFile) error +} + +type ForkAwareIOInterface interface { + // DeleteForkedBlocksAsync will delete forked blocks between lowBoundary and highBoundary (both inclusive) + DeleteForkedBlocksAsync(inclusiveLowBoundary, inclusiveHighBoundary uint64) + + // MoveForkedBlocks will copy an array of oneBlockFiles to the forkedBlocksStore, then delete them (dstore does not have MOVE primitive) + MoveForkedBlocks(ctx context.Context, oneBlockFiles []*bstream.OneBlockFile) +} + +type ForkAwareDStoreIO struct { + *DStoreIO + forkedBlocksStore dstore.Store + forkOd *oneBlockFilesDeleter +} + +type DStoreIO struct { + oneBlocksStore dstore.Store + mergedBlocksStore dstore.Store + + retryAttempts int + retryCooldown time.Duration + + bundleSize uint64 + + logger *zap.Logger + tracer logging.Tracer + od *oneBlockFilesDeleter + forkOd *oneBlockFilesDeleter +} + +func NewDStoreIO( + logger *zap.Logger, + tracer logging.Tracer, + oneBlocksStore dstore.Store, + mergedBlocksStore dstore.Store, + forkedBlocksStore dstore.Store, + retryAttempts int, + retryCooldown time.Duration, + bundleSize uint64, + numDeleteThreads int, +) IOInterface { + + od := &oneBlockFilesDeleter{store: oneBlocksStore, logger: logger} + od.Start(numDeleteThreads, DefaultFilesDeleteBatchSize*2) + dstoreIO := &DStoreIO{ + oneBlocksStore: oneBlocksStore, + mergedBlocksStore: mergedBlocksStore, + retryAttempts: retryAttempts, + retryCooldown: retryCooldown, + bundleSize: bundleSize, + logger: logger, + tracer: tracer, + od: od, + } + + forkAware := forkedBlocksStore != nil + if !forkAware { + return dstoreIO + } + + forkOd := &oneBlockFilesDeleter{store: forkedBlocksStore, logger: logger} + forkOd.Start(numDeleteThreads, DefaultFilesDeleteBatchSize*2) + + return &ForkAwareDStoreIO{ + DStoreIO: dstoreIO, + forkedBlocksStore: forkedBlocksStore, + forkOd: forkOd, + } +} + +func (s *DStoreIO) MergeAndStore(ctx context.Context, inclusiveLowerBlock uint64, oneBlockFiles []*bstream.OneBlockFile) (err error) { + // since we keep the last block from previous merged bundle for future deleting, + // we want to make sure that it does not end up in this merged bundle too + var filteredOBF []*bstream.OneBlockFile + + if len(oneBlockFiles) == 0 { + return fmt.Errorf("cannot merge and store without a single oneBlockFile") + } + anyOneBlockFile := oneBlockFiles[0] + for _, obf := range oneBlockFiles { + if obf.Num >= inclusiveLowerBlock { + filteredOBF = append(filteredOBF, obf) + } + } + t0 := time.Now() + + bundleFilename := fileNameForBlocksBundle(inclusiveLowerBlock) + + zapFields := []zap.Field{ + zap.String("filename", bundleFilename), + zap.Duration("write_timeout", WriteObjectTimeout), + zap.Int("number_of_blocks", len(filteredOBF)), + } + if len(filteredOBF) != 0 { + zapFields = append(zapFields, zap.Uint64("lower_block_num", filteredOBF[0].Num), zap.Uint64("highest_block_num", filteredOBF[len(filteredOBF)-1].Num)) + } + + s.logger.Info("about to write merged blocks to storage location", zapFields...) + + err = Retry(s.logger, s.retryAttempts, s.retryCooldown, func() error { + inCtx, cancel := context.WithTimeout(ctx, WriteObjectTimeout) + defer cancel() + bundleReader, err := NewBundleReader(ctx, s.logger, s.tracer, filteredOBF, anyOneBlockFile, s.DownloadOneBlockFile) + if err != nil { + return err + } + return s.mergedBlocksStore.WriteObject(inCtx, bundleFilename, bundleReader) + }) + if err != nil { + return fmt.Errorf("write object error: %s", err) + } + + s.logger.Info("merged and uploaded", zap.String("filename", fileNameForBlocksBundle(inclusiveLowerBlock)), zap.Duration("merge_time", time.Since(t0))) + + return +} + +func (s *DStoreIO) WalkOneBlockFiles(ctx context.Context, lowestBlock uint64, callback func(*bstream.OneBlockFile) error) error { + return s.oneBlocksStore.WalkFrom(ctx, "", fileNameForBlocksBundle(lowestBlock), func(filename string) error { + if strings.HasSuffix(filename, ".tmp") { + return nil + } + oneBlockFile := bstream.MustNewOneBlockFile(filename) + + if err := callback(oneBlockFile); err != nil { + return err + } + return nil + }) + +} + +// fixLegacyBlock reads the header and looks for "Version 0", rewriting to Version 1 on the fly if needed +func fixLegacyBlock(in []byte) ([]byte, error) { + dbinReader, err := bstream.NewDBinBlockReader(bytes.NewReader(in)) + if err != nil { + return nil, fmt.Errorf("creating block reader in fixLegacyBlock: %w", err) + } + + if dbinReader.Header.Version != 0 { + return in, nil + } + + reader, err := bstream.NewDBinBlockReader(bytes.NewReader(in)) + if err != nil { + return nil, fmt.Errorf("creating block reader in fixLegacyBlock: %w", err) + } + + blk, err := reader.Read() + if err != nil { + return nil, fmt.Errorf("reading block in fixLegacyBlock: %w", err) + } + + out := new(bytes.Buffer) + writer, err := bstream.NewDBinBlockWriter(out) + if err != nil { + return nil, err + } + + if err := writer.Write(blk); err != nil { + return nil, fmt.Errorf("writing block in fixLegacyBlock: %w", err) + } + return out.Bytes(), nil + +} + +func (s *DStoreIO) DownloadOneBlockFile(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + for filename := range oneBlockFile.Filenames { // will try to get MemoizeData from any of those files + var out io.ReadCloser + out, err = s.oneBlocksStore.OpenObject(ctx, filename) + s.logger.Debug("downloading one block", zap.String("file_name", filename)) + if err != nil { + continue + } + defer out.Close() + + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + + data, err = io.ReadAll(out) + if err != nil { + continue + } + + data, err = fixLegacyBlock(data) + if err == nil { + break + } + } + + return +} + +func (s *DStoreIO) NextBundle(ctx context.Context, lowestBaseBlock uint64) (outBaseBlock uint64, lib bstream.BlockRef, err error) { + var lastFound *uint64 + outBaseBlock = lowestBaseBlock + err = s.mergedBlocksStore.WalkFrom(ctx, "", fileNameForBlocksBundle(lowestBaseBlock), func(filename string) error { + num, err := strconv.ParseUint(filename, 10, 64) + if err != nil { + return err + } + + if num != outBaseBlock { + return fmt.Errorf("%w: merged blocks skip from %d to %d, you need to fill this hole, set firstStreamableBlock above this hole or set merger option to ignore holes", ErrHoleFound, outBaseBlock, num) + } + outBaseBlock += s.bundleSize + lastFound = &num + return nil + }) + + if lastFound != nil { + last, lastTime, err := s.readLastBlockFromMerged(ctx, *lastFound) + if err != nil { + return 0, nil, err + } + metrics.HeadBlockTimeDrift.SetBlockTime(*lastTime) + metrics.HeadBlockNumber.SetUint64(last.Num()) + lib = last + } + + return +} + +func (s *DStoreIO) readLastBlockFromMerged(ctx context.Context, baseBlock uint64) (bstream.BlockRef, *time.Time, error) { + subCtx, cancel := context.WithTimeout(ctx, GetObjectTimeout) + defer cancel() + reader, err := s.mergedBlocksStore.OpenObject(subCtx, fileNameForBlocksBundle(baseBlock)) + if err != nil { + return nil, nil, err + } + last, err := lastBlock(reader) + if err != nil { + return nil, nil, err + } + // we truncate the block ID to have the short version that we get on oneBlockFiles + t := last.Timestamp.AsTime() + return bstream.NewBlockRef(bstream.TruncateBlockID(last.Id), last.Number), &t, nil +} + +func (s *DStoreIO) DeleteAsync(oneBlockFiles []*bstream.OneBlockFile) error { + return s.od.Delete(oneBlockFiles) +} + +func (s *ForkAwareDStoreIO) MoveForkedBlocks(ctx context.Context, oneBlockFiles []*bstream.OneBlockFile) { + for _, f := range oneBlockFiles { + for name := range f.Filenames { + reader, err := s.oneBlocksStore.OpenObject(ctx, name) + if err != nil { + s.logger.Warn("could not copy forked block", zap.Error(err)) + continue + } + err = s.forkedBlocksStore.WriteObject(ctx, name, reader) + if err != nil { + s.logger.Warn("could not copy forked block", zap.Error(err)) + continue + } + reader.Close() + break + } + } + _ = s.od.Delete(oneBlockFiles) +} + +func (s *ForkAwareDStoreIO) DeleteForkedBlocksAsync(inclusiveLowBoundary, inclusiveHighBoundary uint64) { + var forkedBlockFiles []*bstream.OneBlockFile + err := s.forkedBlocksStore.WalkFrom(context.Background(), "", "", func(filename string) error { + if strings.HasSuffix(filename, ".tmp") { + return nil + } + obf := bstream.MustNewOneBlockFile(filename) + if obf.Num > inclusiveHighBoundary { + return io.EOF + } + forkedBlockFiles = append(forkedBlockFiles, obf) + return nil + }) + + if err != nil && err != io.EOF { + s.logger.Warn("cannot walk forked block files to delete old ones", + zap.Uint64("inclusive_low_boundary", inclusiveLowBoundary), + zap.Uint64("inclusive_high_boundary", inclusiveHighBoundary), + zap.Error(err), + ) + } + + s.forkOd.Delete(forkedBlockFiles) +} + +type oneBlockFilesDeleter struct { + sync.Mutex + toProcess chan string + retryAttempts int + retryCooldown time.Duration + store dstore.Store + logger *zap.Logger +} + +func (od *oneBlockFilesDeleter) Start(threads int, maxDeletions int) { + od.toProcess = make(chan string, maxDeletions) + for i := 0; i < threads; i++ { + go od.processDeletions() + } +} + +func (od *oneBlockFilesDeleter) Delete(oneBlockFiles []*bstream.OneBlockFile) error { + od.Lock() + defer od.Unlock() + + if len(oneBlockFiles) == 0 { + return nil + } + + var fileNames []string + for _, oneBlockFile := range oneBlockFiles { + for filename := range oneBlockFile.Filenames { + fileNames = append(fileNames, filename) + } + } + od.logger.Info("deleting a bunch of one_block_files", zap.Int("number_of_files", len(fileNames)), zap.String("first_file", fileNames[0]), zap.String("last_file", fileNames[len(fileNames)-1]), zap.Stringer("store", od.store.BaseURL())) + + deletable := make(map[string]bool) + + for _, f := range fileNames { + deletable[f] = true + } + + // dedupe processing queue + for empty := false; !empty; { + select { + case f := <-od.toProcess: + deletable[f] = true + default: + empty = true + } + } + + var deletableArr []string + for file := range deletable { + deletableArr = append(deletableArr, file) + } + sort.Strings(deletableArr) + + var err error + for _, file := range deletableArr { + if len(od.toProcess) == cap(od.toProcess) { + od.logger.Warn("skipping file deletions: the channel is full", zap.Int("capacity", cap(od.toProcess))) + err = fmt.Errorf("skipped some files") + break + } + od.toProcess <- file + } + return err +} + +func (od *oneBlockFilesDeleter) processDeletions() { + for { + file := <-od.toProcess + err := Retry(od.logger, od.retryAttempts, od.retryCooldown, func() error { + ctx, cancel := context.WithTimeout(context.Background(), DeleteObjectTimeout) + defer cancel() + err := od.store.DeleteObject(ctx, file) + if errors.Is(err, dstore.ErrNotFound) { + return nil + } + return err + }) + if err != nil { + od.logger.Warn("cannot delete oneblock file after a few retries", zap.String("file", file), zap.Error(err)) + } + } +} + +func lastBlock(mergeFileReader io.ReadCloser) (out *pbbstream.Block, err error) { + defer mergeFileReader.Close() + + blkReader, err := bstream.NewDBinBlockReader(mergeFileReader) + if err != nil { + return nil, err + } + + for { + block, err := blkReader.Read() + if block != nil { + out = block + } + if err != nil { + if err == io.EOF { + break + } + return nil, err + } + } + + return out, nil +} diff --git a/firehose/firehose-core/merger/merger_io_test.go b/firehose/firehose-core/merger/merger_io_test.go new file mode 100644 index 0000000..a5f0107 --- /dev/null +++ b/firehose/firehose-core/merger/merger_io_test.go @@ -0,0 +1,235 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "bytes" + "context" + "io" + "testing" + "time" + + "google.golang.org/protobuf/types/known/anypb" + + "github.com/streamingfast/firehose-core/test" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dstore" + "github.com/stretchr/testify/require" +) + +func TestNewDstore(t *testing.T) { + store := NewDStoreIO( + testLogger, + testTracer, + dstore.NewMockStore(nil), + dstore.NewMockStore(nil), + dstore.NewMockStore(nil), + 1, + 0, + 100, + 0, + ) + + _, ok := store.(ForkAwareIOInterface) + require.True(t, ok) + + // non-fork-aware + store = NewDStoreIO( + testLogger, + testTracer, + dstore.NewMockStore(nil), + dstore.NewMockStore(nil), + nil, + 1, + 0, + 100, + 0, + ) + + _, ok = store.(ForkAwareIOInterface) + require.False(t, ok) +} + +func newTestDStoreIO( + oneBlocksStore dstore.Store, + mergedBlocksStore dstore.Store, +) IOInterface { + return NewDStoreIO(testLogger, testTracer, oneBlocksStore, mergedBlocksStore, nil, 0, 0, 100, 0) +} + +func TestMergerIO_MergeUploadPerfect(t *testing.T) { + files := []*bstream.OneBlockFile{ + block100(), + block101(), + } + var mergeLastBase string + var filesRead []string + var mergeCounter int + done := make(chan struct{}) + + oneBlockStore := dstore.NewMockStore(nil) + + oneBlockStore.OpenObjectFunc = func(_ context.Context, name string) (io.ReadCloser, error) { + filesRead = append(filesRead, name) + if len(filesRead) == 2 { + close(done) + } + + tb := &test.Block{ + Number: 9999, + } + anyB, err := anypb.New(tb) + require.NoError(t, err) + + pbb := &pbbstream.Block{ + Number: 9999, + Payload: anyB, + } + out := new(bytes.Buffer) + w, err := bstream.NewDBinBlockWriter(out) + require.NoError(t, err) + + err = w.Write(pbb) + require.NoError(t, err) + + return io.NopCloser(out), nil + } + mergedBlocksStore := dstore.NewMockStore( + func(base string, f io.Reader) (err error) { + mergeLastBase = base + mergeCounter++ + return nil + }, + ) + + mio := newTestDStoreIO(oneBlockStore, mergedBlocksStore) + + err := mio.MergeAndStore(context.Background(), 100, files) + require.NoError(t, err) + require.Equal(t, mergeCounter, 1) + require.Equal(t, mergeLastBase, "0000000100") + + expectFilenames := []string{ + "0000000100-0000000000000100a-0000000000000099a-98-suffix", + "0000000101-0000000000000101a-0000000000000100a-99-suffix", + } + + select { + case <-time.After(time.Second): + t.Error("timeout waiting for read", filesRead) + case <-done: + require.Equal(t, expectFilenames, filesRead) + } +} + +func TestMergerIO_MergeUploadFiltered(t *testing.T) { + files := []*bstream.OneBlockFile{ + block98(), + block99(), + block100(), + block101(), + } + + var mergeLastBase string + var filesRead []string + var mergeCounter int + done := make(chan struct{}) + + oneBlockStore := dstore.NewMockStore(nil) + oneBlockStore.OpenObjectFunc = func(_ context.Context, name string) (io.ReadCloser, error) { + filesRead = append(filesRead, name) + if len(filesRead) == 2 { + close(done) + } + tb := &test.Block{ + Number: 9999, + } + anyB, err := anypb.New(tb) + require.NoError(t, err) + + pbb := &pbbstream.Block{ + Number: 9999, + Payload: anyB, + } + out := new(bytes.Buffer) + w, err := bstream.NewDBinBlockWriter(out) + require.NoError(t, err) + + err = w.Write(pbb) + require.NoError(t, err) + + return io.NopCloser(out), nil + + } + mergedBlocksStore := dstore.NewMockStore( + func(base string, f io.Reader) (err error) { + mergeLastBase = base + mergeCounter++ + return nil + }, + ) + + mio := newTestDStoreIO(oneBlockStore, mergedBlocksStore) + + err := mio.MergeAndStore(context.Background(), 100, files) + require.NoError(t, err) + require.Equal(t, mergeCounter, 1) + require.Equal(t, mergeLastBase, "0000000100") + + expectFilenames := []string{ + "0000000098-0000000000000098a-0000000000000097a-96-suffix", // read header + // 99 not read + "0000000100-0000000000000100a-0000000000000099a-98-suffix", + "0000000101-0000000000000101a-0000000000000100a-99-suffix", + } + + select { + case <-time.After(time.Second): + t.Error("timeout waiting for read", filesRead) + case <-done: + require.Equal(t, expectFilenames, filesRead) + } +} + +func TestMergerIO_MergeUploadNoFiles(t *testing.T) { + files := []*bstream.OneBlockFile{} + + oneBlockStore := dstore.NewMockStore(nil) + mergedBlocksStore := dstore.NewMockStore(nil) + mio := newTestDStoreIO(oneBlockStore, mergedBlocksStore) + + err := mio.MergeAndStore(context.Background(), 114, files) + require.Error(t, err) +} +func TestMergerIO_MergeUploadFilteredToZero(t *testing.T) { + b100 := block102Final100() + b101 := block103Final101() + files := []*bstream.OneBlockFile{ + b100, + b101, + } + oneBlockStore := dstore.NewMockStore(nil) + mergedBlocksStore := dstore.NewMockStore(nil) + mio := newTestDStoreIO(oneBlockStore, mergedBlocksStore) + + b100.MemoizeData = append(testOneBlockHeader, []byte{0x0, 0x1, 0x2, 0x3}...) + b101.MemoizeData = append(testOneBlockHeader, []byte{0x0, 0x1, 0x2, 0x3}...) + + err := mio.MergeAndStore(context.Background(), 114, files) + require.NoError(t, err) +} diff --git a/firehose/firehose-core/merger/metrics/metrics.go b/firehose/firehose-core/merger/metrics/metrics.go new file mode 100644 index 0000000..8dae3f8 --- /dev/null +++ b/firehose/firehose-core/merger/metrics/metrics.go @@ -0,0 +1,23 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import "github.com/streamingfast/dmetrics" + +var MetricSet = dmetrics.NewSet() + +var HeadBlockTimeDrift = MetricSet.NewHeadTimeDrift("merger") +var HeadBlockNumber = MetricSet.NewHeadBlockNumber("merger") +var AppReadiness = MetricSet.NewAppReadiness("merger") diff --git a/firehose/firehose-core/merger/server.go b/firehose/firehose-core/merger/server.go new file mode 100644 index 0000000..a4fa455 --- /dev/null +++ b/firehose/firehose-core/merger/server.go @@ -0,0 +1,21 @@ +package merger + +import ( + dgrpcfactory "github.com/streamingfast/dgrpc/server/factory" + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +func (m *Merger) startGRPCServer() { + gs := dgrpcfactory.ServerFromOptions() + gs.OnTerminated(m.Shutdown) + m.logger.Info("grpc server created") + + m.OnTerminated(func(_ error) { + gs.Shutdown(0) + }) + pbhealth.RegisterHealthServer(gs.ServiceRegistrar(), m) + m.logger.Info("server registered") + + go gs.Launch(m.grpcListenAddr) + +} diff --git a/firehose/firehose-core/merger/test_data/0000000001-20150730T152628.0-13406cb6-b1cb8fa3.dbin b/firehose/firehose-core/merger/test_data/0000000001-20150730T152628.0-13406cb6-b1cb8fa3.dbin new file mode 100644 index 0000000..265a969 Binary files /dev/null and b/firehose/firehose-core/merger/test_data/0000000001-20150730T152628.0-13406cb6-b1cb8fa3.dbin differ diff --git a/firehose/firehose-core/merger/test_data/0000000002-20150730T152657.0-044698c9-13406cb6.dbin b/firehose/firehose-core/merger/test_data/0000000002-20150730T152657.0-044698c9-13406cb6.dbin new file mode 100644 index 0000000..b69cb35 Binary files /dev/null and b/firehose/firehose-core/merger/test_data/0000000002-20150730T152657.0-044698c9-13406cb6.dbin differ diff --git a/firehose/firehose-core/merger/test_data/0000000003-20150730T152728.0-a88cf741-044698c9.dbin b/firehose/firehose-core/merger/test_data/0000000003-20150730T152728.0-a88cf741-044698c9.dbin new file mode 100644 index 0000000..1c3952d Binary files /dev/null and b/firehose/firehose-core/merger/test_data/0000000003-20150730T152728.0-a88cf741-044698c9.dbin differ diff --git a/firehose/firehose-core/merger/utils.go b/firehose/firehose-core/merger/utils.go new file mode 100644 index 0000000..9e6cfd2 --- /dev/null +++ b/firehose/firehose-core/merger/utils.go @@ -0,0 +1,95 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package merger + +import ( + "context" + "fmt" + "time" + + "github.com/streamingfast/bstream" + "go.uber.org/zap" + "gopkg.in/olivere/elastic.v3/backoff" +) + +func fileNameForBlocksBundle(blockNum uint64) string { + return fmt.Sprintf("%010d", blockNum) +} + +func toBaseNum(in uint64, bundleSize uint64) uint64 { + return in / bundleSize * bundleSize +} + +func Retry(logger *zap.Logger, attempts int, sleep time.Duration, function func() error) (err error) { + b := backoff.NewExponentialBackoff(sleep, 5*time.Second) + for i := 0; ; i++ { + err = function() + if err == nil { + return + } + + if i >= (attempts - 1) { + break + } + + time.Sleep(b.Next()) + + logger.Warn("retrying after error", zap.Error(err)) + } + return fmt.Errorf("after %d attempts, last error: %s", attempts, err) +} + +type TestMergerIO struct { + NextBundleFunc func(ctx context.Context, lowestBaseBlock uint64) (baseBlock uint64, lastIrreversibleBlock bstream.BlockRef, err error) + WalkOneBlockFilesFunc func(ctx context.Context, inclusiveLowerBlock uint64, callback func(*bstream.OneBlockFile) error) error + MergeAndStoreFunc func(ctx context.Context, inclusiveLowerBlock uint64, oneBlockFiles []*bstream.OneBlockFile) (err error) + DownloadOneBlockFileFunc func(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) + DeleteAsyncFunc func(oneBlockFiles []*bstream.OneBlockFile) error +} + +func (io *TestMergerIO) NextBundle(ctx context.Context, lowestBaseBlock uint64) (baseBlock uint64, lastIrreversibleBlock bstream.BlockRef, err error) { + if io.NextBundleFunc != nil { + return io.NextBundleFunc(ctx, lowestBaseBlock) + } + return lowestBaseBlock, nil, nil +} + +func (io *TestMergerIO) MergeAndStore(ctx context.Context, inclusiveLowerBlock uint64, oneBlockFiles []*bstream.OneBlockFile) (err error) { + if io.MergeAndStoreFunc != nil { + return io.MergeAndStoreFunc(ctx, inclusiveLowerBlock, oneBlockFiles) + } + return nil +} + +func (io *TestMergerIO) DownloadOneBlockFile(ctx context.Context, oneBlockFile *bstream.OneBlockFile) (data []byte, err error) { + if io.DownloadOneBlockFileFunc != nil { + return io.DownloadOneBlockFileFunc(ctx, oneBlockFile) + } + + return nil, nil +} + +func (io *TestMergerIO) WalkOneBlockFiles(ctx context.Context, inclusiveLowerBlock uint64, callback func(*bstream.OneBlockFile) error) error { + if io.WalkOneBlockFilesFunc != nil { + return io.WalkOneBlockFilesFunc(ctx, inclusiveLowerBlock, callback) + } + return nil +} +func (io *TestMergerIO) DeleteAsync(oneBlockFiles []*bstream.OneBlockFile) error { + if io.DeleteAsyncFunc != nil { + return io.DeleteAsyncFunc(oneBlockFiles) + } + return nil +} diff --git a/firehose/firehose-core/metrics.go b/firehose/firehose-core/metrics.go new file mode 100644 index 0000000..d8e30d8 --- /dev/null +++ b/firehose/firehose-core/metrics.go @@ -0,0 +1,11 @@ +package firecore + +import "github.com/streamingfast/dmetrics" + +func RegisterMetrics() { + metrics.Register() +} + +var metrics = dmetrics.NewSet() + +var ConsoleReaderBlockReadCount = metrics.NewCounter("firecore_console_reader_block_read_count", "Number of blocks read by the console reader") diff --git a/firehose/firehose-core/node-manager/app/node_manager/app.go b/firehose/firehose-core/node-manager/app/node_manager/app.go new file mode 100644 index 0000000..ceff8ae --- /dev/null +++ b/firehose/firehose-core/node-manager/app/node_manager/app.go @@ -0,0 +1,150 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package nodemanager + +import ( + "context" + "fmt" + "net/http" + "os" + "time" + + dgrpcserver "github.com/streamingfast/dgrpc/server" + dgrpcfactory "github.com/streamingfast/dgrpc/server/factory" + "github.com/streamingfast/dmetrics" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + "github.com/streamingfast/firehose-core/node-manager/metrics" + "github.com/streamingfast/firehose-core/node-manager/mindreader" + "github.com/streamingfast/firehose-core/node-manager/operator" + "github.com/streamingfast/shutter" + "go.uber.org/zap" + "google.golang.org/grpc" +) + +type Config struct { + StartupDelay time.Duration + + HTTPAddr string // was ManagerAPIAddress + ConnectionWatchdog bool + + GRPCAddr string +} + +type Modules struct { + Operator *operator.Operator + MetricsAndReadinessManager *nodeManager.MetricsAndReadinessManager + LaunchConnectionWatchdogFunc func(terminating <-chan struct{}) + MindreaderPlugin *mindreader.MindReaderPlugin + RegisterGRPCService func(server grpc.ServiceRegistrar) error + StartFailureHandlerFunc func() +} + +type App struct { + *shutter.Shutter + config *Config + modules *Modules + zlogger *zap.Logger +} + +func New(config *Config, modules *Modules, zlogger *zap.Logger) *App { + return &App{ + Shutter: shutter.New(), + config: config, + modules: modules, + zlogger: zlogger, + } +} + +func (a *App) Run() error { + hasMindreader := a.modules.MindreaderPlugin != nil + a.zlogger.Info("running node manager app", zap.Reflect("config", a.config), zap.Bool("mindreader", hasMindreader)) + + hostname, _ := os.Hostname() + a.zlogger.Info("retrieved hostname from os", zap.String("hostname", hostname)) + + dmetrics.Register(metrics.Metricset) + + a.OnTerminating(func(err error) { + a.modules.Operator.Shutdown(err) + <-a.modules.Operator.Terminated() + }) + + a.modules.Operator.OnTerminated(func(err error) { + a.zlogger.Info("chain operator terminated shutting down mindreader app") + a.Shutdown(err) + }) + + if a.config.StartupDelay != 0 { + time.Sleep(a.config.StartupDelay) + } + + var httpOptions []operator.HTTPOption + if hasMindreader { + if err := a.startMindreader(); err != nil { + return fmt.Errorf("unable to start mindreader: %w", err) + } + + } + + a.zlogger.Info("launching operator") + go a.modules.MetricsAndReadinessManager.Launch() + go a.Shutdown(a.modules.Operator.Launch(a.config.HTTPAddr, httpOptions...)) + + if a.config.ConnectionWatchdog { + go a.modules.LaunchConnectionWatchdogFunc(a.Terminating()) + } + + return nil +} + +func (a *App) IsReady() bool { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + url := fmt.Sprintf("http://%s/healthz", a.config.HTTPAddr) + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + a.zlogger.Warn("unable to build get health request", zap.Error(err)) + return false + } + + client := http.DefaultClient + res, err := client.Do(req) + if err != nil { + a.zlogger.Debug("unable to execute get health request", zap.Error(err)) + return false + } + + return res.StatusCode == 200 +} + +func (a *App) startMindreader() error { + a.zlogger.Info("starting mindreader gRPC server") + gs := dgrpcfactory.ServerFromOptions(dgrpcserver.WithLogger(a.zlogger)) + + if a.modules.RegisterGRPCService != nil { + err := a.modules.RegisterGRPCService(gs.ServiceRegistrar()) + if err != nil { + return fmt.Errorf("register extra grpc service: %w", err) + } + } + + gs.OnTerminated(a.Shutdown) + + // Launch is blocking and we don't want to block in this method + go gs.Launch(a.config.GRPCAddr) + + return nil +} diff --git a/firehose/firehose-core/node-manager/app/node_reader_stdin/app.go b/firehose/firehose-core/node-manager/app/node_reader_stdin/app.go new file mode 100644 index 0000000..29e4792 --- /dev/null +++ b/firehose/firehose-core/node-manager/app/node_reader_stdin/app.go @@ -0,0 +1,172 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node_reader_stdin + +import ( + "bufio" + "fmt" + "os" + + "github.com/streamingfast/bstream/blockstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + dgrpcserver "github.com/streamingfast/dgrpc/server" + dgrpcfactory "github.com/streamingfast/dgrpc/server/factory" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + logplugin "github.com/streamingfast/firehose-core/node-manager/log_plugin" + "github.com/streamingfast/firehose-core/node-manager/mindreader" + "github.com/streamingfast/logging" + pbheadinfo "github.com/streamingfast/pbgo/sf/headinfo/v1" + "github.com/streamingfast/shutter" + "go.uber.org/zap" + "google.golang.org/grpc" +) + +type Config struct { + GRPCAddr string + OneBlocksStoreURL string + OneBlockSuffix string + MindReadBlocksChanCapacity int + StartBlockNum uint64 + StopBlockNum uint64 + WorkingDir string + LogToZap bool + DebugDeepMind bool + + // MaxLineLengthInBytes configures the maximum bytes a single line consumed can be + // without any error. If left unspecified or 0, the default is 50 MiB (50 * 1024 * 1024). + MaxLineLengthInBytes int64 +} + +type Modules struct { + ConsoleReaderFactory mindreader.ConsolerReaderFactory + MetricsAndReadinessManager *nodeManager.MetricsAndReadinessManager + RegisterGRPCService func(server grpc.ServiceRegistrar) error +} + +type App struct { + *shutter.Shutter + Config *Config + ReadyFunc func() + modules *Modules + zlogger *zap.Logger + tracer logging.Tracer +} + +func New(c *Config, modules *Modules, zlogger *zap.Logger, tracer logging.Tracer) *App { + n := &App{ + Shutter: shutter.New(), + Config: c, + ReadyFunc: func() {}, + modules: modules, + zlogger: zlogger, + tracer: tracer, + } + return n +} + +func (a *App) Run() error { + a.zlogger.Info("launching reader-node app (reading from stdin)", zap.Reflect("config", a.Config)) + + gs := dgrpcfactory.ServerFromOptions(dgrpcserver.WithLogger(a.zlogger)) + + blockStreamServer := blockstream.NewUnmanagedServer( + blockstream.ServerOptionWithLogger(a.zlogger), + blockstream.ServerOptionWithBuffer(1), + ) + + a.zlogger.Info("launching reader log plugin") + mindreaderLogPlugin, err := mindreader.NewMindReaderPlugin( + a.Config.OneBlocksStoreURL, + a.Config.WorkingDir, + a.modules.ConsoleReaderFactory, + a.Config.StartBlockNum, + a.Config.StopBlockNum, + a.Config.MindReadBlocksChanCapacity, + a.modules.MetricsAndReadinessManager.UpdateHeadBlock, + func(_ error) {}, + a.Config.OneBlockSuffix, + blockStreamServer, + a.zlogger, + a.tracer, + ) + if err != nil { + return err + } + + a.zlogger.Debug("configuring shutter") + mindreaderLogPlugin.OnTerminated(a.Shutdown) + a.OnTerminating(mindreaderLogPlugin.Shutdown) + + serviceRegistrar := gs.ServiceRegistrar() + pbheadinfo.RegisterHeadInfoServer(serviceRegistrar, blockStreamServer) + pbbstream.RegisterBlockStreamServer(serviceRegistrar, blockStreamServer) + + if a.modules.RegisterGRPCService != nil { + err := a.modules.RegisterGRPCService(gs.ServiceRegistrar()) + if err != nil { + return fmt.Errorf("register extra grpc service: %w", err) + } + } + gs.OnTerminated(a.Shutdown) + go gs.Launch(a.Config.GRPCAddr) + + a.zlogger.Debug("running reader log plugin") + mindreaderLogPlugin.Launch() + go a.modules.MetricsAndReadinessManager.Launch() + + var logPlugin *logplugin.ToZapLogPlugin + if a.Config.LogToZap { + logPlugin = logplugin.NewToZapLogPlugin(a.Config.DebugDeepMind, a.zlogger) + } + + maxLineLength := a.Config.MaxLineLengthInBytes + if maxLineLength == 0 { + maxLineLength = 50 * 1024 * 1024 + } + + scanner := bufio.NewScanner(os.Stdin) + scanner.Buffer(make([]byte, int(maxLineLength)), int(maxLineLength)) + + go func() { + a.zlogger.Info("starting stdin consumption loop") + for scanner.Scan() { + line := scanner.Text() + + if logPlugin != nil { + logPlugin.LogLine(line) + } + + mindreaderLogPlugin.LogLine(line) + } + + if err := scanner.Err(); err != nil { + a.zlogger.Error("got an error from while trying to read a line", zap.Error(err)) + mindreaderLogPlugin.Shutdown(err) + return + } + + a.zlogger.Info("done reading from stdin") + }() + + return nil +} + +func (a *App) OnReady(f func()) { + a.ReadyFunc = f +} + +func (a *App) IsReady() bool { + return true +} diff --git a/firehose/firehose-core/node-manager/log_plugin/keep_last_lines_log_plugin.go b/firehose/firehose-core/node-manager/log_plugin/keep_last_lines_log_plugin.go new file mode 100644 index 0000000..2ba73c1 --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/keep_last_lines_log_plugin.go @@ -0,0 +1,60 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logplugin + +import ( + "github.com/streamingfast/shutter" +) + +// KeepLastLinesLogPlugin takes a line and keep the last N lines as requested by the caller. +type KeepLastLinesLogPlugin struct { + *shutter.Shutter + lastLines *lineRingBuffer + includeDeepMindLines bool +} + +func NewKeepLastLinesLogPlugin(lineCount int, includeDeepMindLines bool) *KeepLastLinesLogPlugin { + plugin := &KeepLastLinesLogPlugin{ + Shutter: shutter.New(), + lastLines: &lineRingBuffer{maxCount: lineCount}, + includeDeepMindLines: includeDeepMindLines, + } + + return plugin +} +func (p *KeepLastLinesLogPlugin) Name() string { + return "KeepLastLinesLogPlugin" +} +func (p *KeepLastLinesLogPlugin) Launch() {} +func (p KeepLastLinesLogPlugin) Stop() {} +func (p *KeepLastLinesLogPlugin) DebugDeepMind(enabled bool) { + p.includeDeepMindLines = enabled +} + +func (p *KeepLastLinesLogPlugin) LastLines() []string { + return p.lastLines.lines() +} + +//func (p *KeepLastLinesLogPlugin) Close(_ error) { +//} + +func (p *KeepLastLinesLogPlugin) LogLine(in string) { + if readerInstrumentationPrefixRegex.MatchString(in) && !p.includeDeepMindLines { + // It's a deep mind log line and we don't care about it, skip + return + } + + p.lastLines.append(in) +} diff --git a/firehose/firehose-core/node-manager/log_plugin/keep_last_lines_log_plugin_test.go b/firehose/firehose-core/node-manager/log_plugin/keep_last_lines_log_plugin_test.go new file mode 100644 index 0000000..7f91e02 --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/keep_last_lines_log_plugin_test.go @@ -0,0 +1,54 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logplugin + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestKeepLastLinesLogPlugin(t *testing.T) { + tests := []struct { + name string + in []string + maxLine int + includeDeepMindLines bool + out []string + }{ + {"empty", []string{}, 3, false, nil}, + {"single, not reached", []string{"a"}, 3, false, []string{"a"}}, + {"flush, not reached", []string{"a", "b", "c"}, 3, false, []string{"a", "b", "c"}}, + {"over, count", []string{"a", "b", "c", "d"}, 3, false, []string{"b", "c", "d"}}, + {"multiple over count", []string{"a", "b", "c", "d", "e", "f", "g"}, 3, false, []string{"e", "f", "g"}}, + + {"max count 0 keeps nothing", []string{"a", "b", "c", "d", "e", "f", "g"}, 0, false, nil}, + + {"dm exclude, multiple over count", []string{"a", "b", "DMLOG a", "c", "d", "e", "f", "g", "DMLOG b"}, 3, false, []string{"e", "f", "g"}}, + {"dm include, multiple over count", []string{"a", "b", "DMLOG a", "c", "d", "e", "f", "g", "DMLOG b"}, 3, true, []string{"f", "g", "DMLOG b"}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + plugin := NewKeepLastLinesLogPlugin(test.maxLine, test.includeDeepMindLines) + + for _, line := range test.in { + plugin.LogLine(line) + } + + assert.Equal(t, test.out, plugin.LastLines()) + }) + } +} diff --git a/firehose/firehose-core/node-manager/log_plugin/line_ring_buffer.go b/firehose/firehose-core/node-manager/log_plugin/line_ring_buffer.go new file mode 100644 index 0000000..a0efb0d --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/line_ring_buffer.go @@ -0,0 +1,60 @@ +package logplugin + +type bufferElement struct { + previous *bufferElement + next *bufferElement + line string +} + +type lineRingBuffer struct { + maxCount int + + count int + tail *bufferElement + head *bufferElement +} + +func (b *lineRingBuffer) lines() (out []string) { + if b.count == 0 { + return nil + } + + if b.count == 1 { + return []string{b.head.line} + } + + i := 0 + out = make([]string, b.count) + for current := b.tail; current != nil; current = current.next { + out[i] = current.line + i++ + } + + return +} + +func (b *lineRingBuffer) append(line string) { + // If we keep nothing, there is nothing to do here + if b.maxCount == 0 { + return + } + + oldHead := b.head + b.head = &bufferElement{line: line, previous: oldHead} + + if oldHead != nil { + oldHead.next = b.head + } + + if b.tail == nil { + b.tail = b.head + } + + if b.count == b.maxCount { + // We are full, we need to rotate stuff a bit + b.tail = b.tail.next + } else { + // We are not full, let's just append a new line (so only update count) + b.count++ + } +} diff --git a/firehose/firehose-core/node-manager/log_plugin/log_plugin.go b/firehose/firehose-core/node-manager/log_plugin/log_plugin.go new file mode 100644 index 0000000..0a5458e --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/log_plugin.go @@ -0,0 +1,68 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logplugin + +import ( + "regexp" + + "github.com/streamingfast/bstream/blockstream" +) + +var readerInstrumentationPrefixRegex = regexp.MustCompile("^(DMLOG|FIRE) ") + +type LogPlugin interface { + Name() string + Launch() + LogLine(in string) + //Close(err error) + Shutdown(err error) + IsTerminating() bool + Stop() +} + +type Shutter interface { + Terminated() <-chan struct{} + OnTerminating(f func(error)) + OnTerminated(f func(error)) + IsTerminating() bool + Shutdown(err error) +} + +type BlockStreamer interface { + Run(blockServer *blockstream.Server) +} + +type LogPluginFunc func(line string) + +func (f LogPluginFunc) Launch() {} +func (f LogPluginFunc) LogLine(line string) { f(line) } +func (f LogPluginFunc) Name() string { return "log plug func" } +func (f LogPluginFunc) Stop() {} +func (f LogPluginFunc) Shutdown(_ error) {} +func (f LogPluginFunc) Terminated() <-chan struct{} { + ch := make(chan struct{}) + close(ch) + return ch +} + +func (f LogPluginFunc) IsTerminating() bool { + return false +} + +func (f LogPluginFunc) OnTerminating(_ func(error)) { +} + +func (f LogPluginFunc) OnTerminated(_ func(error)) { +} diff --git a/firehose/firehose-core/node-manager/log_plugin/to_console_log_plugin.go b/firehose/firehose-core/node-manager/log_plugin/to_console_log_plugin.go new file mode 100644 index 0000000..32f1410 --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/to_console_log_plugin.go @@ -0,0 +1,80 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logplugin + +import ( + "fmt" + "os" + "strconv" + + "github.com/streamingfast/shutter" +) + +var DebugLineLength = int64(4096) + +func init() { + if os.Getenv("DEBUG_LINE_LENGTH") != "" { + value, err := strconv.ParseInt(os.Getenv("DEBUG_LINE_LENGTH"), 10, 64) + if err == nil { + DebugLineLength = value + } + } +} + +// ToConsoleLogPlugin takes a line, and if it's not a FIRE (or DMLOG) line or +// if we are actively debugging deep mind, will print the line to the +// standard output +type ToConsoleLogPlugin struct { + *shutter.Shutter + debugDeepMind bool + skipBlankLines bool +} + +func NewToConsoleLogPlugin(debugDeepMind bool) *ToConsoleLogPlugin { + return &ToConsoleLogPlugin{ + Shutter: shutter.New(), + debugDeepMind: debugDeepMind, + } +} + +func (p *ToConsoleLogPlugin) SetSkipBlankLines(skip bool) { + p.skipBlankLines = skip +} + +func (p *ToConsoleLogPlugin) Launch() {} +func (p ToConsoleLogPlugin) Stop() {} +func (p *ToConsoleLogPlugin) Name() string { + return "ToConsoleLogPlugin" +} +func (p *ToConsoleLogPlugin) DebugDeepMind(enabled bool) { + p.debugDeepMind = enabled +} + +func (p *ToConsoleLogPlugin) LogLine(in string) { + if in == "" && p.skipBlankLines { + return + } + + if p.debugDeepMind || !readerInstrumentationPrefixRegex.MatchString(in) { + logLineLength := int64(len(in)) + + // We really want to write lines to stdout and not through our logger, it's the purpose of our plugin! + if logLineLength > DebugLineLength { + fmt.Printf("%s ... bytes: %d\n", in[:DebugLineLength], (logLineLength - DebugLineLength)) + } else { + fmt.Println(in) + } + } +} diff --git a/firehose/firehose-core/node-manager/log_plugin/to_zap_log_plugin.go b/firehose/firehose-core/node-manager/log_plugin/to_zap_log_plugin.go new file mode 100644 index 0000000..65a1165 --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/to_zap_log_plugin.go @@ -0,0 +1,130 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logplugin + +import ( + "github.com/streamingfast/shutter" + + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var NoDisplay = zapcore.Level(zap.FatalLevel + 10) + +type ToZapLogPluginOption interface { + apply(p *ToZapLogPlugin) +} + +type toZapLogPluginOptionFunc func(p *ToZapLogPlugin) + +func (s toZapLogPluginOptionFunc) apply(p *ToZapLogPlugin) { + s(p) +} + +// ToZapLogPluginLogLevel is the option that defines which function to use to extract the log level +// from the line. +// +// The received function will be invoked with the actual line to log. The function should then return +// the log level value to use for this line. If the return value is the special value `NoDisplay` constant +// (which corresponds to log level `15` which does not exist within zap), the line is actually discarded +// completely and not logged to the logger. +func ToZapLogPluginLogLevel(extractLevel func(in string) zapcore.Level) ToZapLogPluginOption { + return toZapLogPluginOptionFunc(func(p *ToZapLogPlugin) { + p.levelExtractor = extractLevel + }) +} + +// ToZapLogPluginTransformer is the option that defines which function to use to transform the line before +// being logged to the logger. +// +// The received function will be invoked with the actual line to log **after** the level have been determined. +// The function should then return the transformed line. If the return line is the empty string, it is discarded +// completely. +func ToZapLogPluginTransformer(transformer func(in string) string) ToZapLogPluginOption { + return toZapLogPluginOptionFunc(func(p *ToZapLogPlugin) { + p.lineTransformer = transformer + }) +} + +// ToZapLogPlugin takes a line, and if it's not a FIRE (or DMLOG) line or +// if we are actively debugging deep mind, will print the line to received +// logger instance. +type ToZapLogPlugin struct { + *shutter.Shutter + + logger *zap.Logger + debugDeepMind bool + + levelExtractor func(in string) zapcore.Level + lineTransformer func(in string) string +} + +func NewToZapLogPlugin(debugDeepMind bool, logger *zap.Logger, options ...ToZapLogPluginOption) *ToZapLogPlugin { + plugin := &ToZapLogPlugin{ + Shutter: shutter.New(), + debugDeepMind: debugDeepMind, + logger: logger, + } + + for _, opt := range options { + opt.apply(plugin) + } + + return plugin +} + +func (p *ToZapLogPlugin) Launch() {} +func (p ToZapLogPlugin) Stop() {} + +func (p *ToZapLogPlugin) Name() string { + return "ToZapLogPlugin" +} + +func (p *ToZapLogPlugin) DebugDeepMind(enabled bool) { + p.debugDeepMind = enabled +} + +//func (p *ToZapLogPlugin) Close(_ error) { +//} + +func (p *ToZapLogPlugin) LogLine(in string) { + if readerInstrumentationPrefixRegex.MatchString(in) { + if p.debugDeepMind { + // Needs to be an info since often used in production where debug level is not enabled by default + p.logger.Info(in) + } + + return + } + + level := zap.DebugLevel + if p.levelExtractor != nil { + level = p.levelExtractor(in) + if level == NoDisplay { + // This is ignored, nothing else to do here ... + return + } + } + + if p.lineTransformer != nil { + in = p.lineTransformer(in) + if in == "" { + // This is ignored, nothing else to do here ... + return + } + } + + p.logger.Check(level, in).Write() +} diff --git a/firehose/firehose-core/node-manager/log_plugin/to_zap_log_plugin_test.go b/firehose/firehose-core/node-manager/log_plugin/to_zap_log_plugin_test.go new file mode 100644 index 0000000..865e545 --- /dev/null +++ b/firehose/firehose-core/node-manager/log_plugin/to_zap_log_plugin_test.go @@ -0,0 +1,141 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package logplugin + +import ( + "strings" + "testing" + + "github.com/streamingfast/logging" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func TestToZapLogPlugin(t *testing.T) { + simpleExtractor := func(in string) zapcore.Level { + if strings.HasPrefix(in, "error") { + return zap.ErrorLevel + } + + if strings.HasPrefix(in, "discard") { + return NoDisplay + } + + return zap.InfoLevel + } + + simpleTransformer := func(in string) string { + if in == "message" { + return in + } + + if in == "discard" { + return "" + } + + return strings.ToUpper(in) + } + + toUnderscoreTransformer := func(in string) string { + return "_" + } + + options := func(opts ...ToZapLogPluginOption) []ToZapLogPluginOption { + return opts + } + + tests := []struct { + name string + in []string + options []ToZapLogPluginOption + out []string + }{ + // Plain + { + "plain, always debug untransformed", + []string{"error message"}, + nil, + []string{`{"level":"debug","msg":"error message"}`}, + }, + + // Log Level + { + "with log leve, match element", + []string{"error message"}, + options(ToZapLogPluginLogLevel(simpleExtractor)), + []string{`{"level":"error","msg":"error message"}`}, + }, + { + "with log leve, no match element", + []string{"warn message"}, + options(ToZapLogPluginLogLevel(simpleExtractor)), + []string{`{"level":"info","msg":"warn message"}`}, + }, + { + "with log leve, no display element", + []string{"discard message"}, + options(ToZapLogPluginLogLevel(simpleExtractor)), + []string(nil), + }, + + // Transformer + { + "with transformer, same", + []string{"message"}, + options(ToZapLogPluginTransformer(simpleTransformer)), + []string{`{"level":"debug","msg":"message"}`}, + }, + { + "with transformer, to upper", + []string{"any"}, + options(ToZapLogPluginTransformer(simpleTransformer)), + []string{`{"level":"debug","msg":"ANY"}`}, + }, + { + "with transformer, discard", + []string{"discard"}, + options(ToZapLogPluginTransformer(simpleTransformer)), + []string(nil), + }, + + // Log Level & Transformer + { + "with transformer & log leve, transform don't affect log level", + []string{"error message"}, + options(ToZapLogPluginTransformer(toUnderscoreTransformer), ToZapLogPluginLogLevel(simpleExtractor)), + []string{`{"level":"error","msg":"_"}`}, + }, + { + "with transformer & log leve, transform don't affect log level, any option order", + []string{"error message"}, + options(ToZapLogPluginLogLevel(simpleExtractor), ToZapLogPluginTransformer(toUnderscoreTransformer)), + []string{`{"level":"error","msg":"_"}`}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testLogger := logging.NewTestLogger(t) + + plugin := NewToZapLogPlugin(false, testLogger.Instance(), test.options...) + for _, in := range test.in { + plugin.LogLine(in) + } + + assert.Equal(t, test.out, testLogger.RecordedLines(t)) + }) + } +} diff --git a/firehose/firehose-core/node-manager/metrics/common.go b/firehose/firehose-core/node-manager/metrics/common.go new file mode 100644 index 0000000..d48249b --- /dev/null +++ b/firehose/firehose-core/node-manager/metrics/common.go @@ -0,0 +1,33 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "github.com/streamingfast/dmetrics" +) + +var Metricset = dmetrics.NewSet() + +func NewHeadBlockTimeDrift(serviceName string) *dmetrics.HeadTimeDrift { + return Metricset.NewHeadTimeDrift(serviceName) +} + +func NewHeadBlockNumber(serviceName string) *dmetrics.HeadBlockNum { + return Metricset.NewHeadBlockNumber(serviceName) +} + +func NewAppReadiness(serviceName string) *dmetrics.AppReadiness { + return Metricset.NewAppReadiness(serviceName) +} diff --git a/firehose/firehose-core/node-manager/mindreader/archiver.go b/firehose/firehose-core/node-manager/mindreader/archiver.go new file mode 100644 index 0000000..fe57702 --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/archiver.go @@ -0,0 +1,115 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mindreader + +import ( + "context" + "fmt" + "io" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dstore" + "github.com/streamingfast/logging" + "github.com/streamingfast/shutter" + "go.uber.org/zap" +) + +type Archiver struct { + *shutter.Shutter + + startBlock uint64 + oneblockSuffix string + + localOneBlocksStore dstore.Store + + fileUploader *FileUploader + logger *zap.Logger + tracer logging.Tracer +} + +func NewArchiver( + startBlock uint64, + oneblockSuffix string, + localOneBlocksStore dstore.Store, + remoteOneBlocksStore dstore.Store, + logger *zap.Logger, + tracer logging.Tracer, +) *Archiver { + + fileUploader := NewFileUploader( + localOneBlocksStore, + remoteOneBlocksStore, + logger) + + a := &Archiver{ + Shutter: shutter.New(), + startBlock: startBlock, + oneblockSuffix: oneblockSuffix, + localOneBlocksStore: localOneBlocksStore, + fileUploader: fileUploader, + logger: logger, + tracer: tracer, + } + + return a +} + +func (a *Archiver) Start(ctx context.Context) { + a.OnTerminating(func(err error) { + a.logger.Info("archiver selector is terminating", zap.Error(err)) + }) + + a.OnTerminated(func(err error) { + a.logger.Info("archiver selector is terminated", zap.Error(err)) + }) + go a.fileUploader.Start(ctx) +} + +func (a *Archiver) StoreBlock(ctx context.Context, block *pbbstream.Block) error { + if block.Number < a.startBlock { + a.logger.Debug("skipping block below start_block", zap.Uint64("block_num", block.Number), zap.Uint64("start_block", a.startBlock)) + return nil + } + + pipeRead, pipeWrite := io.Pipe() + + // We are in a pipe context and `a.blockWriterFactory.New(pipeWrite)` writes some bytes to the writer when called. + // To avoid blocking everything, we must start reading bytes in a goroutine first to ensure the called is not block + // forever because nobody is reading the pipe. + writeObjectErrChan := make(chan error) + go func() { + writeObjectErrChan <- a.localOneBlocksStore.WriteObject(ctx, bstream.BlockFileNameWithSuffix(block, a.oneblockSuffix), pipeRead) + }() + + blockWriter, err := bstream.NewDBinBlockWriter(pipeWrite) + if err != nil { + return fmt.Errorf("write block factory: %w", err) + } + + // If `blockWriter.Write()` emits `nil`, the fact that we close with a `nil` error will actually forwards + // `io.EOF` to the `pipeRead` (e.g. our `WriteObject` call above) which is what we want. If it emits a non + // `nil`, it will be forwarded to the `pipeRead` which is also correct. + pipeWrite.CloseWithError(blockWriter.Write(block)) + + // We are in a pipe context here, wait until the `WriteObject` call has finished + err = <-writeObjectErrChan + if err != nil { + return err + } + + return nil +} diff --git a/firehose/firehose-core/node-manager/mindreader/file_uploader.go b/firehose/firehose-core/node-manager/mindreader/file_uploader.go new file mode 100644 index 0000000..32707ab --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/file_uploader.go @@ -0,0 +1,92 @@ +package mindreader + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/abourget/llerrgroup" + "github.com/streamingfast/dstore" + "github.com/streamingfast/shutter" + "go.uber.org/zap" +) + +type FileUploader struct { + *shutter.Shutter + mutex sync.Mutex + localStore dstore.Store + destinationStore dstore.Store + logger *zap.Logger + complete chan struct{} +} + +func NewFileUploader(localStore dstore.Store, destinationStore dstore.Store, logger *zap.Logger) *FileUploader { + return &FileUploader{ + Shutter: shutter.New(), + complete: make(chan struct{}), + localStore: localStore, + destinationStore: destinationStore, + logger: logger, + } +} + +func (fu *FileUploader) Start(ctx context.Context) { + defer close(fu.complete) + + fu.OnTerminating(func(_ error) { + <-fu.complete + }) + + if fu.IsTerminating() { + return + } + + var terminating bool + for { + err := fu.uploadFiles(ctx) + if err != nil { + fu.logger.Warn("failed to upload file", zap.Error(err)) + } + + if terminating { + return + } + + select { + case <-fu.Terminating(): + fu.logger.Info("terminating upload loop on next pass") + terminating = true + case <-time.After(500 * time.Millisecond): + } + } +} + +func (fu *FileUploader) uploadFiles(ctx context.Context) error { + fu.mutex.Lock() + defer fu.mutex.Unlock() + + eg := llerrgroup.New(200) + _ = fu.localStore.Walk(ctx, "", func(filename string) (err error) { + if eg.Stop() { + return nil + } + eg.Go(func() error { + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() + + if traceEnabled { + fu.logger.Debug("uploading file to storage", zap.String("local_file", filename)) + } + + if err = fu.destinationStore.PushLocalFile(ctx, fu.localStore.ObjectPath(filename), filename); err != nil { + return fmt.Errorf("moving file %q to storage: %w", filename, err) + } + return nil + }) + + return nil + }) + + return eg.Wait() +} diff --git a/firehose/firehose-core/node-manager/mindreader/file_uploader_test.go b/firehose/firehose-core/node-manager/mindreader/file_uploader_test.go new file mode 100644 index 0000000..e268fc4 --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/file_uploader_test.go @@ -0,0 +1,43 @@ +package mindreader + +import ( + "context" + "testing" + "time" + + "github.com/streamingfast/dstore" + "github.com/stretchr/testify/require" +) + +func TestFileUploader(t *testing.T) { + localStore := dstore.NewMockStore(nil) + localStore.SetFile("test1", nil) + localStore.SetFile("test2", nil) + localStore.SetFile("test3", nil) + + destinationStore := dstore.NewMockStore(nil) + + done := make(chan interface{}) + out := make(chan bool, 3) + + destinationStore.PushLocalFileFunc = func(_ context.Context, _, _ string) (err error) { + out <- true + return nil + } + go func() { + for i := 0; i < 3; i++ { + <-out + } + close(done) + }() + + uploader := NewFileUploader(localStore, destinationStore, testLogger) + err := uploader.uploadFiles(context.Background()) + require.NoError(t, err) + + select { + case <-done: + case <-time.After(5 * time.Second): + t.Error("took took long") + } +} diff --git a/firehose/firehose-core/node-manager/mindreader/init_test.go b/firehose/firehose-core/node-manager/mindreader/init_test.go new file mode 100644 index 0000000..9f82b3c --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/init_test.go @@ -0,0 +1,25 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mindreader + +import ( + "github.com/streamingfast/logging" +) + +var testLogger, testTracer = logging.PackageLogger("node-manager", "github.com/streamingfast/firehose-core/node_manager/mindreader/tests") + +func init() { + logging.InstantiateLoggers() +} diff --git a/firehose/firehose-core/node-manager/mindreader/logging.go b/firehose/firehose-core/node-manager/mindreader/logging.go new file mode 100644 index 0000000..1b93050 --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/logging.go @@ -0,0 +1,9 @@ +package mindreader + +import "os" + +var traceEnabled bool + +func init() { + traceEnabled = os.Getenv("TRACE") == "true" +} diff --git a/firehose/firehose-core/node-manager/mindreader/mindreader.go b/firehose/firehose-core/node-manager/mindreader/mindreader.go new file mode 100644 index 0000000..cc6a533 --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/mindreader.go @@ -0,0 +1,374 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mindreader + +import ( + "context" + "fmt" + "io" + "os" + "path" + "regexp" + "sync" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/blockstream" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/dstore" + "github.com/streamingfast/firehose-core/internal/utils" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + "github.com/streamingfast/logging" + "github.com/streamingfast/shutter" + "go.uber.org/zap" +) + +var ( + oneblockSuffixRegexp = regexp.MustCompile(`^[\w\-]+$`) +) + +type ConsolerReader interface { + ReadBlock() (obj *pbbstream.Block, err error) + Done() <-chan interface{} +} + +type CloseableConsoleReader interface { + ConsolerReader + + Close() error +} + +type ConsolerReaderFactory func(lines chan string) (ConsolerReader, error) + +type MindReaderPlugin struct { + *shutter.Shutter + + archiver *Archiver // transformed blocks are sent to Archiver + consoleReaderFactory ConsolerReaderFactory + stopBlock uint64 // if set, call shutdownFunc(nil) when we hit this number + channelCapacity int // transformed blocks are buffered in a channel + forceFinalityAfterBlocks *uint64 + + lastSeenBlock bstream.BlockRef + lastSeenBlockLock sync.RWMutex + + headBlockUpdater nodeManager.HeadBlockUpdater + onBlockWritten nodeManager.OnBlockWritten + blockStreamServer *blockstream.Server + zlogger *zap.Logger + + lines chan string + consoleReader ConsolerReader // contains the 'reader' part of the pipe + consumeReadFlowDone chan interface{} +} + +// NewMindReaderPlugin initiates its own: +// * ConsoleReader (from given Factory) +// * Archiver (from archive store params) +// * Shutter +func NewMindReaderPlugin( + oneBlocksStoreURL string, + workingDirectory string, + consoleReaderFactory ConsolerReaderFactory, + startBlockNum uint64, + stopBlockNum uint64, + channelCapacity int, + headBlockUpdater nodeManager.HeadBlockUpdater, + shutdownFunc func(error), + oneBlockSuffix string, + blockStreamServer *blockstream.Server, + zlogger *zap.Logger, + tracer logging.Tracer, +) (*MindReaderPlugin, error) { + err := validateOneBlockSuffix(oneBlockSuffix) + if err != nil { + return nil, err + } + + zlogger.Info("creating mindreader plugin", + zap.String("one_blocks_store_url", oneBlocksStoreURL), + zap.String("one_block_suffix", oneBlockSuffix), + zap.String("working_directory", workingDirectory), + zap.Uint64("start_block_num", startBlockNum), + zap.Uint64("stop_block_num", stopBlockNum), + zap.Int("channel_capacity", channelCapacity), + zap.Bool("with_head_block_updater", headBlockUpdater != nil), + zap.Bool("with_shutdown_func", shutdownFunc != nil), + ) + + // Create directory and its parent(s), it's a no-op if everything already exists + err = os.MkdirAll(workingDirectory, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("create working directory: %w", err) + } + + // local store + localOnBlocksStoreURL := path.Join(workingDirectory, "uploadable-oneblock") + localOneBlocksStore, err := dstore.NewStore(localOnBlocksStoreURL, "dbin", "", false) + if err != nil { + return nil, fmt.Errorf("new local one block store: %w", err) + } + + remoteOneBlocksStore, err := dstore.NewStore(oneBlocksStoreURL, "dbin.zst", "zstd", false) + if err != nil { + return nil, fmt.Errorf("new remote one block store: %w", err) + } + + archiver := NewArchiver( + startBlockNum, + oneBlockSuffix, + localOneBlocksStore, + remoteOneBlocksStore, + zlogger, + tracer, + ) + + zlogger.Info("creating new mindreader plugin") + return &MindReaderPlugin{ + Shutter: shutter.New(), + archiver: archiver, + consoleReaderFactory: consoleReaderFactory, + stopBlock: stopBlockNum, + channelCapacity: channelCapacity, + headBlockUpdater: headBlockUpdater, + blockStreamServer: blockStreamServer, + forceFinalityAfterBlocks: utils.GetEnvForceFinalityAfterBlocks(), + zlogger: zlogger, + }, nil +} + +// Other components may have issues finding the one block files if suffix is invalid +func validateOneBlockSuffix(suffix string) error { + if suffix == "" { + return fmt.Errorf("oneblock_suffix cannot be empty") + } + if !oneblockSuffixRegexp.MatchString(suffix) { + return fmt.Errorf("oneblock_suffix contains invalid characters: %q", suffix) + } + return nil +} + +func (p *MindReaderPlugin) Name() string { + return "MindReaderPlugin" +} + +func (p *MindReaderPlugin) Launch() { + ctx, cancel := context.WithCancel(context.Background()) + p.OnTerminating(func(_ error) { + cancel() + }) + + p.zlogger.Info("starting mindreader") + + p.consumeReadFlowDone = make(chan interface{}) + + lines := make(chan string, 10000) //need a config here? + p.lines = lines + + consoleReader, err := p.consoleReaderFactory(lines) + if err != nil { + p.Shutdown(err) + } + + p.consoleReader = consoleReader + if closer, ok := consoleReader.(CloseableConsoleReader); ok { + p.OnTerminating(func(_ error) { closer.Close() }) + } + + p.zlogger.Debug("starting archiver") + p.archiver.Start(ctx) + p.launch() + +} +func (p *MindReaderPlugin) launch() { + blocks := make(chan *pbbstream.Block, p.channelCapacity) + p.zlogger.Info("launching blocks reading loop", zap.Int("capacity", p.channelCapacity)) + go p.consumeReadFlow(blocks) + + go func() { + for { + err := p.readOneMessage(blocks) + if err != nil { + if err == io.EOF { + p.zlogger.Info("reached end of console reader stream, nothing more to do") + close(blocks) + return + } + p.zlogger.Error("reading from console logs", zap.Error(err)) + p.Shutdown(err) + // Always read messages otherwise you'll stall the shutdown lifecycle of the managed process, leading to corrupted database if exit uncleanly afterward + p.drainMessages() + close(blocks) + return + } + } + }() +} + +func (p MindReaderPlugin) Stop() { + p.zlogger.Info("mindreader is stopping") + if p.lines == nil { + // If the `lines` channel was not created yet, it means everything was shut down very rapidly + // and means MindreaderPlugin has not launched yet. Since it has not launched yet, there is + // no point in waiting for the read flow to complete since the read flow never started. So + // we exit right now. + return + } + + p.Shutdown(nil) + + close(p.lines) + p.waitForReadFlowToComplete() +} + +func (p *MindReaderPlugin) waitForReadFlowToComplete() { + p.zlogger.Info("waiting until consume read flow (i.e. blocks) is actually done processing blocks...") + <-p.consumeReadFlowDone + p.zlogger.Info("consume read flow terminate") +} + +// consumeReadFlow is the one function blocking termination until consumption/writeBlock/upload is done +func (p *MindReaderPlugin) consumeReadFlow(blocks <-chan *pbbstream.Block) { + p.zlogger.Info("starting consume flow") + defer close(p.consumeReadFlowDone) + + ctx := context.Background() + for { + p.zlogger.Debug("waiting to consume next block") + block, ok := <-blocks + if !ok { + p.zlogger.Info("all blocks in channel were drained, exiting read flow") + p.archiver.Shutdown(nil) + + <-p.archiver.Terminated() + p.zlogger.Info("archiver termination code completed") + + return + } + + p.zlogger.Debug("got one block", zap.Uint64("block_num", block.Number)) + + err := p.archiver.StoreBlock(ctx, block) + if err != nil { + p.zlogger.Error("failed storing block in archiver, shutting down and trying to send next blocks individually. You will need to reprocess over this range.", zap.Error(err), zap.String("received_block", block.Id), zap.Uint64("received_block_num", block.Number)) + + if !p.IsTerminating() { + go p.Shutdown(fmt.Errorf("archiver store block failed: %w", err)) + } + + continue + } + + if p.onBlockWritten != nil { + err = p.onBlockWritten(block) + if err != nil { + p.zlogger.Error("onBlockWritten callback failed", zap.Error(err)) + + if !p.IsTerminating() { + go p.Shutdown(fmt.Errorf("onBlockWritten callback failed: %w", err)) + } + + continue + } + } + + if p.blockStreamServer != nil { + err = p.blockStreamServer.PushBlock(block) + if err != nil { + p.zlogger.Error("failed passing block to block stream server (this should not happen, shutting down)", zap.Error(err)) + + if !p.IsTerminating() { + go p.Shutdown(fmt.Errorf("block stream push block failed: %w", err)) + } + + continue + } + } + } +} + +func (p *MindReaderPlugin) drainMessages() { + for line := range p.lines { + _ = line + } +} + +func (p *MindReaderPlugin) readOneMessage(blocks chan<- *pbbstream.Block) error { + block, err := p.consoleReader.ReadBlock() + if err != nil { + return err + } + + if p.forceFinalityAfterBlocks != nil { + utils.TweakBlockFinality(block, *p.forceFinalityAfterBlocks) + } + + if block.Number < bstream.GetProtocolFirstStreamableBlock { + return nil + } + + p.lastSeenBlockLock.Lock() + p.lastSeenBlock = block.AsRef() + p.lastSeenBlockLock.Unlock() + + if p.headBlockUpdater != nil { + if err := p.headBlockUpdater(block); err != nil { + p.zlogger.Info("shutting down because head block updater generated an error", zap.Error(err)) + + // We are shutting dow in a separate goroutine because the shutdown signal reaches us back at some point which + // if we were not on a goroutine, we would dead block with the shutdown pipeline that would wait for us to + // terminate which would never happen. + // + // 0a33f6b578cc4d0b + go p.Shutdown(err) + } + } + + blocks <- block + + if p.stopBlock != 0 && block.Number >= p.stopBlock && !p.IsTerminating() { + p.zlogger.Info("shutting down because requested end block reached", zap.Stringer("block", block)) + + // See comment tagged 0a33f6b578cc4d0b + go p.Shutdown(nil) + } + + return nil +} + +// LogLine receives log line and write it to "pipe" of the local console reader +func (p *MindReaderPlugin) LogLine(in string) { + if p.IsTerminating() { + return + } + + p.lines <- in +} + +func (p *MindReaderPlugin) OnBlockWritten(callback nodeManager.OnBlockWritten) { + p.onBlockWritten = callback +} + +// GetMindreaderLineChannel is a marker method that `superviser.Superviser` uses to determine if +// `logplugin.LogPlugin` is an actual mindreader plugin without depending on the `mindreader` +// package in which case it would create an import cycle. +// +// The `superviser.Superviser` defines `type mindreaderPlugin interface { LastSeenBlockNum() bstream.BlockRef }` +// which is respected. This is a trick to avoid circual dependency in imports. +func (p *MindReaderPlugin) LastSeenBlock() bstream.BlockRef { + p.lastSeenBlockLock.RLock() + defer p.lastSeenBlockLock.RUnlock() + + return p.lastSeenBlock +} diff --git a/firehose/firehose-core/node-manager/mindreader/mindreader_test.go b/firehose/firehose-core/node-manager/mindreader/mindreader_test.go new file mode 100644 index 0000000..add117d --- /dev/null +++ b/firehose/firehose-core/node-manager/mindreader/mindreader_test.go @@ -0,0 +1,167 @@ +package mindreader + +import ( + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + "sync" + "testing" + "time" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/shutter" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMindReaderPlugin_OfficialPrefix_ReadFlow(t *testing.T) { + testMindReaderPluginReadFlow(t, "FIRE") +} + +func TestMindReaderPlugin_LegacyPrefix_ReadFlow(t *testing.T) { + testMindReaderPluginReadFlow(t, "DMLOG") +} + +func testMindReaderPluginReadFlow(t *testing.T, prefix string) { + numOfLines := 1 + lines := make(chan string, numOfLines) + blocks := make(chan *pbbstream.Block, numOfLines) + + mindReader := &MindReaderPlugin{ + Shutter: shutter.New(), + lines: lines, + consoleReader: newTestConsoleReader(lines), + } + + wg := sync.WaitGroup{} + wg.Add(numOfLines) + + var readMessageError error + go func() { + defer wg.Done() + readMessageError = mindReader.readOneMessage(blocks) + }() + + mindReader.LogLine(prefix + ` {"id":"00000001a"}`) + select { + case b := <-blocks: + require.Equal(t, uint64(01), b.Number) + case <-time.After(time.Second): + t.Error("too long") + } + + wg.Wait() + require.NoError(t, readMessageError) +} + +func TestMindReaderPlugin_StopAtBlockNumReached(t *testing.T) { + numOfLines := 2 + lines := make(chan string, numOfLines) + blocks := make(chan *pbbstream.Block, numOfLines) + done := make(chan interface{}) + + mindReader := &MindReaderPlugin{ + Shutter: shutter.New(), + lines: lines, + consoleReader: newTestConsoleReader(lines), + stopBlock: 2, + zlogger: testLogger, + } + mindReader.OnTerminating(func(err error) { + if err == nil { + close(done) + } else { + t.Error("should not be called") + } + }) + + mindReader.LogLine(`DMLOG {"id":"00000001a"}`) + mindReader.LogLine(`DMLOG {"id":"00000002a"}`) + + wg := sync.WaitGroup{} + wg.Add(numOfLines) + + readErrors := []error{} + go func() { + for i := 0; i < numOfLines; i++ { + err := mindReader.readOneMessage(blocks) + readErrors = append(readErrors, err) + wg.Done() + } + }() + + select { + case <-done: + case <-time.After(1 * time.Millisecond): + t.Error("too long") + } + + wg.Wait() + for _, err := range readErrors { + require.NoError(t, err) + } + + // Validate actually read block + assert.Equal(t, numOfLines, len(blocks)) // moderate requirement, race condition can make it pass more blocks +} + +func TestMindReaderPlugin_OneBlockSuffixFormat(t *testing.T) { + assert.Error(t, validateOneBlockSuffix("")) + assert.NoError(t, validateOneBlockSuffix("example")) + assert.NoError(t, validateOneBlockSuffix("example-hostname-123")) + assert.NoError(t, validateOneBlockSuffix("example_hostname_123")) + assert.Equal(t, `oneblock_suffix contains invalid characters: "example.lan"`, validateOneBlockSuffix("example.lan").Error()) +} + +type testConsoleReader struct { + lines chan string + done chan interface{} +} + +func newTestConsoleReader(lines chan string) *testConsoleReader { + return &testConsoleReader{ + lines: lines, + } +} + +func (c *testConsoleReader) Done() <-chan interface{} { + return c.done +} + +func (c *testConsoleReader) ReadBlock() (*pbbstream.Block, error) { + line, _ := <-c.lines + + var formatedLine string + if strings.HasPrefix(line, "DMLOG") { + formatedLine = line[6:] + } else { + formatedLine = line[5:] + } + + type block struct { + ID string `json:"id"` + } + + data := new(block) + if err := json.Unmarshal([]byte(formatedLine), data); err != nil { + return nil, fmt.Errorf("marshalling error on '%s': %w", formatedLine, err) + } + return &pbbstream.Block{ + Id: data.ID, + Number: toBlockNum(data.ID), + }, nil +} + +func toBlockNum(blockID string) uint64 { + if len(blockID) < 8 { + return 0 + } + bin, err := hex.DecodeString(blockID[:8]) + if err != nil { + return 0 + } + return uint64(binary.BigEndian.Uint32(bin)) +} diff --git a/firehose/firehose-core/node-manager/monitor.go b/firehose/firehose-core/node-manager/monitor.go new file mode 100644 index 0000000..34f8e54 --- /dev/null +++ b/firehose/firehose-core/node-manager/monitor.go @@ -0,0 +1,93 @@ +package node_manager + +import ( + "time" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/dmetrics" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +type Readiness interface { + IsReady() bool +} + +type MetricsAndReadinessManager struct { + headBlockChan chan *pbbstream.Block + headBlockTimeDrift *dmetrics.HeadTimeDrift + headBlockNumber *dmetrics.HeadBlockNum + appReadiness *dmetrics.AppReadiness + readinessProbe *atomic.Bool + + // ReadinessMaxLatency is the max delta between head block time and + // now before /healthz starts returning success + readinessMaxLatency time.Duration + + logger *zap.Logger +} + +func NewMetricsAndReadinessManager(headBlockTimeDrift *dmetrics.HeadTimeDrift, headBlockNumber *dmetrics.HeadBlockNum, appReadiness *dmetrics.AppReadiness, readinessMaxLatency time.Duration) *MetricsAndReadinessManager { + return &MetricsAndReadinessManager{ + headBlockChan: make(chan *pbbstream.Block, 1), // just for non-blocking, saving a few nanoseconds here + readinessProbe: atomic.NewBool(false), + appReadiness: appReadiness, + headBlockTimeDrift: headBlockTimeDrift, + headBlockNumber: headBlockNumber, + readinessMaxLatency: readinessMaxLatency, + } +} + +func (m *MetricsAndReadinessManager) setReadinessProbeOn() { + m.readinessProbe.CAS(false, true) + m.appReadiness.SetReady() +} + +func (m *MetricsAndReadinessManager) setReadinessProbeOff() { + m.readinessProbe.CAS(true, false) + m.appReadiness.SetNotReady() +} + +func (m *MetricsAndReadinessManager) IsReady() bool { + return m.readinessProbe.Load() +} + +func (m *MetricsAndReadinessManager) Launch() { + for { + var lastSeenBlock *pbbstream.Block + select { + case block := <-m.headBlockChan: + lastSeenBlock = block + case <-time.After(time.Second): + } + + if lastSeenBlock == nil { + continue + } + + // metrics + if m.headBlockNumber != nil { + m.headBlockNumber.SetUint64(lastSeenBlock.Number) + } + + if lastSeenBlock.Time().IsZero() { // never act upon zero timestamps + continue + } + if m.headBlockTimeDrift != nil { + m.headBlockTimeDrift.SetBlockTime(lastSeenBlock.Time()) + } + + // readiness + if m.readinessMaxLatency == 0 || time.Since(lastSeenBlock.Time()) < m.readinessMaxLatency { + m.setReadinessProbeOn() + } else { + m.setReadinessProbeOff() + } + } +} + +func (m *MetricsAndReadinessManager) UpdateHeadBlock(block *pbbstream.Block) error { + m.headBlockChan <- block + return nil +} diff --git a/firehose/firehose-core/node-manager/operator/backuper.go b/firehose/firehose-core/node-manager/operator/backuper.go new file mode 100644 index 0000000..7045077 --- /dev/null +++ b/firehose/firehose-core/node-manager/operator/backuper.go @@ -0,0 +1,212 @@ +package operator + +import ( + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "go.uber.org/zap" +) + +type BackupModuleConfig map[string]string + +type BackupModuleFactory func(conf BackupModuleConfig) (BackupModule, error) + +type BackupModule interface { + Backup(lastSeenBlockNum uint32) (string, error) + RequiresStop() bool +} + +type RestorableBackupModule interface { + BackupModule + Restore(name string) error +} + +type BackupSchedule struct { + BlocksBetweenRuns int + TimeBetweenRuns time.Duration + RequiredHostnameMatch string // will not run backup if !empty env.Hostname != HostnameMatch + BackuperName string // must match id of backupModule +} + +func (o *Operator) RegisterBackupModule(name string, mod BackupModule) error { + if o.backupModules == nil { + o.backupModules = make(map[string]BackupModule) + } + + if existing, found := o.backupModules[name]; found { + return fmt.Errorf("backup module %q is already registered, previous module type %s", name, reflect.ValueOf(existing)) + } + + o.backupModules[name] = mod + return nil +} + +func (o *Operator) RegisterBackupSchedule(sched *BackupSchedule) { + o.backupSchedules = append(o.backupSchedules, sched) +} + +func selectBackupModule(mods map[string]BackupModule, optionalName string) (BackupModule, error) { + if len(mods) == 0 { + return nil, fmt.Errorf("no registered backup modules") + } + + if optionalName != "" { + chosen, ok := mods[optionalName] + if !ok { + return nil, fmt.Errorf("invalid backup module: %s", optionalName) + } + return chosen, nil + } + + if len(mods) > 1 { + var modNames []string + for k := range mods { + modNames = append(modNames, k) + } + return nil, fmt.Errorf("more than one module registered, and none specified (%s)", strings.Join(modNames, ",")) + } + + for _, mod := range mods { // single element in map + return mod, nil + } + return nil, fmt.Errorf("impossible path") + +} + +func selectRestoreModule(choices map[string]BackupModule, optionalName string) (RestorableBackupModule, error) { + mods := restorable(choices) + if len(mods) == 0 { + return nil, fmt.Errorf("none of the registered backup modules support 'restore'") + } + + if optionalName != "" { + chosen, ok := mods[optionalName] + if !ok { + return nil, fmt.Errorf("invalid restorable backup module: %s", optionalName) + } + return chosen, nil + } + + if len(mods) > 1 { + var modNames []string + for k := range mods { + modNames = append(modNames, k) + } + return nil, fmt.Errorf("more than one restorable module registered, and none specified (%s)", strings.Join(modNames, ",")) + } + + for _, mod := range mods { // single element in map + return mod, nil + } + return nil, fmt.Errorf("impossible path") + +} + +func restorable(in map[string]BackupModule) map[string]RestorableBackupModule { + out := make(map[string]RestorableBackupModule) + for k, v := range in { + if rest, ok := v.(RestorableBackupModule); ok { + out[k] = rest + } + } + return out +} + +func NewBackupSchedule(freqBlocks, freqTime, requiredHostname, backuperName string) (*BackupSchedule, error) { + switch { + case freqBlocks != "": + freqUint, err := strconv.ParseUint(freqBlocks, 10, 64) + if err != nil || freqUint == 0 { + return nil, fmt.Errorf("invalid value for freq_block in backup schedule (err: %w)", err) + } + + return &BackupSchedule{ + BlocksBetweenRuns: int(freqUint), + RequiredHostnameMatch: requiredHostname, + BackuperName: backuperName, + }, nil + + case freqTime != "": + freqTime, err := time.ParseDuration(freqTime) + if err != nil || freqTime < time.Minute { + return nil, fmt.Errorf("invalid value for freq_time in backup schedule(duration: %s, err: %w)", freqTime, err) + } + + return &BackupSchedule{ + TimeBetweenRuns: freqTime, + RequiredHostnameMatch: requiredHostname, + BackuperName: backuperName, + }, nil + + default: + return nil, fmt.Errorf("schedule created without any frequency value") + } +} + +func ParseBackupConfigs( + logger *zap.Logger, + backupConfigs []string, + backupModuleFactories map[string]BackupModuleFactory, +) ( + mods map[string]BackupModule, + scheds []*BackupSchedule, + err error, +) { + logger.Info("parsing backup configs", zap.Strings("configs", backupConfigs), zap.Int("factory_count", len(backupModuleFactories))) + for key := range backupModuleFactories { + logger.Info("parsing backup known factory", zap.String("name", key)) + } + + mods = make(map[string]BackupModule) + for _, confStr := range backupConfigs { + conf, err := parseKVConfigString(confStr) + if err != nil { + return nil, nil, err + } + + t := conf["type"] + factory, found := backupModuleFactories[t] + if !found { + return nil, nil, fmt.Errorf("unknown backup module type %q", t) + } + + mods[t], err = factory(conf) + if err != nil { + return nil, nil, fmt.Errorf("backup module %q factory: %w", t, err) + } + + if conf["freq-blocks"] != "" || conf["freq-time"] != "" { + newSched, err := NewBackupSchedule(conf["freq-blocks"], conf["freq-time"], conf["required-hostname"], t) + if err != nil { + return nil, nil, fmt.Errorf("error setting up backup schedule for %q: %w", t, err) + } + + scheds = append(scheds, newSched) + } + } + + return +} + +// parseKVConfigString is used for flags that generate key/value data, like +// `--backup="type=something freq_blocks=1000 prefix=v1"`. +func parseKVConfigString(in string) (map[string]string, error) { + fields := strings.Fields(in) + kvs := map[string]string{} + for _, field := range fields { + kv := strings.Split(field, "=") + if len(kv) != 2 { + return nil, fmt.Errorf("invalid key=value in kv config string: %s", field) + } + kvs[kv[0]] = kv[1] + } + typ, ok := kvs["type"] + if !ok || typ == "" { + return nil, fmt.Errorf("no type defined in kv config string (type field mandatory)") + } + + return kvs, nil +} diff --git a/firehose/firehose-core/node-manager/operator/backuper_test.go b/firehose/firehose-core/node-manager/operator/backuper_test.go new file mode 100644 index 0000000..1d64183 --- /dev/null +++ b/firehose/firehose-core/node-manager/operator/backuper_test.go @@ -0,0 +1,73 @@ +package operator + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseKVConfigString(t *testing.T) { + cases := []struct { + name string + in string + expected map[string]string + expectError bool + }{ + { + "vanilla", + "type=pitreos store=file:///var/backups", + map[string]string{"type": "pitreos", "store": "file:///var/backups"}, + false, + }, + { + "missing type", + "store=file:///var/backups", + nil, + true, + }, + { + "empty type", + "type= store=file:///var/backups", + nil, + true, + }, + { + "empty", + "", + nil, + true, + }, + { + "invalid", + "type=blah store=file:///var/backups something", + nil, + true, + }, + { + "multispace_ok", + "type=blah store=file:///var/backups ", + map[string]string{"type": "blah", "store": "file:///var/backups"}, + false, + }, + { + "emptystring ok", + "type=blah store= freq=", + map[string]string{"type": "blah", "store": "", "freq": ""}, + false, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + out, err := parseKVConfigString(tc.in) + if tc.expectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tc.expected, out) + }) + } +} diff --git a/firehose/firehose-core/node-manager/operator/bootstrap.go b/firehose/firehose-core/node-manager/operator/bootstrap.go new file mode 100644 index 0000000..5f69afc --- /dev/null +++ b/firehose/firehose-core/node-manager/operator/bootstrap.go @@ -0,0 +1,5 @@ +package operator + +type Bootstrapper interface { + Bootstrap() error +} diff --git a/firehose/firehose-core/node-manager/operator/errors.go b/firehose/firehose-core/node-manager/operator/errors.go new file mode 100644 index 0000000..ba411d9 --- /dev/null +++ b/firehose/firehose-core/node-manager/operator/errors.go @@ -0,0 +1,19 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operator + +import "errors" + +var ErrCleanExit = errors.New("clean exit") diff --git a/firehose/firehose-core/node-manager/operator/http_server.go b/firehose/firehose-core/node-manager/operator/http_server.go new file mode 100644 index 0000000..f06f9f9 --- /dev/null +++ b/firehose/firehose-core/node-manager/operator/http_server.go @@ -0,0 +1,212 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operator + +import ( + "fmt" + "net/http" + "strings" + + "github.com/gorilla/mux" + "github.com/streamingfast/derr" + "go.uber.org/zap" +) + +type HTTPOption func(r *mux.Router) + +func (o *Operator) RunHTTPServer(httpListenAddr string, options ...HTTPOption) *http.Server { + r := mux.NewRouter() + r.HandleFunc("/v1/ping", o.pingHandler).Methods("GET") + r.HandleFunc("/healthz", o.healthzHandler).Methods("GET") + r.HandleFunc("/v1/healthz", o.healthzHandler).Methods("GET") + r.HandleFunc("/v1/server_id", o.serverIDHandler).Methods("GET") + r.HandleFunc("/v1/is_running", o.isRunningHandler).Methods("GET") + r.HandleFunc("/v1/start_command", o.startcommandHandler).Methods("GET") + r.HandleFunc("/v1/maintenance", o.maintenanceHandler).Methods("POST") + r.HandleFunc("/v1/resume", o.resumeHandler).Methods("POST") + r.HandleFunc("/v1/backup", o.backupHandler).Methods("POST") + r.HandleFunc("/v1/restore", o.restoreHandler).Methods("POST") + r.HandleFunc("/v1/list_backups", o.listBackupsHandler).Methods("GET") + r.HandleFunc("/v1/reload", o.reloadHandler).Methods("POST") + r.HandleFunc("/v1/safely_reload", o.safelyReloadHandler).Methods("POST") + r.HandleFunc("/v1/safely_pause_production", o.safelyPauseProdHandler).Methods("POST") + r.HandleFunc("/v1/safely_resume_production", o.safelyResumeProdHandler).Methods("POST") + + for _, opt := range options { + opt(r) + } + + o.zlogger.Info("starting webserver", zap.String("http_addr", httpListenAddr)) + err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error { + pathTemplate, err := route.GetPathTemplate() + if err == nil { + methodsTmp, err := route.GetMethods() + var methods string + if err == nil { + methods = strings.Join(methodsTmp, ",") + } else { + methods = "GET" + } + + o.zlogger.Debug("walked route methods", zap.String("methods", methods), zap.String("path_template", pathTemplate)) + } + return nil + }) + + if err != nil { + o.zlogger.Error("walking route methods", zap.Error(err)) + } + + srv := &http.Server{Addr: httpListenAddr, Handler: r} + go func() { + if err := srv.ListenAndServe(); err != http.ErrServerClosed { + o.zlogger.Info("http server did not close correctly") + o.Shutdown(err) + } + }() + + return srv +} + +func (o *Operator) pingHandler(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("pong\n")) +} + +func (o *Operator) startcommandHandler(w http.ResponseWriter, _ *http.Request) { + command := "Command:\n" + o.Superviser.GetCommand() + "\n" + _, _ = w.Write([]byte(command)) +} + +func (o *Operator) isRunningHandler(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(fmt.Sprintf(`{"is_running":%t}`, o.Superviser.IsRunning()))) +} + +func (o *Operator) serverIDHandler(w http.ResponseWriter, _ *http.Request) { + id, err := o.Superviser.ServerID() + if err != nil { + http.Error(w, "not ready", http.StatusServiceUnavailable) + return + } + + _, _ = w.Write([]byte(id)) +} + +func (o *Operator) healthzHandler(w http.ResponseWriter, _ *http.Request) { + if !o.Superviser.IsRunning() { + http.Error(w, "not ready: chain is not running", http.StatusServiceUnavailable) + return + } + + if !o.chainReadiness.IsReady() { + http.Error(w, "not ready: chain is not ready", http.StatusServiceUnavailable) + return + } + + if o.aboutToStop.Load() || derr.IsShuttingDown() { + http.Error(w, "not ready: chain about to stop", http.StatusServiceUnavailable) + return + } + + w.Write([]byte("ready\n")) +} + +func (o *Operator) reloadHandler(w http.ResponseWriter, r *http.Request) { + o.triggerWebCommand("reload", nil, w, r) +} + +func (o *Operator) safelyReloadHandler(w http.ResponseWriter, r *http.Request) { + o.triggerWebCommand("safely_reload", nil, w, r) +} + +func (o *Operator) safelyResumeProdHandler(w http.ResponseWriter, r *http.Request) { + o.triggerWebCommand("safely_resume_production", nil, w, r) +} + +func (o *Operator) safelyPauseProdHandler(w http.ResponseWriter, r *http.Request) { + o.triggerWebCommand("safely_pause_production", nil, w, r) +} + +func (o *Operator) restoreHandler(w http.ResponseWriter, r *http.Request) { + params := getRequestParams(r, "backupName", "backupTag", "forceVerify") + o.triggerWebCommand("restore", params, w, r) +} + +func (o *Operator) listBackupsHandler(w http.ResponseWriter, r *http.Request) { + params := getRequestParams(r, "offset", "limit") + o.triggerWebCommand("list", params, w, r) +} + +func getRequestParams(r *http.Request, terms ...string) map[string]string { + params := make(map[string]string) + for _, p := range terms { + val := r.FormValue(p) + if val != "" { + params[p] = val + } + } + return params +} + +func (o *Operator) backupHandler(w http.ResponseWriter, r *http.Request) { + o.triggerWebCommand("backup", nil, w, r) +} + +func (o *Operator) maintenanceHandler(w http.ResponseWriter, r *http.Request) { + o.triggerWebCommand("maintenance", nil, w, r) +} + +func (o *Operator) resumeHandler(w http.ResponseWriter, r *http.Request) { + params := map[string]string{ + "debug-firehose-logs": r.FormValue("debug-firehose-logs"), + } + + if params["debug-firehose-logs"] == "" { + params["debug-firehose-logs"] = "false" + } + + o.triggerWebCommand("resume", params, w, r) +} + +func (o *Operator) triggerWebCommand(cmdName string, params map[string]string, w http.ResponseWriter, r *http.Request) { + c := &Command{cmd: cmdName, logger: o.zlogger} + c.params = params + sync := r.FormValue("sync") + if sync == "true" { + o.sendCommandSync(c, w) + } else { + o.sendCommandAsync(c, w) + } +} + +func (o *Operator) sendCommandAsync(c *Command, w http.ResponseWriter) { + o.zlogger.Info("sending async command to operator through channel", zap.Object("command", c)) + o.commandChan <- c + w.WriteHeader(http.StatusCreated) + _, _ = w.Write([]byte(fmt.Sprintf("%s command submitted\n", c.cmd))) +} + +func (o *Operator) sendCommandSync(c *Command, w http.ResponseWriter) { + o.zlogger.Info("sending sync command to operator through channel", zap.Object("command", c)) + c.returnch = make(chan error) + o.commandChan <- c + err := <-c.returnch + if err == nil { + w.Write([]byte(fmt.Sprintf("Success: %s completed\n", c.cmd))) + } else { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte(fmt.Sprintf("ERROR: %s failed: %s \n", c.cmd, err))) + } + +} diff --git a/firehose/firehose-core/node-manager/operator/operator.go b/firehose/firehose-core/node-manager/operator/operator.go new file mode 100644 index 0000000..1667a99 --- /dev/null +++ b/firehose/firehose-core/node-manager/operator/operator.go @@ -0,0 +1,477 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operator + +import ( + "fmt" + "net/http" + "os" + "strings" + "sync" + "time" + + "github.com/streamingfast/derr" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + "github.com/streamingfast/shutter" + "go.uber.org/atomic" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +type Operator struct { + *shutter.Shutter + options *Options + lastStartCommand time.Time + + backupModules map[string]BackupModule + backupSchedules []*BackupSchedule + + commandChan chan *Command + httpServer *http.Server + + Superviser nodeManager.ChainSuperviser + chainReadiness nodeManager.Readiness + + aboutToStop *atomic.Bool + zlogger *zap.Logger +} + +type Options struct { + Bootstrapper Bootstrapper + + EnableSupervisorMonitoring bool + + // Delay before sending Stop() to superviser, during which we return NotReady + ShutdownDelay time.Duration +} + +type Command struct { + cmd string + params map[string]string + returnch chan error + closer sync.Once + logger *zap.Logger +} + +func (c *Command) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddString("name", c.cmd) + encoder.AddReflected("params", c.params) + return nil +} + +func New(zlogger *zap.Logger, chainSuperviser nodeManager.ChainSuperviser, chainReadiness nodeManager.Readiness, options *Options) (*Operator, error) { + zlogger.Info("creating operator", zap.Reflect("options", options)) + + o := &Operator{ + Shutter: shutter.New(), + chainReadiness: chainReadiness, + commandChan: make(chan *Command, 10), + options: options, + Superviser: chainSuperviser, + aboutToStop: atomic.NewBool(false), + zlogger: zlogger, + } + + chainSuperviser.OnTerminated(func(err error) { + if !o.IsTerminating() { + zlogger.Info("chain superviser is shutting down operator") + o.Shutdown(err) + } + }) + + o.OnTerminating(func(err error) { + //wait for supervisor to terminate, supervisor will wait for plugins to terminate + if !chainSuperviser.IsTerminating() { + zlogger.Info("operator is terminating", zap.Error(err)) + chainSuperviser.Shutdown(err) + } + + zlogger.Info("operator is waiting for superviser to shutdown", zap.Error(err)) + <-o.Superviser.Terminated() + zlogger.Info("operator done waiting for superviser to shutdown", zap.Error(err)) + }) + + return o, nil +} + +func (o *Operator) Launch(httpListenAddr string, options ...HTTPOption) error { + o.zlogger.Info("launching operator HTTP server", zap.String("http_listen_addr", httpListenAddr)) + o.httpServer = o.RunHTTPServer(httpListenAddr, options...) + + // FIXME: too many options for that, maybe use monitoring module like with bootstrapper + if o.options.EnableSupervisorMonitoring { + if monitorable, ok := o.Superviser.(nodeManager.MonitorableChainSuperviser); ok { + go monitorable.Monitor() + } + } + + o.LaunchBackupSchedules() + + if o.options.Bootstrapper != nil { + o.zlogger.Info("operator calling bootstrap function") + err := o.options.Bootstrapper.Bootstrap() + if err != nil { + return fmt.Errorf("unable to bootstrap chain: %w", err) + } + } + o.commandChan <- &Command{cmd: "start", logger: o.zlogger} + + for { + o.zlogger.Info("operator ready to receive commands") + select { + case <-o.Superviser.Stopped(): // the chain stopped outside of a command that was expecting it. + if o.Superviser.IsTerminating() { + o.zlogger.Info("superviser terminating, waiting for operator...") + <-o.Terminating() + return o.Err() + } + // FIXME call a restore handler if passed... + lastLogLines := o.Superviser.LastLogLines() + + // FIXME: Actually, we should create a custom error type that contains the required data, the catching + // code can thus perform the required formatting! + baseFormat := "instance %q stopped (exit code: %d), shutting down" + var shutdownErr error + if len(lastLogLines) > 0 { + shutdownErr = fmt.Errorf(baseFormat+": last log lines:\n%s", o.Superviser.GetName(), o.Superviser.LastExitCode(), formatLogLines(lastLogLines)) + } else { + shutdownErr = fmt.Errorf(baseFormat, o.Superviser.GetName(), o.Superviser.LastExitCode()) + } + + o.Shutdown(shutdownErr) + break + + case cmd := <-o.commandChan: + if cmd.cmd == "start" { // start 'sub' commands after a restore do NOT come through here + o.lastStartCommand = time.Now() + } + err := o.runCommand(cmd) + cmd.Return(err) + if err != nil { + if err == ErrCleanExit { + return nil + } + return fmt.Errorf("command %v execution failed: %v", cmd.cmd, err) + } + } + } +} + +func formatLogLines(lines []string) string { + formattedLines := make([]string, len(lines)) + for i, line := range lines { + formattedLines[i] = " " + line + } + + return strings.Join(formattedLines, "\n") +} + +func (o *Operator) runSubCommand(name string, parentCmd *Command) error { + return o.runCommand(&Command{cmd: name, returnch: parentCmd.returnch, logger: o.zlogger}) +} + +func (o *Operator) cleanSuperviserStop() error { + o.aboutToStop.Store(true) + defer o.aboutToStop.Store(false) + if o.options.ShutdownDelay != 0 && !derr.IsShuttingDown() { + o.zlogger.Info("marked as not_ready, waiting delay before actually stopping for maintenance", zap.Duration("delay", o.options.ShutdownDelay)) + time.Sleep(o.options.ShutdownDelay) + } + + err := o.Superviser.Stop() + return err +} + +// runCommand does its work, and returns an error for irrecoverable states. +func (o *Operator) runCommand(cmd *Command) error { + o.zlogger.Info("received operator command", zap.String("command", cmd.cmd), zap.Reflect("params", cmd.params)) + switch cmd.cmd { + case "maintenance": + o.zlogger.Info("preparing to stop process") + + if err := o.cleanSuperviserStop(); err != nil { + return err + } + + // Careful, we are now "stopped". Every other case can handle that state. + o.zlogger.Info("successfully put in maintenance") + + case "restore": + restoreMod, err := selectRestoreModule(o.backupModules, cmd.params["name"]) + if err != nil { + cmd.Return(err) + return nil + } + + o.zlogger.Info("Stopping to restore a backup") + if restoreMod.RequiresStop() { + if err := o.cleanSuperviserStop(); err != nil { + return err + } + } + + backupName := "latest" + if b, ok := cmd.params["backupName"]; ok { + backupName = b + } + + if err := restoreMod.Restore(backupName); err != nil { + return err + } + + o.zlogger.Info("Restarting after restore") + if restoreMod.RequiresStop() { + return o.runSubCommand("start", cmd) + } + return nil + + case "backup": + backupMod, err := selectBackupModule(o.backupModules, cmd.params["name"]) + if err != nil { + cmd.Return(err) + return nil + } + + o.zlogger.Info("Stopping to perform a backup") + if backupMod.RequiresStop() { + if err := o.cleanSuperviserStop(); err != nil { + return err + } + } + + backupName, err := backupMod.Backup(uint32(o.Superviser.LastSeenBlockNum())) + if err != nil { + return err + } + cmd.logger.Info("Completed backup", zap.String("backup_name", backupName)) + + o.zlogger.Info("Restarting after backup") + if backupMod.RequiresStop() { + return o.runSubCommand("start", cmd) + } + return nil + + case "reload": + o.zlogger.Info("preparing for reload") + if err := o.cleanSuperviserStop(); err != nil { + return err + } + + return o.runSubCommand("start", cmd) + + case "safely_resume_production": + o.zlogger.Info("preparing for safely resume production") + producer, ok := o.Superviser.(nodeManager.ProducerChainSuperviser) + if !ok { + cmd.Return(fmt.Errorf("the chain superviser does not support producing blocks")) + return nil + } + + isProducing, err := producer.IsProducing() + if err != nil { + cmd.Return(fmt.Errorf("unable to check if producing: %w", err)) + return nil + } + + if !isProducing { + o.zlogger.Info("resuming production of blocks") + err := producer.ResumeProduction() + if err != nil { + cmd.Return(fmt.Errorf("error resuming production of blocks: %w", err)) + return nil + } + + o.zlogger.Info("successfully resumed producer") + + } else { + o.zlogger.Info("block production was already running, doing nothing") + } + + o.zlogger.Info("successfully resumed block production") + + case "safely_pause_production": + o.zlogger.Info("preparing for safely pause production") + producer, ok := o.Superviser.(nodeManager.ProducerChainSuperviser) + if !ok { + cmd.Return(fmt.Errorf("the chain superviser does not support producing blocks")) + return nil + } + + isProducing, err := producer.IsProducing() + if err != nil { + cmd.Return(fmt.Errorf("unable to check if producing: %w", err)) + return nil + } + + if !isProducing { + o.zlogger.Info("block production is already paused, command is a no-op") + return nil + } + + o.zlogger.Info("waiting to pause the producer") + err = producer.WaitUntilEndOfNextProductionRound(3 * time.Minute) + if err != nil { + cmd.Return(fmt.Errorf("timeout waiting for production round: %w", err)) + return nil + } + + o.zlogger.Info("pausing block production") + err = producer.PauseProduction() + if err != nil { + cmd.Return(fmt.Errorf("unable to pause production correctly: %w", err)) + return nil + } + + o.zlogger.Info("successfully paused block production") + + case "safely_reload": + o.zlogger.Info("preparing for safely reload") + producer, ok := o.Superviser.(nodeManager.ProducerChainSuperviser) + if ok && producer.IsActiveProducer() { + o.zlogger.Info("waiting right after production round") + err := producer.WaitUntilEndOfNextProductionRound(3 * time.Minute) + if err != nil { + cmd.Return(fmt.Errorf("timeout waiting for production round: %w", err)) + return nil + } + } + + o.zlogger.Info("issuing 'reload' now") + emptied := false + for !emptied { + select { + case interimCmd := <-o.commandChan: + o.zlogger.Info("emptying command queue while safely_reload was running, dropped", zap.Any("interim_cmd", interimCmd)) + default: + emptied = true + } + } + + return o.runSubCommand("reload", cmd) + + case "start", "resume": + o.zlogger.Info("preparing for start") + if o.Superviser.IsRunning() { + o.zlogger.Info("chain is already running") + return nil + } + + o.zlogger.Info("preparing to start chain") + + var options []nodeManager.StartOption + if value := cmd.params["debug-firehose-logs"]; value != "" { + if value == "true" { + options = append(options, nodeManager.EnableDebugDeepmindOption) + } else { + options = append(options, nodeManager.DisableDebugDeepmindOption) + } + } + + if err := o.Superviser.Start(options...); err != nil { + return fmt.Errorf("error starting chain superviser: %w", err) + } + + o.zlogger.Info("successfully start service") + + } + + return nil +} + +func (c *Command) Return(err error) { + c.closer.Do(func() { + if err != nil && err != ErrCleanExit { + c.logger.Error("command failed", zap.String("cmd", c.cmd), zap.Error(err)) + } + + if c.returnch != nil { + c.returnch <- err + } + }) +} + +func (o *Operator) LaunchBackupSchedules() { + for _, sched := range o.backupSchedules { + if sched.RequiredHostnameMatch != "" { + hostname, err := os.Hostname() + if err != nil { + o.zlogger.Error("Disabling automatic backup schedule because requiredHostname is set and cannot retrieve hostname", zap.Error(err)) + continue + } + if sched.RequiredHostnameMatch != hostname { + o.zlogger.Info("Disabling automatic backup schedule because hostname does not match required value", + zap.String("hostname", hostname), + zap.String("required_hostname", sched.RequiredHostnameMatch), + zap.String("backuper_name", sched.BackuperName)) + continue + } + } + + cmdParams := map[string]string{"name": sched.BackuperName} + + if sched.TimeBetweenRuns > time.Second { //loose validation of not-zero (I've seen issues with .IsZero()) + o.zlogger.Info("starting time-based schedule for backup", + zap.Duration("time_between_runs", sched.TimeBetweenRuns), + zap.String("backuper_name", sched.BackuperName), + ) + go o.RunEveryPeriod(sched.TimeBetweenRuns, "backup", cmdParams) + } + if sched.BlocksBetweenRuns > 0 { + o.zlogger.Info("starting block-based schedule for backup", + zap.Int("blocks_between_runs", sched.BlocksBetweenRuns), + zap.String("backuper_name", sched.BackuperName), + ) + go o.RunEveryXBlock(uint32(sched.BlocksBetweenRuns), "backup", cmdParams) + } + } +} + +func (o *Operator) RunEveryPeriod(period time.Duration, commandName string, params map[string]string) { + for { + time.Sleep(100 * time.Microsecond) + + if o.Superviser.IsRunning() { + break + } + } + + ticker := time.NewTicker(period).C + + for range ticker { + if o.Superviser.IsRunning() { + o.commandChan <- &Command{cmd: commandName, logger: o.zlogger, params: params} + } + } +} + +func (o *Operator) RunEveryXBlock(freq uint32, commandName string, params map[string]string) { + var lastHeadReference uint64 + for { + time.Sleep(1 * time.Second) + lastSeenBlockNum := o.Superviser.LastSeenBlockNum() + if lastSeenBlockNum == 0 { + continue + } + + if lastHeadReference == 0 { + lastHeadReference = lastSeenBlockNum + } + + if lastSeenBlockNum > lastHeadReference+uint64(freq) { + o.commandChan <- &Command{cmd: commandName, logger: o.zlogger, params: params} + lastHeadReference = lastSeenBlockNum + } + } +} diff --git a/firehose/firehose-core/node-manager/superviser.go b/firehose/firehose-core/node-manager/superviser.go new file mode 100644 index 0000000..3b2aaeb --- /dev/null +++ b/firehose/firehose-core/node-manager/superviser.go @@ -0,0 +1,99 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node_manager + +import ( + "time" + + logplugin "github.com/streamingfast/firehose-core/node-manager/log_plugin" +) + +type StartOption string + +var EnableDebugDeepmindOption = StartOption("enable-debug-firehose-logs") +var DisableDebugDeepmindOption = StartOption("disable-debug-firehose-logs") + +type ShutterInterface interface { + Shutdown(error) + OnTerminating(func(error)) + OnTerminated(func(error)) + IsTerminated() bool + IsTerminating() bool + Terminated() <-chan struct{} +} + +type ChainSuperviser interface { + ShutterInterface + + GetCommand() string + GetName() string + ServerID() (string, error) + + RegisterLogPlugin(plugin logplugin.LogPlugin) + Start(options ...StartOption) error + Stop() error + + IsRunning() bool + Stopped() <-chan struct{} + + LastExitCode() int + LastLogLines() []string + LastSeenBlockNum() uint64 +} + +type MonitorableChainSuperviser interface { + Monitor() +} + +type ProducerChainSuperviser interface { + IsProducing() (bool, error) + IsActiveProducer() bool + + ResumeProduction() error + PauseProduction() error + + WaitUntilEndOfNextProductionRound(timeout time.Duration) error +} + +type ProductionState int + +const ( + StatePre ProductionState = iota // Just before we produce, don't restart + StateProducing // We're producing right now + StatePost // Right after production + StateStale // We haven't produced for 12 minutes +) + +func (s ProductionState) String() string { + switch s { + case StatePre: + return "pre" + case StateProducing: + return "producing" + case StatePost: + return "post" + case StateStale: + return "stale" + default: + return "unknown" + } +} + +type ProductionEvent int + +const ( + EventProduced ProductionEvent = iota + EventReceived +) diff --git a/firehose/firehose-core/node-manager/superviser/superviser.go b/firehose/firehose-core/node-manager/superviser/superviser.go new file mode 100644 index 0000000..ad433ed --- /dev/null +++ b/firehose/firehose-core/node-manager/superviser/superviser.go @@ -0,0 +1,383 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package superviser + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/ShinyTrinkets/overseer" + "github.com/streamingfast/bstream" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + logplugin "github.com/streamingfast/firehose-core/node-manager/log_plugin" + "github.com/streamingfast/shutter" + "go.uber.org/zap" +) + +// mindreaderPlugin can be used to check if `logplugin.LogPlugin` is actually a mindreader one. +// This is not in `mindreader` package to not introduce a cycle dependencies +type mindreaderPlugin interface { + logplugin.LogPlugin + + LastSeenBlock() bstream.BlockRef +} + +type Superviser struct { + *shutter.Shutter + Binary string + Arguments []string + // Env represents the environment variables the command will run with, the `nil` + // is handled differently than the `[]string{}` empty case. In the `nil` case, + // the process inherits from the parent process. In the empty case, it starts + // without any variables set. + Env []string + Logger *zap.Logger + + cmd *overseer.Cmd + cmdLock sync.Mutex + + logPlugins []logplugin.LogPlugin + logPluginsLock sync.RWMutex + + enableDeepMind bool +} + +func New(logger *zap.Logger, binary string, arguments []string) *Superviser { + s := &Superviser{ + Shutter: shutter.New(), + Binary: binary, + Arguments: arguments, + Logger: logger, + } + + s.Shutter.OnTerminating(func(_ error) { + s.Logger.Info("superviser is terminating") + + if err := s.Stop(); err != nil { + s.Logger.Error("failed to stop supervised node process", zap.Error(err)) + } + + s.Logger.Info("shutting down plugins", zap.Int("last_exit_code", s.LastExitCode())) + s.endLogPlugins() + }) + + return s +} + +func (s *Superviser) RegisterLogPlugin(plugin logplugin.LogPlugin) { + s.logPluginsLock.Lock() + defer s.logPluginsLock.Unlock() + + s.logPlugins = append(s.logPlugins, plugin) + if shut, ok := plugin.(logplugin.Shutter); ok { + s.Logger.Info("adding superviser shutdown to plugins", zap.String("plugin_name", plugin.Name())) + shut.OnTerminating(func(err error) { + if !s.IsTerminating() { + s.Logger.Info("superviser shutting down because of a plugin", zap.String("plugin_name", plugin.Name())) + go s.Shutdown(err) + } + }) + } + + s.Logger.Info("registered log plugin", zap.Int("plugin count", len(s.logPlugins))) +} + +func (s *Superviser) GetLogPlugins() []logplugin.LogPlugin { + s.logPluginsLock.RLock() + defer s.logPluginsLock.RUnlock() + + return s.logPlugins +} + +func (s *Superviser) setDeepMindDebug(enabled bool) { + s.Logger.Info("setting deep mind debug mode", zap.Bool("enabled", enabled)) + for _, logPlugin := range s.logPlugins { + if v, ok := logPlugin.(nodeManager.DeepMindDebuggable); ok { + v.DebugDeepMind(enabled) + } + } +} + +func (s *Superviser) Stopped() <-chan struct{} { + if s.cmd != nil { + return s.cmd.Done() + } + return nil +} + +func (s *Superviser) LastExitCode() int { + if s.cmd != nil { + return s.cmd.Status().Exit + } + return 0 +} + +func (s *Superviser) LastLogLines() []string { + if s.hasToConsolePlugin() { + // There is no point in showing the last log lines when the user already saw it through the to console log plugin + return nil + } + + for _, plugin := range s.logPlugins { + if v, ok := plugin.(*logplugin.KeepLastLinesLogPlugin); ok { + return v.LastLines() + } + } + + return nil +} + +func (s *Superviser) LastSeenBlockNum() uint64 { + for _, plugin := range s.GetLogPlugins() { + if v, ok := plugin.(mindreaderPlugin); ok { + return v.LastSeenBlock().Num() + } + } + return 0 +} + +func (s *Superviser) Start(options ...nodeManager.StartOption) error { + for _, opt := range options { + if opt == nodeManager.EnableDebugDeepmindOption { + s.setDeepMindDebug(true) + } + if opt == nodeManager.DisableDebugDeepmindOption { + s.setDeepMindDebug(false) + } + } + + for _, plugin := range s.logPlugins { + plugin.Launch() + } + + s.cmdLock.Lock() + defer s.cmdLock.Unlock() + + if s.cmd != nil { + if s.cmd.State == overseer.STARTING || s.cmd.State == overseer.RUNNING { + s.Logger.Info("underlying process already running, nothing to do") + return nil + } + + if s.cmd.State == overseer.STOPPING { + s.Logger.Info("underlying process is currently stopping, waiting for it to finish") + <-s.cmd.Done() + } + } + + s.Logger.Info("creating new command instance and launch read loop", zap.String("binary", s.Binary), zap.Strings("arguments", s.Arguments)) + var args []interface{} + for _, a := range s.Arguments { + args = append(args, a) + } + + s.cmd = overseer.NewCmd(s.Binary, s.Arguments, overseer.Options{Streaming: true, Env: s.Env}) + + go s.start(s.cmd) + + return nil +} + +func (s *Superviser) Stop() error { + s.cmdLock.Lock() + defer s.cmdLock.Unlock() + + s.Logger.Info("supervisor received a stop request, terminating supervised node process") + + if !s.isRunning() { + s.Logger.Info("underlying process is not running, nothing to do") + return nil + } + + if s.cmd.State == overseer.STARTING || s.cmd.State == overseer.RUNNING { + s.Logger.Info("stopping underlying process") + err := s.cmd.Stop() + if err != nil { + s.Logger.Error("failed to stop overseer cmd", zap.Error(err)) + return err + } + } + + // Blocks until command finished completely + s.Logger.Debug("blocking until command actually ends") + +nodeProcessDone: + for { + select { + case <-s.cmd.Done(): + break nodeProcessDone + case <-time.After(500 * time.Millisecond): + s.Logger.Debug("still blocking until command actually ends") + } + } + + s.Logger.Info("supervised process has been terminated") + + s.Logger.Info("waiting for stdout and stderr to be drained", s.getProcessOutputStatsLogFields()...) + for { + if s.isBufferEmpty() { + break + } + + s.Logger.Debug("draining stdout and stderr", s.getProcessOutputStatsLogFields()...) + time.Sleep(500 * time.Millisecond) + } + + s.Logger.Info("stdout and stderr are now drained") + + // Must be after `for { ... }` as `s.cmd` is used within the loop and also before it via call to `getProcessOutputStatsLogFields` + s.cmd = nil + + return nil +} + +func (s *Superviser) getProcessOutputStats() (stdoutLineCount, stderrLineCount int) { + if s.cmd != nil { + return len(s.cmd.Stdout), len(s.cmd.Stderr) + } + + return +} + +func (s *Superviser) getProcessOutputStatsLogFields() []zap.Field { + stdoutLineCount, stderrLineCount := s.getProcessOutputStats() + + return []zap.Field{zap.Int("stdout_len", stdoutLineCount), zap.Int("stderr_len", stderrLineCount)} +} + +func (s *Superviser) IsRunning() bool { + s.cmdLock.Lock() + defer s.cmdLock.Unlock() + + return s.isRunning() +} + +// This one assuming the lock is properly held already +func (s *Superviser) isRunning() bool { + if s.cmd == nil { + return false + } + return s.cmd.State == overseer.STARTING || s.cmd.State == overseer.RUNNING || s.cmd.State == overseer.STOPPING +} + +func (s *Superviser) isBufferEmpty() bool { + if s.cmd == nil { + return true + } + return len(s.cmd.Stdout) == 0 && len(s.cmd.Stderr) == 0 +} + +func (s *Superviser) start(cmd *overseer.Cmd) { + statusChan := cmd.Start() + + processTerminated := false + for { + select { + case status := <-statusChan: + processTerminated = true + if status.Exit == 0 { + s.Logger.Info("command terminated with zero status", s.getProcessOutputStatsLogFields()...) + } else { + s.Logger.Error(fmt.Sprintf("command terminated with non-zero status, last log lines:\n%s\n", formatLogLines(s.LastLogLines())), overseerStatusLogFields(status)...) + } + + case line := <-cmd.Stdout: + s.processLogLine(line) + case line := <-cmd.Stderr: + s.processLogLine(line) + } + + if processTerminated { + s.Logger.Debug("command terminated but continue read loop to fully consume stdout/sdterr line channels", zap.Bool("buffer_empty", s.isBufferEmpty())) + if s.isBufferEmpty() { + return + } + } + } +} + +func overseerStatusLogFields(status overseer.Status) []zap.Field { + fields := []zap.Field{ + zap.String("command", status.Cmd), + zap.Int("exit_code", status.Exit), + } + + if status.Error != nil { + fields = append(fields, zap.String("error", status.Error.Error())) + } + + if status.PID != 0 { + fields = append(fields, zap.Int("pid", status.PID)) + } + + if status.Runtime > 0 { + fields = append(fields, zap.Duration("runtime", time.Duration(status.Runtime*float64(time.Second)))) + } + + if status.StartTs > 0 { + fields = append(fields, zap.Time("started_at", time.Unix(0, status.StartTs))) + } + + if status.StopTs > 0 { + fields = append(fields, zap.Time("stopped_at", time.Unix(0, status.StopTs))) + } + + return fields +} + +func formatLogLines(lines []string) string { + if len(lines) == 0 { + return "" + } + + formattedLines := make([]string, len(lines)) + for i, line := range lines { + formattedLines[i] = " " + line + } + + return strings.Join(formattedLines, "\n") +} + +func (s *Superviser) endLogPlugins() { + s.logPluginsLock.Lock() + defer s.logPluginsLock.Unlock() + + for _, plugin := range s.logPlugins { + s.Logger.Info("stopping plugin", zap.String("plugin_name", plugin.Name())) + plugin.Stop() + } + s.Logger.Info("all plugins closed") +} + +func (s *Superviser) processLogLine(line string) { + s.logPluginsLock.Lock() + defer s.logPluginsLock.Unlock() + + for _, plugin := range s.logPlugins { + plugin.LogLine(line) + } +} + +func (s *Superviser) hasToConsolePlugin() bool { + for _, plugin := range s.logPlugins { + if _, ok := plugin.(*logplugin.ToConsoleLogPlugin); ok { + return true + } + } + + return false +} diff --git a/firehose/firehose-core/node-manager/superviser/superviser_test.go b/firehose/firehose-core/node-manager/superviser/superviser_test.go new file mode 100644 index 0000000..1f628b2 --- /dev/null +++ b/firehose/firehose-core/node-manager/superviser/superviser_test.go @@ -0,0 +1,137 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package superviser + +import ( + "os" + "testing" + "time" + + logplugin "github.com/streamingfast/firehose-core/node-manager/log_plugin" + "github.com/streamingfast/logging" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +var infiniteScript = ` + echo "Starting" + while true; do + sleep 0.25 + echo "In loop" + done +` + +var zlog = zap.NewNop() + +func init() { + if os.Getenv("DEBUG") != "" || os.Getenv("TRACE") == "true" { + zlog, _ := zap.NewDevelopment() + logging.Override(zlog) + } +} + +var waitDefaultTimeout = 500 * time.Millisecond + +func TestSuperviser_NotRunningAfterCreation(t *testing.T) { + assert.Equal(t, false, testSuperviserInfinite().IsRunning()) +} + +func TestSuperviser_StartsCorrectly(t *testing.T) { + superviser := testSuperviserInfinite() + defer superviser.Stop() + + lineChan := make(chan string) + superviser.RegisterLogPlugin(logplugin.LogPluginFunc(func(line string) { + lineChan <- line + })) + + go superviser.Start() + + waitForSuperviserTaskCompletion(superviser) + waitForOutput(t, lineChan, waitDefaultTimeout) + + assert.Equal(t, true, superviser.IsRunning()) +} + +func TestSuperviser_CanBeRestartedCorrectly(t *testing.T) { + superviser := testSuperviserInfinite() + defer superviser.Stop() + + lineChan := make(chan string) + superviser.RegisterLogPlugin(logplugin.LogPluginFunc(func(line string) { + lineChan <- line + })) + + go superviser.Start() + waitForSuperviserTaskCompletion(superviser) + waitForOutput(t, lineChan, waitDefaultTimeout) + + superviser.Stop() + assert.Equal(t, false, superviser.IsRunning()) + + go superviser.Start() + waitForSuperviserTaskCompletion(superviser) + waitForOutput(t, lineChan, waitDefaultTimeout) + + assert.Equal(t, true, superviser.IsRunning()) +} + +func TestSuperviser_CapturesStdoutCorrectly(t *testing.T) { + superviser := testSuperviserSh("echo first; sleep 0.1; echo second") + defer superviser.Stop() + + lineChan := make(chan string) + superviser.RegisterLogPlugin(logplugin.LogPluginFunc(func(line string) { + lineChan <- line + })) + + go superviser.Start() + waitForSuperviserTaskCompletion(superviser) + + var lines []string + lines = append(lines, waitForOutput(t, lineChan, waitDefaultTimeout)) + lines = append(lines, waitForOutput(t, lineChan, waitDefaultTimeout)) + + assert.Equal(t, []string{"first", "second"}, lines) +} + +func testSuperviserBash(script string) *Superviser { + return New(zlog, "bash", []string{"-c", script}) +} + +func testSuperviserSh(script string) *Superviser { + return New(zlog, "sh", []string{"-c", script}) +} + +func testSuperviserInfinite() *Superviser { + return testSuperviserSh(infiniteScript) +} + +func waitForSuperviserTaskCompletion(superviser *Superviser) { + superviser.cmdLock.Lock() + superviser.cmdLock.Unlock() +} + +func waitForOutput(t *testing.T, lineChan chan string, timeout time.Duration) (line string) { + select { + case line = <-lineChan: + return + case <-time.After(timeout): + t.Error("no line seen before timeout") + } + + // Will fail before reaching this line + return "" +} diff --git a/firehose/firehose-core/node-manager/types.go b/firehose/firehose-core/node-manager/types.go new file mode 100644 index 0000000..450b155 --- /dev/null +++ b/firehose/firehose-core/node-manager/types.go @@ -0,0 +1,25 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node_manager + +import pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + +type DeepMindDebuggable interface { + DebugDeepMind(enabled bool) +} + +type HeadBlockUpdater func(block *pbbstream.Block) error + +type OnBlockWritten func(block *pbbstream.Block) error diff --git a/firehose/firehose-core/node-manager/utils.go b/firehose/firehose-core/node-manager/utils.go new file mode 100644 index 0000000..90999db --- /dev/null +++ b/firehose/firehose-core/node-manager/utils.go @@ -0,0 +1,23 @@ +package node_manager + +import ( + "fmt" + "syscall" +) + +func AugmentStackSizeLimit() error { + // Set ulimit for stack + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_STACK, &rLimit) + if err != nil { + return fmt.Errorf("getting rlimit: %w", err) + } + rLimit.Cur = 67104768 + + err = syscall.Setrlimit(syscall.RLIMIT_STACK, &rLimit) + if err != nil { + return fmt.Errorf("setting rlimit: %w", err) + } + + return nil +} diff --git a/firehose/firehose-fuel/pb/generate.sh b/firehose/firehose-core/pb/generate.sh similarity index 100% rename from firehose/firehose-fuel/pb/generate.sh rename to firehose/firehose-core/pb/generate.sh diff --git a/firehose/firehose-fuel/pb/last_generate.txt b/firehose/firehose-core/pb/last_generate.txt similarity index 100% rename from firehose/firehose-fuel/pb/last_generate.txt rename to firehose/firehose-core/pb/last_generate.txt diff --git a/firehose/firehose-core/pb/sf/fuel/type/v1/type.go b/firehose/firehose-core/pb/sf/fuel/type/v1/type.go new file mode 100644 index 0000000..9c780ee --- /dev/null +++ b/firehose/firehose-core/pb/sf/fuel/type/v1/type.go @@ -0,0 +1,35 @@ +package pbfuel + +import ( + "encoding/hex" + "time" +) + +func (b *Block) GetFirehoseBlockID() string { + return hex.EncodeToString(b.Id) +} + +func (b *Block) GetFirehoseBlockNumber() uint64 { + return uint64(b.Height) +} + +func (b *Block) GetFirehoseBlockParentNumber() uint64 { + return b.GetFirehoseBlockNumber() - 1 +} + +func (b *Block) GetFirehoseBlockParentID() string { + return hex.EncodeToString(b.PrevId) +} + +func (b *Block) GetFirehoseBlockTime() time.Time { + return time.Unix(0, int64(b.Timestamp)).UTC() +} + +func (b *Block) GetFirehoseBlockVersion() int32 { + // TODO: This needs to be adapted for your own version used in pbbstream + return 1 +} + +func (b *Block) GetFirehoseBlockLIBNum() uint64 { + return b.GetFirehoseBlockNumber() +} diff --git a/firehose/firehose-fuel/pb/sf/fuel/type/v1/type.pb.go b/firehose/firehose-core/pb/sf/fuel/type/v1/type.pb.go similarity index 100% rename from firehose/firehose-fuel/pb/sf/fuel/type/v1/type.pb.go rename to firehose/firehose-core/pb/sf/fuel/type/v1/type.pb.go diff --git a/firehose/firehose-core/proto/README.md b/firehose/firehose-core/proto/README.md new file mode 100644 index 0000000..8153491 --- /dev/null +++ b/firehose/firehose-core/proto/README.md @@ -0,0 +1,19 @@ +### Protobuf Registry + +The well-known Protobuf definitions are pulled from Buf Build Registry. This makes `firehose-core` able to decode some well-known block files directly. + +#### Re-generate + +To re-generate the well-known types, simply do: + +```bash +go generate ./protoregistry +``` + +While being at the root of the **project** (if you run from this directory here, adjust `./protoregistry` to `.`). Before re-generating, ensure you have push to Buf Registry the latest version of the definitions. + +#### Add new well-known types + +Push your definitions to the Buf Registry. Edit file [./generator/generator.go](./generator/generator.go) and add the Buf Registry path of the package in `wellKnownProtoRepos` variables. + +Then [re-generate Protobuf definitions](#re-generate) and send a PR with the changes. diff --git a/firehose/firehose-core/proto/generator/generator.go b/firehose/firehose-core/proto/generator/generator.go new file mode 100644 index 0000000..dcc663c --- /dev/null +++ b/firehose/firehose-core/proto/generator/generator.go @@ -0,0 +1,155 @@ +package main + +import ( + "bufio" + "context" + "embed" + "encoding/hex" + "fmt" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" + "text/template" + "time" + + "buf.build/gen/go/bufbuild/reflect/connectrpc/go/buf/reflect/v1beta1/reflectv1beta1connect" + reflectv1beta1 "buf.build/gen/go/bufbuild/reflect/protocolbuffers/go/buf/reflect/v1beta1" + connect "connectrpc.com/connect" + "github.com/iancoleman/strcase" + "github.com/streamingfast/cli" + "google.golang.org/protobuf/proto" +) + +//go:embed *.gotmpl +var templates embed.FS + +var wellKnownProtoRepos = []string{ + "buf.build/streamingfast/firehose-ethereum", + "buf.build/streamingfast/firehose-near", + "buf.build/streamingfast/firehose-solana", + "buf.build/streamingfast/firehose-bitcoin", + "buf.build/pinax/firehose-antelope", + "buf.build/pinax/firehose-arweave", + "buf.build/pinax/firehose-beacon", + "buf.build/salka1988/firehose-fuel", +} + +func main() { + cli.Ensure(len(os.Args) == 3, "go run ./generator ") + + authToken := os.Getenv("BUFBUILD_AUTH_TOKEN") + if authToken == "" { + log.Fatalf("You must set the BUFBUILD_AUTH_TOKEN environment variable to generate well known registry. See https://buf.build/docs/bsr/authentication") + return + } + + output := os.Args[1] + packageName := os.Args[2] + + client := reflectv1beta1connect.NewFileDescriptorSetServiceClient( + http.DefaultClient, + "https://buf.build", + ) + + var protofiles []ProtoFile + + for _, wellKnownProtoRepo := range wellKnownProtoRepos { + request := connect.NewRequest(&reflectv1beta1.GetFileDescriptorSetRequest{ + Module: wellKnownProtoRepo, + }) + request.Header().Set("Authorization", "Bearer "+authToken) + fileDescriptorSet, err := client.GetFileDescriptorSet(context.Background(), request) + if err != nil { + log.Fatalf("failed to call file descriptor set service: %v", err) + return + } + + for _, file := range fileDescriptorSet.Msg.FileDescriptorSet.File { + cnt, err := proto.Marshal(file) + if err != nil { + log.Fatalf("failed to marshall proto file %s: %v", file.GetName(), err) + return + } + name := "" + if file.Name != nil { + name = *file.Name + } + + bytesEncoding := "hex" + + if strings.Contains(name, "solana") { + bytesEncoding = "base58" + } + + protofiles = append(protofiles, ProtoFile{ + Name: name, + Data: cnt, + BufRegistryPackageURL: buildBufRegistryPackageURL(wellKnownProtoRepo, deferPtr(file.Package, ""), fileDescriptorSet.Msg.Version), + BytesEncoding: bytesEncoding, + }) + } + // avoid hitting the buf.build rate limit + time.Sleep(1 * time.Second) + } + + tmpl, err := template.New("wellknown").Funcs(templateFunctions()).ParseFS(templates, "*.gotmpl") + cli.NoError(err, "Unable to instantiate template") + + var out io.Writer = os.Stdout + if output != "-" { + cli.NoError(os.MkdirAll(filepath.Dir(output), os.ModePerm), "Unable to create output file directories") + + file, err := os.Create(output) + cli.NoError(err, "Unable to open output file") + + bufferedOut := bufio.NewWriter(file) + out = bufferedOut + + defer func() { + bufferedOut.Flush() + file.Close() + }() + } + + err = tmpl.ExecuteTemplate(out, "template.gotmpl", map[string]any{ + "Package": packageName, + "ProtoFiles": protofiles, + }) + cli.NoError(err, "Unable to render template") + + fmt.Println("Done creating well known registry") +} + +func buildBufRegistryPackageURL(module string, fullyQualifiedPackage string, revision string) string { + // Example full URL is https://buf.build/streamingfast/firehose-near/docs/146e2ae8bd9b49e29b132b8627f29a70:sf.near.type.v1 + return fmt.Sprintf("https://%s/docs/%s:%s", module, revision, fullyQualifiedPackage) +} + +type ProtoFile struct { + Name string + Data []byte + BufRegistryPackageURL string + BytesEncoding string +} + +func templateFunctions() template.FuncMap { + return template.FuncMap{ + "lower": strings.ToLower, + "pascalCase": strcase.ToCamel, + "camelCase": strcase.ToLowerCamel, + "toHex": func(in []byte) string { + return hex.EncodeToString(in) + }, + } +} + +func deferPtr[T any](in *T, orValue T) T { + if in == nil { + return orValue + } + + return *in +} diff --git a/firehose/firehose-core/proto/generator/template.gotmpl b/firehose/firehose-core/proto/generator/template.gotmpl new file mode 100644 index 0000000..1da9e24 --- /dev/null +++ b/firehose/firehose-core/proto/generator/template.gotmpl @@ -0,0 +1,15 @@ +// Code generated by 'go run github.com/streamingfast/firehose-core/protoregistry/generator well_known.go protoregistry', DO NOT EDIT! +package {{.Package}} + +var wellKnownTypes []*WellKnownType + +func init() { + wellKnownTypes = []*WellKnownType{ +{{- range .ProtoFiles}} + { + // {{.Name}} ({{.BufRegistryPackageURL}}) + proto: "{{.Data | toHex}}", + }, +{{- end}} + } +} \ No newline at end of file diff --git a/firehose/firehose-core/proto/registry.go b/firehose/firehose-core/proto/registry.go new file mode 100644 index 0000000..c975a87 --- /dev/null +++ b/firehose/firehose-core/proto/registry.go @@ -0,0 +1,132 @@ +package proto + +import ( + "errors" + "fmt" + "strings" + + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/types/dynamicpb" + "google.golang.org/protobuf/types/known/anypb" +) + +// Generate the flags based on Go code in this project directly, this however +// creates a chicken & egg problem if there is compilation error within the project +// but to fix them we must re-generate it. +//go:generate go run ./generator well_known_types.go proto + +type Registry struct { + Types *protoregistry.Types + Files *protoregistry.Files +} + +func NewRegistry(chainFileDescriptor protoreflect.FileDescriptor, protoPaths ...string) (*Registry, error) { + r := &Registry{ + Types: new(protoregistry.Types), + Files: new(protoregistry.Files), + } + // Proto paths have the highest precedence, so we register them first + if len(protoPaths) > 0 { + if err := r.RegisterFiles(protoPaths); err != nil { + return nil, fmt.Errorf("register proto files: %w", err) + } + } + + // Chain file descriptor has the second highest precedence, it always + // override built-in types if defined. + if chainFileDescriptor != nil { + err := r.RegisterFileDescriptor(chainFileDescriptor) + if err != nil { + return nil, fmt.Errorf("register chain file descriptor: %w", err) + } + } + + //Last are well known types, they have the lowest precedence + err := RegisterWellKnownFileDescriptors(r) + if err != nil { + return nil, fmt.Errorf("registering well known file descriptors: %w", err) + } + + return r, nil +} + +func (r *Registry) RegisterFiles(files []string) error { + if len(files) == 0 { + return nil + } + + fileDescriptors, err := parseProtoFiles(files) + if err != nil { + return fmt.Errorf("parsing proto files: %w", err) + } + + return r.RegisterFileDescriptors(fileDescriptors) +} + +func (r *Registry) RegisterFileDescriptors(fds []protoreflect.FileDescriptor) error { + for _, fd := range fds { + err := r.RegisterFileDescriptor(fd) + if err != nil { + return fmt.Errorf("registering proto file: %w", err) + } + } + return nil +} +func (r *Registry) RegisterFileDescriptor(fd protoreflect.FileDescriptor) error { + path := fd.Path() + _, err := r.Files.FindFileByPath(path) + + if err != nil { + if errors.Is(err, protoregistry.NotFound) { + // NewRegistry the new file descriptor. + if err := r.Files.RegisterFile(fd); err != nil { + return fmt.Errorf("registering proto file: %w", err) + } + + // Create a new MessageType using the registered FileDescriptor + msgCount := fd.Messages().Len() + for i := 0; i < msgCount; i++ { + messageType := fd.Messages().Get(i) + if messageType == nil { + return fmt.Errorf("message type not found in the registered file") + } + + dmt := dynamicpb.NewMessageType(messageType) // NewRegistry the MessageType + err := r.Types.RegisterMessage(dmt) + if err != nil { + return fmt.Errorf("registering message type: %w", err) + } + } + return nil + } + return fmt.Errorf("finding file by path: %w", err) + } + + //that mean we already have this file registered, we need to check if we have the message type registered + return nil +} + +func (r *Registry) Unmarshal(a *anypb.Any) (*dynamicpb.Message, error) { + messageType, err := r.Types.FindMessageByURL(a.TypeUrl) + if err != nil { + return nil, fmt.Errorf("failed to find message '%s': %v", urlToMessageFullName(a.TypeUrl), err) + } + + message := dynamicpb.NewMessage(messageType.Descriptor()) + err = a.UnmarshalTo(message) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal message: %v", err) + } + + return message, nil +} + +func urlToMessageFullName(url string) protoreflect.FullName { + message := protoreflect.FullName(url) + if i := strings.LastIndexByte(url, '/'); i >= 0 { + message = message[i+len("/"):] + } + + return message +} diff --git a/firehose/firehose-core/proto/registry_test.go b/firehose/firehose-core/proto/registry_test.go new file mode 100644 index 0000000..57b1144 --- /dev/null +++ b/firehose/firehose-core/proto/registry_test.go @@ -0,0 +1,102 @@ +package proto + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/dynamicpb" + "google.golang.org/protobuf/types/known/anypb" +) + +func TestUnmarshal(t *testing.T) { + acme := readTestProto(t, "testdata/acme") + + type args struct { + typeURL string + value []byte + } + tests := []struct { + name string + protoPaths []string + want func(tt *testing.T, out *dynamicpb.Message) + assertion require.ErrorAssertionFunc + value []byte + typeURL string + }{ + { + name: "chain alone", + typeURL: "sf.acme.type.v1.Block", + want: func(tt *testing.T, out *dynamicpb.Message) { + h := out.Get(out.Descriptor().Fields().ByName("hash")).String() + blockNum := out.Get(out.Descriptor().Fields().ByName("num")).Uint() + assert.Equal(tt, "", h) + assert.Equal(tt, uint64(0), blockNum) + }, + assertion: require.NoError, + }, + { + name: "overriding built-in chain with proto path", + protoPaths: []string{"testdata/override_acme"}, + typeURL: "sf.acme.type.v1.Block", + want: func(tt *testing.T, out *dynamicpb.Message) { + // If you reach this point following a panic in the Go test, the reason there + // is a panic here is because the override_ethereum.proto file is taking + // precedence over the ethereum.proto file, which is not what we want. + h := out.Get(out.Descriptor().Fields().ByName("hash_custom")).String() + blockNum := out.Get(out.Descriptor().Fields().ByName("num_custom")).Uint() + assert.Equal(tt, "", h) + assert.Equal(tt, uint64(0), blockNum) + }, + assertion: require.NoError, + }, + { + name: "well-know chain (ethereum)", + typeURL: "sf.ethereum.type.v2.Block", + value: []byte{0x18, 0x0a}, + want: func(tt *testing.T, out *dynamicpb.Message) { + // If you reach this point following a panic in the Go test, the reason there + // is a panic here is because the override_ethereum.proto file is taking + // precedence over the ethereum.proto file, which is not what we want. + cn := out.Get(out.Descriptor().Fields().ByName("number")).Uint() + assert.Equal(tt, uint64(10), cn) + }, + assertion: require.NoError, + }, + { + name: "overridding well-know chain (ethereum) with proto path", + protoPaths: []string{"testdata/override"}, + typeURL: "sf.ethereum.type.v2.Block", + value: []byte{0x18, 0x0a}, + want: func(tt *testing.T, out *dynamicpb.Message) { + // If you reach this point following a panic in the Go test, the reason there + // is a panic here is because the override_ethereum.proto file is taking + // precedence over the ethereum.proto file, which is not what we want. + cn := out.Get(out.Descriptor().Fields().ByName("number_custom")).Uint() + assert.Equal(tt, uint64(10), cn) + }, + assertion: require.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + registry, err := NewRegistry(acme, tt.protoPaths...) + require.NoError(t, err) + + a := &anypb.Any{TypeUrl: "type.googleapis.com/" + tt.typeURL, Value: tt.value} + out, err := registry.Unmarshal(a) + tt.assertion(t, err) + + tt.want(t, out) + }) + } +} +func readTestProto(t *testing.T, file string) protoreflect.FileDescriptor { + t.Helper() + + descs, err := parseProtoFiles([]string{file}) + require.NoError(t, err) + + return descs[0] +} diff --git a/firehose/firehose-core/proto/testdata/acme/acme.proto b/firehose/firehose-core/proto/testdata/acme/acme.proto new file mode 100644 index 0000000..f547b76 --- /dev/null +++ b/firehose/firehose-core/proto/testdata/acme/acme.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package sf.acme.type.v1; + +message Block { + string hash = 1; + uint64 num = 2; +} \ No newline at end of file diff --git a/firehose/firehose-core/proto/testdata/override/sf/ethereum/type/v2/type.proto b/firehose/firehose-core/proto/testdata/override/sf/ethereum/type/v2/type.proto new file mode 100644 index 0000000..b83655e --- /dev/null +++ b/firehose/firehose-core/proto/testdata/override/sf/ethereum/type/v2/type.proto @@ -0,0 +1,666 @@ +syntax = "proto3"; + +package sf.ethereum.type.v2; + +option go_package = "github.com/streamingfast/firehose-ethereum/types/pb/sf/ethereum/type/v2;pbeth"; + +import "google/protobuf/timestamp.proto"; + +message Block { + + // Hash is the block's hash. + bytes hash_custom = 2; + // Number is the block's height at which this block was mined. + uint64 number_custom = 3; + // Size is the size in bytes of the RLP encoding of the block according to Ethereum + // rules. + uint64 size = 4; + // Header contain's the block's header information like its parent hash, the merkel root hash + // and all other information the form a block. + BlockHeader header = 5; + + // Uncles represents block produced with a valid solution but were not actually choosen + // as the canonical block for the given height so they are mostly "forked" blocks. + // + // If the Block has been produced using the Proof of Stake consensus algorithm, this + // field will actually be always empty. + repeated BlockHeader uncles = 6; + + // TransactionTraces hold the execute trace of all the transactions that were executed + // in this block. In in there that you will find most of the Ethereum data model. + repeated TransactionTrace transaction_traces = 10; + + // BalanceChanges here is the array of ETH transfer that happened at the block level + // outside of the normal transaction flow of a block. The best example of this is mining + // reward for the block mined, the transfer of ETH to the miner happens outside the normal + // transaction flow of the chain and is recorded as a `BalanceChange` here since we cannot + // attached it to any transaction. + // + // Only available in DetailLevel: EXTENDED + repeated BalanceChange balance_changes = 11; + + enum DetailLevel{ + DETAILLEVEL_EXTENDED = 0; + // DETAILLEVEL_TRACE = 1; // TBD + DETAILLEVEL_BASE = 2; + } + + // DetailLevel affects the data available in this block. + // + // EXTENDED describes the most complete block, with traces, balance changes, storage changes. It is extracted during the execution of the block. + // BASE describes a block that contains only the block header, transaction receipts and event logs: everything that can be extracted using the base JSON-RPC interface (https://ethereum.org/en/developers/docs/apis/json-rpc/#json-rpc-methods) + // Furthermore, the eth_getTransactionReceipt call has been avoided because it brings only minimal improvements at the cost of requiring an archive node or a full node with complete transaction index. + DetailLevel detail_level = 12; + + // CodeChanges here is the array of smart code change that happened that happened at the block level + // outside of the normal transaction flow of a block. Some Ethereum's fork like BSC and Polygon + // has some capabilities to upgrade internal smart contracts used usually to track the validator + // list. + // + // On hard fork, some procedure runs to upgrade the smart contract code to a new version. In those + // network, a `CodeChange` for each modified smart contract on upgrade would be present here. Note + // that this happen rarely, so the vast majority of block will have an empty list here. + // Only available in DetailLevel: EXTENDED + repeated CodeChange code_changes = 20; + + reserved 40; // bool filtering_applied = 40 [deprecated = true]; + reserved 41; // string filtering_include_filter_expr = 41 [deprecated = true]; + reserved 42; // string filtering_exclude_filter_expr = 42 [deprecated = true]; + + // Ver represents that data model version of the block, it is used internally by Firehose on Ethereum + // as a validation that we are reading the correct version. + int32 ver = 1; +} + +message BlockHeader { + bytes parent_hash = 1; + + // Uncle hash of the block, some reference it as `sha3Uncles`, but `sha3`` is badly worded, so we prefer `uncle_hash`, also + // referred as `ommers` in EIP specification. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347`. + bytes uncle_hash = 2; + + bytes coinbase = 3; + bytes state_root = 4; + bytes transactions_root = 5; + bytes receipt_root = 6; + bytes logs_bloom = 7; + + // Difficulty is the difficulty of the Proof of Work algorithm that was required to compute a solution. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0x00`. + BigInt difficulty = 8; + + // TotalDifficulty is the sum of all previous blocks difficulty including this block difficulty. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to the terminal total difficulty + // that was required to transition to Proof of Stake algorithm, which varies per network. It is set to + // 58 750 000 000 000 000 000 000 on Ethereum Mainnet and to 10 790 000 on Ethereum Testnet Goerli. + BigInt total_difficulty = 17; + + uint64 number = 9; + uint64 gas_limit = 10; + uint64 gas_used = 11; + google.protobuf.Timestamp timestamp = 12; + + // ExtraData is free-form bytes included in the block by the "miner". While on Yellow paper of + // Ethereum this value is maxed to 32 bytes, other consensus algorithm like Clique and some other + // forks are using bigger values to carry special consensus data. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field is strictly enforced to be <= 32 bytes. + bytes extra_data = 13; + + // MixHash is used to prove, when combined with the `nonce` that sufficient amount of computation has been + // achieved and that the solution found is valid. + bytes mix_hash = 14; + + // Nonce is used to prove, when combined with the `mix_hash` that sufficient amount of computation has been + // achieved and that the solution found is valid. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0`. + uint64 nonce = 15; + + // Hash is the hash of the block which is actually the computation: + // + // Keccak256(rlp([ + // parent_hash, + // uncle_hash, + // coinbase, + // state_root, + // transactions_root, + // receipt_root, + // logs_bloom, + // difficulty, + // number, + // gas_limit, + // gas_used, + // timestamp, + // extra_data, + // mix_hash, + // nonce, + // base_fee_per_gas (to be included, only if London Fork is active) + // withdrawals_root (to be included, only if Shangai Fork is active) + // ])) + // + bytes hash = 16; + + // Base fee per gas according to EIP-1559 (e.g. London Fork) rules, only set if London is present/active on the chain. + BigInt base_fee_per_gas = 18; + + // Withdrawals root hash according to EIP-4895 (e.g. Shangai Fork) rules, only set if Shangai is present/active on the chain. + // + // Only available in DetailLevel: EXTENDED + bytes withdrawals_root = 19; + + // Only available in DetailLevel: EXTENDED + Uint64NestedArray tx_dependency = 20; +} + +message Uint64NestedArray { + repeated Uint64Array val = 1; +} + +message Uint64Array { + repeated uint64 val = 1; +} + +message BigInt { + bytes bytes = 1; +} + +message TransactionTrace { + // consensus + bytes to = 1; + uint64 nonce = 2; + // GasPrice represents the effective price that has been paid for each gas unit of this transaction. Over time, the + // Ethereum rules changes regarding GasPrice field here. Before London fork, the GasPrice was always set to the + // fixed gas price. After London fork, this value has different meaning depending on the transaction type (see `Type` field). + // + // In cases where `TransactionTrace.Type == TRX_TYPE_LEGACY || TRX_TYPE_ACCESS_LIST`, then GasPrice has the same meaning + // as before the London fork. + // + // In cases where `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE`, then GasPrice is the effective gas price paid + // for the transaction which is equals to `BlockHeader.BaseFeePerGas + TransactionTrace.` + BigInt gas_price = 3; + + // GasLimit is the maximum of gas unit the sender of the transaction is willing to consume when perform the EVM + // execution of the whole transaction + uint64 gas_limit = 4; + + // Value is the amount of Ether transferred as part of this transaction. + BigInt value = 5; + + // Input data the transaction will receive for execution of EVM. + bytes input = 6; + + // V is the recovery ID value for the signature Y point. + bytes v = 7; + + // R is the signature's X point on the elliptic curve (32 bytes). + bytes r = 8; + + // S is the signature's Y point on the elliptic curve (32 bytes). + bytes s = 9; + + // GasUsed is the total amount of gas unit used for the whole execution of the transaction. + uint64 gas_used = 10; + + // Type represents the Ethereum transaction type, available only since EIP-2718 & EIP-2930 activation which happened on Berlin fork. + // The value is always set even for transaction before Berlin fork because those before the fork are still legacy transactions. + Type type = 12; + + enum Type { + // All transactions that ever existed prior Berlin fork before EIP-2718 was implemented. + TRX_TYPE_LEGACY = 0; + + // Transaction that specicy an access list of contract/storage_keys that is going to be used + // in this transaction. + // + // Added in Berlin fork (EIP-2930). + TRX_TYPE_ACCESS_LIST = 1; + + // Transaction that specifis an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the + // max base gas gee and max priority gas fee to pay for this transaction. Transaction's of those type are + // executed against EIP-1559 rules which dictates a dynamic gas cost based on the congestion of the network. + TRX_TYPE_DYNAMIC_FEE = 2; + + // Arbitrum-specific transactions + TRX_TYPE_ARBITRUM_DEPOSIT = 100; + TRX_TYPE_ARBITRUM_UNSIGNED = 101; + TRX_TYPE_ARBITRUM_CONTRACT = 102; + TRX_TYPE_ARBITRUM_RETRY = 104; + TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE = 105; + TRX_TYPE_ARBITRUM_INTERNAL = 106; + TRX_TYPE_ARBITRUM_LEGACY = 120; + + } + + // AcccessList represents the storage access this transaction has agreed to do in which case those storage + // access cost less gas unit per access. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_ACCESS_LIST || TRX_TYPE_DYNAMIC_FEE` which + // is possible only if Berlin (TRX_TYPE_ACCESS_LIST) nor London (TRX_TYPE_DYNAMIC_FEE) fork are active on the chain. + repeated AccessTuple access_list = 14; + + // MaxFeePerGas is the maximum fee per gas the user is willing to pay for the transaction gas used. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only + // if Londong fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED + BigInt max_fee_per_gas = 11; + + // MaxPriorityFeePerGas is priority fee per gas the user to pay in extra to the miner on top of the block's + // base fee. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only + // if London fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED + BigInt max_priority_fee_per_gas = 13; + + // meta + uint32 index = 20; + bytes hash = 21; + bytes from = 22; + // Only available in DetailLevel: EXTENDED + bytes return_data = 23; + // Only available in DetailLevel: EXTENDED + bytes public_key = 24; + uint64 begin_ordinal = 25; + uint64 end_ordinal = 26; + + // TransactionTraceStatus is the status of the transaction execution and will let you know if the transaction + // was successful or not. + // + // A successful transaction has been recorded to the blockchain's state for calls in it that were successful. + // This means it's possible only a subset of the calls were properly recorded, refer to [calls[].state_reverted] field + // to determine which calls were reverted. + // + // A quirks of the Ethereum protocol is that a transaction `FAILED` or `REVERTED` still affects the blockchain's + // state for **some** of the state changes. Indeed, in those cases, the transactions fees are still paid to the miner + // which means there is a balance change for the transaction's emitter (e.g. `from`) to pay the gas fees, an optional + // balance change for gas refunded to the transaction's emitter (e.g. `from`) and a balance change for the miner who + // received the transaction fees. There is also a nonce change for the transaction's emitter (e.g. `from`). + // + // This means that to properly record the state changes for a transaction, you need to conditionally procees the + // transaction's status. + // + // For a `SUCCEEDED` transaction, you iterate over the `calls` array and record the state changes for each call for + // which `state_reverted == false` (if a transaction succeeded, the call at #0 will always `state_reverted == false` + // because it aligns with the transaction). + // + // For a `FAILED` or `REVERTED` transaction, you iterate over the root call (e.g. at #0, will always exist) for + // balance changes you process those where `reason` is either `REASON_GAS_BUY`, `REASON_GAS_REFUND` or + // `REASON_REWARD_TRANSACTION_FEE` and for nonce change, still on the root call, you pick the nonce change which the + // smallest ordinal (if more than one). + TransactionTraceStatus status = 30; + + TransactionReceipt receipt = 31; + + // Only available in DetailLevel: EXTENDED + repeated Call calls = 32; +} + +// AccessTuple represents a list of storage keys for a given contract's address and is used +// for AccessList construction. +message AccessTuple { + bytes address = 1; + repeated bytes storage_keys = 2; +} + +enum TransactionTraceStatus { + UNKNOWN = 0; + SUCCEEDED = 1; + FAILED = 2; + REVERTED = 3; +} + +message TransactionReceipt { + // State root is an intermediate state_root hash, computed in-between transactions to make + // **sure** you could build a proof and point to state in the middle of a block. Geth client + // uses `PostState + root + PostStateOrStatus`` while Parity used `status_code, root...`` this piles + // hardforks, see (read the EIPs first): + // - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md + // + // Moreover, the notion of `Outcome`` in parity, which segregates the two concepts, which are + // stored in the same field `status_code`` can be computed based on such a hack of the `state_root` + // field, following `EIP-658`. + // + // Before Byzantinium hard fork, this field is always empty. + bytes state_root = 1; + uint64 cumulative_gas_used = 2; + bytes logs_bloom = 3; + repeated Log logs = 4; +} + +message Log { + bytes address = 1; + repeated bytes topics = 2; + bytes data = 3; + + // Index is the index of the log relative to the transaction. This index + // is always populated regardless of the state revertion of the the call + // that emitted this log. + // + // Only available in DetailLevel: EXTENDED + uint32 index = 4; + + // BlockIndex represents the index of the log relative to the Block. + // + // An **important** notice is that this field will be 0 when the call + // that emitted the log has been reverted by the chain. + // + // Currently, there is two locations where a Log can be obtained: + // - block.transaction_traces[].receipt.logs[] + // - block.transaction_traces[].calls[].logs[] + // + // In the `receipt` case, the logs will be populated only when the call + // that emitted them has not been reverted by the chain and when in this + // position, the `blockIndex` is always populated correctly. + // + // In the case of `calls` case, for `call` where `stateReverted == true`, + // the `blockIndex` value will always be 0. + uint32 blockIndex = 6; + + uint64 ordinal = 7; +} + +message Call { + uint32 index = 1; + uint32 parent_index = 2; + uint32 depth = 3; + CallType call_type = 4; + bytes caller = 5; + bytes address = 6; + BigInt value = 7; + uint64 gas_limit = 8; + uint64 gas_consumed = 9; + bytes return_data = 13; + bytes input = 14; + bool executed_code = 15; + bool suicide = 16; + + /* hex representation of the hash -> preimage */ + map keccak_preimages = 20; + repeated StorageChange storage_changes = 21; + repeated BalanceChange balance_changes = 22; + repeated NonceChange nonce_changes = 24; + repeated Log logs = 25; + repeated CodeChange code_changes = 26; + + // Deprecated: repeated bytes created_accounts + reserved 27; + + repeated GasChange gas_changes = 28; + + // Deprecated: repeated GasEvent gas_events + reserved 29; + + // In Ethereum, a call can be either: + // - Successfull, execution passes without any problem encountered + // - Failed, execution failed, and remaining gas should be consumed + // - Reverted, execution failed, but only gas consumed so far is billed, remaining gas is refunded + // + // When a call is either `failed` or `reverted`, the `status_failed` field + // below is set to `true`. If the status is `reverted`, then both `status_failed` + // and `status_reverted` are going to be set to `true`. + bool status_failed = 10; + bool status_reverted = 12; + + // Populated when a call either failed or reverted, so when `status_failed == true`, + // see above for details about those flags. + string failure_reason = 11; + + // This field represents wheter or not the state changes performed + // by this call were correctly recorded by the blockchain. + // + // On Ethereum, a transaction can record state changes even if some + // of its inner nested calls failed. This is problematic however since + // a call will invalidate all its state changes as well as all state + // changes performed by its child call. This means that even if a call + // has a status of `SUCCESS`, the chain might have reverted all the state + // changes it performed. + // + // ```text + // Trx 1 + // Call #1 + // Call #2 + // Call #3 + // |--- Failure here + // Call #4 + // ``` + // + // In the transaction above, while Call #2 and Call #3 would have the + // status `EXECUTED`. + // + // If you check all calls and check only `state_reverted` flag, you might be missing + // some balance changes and nonce changes. This is because when a full transaction fails + // in ethereum (e.g. `calls.all(x.state_reverted == true)`), there is still the transaction + // fee that are recorded to the chain. + // + // Refer to [TransactionTrace#status] field for more details about the handling you must + // perform. + bool state_reverted = 30; + + uint64 begin_ordinal = 31; + uint64 end_ordinal = 32; + + repeated AccountCreation account_creations = 33; + + reserved 50; // repeated ERC20BalanceChange erc20_balance_changes = 50 [deprecated = true]; + reserved 51; // repeated ERC20TransferEvent erc20_transfer_events = 51 [deprecated = true]; + reserved 60; // bool filtering_matched = 60 [deprecated = true]; +} + +enum CallType { + UNSPECIFIED = 0; + CALL = 1; // direct? what's the name for `Call` alone? + CALLCODE = 2; + DELEGATE = 3; + STATIC = 4; + CREATE = 5; // create2 ? any other form of calls? +} + +message StorageChange { + bytes address = 1; + bytes key = 2; + bytes old_value = 3; + bytes new_value = 4; + + uint64 ordinal = 5; +} + +message BalanceChange { + bytes address = 1; + BigInt old_value = 2; + BigInt new_value = 3; + Reason reason = 4; + + // Obtain all balanche change reasons under deep mind repository: + // + // ```shell + // ack -ho 'BalanceChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq + // ``` + enum Reason { + REASON_UNKNOWN = 0; + REASON_REWARD_MINE_UNCLE = 1; + REASON_REWARD_MINE_BLOCK = 2; + REASON_DAO_REFUND_CONTRACT = 3; + REASON_DAO_ADJUST_BALANCE = 4; + REASON_TRANSFER = 5; + REASON_GENESIS_BALANCE = 6; + REASON_GAS_BUY = 7; + REASON_REWARD_TRANSACTION_FEE = 8; + REASON_REWARD_FEE_RESET = 14; + REASON_GAS_REFUND = 9; + REASON_TOUCH_ACCOUNT = 10; + REASON_SUICIDE_REFUND = 11; + REASON_SUICIDE_WITHDRAW = 13; + REASON_CALL_BALANCE_OVERRIDE = 12; + // Used on chain(s) where some Ether burning happens + REASON_BURN = 15; + REASON_WITHDRAWAL = 16; + } + + uint64 ordinal = 5; +} + +message NonceChange { + bytes address = 1; + uint64 old_value = 2; + uint64 new_value = 3; + uint64 ordinal = 4; +} + +message AccountCreation { + bytes account = 1; + uint64 ordinal = 2; +} + +message CodeChange { + bytes address = 1; + bytes old_hash = 2; + bytes old_code = 3; + bytes new_hash = 4; + bytes new_code = 5; + + uint64 ordinal = 6; +} + +// The gas change model represents the reason why some gas cost has occurred. +// The gas is computed per actual op codes. Doing them completely might prove +// overwhelming in most cases. +// +// Hence, we only index some of them, those that are costy like all the calls +// one, log events, return data, etc. +message GasChange { + uint64 old_value = 1; + uint64 new_value = 2; + Reason reason = 3; + + // Obtain all gas change reasons under deep mind repository: + // + // ```shell + // ack -ho 'GasChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq + // ``` + enum Reason { + REASON_UNKNOWN = 0; + // REASON_CALL is the amount of gas that will be charged for a 'CALL' opcode executed by the EVM + REASON_CALL = 1; + // REASON_CALL_CODE is the amount of gas that will be charged for a 'CALLCODE' opcode executed by the EVM + REASON_CALL_CODE = 2; + // REASON_CALL_DATA_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM + REASON_CALL_DATA_COPY = 3; + // REASON_CODE_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM + REASON_CODE_COPY = 4; + // REASON_CODE_STORAGE is the amount of gas that will be charged for code storage + REASON_CODE_STORAGE = 5; + // REASON_CONTRACT_CREATION is the amount of gas that will be charged for a 'CREATE' opcode executed by the EVM and for the gas + // burned for a CREATE, today controlled by EIP150 rules + REASON_CONTRACT_CREATION = 6; + // REASON_CONTRACT_CREATION2 is the amount of gas that will be charged for a 'CREATE2' opcode executed by the EVM and for the gas + // burned for a CREATE2, today controlled by EIP150 rules + REASON_CONTRACT_CREATION2 = 7; + // REASON_DELEGATE_CALL is the amount of gas that will be charged for a 'DELEGATECALL' opcode executed by the EVM + REASON_DELEGATE_CALL = 8; + // REASON_EVENT_LOG is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM + REASON_EVENT_LOG = 9; + // REASON_EXT_CODE_COPY is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM + REASON_EXT_CODE_COPY = 10; + // REASON_FAILED_EXECUTION is the burning of the remaining gas when the execution failed without a revert + REASON_FAILED_EXECUTION = 11; + // REASON_INTRINSIC_GAS is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + // always exactly one of those per transaction + REASON_INTRINSIC_GAS = 12; + // GasChangePrecompiledContract is the amount of gas that will be charged for a precompiled contract execution + REASON_PRECOMPILED_CONTRACT = 13; + // REASON_REFUND_AFTER_EXECUTION is the amount of gas that will be refunded to the caller after the execution of the call, + // if there is left over at the end of execution + REASON_REFUND_AFTER_EXECUTION = 14; + // REASON_RETURN is the amount of gas that will be charged for a 'RETURN' opcode executed by the EVM + REASON_RETURN = 15; + // REASON_RETURN_DATA_COPY is the amount of gas that will be charged for a 'RETURNDATACOPY' opcode executed by the EVM + REASON_RETURN_DATA_COPY = 16; + // REASON_REVERT is the amount of gas that will be charged for a 'REVERT' opcode executed by the EVM + REASON_REVERT = 17; + // REASON_SELF_DESTRUCT is the amount of gas that will be charged for a 'SELFDESTRUCT' opcode executed by the EVM + REASON_SELF_DESTRUCT = 18; + // REASON_STATIC_CALL is the amount of gas that will be charged for a 'STATICALL' opcode executed by the EVM + REASON_STATIC_CALL = 19; + + // REASON_STATE_COLD_ACCESS is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules + // + // Added in Berlin fork (Geth 1.10+) + REASON_STATE_COLD_ACCESS = 20; + + // REASON_TX_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call + // + // Added as new tracing reason in Geth, available only on some chains + REASON_TX_INITIAL_BALANCE = 21; + // REASON_TX_REFUNDS is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + // this generates an increase in gas. There is only one such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + REASON_TX_REFUNDS = 22; + // REASON_TX_LEFT_OVER_RETURNED is the amount of gas left over at the end of transaction's execution that will be returned + // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + // There is at most one of such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + REASON_TX_LEFT_OVER_RETURNED = 23; + + // REASON_CALL_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per call. + // + // Added as new tracing reason in Geth, available only on some chains + REASON_CALL_INITIAL_BALANCE = 24; + // REASON_CALL_LEFT_OVER_RETURNED is the amount of gas left over that will be returned to the caller, this change will always + // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + // will be emitted. + REASON_CALL_LEFT_OVER_RETURNED = 25; + } + + uint64 ordinal = 4; +} + +// HeaderOnlyBlock is used to optimally unpack the [Block] structure (note the +// corresponding message number for the `header` field) while consuming less +// memory, when only the `header` is desired. +// +// WARN: this is a client-side optimization pattern and should be moved in the +// consuming code. +message HeaderOnlyBlock { + BlockHeader header = 5; +} + +// BlockWithRefs is a lightweight block, with traces and transactions +// purged from the `block` within, and only. It is used in transports +// to pass block data around. +message BlockWithRefs { + string id = 1; + Block block = 2; + TransactionRefs transaction_trace_refs = 3; + bool irreversible = 4; +} + +message TransactionTraceWithBlockRef { + TransactionTrace trace = 1; + BlockRef block_ref = 2; +} + +message TransactionRefs { + repeated bytes hashes = 1; +} + +message BlockRef { + bytes hash = 1; + uint64 number = 2; +} diff --git a/firehose/firehose-core/proto/testdata/override_acme/acme.proto b/firehose/firehose-core/proto/testdata/override_acme/acme.proto new file mode 100644 index 0000000..4a34bad --- /dev/null +++ b/firehose/firehose-core/proto/testdata/override_acme/acme.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +package sf.acme.type.v1; + +message Block { + string hash_custom = 1; + uint64 num_custom = 2; +} \ No newline at end of file diff --git a/firehose/firehose-core/proto/utils.go b/firehose/firehose-core/proto/utils.go new file mode 100644 index 0000000..b5a24cf --- /dev/null +++ b/firehose/firehose-core/proto/utils.go @@ -0,0 +1,69 @@ +package proto + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/jhump/protoreflect/desc/protoparse" + "google.golang.org/protobuf/reflect/protoreflect" +) + +func parseProtoFiles(importPaths []string) (fds []protoreflect.FileDescriptor, err error) { + userDir, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("get user home dir: %w", err) + } + + var ip []string + for _, importPath := range importPaths { + if importPath == "~" { + importPath = userDir + } else if strings.HasPrefix(importPath, "~/") { + importPath = filepath.Join(userDir, importPath[2:]) + } + + importPath, err = filepath.Abs(importPath) + if err != nil { + return nil, fmt.Errorf("getting absolute path for %q: %w", importPath, err) + } + + if !strings.HasSuffix(importPath, "/") { + importPath += "/" + } + ip = append(ip, importPath) + } + + parser := protoparse.Parser{ + ImportPaths: ip, + } + + var protoFiles []string + for _, importPath := range ip { + err := filepath.Walk(importPath, + func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, ".proto") && !info.IsDir() { + protoFiles = append(protoFiles, strings.TrimPrefix(path, importPath)) + } + return nil + }) + if err != nil { + return nil, fmt.Errorf("walking import path %q: %w", importPath, err) + } + } + + parsed, err := parser.ParseFiles(protoFiles...) + if err != nil { + return nil, fmt.Errorf("parsing proto files: %w", err) + } + + for _, fd := range parsed { + fds = append(fds, fd.UnwrapFile()) + } + return + +} diff --git a/firehose/firehose-core/proto/well_known.go b/firehose/firehose-core/proto/well_known.go new file mode 100644 index 0000000..53f5952 --- /dev/null +++ b/firehose/firehose-core/proto/well_known.go @@ -0,0 +1,51 @@ +package proto + +import ( + "encoding/hex" + "fmt" + + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protodesc" + "google.golang.org/protobuf/reflect/protoreflect" + "google.golang.org/protobuf/types/descriptorpb" +) + +type WellKnownType struct { + proto string + BytesEncoding string +} + +func RegisterWellKnownFileDescriptors(registry *Registry) error { + + for _, wt := range wellKnownTypes { + fd, err := protoToFileDescriptor(registry, wt.proto) + if err != nil { + return fmt.Errorf("generating proto file: %w", err) + } + err = registry.RegisterFileDescriptor(fd) + if err != nil { + return fmt.Errorf("registering file descriptor: %w", err) + } + + } + return nil +} + +func protoToFileDescriptor(registry *Registry, in string) (protoreflect.FileDescriptor, error) { + protoBytes, err := hex.DecodeString(in) + if err != nil { + panic(fmt.Errorf("failed to hex decode payload: %w", err)) + } + + fileDescriptorProto := &descriptorpb.FileDescriptorProto{} + if err := proto.Unmarshal(protoBytes, fileDescriptorProto); err != nil { + return nil, fmt.Errorf("failed to unmarshal file descriptor: %w", err) + } + + fd, err := protodesc.NewFile(fileDescriptorProto, registry.Files) + if err != nil { + return nil, fmt.Errorf("creating new file descriptor: %w", err) + + } + return fd, nil +} diff --git a/firehose/firehose-core/proto/well_known_types.go b/firehose/firehose-core/proto/well_known_types.go new file mode 100644 index 0000000..79c699f --- /dev/null +++ b/firehose/firehose-core/proto/well_known_types.go @@ -0,0 +1,77 @@ +// Code generated by 'go run github.com/streamingfast/firehose-core/protoregistry/generator well_known.go protoregistry', DO NOT EDIT! +package proto + +var wellKnownTypes []*WellKnownType + +func init() { + wellKnownTypes = []*WellKnownType{ + { + // sf/ethereum/substreams/v1/rpc.proto (https://buf.build/streamingfast/firehose-ethereum/docs/95c5cc71e5c941519b979c4f2c583caf:sf.ethereum.substreams.v1) + proto: "0a2373662f657468657265756d2f73756273747265616d732f76312f7270632e70726f746f121973662e657468657265756d2e73756273747265616d732e763122440a0852706343616c6c7312380a0563616c6c7318012003280b32222e73662e657468657265756d2e73756273747265616d732e76312e52706343616c6c520563616c6c7322360a0752706343616c6c12170a07746f5f6164647218012001280c5206746f4164647212120a046461746118022001280c52046461746122540a0c527063526573706f6e73657312440a09726573706f6e73657318012003280b32262e73662e657468657265756d2e73756273747265616d732e76312e527063526573706f6e73655209726573706f6e73657322370a0b527063526573706f6e736512100a0372617718012001280c520372617712160a066661696c656418022001280852066661696c656442575a556769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d657468657265756d2f74797065732f70622f73662f657468657265756d2f73756273747265616d732f76313b70626574687373620670726f746f33", + }, + { + // sf/ethereum/transform/v1/transforms.proto (https://buf.build/streamingfast/firehose-ethereum/docs/95c5cc71e5c941519b979c4f2c583caf:sf.ethereum.transform.v1) + proto: "0a2973662f657468657265756d2f7472616e73666f726d2f76312f7472616e73666f726d732e70726f746f121873662e657468657265756d2e7472616e73666f726d2e763122d6010a0e436f6d62696e656446696c74657212440a0b6c6f675f66696c7465727318012003280b32232e73662e657468657265756d2e7472616e73666f726d2e76312e4c6f6746696c746572520a6c6f6746696c7465727312490a0c63616c6c5f66696c7465727318022003280b32262e73662e657468657265756d2e7472616e73666f726d2e76312e43616c6c546f46696c746572520b63616c6c46696c7465727312330a1673656e645f616c6c5f626c6f636b5f68656164657273180320012808521373656e64416c6c426c6f636b4865616465727322560a0e4d756c74694c6f6746696c74657212440a0b6c6f675f66696c7465727318012003280b32232e73662e657468657265756d2e7472616e73666f726d2e76312e4c6f6746696c746572520a6c6f6746696c7465727322540a094c6f6746696c746572121c0a0961646472657373657318012003280c520961646472657373657312290a106576656e745f7369676e61747572657318022003280c520f6576656e745369676e617475726573225e0a114d756c746943616c6c546f46696c74657212490a0c63616c6c5f66696c7465727318012003280b32262e73662e657468657265756d2e7472616e73666f726d2e76312e43616c6c546f46696c746572520b63616c6c46696c74657273224c0a0c43616c6c546f46696c746572121c0a0961646472657373657318012003280c5209616464726573736573121e0a0a7369676e61747572657318022003280c520a7369676e617475726573220c0a0a4865616465724f6e6c79425a5a586769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d657468657265756d2f74797065732f70622f73662f657468657265756d2f7472616e73666f726d2f76313b70627472616e73666f726d620670726f746f33", + }, + { + // google/protobuf/timestamp.proto (https://buf.build/streamingfast/firehose-ethereum/docs/95c5cc71e5c941519b979c4f2c583caf:google.protobuf) + proto: "0a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f120f676f6f676c652e70726f746f627566223b0a0954696d657374616d7012180a077365636f6e647318012001280352077365636f6e647312140a056e616e6f7318022001280552056e616e6f734285010a13636f6d2e676f6f676c652e70726f746f627566420e54696d657374616d7050726f746f50015a32676f6f676c652e676f6c616e672e6f72672f70726f746f6275662f74797065732f6b6e6f776e2f74696d657374616d707062f80101a20203475042aa021e476f6f676c652e50726f746f6275662e57656c6c4b6e6f776e5479706573620670726f746f33", + }, + { + // sf/ethereum/type/v2/type.proto (https://buf.build/streamingfast/firehose-ethereum/docs/95c5cc71e5c941519b979c4f2c583caf:sf.ethereum.type.v2) + proto: "0a1e73662f657468657265756d2f747970652f76322f747970652e70726f746f121373662e657468657265756d2e747970652e76321a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f228e050a05426c6f636b12120a046861736818022001280c52046861736812160a066e756d62657218032001280452066e756d62657212120a0473697a65180420012804520473697a6512380a0668656164657218052001280b32202e73662e657468657265756d2e747970652e76322e426c6f636b486561646572520668656164657212380a06756e636c657318062003280b32202e73662e657468657265756d2e747970652e76322e426c6f636b4865616465725206756e636c657312540a127472616e73616374696f6e5f747261636573180a2003280b32252e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e547261636552117472616e73616374696f6e547261636573124b0a0f62616c616e63655f6368616e676573180b2003280b32222e73662e657468657265756d2e747970652e76322e42616c616e63654368616e6765520e62616c616e63654368616e67657312490a0c64657461696c5f6c6576656c180c2001280e32262e73662e657468657265756d2e747970652e76322e426c6f636b2e44657461696c4c6576656c520b64657461696c4c6576656c12420a0c636f64655f6368616e67657318142003280b321f2e73662e657468657265756d2e747970652e76322e436f64654368616e6765520b636f64654368616e676573123c0a0c73797374656d5f63616c6c7318152003280b32192e73662e657468657265756d2e747970652e76322e43616c6c520b73797374656d43616c6c7312100a037665721801200128055203766572223d0a0b44657461696c4c6576656c12180a1444455441494c4c4556454c5f455854454e444544100012140a1044455441494c4c4556454c5f4241534510024a04082810294a040829102a4a04082a102b22d2070a0b426c6f636b486561646572121f0a0b706172656e745f6861736818012001280c520a706172656e7448617368121d0a0a756e636c655f6861736818022001280c5209756e636c6548617368121a0a08636f696e6261736518032001280c5208636f696e62617365121d0a0a73746174655f726f6f7418042001280c52097374617465526f6f74122b0a117472616e73616374696f6e735f726f6f7418052001280c52107472616e73616374696f6e73526f6f7412210a0c726563656970745f726f6f7418062001280c520b72656365697074526f6f74121d0a0a6c6f67735f626c6f6f6d18072001280c52096c6f6773426c6f6f6d123b0a0a646966666963756c747918082001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520a646966666963756c747912460a10746f74616c5f646966666963756c747918112001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520f746f74616c446966666963756c747912160a066e756d62657218092001280452066e756d626572121b0a096761735f6c696d6974180a2001280452086761734c696d697412190a086761735f75736564180b2001280452076761735573656412380a0974696d657374616d70180c2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d70121d0a0a65787472615f64617461180d2001280c520965787472614461746112190a086d69785f68617368180e2001280c52076d69784861736812140a056e6f6e6365180f2001280452056e6f6e636512120a046861736818102001280c52046861736812440a10626173655f6665655f7065725f67617318122001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520d6261736546656550657247617312290a107769746864726177616c735f726f6f7418132001280c520f7769746864726177616c73526f6f74124b0a0d74785f646570656e64656e637918142001280b32262e73662e657468657265756d2e747970652e76322e55696e7436344e65737465644172726179520c7478446570656e64656e637912270a0d626c6f625f6761735f757365641816200128044800520b626c6f6247617355736564880101122b0a0f6578636573735f626c6f625f6761731817200128044801520d657863657373426c6f62476173880101122c0a12706172656e745f626561636f6e5f726f6f7418182001280c5210706172656e74426561636f6e526f6f7442100a0e5f626c6f625f6761735f7573656442120a105f6578636573735f626c6f625f67617322470a1155696e7436344e6573746564417272617912320a0376616c18012003280b32202e73662e657468657265756d2e747970652e76322e55696e7436344172726179520376616c221f0a0b55696e743634417272617912100a0376616c180120032804520376616c221e0a06426967496e7412140a05627974657318012001280c5205627974657322ab0b0a105472616e73616374696f6e5472616365120e0a02746f18012001280c5202746f12140a056e6f6e636518022001280452056e6f6e636512380a096761735f707269636518032001280b321b2e73662e657468657265756d2e747970652e76322e426967496e7452086761735072696365121b0a096761735f6c696d697418042001280452086761734c696d697412310a0576616c756518052001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520576616c756512140a05696e70757418062001280c5205696e707574120c0a017618072001280c520176120c0a017218082001280c520172120c0a017318092001280c52017312190a086761735f75736564180a20012804520767617355736564123e0a0474797065180c2001280e322a2e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e54726163652e5479706552047479706512410a0b6163636573735f6c697374180e2003280b32202e73662e657468657265756d2e747970652e76322e4163636573735475706c65520a6163636573734c69737412420a0f6d61785f6665655f7065725f676173180b2001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520c6d617846656550657247617312530a186d61785f7072696f726974795f6665655f7065725f676173180d2001280b321b2e73662e657468657265756d2e747970652e76322e426967496e7452146d61785072696f7269747946656550657247617312140a05696e64657818142001280d5205696e64657812120a046861736818152001280c52046861736812120a0466726f6d18162001280c520466726f6d121f0a0b72657475726e5f6461746118172001280c520a72657475726e44617461121d0a0a7075626c69635f6b657918182001280c52097075626c69634b657912230a0d626567696e5f6f7264696e616c181920012804520c626567696e4f7264696e616c121f0a0b656e645f6f7264696e616c181a20012804520a656e644f7264696e616c12430a06737461747573181e2001280e322b2e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e5472616365537461747573520673746174757312410a0772656365697074181f2001280b32272e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e52656365697074520772656365697074122f0a0563616c6c7318202003280b32192e73662e657468657265756d2e747970652e76322e43616c6c520563616c6c73121e0a08626c6f625f67617318212001280448005207626c6f6247617388010112490a10626c6f625f6761735f6665655f63617018222001280b321b2e73662e657468657265756d2e747970652e76322e426967496e744801520d626c6f62476173466565436170880101121f0a0b626c6f625f68617368657318232003280c520a626c6f6248617368657322c4020a045479706512130a0f5452585f545950455f4c4547414359100012180a145452585f545950455f4143434553535f4c495354100112180a145452585f545950455f44594e414d49435f464545100212110a0d5452585f545950455f424c4f421003121d0a195452585f545950455f415242495452554d5f4445504f5349541064121e0a1a5452585f545950455f415242495452554d5f554e5349474e45441065121e0a1a5452585f545950455f415242495452554d5f434f4e54524143541066121b0a175452585f545950455f415242495452554d5f5245545259106812260a225452585f545950455f415242495452554d5f5355424d49545f524554525941424c451069121e0a1a5452585f545950455f415242495452554d5f494e5445524e414c106a121c0a185452585f545950455f415242495452554d5f4c45474143591078420b0a095f626c6f625f67617342130a115f626c6f625f6761735f6665655f636170224a0a0b4163636573735475706c6512180a076164647265737318012001280c52076164647265737312210a0c73746f726167655f6b65797318022003280c520b73746f726167654b65797322c6020a125472616e73616374696f6e52656365697074121d0a0a73746174655f726f6f7418012001280c52097374617465526f6f74122e0a1363756d756c61746976655f6761735f75736564180220012804521163756d756c617469766547617355736564121d0a0a6c6f67735f626c6f6f6d18032001280c52096c6f6773426c6f6f6d122c0a046c6f677318042003280b32182e73662e657468657265756d2e747970652e76322e4c6f6752046c6f677312270a0d626c6f625f6761735f757365641805200128044800520b626c6f624761735573656488010112460a0e626c6f625f6761735f707269636518062001280b321b2e73662e657468657265756d2e747970652e76322e426967496e744801520c626c6f62476173507269636588010142100a0e5f626c6f625f6761735f7573656442110a0f5f626c6f625f6761735f7072696365229b010a034c6f6712180a076164647265737318012001280c52076164647265737312160a06746f7069637318022003280c5206746f7069637312120a046461746118032001280c52046461746112140a05696e64657818042001280d5205696e646578121e0a0a626c6f636b496e64657818062001280d520a626c6f636b496e64657812180a076f7264696e616c18072001280452076f7264696e616c22b20a0a0443616c6c12140a05696e64657818012001280d5205696e64657812210a0c706172656e745f696e64657818022001280d520b706172656e74496e64657812140a05646570746818032001280d52056465707468123a0a0963616c6c5f7479706518042001280e321d2e73662e657468657265756d2e747970652e76322e43616c6c54797065520863616c6c5479706512160a0663616c6c657218052001280c520663616c6c657212180a076164647265737318062001280c52076164647265737312310a0576616c756518072001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520576616c7565121b0a096761735f6c696d697418082001280452086761734c696d697412210a0c6761735f636f6e73756d6564180920012804520b676173436f6e73756d6564121f0a0b72657475726e5f64617461180d2001280c520a72657475726e4461746112140a05696e707574180e2001280c5205696e70757412230a0d65786563757465645f636f6465180f20012808520c6578656375746564436f646512180a077375696369646518102001280852077375696369646512590a106b656363616b5f707265696d6167657318142003280b322e2e73662e657468657265756d2e747970652e76322e43616c6c2e4b656363616b507265696d61676573456e747279520f6b656363616b507265696d61676573124b0a0f73746f726167655f6368616e67657318152003280b32222e73662e657468657265756d2e747970652e76322e53746f726167654368616e6765520e73746f726167654368616e676573124b0a0f62616c616e63655f6368616e67657318162003280b32222e73662e657468657265756d2e747970652e76322e42616c616e63654368616e6765520e62616c616e63654368616e67657312450a0d6e6f6e63655f6368616e67657318182003280b32202e73662e657468657265756d2e747970652e76322e4e6f6e63654368616e6765520c6e6f6e63654368616e676573122c0a046c6f677318192003280b32182e73662e657468657265756d2e747970652e76322e4c6f6752046c6f677312420a0c636f64655f6368616e676573181a2003280b321f2e73662e657468657265756d2e747970652e76322e436f64654368616e6765520b636f64654368616e676573123f0a0b6761735f6368616e676573181c2003280b321e2e73662e657468657265756d2e747970652e76322e4761734368616e6765520a6761734368616e67657312230a0d7374617475735f6661696c6564180a20012808520c7374617475734661696c656412270a0f7374617475735f7265766572746564180c20012808520e737461747573526576657274656412250a0e6661696c7572655f726561736f6e180b20012809520d6661696c757265526561736f6e12250a0e73746174655f7265766572746564181e20012808520d7374617465526576657274656412230a0d626567696e5f6f7264696e616c181f20012804520c626567696e4f7264696e616c121f0a0b656e645f6f7264696e616c182020012804520a656e644f7264696e616c12510a116163636f756e745f6372656174696f6e7318212003280b32242e73662e657468657265756d2e747970652e76322e4163636f756e744372656174696f6e52106163636f756e744372656174696f6e731a420a144b656363616b507265696d61676573456e74727912100a036b657918012001280952036b657912140a0576616c7565180220012809520576616c75653a0238014a04081b101c4a04081d101e4a04083210334a04083310344a04083c103d228f010a0d53746f726167654368616e676512180a076164647265737318012001280c52076164647265737312100a036b657918022001280c52036b6579121b0a096f6c645f76616c756518032001280c52086f6c6456616c7565121b0a096e65775f76616c756518042001280c52086e657756616c756512180a076f7264696e616c18052001280452076f7264696e616c22cc050a0d42616c616e63654368616e676512180a076164647265737318012001280c52076164647265737312380a096f6c645f76616c756518022001280b321b2e73662e657468657265756d2e747970652e76322e426967496e7452086f6c6456616c756512380a096e65775f76616c756518032001280b321b2e73662e657468657265756d2e747970652e76322e426967496e7452086e657756616c756512410a06726561736f6e18042001280e32292e73662e657468657265756d2e747970652e76322e42616c616e63654368616e67652e526561736f6e5206726561736f6e12180a076f7264696e616c18052001280452076f7264696e616c22cf030a06526561736f6e12120a0e524541534f4e5f554e4b4e4f574e1000121c0a18524541534f4e5f5245574152445f4d494e455f554e434c451001121c0a18524541534f4e5f5245574152445f4d494e455f424c4f434b1002121e0a1a524541534f4e5f44414f5f524546554e445f434f4e54524143541003121d0a19524541534f4e5f44414f5f41444a5553545f42414c414e4345100412130a0f524541534f4e5f5452414e534645521005121a0a16524541534f4e5f47454e455349535f42414c414e4345100612120a0e524541534f4e5f4741535f425559100712210a1d524541534f4e5f5245574152445f5452414e53414354494f4e5f4645451008121b0a17524541534f4e5f5245574152445f4645455f5245534554100e12150a11524541534f4e5f4741535f524546554e44100912180a14524541534f4e5f544f5543485f4143434f554e54100a12190a15524541534f4e5f535549434944455f524546554e44100b121b0a17524541534f4e5f535549434944455f5749544844524157100d12200a1c524541534f4e5f43414c4c5f42414c414e43455f4f56455252494445100c120f0a0b524541534f4e5f4255524e100f12150a11524541534f4e5f5749544844524157414c1010227b0a0b4e6f6e63654368616e676512180a076164647265737318012001280c520761646472657373121b0a096f6c645f76616c756518022001280452086f6c6456616c7565121b0a096e65775f76616c756518032001280452086e657756616c756512180a076f7264696e616c18042001280452076f7264696e616c22450a0f4163636f756e744372656174696f6e12180a076163636f756e7418012001280c52076163636f756e7412180a076f7264696e616c18022001280452076f7264696e616c22ac010a0a436f64654368616e676512180a076164647265737318012001280c52076164647265737312190a086f6c645f6861736818022001280c52076f6c644861736812190a086f6c645f636f646518032001280c52076f6c64436f646512190a086e65775f6861736818042001280c52076e65774861736812190a086e65775f636f646518052001280c52076e6577436f646512180a076f7264696e616c18062001280452076f7264696e616c22e0060a094761734368616e6765121b0a096f6c645f76616c756518012001280452086f6c6456616c7565121b0a096e65775f76616c756518022001280452086e657756616c7565123d0a06726561736f6e18032001280e32252e73662e657468657265756d2e747970652e76322e4761734368616e67652e526561736f6e5206726561736f6e12180a076f7264696e616c18042001280452076f7264696e616c22bf050a06526561736f6e12120a0e524541534f4e5f554e4b4e4f574e1000120f0a0b524541534f4e5f43414c4c100112140a10524541534f4e5f43414c4c5f434f4445100212190a15524541534f4e5f43414c4c5f444154415f434f5059100312140a10524541534f4e5f434f44455f434f5059100412170a13524541534f4e5f434f44455f53544f524147451005121c0a18524541534f4e5f434f4e54524143545f4352454154494f4e1006121d0a19524541534f4e5f434f4e54524143545f4352454154494f4e32100712180a14524541534f4e5f44454c45474154455f43414c4c100812140a10524541534f4e5f4556454e545f4c4f47100912180a14524541534f4e5f4558545f434f44455f434f5059100a121b0a17524541534f4e5f4641494c45445f455845435554494f4e100b12180a14524541534f4e5f494e5452494e5349435f474153100c121f0a1b524541534f4e5f505245434f4d50494c45445f434f4e5452414354100d12210a1d524541534f4e5f524546554e445f41465445525f455845435554494f4e100e12110a0d524541534f4e5f52455455524e100f121b0a17524541534f4e5f52455455524e5f444154415f434f5059101012110a0d524541534f4e5f524556455254101112180a14524541534f4e5f53454c465f4445535452554354101212160a12524541534f4e5f5354415449435f43414c4c1013121c0a18524541534f4e5f53544154455f434f4c445f4143434553531014121d0a19524541534f4e5f54585f494e495449414c5f42414c414e4345101512150a11524541534f4e5f54585f524546554e4453101612200a1c524541534f4e5f54585f4c4546545f4f5645525f52455455524e45441017121f0a1b524541534f4e5f43414c4c5f494e495449414c5f42414c414e4345101812220a1e524541534f4e5f43414c4c5f4c4546545f4f5645525f52455455524e45441019224b0a0f4865616465724f6e6c79426c6f636b12380a0668656164657218052001280b32202e73662e657468657265756d2e747970652e76322e426c6f636b486561646572520668656164657222d1010a0d426c6f636b5769746852656673120e0a0269641801200128095202696412300a05626c6f636b18022001280b321a2e73662e657468657265756d2e747970652e76322e426c6f636b5205626c6f636b125a0a167472616e73616374696f6e5f74726163655f7265667318032001280b32242e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e5265667352147472616e73616374696f6e54726163655265667312220a0c697272657665727369626c65180420012808520c697272657665727369626c652297010a1c5472616e73616374696f6e547261636557697468426c6f636b526566123b0a05747261636518012001280b32252e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e547261636552057472616365123a0a09626c6f636b5f72656618022001280b321d2e73662e657468657265756d2e747970652e76322e426c6f636b5265665208626c6f636b52656622290a0f5472616e73616374696f6e5265667312160a0668617368657318012003280c520668617368657322360a08426c6f636b52656612120a046861736818012001280c52046861736812160a066e756d62657218022001280452066e756d6265722a4e0a165472616e73616374696f6e5472616365537461747573120b0a07554e4b4e4f574e1000120d0a095355434345454445441001120a0a064641494c45441002120c0a08524556455254454410032a590a0843616c6c54797065120f0a0b554e535045434946494544100012080a0443414c4c1001120c0a0843414c4c434f44451002120c0a0844454c45474154451003120a0a065354415449431004120a0a064352454154451005424f5a4d6769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d657468657265756d2f74797065732f70622f73662f657468657265756d2f747970652f76323b7062657468620670726f746f33", + }, + { + // sf/ethereum/trxstream/v1/trxstream.proto (https://buf.build/streamingfast/firehose-ethereum/docs/95c5cc71e5c941519b979c4f2c583caf:sf.ethereum.trxstream.v1) + proto: "0a2873662f657468657265756d2f74727873747265616d2f76312f74727873747265616d2e70726f746f121873662e657468657265756d2e74727873747265616d2e76311a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f1a1e73662f657468657265756d2f747970652f76322f747970652e70726f746f22140a125472616e73616374696f6e5265717565737422a5080a105472616e73616374696f6e537461746512570a0e70726576696f75735f737461746518012001280e32302e73662e657468657265756d2e74727873747265616d2e76312e5472616e73616374696f6e53746174652e5374617465520d70726576696f7573537461746512550a0d63757272656e745f737461746518022001280e32302e73662e657468657265756d2e74727873747265616d2e76312e5472616e73616374696f6e53746174652e5374617465520c63757272656e74537461746512550a0a7472616e736974696f6e180a2001280e32352e73662e657468657265756d2e74727873747265616d2e76312e5472616e73616374696f6e53746174652e5472616e736974696f6e520a7472616e736974696f6e12120a0468617368180b2001280c52046861736812370a0374727818032001280b32252e73662e657468657265756d2e74727873747265616d2e76312e5472616e73616374696f6e520374727812430a0c626c6f636b5f68656164657218042001280b32202e73662e657468657265756d2e747970652e76322e426c6f636b486561646572520b626c6f636b48656164657212540a127472616e73616374696f6e5f74726163657318052001280b32252e73662e657468657265756d2e747970652e76322e5472616e73616374696f6e547261636552117472616e73616374696f6e54726163657312220a0c636f6e6669726d6174696f6e180620012804520c636f6e6669726d6174696f6e124c0a11686561645f626c6f636b5f68656164657218072001280b32202e73662e657468657265756d2e747970652e76322e426c6f636b486561646572520f68656164426c6f636b48656164657212280a107265706c616365645f62795f6861736818082001280c520e7265706c6163656442794861736812480a1270656e64696e675f66697273745f7365656e180c2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70521070656e64696e6746697273745365656e12460a1170656e64696e675f6c6173745f7365656e180d2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520f70656e64696e674c6173745365656e229c010a0a5472616e736974696f6e120e0a0a5452414e535f494e4954100012100a0c5452414e535f504f4f4c45441001120f0a0b5452414e535f4d494e4544100212100a0c5452414e535f464f524b4544100312130a0f5452414e535f434f4e4649524d4544100412120a0e5452414e535f5245504c41434544100512200a1c5452414e535f53504543554c41544956454c595f4558454355544544100622550a05537461746512110a0d53544154455f554e4b4e4f574e100012110a0d53544154455f50454e44494e47100112120a0e53544154455f494e5f424c4f434b100212120a0e53544154455f5245504c41434544100322a5020a0b5472616e73616374696f6e120e0a02746f18012001280c5202746f12140a056e6f6e636518022001280452056e6f6e636512380a096761735f707269636518032001280b321b2e73662e657468657265756d2e747970652e76322e426967496e7452086761735072696365121b0a096761735f6c696d697418042001280452086761734c696d697412310a0576616c756518052001280b321b2e73662e657468657265756d2e747970652e76322e426967496e74520576616c756512140a05696e70757418062001280c5205696e707574120c0a017618072001280c520176120c0a017218082001280c520172120c0a017318092001280c52017312120a046861736818152001280c52046861736812120a0466726f6d18162001280c520466726f6d327a0a115472616e73616374696f6e53747265616d12650a0c5472616e73616374696f6e73122c2e73662e657468657265756d2e74727873747265616d2e76312e5472616e73616374696f6e526571756573741a252e73662e657468657265756d2e74727873747265616d2e76312e5472616e73616374696f6e3001425f5a5d6769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d657468657265756d2d707269762f74797065732f70622f73662f657468657265756d2f74727873747265616d2f76313b706274727873747265616d620670726f746f33", + }, + { + // sf/near/transform/v1/transform.proto (https://buf.build/streamingfast/firehose-near/docs/0b7e4efe1b9f447495fdfb54b7a6880f:sf.near.transform.v1) + proto: "0a2473662f6e6561722f7472616e73666f726d2f76312f7472616e73666f726d2e70726f746f121473662e6e6561722e7472616e73666f726d2e7631228f010a1242617369635265636569707446696c746572121a0a086163636f756e747318012003280952086163636f756e7473125d0a177072656669785f616e645f7375666669785f706169727318022003280b32262e73662e6e6561722e7472616e73666f726d2e76312e507265666978537566666978506169725214707265666978416e64537566666978506169727322420a105072656669785375666669785061697212160a06707265666978180120012809520670726566697812160a067375666669781802200128095206737566666978220c0a0a4865616465724f6e6c79424c5a4a6769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d6e6561722f70622f73662f6e6561722f7472616e73666f726d2f76313b70627472616e73666f726d620670726f746f33", + }, + { + // sf/near/type/v1/type.proto (https://buf.build/streamingfast/firehose-near/docs/0b7e4efe1b9f447495fdfb54b7a6880f:sf.near.type.v1) + proto: "0a1a73662f6e6561722f747970652f76312f747970652e70726f746f120f73662e6e6561722e747970652e7631229b020a05426c6f636b12160a06617574686f721801200128095206617574686f7212340a0668656164657218022001280b321c2e73662e6e6561722e747970652e76312e426c6f636b486561646572520668656164657212410a0d6368756e6b5f6865616465727318032003280b321c2e73662e6e6561722e747970652e76312e4368756e6b486561646572520c6368756e6b4865616465727312350a0673686172647318042003280b321d2e73662e6e6561722e747970652e76312e496e646578657253686172645206736861726473124a0a0d73746174655f6368616e67657318052003280b32252e73662e6e6561722e747970652e76312e53746174654368616e6765576974684361757365520c73746174654368616e67657322470a0f4865616465724f6e6c79426c6f636b12340a0668656164657218022001280b321c2e73662e6e6561722e747970652e76312e426c6f636b48656164657252066865616465722288010a1453746174654368616e676557697468436175736512370a0576616c756518012001280b32212e73662e6e6561722e747970652e76312e53746174654368616e676556616c7565520576616c756512370a05636175736518022001280b32212e73662e6e6561722e747970652e76312e53746174654368616e676543617573655205636175736522d50c0a1053746174654368616e6765436175736512660a146e6f745f7772697461626c655f746f5f6469736b18012001280b32332e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e4e6f745772697461626c65546f4469736b480052116e6f745772697461626c65546f4469736b12550a0d696e697469616c5f737461746518022001280b322e2e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e496e697469616c53746174654800520c696e697469616c537461746512700a167472616e73616374696f6e5f70726f63657373696e6718032001280b32372e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e5472616e73616374696f6e50726f63657373696e67480052157472616e73616374696f6e50726f63657373696e67128d010a21616374696f6e5f726563656970745f70726f63657373696e675f7374617274656418042001280b32402e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e416374696f6e5265636569707450726f63657373696e67537461727465644800521e616374696f6e5265636569707450726f63657373696e675374617274656412750a19616374696f6e5f726563656970745f6761735f72657761726418052001280b32382e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e416374696f6e5265636569707447617352657761726448005216616374696f6e5265636569707447617352657761726412640a12726563656970745f70726f63657373696e6718062001280b32332e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e5265636569707450726f63657373696e67480052117265636569707450726f63657373696e6712610a11706f7374706f6e65645f7265636569707418072001280b32322e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e506f7374706f6e65645265636569707448005210706f7374706f6e65645265636569707412740a18757064617465645f64656c617965645f726563656970747318082001280b32382e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e5570646174656444656c617965645265636569707473480052167570646174656444656c61796564526563656970747312770a1976616c696461746f725f6163636f756e74735f75706461746518092001280b32392e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e56616c696461746f724163636f756e74735570646174654800521776616c696461746f724163636f756e7473557064617465124b0a096d6967726174696f6e180a2001280b322b2e73662e6e6561722e747970652e76312e53746174654368616e676543617573652e4d6967726174696f6e480052096d6967726174696f6e1a130a114e6f745772697461626c65546f4469736b1a0e0a0c496e697469616c53746174651a4d0a155472616e73616374696f6e50726f63657373696e6712340a0774785f6861736818012001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852067478486173681a600a1e416374696f6e5265636569707450726f63657373696e6753746172746564123e0a0c726563656970745f6861736818012001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520b72656365697074486173681a4e0a16416374696f6e5265636569707447617352657761726412340a0774785f6861736818012001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852067478486173681a490a115265636569707450726f63657373696e6712340a0774785f6861736818012001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852067478486173681a480a10506f7374706f6e65645265636569707412340a0774785f6861736818012001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852067478486173681a180a165570646174656444656c6179656452656365697074731a190a1756616c696461746f724163636f756e74735570646174651a0b0a094d6967726174696f6e42070a05636175736522da0b0a1053746174654368616e676556616c756512580a0e6163636f756e745f75706461746518012001280b322f2e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e4163636f756e745570646174654800520d6163636f756e74557064617465125e0a106163636f756e745f64656c6574696f6e18022001280b32312e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e4163636f756e7444656c6574696f6e4800520f6163636f756e7444656c6574696f6e125f0a116163636573735f6b65795f75706461746518032001280b32312e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e4163636573734b65795570646174654800520f6163636573734b657955706461746512650a136163636573735f6b65795f64656c6574696f6e18042001280b32332e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e4163636573734b657944656c6574696f6e480052116163636573734b657944656c6574696f6e124f0a0b646174615f75706461746518052001280b322c2e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e446174615570646174654800520a6461746155706461746512550a0d646174615f64656c6574696f6e18062001280b322e2e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e4461746144656c6574696f6e4800520c6461746144656c6574696f6e12680a14636f6e74726163745f636f64655f75706461746518072001280b32342e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e436f6e7472616374436f646555706461746548005212636f6e7472616374436f646555706461746512650a11636f6e74726163745f64656c6574696f6e18082001280b32362e73662e6e6561722e747970652e76312e53746174654368616e676556616c75652e436f6e7472616374436f646544656c6574696f6e48005210636f6e747261637444656c6574696f6e1a620a0d4163636f756e74557064617465121d0a0a6163636f756e745f696418012001280952096163636f756e74496412320a076163636f756e7418022001280b32182e73662e6e6561722e747970652e76312e4163636f756e7452076163636f756e741a300a0f4163636f756e7444656c6574696f6e121d0a0a6163636f756e745f696418012001280952096163636f756e7449641aa6010a0f4163636573734b6579557064617465121d0a0a6163636f756e745f696418012001280952096163636f756e74496412390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b657912390a0a6163636573735f6b657918032001280b321a2e73662e6e6561722e747970652e76312e4163636573734b657952096163636573734b65791a6d0a114163636573734b657944656c6574696f6e121d0a0a6163636f756e745f696418012001280952096163636f756e74496412390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b65791a530a0a44617461557064617465121d0a0a6163636f756e745f696418012001280952096163636f756e74496412100a036b657918022001280c52036b657912140a0576616c756518032001280c520576616c75651a3f0a0c4461746144656c6574696f6e121d0a0a6163636f756e745f696418012001280952096163636f756e74496412100a036b657918022001280c52036b65791a470a12436f6e7472616374436f6465557064617465121d0a0a6163636f756e745f696418012001280952096163636f756e74496412120a04636f646518022001280c5204636f64651a350a14436f6e7472616374436f646544656c6574696f6e121d0a0a6163636f756e745f696418012001280952096163636f756e74496442070a0576616c756522ca010a074163636f756e74122f0a06616d6f756e7418012001280b32172e73662e6e6561722e747970652e76312e426967496e745206616d6f756e74122f0a066c6f636b656418022001280b32172e73662e6e6561722e747970652e76312e426967496e7452066c6f636b656412380a09636f64655f6861736818032001280b321b2e73662e6e6561722e747970652e76312e43727970746f486173685208636f64654861736812230a0d73746f726167655f7573616765180420012804520c73746f72616765557361676522c50e0a0b426c6f636b48656164657212160a066865696768741801200128045206686569676874121f0a0b707265765f686569676874180220012804520a7072657648656967687412360a0865706f63685f696418032001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520765706f63684964123f0a0d6e6578745f65706f63685f696418042001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520b6e65787445706f63684964122f0a046861736818052001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852046861736812380a09707265765f6861736818062001280b321b2e73662e6e6561722e747970652e76312e43727970746f486173685208707265764861736812430a0f707265765f73746174655f726f6f7418072001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520d707265765374617465526f6f74124b0a136368756e6b5f72656365697074735f726f6f7418082001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852116368756e6b5265636569707473526f6f7412490a126368756e6b5f686561646572735f726f6f7418092001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852106368756e6b48656164657273526f6f74123f0a0d6368756e6b5f74785f726f6f74180a2001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520b6368756e6b5478526f6f74123e0a0c6f7574636f6d655f726f6f74180b2001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520b6f7574636f6d65526f6f7412270a0f6368756e6b735f696e636c75646564180c20012804520e6368756e6b73496e636c7564656412440a0f6368616c6c656e6765735f726f6f74180d2001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520e6368616c6c656e676573526f6f74121c0a0974696d657374616d70180e20012804520974696d657374616d70122b0a1174696d657374616d705f6e616e6f736563180f20012804521074696d657374616d704e616e6f736563123e0a0c72616e646f6d5f76616c756518102001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520b72616e646f6d56616c756512500a1376616c696461746f725f70726f706f73616c7318112003280b321f2e73662e6e6561722e747970652e76312e56616c696461746f725374616b65521276616c696461746f7250726f706f73616c73121d0a0a6368756e6b5f6d61736b18122003280852096368756e6b4d61736b12340a096761735f707269636518132001280b32172e73662e6e6561722e747970652e76312e426967496e745208676173507269636512230a0d626c6f636b5f6f7264696e616c181420012804520c626c6f636b4f7264696e616c123a0a0c746f74616c5f737570706c7918152001280b32172e73662e6e6561722e747970652e76312e426967496e74520b746f74616c537570706c79124e0a116368616c6c656e6765735f726573756c7418162003280b32212e73662e6e6561722e747970652e76312e536c617368656456616c696461746f7252106368616c6c656e676573526573756c7412350a176c6173745f66696e616c5f626c6f636b5f68656967687418172001280452146c61737446696e616c426c6f636b48656967687412450a106c6173745f66696e616c5f626c6f636b18182001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520e6c61737446696e616c426c6f636b123a0a1a6c6173745f64735f66696e616c5f626c6f636b5f68656967687418192001280452166c617374447346696e616c426c6f636b486569676874124a0a136c6173745f64735f66696e616c5f626c6f636b181a2001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852106c617374447346696e616c426c6f636b123d0a0c6e6578745f62705f68617368181b2001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520a6e65787442704861736812470a11626c6f636b5f6d65726b6c655f726f6f74181c2001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520f626c6f636b4d65726b6c65526f6f74122f0a1465706f63685f73796e635f646174615f68617368181d2001280c521165706f636853796e63446174614861736812380a09617070726f76616c73181e2003280b321a2e73662e6e6561722e747970652e76312e5369676e61747572655209617070726f76616c7312380a097369676e6174757265181f2001280b321a2e73662e6e6561722e747970652e76312e5369676e617475726552097369676e617475726512360a176c61746573745f70726f746f636f6c5f76657273696f6e18202001280d52156c617465737450726f746f636f6c56657273696f6e221e0a06426967496e7412140a05627974657318012001280c5205627974657322220a0a43727970746f4861736812140a05627974657318012001280c5205627974657322510a095369676e6174757265122e0a047479706518012001280e321a2e73662e6e6561722e747970652e76312e43757276654b696e6452047479706512140a05627974657318022001280c5205627974657322510a095075626c69634b6579122e0a047479706518012001280e321a2e73662e6e6561722e747970652e76312e43757276654b696e6452047479706512140a05627974657318022001280c520562797465732299010a0e56616c696461746f725374616b65121d0a0a6163636f756e745f696418012001280952096163636f756e74496412390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b6579122d0a057374616b6518032001280b32172e73662e6e6561722e747970652e76312e426967496e7452057374616b6522570a10536c617368656456616c696461746f72121d0a0a6163636f756e745f696418012001280952096163636f756e74496412240a0e69735f646f75626c655f7369676e180220012808520c6973446f75626c655369676e22f6050a0b4368756e6b486561646572121d0a0a6368756e6b5f6861736818012001280c52096368756e6b4861736812260a0f707265765f626c6f636b5f6861736818022001280c520d70726576426c6f636b4861736812210a0c6f7574636f6d655f726f6f7418032001280c520b6f7574636f6d65526f6f7412260a0f707265765f73746174655f726f6f7418042001280c520d707265765374617465526f6f74122e0a13656e636f6465645f6d65726b6c655f726f6f7418052001280c5211656e636f6465644d65726b6c65526f6f7412250a0e656e636f6465645f6c656e677468180620012804520d656e636f6465644c656e67746812250a0e6865696768745f63726561746564180720012804520d6865696768744372656174656412270a0f6865696768745f696e636c75646564180820012804520e686569676874496e636c7564656412190a0873686172645f696418092001280452077368617264496412190a086761735f75736564180a20012804520767617355736564121b0a096761735f6c696d6974180b2001280452086761734c696d697412420a1076616c696461746f725f726577617264180c2001280b32172e73662e6e6561722e747970652e76312e426967496e74520f76616c696461746f72526577617264123c0a0d62616c616e63655f6275726e74180d2001280b32172e73662e6e6561722e747970652e76312e426967496e74520c62616c616e63654275726e7412340a166f7574676f696e675f72656365697074735f726f6f74180e2001280c52146f7574676f696e675265636569707473526f6f7412170a0774785f726f6f74180f2001280c52067478526f6f7412500a1376616c696461746f725f70726f706f73616c7318102003280b321f2e73662e6e6561722e747970652e76312e56616c696461746f725374616b65521276616c696461746f7250726f706f73616c7312380a097369676e617475726518112001280b321a2e73662e6e6561722e747970652e76312e5369676e617475726552097369676e617475726522d1010a0c496e6465786572536861726412190a0873686172645f696418012001280452077368617264496412330a056368756e6b18022001280b321d2e73662e6e6561722e747970652e76312e496e64657865724368756e6b52056368756e6b12710a1a726563656970745f657865637574696f6e5f6f7574636f6d657318032003280b32332e73662e6e6561722e747970652e76312e496e6465786572457865637574696f6e4f7574636f6d655769746852656365697074521872656365697074457865637574696f6e4f7574636f6d657322ae010a22496e6465786572457865637574696f6e4f7574636f6d65576974685265636569707412540a11657865637574696f6e5f6f7574636f6d6518012001280b32272e73662e6e6561722e747970652e76312e457865637574696f6e4f7574636f6d655769746849645210657865637574696f6e4f7574636f6d6512320a077265636569707418022001280b32182e73662e6e6561722e747970652e76312e5265636569707452077265636569707422e6010a0c496e64657865724368756e6b12160a06617574686f721801200128095206617574686f7212340a0668656164657218022001280b321c2e73662e6e6561722e747970652e76312e4368756e6b486561646572520668656164657212520a0c7472616e73616374696f6e7318032003280b322e2e73662e6e6561722e747970652e76312e496e64657865725472616e73616374696f6e576974684f7574636f6d65520c7472616e73616374696f6e7312340a08726563656970747318042003280b32182e73662e6e6561722e747970652e76312e526563656970745208726563656970747322bc010a1d496e64657865725472616e73616374696f6e576974684f7574636f6d6512440a0b7472616e73616374696f6e18012001280b32222e73662e6e6561722e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e12550a076f7574636f6d6518022001280b323b2e73662e6e6561722e747970652e76312e496e6465786572457865637574696f6e4f7574636f6d65576974684f7074696f6e616c5265636569707452076f7574636f6d6522c0020a115369676e65645472616e73616374696f6e121b0a097369676e65725f696418012001280952087369676e6572496412390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b657912140a056e6f6e636518032001280452056e6f6e6365121f0a0b72656365697665725f6964180420012809520a7265636569766572496412310a07616374696f6e7318052003280b32172e73662e6e6561722e747970652e76312e416374696f6e5207616374696f6e7312380a097369676e617475726518062001280b321a2e73662e6e6561722e747970652e76312e5369676e617475726552097369676e6174757265122f0a046861736818072001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852046861736822b6010a2a496e6465786572457865637574696f6e4f7574636f6d65576974684f7074696f6e616c5265636569707412540a11657865637574696f6e5f6f7574636f6d6518012001280b32272e73662e6e6561722e747970652e76312e457865637574696f6e4f7574636f6d655769746849645210657865637574696f6e4f7574636f6d6512320a077265636569707418022001280b32182e73662e6e6561722e747970652e76312e526563656970745207726563656970742286020a075265636569707412250a0e7072656465636573736f725f6964180120012809520d7072656465636573736f724964121f0a0b72656365697665725f6964180220012809520a72656365697665724964123a0a0a726563656970745f696418032001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520972656365697074496412380a06616374696f6e180a2001280b321e2e73662e6e6561722e747970652e76312e52656365697074416374696f6e48005206616374696f6e12320a0464617461180b2001280b321c2e73662e6e6561722e747970652e76312e5265636569707444617461480052046461746142090a077265636569707422570a0b526563656970744461746112340a07646174615f696418012001280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520664617461496412120a046461746118022001280c52046461746122f3020a0d52656365697074416374696f6e121b0a097369676e65725f696418012001280952087369676e6572496412460a117369676e65725f7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b6579520f7369676e65725075626c69634b657912340a096761735f707269636518032001280b32172e73662e6e6561722e747970652e76312e426967496e745208676173507269636512510a156f75747075745f646174615f72656365697665727318042003280b321d2e73662e6e6561722e747970652e76312e44617461526563656976657252136f75747075744461746152656365697665727312410a0e696e7075745f646174615f69647318052003280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520c696e7075744461746149647312310a07616374696f6e7318062003280b32172e73662e6e6561722e747970652e76312e416374696f6e5207616374696f6e7322650a0c44617461526563656976657212340a07646174615f696418012001280b321b2e73662e6e6561722e747970652e76312e43727970746f486173685206646174614964121f0a0b72656365697665725f6964180220012809520a7265636569766572496422f1010a16457865637574696f6e4f7574636f6d6557697468496412310a0570726f6f6618012001280b321b2e73662e6e6561722e747970652e76312e4d65726b6c6550617468520570726f6f66123a0a0a626c6f636b5f6861736818022001280b321b2e73662e6e6561722e747970652e76312e43727970746f486173685209626c6f636b48617368122b0a02696418032001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852026964123b0a076f7574636f6d6518042001280b32212e73662e6e6561722e747970652e76312e457865637574696f6e4f7574636f6d6552076f7574636f6d6522e9040a10457865637574696f6e4f7574636f6d6512120a046c6f677318012003280952046c6f6773123c0a0b726563656970745f69647318022003280b321b2e73662e6e6561722e747970652e76312e43727970746f48617368520a72656365697074496473121b0a096761735f6275726e7418032001280452086761734275726e74123a0a0c746f6b656e735f6275726e7418042001280b32172e73662e6e6561722e747970652e76312e426967496e74520b746f6b656e734275726e74121f0a0b6578656375746f725f6964180520012809520a6578656375746f72496412430a07756e6b6e6f776e18142001280b32272e73662e6e6561722e747970652e76312e556e6b6e6f776e457865637574696f6e53746174757348005207756e6b6e6f776e12430a076661696c75726518152001280b32272e73662e6e6561722e747970652e76312e4661696c757265457865637574696f6e537461747573480052076661696c75726512530a0d737563636573735f76616c756518162001280b322c2e73662e6e6561722e747970652e76312e5375636365737356616c7565457865637574696f6e5374617475734800520c7375636365737356616c756512600a12737563636573735f726563656970745f696418172001280b32302e73662e6e6561722e747970652e76312e53756363657373526563656970744964457865637574696f6e5374617475734800521073756363657373526563656970744964123e0a086d6574616461746118062001280e32222e73662e6e6561722e747970652e76312e457865637574696f6e4d6574616461746152086d6574616461746142080a0673746174757322330a1b5375636365737356616c7565457865637574696f6e53746174757312140a0576616c756518012001280c520576616c7565224e0a1f53756363657373526563656970744964457865637574696f6e537461747573122b0a02696418012001280b321b2e73662e6e6561722e747970652e76312e43727970746f486173685202696422180a16556e6b6e6f776e457865637574696f6e53746174757322b3010a164661696c757265457865637574696f6e53746174757312410a0c616374696f6e5f6572726f7218012001280b321c2e73662e6e6561722e747970652e76312e416374696f6e4572726f724800520b616374696f6e4572726f72124b0a10696e76616c69645f74785f6572726f7218022001280e321f2e73662e6e6561722e747970652e76312e496e76616c696454784572726f724800520e696e76616c696454784572726f7242090a076661696c75726522bc130a0b416374696f6e4572726f7212140a05696e6465781801200128045205696e64657812640a156163636f756e745f616c72656164795f657869737418152001280b322e2e73662e6e6561722e747970652e76312e4163636f756e74416c72656164794578697374734572726f724b696e64480052136163636f756e74416c7265616479457869737412640a166163636f756e745f646f65735f6e6f745f657869737418162001280b322d2e73662e6e6561722e747970652e76312e4163636f756e74446f65734e6f7445786973744572726f724b696e64480052136163636f756e74446f65734e6f7445786973741280010a206372656174655f6163636f756e745f6f6e6c795f62795f72656769737472617218172001280b32362e73662e6e6561722e747970652e76312e4372656174654163636f756e744f6e6c7942795265676973747261724572726f724b696e644800521c6372656174654163636f756e744f6e6c79427952656769737472617212700a1a6372656174655f6163636f756e745f6e6f745f616c6c6f77656418182001280b32312e73662e6e6561722e747970652e76312e4372656174654163636f756e744e6f74416c6c6f7765644572726f724b696e64480052176372656174654163636f756e744e6f74416c6c6f776564125d0a136163746f725f6e6f5f7065726d697373696f6e18192001280b322b2e73662e6e6561722e747970652e76312e4163746f724e6f5065726d697373696f6e4572726f724b696e64480052116163746f724e6f5065726d697373696f6e126b0a1964656c6574655f6b65795f646f65735f6e6f745f6578697374181a2001280b322f2e73662e6e6561722e747970652e76312e44656c6574654b6579446f65734e6f7445786973744572726f724b696e644800521564656c6574654b6579446f65734e6f74457869737412640a166164645f6b65795f616c72656164795f657869737473181b2001280b322d2e73662e6e6561722e747970652e76312e4164644b6579416c72656164794578697374734572726f724b696e64480052136164644b6579416c726561647945786973747312660a1664656c6574655f6163636f756e745f7374616b696e67181c2001280b322e2e73662e6e6561722e747970652e76312e44656c6574654163636f756e745374616b696e674572726f724b696e644800521464656c6574654163636f756e745374616b696e6712640a166c61636b5f62616c616e63655f666f725f7374617465181d2001280b322d2e73662e6e6561722e747970652e76312e4c61636b42616c616e6365466f7253746174654572726f724b696e64480052136c61636b42616c616e6365466f72537461746512540a1074726965735f746f5f756e7374616b65181e2001280b32282e73662e6e6561722e747970652e76312e5472696573546f556e7374616b654572726f724b696e644800520e7472696573546f556e7374616b65124e0a0e74726965735f746f5f7374616b65181f2001280b32262e73662e6e6561722e747970652e76312e5472696573546f5374616b654572726f724b696e644800520c7472696573546f5374616b65125c0a12696e73756666696369656e745f7374616b6518202001280b322b2e73662e6e6561722e747970652e76312e496e73756666696369656e745374616b654572726f724b696e6448005211696e73756666696369656e745374616b65124d0a0d66756e6374696f6e5f63616c6c18212001280b32262e73662e6e6561722e747970652e76312e46756e6374696f6e43616c6c4572726f724b696e644800520c66756e6374696f6e43616c6c12660a166e65775f726563656970745f76616c69646174696f6e18222001280b322e2e73662e6e6561722e747970652e76312e4e65775265636569707456616c69646174696f6e4572726f724b696e64480052146e65775265636569707456616c69646174696f6e1292010a266f6e6c795f696d706c696369745f6163636f756e745f6372656174696f6e5f616c6c6f77656418232001280b323c2e73662e6e6561722e747970652e76312e4f6e6c79496d706c696369744163636f756e744372656174696f6e416c6c6f7765644572726f724b696e64480052226f6e6c79496d706c696369744163636f756e744372656174696f6e416c6c6f776564127d0a1f64656c6574655f6163636f756e745f776974685f6c617267655f737461746518242001280b32352e73662e6e6561722e747970652e76312e44656c6574654163636f756e74576974684c6172676553746174654572726f724b696e644800521b64656c6574654163636f756e74576974684c6172676553746174651280010a2164656c65676174655f616374696f6e5f696e76616c69645f7369676e617475726518252001280b32332e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e496e76616c69645369676e61747572654b696e644800521e64656c6567617465416374696f6e496e76616c69645369676e617475726512a8010a3164656c65676174655f616374696f6e5f73656e6465725f646f65735f6e6f745f6d617463685f74785f726563656976657218262001280b323f2e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e53656e646572446f65734e6f744d61746368547852656365697665724b696e644800522a64656c6567617465416374696f6e53656e646572446f65734e6f744d617463685478526563656976657212640a1764656c65676174655f616374696f6e5f6578706972656418272001280b322a2e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e457870697265644b696e644800521564656c6567617465416374696f6e45787069726564127b0a2064656c65676174655f616374696f6e5f6163636573735f6b65795f6572726f7218282001280b32312e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e4163636573734b65794572726f724b696e644800521c64656c6567617465416374696f6e4163636573734b65794572726f7212740a1d64656c65676174655f616374696f6e5f696e76616c69645f6e6f6e636518292001280b322f2e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e496e76616c69644e6f6e63654b696e644800521a64656c6567617465416374696f6e496e76616c69644e6f6e636512780a1f64656c65676174655f616374696f6e5f6e6f6e63655f746f6f5f6c61726765182a2001280b32302e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e4e6f6e6365546f6f4c617267654b696e644800521b64656c6567617465416374696f6e4e6f6e6365546f6f4c6172676542060a046b696e64223e0a1d4163636f756e74416c72656164794578697374734572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e744964223d0a1c4163636f756e74446f65734e6f7445786973744572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e744964229f010a254372656174654163636f756e744f6e6c7942795265676973747261724572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496412300a147265676973747261725f6163636f756e745f696418022001280952127265676973747261724163636f756e74496412250a0e7072656465636573736f725f6964180320012809520d7072656465636573736f72496422680a204372656174654163636f756e744e6f74416c6c6f7765644572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496412250a0e7072656465636573736f725f6964180220012809520d7072656465636573736f72496422560a1a4163746f724e6f5065726d697373696f6e4572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496412190a086163746f725f696418022001280952076163746f724964227a0a1e44656c6574654b6579446f65734e6f7445786973744572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496412390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b657922780a1c4164644b6579416c72656164794578697374734572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496412390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b6579223e0a1d44656c6574654163636f756e745374616b696e674572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496422700a1c4c61636b42616c616e6365466f7253746174654572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496412310a0762616c616e636518022001280b32172e73662e6e6561722e747970652e76312e426967496e74520762616c616e636522380a175472696573546f556e7374616b654572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496422c9010a155472696573546f5374616b654572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e744964122d0a057374616b6518022001280b32172e73662e6e6561722e747970652e76312e426967496e7452057374616b65122f0a066c6f636b656418032001280b32172e73662e6e6561722e747970652e76312e426967496e7452066c6f636b656412310a0762616c616e636518042001280b32172e73662e6e6561722e747970652e76312e426967496e74520762616c616e636522a8010a1a496e73756666696369656e745374616b654572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e744964122d0a057374616b6518022001280b32172e73662e6e6561722e747970652e76312e426967496e7452057374616b65123c0a0d6d696e696d756d5f7374616b6518032001280b32172e73662e6e6561722e747970652e76312e426967496e74520c6d696e696d756d5374616b6522540a1546756e6374696f6e43616c6c4572726f724b696e64123b0a056572726f7218012001280e32252e73662e6e6561722e747970652e76312e46756e6374696f6e43616c6c4572726f7253657252056572726f72225e0a1d4e65775265636569707456616c69646174696f6e4572726f724b696e64123d0a056572726f7218012001280e32272e73662e6e6561722e747970652e76312e5265636569707456616c69646174696f6e4572726f7252056572726f72224c0a2b4f6e6c79496d706c696369744163636f756e744372656174696f6e416c6c6f7765644572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496422450a2444656c6574654163636f756e74576974684c6172676553746174654572726f724b696e64121d0a0a6163636f756e745f696418012001280952096163636f756e74496422240a2244656c6567617465416374696f6e496e76616c69645369676e61747572654b696e64226e0a2e44656c6567617465416374696f6e53656e646572446f65734e6f744d61746368547852656365697665724b696e64121b0a0973656e6465725f6964180120012809520873656e6465724964121f0a0b72656365697665725f6964180220012809520a72656365697665724964221b0a1944656c6567617465416374696f6e457870697265644b696e6422590a2044656c6567617465416374696f6e4163636573734b65794572726f724b696e6412350a056572726f7218012001280e321f2e73662e6e6561722e747970652e76312e496e76616c696454784572726f7252056572726f7222620a1e44656c6567617465416374696f6e496e76616c69644e6f6e63654b696e6412250a0e64656c65676174655f6e6f6e6365180120012804520d64656c65676174654e6f6e636512190a08616b5f6e6f6e63651802200128045207616b4e6f6e636522690a1f44656c6567617465416374696f6e4e6f6e6365546f6f4c617267654b696e6412250a0e64656c65676174655f6e6f6e6365180120012804520d64656c65676174654e6f6e6365121f0a0b75707065725f626f756e64180220012804520a7570706572426f756e6422410a0a4d65726b6c655061746812330a047061746818012003280b321f2e73662e6e6561722e747970652e76312e4d65726b6c65506174684974656d520470617468227b0a0e4d65726b6c65506174684974656d122f0a046861736818012001280b321b2e73662e6e6561722e747970652e76312e43727970746f4861736852046861736812380a09646972656374696f6e18022001280e321a2e73662e6e6561722e747970652e76312e446972656374696f6e5209646972656374696f6e2285050a06416374696f6e124d0a0e6372656174655f6163636f756e7418012001280b32242e73662e6e6561722e747970652e76312e4372656174654163636f756e74416374696f6e4800520d6372656174654163636f756e7412500a0f6465706c6f795f636f6e747261637418022001280b32252e73662e6e6561722e747970652e76312e4465706c6f79436f6e7472616374416374696f6e4800520e6465706c6f79436f6e7472616374124a0a0d66756e6374696f6e5f63616c6c18032001280b32232e73662e6e6561722e747970652e76312e46756e6374696f6e43616c6c416374696f6e4800520c66756e6374696f6e43616c6c123d0a087472616e7366657218042001280b321f2e73662e6e6561722e747970652e76312e5472616e73666572416374696f6e480052087472616e7366657212340a057374616b6518052001280b321c2e73662e6e6561722e747970652e76312e5374616b65416374696f6e480052057374616b6512380a076164645f6b657918062001280b321d2e73662e6e6561722e747970652e76312e4164644b6579416374696f6e480052066164644b657912410a0a64656c6574655f6b657918072001280b32202e73662e6e6561722e747970652e76312e44656c6574654b6579416374696f6e4800520964656c6574654b6579124d0a0e64656c6574655f6163636f756e7418082001280b32242e73662e6e6561722e747970652e76312e44656c6574654163636f756e74416374696f6e4800520d64656c6574654163636f756e7412430a0864656c656761746518092001280b32252e73662e6e6561722e747970652e76312e5369676e656444656c6567617465416374696f6e4800520864656c656761746542080a06616374696f6e22150a134372656174654163636f756e74416374696f6e222a0a144465706c6f79436f6e7472616374416374696f6e12120a04636f646518012001280c5204636f6465228e010a1246756e6374696f6e43616c6c416374696f6e121f0a0b6d6574686f645f6e616d65180120012809520a6d6574686f644e616d6512120a046172677318022001280c52046172677312100a03676173180320012804520367617312310a076465706f73697418042001280b32172e73662e6e6561722e747970652e76312e426967496e7452076465706f73697422430a0e5472616e73666572416374696f6e12310a076465706f73697418012001280b32172e73662e6e6561722e747970652e76312e426967496e7452076465706f73697422770a0b5374616b65416374696f6e122d0a057374616b6518012001280b32172e73662e6e6561722e747970652e76312e426967496e7452057374616b6512390a0a7075626c69635f6b657918022001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b65792284010a0c4164644b6579416374696f6e12390a0a7075626c69635f6b657918012001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b657912390a0a6163636573735f6b657918022001280b321a2e73662e6e6561722e747970652e76312e4163636573734b657952096163636573734b6579224c0a0f44656c6574654b6579416374696f6e12390a0a7075626c69635f6b657918012001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b6579223c0a1344656c6574654163636f756e74416374696f6e12250a0e62656e65666963696172795f6964180120012809520d62656e65666963696172794964229a010a145369676e656444656c6567617465416374696f6e12380a097369676e617475726518012001280b321a2e73662e6e6561722e747970652e76312e5369676e617475726552097369676e617475726512480a0f64656c65676174655f616374696f6e18022001280b321f2e73662e6e6561722e747970652e76312e44656c6567617465416374696f6e520e64656c6567617465416374696f6e22fc010a0e44656c6567617465416374696f6e121b0a0973656e6465725f6964180120012809520873656e6465724964121f0a0b72656365697665725f6964180220012809520a7265636569766572496412310a07616374696f6e7318032003280b32172e73662e6e6561722e747970652e76312e416374696f6e5207616374696f6e7312140a056e6f6e636518042001280452056e6f6e636512280a106d61785f626c6f636b5f686569676874180520012804520e6d6178426c6f636b48656967687412390a0a7075626c69635f6b657918062001280b321a2e73662e6e6561722e747970652e76312e5075626c69634b657952097075626c69634b657922670a094163636573734b657912140a056e6f6e636518012001280452056e6f6e636512440a0a7065726d697373696f6e18022001280b32242e73662e6e6561722e747970652e76312e4163636573734b65795065726d697373696f6e520a7065726d697373696f6e22bd010a134163636573734b65795065726d697373696f6e124e0a0d66756e6374696f6e5f63616c6c18012001280b32272e73662e6e6561722e747970652e76312e46756e6374696f6e43616c6c5065726d697373696f6e4800520c66756e6374696f6e43616c6c12480a0b66756c6c5f61636365737318022001280b32252e73662e6e6561722e747970652e76312e46756c6c4163636573735065726d697373696f6e4800520a66756c6c416363657373420c0a0a7065726d697373696f6e2293010a1646756e6374696f6e43616c6c5065726d697373696f6e12350a09616c6c6f77616e636518012001280b32172e73662e6e6561722e747970652e76312e426967496e745209616c6c6f77616e6365121f0a0b72656365697665725f6964180220012809520a7265636569766572496412210a0c6d6574686f645f6e616d6573180320032809520b6d6574686f644e616d657322160a1446756c6c4163636573735065726d697373696f6e2a270a0943757276654b696e64120b0a07454432353531391000120d0a09534543503235364b3110012a2c0a11457865637574696f6e4d6574616461746112170a13457865637574696f6e4d65746164617461563110002aa9010a1446756e6374696f6e43616c6c4572726f7253657212140a10436f6d70696c6174696f6e4572726f721000120d0a094c696e6b4572726f72100112160a124d6574686f645265736f6c76654572726f721002120c0a085761736d54726170100312140a105761736d556e6b6e6f776e4572726f721004120d0a09486f73744572726f721005120d0a095f45564d4572726f72100612120a0e457865637574696f6e4572726f7210072aed010a165265636569707456616c69646174696f6e4572726f7212180a14496e76616c69645072656465636573736f7249641000121c0a18496e76616c696452656365697665724163636f756e7449641001121a0a16496e76616c69645369676e65724163636f756e744964100212190a15496e76616c696444617461526563656976657249641003121f0a1b52657475726e656456616c75654c656e6774684578636565646564100412270a234e756d626572496e70757444617461446570656e64656e6369657345786365656465641005121a0a16416374696f6e7356616c69646174696f6e4572726f7210062abe020a0e496e76616c696454784572726f7212190a15496e76616c69644163636573734b65794572726f72100012130a0f496e76616c69645369676e65724964100112160a125369676e6572446f65734e6f744578697374100212100a0c496e76616c69644e6f6e6365100312110a0d4e6f6e6365546f6f4c61726765100412150a11496e76616c696452656365697665724964100512140a10496e76616c69645369676e6174757265100612140a104e6f74456e6f75676842616c616e6365100712170a134c61636b42616c616e6365466f725374617465100812100a0c436f73744f766572666c6f77100912100a0c496e76616c6964436861696e100a120b0a0745787069726564100b12150a11416374696f6e7356616c69646174696f6e100c121b0a175472616e73616374696f6e53697a654578636565646564100d2a200a09446972656374696f6e12080a046c656674100012090a057269676874100142425a406769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d6e6561722f70622f73662f6e6561722f747970652f76313b70626e656172620670726f746f33", + }, + { + // sf/solana/transforms/v1/transforms.proto (https://buf.build/streamingfast/firehose-solana/docs/7e16dd37d68049d1a3f548203be431d7:sf.solana.transforms.v1) + proto: "0a2873662f736f6c616e612f7472616e73666f726d732f76312f7472616e73666f726d732e70726f746f121773662e736f6c616e612e7472616e73666f726d732e763122300a0d50726f6772616d46696c746572121f0a0b70726f6772616d5f696473180120032809520a70726f6772616d49647342525a506769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d736f6c616e612f70622f73662f736f6c616e612f7472616e73666f726d732f76313b70627472616e73666f726d73620670726f746f33", + }, + { + // sf/solana/type/v1/type.proto (https://buf.build/streamingfast/firehose-solana/docs/7e16dd37d68049d1a3f548203be431d7:sf.solana.type.v1) + proto: "0a1c73662f736f6c616e612f747970652f76312f747970652e70726f746f121173662e736f6c616e612e747970652e7631228f030a05426c6f636b122d0a1270726576696f75735f626c6f636b68617368180120012809521170726576696f7573426c6f636b68617368121c0a09626c6f636b686173681802200128095209626c6f636b68617368121f0a0b706172656e745f736c6f74180320012804520a706172656e74536c6f74124b0a0c7472616e73616374696f6e7318042003280b32272e73662e736f6c616e612e747970652e76312e436f6e6669726d65645472616e73616374696f6e520c7472616e73616374696f6e7312330a077265776172647318052003280b32192e73662e736f6c616e612e747970652e76312e526577617264520772657761726473123f0a0a626c6f636b5f74696d6518062001280b32202e73662e736f6c616e612e747970652e76312e556e697854696d657374616d705209626c6f636b54696d6512410a0c626c6f636b5f68656967687418072001280b321e2e73662e736f6c616e612e747970652e76312e426c6f636b486569676874520b626c6f636b48656967687412120a04736c6f741814200128045204736c6f742296010a14436f6e6669726d65645472616e73616374696f6e12400a0b7472616e73616374696f6e18012001280b321e2e73662e736f6c616e612e747970652e76312e5472616e73616374696f6e520b7472616e73616374696f6e123c0a046d65746118022001280b32282e73662e736f6c616e612e747970652e76312e5472616e73616374696f6e5374617475734d65746152046d65746122630a0b5472616e73616374696f6e121e0a0a7369676e61747572657318012003280c520a7369676e61747572657312340a076d65737361676518022001280b321a2e73662e736f6c616e612e747970652e76312e4d65737361676552076d65737361676522dd020a074d65737361676512380a0668656164657218012001280b32202e73662e736f6c616e612e747970652e76312e4d657373616765486561646572520668656164657212210a0c6163636f756e745f6b65797318022003280c520b6163636f756e744b65797312290a10726563656e745f626c6f636b6861736818032001280c520f726563656e74426c6f636b68617368124a0a0c696e737472756374696f6e7318042003280b32262e73662e736f6c616e612e747970652e76312e436f6d70696c6564496e737472756374696f6e520c696e737472756374696f6e73121c0a0976657273696f6e6564180520012808520976657273696f6e656412600a15616464726573735f7461626c655f6c6f6f6b75707318062003280b322c2e73662e736f6c616e612e747970652e76312e4d657373616765416464726573735461626c654c6f6f6b75705213616464726573735461626c654c6f6f6b75707322cd010a0d4d65737361676548656164657212360a176e756d5f72657175697265645f7369676e61747572657318012001280d52156e756d52657175697265645369676e617475726573123f0a1c6e756d5f726561646f6e6c795f7369676e65645f6163636f756e747318022001280d52196e756d526561646f6e6c795369676e65644163636f756e747312430a1e6e756d5f726561646f6e6c795f756e7369676e65645f6163636f756e747318032001280d521b6e756d526561646f6e6c79556e7369676e65644163636f756e74732292010a194d657373616765416464726573735461626c654c6f6f6b7570121f0a0b6163636f756e745f6b657918012001280c520a6163636f756e744b657912290a107772697461626c655f696e646578657318022001280c520f7772697461626c65496e646578657312290a10726561646f6e6c795f696e646578657318032001280c520f726561646f6e6c79496e64657865732283060a155472616e73616374696f6e5374617475734d65746112350a0365727218012001280b32232e73662e736f6c616e612e747970652e76312e5472616e73616374696f6e4572726f72520365727212100a03666565180220012804520366656512210a0c7072655f62616c616e636573180320032804520b70726542616c616e63657312230a0d706f73745f62616c616e636573180420032804520c706f737442616c616e63657312530a12696e6e65725f696e737472756374696f6e7318052003280b32242e73662e736f6c616e612e747970652e76312e496e6e6572496e737472756374696f6e735211696e6e6572496e737472756374696f6e7312210a0c6c6f675f6d65737361676573180620032809520b6c6f674d65737361676573124d0a127072655f746f6b656e5f62616c616e63657318072003280b321f2e73662e736f6c616e612e747970652e76312e546f6b656e42616c616e63655210707265546f6b656e42616c616e636573124f0a13706f73745f746f6b656e5f62616c616e63657318082003280b321f2e73662e736f6c616e612e747970652e76312e546f6b656e42616c616e63655211706f7374546f6b656e42616c616e63657312330a077265776172647318092003280b32192e73662e736f6c616e612e747970652e76312e526577617264520772657761726473123a0a196c6f616465645f7772697461626c655f616464726573736573180c2003280c52176c6f616465645772697461626c65416464726573736573123a0a196c6f616465645f726561646f6e6c795f616464726573736573180d2003280c52176c6f61646564526561646f6e6c79416464726573736573123e0a0b72657475726e5f64617461180e2001280b321d2e73662e736f6c616e612e747970652e76312e52657475726e44617461520a72657475726e4461746112390a16636f6d707574655f756e6974735f636f6e73756d656418102001280448005214636f6d70757465556e697473436f6e73756d656488010142190a175f636f6d707574655f756e6974735f636f6e73756d656422240a105472616e73616374696f6e4572726f7212100a0365727218012001280c520365727222720a11496e6e6572496e737472756374696f6e7312140a05696e64657818012001280d5205696e64657812470a0c696e737472756374696f6e7318022003280b32232e73662e736f6c616e612e747970652e76312e496e6e6572496e737472756374696f6e520c696e737472756374696f6e7322a5010a10496e6e6572496e737472756374696f6e12280a1070726f6772616d5f69645f696e64657818012001280d520e70726f6772616d4964496e646578121a0a086163636f756e747318022001280c52086163636f756e747312120a046461746118032001280c52046461746112260a0c737461636b5f68656967687418042001280d4800520b737461636b486569676874880101420f0a0d5f737461636b5f686569676874226f0a13436f6d70696c6564496e737472756374696f6e12280a1070726f6772616d5f69645f696e64657818012001280d520e70726f6772616d4964496e646578121a0a086163636f756e747318022001280c52086163636f756e747312120a046461746118032001280c52046461746122c6010a0c546f6b656e42616c616e636512230a0d6163636f756e745f696e64657818012001280d520c6163636f756e74496e64657812120a046d696e7418022001280952046d696e7412480a0f75695f746f6b656e5f616d6f756e7418032001280b32202e73662e736f6c616e612e747970652e76312e5569546f6b656e416d6f756e74520d7569546f6b656e416d6f756e7412140a056f776e657218042001280952056f776e6572121d0a0a70726f6772616d5f6964180520012809520970726f6772616d4964228a010a0d5569546f6b656e416d6f756e74121b0a0975695f616d6f756e7418012001280152087569416d6f756e74121a0a08646563696d616c7318022001280d5208646563696d616c7312160a06616d6f756e741803200128095206616d6f756e7412280a1075695f616d6f756e745f737472696e67180420012809520e7569416d6f756e74537472696e67223f0a0a52657475726e44617461121d0a0a70726f6772616d5f696418012001280c520970726f6772616d496412120a046461746118022001280c52046461746122bf010a0652657761726412160a067075626b657918012001280952067075626b6579121a0a086c616d706f72747318022001280352086c616d706f72747312210a0c706f73745f62616c616e6365180320012804520b706f737442616c616e6365123e0a0b7265776172645f7479706518042001280e321d2e73662e736f6c616e612e747970652e76312e52657761726454797065520a72657761726454797065121e0a0a636f6d6d697373696f6e180520012809520a636f6d6d697373696f6e223e0a075265776172647312330a077265776172647318012003280b32192e73662e736f6c616e612e747970652e76312e526577617264520772657761726473222d0a0d556e697854696d657374616d70121c0a0974696d657374616d70180120012803520974696d657374616d7022300a0b426c6f636b48656967687412210a0c626c6f636b5f686569676874180120012804520b626c6f636b4865696768742a490a0a52657761726454797065120f0a0b556e737065636966696564100012070a03466565100112080a0452656e741002120b0a075374616b696e671003120a0a06566f74696e67100442455a436769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d736f6c616e612f70622f73662f736f6c616e612f747970652f76313b7062736f6c620670726f746f33", + }, + { + // google/protobuf/timestamp.proto (https://buf.build/streamingfast/firehose-bitcoin/docs/0d8ce32fe71441df82c89dcccda35366:google.protobuf) + proto: "0a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f120f676f6f676c652e70726f746f627566223b0a0954696d657374616d7012180a077365636f6e647318012001280352077365636f6e647312140a056e616e6f7318022001280552056e616e6f734285010a13636f6d2e676f6f676c652e70726f746f627566420e54696d657374616d7050726f746f50015a32676f6f676c652e676f6c616e672e6f72672f70726f746f6275662f74797065732f6b6e6f776e2f74696d657374616d707062f80101a20203475042aa021e476f6f676c652e50726f746f6275662e57656c6c4b6e6f776e5479706573620670726f746f33", + }, + { + // sf/bitcoin/type/v1/type.proto (https://buf.build/streamingfast/firehose-bitcoin/docs/0d8ce32fe71441df82c89dcccda35366:sf.bitcoin.type.v1) + proto: "0a1d73662f626974636f696e2f747970652f76312f747970652e70726f746f121273662e626974636f696e2e747970652e76311a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f22e5030a05426c6f636b12120a046861736818012001280952046861736812120a0473697a65180320012805520473697a6512230a0d73747269707065645f73697a65180420012805520c737472697070656453697a6512160a06776569676874180520012805520677656967687412160a06686569676874180620012803520668656967687412180a0776657273696f6e180720012805520776657273696f6e121f0a0b76657273696f6e5f686578180820012809520a76657273696f6e486578121f0a0b6d65726b6c655f726f6f74180920012809520a6d65726b6c65526f6f74122f0a027478180a2003280b321f2e73662e626974636f696e2e747970652e76312e5472616e73616374696f6e5202747812120a0474696d65180b20012803520474696d65121e0a0a6d656469616e74696d65180c20012803520a6d656469616e74696d6512140a056e6f6e6365180d2001280d52056e6f6e636512120a0462697473180e20012809520462697473121e0a0a646966666963756c7479180f20012801520a646966666963756c7479121c0a09636861696e776f726b1810200128095209636861696e776f726b12110a046e5f747818112001280d52036e547812230a0d70726576696f75735f68617368181220012809520c70726576696f75734861736822d4020a0b5472616e73616374696f6e12100a03686578180120012809520368657812120a047478696418022001280952047478696412120a046861736818032001280952046861736812120a0473697a65180420012805520473697a6512140a057673697a6518052001280552057673697a6512160a06776569676874180620012805520677656967687412180a0776657273696f6e18072001280d520776657273696f6e121a0a086c6f636b74696d6518082001280d52086c6f636b74696d6512290a0376696e18092003280b32172e73662e626974636f696e2e747970652e76312e56696e520376696e122c0a04766f7574180a2003280b32182e73662e626974636f696e2e747970652e76312e566f75745204766f7574121c0a09626c6f636b68617368180b200128095209626c6f636b68617368121c0a09626c6f636b74696d65180c200128035209626c6f636b74696d6522c5010a0356696e12120a047478696418012001280952047478696412120a04766f757418022001280d5204766f7574123c0a0a7363726970745f73696718032001280b321d2e73662e626974636f696e2e747970652e76312e5363726970745369675209736372697074536967121a0a0873657175656e636518042001280d520873657175656e636512200a0b7478696e7769746e657373180520032809520b7478696e7769746e657373121a0a08636f696e626173651806200128095208636f696e6261736522710a04566f757412140a0576616c7565180120012801520576616c7565120c0a016e18022001280d52016e12450a0d7363726970745f7075624b657918032001280b32202e73662e626974636f696e2e747970652e76312e5363726970745075624b6579520c7363726970745075624b6579222f0a0953637269707453696712100a0361736d180120012809520361736d12100a0368657818022001280952036865782299010a0c5363726970745075624b657912100a0361736d180120012809520361736d12100a03686578180220012809520368657812190a087265715f7369677318032001280552077265715369677312120a047479706518042001280952047479706512180a0761646472657373180520012809520761646472657373121c0a096164647265737365731806200328095209616464726573736573424d5a4b6769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d626974636f696e2f74797065732f70622f73662f626974636f696e2f747970652f76313b7062627463620670726f746f33", + }, + { + // google/protobuf/timestamp.proto (https://buf.build/pinax/firehose-antelope/docs/d0bfddff3ea641368c1215bf1e66a76d:google.protobuf) + proto: "0a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f120f676f6f676c652e70726f746f627566223b0a0954696d657374616d7012180a077365636f6e647318012001280352077365636f6e647312140a056e616e6f7318022001280552056e616e6f734285010a13636f6d2e676f6f676c652e70726f746f627566420e54696d657374616d7050726f746f50015a32676f6f676c652e676f6c616e672e6f72672f70726f746f6275662f74797065732f6b6e6f776e2f74696d657374616d707062f80101a20203475042aa021e476f6f676c652e50726f746f6275662e57656c6c4b6e6f776e5479706573620670726f746f33", + }, + { + // sf/antelope/type/v1/type.proto (https://buf.build/pinax/firehose-antelope/docs/d0bfddff3ea641368c1215bf1e66a76d:sf.antelope.type.v1) + proto: "0a1e73662f616e74656c6f70652f747970652f76312f747970652e70726f746f121373662e616e74656c6f70652e747970652e76311a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f22550a0c416374696f6e54726163657312450a0d616374696f6e5f74726163657318012003280b32202e73662e616e74656c6f70652e747970652e76312e416374696f6e5472616365520c616374696f6e54726163657322690a115472616e73616374696f6e54726163657312540a127472616e73616374696f6e5f74726163657318012003280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e547261636552117472616e73616374696f6e54726163657322390a0544424f707312300a0664625f6f707318012003280b32192e73662e616e74656c6f70652e747970652e76312e44424f70520564624f7073228b160a05426c6f636b120e0a0269641801200128095202696412160a066e756d62657218022001280d52066e756d62657212180a0776657273696f6e18032001280d520776657273696f6e12380a0668656164657218042001280b32202e73662e616e74656c6f70652e747970652e76312e426c6f636b4865616465725206686561646572122d0a1270726f64756365725f7369676e6174757265180520012809521170726f64756365725369676e617475726512490a10626c6f636b5f657874656e73696f6e7318072003280b321e2e73662e616e74656c6f70652e747970652e76312e457874656e73696f6e520f626c6f636b457874656e73696f6e73124d0a2364706f735f70726f706f7365645f697272657665727369626c655f626c6f636b6e756d18082001280d522064706f7350726f706f736564497272657665727369626c65426c6f636b6e756d123c0a1a64706f735f697272657665727369626c655f626c6f636b6e756d18092001280d521864706f73497272657665727369626c65426c6f636b6e756d124f0a10626c6f636b726f6f745f6d65726b6c65180b2001280b32242e73662e616e74656c6f70652e747970652e76312e426c6f636b526f6f744d65726b6c65520f626c6f636b726f6f744d65726b6c6512660a1970726f64756365725f746f5f6c6173745f70726f6475636564180c2003280b322b2e73662e616e74656c6f70652e747970652e76312e50726f6475636572546f4c61737450726f6475636564521670726f6475636572546f4c61737450726f6475636564126d0a1c70726f64756365725f746f5f6c6173745f696d706c6965645f697262180d2003280b322d2e73662e616e74656c6f70652e747970652e76312e50726f6475636572546f4c617374496d706c696564495242521870726f6475636572546f4c617374496d706c69656449726212230a0d636f6e6669726d5f636f756e74180f2003280d520c636f6e6669726d436f756e7412570a1070656e64696e675f7363686564756c6518102001280b322c2e73662e616e74656c6f70652e747970652e76312e50656e64696e6750726f64756365725363686564756c65520f70656e64696e675363686564756c65126e0a1b6163746976617465645f70726f746f636f6c5f666561747572657318112001280b322e2e73662e616e74656c6f70652e747970652e76312e41637469766174656450726f746f636f6c4665617475726573521961637469766174656450726f746f636f6c4665617475726573123c0a0a726c696d69745f6f707318132003280b321d2e73662e616e74656c6f70652e747970652e76312e526c696d69744f705209726c696d69744f707312600a17756e66696c74657265645f7472616e73616374696f6e7318062003280b32272e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e526563656970745216756e66696c74657265645472616e73616374696f6e73125c0a1566696c74657265645f7472616e73616374696f6e73182f2003280b32272e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e52656365697074521466696c74657265645472616e73616374696f6e7312400a1c756e66696c74657265645f7472616e73616374696f6e5f636f756e7418162001280d521a756e66696c74657265645472616e73616374696f6e436f756e74123c0a1a66696c74657265645f7472616e73616374696f6e5f636f756e7418302001280d521866696c74657265645472616e73616374696f6e436f756e7412690a23756e66696c74657265645f696d706c696369745f7472616e73616374696f6e5f6f707318142003280b321a2e73662e616e74656c6f70652e747970652e76312e5472784f705220756e66696c7465726564496d706c696369745472616e73616374696f6e4f707312650a2166696c74657265645f696d706c696369745f7472616e73616374696f6e5f6f707318312003280b321a2e73662e616e74656c6f70652e747970652e76312e5472784f70521e66696c7465726564496d706c696369745472616e73616374696f6e4f707312690a1d756e66696c74657265645f7472616e73616374696f6e5f74726163657318152003280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5472616365521b756e66696c74657265645472616e73616374696f6e54726163657312650a1b66696c74657265645f7472616e73616374696f6e5f747261636573182e2003280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5472616365521966696c74657265645472616e73616374696f6e547261636573124b0a22756e66696c74657265645f7472616e73616374696f6e5f74726163655f636f756e7418172001280d521f756e66696c74657265645472616e73616374696f6e5472616365436f756e7412470a2066696c74657265645f7472616e73616374696f6e5f74726163655f636f756e74182b2001280d521d66696c74657265645472616e73616374696f6e5472616365436f756e7412520a26756e66696c74657265645f65786563757465645f696e7075745f616374696f6e5f636f756e7418182001280d5222756e66696c74657265644578656375746564496e707574416374696f6e436f756e74124e0a2466696c74657265645f65786563757465645f696e7075745f616374696f6e5f636f756e74182c2001280d522066696c74657265644578656375746564496e707574416374696f6e436f756e7412520a26756e66696c74657265645f65786563757465645f746f74616c5f616374696f6e5f636f756e7418192001280d5222756e66696c74657265644578656375746564546f74616c416374696f6e436f756e74124e0a2466696c74657265645f65786563757465645f746f74616c5f616374696f6e5f636f756e74182d2001280d522066696c74657265644578656375746564546f74616c416374696f6e436f756e74122a0a11626c6f636b5f7369676e696e675f6b6579180e20012809520f626c6f636b5369676e696e674b657912530a126163746976655f7363686564756c655f7631180a2001280b32252e73662e616e74656c6f70652e747970652e76312e50726f64756365725363686564756c6552106163746976655363686564756c65563112720a2076616c69645f626c6f636b5f7369676e696e675f617574686f726974795f7632181e2001280b322a2e73662e616e74656c6f70652e747970652e76312e426c6f636b5369676e696e67417574686f72697479521c76616c6964426c6f636b5369676e696e67417574686f726974795632125c0a126163746976655f7363686564756c655f7632181f2001280b322e2e73662e616e74656c6f70652e747970652e76312e50726f6475636572417574686f726974795363686564756c6552106163746976655363686564756c655632122b0a1166696c746572696e675f6170706c696564182820012808521066696c746572696e674170706c69656412410a1d66696c746572696e675f696e636c7564655f66696c7465725f65787072182920012809521a66696c746572696e67496e636c75646546696c7465724578707212410a1d66696c746572696e675f6578636c7564655f66696c7465725f65787072182a20012809521a66696c746572696e674578636c75646546696c74657245787072125d0a2c66696c746572696e675f73797374656d5f616374696f6e735f696e636c7564655f66696c7465725f65787072183220012809522766696c746572696e6753797374656d416374696f6e73496e636c75646546696c746572457870724a04081210132284030a0d426c6f636b5769746852656673120e0a0269641801200128095202696412300a05626c6f636b18022001280b321a2e73662e616e74656c6f70652e747970652e76312e426c6f636b5205626c6f636b12600a19696d706c696369745f7472616e73616374696f6e5f7265667318032001280b32242e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e526566735217696d706c696369745472616e73616374696f6e52656673124f0a107472616e73616374696f6e5f7265667318042001280b32242e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e52656673520f7472616e73616374696f6e52656673125a0a167472616e73616374696f6e5f74726163655f7265667318052001280b32242e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5265667352147472616e73616374696f6e54726163655265667312220a0c697272657665727369626c65180620012808520c697272657665727369626c6522290a0f5472616e73616374696f6e5265667312160a0668617368657318012003280c520668617368657322480a1941637469766174656450726f746f636f6c4665617475726573122b0a1170726f746f636f6c5f666561747572657318012003280c521070726f746f636f6c46656174757265732281020a1750656e64696e6750726f64756365725363686564756c6512280a107363686564756c655f6c69625f6e756d18012001280d520e7363686564756c654c69624e756d12230a0d7363686564756c655f6861736818022001280c520c7363686564756c654861736812460a0b7363686564756c655f763118032001280b32252e73662e616e74656c6f70652e747970652e76312e50726f64756365725363686564756c65520a7363686564756c655631124f0a0b7363686564756c655f763218042001280b322e2e73662e616e74656c6f70652e747970652e76312e50726f6475636572417574686f726974795363686564756c65520a7363686564756c655632226c0a1050726f64756365725363686564756c6512180a0776657273696f6e18012001280d520776657273696f6e123e0a0970726f64756365727318022003280b32202e73662e616e74656c6f70652e747970652e76312e50726f64756365724b6579520970726f647563657273225c0a0b50726f64756365724b657912210a0c6163636f756e745f6e616d65180120012809520b6163636f756e744e616d65122a0a11626c6f636b5f7369676e696e675f6b6579180220012809520f626c6f636b5369676e696e674b6579227b0a1950726f6475636572417574686f726974795363686564756c6512180a0776657273696f6e18012001280d520776657273696f6e12440a0970726f64756365727318022003280b32262e73662e616e74656c6f70652e747970652e76312e50726f6475636572417574686f72697479520970726f647563657273229a010a1150726f6475636572417574686f7269747912210a0c6163636f756e745f6e616d65180120012809520b6163636f756e744e616d6512620a17626c6f636b5f7369676e696e675f617574686f7269747918022001280b322a2e73662e616e74656c6f70652e747970652e76312e426c6f636b5369676e696e67417574686f726974795215626c6f636b5369676e696e67417574686f7269747922620a15426c6f636b5369676e696e67417574686f72697479123e0a02763018012001280b322c2e73662e616e74656c6f70652e747970652e76312e426c6f636b5369676e696e67417574686f72697479563048005202763042090a0776617269616e74226b0a17426c6f636b5369676e696e67417574686f726974795630121c0a097468726573686f6c6418012001280d52097468726573686f6c6412320a046b65797318022003280b321e2e73662e616e74656c6f70652e747970652e76312e4b657957656967687452046b65797322530a0f426c6f636b526f6f744d65726b6c65121d0a0a6e6f64655f636f756e7418012001280d52096e6f6465436f756e7412210a0c6163746976655f6e6f64657318022003280c520b6163746976654e6f64657322630a1650726f6475636572546f4c61737450726f647563656412120a046e616d6518012001280952046e616d6512350a176c6173745f626c6f636b5f6e756d5f70726f647563656418022001280d52146c617374426c6f636b4e756d50726f647563656422650a1850726f6475636572546f4c617374496d706c69656449524212120a046e616d6518012001280952046e616d6512350a176c6173745f626c6f636b5f6e756d5f70726f647563656418022001280d52146c617374426c6f636b4e756d50726f647563656422b0020a125472616e73616374696f6e52656365697074120e0a0269641804200128095202696412140a05696e6465781806200128045205696e646578123e0a0673746174757318012001280e32262e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e537461747573520673746174757312350a176370755f75736167655f6d6963726f5f7365636f6e647318022001280d521463707555736167654d6963726f5365636f6e647312260a0f6e65745f75736167655f776f72647318032001280d520d6e65745573616765576f72647312550a127061636b65645f7472616e73616374696f6e18052001280b32262e73662e616e74656c6f70652e747970652e76312e5061636b65645472616e73616374696f6e52117061636b65645472616e73616374696f6e22bd010a115061636b65645472616e73616374696f6e121e0a0a7369676e617475726573180120032809520a7369676e61747572657312200a0b636f6d7072657373696f6e18022001280d520b636f6d7072657373696f6e12370a187061636b65645f636f6e746578745f667265655f6461746118032001280c52157061636b6564436f6e746578744672656544617461122d0a127061636b65645f7472616e73616374696f6e18042001280c52117061636b65645472616e73616374696f6e22b6030a0b426c6f636b48656164657212380a0974696d657374616d7018032001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d70121a0a0870726f6475636572180420012809520870726f6475636572121c0a09636f6e6669726d656418052001280d5209636f6e6669726d6564121a0a0870726576696f7573180620012809520870726576696f7573122b0a117472616e73616374696f6e5f6d726f6f7418072001280c52107472616e73616374696f6e4d726f6f7412210a0c616374696f6e5f6d726f6f7418082001280c520b616374696f6e4d726f6f7412290a107363686564756c655f76657273696f6e18092001280d520f7363686564756c6556657273696f6e124b0a116865616465725f657874656e73696f6e73180b2003280b321e2e73662e616e74656c6f70652e747970652e76312e457874656e73696f6e5210686561646572457874656e73696f6e73124f0a106e65775f70726f6475636572735f7631180a2001280b32252e73662e616e74656c6f70652e747970652e76312e50726f64756365725363686564756c65520e6e657750726f647563657273563122fb090a105472616e73616374696f6e4576656e74120e0a0269641801200128095202696412190a08626c6f636b5f69641802200128095207626c6f636b4964121b0a09626c6f636b5f6e756d18032001280d5208626c6f636b4e756d12220a0c697272657665727369626c65180420012808520c697272657665727369626c6512640a11696e7465726e616c5f6164646974696f6e18052001280b32352e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e4576656e742e4164646564496e7465726e616c6c7948005210696e7465726e616c4164646974696f6e12490a086164646974696f6e18062001280b322b2e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e4576656e742e4164646564480052086164646974696f6e124e0a09657865637574696f6e18072001280b322e2e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e4576656e742e457865637574656448005209657865637574696f6e125e0a0f647472785f7363686564756c696e6718082001280b32332e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e4576656e742e447472785363686564756c65644800520e647472785363686564756c696e6712610a11647472785f63616e63656c6c6174696f6e18092001280b32322e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e4576656e742e4474727843616e63656c6564480052106474727843616e63656c6c6174696f6e1a5b0a0f4164646564496e7465726e616c6c7912480a0b7472616e73616374696f6e18012001280b32262e73662e616e74656c6f70652e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e1ad6010a05416464656412410a077265636569707418012001280b32272e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5265636569707452077265636569707412480a0b7472616e73616374696f6e18022001280b32262e73662e616e74656c6f70652e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e12400a0b7075626c69635f6b65797318032001280b321f2e73662e616e74656c6f70652e747970652e76312e5075626c69634b657973520a7075626c69634b6579731a8b010a084578656375746564123b0a05747261636518012001280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e54726163655205747261636512420a0b626c6f636b48656164657218022001280b32202e73662e616e74656c6f70652e747970652e76312e426c6f636b486561646572520b626c6f636b4865616465721a98010a0d447472785363686564756c6564123d0a0a637265617465645f627918012001280b321e2e73662e616e74656c6f70652e747970652e76312e457874445472784f70520963726561746564427912480a0b7472616e73616374696f6e18022001280b32262e73662e616e74656c6f70652e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e1a4f0a0c4474727843616e63656c6564123f0a0b63616e63656c65645f627918012001280b321e2e73662e616e74656c6f70652e747970652e76312e457874445472784f70520a63616e63656c6564427942070a056576656e74222d0a0a5075626c69634b657973121f0a0b7075626c69635f6b657973180120032809520a7075626c69634b657973229d060a145472616e73616374696f6e4c6966656379636c65120e0a0269641801200128095202696412550a127472616e73616374696f6e5f73746174757318022001280e32262e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e53746174757352117472616e73616374696f6e53746174757312580a137472616e73616374696f6e5f7265636569707418242001280b32272e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5265636569707452127472616e73616374696f6e5265636569707412480a0b7472616e73616374696f6e180a2001280b32262e73662e616e74656c6f70652e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e121f0a0b7075626c69635f6b657973181320032809520a7075626c69634b657973124e0a0f657865637574696f6e5f7472616365180b2001280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5472616365520e657865637574696f6e547261636512560a16657865637574696f6e5f626c6f636b5f686561646572180c2001280b32202e73662e616e74656c6f70652e747970652e76312e426c6f636b4865616465725214657865637574696f6e426c6f636b486561646572123d0a0a637265617465645f627918142001280b321e2e73662e616e74656c6f70652e747970652e76312e457874445472784f705209637265617465644279123f0a0b63616e63656c65645f627918152001280b321e2e73662e616e74656c6f70652e747970652e76312e457874445472784f70520a63616e63656c6564427912330a156372656174696f6e5f697272657665727369626c6518212001280852146372656174696f6e497272657665727369626c6512350a16657865637574696f6e5f697272657665727369626c651822200128085215657865637574696f6e497272657665727369626c6512390a1863616e63656c6174696f6e5f697272657665727369626c65182320012808521763616e63656c6174696f6e497272657665727369626c654a04080d10134a040816102122a3010a115369676e65645472616e73616374696f6e12420a0b7472616e73616374696f6e18012001280b32202e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e520b7472616e73616374696f6e121e0a0a7369676e617475726573180220032809520a7369676e617475726573122a0a11636f6e746578745f667265655f6461746118032003280c520f636f6e7465787446726565446174612293020a0b5472616e73616374696f6e123e0a0668656164657218012001280b32262e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e4865616465725206686561646572124d0a14636f6e746578745f667265655f616374696f6e7318022003280b321b2e73662e616e74656c6f70652e747970652e76312e416374696f6e5212636f6e7465787446726565416374696f6e7312350a07616374696f6e7318032003280b321b2e73662e616e74656c6f70652e747970652e76312e416374696f6e5207616374696f6e73123e0a0a657874656e73696f6e7318042003280b321e2e73662e616e74656c6f70652e747970652e76312e457874656e73696f6e520a657874656e73696f6e732292020a115472616e73616374696f6e486561646572123a0a0a65787069726174696f6e18012001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520a65787069726174696f6e12220a0d7265665f626c6f636b5f6e756d18022001280d520b726566426c6f636b4e756d12280a107265665f626c6f636b5f70726566697818032001280d520e726566426c6f636b507265666978122d0a136d61785f6e65745f75736167655f776f72647318042001280d52106d61784e65745573616765576f72647312270a106d61785f6370755f75736167655f6d7318052001280d520d6d617843707555736167654d73121b0a0964656c61795f73656318062001280d520864656c61795365632288090a105472616e73616374696f6e5472616365120e0a02696418012001280952026964121b0a09626c6f636b5f6e756d1802200128045208626c6f636b4e756d12140a05696e646578181a200128045205696e64657812390a0a626c6f636b5f74696d6518032001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d705209626c6f636b54696d65122a0a1170726f64756365725f626c6f636b5f6964180420012809520f70726f6475636572426c6f636b496412470a077265636569707418052001280b322d2e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5265636569707448656164657252077265636569707412180a07656c61707365641806200128035207656c6170736564121b0a096e65745f757361676518072001280452086e65745573616765121c0a097363686564756c656418082001280852097363686564756c656412450a0d616374696f6e5f74726163657318092003280b32202e73662e616e74656c6f70652e747970652e76312e416374696f6e5472616365520c616374696f6e54726163657312510a116661696c65645f647472785f7472616365180a2001280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e5472616365520f6661696c6564447472785472616365123c0a09657863657074696f6e180f2001280b321e2e73662e616e74656c6f70652e747970652e76312e457863657074696f6e5209657863657074696f6e121d0a0a6572726f725f636f646518102001280452096572726f72436f646512300a0664625f6f707318112003280b32192e73662e616e74656c6f70652e747970652e76312e44424f70520564624f707312360a08647472785f6f707318122003280b321b2e73662e616e74656c6f70652e747970652e76312e445472784f705207647472784f7073123f0a0b666561747572655f6f707318132003280b321e2e73662e616e74656c6f70652e747970652e76312e466561747572654f70520a666561747572654f707312360a087065726d5f6f707318142003280b321b2e73662e616e74656c6f70652e747970652e76312e5065726d4f7052077065726d4f707312330a0772616d5f6f707318152003280b321a2e73662e616e74656c6f70652e747970652e76312e52414d4f70520672616d4f707312520a1272616d5f636f7272656374696f6e5f6f707318162003280b32242e73662e616e74656c6f70652e747970652e76312e52414d436f7272656374696f6e4f70521072616d436f7272656374696f6e4f7073123c0a0a726c696d69745f6f707318172003280b321d2e73662e616e74656c6f70652e747970652e76312e526c696d69744f705209726c696d69744f707312390a097461626c655f6f707318182003280b321c2e73662e616e74656c6f70652e747970652e76312e5461626c654f7052087461626c654f7073124a0a0d6372656174696f6e5f7472656518192003280b32252e73662e616e74656c6f70652e747970652e76312e4372656174696f6e466c61744e6f6465520c6372656174696f6e547265654a04081e101f22b9010a185472616e73616374696f6e52656365697074486561646572123e0a0673746174757318012001280e32262e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e537461747573520673746174757312350a176370755f75736167655f6d6963726f5f7365636f6e647318022001280d521463707555736167654d6963726f5365636f6e647312260a0f6e65745f75736167655f776f72647318032001280d520d6e65745573616765576f72647322ba010a06416374696f6e12180a076163636f756e7418012001280952076163636f756e7412120a046e616d6518022001280952046e616d65124a0a0d617574686f72697a6174696f6e18032003280b32242e73662e616e74656c6f70652e747970652e76312e5065726d697373696f6e4c6576656c520d617574686f72697a6174696f6e121b0a096a736f6e5f6461746118042001280952086a736f6e4461746112190a087261775f6461746118052001280c520772617744617461228e080a0b416374696f6e5472616365121a0a087265636569766572180b2001280952087265636569766572123c0a077265636569707418012001280b32222e73662e616e74656c6f70652e747970652e76312e416374696f6e5265636569707452077265636569707412330a06616374696f6e18022001280b321b2e73662e616e74656c6f70652e747970652e76312e416374696f6e5206616374696f6e12210a0c636f6e746578745f66726565180320012808520b636f6e746578744672656512180a07656c61707365641804200128035207656c617073656412180a07636f6e736f6c651805200128095207636f6e736f6c6512250a0e7472616e73616374696f6e5f6964180620012809520d7472616e73616374696f6e4964121b0a09626c6f636b5f6e756d1807200128045208626c6f636b4e756d122a0a1170726f64756365725f626c6f636b5f6964180820012809520f70726f6475636572426c6f636b496412390a0a626c6f636b5f74696d6518092001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d705209626c6f636b54696d6512520a126163636f756e745f72616d5f64656c746173180a2003280b32242e73662e616e74656c6f70652e747970652e76312e4163636f756e7452414d44656c746152106163636f756e7452616d44656c74617312280a107261775f72657475726e5f76616c756518292001280c520e72617752657475726e56616c7565122a0a116a736f6e5f72657475726e5f76616c7565182a20012809520f6a736f6e52657475726e56616c7565123c0a09657863657074696f6e180f2001280b321e2e73662e616e74656c6f70652e747970652e76312e457863657074696f6e5209657863657074696f6e121d0a0a6572726f725f636f646518142001280452096572726f72436f646512250a0e616374696f6e5f6f7264696e616c18102001280d520d616374696f6e4f7264696e616c12340a1663726561746f725f616374696f6e5f6f7264696e616c18112001280d521463726561746f72416374696f6e4f7264696e616c125a0a2a636c6f736573745f756e6e6f7469666965645f616e636573746f725f616374696f6e5f6f7264696e616c18122001280d5226636c6f73657374556e6e6f746966696564416e636573746f72416374696f6e4f7264696e616c12270a0f657865637574696f6e5f696e64657818132001280d520e657865637574696f6e496e646578122b0a1166696c746572696e675f6d617463686564181e20012808521066696c746572696e674d61746368656412520a2666696c746572696e675f6d6174636865645f73797374656d5f616374696f6e5f66696c746572181f20012808522266696c746572696e674d61746368656453797374656d416374696f6e46696c7465724a040828102922a1020a0d416374696f6e52656365697074121a0a0872656365697665721801200128095208726563656976657212160a06646967657374180220012809520664696765737412270a0f676c6f62616c5f73657175656e6365180320012804520e676c6f62616c53657175656e636512460a0d617574685f73657175656e636518042003280b32212e73662e616e74656c6f70652e747970652e76312e4175746853657175656e6365520c6175746853657175656e636512230a0d726563765f73657175656e6365180520012804520c7265637653657175656e636512230a0d636f64655f73657175656e6365180620012804520c636f646553657175656e636512210a0c6162695f73657175656e6365180720012804520b61626953657175656e6365224d0a0c4175746853657175656e636512210a0c6163636f756e745f6e616d65180120012809520b6163636f756e744e616d65121a0a0873657175656e6365180220012804520873657175656e636522410a0f4163636f756e7452414d44656c746112180a076163636f756e7418012001280952076163636f756e7412140a0564656c7461180220012803520564656c7461223e0a0c4163636f756e7444656c746112180a076163636f756e7418012001280952076163636f756e7412140a0564656c7461180220012803520564656c746122330a09457874656e73696f6e12120a047479706518012001280d52047479706512120a046461746118022001280c520464617461228a020a055472784f7012420a096f7065726174696f6e18012001280e32242e73662e616e74656c6f70652e747970652e76312e5472784f702e4f7065726174696f6e52096f7065726174696f6e12120a046e616d6518022001280952046e616d6512250a0e7472616e73616374696f6e5f6964180320012809520d7472616e73616374696f6e496412480a0b7472616e73616374696f6e18042001280b32262e73662e616e74656c6f70652e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e22380a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e100012140a104f5045524154494f4e5f435245415445100122f4030a0444424f7012410a096f7065726174696f6e18012001280e32232e73662e616e74656c6f70652e747970652e76312e44424f702e4f7065726174696f6e52096f7065726174696f6e12210a0c616374696f6e5f696e64657818022001280d520b616374696f6e496e64657812120a04636f64651803200128095204636f646512140a0573636f7065180420012809520573636f7065121d0a0a7461626c655f6e616d6518052001280952097461626c654e616d65121f0a0b7072696d6172795f6b6579180620012809520a7072696d6172794b6579121b0a096f6c645f706179657218072001280952086f6c645061796572121b0a096e65775f706179657218082001280952086e6577506179657212190a086f6c645f6461746118092001280c52076f6c644461746112190a086e65775f64617461180a2001280c52076e65774461746112220a0d6f6c645f646174615f6a736f6e180b20012809520b6f6c64446174614a736f6e12220a0d6e65775f646174615f6a736f6e180c20012809520b6e6577446174614a736f6e22640a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e100012140a104f5045524154494f4e5f494e53455254100112140a104f5045524154494f4e5f555044415445100212140a104f5045524154494f4e5f52454d4f5645100322e20c0a0552414d4f7012420a096f7065726174696f6e18012001280e32242e73662e616e74656c6f70652e747970652e76312e52414d4f702e4f7065726174696f6e52096f7065726174696f6e12210a0c616374696f6e5f696e64657818022001280d520b616374696f6e496e64657812140a0570617965721803200128095205706179657212140a0564656c7461180420012803520564656c746112140a0575736167651805200128045205757361676512420a096e616d65737061636518062001280e32242e73662e616e74656c6f70652e747970652e76312e52414d4f702e4e616d65737061636552096e616d65737061636512390a06616374696f6e18072001280e32212e73662e616e74656c6f70652e747970652e76312e52414d4f702e416374696f6e5206616374696f6e121d0a0a756e697175655f6b65791808200128095209756e697175654b65792282070a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e1000121a0a164f5045524154494f4e5f4352454154455f5441424c451001121e0a1a4f5045524154494f4e5f44454645525245445f5452585f414444100212210a1d4f5045524154494f4e5f44454645525245445f5452585f43414e43454c100312210a1d4f5045524154494f4e5f44454645525245445f5452585f505553484544100412290a254f5045524154494f4e5f44454645525245445f5452585f52414d5f434f5252454354494f4e100512220a1e4f5045524154494f4e5f44454645525245445f5452585f52454d4f564544100612180a144f5045524154494f4e5f44454c45544541555448100712160a124f5045524154494f4e5f4c494e4b41555448100812180a144f5045524154494f4e5f4e45574143434f554e541009121f0a1b4f5045524154494f4e5f5052494d4152595f494e4445585f414444100a12220a1e4f5045524154494f4e5f5052494d4152595f494e4445585f52454d4f5645100b12220a1e4f5045524154494f4e5f5052494d4152595f494e4445585f555044415445100c12300a2c4f5045524154494f4e5f5052494d4152595f494e4445585f5550444154455f4144445f4e45575f5041594552100d12330a2f4f5045524154494f4e5f5052494d4152595f494e4445585f5550444154455f52454d4f56455f4f4c445f5041594552100e121a0a164f5045524154494f4e5f52454d4f56455f5441424c45100f12210a1d4f5045524154494f4e5f5345434f4e444152595f494e4445585f414444101012240a204f5045524154494f4e5f5345434f4e444152595f494e4445585f52454d4f5645101112320a2e4f5045524154494f4e5f5345434f4e444152595f494e4445585f5550444154455f4144445f4e45575f5041594552101212350a314f5045524154494f4e5f5345434f4e444152595f494e4445585f5550444154455f52454d4f56455f4f4c445f5041594552101312140a104f5045524154494f4e5f534554414249101412150a114f5045524154494f4e5f534554434f4445101512180a144f5045524154494f4e5f554e4c494e4b415554481016121f0a1b4f5045524154494f4e5f555044415445415554485f4352454154451017121f0a1b4f5045524154494f4e5f555044415445415554485f555044415445101812180a144f5045524154494f4e5f44455052454341544544101922fc010a094e616d65737061636512150a114e414d4553504143455f554e4b4e4f574e100012110a0d4e414d4553504143455f414249100112150a114e414d4553504143455f4143434f554e54100212120a0e4e414d4553504143455f41555448100312170a134e414d4553504143455f415554485f4c494e4b100412120a0e4e414d4553504143455f434f44451005121a0a164e414d4553504143455f44454645525245445f5452581006121d0a194e414d4553504143455f5345434f4e444152595f494e444558100712130a0f4e414d4553504143455f5441424c45100812170a134e414d4553504143455f5441424c455f524f5710092204080a100a228d010a06416374696f6e12120a0e414354494f4e5f554e4b4e4f574e1000120e0a0a414354494f4e5f414444100112110a0d414354494f4e5f43414e43454c100212150a11414354494f4e5f434f5252454354494f4e1003120f0a0b414354494f4e5f50555348100412110a0d414354494f4e5f52454d4f5645100512110a0d414354494f4e5f55504441544510062281010a0f52414d436f7272656374696f6e4f7012230a0d636f7272656374696f6e5f6964180120012809520c636f7272656374696f6e4964121d0a0a756e697175655f6b65791802200128095209756e697175654b657912140a0570617965721803200128095205706179657212140a0564656c7461180420012803520564656c746122a1020a075461626c654f7012440a096f7065726174696f6e18012001280e32262e73662e616e74656c6f70652e747970652e76312e5461626c654f702e4f7065726174696f6e52096f7065726174696f6e12210a0c616374696f6e5f696e64657818022001280d520b616374696f6e496e64657812140a0570617965721803200128095205706179657212120a04636f64651804200128095204636f646512140a0573636f7065180520012809520573636f7065121d0a0a7461626c655f6e616d6518062001280952097461626c654e616d65224e0a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e100012140a104f5045524154494f4e5f494e53455254100112140a104f5045524154494f4e5f52454d4f5645100222d1040a06445472784f7012430a096f7065726174696f6e18012001280e32252e73662e616e74656c6f70652e747970652e76312e445472784f702e4f7065726174696f6e52096f7065726174696f6e12210a0c616374696f6e5f696e64657818022001280d520b616374696f6e496e64657812160a0673656e646572180320012809520673656e646572121b0a0973656e6465725f6964180420012809520873656e646572496412140a0570617965721805200128095205706179657212210a0c7075626c69736865645f6174180620012809520b7075626c69736865644174121f0a0b64656c61795f756e74696c180720012809520a64656c6179556e74696c12230a0d65787069726174696f6e5f6174180820012809520c65787069726174696f6e417412250a0e7472616e73616374696f6e5f6964180920012809520d7472616e73616374696f6e496412480a0b7472616e73616374696f6e180a2001280b32262e73662e616e74656c6f70652e747970652e76312e5369676e65645472616e73616374696f6e520b7472616e73616374696f6e22b9010a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e100012140a104f5045524154494f4e5f435245415445100112190a154f5045524154494f4e5f505553485f435245415445100212140a104f5045524154494f4e5f4641494c4544100312140a104f5045524154494f4e5f43414e43454c1004121b0a174f5045524154494f4e5f4d4f444946595f43414e43454c1005121b0a174f5045524154494f4e5f4d4f444946595f435245415445100622e8010a09457874445472784f7012320a15736f757263655f7472616e73616374696f6e5f69641801200128095213736f757263655472616e73616374696f6e4964121b0a09626c6f636b5f6e756d1802200128045208626c6f636b4e756d12190a08626c6f636b5f69641803200128095207626c6f636b496412390a0a626c6f636b5f74696d6518042001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d705209626c6f636b54696d6512340a07647472785f6f7018052001280b321b2e73662e616e74656c6f70652e747970652e76312e445472784f705206647472784f7022e5010a09466561747572654f7012120a046b696e6418012001280952046b696e6412210a0c616374696f6e5f696e64657818022001280d520b616374696f6e496e64657812250a0e666561747572655f646967657374180320012809520d6665617475726544696765737412360a076665617475726518042001280b321c2e73662e616e74656c6f70652e747970652e76312e4665617475726552076665617475726522420a044b696e6412100a0c4b494e445f554e4b4e4f574e100012150a114b494e445f5052455f4143544956415445100112110a0d4b494e445f41435449564154451002227a0a104372656174696f6e466c61744e6f646512300a1463726561746f725f616374696f6e5f696e646578180120012805521263726561746f72416374696f6e496e64657812340a16657865637574696f6e5f616374696f6e5f696e64657818022001280d5214657865637574696f6e416374696f6e496e64657822da020a065065726d4f7012430a096f7065726174696f6e18012001280e32252e73662e616e74656c6f70652e747970652e76312e5065726d4f702e4f7065726174696f6e52096f7065726174696f6e12210a0c616374696f6e5f696e64657818022001280d520b616374696f6e496e64657812400a086f6c645f7065726d18082001280b32252e73662e616e74656c6f70652e747970652e76312e5065726d697373696f6e4f626a65637452076f6c645065726d12400a086e65775f7065726d18092001280b32252e73662e616e74656c6f70652e747970652e76312e5065726d697373696f6e4f626a65637452076e65775065726d22640a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e100012140a104f5045524154494f4e5f494e53455254100112140a104f5045524154494f4e5f555044415445100212140a104f5045524154494f4e5f52454d4f5645100322e6010a105065726d697373696f6e4f626a656374120e0a026964180a2001280452026964121b0a09706172656e745f6964180b200128045208706172656e74496412140a056f776e657218012001280952056f776e657212120a046e616d6518022001280952046e616d65123d0a0c6c6173745f7570646174656418032001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520b6c61737455706461746564123c0a09617574686f7269747918042001280b321e2e73662e616e74656c6f70652e747970652e76312e417574686f726974795209617574686f72697479227d0a0a5065726d697373696f6e12120a046e616d6518012001280952046e616d6512160a06706172656e741802200128095206706172656e7412430a0d72657175697265645f6175746818032001280b321e2e73662e616e74656c6f70652e747970652e76312e417574686f72697479520c72657175697265644175746822dc010a09417574686f72697479121c0a097468726573686f6c6418012001280d52097468726573686f6c6412320a046b65797318022003280b321e2e73662e616e74656c6f70652e747970652e76312e4b657957656967687452046b65797312460a086163636f756e747318032003280b322a2e73662e616e74656c6f70652e747970652e76312e5065726d697373696f6e4c6576656c57656967687452086163636f756e747312350a05776169747318042003280b321f2e73662e616e74656c6f70652e747970652e76312e576169745765696768745205776169747322420a094b6579576569676874121d0a0a7075626c69635f6b657918012001280952097075626c69634b657912160a0677656967687418022001280d520677656967687422470a0f5065726d697373696f6e4c6576656c12140a056163746f7218012001280952056163746f72121e0a0a7065726d697373696f6e180220012809520a7065726d697373696f6e22750a155065726d697373696f6e4c6576656c57656967687412440a0a7065726d697373696f6e18012001280b32242e73662e616e74656c6f70652e747970652e76312e5065726d697373696f6e4c6576656c520a7065726d697373696f6e12160a0677656967687418022001280d5206776569676874223f0a0a5761697457656967687412190a08776169745f73656318012001280d52077761697453656312160a0677656967687418022001280d520677656967687422c3030a08526c696d69744f7012450a096f7065726174696f6e18012001280e32272e73662e616e74656c6f70652e747970652e76312e526c696d69744f702e4f7065726174696f6e52096f7065726174696f6e12380a05737461746518022001280b32202e73662e616e74656c6f70652e747970652e76312e526c696d69745374617465480052057374617465123b0a06636f6e66696718032001280b32212e73662e616e74656c6f70652e747970652e76312e526c696d6974436f6e66696748005206636f6e66696712510a0e6163636f756e745f6c696d69747318042001280b32282e73662e616e74656c6f70652e747970652e76312e526c696d69744163636f756e744c696d6974734800520d6163636f756e744c696d697473124e0a0d6163636f756e745f757361676518052001280b32272e73662e616e74656c6f70652e747970652e76312e526c696d69744163636f756e7455736167654800520c6163636f756e745573616765224e0a094f7065726174696f6e12150a114f5045524154494f4e5f554e4b4e4f574e100012140a104f5045524154494f4e5f494e53455254100112140a104f5045524154494f4e5f555044415445100242060a046b696e6422f5030a0b526c696d69745374617465125c0a17617665726167655f626c6f636b5f6e65745f757361676518012001280b32252e73662e616e74656c6f70652e747970652e76312e5573616765416363756d756c61746f72521461766572616765426c6f636b4e65745573616765125c0a17617665726167655f626c6f636b5f6370755f757361676518022001280b32252e73662e616e74656c6f70652e747970652e76312e5573616765416363756d756c61746f72521461766572616765426c6f636b4370755573616765122a0a1170656e64696e675f6e65745f7573616765180320012804520f70656e64696e674e65745573616765122a0a1170656e64696e675f6370755f7573616765180420012804520f70656e64696e67437075557361676512280a10746f74616c5f6e65745f776569676874180520012804520e746f74616c4e657457656967687412280a10746f74616c5f6370755f776569676874180620012804520e746f74616c43707557656967687412260a0f746f74616c5f72616d5f6279746573180720012804520d746f74616c52616d4279746573122a0a117669727475616c5f6e65745f6c696d6974180820012804520f7669727475616c4e65744c696d6974122a0a117669727475616c5f6370755f6c696d6974180920012804520f7669727475616c4370754c696d697422dc020a0c526c696d6974436f6e666967125d0a146370755f6c696d69745f706172616d657465727318012001280b322b2e73662e616e74656c6f70652e747970652e76312e456c61737469634c696d6974506172616d657465727352126370754c696d6974506172616d6574657273125d0a146e65745f6c696d69745f706172616d657465727318022001280b322b2e73662e616e74656c6f70652e747970652e76312e456c61737469634c696d6974506172616d657465727352126e65744c696d6974506172616d657465727312460a206163636f756e745f6370755f75736167655f617665726167655f77696e646f7718032001280d521c6163636f756e7443707555736167654176657261676557696e646f7712460a206163636f756e745f6e65745f75736167655f617665726167655f77696e646f7718042001280d521c6163636f756e744e657455736167654176657261676557696e646f7722a0010a13526c696d69744163636f756e744c696d69747312140a056f776e657218012001280952056f776e657212180a0770656e64696e67180220012808520770656e64696e67121d0a0a6e65745f77656967687418032001280352096e6574576569676874121d0a0a6370755f7765696768741804200128035209637075576569676874121b0a0972616d5f6279746573180520012803520872616d427974657322cf010a12526c696d69744163636f756e74557361676512140a056f776e657218012001280952056f776e657212420a096e65745f757361676518022001280b32252e73662e616e74656c6f70652e747970652e76312e5573616765416363756d756c61746f7252086e6574557361676512420a096370755f757361676518032001280b32252e73662e616e74656c6f70652e747970652e76312e5573616765416363756d756c61746f7252086370755573616765121b0a0972616d5f7573616765180420012804520872616d5573616765226c0a105573616765416363756d756c61746f7212210a0c6c6173745f6f7264696e616c18012001280d520b6c6173744f7264696e616c12190a0876616c75655f6578180220012804520776616c75654578121a0a08636f6e73756d65641803200128045208636f6e73756d65642281020a16456c61737469634c696d6974506172616d657465727312160a06746172676574180120012804520674617267657412100a036d617818022001280452036d617812180a07706572696f647318032001280d5207706572696f647312250a0e6d61785f6d756c7469706c69657218042001280d520d6d61784d756c7469706c696572123f0a0d636f6e74726163745f7261746518052001280b321a2e73662e616e74656c6f70652e747970652e76312e526174696f520c636f6e747261637452617465123b0a0b657870616e645f7261746518062001280b321a2e73662e616e74656c6f70652e747970652e76312e526174696f520a657870616e645261746522470a05526174696f121c0a096e756d657261746f7218012001280452096e756d657261746f7212200a0b64656e6f6d696e61746f72180220012804520b64656e6f6d696e61746f7222ae040a09457863657074696f6e12120a04636f64651801200128055204636f646512120a046e616d6518022001280952046e616d6512180a076d65737361676518032001280952076d657373616765123f0a05737461636b18042003280b32292e73662e616e74656c6f70652e747970652e76312e457863657074696f6e2e4c6f674d6573736167655205737461636b1a7d0a0a4c6f674d65737361676512430a07636f6e7465787418012001280b32292e73662e616e74656c6f70652e747970652e76312e457863657074696f6e2e4c6f67436f6e746578745207636f6e7465787412160a06666f726d61741802200128095206666f726d617412120a046461746118042001280c5204646174611a9e020a0a4c6f67436f6e7465787412140a056c6576656c18012001280952056c6576656c12120a0466696c65180220012809520466696c6512120a046c696e6518032001280552046c696e6512160a066d6574686f6418042001280952066d6574686f64121a0a08686f73746e616d651805200128095208686f73746e616d65121f0a0b7468726561645f6e616d65180620012809520a7468726561644e616d6512380a0974696d657374616d7018072001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d7012430a07636f6e7465787418082001280b32292e73662e616e74656c6f70652e747970652e76312e457863657074696f6e2e4c6f67436f6e746578745207636f6e7465787422e7020a074665617475726512250a0e666561747572655f646967657374180120012809520d6665617475726544696765737412640a177375626a6563746976655f7265737472696374696f6e7318022001280b322b2e73662e616e74656c6f70652e747970652e76312e5375626a6563746976655265737472696374696f6e7352167375626a6563746976655265737472696374696f6e73122d0a126465736372697074696f6e5f64696765737418032001280952116465736372697074696f6e44696765737412220a0c646570656e64656e63696573180420032809520c646570656e64656e6369657312320a1570726f746f636f6c5f666561747572655f74797065180520012809521370726f746f636f6c466561747572655479706512480a0d73706563696669636174696f6e18062003280b32222e73662e616e74656c6f70652e747970652e76312e53706563696669636174696f6e520d73706563696669636174696f6e22b2010a165375626a6563746976655265737472696374696f6e7312180a07656e61626c65641801200128085207656e61626c656412350a1670726561637469766174696f6e5f7265717569726564180220012808521570726561637469766174696f6e526571756972656412470a206561726c696573745f616c6c6f7765645f61637469766174696f6e5f74696d65180320012809521d6561726c69657374416c6c6f77656441637469766174696f6e54696d6522390a0d53706563696669636174696f6e12120a046e616d6518012001280952046e616d6512140a0576616c7565180220012809520576616c756522e2010a124163636f756e744372656174696f6e52656612180a076163636f756e7418012001280952076163636f756e7412180a0763726561746f72180220012809520763726561746f72121b0a09626c6f636b5f6e756d1803200128045208626c6f636b4e756d12190a08626c6f636b5f69641804200128095207626c6f636b496412390a0a626c6f636b5f74696d6518052001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d705209626c6f636b54696d6512250a0e7472616e73616374696f6e5f6964180620012809520d7472616e73616374696f6e496422c4010a0f4865616465724f6e6c79426c6f636b120e0a0269641801200128095202696412160a066e756d62657218022001280d52066e756d62657212380a0668656164657218042001280b32202e73662e616e74656c6f70652e747970652e76312e426c6f636b4865616465725206686561646572124f0a10626c6f636b726f6f745f6d65726b6c65180b2001280b32242e73662e616e74656c6f70652e747970652e76312e426c6f636b526f6f744d65726b6c65520f626c6f636b726f6f744d65726b6c652297010a1c5472616e73616374696f6e547261636557697468426c6f636b526566123b0a05747261636518012001280b32252e73662e616e74656c6f70652e747970652e76312e5472616e73616374696f6e547261636552057472616365123a0a09626c6f636b5f72656618022001280b321d2e73662e616e74656c6f70652e747970652e76312e426c6f636b5265665208626c6f636b52656622360a08426c6f636b52656612120a046861736818012001280c52046861736812160a066e756d62657218022001280452066e756d6265722aba010a12426c6f636b5265766572736962696c697479121b0a17424c4f434b5245564552534942494c4954595f4e4f4e45100012210a1d424c4f434b5245564552534942494c4954595f52455645525349424c45100112230a1f424c4f434b5245564552534942494c4954595f495252455645525349424c451002121c0a18424c4f434b5245564552534942494c4954595f5354414c45100312210a1d424c4f434b5245564552534942494c4954595f4d415942455354414c4510042a8c020a115472616e73616374696f6e537461747573121a0a165452414e53414354494f4e5354415455535f4e4f4e451000121e0a1a5452414e53414354494f4e5354415455535f45584543555445441001121e0a1a5452414e53414354494f4e5354415455535f534f46544641494c1002121e0a1a5452414e53414354494f4e5354415455535f484152444641494c1003121d0a195452414e53414354494f4e5354415455535f44454c415945441004121d0a195452414e53414354494f4e5354415455535f455850495245441005121d0a195452414e53414354494f4e5354415455535f554e4b4e4f574e1006121e0a1a5452414e53414354494f4e5354415455535f43414e43454c4544100742545a526769746875622e636f6d2f73747265616d696e67666173742f66697265686f73652d616e74656c6f70652f74797065732f70622f73662f616e74656c6f70652f747970652f76313b7062616e74656c6f7065620670726f746f33", + }, + { + // sf/arweave/type/v1/type.proto (https://buf.build/pinax/firehose-arweave/docs/eeea46c6211b43189e1af8bb682b3bc6:sf.arweave.type.v1) + proto: "0a1d73662f617277656176652f747970652f76312f747970652e70726f746f121273662e617277656176652e747970652e7631221e0a06426967496e7412140a05627974657318012001280c5205627974657322a6060a05426c6f636b12100a0376657218012001280d5203766572121d0a0a696e6465705f6861736818022001280c5209696e6465704861736812140a056e6f6e636518032001280c52056e6f6e636512250a0e70726576696f75735f626c6f636b18042001280c520d70726576696f7573426c6f636b121c0a0974696d657374616d70180520012804520974696d657374616d7012230a0d6c6173745f7265746172676574180620012804520c6c6173745265746172676574122e0a046469666618072001280b321a2e73662e617277656176652e747970652e76312e426967496e7452046469666612160a06686569676874180820012804520668656967687412120a046861736818092001280c52046861736812170a0774785f726f6f74180a2001280c52067478526f6f7412310a03747873180b2003280b321f2e73662e617277656176652e747970652e76312e5472616e73616374696f6e5203747873121f0a0b77616c6c65745f6c697374180c2001280c520a77616c6c65744c697374121f0a0b7265776172645f61646472180d2001280c520a72657761726441646472122b0a0474616773180e2003280b32172e73662e617277656176652e747970652e76312e546167520474616773123b0a0b7265776172645f706f6f6c180f2001280b321a2e73662e617277656176652e747970652e76312e426967496e74520a726577617264506f6f6c12390a0a77656176655f73697a6518102001280b321a2e73662e617277656176652e747970652e76312e426967496e745209776561766553697a6512390a0a626c6f636b5f73697a6518112001280b321a2e73662e617277656176652e747970652e76312e426967496e745209626c6f636b53697a6512430a0f63756d756c61746976655f6469666618122001280b321a2e73662e617277656176652e747970652e76312e426967496e74520e63756d756c61746976654469666612280a10686173685f6c6973745f6d65726b6c6518142001280c520e686173684c6973744d65726b6c6512330a03706f6118152001280b32212e73662e617277656176652e747970652e76312e50726f6f664f664163636573735203706f6122730a0d50726f6f664f6641636365737312160a066f7074696f6e18012001280952066f7074696f6e12170a0774785f7061746818022001280c5206747850617468121b0a09646174615f7061746818032001280c5208646174615061746812140a056368756e6b18042001280c52056368756e6b229d030a0b5472616e73616374696f6e12160a06666f726d617418012001280d5206666f726d6174120e0a02696418022001280c5202696412170a076c6173745f747818032001280c52066c617374547812140a056f776e657218042001280c52056f776e6572122b0a047461677318052003280b32172e73662e617277656176652e747970652e76312e54616752047461677312160a0674617267657418062001280c520674617267657412360a087175616e7469747918072001280b321a2e73662e617277656176652e747970652e76312e426967496e7452087175616e7469747912120a046461746118082001280c52046461746112370a09646174615f73697a6518092001280b321a2e73662e617277656176652e747970652e76312e426967496e7452086461746153697a65121b0a09646174615f726f6f74180a2001280c520864617461526f6f74121c0a097369676e6174757265180b2001280c52097369676e617475726512320a06726577617264180c2001280b321a2e73662e617277656176652e747970652e76312e426967496e745206726577617264222f0a0354616712120a046e616d6518012001280c52046e616d6512140a0576616c756518022001280c520576616c756542515a4f6769746875622e636f6d2f70696e61782d6e6574776f726b2f66697265686f73652d617277656176652f74797065732f70622f73662f617277656176652f747970652f76313b706261727765617665620670726f746f33", + }, + { + // google/protobuf/timestamp.proto (https://buf.build/pinax/firehose-beacon/docs/93533eddef61485e8cabea82049d6e42:google.protobuf) + proto: "0a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f120f676f6f676c652e70726f746f627566223b0a0954696d657374616d7012180a077365636f6e647318012001280352077365636f6e647312140a056e616e6f7318022001280552056e616e6f734285010a13636f6d2e676f6f676c652e70726f746f627566420e54696d657374616d7050726f746f50015a32676f6f676c652e676f6c616e672e6f72672f70726f746f6275662f74797065732f6b6e6f776e2f74696d657374616d707062f80101a20203475042aa021e476f6f676c652e50726f746f6275662e57656c6c4b6e6f776e5479706573620670726f746f33", + }, + { + // sf/beacon/type/v1/type.proto (https://buf.build/pinax/firehose-beacon/docs/93533eddef61485e8cabea82049d6e42:sf.beacon.type.v1) + proto: "0a1c73662f626561636f6e2f747970652f76312f747970652e70726f746f121173662e626561636f6e2e747970652e76311a1f676f6f676c652f70726f746f6275662f74696d657374616d702e70726f746f22a1050a05426c6f636b12180a0776657273696f6e18012001280d520776657273696f6e122b0a047370656318022001280e32172e73662e626561636f6e2e747970652e76312e5370656352047370656312120a04736c6f741803200128045204736c6f74121f0a0b706172656e745f736c6f74180420012804520a706172656e74536c6f7412120a04726f6f7418052001280c5204726f6f74121f0a0b706172656e745f726f6f7418062001280c520a706172656e74526f6f74121d0a0a73746174655f726f6f7418072001280c52097374617465526f6f7412250a0e70726f706f7365725f696e646578180820012804520d70726f706f736572496e646578121b0a09626f64795f726f6f7418092001280c5208626f6479526f6f7412370a0670686173653018142001280b321d2e73662e626561636f6e2e747970652e76312e506861736530426f64794800520670686173653012370a06616c7461697218152001280b321d2e73662e626561636f6e2e747970652e76312e416c74616972426f647948005206616c7461697212400a0962656c6c617472697818162001280b32202e73662e626561636f6e2e747970652e76312e42656c6c6174726978426f64794800520962656c6c6174726978123a0a07636170656c6c6118172001280b321e2e73662e626561636f6e2e747970652e76312e436170656c6c61426f647948005207636170656c6c6112340a0564656e656218182001280b321c2e73662e626561636f6e2e747970652e76312e44656e6562426f64794800520564656e6562121c0a097369676e6174757265181e2001280c52097369676e617475726512380a0974696d657374616d70181f2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d7042060a04426f647922fa030a0a506861736530426f647912210a0c72616e646f5f72657665616c18012001280c520b72616e646f52657665616c12380a09657468315f6461746118022001280b321b2e73662e626561636f6e2e747970652e76312e457468314461746152086574683144617461121a0a08677261666669746918032001280c5208677261666669746912520a1270726f706f7365725f736c617368696e677318042003280b32232e73662e626561636f6e2e747970652e76312e50726f706f736572536c617368696e67521170726f706f736572536c617368696e677312520a1261747465737465725f736c617368696e677318052003280b32232e73662e626561636f6e2e747970652e76312e4174746573746572536c617368696e6752116174746573746572536c617368696e677312420a0c6174746573746174696f6e7318062003280b321e2e73662e626561636f6e2e747970652e76312e4174746573746174696f6e520c6174746573746174696f6e7312360a086465706f7369747318072003280b321a2e73662e626561636f6e2e747970652e76312e4465706f73697452086465706f73697473124f0a0f766f6c756e746172795f657869747318082003280b32262e73662e626561636f6e2e747970652e76312e5369676e6564566f6c756e7461727945786974520e766f6c756e74617279457869747322c3040a0a416c74616972426f647912210a0c72616e646f5f72657665616c18012001280c520b72616e646f52657665616c12380a09657468315f6461746118022001280b321b2e73662e626561636f6e2e747970652e76312e457468314461746152086574683144617461121a0a08677261666669746918032001280c5208677261666669746912520a1270726f706f7365725f736c617368696e677318042003280b32232e73662e626561636f6e2e747970652e76312e50726f706f736572536c617368696e67521170726f706f736572536c617368696e677312520a1261747465737465725f736c617368696e677318052003280b32232e73662e626561636f6e2e747970652e76312e4174746573746572536c617368696e6752116174746573746572536c617368696e677312420a0c6174746573746174696f6e7318062003280b321e2e73662e626561636f6e2e747970652e76312e4174746573746174696f6e520c6174746573746174696f6e7312360a086465706f7369747318072003280b321a2e73662e626561636f6e2e747970652e76312e4465706f73697452086465706f73697473124f0a0f766f6c756e746172795f657869747318082003280b32262e73662e626561636f6e2e747970652e76312e5369676e6564566f6c756e7461727945786974520e766f6c756e74617279457869747312470a0e73796e635f61676772656761746518092001280b32202e73662e626561636f6e2e747970652e76312e53796e63416767726567617465520d73796e6341676772656761746522a1050a0d42656c6c6174726978426f647912210a0c72616e646f5f72657665616c18012001280c520b72616e646f52657665616c12380a09657468315f6461746118022001280b321b2e73662e626561636f6e2e747970652e76312e457468314461746152086574683144617461121a0a08677261666669746918032001280c5208677261666669746912520a1270726f706f7365725f736c617368696e677318042003280b32232e73662e626561636f6e2e747970652e76312e50726f706f736572536c617368696e67521170726f706f736572536c617368696e677312520a1261747465737465725f736c617368696e677318052003280b32232e73662e626561636f6e2e747970652e76312e4174746573746572536c617368696e6752116174746573746572536c617368696e677312420a0c6174746573746174696f6e7318062003280b321e2e73662e626561636f6e2e747970652e76312e4174746573746174696f6e520c6174746573746174696f6e7312360a086465706f7369747318072003280b321a2e73662e626561636f6e2e747970652e76312e4465706f73697452086465706f73697473124f0a0f766f6c756e746172795f657869747318082003280b32262e73662e626561636f6e2e747970652e76312e5369676e6564566f6c756e7461727945786974520e766f6c756e74617279457869747312470a0e73796e635f61676772656761746518092001280b32202e73662e626561636f6e2e747970652e76312e53796e63416767726567617465520d73796e6341676772656761746512590a11657865637574696f6e5f7061796c6f6164180a2001280b322c2e73662e626561636f6e2e747970652e76312e42656c6c6174726978457865637574696f6e5061796c6f61645210657865637574696f6e5061796c6f6164229d050a0b436170656c6c61426f647912210a0c72616e646f5f72657665616c18012001280c520b72616e646f52657665616c12380a09657468315f6461746118022001280b321b2e73662e626561636f6e2e747970652e76312e457468314461746152086574683144617461121a0a08677261666669746918032001280c5208677261666669746912520a1270726f706f7365725f736c617368696e677318042003280b32232e73662e626561636f6e2e747970652e76312e50726f706f736572536c617368696e67521170726f706f736572536c617368696e677312520a1261747465737465725f736c617368696e677318052003280b32232e73662e626561636f6e2e747970652e76312e4174746573746572536c617368696e6752116174746573746572536c617368696e677312420a0c6174746573746174696f6e7318062003280b321e2e73662e626561636f6e2e747970652e76312e4174746573746174696f6e520c6174746573746174696f6e7312360a086465706f7369747318072003280b321a2e73662e626561636f6e2e747970652e76312e4465706f73697452086465706f73697473124f0a0f766f6c756e746172795f657869747318082003280b32262e73662e626561636f6e2e747970652e76312e5369676e6564566f6c756e7461727945786974520e766f6c756e74617279457869747312470a0e73796e635f61676772656761746518092001280b32202e73662e626561636f6e2e747970652e76312e53796e63416767726567617465520d73796e6341676772656761746512570a11657865637574696f6e5f7061796c6f6164180a2001280b322a2e73662e626561636f6e2e747970652e76312e436170656c6c61457865637574696f6e5061796c6f61645210657865637574696f6e5061796c6f616422f3060a0944656e6562426f647912210a0c72616e646f5f72657665616c18012001280c520b72616e646f52657665616c12380a09657468315f6461746118022001280b321b2e73662e626561636f6e2e747970652e76312e457468314461746152086574683144617461121a0a08677261666669746918032001280c5208677261666669746912520a1270726f706f7365725f736c617368696e677318042003280b32232e73662e626561636f6e2e747970652e76312e50726f706f736572536c617368696e67521170726f706f736572536c617368696e677312520a1261747465737465725f736c617368696e677318052003280b32232e73662e626561636f6e2e747970652e76312e4174746573746572536c617368696e6752116174746573746572536c617368696e677312420a0c6174746573746174696f6e7318062003280b321e2e73662e626561636f6e2e747970652e76312e4174746573746174696f6e520c6174746573746174696f6e7312360a086465706f7369747318072003280b321a2e73662e626561636f6e2e747970652e76312e4465706f73697452086465706f73697473124f0a0f766f6c756e746172795f657869747318082003280b32262e73662e626561636f6e2e747970652e76312e5369676e6564566f6c756e7461727945786974520e766f6c756e74617279457869747312470a0e73796e635f61676772656761746518092001280b32202e73662e626561636f6e2e747970652e76312e53796e63416767726567617465520d73796e6341676772656761746512550a11657865637574696f6e5f7061796c6f6164180a2001280b32282e73662e626561636f6e2e747970652e76312e44656e6562457865637574696f6e5061796c6f61645210657865637574696f6e5061796c6f616412660a18626c735f746f5f657865637574696f6e5f6368616e676573180b2003280b322d2e73662e626561636f6e2e747970652e76312e5369676e6564424c53546f457865637574696f6e4368616e67655215626c73546f457865637574696f6e4368616e67657312300a14626c6f625f6b7a675f636f6d6d69746d656e7473180c2003280c5212626c6f624b7a67436f6d6d69746d656e7473123e0a0e656d6265646465645f626c6f627318142003280b32172e73662e626561636f6e2e747970652e76312e426c6f62520d656d626564646564426c6f627322710a08457468314461746112210a0c6465706f7369745f726f6f7418012001280c520b6465706f736974526f6f7412230a0d6465706f7369745f636f756e74180220012804520c6465706f736974436f756e74121d0a0a626c6f636b5f6861736818032001280c5209626c6f636b4861736822ba010a1050726f706f736572536c617368696e6712520a0f7369676e65645f6865616465725f3118012001280b322a2e73662e626561636f6e2e747970652e76312e5369676e6564426561636f6e426c6f636b486561646572520d7369676e65644865616465723112520a0f7369676e65645f6865616465725f3218022001280b322a2e73662e626561636f6e2e747970652e76312e5369676e6564426561636f6e426c6f636b486561646572520d7369676e65644865616465723222aa010a104174746573746572536c617368696e67124a0a0d6174746573746174696f6e5f3118012001280b32252e73662e626561636f6e2e747970652e76312e496e64657865644174746573746174696f6e520c6174746573746174696f6e31124a0a0d6174746573746174696f6e5f3218022001280b32252e73662e626561636f6e2e747970652e76312e496e64657865644174746573746174696f6e520c6174746573746174696f6e32228e010a0b4174746573746174696f6e12290a106167677265676174696f6e5f6269747318012001280c520f6167677265676174696f6e4269747312360a046461746118022001280b32222e73662e626561636f6e2e747970652e76312e4174746573746174696f6e44617461520464617461121c0a097369676e617475726518032001280c52097369676e617475726522530a074465706f73697412140a0570726f6f6618012003280c520570726f6f6612320a046461746118022001280b321e2e73662e626561636f6e2e747970652e76312e4465706f73697444617461520464617461226f0a135369676e6564566f6c756e7461727945786974123a0a076d65737361676518012001280b32202e73662e626561636f6e2e747970652e76312e566f6c756e746172794578697452076d657373616765121c0a097369676e617475726518022001280c52097369676e617475726522750a0d53796e63416767726567617465122c0a1273796e635f636f6d6d697465655f6269747318012001280c521073796e63436f6d6d697465654269747312360a1773796e635f636f6d69747465655f7369676e617475726518022001280c521573796e63436f6d69747465655369676e61747572652285040a1942656c6c6174726978457865637574696f6e5061796c6f6164121f0a0b706172656e745f6861736818012001280c520a706172656e744861736812230a0d6665655f726563697069656e7418022001280c520c666565526563697069656e74121d0a0a73746174655f726f6f7418032001280c52097374617465526f6f7412230a0d72656365697074735f726f6f7418042001280c520c7265636569707473526f6f74121d0a0a6c6f67735f626c6f6f6d18052001280c52096c6f6773426c6f6f6d121f0a0b707265765f72616e64616f18062001280c520a7072657652616e64616f12210a0c626c6f636b5f6e756d626572180720012804520b626c6f636b4e756d626572121b0a096761735f6c696d697418082001280452086761734c696d697412190a086761735f7573656418092001280452076761735573656412380a0974696d657374616d70180a2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d70121d0a0a65787472615f64617461180b2001280c520965787472614461746112270a10626173655f6665655f7065725f676173180c2001280c520d62617365466565506572476173121d0a0a626c6f636b5f68617368180d2001280c5209626c6f636b4861736812220a0c7472616e73616374696f6e73180e2003280c520c7472616e73616374696f6e7322c4040a17436170656c6c61457865637574696f6e5061796c6f6164121f0a0b706172656e745f6861736818012001280c520a706172656e744861736812230a0d6665655f726563697069656e7418022001280c520c666565526563697069656e74121d0a0a73746174655f726f6f7418032001280c52097374617465526f6f7412230a0d72656365697074735f726f6f7418042001280c520c7265636569707473526f6f74121d0a0a6c6f67735f626c6f6f6d18052001280c52096c6f6773426c6f6f6d121f0a0b707265765f72616e64616f18062001280c520a7072657652616e64616f12210a0c626c6f636b5f6e756d626572180720012804520b626c6f636b4e756d626572121b0a096761735f6c696d697418082001280452086761734c696d697412190a086761735f7573656418092001280452076761735573656412380a0974696d657374616d70180a2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d70121d0a0a65787472615f64617461180b2001280c520965787472614461746112270a10626173655f6665655f7065725f676173180c2001280c520d62617365466565506572476173121d0a0a626c6f636b5f68617368180d2001280c5209626c6f636b4861736812220a0c7472616e73616374696f6e73180e2003280c520c7472616e73616374696f6e73123f0a0b7769746864726177616c73180f2003280b321d2e73662e626561636f6e2e747970652e76312e5769746864726177616c520b7769746864726177616c73228e050a1544656e6562457865637574696f6e5061796c6f6164121f0a0b706172656e745f6861736818012001280c520a706172656e744861736812230a0d6665655f726563697069656e7418022001280c520c666565526563697069656e74121d0a0a73746174655f726f6f7418032001280c52097374617465526f6f7412230a0d72656365697074735f726f6f7418042001280c520c7265636569707473526f6f74121d0a0a6c6f67735f626c6f6f6d18052001280c52096c6f6773426c6f6f6d121f0a0b707265765f72616e64616f18062001280c520a7072657652616e64616f12210a0c626c6f636b5f6e756d626572180720012804520b626c6f636b4e756d626572121b0a096761735f6c696d697418082001280452086761734c696d697412190a086761735f7573656418092001280452076761735573656412380a0974696d657374616d70180a2001280b321a2e676f6f676c652e70726f746f6275662e54696d657374616d70520974696d657374616d70121d0a0a65787472615f64617461180b2001280c520965787472614461746112270a10626173655f6665655f7065725f676173180c2001280c520d62617365466565506572476173121d0a0a626c6f636b5f68617368180d2001280c5209626c6f636b4861736812220a0c7472616e73616374696f6e73180e2003280c520c7472616e73616374696f6e73123f0a0b7769746864726177616c73180f2003280b321d2e73662e626561636f6e2e747970652e76312e5769746864726177616c520b7769746864726177616c7312220a0d626c6f625f6761735f75736564181020012804520b626c6f624761735573656412260a0f6578636573735f626c6f625f676173181120012804520d657863657373426c6f62476173227d0a1a5369676e6564424c53546f457865637574696f6e4368616e676512410a076d65737361676518012001280b32272e73662e626561636f6e2e747970652e76312e424c53546f457865637574696f6e4368616e676552076d657373616765121c0a097369676e617475726518022001280c52097369676e6174757265229a010a14424c53546f457865637574696f6e4368616e676512270a0f76616c696461746f725f696e646578180120012804520e76616c696461746f72496e64657812270a1066726f6d5f626c735f7075625f6b657918022001280c520d66726f6d426c735075624b657912300a14746f5f657865637574696f6e5f6164647265737318032001280c5212746f457865637574696f6e41646472657373228e010a0a5769746864726177616c12290a107769746864726177616c5f696e646578180120012804520f7769746864726177616c496e64657812270a0f76616c696461746f725f696e646578180220012804520e76616c696461746f72496e64657812180a076164647265737318032001280c52076164647265737312120a0467776569180420012804520467776569224e0a0d566f6c756e746172794578697412140a0565706f6368180120012804520565706f636812270a0f76616c696461746f725f696e646578180220012804520e76616c696461746f72496e6465782295010a0b4465706f73697444617461121d0a0a7075626c69635f6b657918012001280c52097075626c69634b657912350a167769746864726177616c5f63726564656e7469616c7318022001280c52157769746864726177616c43726564656e7469616c7312120a0467776569180320012804520467776569121c0a097369676e617475726518042001280c52097369676e61747572652297010a12496e64657865644174746573746174696f6e122b0a11617474657374696e675f696e64696365731801200328045210617474657374696e67496e646963657312360a046461746118022001280b32222e73662e626561636f6e2e747970652e76312e4174746573746174696f6e44617461520464617461121c0a097369676e617475726518032001280c52097369676e617475726522e8010a0f4174746573746174696f6e4461746112120a04736c6f741801200128045204736c6f7412270a0f636f6d6d69747465655f696e646578180220012804520e636f6d6d6974746565496e646578122a0a11626561636f6e5f626c6f636b5f726f6f7418032001280c520f626561636f6e426c6f636b526f6f7412350a06736f7572636518042001280b321d2e73662e626561636f6e2e747970652e76312e436865636b706f696e745206736f7572636512350a0674617267657418052001280b321d2e73662e626561636f6e2e747970652e76312e436865636b706f696e74520674617267657422360a0a436865636b706f696e7412140a0565706f6368180120012804520565706f636812120a04726f6f7418022001280c5204726f6f7422770a175369676e6564426561636f6e426c6f636b486561646572123e0a076d65737361676518012001280b32242e73662e626561636f6e2e747970652e76312e426561636f6e426c6f636b48656164657252076d657373616765121c0a095369676e617475726518022001280c52095369676e617475726522ab010a11426561636f6e426c6f636b48656164657212120a04736c6f741801200128045204736c6f7412250a0e70726f706f7365725f696e646578180220012804520d70726f706f736572496e646578121f0a0b706172656e745f726f6f7418032001280c520a706172656e74526f6f74121d0a0a73746174655f726f6f7418042001280c52097374617465526f6f74121b0a09626f64795f726f6f7418052001280c5208626f6479526f6f7422b9010a04426c6f6212140a05696e6465781801200128045205696e64657812120a04626c6f6218022001280c5204626c6f6212250a0e6b7a675f636f6d6d69746d656e7418032001280c520d6b7a67436f6d6d69746d656e74121b0a096b7a675f70726f6f6618042001280c52086b7a6750726f6f6612430a1e6b7a675f636f6d6d69746d656e745f696e636c7573696f6e5f70726f6f6618052003280c521b6b7a67436f6d6d69746d656e74496e636c7573696f6e50726f6f662a560a0453706563120f0a0b554e5350454349464945441000120a0a065048415345301001120a0a06414c544149521002120d0a0942454c4c41545249581003120b0a07434150454c4c41100412090a0544454e4542100542485a466769746875622e636f6d2f70696e61782d6e6574776f726b2f66697265686f73652d626561636f6e2f70622f73662f626561636f6e2f747970652f76313b7062626561636f6e620670726f746f33", + }, + { + // sf/fuel/type/v1/fuel.proto (https://buf.build/salka1988/firehose-fuel/docs/ee9fe9b3514149b7916f3af095a0e9b3:sf.fuel.type.v1) + proto: "0a1a73662f6675656c2f747970652f76312f6675656c2e70726f746f120f73662e6675656c2e747970652e763122fc020a05426c6f636b120e0a02696418012001280c5202696412160a0668656967687418022001280d5206686569676874121b0a0964615f68656967687418032001280452086461486569676874122a0a116d73675f726563656970745f636f756e74180420012804520f6d736752656365697074436f756e7412170a0774785f726f6f7418052001280c52067478526f6f7412280a106d73675f726563656970745f726f6f7418062001280c520e6d736752656365697074526f6f7412170a07707265765f696418072001280c5206707265764964121b0a09707265765f726f6f7418082001280c520870726576526f6f74121c0a0974696d657374616d70180920012806520974696d657374616d7012290a106170706c69636174696f6e5f68617368180a2001280c520f6170706c69636174696f6e4861736812400a0c7472616e73616374696f6e73180b2003280b321c2e73662e6675656c2e747970652e76312e5472616e73616374696f6e520c7472616e73616374696f6e7322ee010a0b5472616e73616374696f6e120e0a02696418012001280c5202696412340a08726563656970747318022003280b32182e73662e6675656c2e747970652e76312e526563656970745208726563656970747312310a0673637269707418032001280b32172e73662e6675656c2e747970652e76312e5363726970744800520673637269707412310a0663726561746518042001280b32172e73662e6675656c2e747970652e76312e43726561746548005206637265617465122b0a046d696e7418052001280b32152e73662e6675656c2e747970652e76312e4d696e74480052046d696e7442060a046b696e6422c8020a0653637269707412280a107363726970745f6761735f6c696d6974180120012804520e7363726970744761734c696d697412160a0673637269707418022001280c5206736372697074121f0a0b7363726970745f6461746118032001280c520a7363726970744461746112350a08706f6c696369657318042001280b32192e73662e6675656c2e747970652e76312e506f6c69636965735208706f6c6963696573122e0a06696e7075747318052003280b32162e73662e6675656c2e747970652e76312e496e7075745206696e7075747312310a076f75747075747318062003280b32172e73662e6675656c2e747970652e76312e4f757470757452076f757470757473121c0a097769746e657373657318072003280c52097769746e657373657312230a0d72656365697074735f726f6f7418082001280c520c7265636569707473526f6f7422f6020a0643726561746512270a0f62797465636f64655f6c656e677468180120012804520e62797465636f64654c656e67746812340a1662797465636f64655f7769746e6573735f696e64657818022001280d521462797465636f64655769746e657373496e64657812350a08706f6c696369657318032001280b32192e73662e6675656c2e747970652e76312e506f6c69636965735208706f6c696369657312410a0d73746f726167655f736c6f747318042003280b321c2e73662e6675656c2e747970652e76312e53746f72616765536c6f74520c73746f72616765536c6f7473122e0a06696e7075747318052003280b32162e73662e6675656c2e747970652e76312e496e7075745206696e7075747312310a076f75747075747318062003280b32172e73662e6675656c2e747970652e76312e4f757470757452076f757470757473121c0a097769746e657373657318072003280c52097769746e657373657312120a0473616c7418082001280c520473616c742297020a044d696e7412390a0a74785f706f696e74657218012001280b321a2e73662e6675656c2e747970652e76312e5478506f696e74657252097478506f696e74657212450a0e696e7075745f636f6e747261637418022001280b321e2e73662e6675656c2e747970652e76312e496e707574436f6e7472616374520d696e707574436f6e747261637412480a0f6f75747075745f636f6e747261637418032001280b321f2e73662e6675656c2e747970652e76312e4f7574707574436f6e7472616374520e6f7574707574436f6e7472616374121f0a0b6d696e745f616d6f756e74180420012804520a6d696e74416d6f756e7412220a0d6d696e745f61737365745f696418052001280c520b6d696e74417373657449642283040a05496e70757412380a0b636f696e5f7369676e656418012001280b32152e73662e6675656c2e747970652e76312e436f696e4800520a636f696e5369676e6564123e0a0e636f696e5f70726564696361746518022001280b32152e73662e6675656c2e747970652e76312e436f696e4800520d636f696e507265646963617465123c0a08636f6e747261637418032001280b321e2e73662e6675656c2e747970652e76312e496e707574436f6e747261637448005208636f6e7472616374124a0a136d6573736167655f636f696e5f7369676e656418042001280b32182e73662e6675656c2e747970652e76312e4d657373616765480052116d657373616765436f696e5369676e656412500a166d6573736167655f636f696e5f70726564696361746518052001280b32182e73662e6675656c2e747970652e76312e4d657373616765480052146d657373616765436f696e507265646963617465124a0a136d6573736167655f646174615f7369676e656418062001280b32182e73662e6675656c2e747970652e76312e4d657373616765480052116d657373616765446174615369676e656412500a166d6573736167655f646174615f70726564696361746518072001280b32182e73662e6675656c2e747970652e76312e4d657373616765480052146d6573736167654461746150726564696361746542060a046b696e6422f0020a04436f696e12300a077574786f5f696418012001280b32172e73662e6675656c2e747970652e76312e5574786f496452067574786f496412140a056f776e657218022001280c52056f776e657212160a06616d6f756e741803200128045206616d6f756e7412190a0861737365745f696418042001280c52076173736574496412390a0a74785f706f696e74657218052001280b321a2e73662e6675656c2e747970652e76312e5478506f696e74657252097478506f696e74657212230a0d7769746e6573735f696e64657818062001280d520c7769746e657373496e646578121a0a086d6174757269747918072001280d52086d61747572697479122c0a127072656469636174655f6761735f75736564180820012804521070726564696361746547617355736564121c0a0970726564696361746518092001280c520970726564696361746512250a0e7072656469636174655f64617461180a2001280c520d707265646963617465446174612299020a074d65737361676512160a0673656e64657218012001280c520673656e646572121c0a09726563697069656e7418022001280c5209726563697069656e7412160a06616d6f756e741803200128045206616d6f756e7412140a056e6f6e636518042001280c52056e6f6e636512230a0d7769746e6573735f696e64657818052001280d520c7769746e657373496e646578122c0a127072656469636174655f6761735f7573656418062001280452107072656469636174654761735573656412120a046461746118072001280c520464617461121c0a0970726564696361746518082001280c520970726564696361746512250a0e7072656469636174655f6461746118092001280c520d7072656469636174654461746122c9020a064f757470757412310a04636f696e18012001280b321b2e73662e6675656c2e747970652e76312e4f7574707574436f696e48005204636f696e123d0a08636f6e747261637418022001280b321f2e73662e6675656c2e747970652e76312e4f7574707574436f6e747261637448005208636f6e747261637412350a066368616e676518032001280b321b2e73662e6675656c2e747970652e76312e4f7574707574436f696e480052066368616e676512390a087661726961626c6518042001280b321b2e73662e6675656c2e747970652e76312e4f7574707574436f696e480052087661726961626c6512530a10636f6e74726163745f6372656174656418052001280b32262e73662e6675656c2e747970652e76312e4f7574707574436f6e7472616374437265617465644800520f636f6e74726163744372656174656442060a046b696e64224f0a0a4f7574707574436f696e120e0a02746f18012001280c5202746f12160a06616d6f756e741802200128045206616d6f756e7412190a0861737365745f696418032001280c52076173736574496422570a154f7574707574436f6e747261637443726561746564121f0a0b636f6e74726163745f696418012001280c520a636f6e74726163744964121d0a0a73746174655f726f6f7418022001280c52097374617465526f6f7422df010a0d496e707574436f6e747261637412300a077574786f5f696418012001280b32172e73662e6675656c2e747970652e76312e5574786f496452067574786f496412210a0c62616c616e63655f726f6f7418022001280c520b62616c616e6365526f6f74121d0a0a73746174655f726f6f7418032001280c52097374617465526f6f7412390a0a74785f706f696e74657218042001280b321a2e73662e6675656c2e747970652e76312e5478506f696e74657252097478506f696e746572121f0a0b636f6e74726163745f696418052001280c520a636f6e7472616374496422730a0e4f7574707574436f6e7472616374121f0a0b696e7075745f696e64657818012001280d520a696e707574496e64657812210a0c62616c616e63655f726f6f7418022001280c520b62616c616e6365526f6f74121d0a0a73746174655f726f6f7418032001280c52097374617465526f6f7422350a0b53746f72616765536c6f7412100a036b657918012001280c52036b657912140a0576616c756518022001280c520576616c756522400a065574786f496412130a0574785f696418012001280c52047478496412210a0c6f75747075745f696e64657818022001280d520b6f7574707574496e64657822490a095478506f696e74657212210a0c626c6f636b5f68656967687418012001280d520b626c6f636b48656967687412190a0874785f696e64657818022001280d52077478496e64657822220a08506f6c696369657312160a0676616c756573180120032804520676616c75657322530a1050616e6963496e737472756374696f6e12160a06726561736f6e18012001280d5206726561736f6e12270a0f7261775f696e737472756374696f6e18022001280d520e726177496e737472756374696f6e22b7060a075265636569707412320a0463616c6c18012001280b321c2e73662e6675656c2e747970652e76312e43616c6c526563656970744800520463616c6c12430a0c72657475726e5f76616c756518022001280b321e2e73662e6675656c2e747970652e76312e52657475726e526563656970744800520b72657475726e56616c756512450a0b72657475726e5f6461746118032001280b32222e73662e6675656c2e747970652e76312e52657475726e44617461526563656970744800520a72657475726e4461746112350a0570616e696318042001280b321d2e73662e6675656c2e747970652e76312e50616e6963526563656970744800520570616e696312380a0672657665727418052001280b321e2e73662e6675656c2e747970652e76312e5265766572745265636569707448005206726576657274122f0a036c6f6718062001280b321b2e73662e6675656c2e747970652e76312e4c6f6752656365697074480052036c6f67123c0a086c6f675f6461746118072001280b321f2e73662e6675656c2e747970652e76312e4c6f674461746152656365697074480052076c6f6744617461123e0a087472616e7366657218082001280b32202e73662e6675656c2e747970652e76312e5472616e7366657252656365697074480052087472616e7366657212480a0c7472616e736665725f6f757418092001280b32232e73662e6675656c2e747970652e76312e5472616e736665724f7574526563656970744800520b7472616e736665724f7574124b0a0d7363726970745f726573756c74180a2001280b32242e73662e6675656c2e747970652e76312e536372697074526573756c74526563656970744800520c736372697074526573756c7412450a0b6d6573736167655f6f7574180b2001280b32222e73662e6675656c2e747970652e76312e4d6573736167654f7574526563656970744800520a6d6573736167654f757412320a046d696e74180c2001280b321c2e73662e6675656c2e747970652e76312e4d696e7452656365697074480052046d696e7412320a046275726e180d2001280b321c2e73662e6675656c2e747970652e76312e4275726e52656365697074480052046275726e42060a046b696e6422c2010a0b43616c6c52656365697074120e0a02696418012001280c52026964120e0a02746f18022001280c5202746f12160a06616d6f756e741803200128045206616d6f756e7412190a0861737365745f696418042001280c52076173736574496412100a03676173180520012804520367617312160a06706172616d311806200128045206706172616d3112160a06706172616d321807200128045206706172616d32120e0a02706318082001280452027063120e0a0269731809200128045202697322510a0d52657475726e52656365697074120e0a02696418012001280c5202696412100a0376616c180220012804520376616c120e0a02706318032001280452027063120e0a026973180420012804520269732293010a1152657475726e4461746152656365697074120e0a02696418012001280c5202696412100a03707472180220012804520370747212100a036c656e18032001280452036c656e12160a0664696765737418042001280c5206646967657374120e0a02706318052001280452027063120e0a0269731806200128045202697312120a046461746118072001280c520464617461229a010a0c50616e696352656365697074120e0a02696418012001280c5202696412390a06726561736f6e18022001280b32212e73662e6675656c2e747970652e76312e50616e6963496e737472756374696f6e5206726561736f6e120e0a02706318032001280452027063120e0a02697318042001280452026973121f0a0b636f6e74726163745f696418052001280c520a636f6e74726163744964224f0a0d52657665727452656365697074120e0a02696418012001280c52026964120e0a02726118022001280452027261120e0a02706318032001280452027063120e0a02697318042001280452026973227c0a0a4c6f6752656365697074120e0a02696418012001280c52026964120e0a02726118022001280452027261120e0a02726218032001280452027262120e0a02726318042001280452027263120e0a02726418052001280452027264120e0a02706318062001280452027063120e0a0269731807200128045202697322b0010a0e4c6f674461746152656365697074120e0a02696418012001280c52026964120e0a02726118022001280452027261120e0a0272621803200128045202726212100a03707472180420012804520370747212100a036c656e18052001280452036c656e12160a0664696765737418062001280c5206646967657374120e0a02706318072001280452027063120e0a0269731808200128045202697312120a046461746118092001280c5204646174612284010a0f5472616e7366657252656365697074120e0a02696418012001280c52026964120e0a02746f18022001280c5202746f12160a06616d6f756e741803200128045206616d6f756e7412190a0861737365745f696418042001280c520761737365744964120e0a02706318052001280452027063120e0a026973180620012804520269732287010a125472616e736665724f757452656365697074120e0a02696418012001280c52026964120e0a02746f18022001280c5202746f12160a06616d6f756e741803200128045206616d6f756e7412190a0861737365745f696418042001280c520761737365744964120e0a02706318052001280452027063120e0a0269731806200128045202697322480a13536372697074526573756c745265636569707412160a06726573756c741801200128045206726573756c7412190a086761735f7573656418022001280452076761735573656422b5010a114d6573736167654f75745265636569707412160a0673656e64657218012001280c520673656e646572121c0a09726563697069656e7418022001280c5209726563697069656e7412160a06616d6f756e741803200128045206616d6f756e7412140a056e6f6e636518042001280c52056e6f6e636512100a036c656e18052001280452036c656e12160a0664696765737418062001280c520664696765737412120a046461746118072001280c52046461746122770a0b4d696e745265636569707412150a067375625f696418012001280c52057375624964121f0a0b636f6e74726163745f696418022001280c520a636f6e7472616374496412100a0376616c180320012804520376616c120e0a02706318042001280452027063120e0a0269731805200128045202697322770a0b4275726e5265636569707412150a067375625f696418012001280c52057375624964121f0a0b636f6e74726163745f696418022001280c520a636f6e7472616374496412100a0376616c180320012804520376616c120e0a02706318042001280452027063120e0a02697318052001280452026973423d5a3b6769746875622e636f6d2f4675656c4c6162732f66697265686f73652d6675656c2f70622f73662f6675656c2f747970652f76313b70626675656c620670726f746f33", + }, + } +} \ No newline at end of file diff --git a/firehose/firehose-core/reader_node.go b/firehose/firehose-core/reader_node.go new file mode 100644 index 0000000..3ef2197 --- /dev/null +++ b/firehose/firehose-core/reader_node.go @@ -0,0 +1,22 @@ +package firecore + +import "golang.org/x/exp/maps" + +var ReaderNodeVariablesDocumentation = map[string]string{ + "{data-dir}": "The current data-dir path defined by the flag 'data-dir'", + "{node-data-dir}": "The node data dir path defined by the flag 'reader-node-data-dir'", + "{hostname}": "The machine's hostname", + "{start-block-num}": "The resolved start block number defined by the flag 'reader-node-start-block-num' (can be overwritten)", + "{stop-block-num}": "The stop block number defined by the flag 'reader-node-stop-block-num'", +} + +var ReaderNodeVariables = maps.Keys(ReaderNodeVariablesDocumentation) + +func ReaderNodeVariablesValues(resolver ReaderNodeArgumentResolver) map[string]string { + values := make(map[string]string, len(ReaderNodeVariables)) + for _, variable := range ReaderNodeVariables { + values[variable] = resolver(variable) + } + + return values +} diff --git a/firehose/firehose-core/reader_node_bootstrap.go b/firehose/firehose-core/reader_node_bootstrap.go new file mode 100644 index 0000000..f7136ae --- /dev/null +++ b/firehose/firehose-core/reader_node_bootstrap.go @@ -0,0 +1,126 @@ +package firecore + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/firehose-core/node-manager/operator" + "go.uber.org/zap" +) + +type ReaderNodeBootstrapperFactory func( + ctx context.Context, + logger *zap.Logger, + cmd *cobra.Command, + resolvedNodeArguments []string, + resolver ReaderNodeArgumentResolver, +) (operator.Bootstrapper, error) + +// noOpReaderNodeBootstrapperFactory is a factory that returns always `nil, nil` and is used +// as an empty override for the default bootstrapper logic. +func noOpReaderNodeBootstrapperFactory(ctx context.Context, logger *zap.Logger, cmd *cobra.Command, resolvedNodeArguments []string, resolver ReaderNodeArgumentResolver) (operator.Bootstrapper, error) { + return nil, nil +} + +func DefaultReaderNodeBootstrapDataURLFlagDescription() string { + return cli.Dedent(` + When specified, if the reader node is emtpy (e.g. that 'reader-node-data-dir' location doesn't exist + or has no file within it), the 'reader-node' is going to be boostrapped from it. The exact bootstrapping + behavior depends on the URL received. + + If the bootstrap URL is of the form 'bash:///?', the bash script at + '' will be executed. The script is going to receive in environment variables the resolved + reader node variables in the form of 'READER_NODE_'. The fully resolved node arguments + (from 'reader-node-arguments') are passed as args to the bash script. The query parameters accepted are: + + - arg= | Pass as extra argument to the script, prepended to the list of resolved node arguments + - env=%%3d | Pass as extra environment variable as = with key being upper-cased (multiple(s) allowed) + - env_= | Pass as extra environment variable as = with key being upper-cased (multiple(s) allowed) + - cwd= | Change the working directory to before running the script + - interpreter= | Use as the interpreter to run the script + - interpreter_arg= | Pass as arguments to the interpreter before the script path (multiple(s) allowed) + + If the bootstrap URL ends with 'tar.zst' or 'tar.zstd', the archive is read and extracted into the + 'reader-node-data-dir' location. The archive is expected to contain the full content of the 'reader-node-data-dir' + and is expanded as is. + `) + "\n" +} + +// DefaultReaderNodeBootstrapper is a constrtuction you can when you want the default bootstrapper logic to be applied +// but you need support new bootstrap data URL(s) format or override the default behavior for some type. +// +// The `overrideFactory` argument is a factory function that will be called first, if it returns a non-nil bootstrapper, +// it will be used and the default logic will be skipped. If it returns nil, the default logic will be applied. +func DefaultReaderNodeBootstrapper( + overrideFactory ReaderNodeBootstrapperFactory, +) ReaderNodeBootstrapperFactory { + return func( + ctx context.Context, + logger *zap.Logger, + cmd *cobra.Command, + resolvedNodeArguments []string, + resolver ReaderNodeArgumentResolver, + ) (operator.Bootstrapper, error) { + bootstrapDataURL := sflags.MustGetString(cmd, "reader-node-bootstrap-data-url") + if bootstrapDataURL == "" { + return nil, nil + } + + nodeDataDir := resolver("{node-data-dir}") + + if overrideFactory == nil { + panic("overrideFactory argument must be set") + } + + bootstrapper, err := overrideFactory(ctx, logger, cmd, resolvedNodeArguments, resolver) + if err != nil { + return nil, fmt.Errorf("override factory failed: %w", err) + } + + if bootstrapper != nil { + return bootstrapper, nil + } + + // Otherwise apply the default logic + switch { + case strings.HasSuffix(bootstrapDataURL, "tar.zst") || strings.HasSuffix(bootstrapDataURL, "tar.zstd"): + // There could be a mistmatch here if the user override `--datadir` manually, we live it for now + return NewTarballReaderNodeBootstrapper(bootstrapDataURL, nodeDataDir, logger), nil + + case strings.HasPrefix(bootstrapDataURL, "bash://"): + return NewBashNodeReaderBootstrapper(cmd, bootstrapDataURL, resolver, resolvedNodeArguments, logger), nil + + default: + return nil, fmt.Errorf("'reader-node-bootstrap-data-url' config should point to either an archive ending in '.tar.zstd' or a genesis file ending in '.json', not %s", bootstrapDataURL) + } + } +} + +func isBootstrapped(dataDir string, logger *zap.Logger) bool { + var foundFile bool + err := filepath.Walk(dataDir, + func(_ string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + + // As soon as there is a file, we assume it's bootstrapped + foundFile = true + return io.EOF + }) + if err != nil && !os.IsNotExist(err) && err != io.EOF { + logger.Warn("error while checking for bootstrapped status", zap.Error(err)) + } + + return foundFile +} diff --git a/firehose/firehose-core/reader_node_bootstrapper_bash.go b/firehose/firehose-core/reader_node_bootstrapper_bash.go new file mode 100644 index 0000000..e086010 --- /dev/null +++ b/firehose/firehose-core/reader_node_bootstrapper_bash.go @@ -0,0 +1,177 @@ +package firecore + +import ( + "context" + "fmt" + "net/url" + "os" + "os/exec" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli/sflags" + "go.uber.org/zap" +) + +func NewBashNodeReaderBootstrapper( + cmd *cobra.Command, + url string, + resolver ReaderNodeArgumentResolver, + resolvedNodeArguments []string, + logger *zap.Logger, +) *BashNodeBootstrapper { + dataDir := resolver("{node-data-dir}") + binaryPath := sflags.MustGetString(cmd, "reader-node-path") + + return &BashNodeBootstrapper{ + url: url, + dataDir: dataDir, + binaryPath: binaryPath, + resolver: resolver, + resolvedNodeArguments: resolvedNodeArguments, + logger: logger, + } +} + +type BashNodeBootstrapper struct { + url string + dataDir string + binaryPath string + resolver ReaderNodeArgumentResolver + resolvedNodeArguments []string + logger *zap.Logger +} + +func (b *BashNodeBootstrapper) isBootstrapped() bool { + return isBootstrapped(b.dataDir, b.logger) +} + +func (b *BashNodeBootstrapper) Bootstrap() error { + if b.isBootstrapped() { + return nil + } + + b.logger.Info("bootstrapping chain data from bash script", zap.String("bootstrap_data_url", b.url)) + + url, err := url.Parse(b.url) + if err != nil { + return fmt.Errorf("cannot parse bootstrap data URL %q: %w", b.url, err) + } + + // Should not happen but let's play safe and check the scheme for now + if url.Scheme != "bash" { + return fmt.Errorf("unsupported bootstrap data URL scheme %q", url.Scheme) + } + + parameters := url.Query() + scriptPath := b.scriptPath(url) + + interpreter := "bash" + if interpreterOverride := parameters.Get("interpreter"); interpreterOverride != "" { + interpreter = interpreterOverride + } + workingDirectory := parameters.Get("cwd") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) + defer cancel() + + args := []string{} + // First arguments are those to be passed to the interpeter + args = append(args, parameters["interpreter_arg"]...) + // Then the argument is always the script path + args = append(args, scriptPath) + // Next arguments are those provided in the URL query part with the `arg` key (multiple(s) allowed) + args = append(args, parameters["arg"]...) + // Then we append the resolved node arguments + args = append(args, b.resolvedNodeArguments...) + + cmd := exec.CommandContext(ctx, interpreter, args...) + if workingDirectory != "" { + cmd.Dir = workingDirectory + } + + cmd.Env = os.Environ() + cmd.Env = append(cmd.Env, b.scriptCustomEnv(url, parameters)...) + cmd.Env = append(cmd.Env, b.nodeVariablesToEnv()...) + cmd.Env = append(cmd.Env, fmt.Sprintf("READER_NODE_BINARY_PATH=%s", b.binaryPath)) + + // Everything goes to `os.Stderr`, standard logging in nodes is to go to `os.Stderr` + // so by default, we output everything to `os.Stderr`. + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("bootstrap script %q failed: %w", cmd.String(), err) + } + + return nil +} + +func (b *BashNodeBootstrapper) scriptPath(bootstrapURL *url.URL) string { + scriptPath := bootstrapURL.EscapedPath() + + // We handle relative paths differently to ensure they are properly + // resolved to the current working directory. + if strings.HasPrefix(scriptPath, "/.") { + return scriptPath[1:] + } + + return scriptPath +} + +// scriptCustomEnv returns the custom environment variables to be set when running the bootstrap script +// based on the URL query parameters. +// +// We support actually two form(s): `env=%3d` and `env_=`. When ecountering +// the first form, the key is made uppercase and the value is URL-decoded and we appendd them to the +// environment. When encountering the second form, we trim the `env_` prefix, make the key uppercase +// and append the value to the environment. +func (b *BashNodeBootstrapper) scriptCustomEnv(bootstrapURL *url.URL, parameters url.Values) (out []string) { + for k, values := range parameters { + for _, value := range values { + if k == "env" { + // We assume the full element is already URL-decoded + parts := strings.SplitN(value, "=", 2) + if len(parts) != 2 { + b.logger.Warn("invalid env URL query parameter", zap.String("key", k), zap.String("value", value)) + continue + } + + out = append(out, fmt.Sprintf("%s=%s", strings.ToUpper(parts[0]), parts[1])) + } + + if strings.HasPrefix(k, "env_") { + out = append(out, fmt.Sprintf("%s=%s", strings.ToUpper(k[4:]), value)) + } + } + } + + return +} + +func (b *BashNodeBootstrapper) nodeVariablesToEnv() []string { + variablesValues := ReaderNodeVariablesValues(b.resolver) + if len(variablesValues) == 0 { + return nil + } + + i := 0 + env := make([]string, len(variablesValues)) + for k, v := range variablesValues { + env[i] = fmt.Sprintf("%s=%s", variableNameToEnvName(k), v) + i++ + } + + return env +} + +func variableNameToEnvName(variable string) string { + name := strings.ToUpper(variable) + name = strings.ReplaceAll(name, "-", "_") + name = strings.TrimSpace(name) + name = strings.TrimPrefix(name, "{") + name = strings.TrimSuffix(name, "}") + + return "READER_NODE_" + name +} diff --git a/firehose/firehose-core/reader_node_bootstrapper_tarball.go b/firehose/firehose-core/reader_node_bootstrapper_tarball.go new file mode 100644 index 0000000..137fa8c --- /dev/null +++ b/firehose/firehose-core/reader_node_bootstrapper_tarball.go @@ -0,0 +1,98 @@ +package firecore + +import ( + "archive/tar" + "context" + "fmt" + "io" + "os" + "path/filepath" + "time" + + "github.com/streamingfast/dstore" + "go.uber.org/zap" +) + +func NewTarballReaderNodeBootstrapper( + url string, + dataDir string, + logger *zap.Logger, +) *TarballNodeBootstrapper { + return &TarballNodeBootstrapper{ + url: url, + dataDir: dataDir, + logger: logger, + } +} + +type TarballNodeBootstrapper struct { + url string + dataDir string + logger *zap.Logger +} + +func (b *TarballNodeBootstrapper) isBootstrapped() bool { + return isBootstrapped(b.dataDir, b.logger) +} + +func (b *TarballNodeBootstrapper) Bootstrap() error { + if b.isBootstrapped() { + return nil + } + + b.logger.Info("bootstrapping native node chain data from pre-built archive", zap.String("bootstrap_data_url", b.url)) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute) + defer cancel() + + reader, _, _, err := dstore.OpenObject(ctx, b.url, dstore.Compression("zstd")) + if err != nil { + return fmt.Errorf("cannot get snapshot from gstore: %w", err) + } + defer reader.Close() + + b.createChainData(reader) + return nil +} + +func (b *TarballNodeBootstrapper) createChainData(reader io.Reader) error { + err := os.MkdirAll(b.dataDir, os.ModePerm) + if err != nil { + return fmt.Errorf("unable to create blocks log file: %w", err) + } + + b.logger.Info("extracting bootstrapping data into node data directory", zap.String("data_dir", b.dataDir)) + tr := tar.NewReader(reader) + for { + header, err := tr.Next() + if err != nil { + if err == io.EOF { + return nil + } + + return err + } + + path := filepath.Join(b.dataDir, header.Name) + b.logger.Debug("about to write content of entry", zap.String("name", header.Name), zap.String("path", path), zap.Bool("is_dir", header.FileInfo().IsDir())) + if header.FileInfo().IsDir() { + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + return fmt.Errorf("unable to create directory: %w", err) + } + + continue + } + + file, err := os.Create(path) + if err != nil { + return fmt.Errorf("unable to create file: %w", err) + } + + if _, err := io.Copy(file, tr); err != nil { + file.Close() + return err + } + file.Close() + } +} diff --git a/firehose/firehose-core/relayer/app/relayer/app.go b/firehose/firehose-core/relayer/app/relayer/app.go new file mode 100644 index 0000000..00ee91f --- /dev/null +++ b/firehose/firehose-core/relayer/app/relayer/app.go @@ -0,0 +1,118 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package relayer + +import ( + "context" + "fmt" + "time" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/dstore" + "github.com/streamingfast/firehose-core/relayer" + "github.com/streamingfast/firehose-core/relayer/metrics" + "github.com/streamingfast/shutter" + "go.uber.org/zap" + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +var RelayerStartAborted = fmt.Errorf("getting start block aborted by relayer application terminating signal") + +type Config struct { + SourcesAddr []string + GRPCListenAddr string + SourceRequestBurst int + MaxSourceLatency time.Duration + OneBlocksURL string +} + +func (c *Config) ZapFields() []zap.Field { + return []zap.Field{ + zap.Strings("sources_addr", c.SourcesAddr), + zap.String("grpc_listen_addr", c.GRPCListenAddr), + zap.Int("source_request_burst", c.SourceRequestBurst), + zap.Duration("max_source_latency", c.MaxSourceLatency), + zap.String("one_blocks_url", c.OneBlocksURL), + } +} + +type App struct { + *shutter.Shutter + config *Config + + relayer *relayer.Relayer +} + +func New(config *Config) *App { + return &App{ + Shutter: shutter.New(), + config: config, + } +} + +func (a *App) Run() error { + dmetrics.Register(metrics.MetricSet) + + oneBlocksStore, err := dstore.NewDBinStore(a.config.OneBlocksURL) + if err != nil { + return fmt.Errorf("getting block store: %w", err) + } + + liveSourceFactory := bstream.SourceFactory(func(h bstream.Handler) bstream.Source { + return relayer.NewMultiplexedSource( + h, + a.config.SourcesAddr, + a.config.MaxSourceLatency, + a.config.SourceRequestBurst, + ) + }) + oneBlocksSourceFactory := bstream.SourceFromNumFactoryWithSkipFunc(func(num uint64, h bstream.Handler, skipFunc func(idSuffix string) bool) bstream.Source { + src, err := bstream.NewOneBlocksSource(num, oneBlocksStore, h, bstream.OneBlocksSourceWithSkipperFunc(skipFunc)) + if err != nil { + return nil + } + return src + }) + + zlog.Info("starting relayer", a.config.ZapFields()...) + a.relayer = relayer.NewRelayer( + liveSourceFactory, + oneBlocksSourceFactory, + a.config.GRPCListenAddr, + ) + + a.OnTerminating(a.relayer.Shutdown) + a.relayer.OnTerminated(a.Shutdown) + + a.relayer.Run() + return nil +} + +var emptyHealthCheckRequest = &pbhealth.HealthCheckRequest{} + +func (a *App) IsReady() bool { + if a.relayer == nil { + return false + } + + resp, err := a.relayer.Check(context.Background(), emptyHealthCheckRequest) + if err != nil { + zlog.Info("readiness check failed", zap.Error(err)) + return false + } + + return resp.Status == pbhealth.HealthCheckResponse_SERVING +} diff --git a/firehose/firehose-core/relayer/app/relayer/logging.go b/firehose/firehose-core/relayer/app/relayer/logging.go new file mode 100644 index 0000000..b68787c --- /dev/null +++ b/firehose/firehose-core/relayer/app/relayer/logging.go @@ -0,0 +1,21 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package relayer + +import ( + "github.com/streamingfast/logging" +) + +var zlog, _ = logging.PackageLogger("relayer", "github.com/streamingfast/firehose-core/relayer/app/relayer") diff --git a/firehose/firehose-core/relayer/healthz.go b/firehose/firehose-core/relayer/healthz.go new file mode 100644 index 0000000..fd3a406 --- /dev/null +++ b/firehose/firehose-core/relayer/healthz.go @@ -0,0 +1,46 @@ +package relayer + +import ( + "context" + "time" + + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +func (r *Relayer) Check(ctx context.Context, in *pbhealth.HealthCheckRequest) (*pbhealth.HealthCheckResponse, error) { + return &pbhealth.HealthCheckResponse{ + Status: r.healthStatus(), + }, nil +} + +func (r *Relayer) Watch(req *pbhealth.HealthCheckRequest, stream pbhealth.Health_WatchServer) error { + currentStatus := pbhealth.HealthCheckResponse_SERVICE_UNKNOWN + waitTime := 0 * time.Second + + for { + select { + case <-stream.Context().Done(): + return nil + case <-time.After(waitTime): + newStatus := r.healthStatus() + waitTime = 5 * time.Second + + if newStatus != currentStatus { + currentStatus = newStatus + + if err := stream.Send(&pbhealth.HealthCheckResponse{Status: currentStatus}); err != nil { + return err + } + } + } + } +} + +func (r *Relayer) healthStatus() pbhealth.HealthCheckResponse_ServingStatus { + status := pbhealth.HealthCheckResponse_NOT_SERVING + if r.ready { + status = pbhealth.HealthCheckResponse_SERVING + } + + return status +} diff --git a/firehose/firehose-core/relayer/logging.go b/firehose/firehose-core/relayer/logging.go new file mode 100644 index 0000000..e123d96 --- /dev/null +++ b/firehose/firehose-core/relayer/logging.go @@ -0,0 +1,21 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package relayer + +import ( + "github.com/streamingfast/logging" +) + +var zlog, ztrace = logging.PackageLogger("relayer", "github.com/streamingfast/firehose-core/relayer") diff --git a/firehose/firehose-core/relayer/metrics/metrics.go b/firehose/firehose-core/relayer/metrics/metrics.go new file mode 100644 index 0000000..42e662b --- /dev/null +++ b/firehose/firehose-core/relayer/metrics/metrics.go @@ -0,0 +1,25 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +import ( + "github.com/streamingfast/dmetrics" +) + +var MetricSet = dmetrics.NewSet() + +var HeadBlockTimeDrift = MetricSet.NewHeadTimeDrift("relayer") +var HeadBlockNumber = MetricSet.NewHeadBlockNumber("relayer") +var AppReadiness = MetricSet.NewAppReadiness("relayer") diff --git a/firehose/firehose-core/relayer/relayer.go b/firehose/firehose-core/relayer/relayer.go new file mode 100644 index 0000000..d572e06 --- /dev/null +++ b/firehose/firehose-core/relayer/relayer.go @@ -0,0 +1,152 @@ +// Copyright 2019 dfuse Platform Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package relayer + +import ( + "context" + "strings" + "time" + + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/blockstream" + "github.com/streamingfast/bstream/forkable" + "github.com/streamingfast/bstream/hub" + dgrpcfactory "github.com/streamingfast/dgrpc/server/factory" + "github.com/streamingfast/firehose-core/relayer/metrics" + "github.com/streamingfast/shutter" + pbhealth "google.golang.org/grpc/health/grpc_health_v1" +) + +const ( + getHeadInfoTimeout = 10 * time.Second +) + +type Relayer struct { + *shutter.Shutter + + grpcListenAddr string + liveSourceFactory bstream.SourceFactory + oneBlocksSourceFactory bstream.SourceFromNumFactoryWithSkipFunc + + hub *hub.ForkableHub + + ready bool + + blockStreamServer *hub.BlockstreamServer +} + +func NewRelayer( + liveSourceFactory bstream.SourceFactory, + oneBlocksSourceFactory bstream.SourceFromNumFactoryWithSkipFunc, + grpcListenAddr string, +) *Relayer { + r := &Relayer{ + Shutter: shutter.New(), + grpcListenAddr: grpcListenAddr, + liveSourceFactory: liveSourceFactory, + oneBlocksSourceFactory: oneBlocksSourceFactory, + } + + gs := dgrpcfactory.ServerFromOptions() + pbhealth.RegisterHealthServer(gs.ServiceRegistrar(), r) + + forkableHub := hub.NewForkableHub( + r.liveSourceFactory, + r.oneBlocksSourceFactory, + 10, + forkable.EnsureAllBlocksTriggerLongestChain(), // send every forked block too + forkable.WithFilters(bstream.StepNew), + forkable.WithFailOnUnlinkableBlocks(20, time.Minute), + ) + r.hub = forkableHub + gs.OnTerminated(r.Shutdown) + r.blockStreamServer = r.hub.NewBlockstreamServer(gs) + return r + +} + +func NewMultiplexedSource(handler bstream.Handler, sourceAddresses []string, maxSourceLatency time.Duration, sourceRequestBurst int) bstream.Source { + ctx := context.Background() + + var sourceFactories []bstream.SourceFactory + for _, u := range sourceAddresses { + + url := u // https://github.com/golang/go/wiki/CommonMistakes (url is given to the blockstream newSource) + sourceName := urlToLoggerName(url) + logger := zlog.Named("src").Named(sourceName) + sf := func(subHandler bstream.Handler) bstream.Source { + + gate := bstream.NewRealtimeGate(maxSourceLatency, subHandler, bstream.GateOptionWithLogger(logger)) + var upstreamHandler bstream.Handler + upstreamHandler = bstream.HandlerFunc(func(blk *pbbstream.Block, obj interface{}) error { + return gate.ProcessBlock(blk, &namedObj{ + Obj: obj, + Name: sourceName, + }) + }) + + src := blockstream.NewSource(ctx, url, int64(sourceRequestBurst), upstreamHandler, blockstream.WithLogger(logger), blockstream.WithRequester("relayer")) + return src + } + sourceFactories = append(sourceFactories, sf) + } + + return bstream.NewMultiplexedSource(sourceFactories, handler, bstream.MultiplexedSourceWithLogger(zlog)) +} + +func urlToLoggerName(url string) string { + return strings.TrimPrefix(strings.TrimPrefix(url, "dns:///"), ":") +} + +func pollMetrics(fh *hub.ForkableHub) { + for { + time.Sleep(time.Second * 2) + headNum, _, headTime, _, err := fh.HeadInfo() + if err != nil { + zlog.Info("cannot get head info yet") + continue + } + metrics.HeadBlockTimeDrift.SetBlockTime(headTime) + metrics.HeadBlockNumber.SetUint64(headNum) + } +} + +func (r *Relayer) Run() { + go r.hub.Run() + zlog.Info("waiting for hub to be ready...") + <-r.hub.Ready + go pollMetrics(r.hub) + + r.OnTerminating(func(e error) { + zlog.Info("closing block stream server") + r.blockStreamServer.Close() + }) + + r.blockStreamServer.Launch(r.grpcListenAddr) + + zlog.Info("relayer started") + r.ready = true + metrics.AppReadiness.SetReady() + + <-r.hub.Terminating() + r.Shutdown(r.hub.Err()) +} + +type namedObj struct { + Name string + Obj interface{} +} diff --git a/firehose/firehose-core/storage.go b/firehose/firehose-core/storage.go new file mode 100644 index 0000000..0d6808b --- /dev/null +++ b/firehose/firehose-core/storage.go @@ -0,0 +1,111 @@ +package firecore + +import ( + "context" + "fmt" + + "github.com/spf13/viper" + "github.com/streamingfast/dstore" + "go.uber.org/zap" +) + +var commonStoresCreated bool +var indexStoreCreated bool + +func GetCommonStoresURLs(dataDir string) (mergedBlocksStoreURL, oneBlocksStoreURL, forkedBlocksStoreURL string, err error) { + mergedBlocksStoreURL = MustReplaceDataDir(dataDir, viper.GetString("common-merged-blocks-store-url")) + oneBlocksStoreURL = MustReplaceDataDir(dataDir, viper.GetString("common-one-block-store-url")) + forkedBlocksStoreURL = MustReplaceDataDir(dataDir, viper.GetString("common-forked-blocks-store-url")) + + if commonStoresCreated { + return + } + + if err = mkdirStorePathIfLocal(forkedBlocksStoreURL); err != nil { + return + } + + if err = mkdirStorePathIfLocal(oneBlocksStoreURL); err != nil { + return + } + + if err = mkdirStorePathIfLocal(mergedBlocksStoreURL); err != nil { + return + } + + commonStoresCreated = true + return +} + +func GetIndexStore(dataDir string) (indexStore dstore.Store, possibleIndexSizes []uint64, err error) { + indexStoreURL := MustReplaceDataDir(dataDir, viper.GetString("common-index-store-url")) + + if indexStoreURL != "" { + s, err := dstore.NewStore(indexStoreURL, "", "", false) + if err != nil { + return nil, nil, fmt.Errorf("couldn't create index store: %w", err) + } + if !indexStoreCreated { + if err = mkdirStorePathIfLocal(indexStoreURL); err != nil { + return nil, nil, err + } + } + indexStoreCreated = true + indexStore = s + } + + for _, size := range viper.GetIntSlice("common-index-block-sizes") { + if size < 0 { + return nil, nil, fmt.Errorf("invalid negative size for common-index-block-sizes: %d", size) + } + possibleIndexSizes = append(possibleIndexSizes, uint64(size)) + } + + return +} + +func LastMergedBlockNum(ctx context.Context, startBlockNum uint64, store dstore.Store, logger *zap.Logger) uint64 { + value, err := searchBlockNum(startBlockNum, func(u uint64) (bool, error) { + filepath := fmt.Sprintf("%010d", u) + found, err := store.FileExists(ctx, filepath) + if err != nil { + return false, fmt.Errorf("failed to file exists %s: %w", filepath, err) + } + return found, nil + }) + if err != nil { + logger.Warn("failed to resolve block", zap.Error(err)) + return startBlockNum + } + return value + +} + +func searchBlockNum(startBlockNum uint64, f func(uint64) (bool, error)) (uint64, error) { + blockNum, err := blockNumIter(startBlockNum, 10_000_000_000, 1_000_000_000, f) + if err != nil { + return 0, err + } + if blockNum < startBlockNum { + return startBlockNum, nil + } + return blockNum, nil +} + +func blockNumIter(startBlockNum, exclusiveEndBlockNum, interval uint64, f func(uint64) (bool, error)) (uint64, error) { + i := exclusiveEndBlockNum + for i >= startBlockNum { + i -= interval + match, err := f(i) + if err != nil { + return 0, fmt.Errorf("failed to match blcok num %d: %w", i, err) + } + if match { + if interval == 100 { + return i, nil + } + return blockNumIter(i, i+interval, interval/10, f) + } + } + return startBlockNum, nil +} diff --git a/firehose/firehose-core/storage_test.go b/firehose/firehose-core/storage_test.go new file mode 100644 index 0000000..246e070 --- /dev/null +++ b/firehose/firehose-core/storage_test.go @@ -0,0 +1,49 @@ +package firecore + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_searchBlockNum(t *testing.T) { + tests := []struct { + name string + startBlockNum uint64 + lastBlockNum *uint64 + expect int + expectErr bool + }{ + {"golden path", 1_690_600, uptr(208_853_300), 208_853_300, false}, + {"no block file found", 1_690_600, nil, 1_690_600, false}, + {"block file greater then start block", 0, uptr(100), 100, false}, + {"block file less then start block", 200, uptr(100), 200, false}, + {"golden path 2", 0, uptr(17821900), 17821900, false}, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + dstoreOpt := 0 + v, err := searchBlockNum(tt.startBlockNum, func(i uint64) (bool, error) { + dstoreOpt++ + if tt.lastBlockNum == nil { + return false, nil + } + if i > *tt.lastBlockNum { + return false, nil + } + return true, nil + }) + if tt.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expect, int(v)) + } + }) + } +} + +func uptr(v uint64) *uint64 { + return &v +} diff --git a/firehose/firehose-core/stream_factory.go b/firehose/firehose-core/stream_factory.go new file mode 100644 index 0000000..2df62e8 --- /dev/null +++ b/firehose/firehose-core/stream_factory.go @@ -0,0 +1,134 @@ +package firecore + +import ( + "context" + "fmt" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/hub" + "github.com/streamingfast/bstream/stream" + "github.com/streamingfast/bstream/transform" + "github.com/streamingfast/dauth" + "github.com/streamingfast/dmetering" + "github.com/streamingfast/dstore" + pbfirehose "github.com/streamingfast/pbgo/sf/firehose/v2" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +var StreamMergedBlocksPreprocThreads = 25 + +type StreamFactory struct { + mergedBlocksStore dstore.Store + forkedBlocksStore dstore.Store + hub *hub.ForkableHub + transformRegistry *transform.Registry +} + +func NewStreamFactory( + mergedBlocksStore dstore.Store, + forkedBlocksStore dstore.Store, + hub *hub.ForkableHub, + transformRegistry *transform.Registry, +) *StreamFactory { + return &StreamFactory{ + mergedBlocksStore: mergedBlocksStore, + forkedBlocksStore: forkedBlocksStore, + hub: hub, + transformRegistry: transformRegistry, + } +} + +func (sf *StreamFactory) New( + ctx context.Context, + handler bstream.Handler, + request *pbfirehose.Request, + logger *zap.Logger) (*stream.Stream, error) { + + reqLogger := logger.With( + zap.Int64("req_start_block", request.StartBlockNum), + zap.String("req_cursor", request.Cursor), + zap.Uint64("req_stop_block", request.StopBlockNum), + zap.Bool("final_blocks_only", request.FinalBlocksOnly), + ) + + options := []stream.Option{ + stream.WithStopBlock(request.StopBlockNum), + } + + preprocFunc, blockIndexProvider, desc, err := sf.transformRegistry.BuildFromTransforms(request.Transforms) + if err != nil { + reqLogger.Error("cannot process incoming blocks request transforms", zap.Error(err)) + return nil, fmt.Errorf("building from transforms: %w", err) + } + if preprocFunc != nil { + options = append(options, stream.WithPreprocessFunc(preprocFunc, StreamMergedBlocksPreprocThreads)) + } + if blockIndexProvider != nil { + reqLogger = reqLogger.With(zap.Bool("with_index_provider", true)) + } + if desc != "" { + reqLogger = reqLogger.With(zap.String("transform_desc", desc)) + } + options = append(options, stream.WithLogger(logger)) // stream won't have the full reqLogger, use the traceID to connect them together + + if blockIndexProvider != nil { + options = append(options, stream.WithBlockIndexProvider(blockIndexProvider)) + } + + if request.FinalBlocksOnly { + options = append(options, stream.WithFinalBlocksOnly()) + } + + var fields []zap.Field + auth := dauth.FromContext(ctx) + if auth != nil { + fields = append(fields, + zap.String("api_key_id", auth.APIKeyID()), + zap.String("user_id", auth.UserID()), + zap.String("real_ip", auth.RealIP()), + ) + } + + reqLogger.Info("processing incoming blocks request", fields...) + + if request.Cursor != "" { + cur, err := bstream.CursorFromOpaque(request.Cursor) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "invalid start cursor %q: %s", request.Cursor, err) + } + + options = append(options, stream.WithCursor(cur)) + } + + forkedBlocksStore := sf.forkedBlocksStore + if clonable, ok := forkedBlocksStore.(dstore.Clonable); ok { + var err error + forkedBlocksStore, err = clonable.Clone(ctx) + if err != nil { + return nil, err + } + forkedBlocksStore.SetMeter(dmetering.GetBytesMeter(ctx)) + } + + mergedBlocksStore := sf.mergedBlocksStore + if clonable, ok := mergedBlocksStore.(dstore.Clonable); ok { + var err error + mergedBlocksStore, err = clonable.Clone(ctx) + if err != nil { + return nil, err + } + mergedBlocksStore.SetMeter(dmetering.GetBytesMeter(ctx)) + } + + str := stream.New( + forkedBlocksStore, + mergedBlocksStore, + sf.hub, + request.StartBlockNum, + handler, + options...) + + return str, nil +} diff --git a/firehose/firehose-core/superviser/genericsupervisor.go b/firehose/firehose-core/superviser/genericsupervisor.go new file mode 100644 index 0000000..fa4e416 --- /dev/null +++ b/firehose/firehose-core/superviser/genericsupervisor.go @@ -0,0 +1,51 @@ +package superviser + +import ( + "strings" + + "github.com/ShinyTrinkets/overseer" + nodeManager "github.com/streamingfast/firehose-core/node-manager" + "github.com/streamingfast/firehose-core/node-manager/superviser" + "go.uber.org/zap" +) + +var ( + SupervisorFactory = newGenericSupervisor +) + +type GenericSuperviser struct { + *superviser.Superviser + + binary string + arguments []string + name string +} + +const LIMIT_BYTE = 100 * 1024 * 1024 + +// This is the default implementation of the Chain Supervisor. If you wish to override the implementation for +// your given chain you can override the 'SupervisorFactory' variable +func newGenericSupervisor(name, binary string, arguments []string, appLogger *zap.Logger) nodeManager.ChainSuperviser { + if overseer.DEFAULT_LINE_BUFFER_SIZE < LIMIT_BYTE { + overseer.DEFAULT_LINE_BUFFER_SIZE = LIMIT_BYTE + } + + return &GenericSuperviser{ + Superviser: superviser.New(appLogger, binary, arguments), + name: name, + binary: binary, + arguments: arguments, + } +} + +func (g *GenericSuperviser) GetCommand() string { + return g.binary + " " + strings.Join(g.arguments, " ") +} + +func (g *GenericSuperviser) GetName() string { + return g.name +} + +func (g *GenericSuperviser) ServerID() (string, error) { + return "", nil +} diff --git a/firehose/firehose-core/superviser/logging.go b/firehose/firehose-core/superviser/logging.go new file mode 100644 index 0000000..5c79bfe --- /dev/null +++ b/firehose/firehose-core/superviser/logging.go @@ -0,0 +1,15 @@ +package superviser + +import ( + logplugin "github.com/streamingfast/firehose-core/node-manager/log_plugin" +) + +// This file configures a logging reader that transforms log lines received from the blockchain process running +// and then logs them inside the Firehose stack logging system. +// +// A default implementation uses a regex to identify the level of the line and turn it into our internal level value. +// +// You should override the `GetLogLevelFunc` above to determine the log level for your speficic chain +func NewNodeLogPlugin(debugFirehose bool) logplugin.LogPlugin { + return logplugin.NewToConsoleLogPlugin(debugFirehose) +} diff --git a/firehose/firehose-core/test/type_test.pb.go b/firehose/firehose-core/test/type_test.pb.go new file mode 100644 index 0000000..e78f046 --- /dev/null +++ b/firehose/firehose-core/test/type_test.pb.go @@ -0,0 +1,3508 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.28.1 +// protoc v4.24.2 +// source: sf/ethereum/type/v2/type.proto + +package test + +import ( + reflect "reflect" + sync "sync" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type TransactionTraceStatus int32 + +const ( + TransactionTraceStatus_UNKNOWN TransactionTraceStatus = 0 + TransactionTraceStatus_SUCCEEDED TransactionTraceStatus = 1 + TransactionTraceStatus_FAILED TransactionTraceStatus = 2 + TransactionTraceStatus_REVERTED TransactionTraceStatus = 3 +) + +// Enum value maps for TransactionTraceStatus. +var ( + TransactionTraceStatus_name = map[int32]string{ + 0: "UNKNOWN", + 1: "SUCCEEDED", + 2: "FAILED", + 3: "REVERTED", + } + TransactionTraceStatus_value = map[string]int32{ + "UNKNOWN": 0, + "SUCCEEDED": 1, + "FAILED": 2, + "REVERTED": 3, + } +) + +func (x TransactionTraceStatus) Enum() *TransactionTraceStatus { + p := new(TransactionTraceStatus) + *p = x + return p +} + +func (x TransactionTraceStatus) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TransactionTraceStatus) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[0].Descriptor() +} + +func (TransactionTraceStatus) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[0] +} + +func (x TransactionTraceStatus) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TransactionTraceStatus.Descriptor instead. +func (TransactionTraceStatus) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{0} +} + +type CallType int32 + +const ( + CallType_UNSPECIFIED CallType = 0 + CallType_CALL CallType = 1 // direct? what's the name for `Call` alone? + CallType_CALLCODE CallType = 2 + CallType_DELEGATE CallType = 3 + CallType_STATIC CallType = 4 + CallType_CREATE CallType = 5 // create2 ? any other form of calls? +) + +// Enum value maps for CallType. +var ( + CallType_name = map[int32]string{ + 0: "UNSPECIFIED", + 1: "CALL", + 2: "CALLCODE", + 3: "DELEGATE", + 4: "STATIC", + 5: "CREATE", + } + CallType_value = map[string]int32{ + "UNSPECIFIED": 0, + "CALL": 1, + "CALLCODE": 2, + "DELEGATE": 3, + "STATIC": 4, + "CREATE": 5, + } +) + +func (x CallType) Enum() *CallType { + p := new(CallType) + *p = x + return p +} + +func (x CallType) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (CallType) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[1].Descriptor() +} + +func (CallType) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[1] +} + +func (x CallType) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use CallType.Descriptor instead. +func (CallType) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{1} +} + +type Block_DetailLevel int32 + +const ( + Block_DETAILLEVEL_EXTENDED Block_DetailLevel = 0 + // DETAILLEVEL_TRACE = 1; // TBD + Block_DETAILLEVEL_BASE Block_DetailLevel = 2 +) + +// Enum value maps for Block_DetailLevel. +var ( + Block_DetailLevel_name = map[int32]string{ + 0: "DETAILLEVEL_EXTENDED", + 2: "DETAILLEVEL_BASE", + } + Block_DetailLevel_value = map[string]int32{ + "DETAILLEVEL_EXTENDED": 0, + "DETAILLEVEL_BASE": 2, + } +) + +func (x Block_DetailLevel) Enum() *Block_DetailLevel { + p := new(Block_DetailLevel) + *p = x + return p +} + +func (x Block_DetailLevel) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (Block_DetailLevel) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[2].Descriptor() +} + +func (Block_DetailLevel) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[2] +} + +func (x Block_DetailLevel) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use Block_DetailLevel.Descriptor instead. +func (Block_DetailLevel) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{0, 0} +} + +type TransactionTrace_Type int32 + +const ( + // All transactions that ever existed prior Berlin fork before EIP-2718 was implemented. + TransactionTrace_TRX_TYPE_LEGACY TransactionTrace_Type = 0 + // Transaction that specicy an access list of contract/storage_keys that is going to be used + // in this transaction. + // + // Added in Berlin fork (EIP-2930). + TransactionTrace_TRX_TYPE_ACCESS_LIST TransactionTrace_Type = 1 + // Transaction that specifis an access list just like TRX_TYPE_ACCESS_LIST but in addition defines the + // max base gas gee and max priority gas fee to pay for this transaction. Transaction's of those type are + // executed against EIP-1559 rules which dictates a dynamic gas cost based on the congestion of the network. + TransactionTrace_TRX_TYPE_DYNAMIC_FEE TransactionTrace_Type = 2 + // Arbitrum-specific transactions + TransactionTrace_TRX_TYPE_ARBITRUM_DEPOSIT TransactionTrace_Type = 100 + TransactionTrace_TRX_TYPE_ARBITRUM_UNSIGNED TransactionTrace_Type = 101 + TransactionTrace_TRX_TYPE_ARBITRUM_CONTRACT TransactionTrace_Type = 102 + TransactionTrace_TRX_TYPE_ARBITRUM_RETRY TransactionTrace_Type = 104 + TransactionTrace_TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE TransactionTrace_Type = 105 + TransactionTrace_TRX_TYPE_ARBITRUM_INTERNAL TransactionTrace_Type = 106 + TransactionTrace_TRX_TYPE_ARBITRUM_LEGACY TransactionTrace_Type = 120 +) + +// Enum value maps for TransactionTrace_Type. +var ( + TransactionTrace_Type_name = map[int32]string{ + 0: "TRX_TYPE_LEGACY", + 1: "TRX_TYPE_ACCESS_LIST", + 2: "TRX_TYPE_DYNAMIC_FEE", + 100: "TRX_TYPE_ARBITRUM_DEPOSIT", + 101: "TRX_TYPE_ARBITRUM_UNSIGNED", + 102: "TRX_TYPE_ARBITRUM_CONTRACT", + 104: "TRX_TYPE_ARBITRUM_RETRY", + 105: "TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE", + 106: "TRX_TYPE_ARBITRUM_INTERNAL", + 120: "TRX_TYPE_ARBITRUM_LEGACY", + } + TransactionTrace_Type_value = map[string]int32{ + "TRX_TYPE_LEGACY": 0, + "TRX_TYPE_ACCESS_LIST": 1, + "TRX_TYPE_DYNAMIC_FEE": 2, + "TRX_TYPE_ARBITRUM_DEPOSIT": 100, + "TRX_TYPE_ARBITRUM_UNSIGNED": 101, + "TRX_TYPE_ARBITRUM_CONTRACT": 102, + "TRX_TYPE_ARBITRUM_RETRY": 104, + "TRX_TYPE_ARBITRUM_SUBMIT_RETRYABLE": 105, + "TRX_TYPE_ARBITRUM_INTERNAL": 106, + "TRX_TYPE_ARBITRUM_LEGACY": 120, + } +) + +func (x TransactionTrace_Type) Enum() *TransactionTrace_Type { + p := new(TransactionTrace_Type) + *p = x + return p +} + +func (x TransactionTrace_Type) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (TransactionTrace_Type) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[3].Descriptor() +} + +func (TransactionTrace_Type) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[3] +} + +func (x TransactionTrace_Type) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use TransactionTrace_Type.Descriptor instead. +func (TransactionTrace_Type) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{5, 0} +} + +// Obtain all balanche change reasons under deep mind repository: +// +// ```shell +// ack -ho 'BalanceChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq +// ``` +type BalanceChange_Reason int32 + +const ( + BalanceChange_REASON_UNKNOWN BalanceChange_Reason = 0 + BalanceChange_REASON_REWARD_MINE_UNCLE BalanceChange_Reason = 1 + BalanceChange_REASON_REWARD_MINE_BLOCK BalanceChange_Reason = 2 + BalanceChange_REASON_DAO_REFUND_CONTRACT BalanceChange_Reason = 3 + BalanceChange_REASON_DAO_ADJUST_BALANCE BalanceChange_Reason = 4 + BalanceChange_REASON_TRANSFER BalanceChange_Reason = 5 + BalanceChange_REASON_GENESIS_BALANCE BalanceChange_Reason = 6 + BalanceChange_REASON_GAS_BUY BalanceChange_Reason = 7 + BalanceChange_REASON_REWARD_TRANSACTION_FEE BalanceChange_Reason = 8 + BalanceChange_REASON_REWARD_FEE_RESET BalanceChange_Reason = 14 + BalanceChange_REASON_GAS_REFUND BalanceChange_Reason = 9 + BalanceChange_REASON_TOUCH_ACCOUNT BalanceChange_Reason = 10 + BalanceChange_REASON_SUICIDE_REFUND BalanceChange_Reason = 11 + BalanceChange_REASON_SUICIDE_WITHDRAW BalanceChange_Reason = 13 + BalanceChange_REASON_CALL_BALANCE_OVERRIDE BalanceChange_Reason = 12 + // Used on chain(s) where some Ether burning happens + BalanceChange_REASON_BURN BalanceChange_Reason = 15 + BalanceChange_REASON_WITHDRAWAL BalanceChange_Reason = 16 +) + +// Enum value maps for BalanceChange_Reason. +var ( + BalanceChange_Reason_name = map[int32]string{ + 0: "REASON_UNKNOWN", + 1: "REASON_REWARD_MINE_UNCLE", + 2: "REASON_REWARD_MINE_BLOCK", + 3: "REASON_DAO_REFUND_CONTRACT", + 4: "REASON_DAO_ADJUST_BALANCE", + 5: "REASON_TRANSFER", + 6: "REASON_GENESIS_BALANCE", + 7: "REASON_GAS_BUY", + 8: "REASON_REWARD_TRANSACTION_FEE", + 14: "REASON_REWARD_FEE_RESET", + 9: "REASON_GAS_REFUND", + 10: "REASON_TOUCH_ACCOUNT", + 11: "REASON_SUICIDE_REFUND", + 13: "REASON_SUICIDE_WITHDRAW", + 12: "REASON_CALL_BALANCE_OVERRIDE", + 15: "REASON_BURN", + 16: "REASON_WITHDRAWAL", + } + BalanceChange_Reason_value = map[string]int32{ + "REASON_UNKNOWN": 0, + "REASON_REWARD_MINE_UNCLE": 1, + "REASON_REWARD_MINE_BLOCK": 2, + "REASON_DAO_REFUND_CONTRACT": 3, + "REASON_DAO_ADJUST_BALANCE": 4, + "REASON_TRANSFER": 5, + "REASON_GENESIS_BALANCE": 6, + "REASON_GAS_BUY": 7, + "REASON_REWARD_TRANSACTION_FEE": 8, + "REASON_REWARD_FEE_RESET": 14, + "REASON_GAS_REFUND": 9, + "REASON_TOUCH_ACCOUNT": 10, + "REASON_SUICIDE_REFUND": 11, + "REASON_SUICIDE_WITHDRAW": 13, + "REASON_CALL_BALANCE_OVERRIDE": 12, + "REASON_BURN": 15, + "REASON_WITHDRAWAL": 16, + } +) + +func (x BalanceChange_Reason) Enum() *BalanceChange_Reason { + p := new(BalanceChange_Reason) + *p = x + return p +} + +func (x BalanceChange_Reason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (BalanceChange_Reason) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[4].Descriptor() +} + +func (BalanceChange_Reason) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[4] +} + +func (x BalanceChange_Reason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use BalanceChange_Reason.Descriptor instead. +func (BalanceChange_Reason) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{11, 0} +} + +// Obtain all gas change reasons under deep mind repository: +// +// ```shell +// ack -ho 'GasChangeReason\(".*"\)' | grep -Eo '".*"' | sort | uniq +// ``` +type GasChange_Reason int32 + +const ( + GasChange_REASON_UNKNOWN GasChange_Reason = 0 + // REASON_CALL is the amount of gas that will be charged for a 'CALL' opcode executed by the EVM + GasChange_REASON_CALL GasChange_Reason = 1 + // REASON_CALL_CODE is the amount of gas that will be charged for a 'CALLCODE' opcode executed by the EVM + GasChange_REASON_CALL_CODE GasChange_Reason = 2 + // REASON_CALL_DATA_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM + GasChange_REASON_CALL_DATA_COPY GasChange_Reason = 3 + // REASON_CODE_COPY is the amount of gas that will be charged for a 'CALLDATACOPY' opcode executed by the EVM + GasChange_REASON_CODE_COPY GasChange_Reason = 4 + // REASON_CODE_STORAGE is the amount of gas that will be charged for code storage + GasChange_REASON_CODE_STORAGE GasChange_Reason = 5 + // REASON_CONTRACT_CREATION is the amount of gas that will be charged for a 'CREATE' opcode executed by the EVM and for the gas + // burned for a CREATE, today controlled by EIP150 rules + GasChange_REASON_CONTRACT_CREATION GasChange_Reason = 6 + // REASON_CONTRACT_CREATION2 is the amount of gas that will be charged for a 'CREATE2' opcode executed by the EVM and for the gas + // burned for a CREATE2, today controlled by EIP150 rules + GasChange_REASON_CONTRACT_CREATION2 GasChange_Reason = 7 + // REASON_DELEGATE_CALL is the amount of gas that will be charged for a 'DELEGATECALL' opcode executed by the EVM + GasChange_REASON_DELEGATE_CALL GasChange_Reason = 8 + // REASON_EVENT_LOG is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM + GasChange_REASON_EVENT_LOG GasChange_Reason = 9 + // REASON_EXT_CODE_COPY is the amount of gas that will be charged for a 'LOG' opcode executed by the EVM + GasChange_REASON_EXT_CODE_COPY GasChange_Reason = 10 + // REASON_FAILED_EXECUTION is the burning of the remaining gas when the execution failed without a revert + GasChange_REASON_FAILED_EXECUTION GasChange_Reason = 11 + // REASON_INTRINSIC_GAS is the amount of gas that will be charged for the intrinsic cost of the transaction, there is + // always exactly one of those per transaction + GasChange_REASON_INTRINSIC_GAS GasChange_Reason = 12 + // GasChangePrecompiledContract is the amount of gas that will be charged for a precompiled contract execution + GasChange_REASON_PRECOMPILED_CONTRACT GasChange_Reason = 13 + // REASON_REFUND_AFTER_EXECUTION is the amount of gas that will be refunded to the caller after the execution of the call, + // if there is left over at the end of execution + GasChange_REASON_REFUND_AFTER_EXECUTION GasChange_Reason = 14 + // REASON_RETURN is the amount of gas that will be charged for a 'RETURN' opcode executed by the EVM + GasChange_REASON_RETURN GasChange_Reason = 15 + // REASON_RETURN_DATA_COPY is the amount of gas that will be charged for a 'RETURNDATACOPY' opcode executed by the EVM + GasChange_REASON_RETURN_DATA_COPY GasChange_Reason = 16 + // REASON_REVERT is the amount of gas that will be charged for a 'REVERT' opcode executed by the EVM + GasChange_REASON_REVERT GasChange_Reason = 17 + // REASON_SELF_DESTRUCT is the amount of gas that will be charged for a 'SELFDESTRUCT' opcode executed by the EVM + GasChange_REASON_SELF_DESTRUCT GasChange_Reason = 18 + // REASON_STATIC_CALL is the amount of gas that will be charged for a 'STATICALL' opcode executed by the EVM + GasChange_REASON_STATIC_CALL GasChange_Reason = 19 + // REASON_STATE_COLD_ACCESS is the amount of gas that will be charged for a cold storage access as controlled by EIP2929 rules + // + // Added in Berlin fork (Geth 1.10+) + GasChange_REASON_STATE_COLD_ACCESS GasChange_Reason = 20 + // REASON_TX_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_TX_INITIAL_BALANCE GasChange_Reason = 21 + // REASON_TX_REFUNDS is the sum of all refunds which happened during the tx execution (e.g. storage slot being cleared) + // this generates an increase in gas. There is only one such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_TX_REFUNDS GasChange_Reason = 22 + // REASON_TX_LEFT_OVER_RETURNED is the amount of gas left over at the end of transaction's execution that will be returned + // to the chain. This change will always be a negative change as we "drain" left over gas towards 0. If there was no gas + // left at the end of execution, no such even will be emitted. The returned gas's value in Wei is returned to caller. + // There is at most one of such gas change per transaction. + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_TX_LEFT_OVER_RETURNED GasChange_Reason = 23 + // REASON_CALL_INITIAL_BALANCE is the initial balance for the call which will be equal to the gasLimit of the call. There is only + // one such gas change per call. + // + // Added as new tracing reason in Geth, available only on some chains + GasChange_REASON_CALL_INITIAL_BALANCE GasChange_Reason = 24 + // REASON_CALL_LEFT_OVER_RETURNED is the amount of gas left over that will be returned to the caller, this change will always + // be a negative change as we "drain" left over gas towards 0. If there was no gas left at the end of execution, no such even + // will be emitted. + GasChange_REASON_CALL_LEFT_OVER_RETURNED GasChange_Reason = 25 +) + +// Enum value maps for GasChange_Reason. +var ( + GasChange_Reason_name = map[int32]string{ + 0: "REASON_UNKNOWN", + 1: "REASON_CALL", + 2: "REASON_CALL_CODE", + 3: "REASON_CALL_DATA_COPY", + 4: "REASON_CODE_COPY", + 5: "REASON_CODE_STORAGE", + 6: "REASON_CONTRACT_CREATION", + 7: "REASON_CONTRACT_CREATION2", + 8: "REASON_DELEGATE_CALL", + 9: "REASON_EVENT_LOG", + 10: "REASON_EXT_CODE_COPY", + 11: "REASON_FAILED_EXECUTION", + 12: "REASON_INTRINSIC_GAS", + 13: "REASON_PRECOMPILED_CONTRACT", + 14: "REASON_REFUND_AFTER_EXECUTION", + 15: "REASON_RETURN", + 16: "REASON_RETURN_DATA_COPY", + 17: "REASON_REVERT", + 18: "REASON_SELF_DESTRUCT", + 19: "REASON_STATIC_CALL", + 20: "REASON_STATE_COLD_ACCESS", + 21: "REASON_TX_INITIAL_BALANCE", + 22: "REASON_TX_REFUNDS", + 23: "REASON_TX_LEFT_OVER_RETURNED", + 24: "REASON_CALL_INITIAL_BALANCE", + 25: "REASON_CALL_LEFT_OVER_RETURNED", + } + GasChange_Reason_value = map[string]int32{ + "REASON_UNKNOWN": 0, + "REASON_CALL": 1, + "REASON_CALL_CODE": 2, + "REASON_CALL_DATA_COPY": 3, + "REASON_CODE_COPY": 4, + "REASON_CODE_STORAGE": 5, + "REASON_CONTRACT_CREATION": 6, + "REASON_CONTRACT_CREATION2": 7, + "REASON_DELEGATE_CALL": 8, + "REASON_EVENT_LOG": 9, + "REASON_EXT_CODE_COPY": 10, + "REASON_FAILED_EXECUTION": 11, + "REASON_INTRINSIC_GAS": 12, + "REASON_PRECOMPILED_CONTRACT": 13, + "REASON_REFUND_AFTER_EXECUTION": 14, + "REASON_RETURN": 15, + "REASON_RETURN_DATA_COPY": 16, + "REASON_REVERT": 17, + "REASON_SELF_DESTRUCT": 18, + "REASON_STATIC_CALL": 19, + "REASON_STATE_COLD_ACCESS": 20, + "REASON_TX_INITIAL_BALANCE": 21, + "REASON_TX_REFUNDS": 22, + "REASON_TX_LEFT_OVER_RETURNED": 23, + "REASON_CALL_INITIAL_BALANCE": 24, + "REASON_CALL_LEFT_OVER_RETURNED": 25, + } +) + +func (x GasChange_Reason) Enum() *GasChange_Reason { + p := new(GasChange_Reason) + *p = x + return p +} + +func (x GasChange_Reason) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (GasChange_Reason) Descriptor() protoreflect.EnumDescriptor { + return file_sf_ethereum_type_v2_type_proto_enumTypes[5].Descriptor() +} + +func (GasChange_Reason) Type() protoreflect.EnumType { + return &file_sf_ethereum_type_v2_type_proto_enumTypes[5] +} + +func (x GasChange_Reason) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use GasChange_Reason.Descriptor instead. +func (GasChange_Reason) EnumDescriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{15, 0} +} + +type Block struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // Hash is the block's hash. + Hash []byte `protobuf:"bytes,2,opt,name=hash,proto3" json:"hash,omitempty"` + // Number is the block's height at which this block was mined. + Number uint64 `protobuf:"varint,3,opt,name=number,proto3" json:"number,omitempty"` + // Size is the size in bytes of the RLP encoding of the block according to Ethereum + // rules. + Size uint64 `protobuf:"varint,4,opt,name=size,proto3" json:"size,omitempty"` + // Header contain's the block's header information like its parent hash, the merkel root hash + // and all other information the form a block. + Header *BlockHeader `protobuf:"bytes,5,opt,name=header,proto3" json:"header,omitempty"` + // Uncles represents block produced with a valid solution but were not actually choosen + // as the canonical block for the given height so they are mostly "forked" blocks. + // + // If the Block has been produced using the Proof of Stake consensus algorithm, this + // field will actually be always empty. + Uncles []*BlockHeader `protobuf:"bytes,6,rep,name=uncles,proto3" json:"uncles,omitempty"` + // TransactionTraces hold the execute trace of all the transactions that were executed + // in this block. In in there that you will find most of the Ethereum data model. + TransactionTraces []*TransactionTrace `protobuf:"bytes,10,rep,name=transaction_traces,json=transactionTraces,proto3" json:"transaction_traces,omitempty"` + // BalanceChanges here is the array of ETH transfer that happened at the block level + // outside of the normal transaction flow of a block. The best example of this is mining + // reward for the block mined, the transfer of ETH to the miner happens outside the normal + // transaction flow of the chain and is recorded as a `BalanceChange` here since we cannot + // attached it to any transaction. + // + // Only available in DetailLevel: EXTENDED + BalanceChanges []*BalanceChange `protobuf:"bytes,11,rep,name=balance_changes,json=balanceChanges,proto3" json:"balance_changes,omitempty"` + // DetailLevel affects the data available in this block. + // + // EXTENDED describes the most complete block, with traces, balance changes, storage changes. It is extracted during the execution of the block. + // BASE describes a block that contains only the block header, transaction receipts and event logs: everything that can be extracted using the base JSON-RPC interface (https://ethereum.org/en/developers/docs/apis/json-rpc/#json-rpc-methods) + // + // Furthermore, the eth_getTransactionReceipt call has been avoided because it brings only minimal improvements at the cost of requiring an archive node or a full node with complete transaction index. + DetailLevel Block_DetailLevel `protobuf:"varint,12,opt,name=detail_level,json=detailLevel,proto3,enum=sf.ethereum.type.v2.Block_DetailLevel" json:"detail_level,omitempty"` + // CodeChanges here is the array of smart code change that happened that happened at the block level + // outside of the normal transaction flow of a block. Some Ethereum's fork like BSC and Polygon + // has some capabilities to upgrade internal smart contracts used usually to track the validator + // list. + // + // On hard fork, some procedure runs to upgrade the smart contract code to a new version. In those + // network, a `CodeChange` for each modified smart contract on upgrade would be present here. Note + // that this happen rarely, so the vast majority of block will have an empty list here. + // Only available in DetailLevel: EXTENDED + CodeChanges []*CodeChange `protobuf:"bytes,20,rep,name=code_changes,json=codeChanges,proto3" json:"code_changes,omitempty"` + // Ver represents that data model version of the block, it is used internally by Firehose on Ethereum + // as a validation that we are reading the correct version. + Ver int32 `protobuf:"varint,1,opt,name=ver,proto3" json:"ver,omitempty"` +} + +func (x *Block) Reset() { + *x = Block{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Block) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Block) ProtoMessage() {} + +func (x *Block) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Block.ProtoReflect.Descriptor instead. +func (*Block) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{0} +} + +func (x *Block) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *Block) GetNumber() uint64 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *Block) GetSize() uint64 { + if x != nil { + return x.Size + } + return 0 +} + +func (x *Block) GetHeader() *BlockHeader { + if x != nil { + return x.Header + } + return nil +} + +func (x *Block) GetUncles() []*BlockHeader { + if x != nil { + return x.Uncles + } + return nil +} + +func (x *Block) GetTransactionTraces() []*TransactionTrace { + if x != nil { + return x.TransactionTraces + } + return nil +} + +func (x *Block) GetBalanceChanges() []*BalanceChange { + if x != nil { + return x.BalanceChanges + } + return nil +} + +func (x *Block) GetDetailLevel() Block_DetailLevel { + if x != nil { + return x.DetailLevel + } + return Block_DETAILLEVEL_EXTENDED +} + +func (x *Block) GetCodeChanges() []*CodeChange { + if x != nil { + return x.CodeChanges + } + return nil +} + +func (x *Block) GetVer() int32 { + if x != nil { + return x.Ver + } + return 0 +} + +type BlockHeader struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ParentHash []byte `protobuf:"bytes,1,opt,name=parent_hash,json=parentHash,proto3" json:"parent_hash,omitempty"` + // Uncle hash of the block, some reference it as `sha3Uncles`, but `sha3“ is badly worded, so we prefer `uncle_hash`, also + // referred as `ommers` in EIP specification. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347`. + UncleHash []byte `protobuf:"bytes,2,opt,name=uncle_hash,json=uncleHash,proto3" json:"uncle_hash,omitempty"` + Coinbase []byte `protobuf:"bytes,3,opt,name=coinbase,proto3" json:"coinbase,omitempty"` + StateRoot []byte `protobuf:"bytes,4,opt,name=state_root,json=stateRoot,proto3" json:"state_root,omitempty"` + TransactionsRoot []byte `protobuf:"bytes,5,opt,name=transactions_root,json=transactionsRoot,proto3" json:"transactions_root,omitempty"` + ReceiptRoot []byte `protobuf:"bytes,6,opt,name=receipt_root,json=receiptRoot,proto3" json:"receipt_root,omitempty"` + LogsBloom []byte `protobuf:"bytes,7,opt,name=logs_bloom,json=logsBloom,proto3" json:"logs_bloom,omitempty"` + // Difficulty is the difficulty of the Proof of Work algorithm that was required to compute a solution. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0x00`. + Difficulty *BigInt `protobuf:"bytes,8,opt,name=difficulty,proto3" json:"difficulty,omitempty"` + // TotalDifficulty is the sum of all previous blocks difficulty including this block difficulty. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to the terminal total difficulty + // that was required to transition to Proof of Stake algorithm, which varies per network. It is set to + // 58 750 000 000 000 000 000 000 on Ethereum Mainnet and to 10 790 000 on Ethereum Testnet Goerli. + TotalDifficulty *BigInt `protobuf:"bytes,17,opt,name=total_difficulty,json=totalDifficulty,proto3" json:"total_difficulty,omitempty"` + Number uint64 `protobuf:"varint,9,opt,name=number,proto3" json:"number,omitempty"` + GasLimit uint64 `protobuf:"varint,10,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + GasUsed uint64 `protobuf:"varint,11,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + Timestamp *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + // ExtraData is free-form bytes included in the block by the "miner". While on Yellow paper of + // Ethereum this value is maxed to 32 bytes, other consensus algorithm like Clique and some other + // forks are using bigger values to carry special consensus data. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field is strictly enforced to be <= 32 bytes. + ExtraData []byte `protobuf:"bytes,13,opt,name=extra_data,json=extraData,proto3" json:"extra_data,omitempty"` + // MixHash is used to prove, when combined with the `nonce` that sufficient amount of computation has been + // achieved and that the solution found is valid. + MixHash []byte `protobuf:"bytes,14,opt,name=mix_hash,json=mixHash,proto3" json:"mix_hash,omitempty"` + // Nonce is used to prove, when combined with the `mix_hash` that sufficient amount of computation has been + // achieved and that the solution found is valid. + // + // If the Block containing this `BlockHeader` has been produced using the Proof of Stake + // consensus algorithm, this field will actually be constant and set to `0`. + Nonce uint64 `protobuf:"varint,15,opt,name=nonce,proto3" json:"nonce,omitempty"` + // Hash is the hash of the block which is actually the computation: + // + // Keccak256(rlp([ + // parent_hash, + // uncle_hash, + // coinbase, + // state_root, + // transactions_root, + // receipt_root, + // logs_bloom, + // difficulty, + // number, + // gas_limit, + // gas_used, + // timestamp, + // extra_data, + // mix_hash, + // nonce, + // base_fee_per_gas (to be included, only if London Fork is active) + // withdrawals_root (to be included, only if Shangai Fork is active) + // ])) + Hash []byte `protobuf:"bytes,16,opt,name=hash,proto3" json:"hash,omitempty"` + // Base fee per gas according to EIP-1559 (e.g. London Fork) rules, only set if London is present/active on the chain. + BaseFeePerGas *BigInt `protobuf:"bytes,18,opt,name=base_fee_per_gas,json=baseFeePerGas,proto3" json:"base_fee_per_gas,omitempty"` + // Withdrawals root hash according to EIP-4895 (e.g. Shangai Fork) rules, only set if Shangai is present/active on the chain. + // + // Only available in DetailLevel: EXTENDED + WithdrawalsRoot []byte `protobuf:"bytes,19,opt,name=withdrawals_root,json=withdrawalsRoot,proto3" json:"withdrawals_root,omitempty"` + // Only available in DetailLevel: EXTENDED + TxDependency *Uint64NestedArray `protobuf:"bytes,20,opt,name=tx_dependency,json=txDependency,proto3" json:"tx_dependency,omitempty"` +} + +func (x *BlockHeader) Reset() { + *x = BlockHeader{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockHeader) ProtoMessage() {} + +func (x *BlockHeader) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockHeader.ProtoReflect.Descriptor instead. +func (*BlockHeader) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{1} +} + +func (x *BlockHeader) GetParentHash() []byte { + if x != nil { + return x.ParentHash + } + return nil +} + +func (x *BlockHeader) GetUncleHash() []byte { + if x != nil { + return x.UncleHash + } + return nil +} + +func (x *BlockHeader) GetCoinbase() []byte { + if x != nil { + return x.Coinbase + } + return nil +} + +func (x *BlockHeader) GetStateRoot() []byte { + if x != nil { + return x.StateRoot + } + return nil +} + +func (x *BlockHeader) GetTransactionsRoot() []byte { + if x != nil { + return x.TransactionsRoot + } + return nil +} + +func (x *BlockHeader) GetReceiptRoot() []byte { + if x != nil { + return x.ReceiptRoot + } + return nil +} + +func (x *BlockHeader) GetLogsBloom() []byte { + if x != nil { + return x.LogsBloom + } + return nil +} + +func (x *BlockHeader) GetDifficulty() *BigInt { + if x != nil { + return x.Difficulty + } + return nil +} + +func (x *BlockHeader) GetTotalDifficulty() *BigInt { + if x != nil { + return x.TotalDifficulty + } + return nil +} + +func (x *BlockHeader) GetNumber() uint64 { + if x != nil { + return x.Number + } + return 0 +} + +func (x *BlockHeader) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *BlockHeader) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *BlockHeader) GetTimestamp() *timestamppb.Timestamp { + if x != nil { + return x.Timestamp + } + return nil +} + +func (x *BlockHeader) GetExtraData() []byte { + if x != nil { + return x.ExtraData + } + return nil +} + +func (x *BlockHeader) GetMixHash() []byte { + if x != nil { + return x.MixHash + } + return nil +} + +func (x *BlockHeader) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *BlockHeader) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *BlockHeader) GetBaseFeePerGas() *BigInt { + if x != nil { + return x.BaseFeePerGas + } + return nil +} + +func (x *BlockHeader) GetWithdrawalsRoot() []byte { + if x != nil { + return x.WithdrawalsRoot + } + return nil +} + +func (x *BlockHeader) GetTxDependency() *Uint64NestedArray { + if x != nil { + return x.TxDependency + } + return nil +} + +type Uint64NestedArray struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Val []*Uint64Array `protobuf:"bytes,1,rep,name=val,proto3" json:"val,omitempty"` +} + +func (x *Uint64NestedArray) Reset() { + *x = Uint64NestedArray{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Uint64NestedArray) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Uint64NestedArray) ProtoMessage() {} + +func (x *Uint64NestedArray) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Uint64NestedArray.ProtoReflect.Descriptor instead. +func (*Uint64NestedArray) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{2} +} + +func (x *Uint64NestedArray) GetVal() []*Uint64Array { + if x != nil { + return x.Val + } + return nil +} + +type Uint64Array struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Val []uint64 `protobuf:"varint,1,rep,packed,name=val,proto3" json:"val,omitempty"` +} + +func (x *Uint64Array) Reset() { + *x = Uint64Array{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Uint64Array) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Uint64Array) ProtoMessage() {} + +func (x *Uint64Array) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Uint64Array.ProtoReflect.Descriptor instead. +func (*Uint64Array) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{3} +} + +func (x *Uint64Array) GetVal() []uint64 { + if x != nil { + return x.Val + } + return nil +} + +type BigInt struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Bytes []byte `protobuf:"bytes,1,opt,name=bytes,proto3" json:"bytes,omitempty"` +} + +func (x *BigInt) Reset() { + *x = BigInt{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BigInt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BigInt) ProtoMessage() {} + +func (x *BigInt) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BigInt.ProtoReflect.Descriptor instead. +func (*BigInt) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{4} +} + +func (x *BigInt) GetBytes() []byte { + if x != nil { + return x.Bytes + } + return nil +} + +type TransactionTrace struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // consensus + To []byte `protobuf:"bytes,1,opt,name=to,proto3" json:"to,omitempty"` + Nonce uint64 `protobuf:"varint,2,opt,name=nonce,proto3" json:"nonce,omitempty"` + // GasPrice represents the effective price that has been paid for each gas unit of this transaction. Over time, the + // Ethereum rules changes regarding GasPrice field here. Before London fork, the GasPrice was always set to the + // fixed gas price. After London fork, this value has different meaning depending on the transaction type (see `Type` field). + // + // In cases where `TransactionTrace.Type == TRX_TYPE_LEGACY || TRX_TYPE_ACCESS_LIST`, then GasPrice has the same meaning + // as before the London fork. + // + // In cases where `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE`, then GasPrice is the effective gas price paid + // for the transaction which is equals to `BlockHeader.BaseFeePerGas + TransactionTrace.` + GasPrice *BigInt `protobuf:"bytes,3,opt,name=gas_price,json=gasPrice,proto3" json:"gas_price,omitempty"` + // GasLimit is the maximum of gas unit the sender of the transaction is willing to consume when perform the EVM + // execution of the whole transaction + GasLimit uint64 `protobuf:"varint,4,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + // Value is the amount of Ether transferred as part of this transaction. + Value *BigInt `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` + // Input data the transaction will receive for execution of EVM. + Input []byte `protobuf:"bytes,6,opt,name=input,proto3" json:"input,omitempty"` + // V is the recovery ID value for the signature Y point. + V []byte `protobuf:"bytes,7,opt,name=v,proto3" json:"v,omitempty"` + // R is the signature's X point on the elliptic curve (32 bytes). + R []byte `protobuf:"bytes,8,opt,name=r,proto3" json:"r,omitempty"` + // S is the signature's Y point on the elliptic curve (32 bytes). + S []byte `protobuf:"bytes,9,opt,name=s,proto3" json:"s,omitempty"` + // GasUsed is the total amount of gas unit used for the whole execution of the transaction. + // + // Only available in DetailLevel: EXTENDED + GasUsed uint64 `protobuf:"varint,10,opt,name=gas_used,json=gasUsed,proto3" json:"gas_used,omitempty"` + // Type represents the Ethereum transaction type, available only since EIP-2718 & EIP-2930 activation which happened on Berlin fork. + // The value is always set even for transaction before Berlin fork because those before the fork are still legacy transactions. + // + // Only available in DetailLevel: EXTENDED + Type TransactionTrace_Type `protobuf:"varint,12,opt,name=type,proto3,enum=sf.ethereum.type.v2.TransactionTrace_Type" json:"type,omitempty"` + // AcccessList represents the storage access this transaction has agreed to do in which case those storage + // access cost less gas unit per access. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_ACCESS_LIST || TRX_TYPE_DYNAMIC_FEE` which + // is possible only if Berlin (TRX_TYPE_ACCESS_LIST) nor London (TRX_TYPE_DYNAMIC_FEE) fork are active on the chain. + AccessList []*AccessTuple `protobuf:"bytes,14,rep,name=access_list,json=accessList,proto3" json:"access_list,omitempty"` + // MaxFeePerGas is the maximum fee per gas the user is willing to pay for the transaction gas used. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only + // if Londong fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED + MaxFeePerGas *BigInt `protobuf:"bytes,11,opt,name=max_fee_per_gas,json=maxFeePerGas,proto3" json:"max_fee_per_gas,omitempty"` + // MaxPriorityFeePerGas is priority fee per gas the user to pay in extra to the miner on top of the block's + // base fee. + // + // This will is populated only if `TransactionTrace.Type == TRX_TYPE_DYNAMIC_FEE` which is possible only + // if London fork is active on the chain. + // + // Only available in DetailLevel: EXTENDED + MaxPriorityFeePerGas *BigInt `protobuf:"bytes,13,opt,name=max_priority_fee_per_gas,json=maxPriorityFeePerGas,proto3" json:"max_priority_fee_per_gas,omitempty"` + // meta + Index uint32 `protobuf:"varint,20,opt,name=index,proto3" json:"index,omitempty"` + Hash []byte `protobuf:"bytes,21,opt,name=hash,proto3" json:"hash,omitempty"` + From []byte `protobuf:"bytes,22,opt,name=from,proto3" json:"from,omitempty"` + // Only available in DetailLevel: EXTENDED + ReturnData []byte `protobuf:"bytes,23,opt,name=return_data,json=returnData,proto3" json:"return_data,omitempty"` + // Only available in DetailLevel: EXTENDED + PublicKey []byte `protobuf:"bytes,24,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + BeginOrdinal uint64 `protobuf:"varint,25,opt,name=begin_ordinal,json=beginOrdinal,proto3" json:"begin_ordinal,omitempty"` + EndOrdinal uint64 `protobuf:"varint,26,opt,name=end_ordinal,json=endOrdinal,proto3" json:"end_ordinal,omitempty"` + // TransactionTraceStatus is the status of the transaction execution and will let you know if the transaction + // was successful or not. + // + // A successful transaction has been recorded to the blockchain's state for calls in it that were successful. + // This means it's possible only a subset of the calls were properly recorded, refer to [calls[].state_reverted] field + // to determine which calls were reverted. + // + // A quirks of the Ethereum protocol is that a transaction `FAILED` or `REVERTED` still affects the blockchain's + // state for **some** of the state changes. Indeed, in those cases, the transactions fees are still paid to the miner + // which means there is a balance change for the transaction's emitter (e.g. `from`) to pay the gas fees, an optional + // balance change for gas refunded to the transaction's emitter (e.g. `from`) and a balance change for the miner who + // received the transaction fees. There is also a nonce change for the transaction's emitter (e.g. `from`). + // + // This means that to properly record the state changes for a transaction, you need to conditionally procees the + // transaction's status. + // + // For a `SUCCEEDED` transaction, you iterate over the `calls` array and record the state changes for each call for + // which `state_reverted == false` (if a transaction succeeded, the call at #0 will always `state_reverted == false` + // because it aligns with the transaction). + // + // For a `FAILED` or `REVERTED` transaction, you iterate over the root call (e.g. at #0, will always exist) for + // balance changes you process those where `reason` is either `REASON_GAS_BUY`, `REASON_GAS_REFUND` or + // `REASON_REWARD_TRANSACTION_FEE` and for nonce change, still on the root call, you pick the nonce change which the + // smallest ordinal (if more than one). + // + // Only available in DetailLevel: EXTENDED + Status TransactionTraceStatus `protobuf:"varint,30,opt,name=status,proto3,enum=sf.ethereum.type.v2.TransactionTraceStatus" json:"status,omitempty"` + Receipt *TransactionReceipt `protobuf:"bytes,31,opt,name=receipt,proto3" json:"receipt,omitempty"` + // Only available in DetailLevel: EXTENDED + Calls []*Call `protobuf:"bytes,32,rep,name=calls,proto3" json:"calls,omitempty"` +} + +func (x *TransactionTrace) Reset() { + *x = TransactionTrace{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionTrace) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionTrace) ProtoMessage() {} + +func (x *TransactionTrace) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionTrace.ProtoReflect.Descriptor instead. +func (*TransactionTrace) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{5} +} + +func (x *TransactionTrace) GetTo() []byte { + if x != nil { + return x.To + } + return nil +} + +func (x *TransactionTrace) GetNonce() uint64 { + if x != nil { + return x.Nonce + } + return 0 +} + +func (x *TransactionTrace) GetGasPrice() *BigInt { + if x != nil { + return x.GasPrice + } + return nil +} + +func (x *TransactionTrace) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *TransactionTrace) GetValue() *BigInt { + if x != nil { + return x.Value + } + return nil +} + +func (x *TransactionTrace) GetInput() []byte { + if x != nil { + return x.Input + } + return nil +} + +func (x *TransactionTrace) GetV() []byte { + if x != nil { + return x.V + } + return nil +} + +func (x *TransactionTrace) GetR() []byte { + if x != nil { + return x.R + } + return nil +} + +func (x *TransactionTrace) GetS() []byte { + if x != nil { + return x.S + } + return nil +} + +func (x *TransactionTrace) GetGasUsed() uint64 { + if x != nil { + return x.GasUsed + } + return 0 +} + +func (x *TransactionTrace) GetType() TransactionTrace_Type { + if x != nil { + return x.Type + } + return TransactionTrace_TRX_TYPE_LEGACY +} + +func (x *TransactionTrace) GetAccessList() []*AccessTuple { + if x != nil { + return x.AccessList + } + return nil +} + +func (x *TransactionTrace) GetMaxFeePerGas() *BigInt { + if x != nil { + return x.MaxFeePerGas + } + return nil +} + +func (x *TransactionTrace) GetMaxPriorityFeePerGas() *BigInt { + if x != nil { + return x.MaxPriorityFeePerGas + } + return nil +} + +func (x *TransactionTrace) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *TransactionTrace) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *TransactionTrace) GetFrom() []byte { + if x != nil { + return x.From + } + return nil +} + +func (x *TransactionTrace) GetReturnData() []byte { + if x != nil { + return x.ReturnData + } + return nil +} + +func (x *TransactionTrace) GetPublicKey() []byte { + if x != nil { + return x.PublicKey + } + return nil +} + +func (x *TransactionTrace) GetBeginOrdinal() uint64 { + if x != nil { + return x.BeginOrdinal + } + return 0 +} + +func (x *TransactionTrace) GetEndOrdinal() uint64 { + if x != nil { + return x.EndOrdinal + } + return 0 +} + +func (x *TransactionTrace) GetStatus() TransactionTraceStatus { + if x != nil { + return x.Status + } + return TransactionTraceStatus_UNKNOWN +} + +func (x *TransactionTrace) GetReceipt() *TransactionReceipt { + if x != nil { + return x.Receipt + } + return nil +} + +func (x *TransactionTrace) GetCalls() []*Call { + if x != nil { + return x.Calls + } + return nil +} + +// AccessTuple represents a list of storage keys for a given contract's address and is used +// for AccessList construction. +type AccessTuple struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + StorageKeys [][]byte `protobuf:"bytes,2,rep,name=storage_keys,json=storageKeys,proto3" json:"storage_keys,omitempty"` +} + +func (x *AccessTuple) Reset() { + *x = AccessTuple{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AccessTuple) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccessTuple) ProtoMessage() {} + +func (x *AccessTuple) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccessTuple.ProtoReflect.Descriptor instead. +func (*AccessTuple) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{6} +} + +func (x *AccessTuple) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *AccessTuple) GetStorageKeys() [][]byte { + if x != nil { + return x.StorageKeys + } + return nil +} + +type TransactionReceipt struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // State root is an intermediate state_root hash, computed in-between transactions to make + // **sure** you could build a proof and point to state in the middle of a block. Geth client + // uses `PostState + root + PostStateOrStatus“ while Parity used `status_code, root...“ this piles + // hardforks, see (read the EIPs first): + // - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md + // + // Moreover, the notion of `Outcome“ in parity, which segregates the two concepts, which are + // stored in the same field `status_code“ can be computed based on such a hack of the `state_root` + // field, following `EIP-658`. + // + // Before Byzantinium hard fork, this field is always empty. + // + // Only available in DetailLevel: EXTENDED + StateRoot []byte `protobuf:"bytes,1,opt,name=state_root,json=stateRoot,proto3" json:"state_root,omitempty"` + // Only available in DetailLevel: EXTENDED + CumulativeGasUsed uint64 `protobuf:"varint,2,opt,name=cumulative_gas_used,json=cumulativeGasUsed,proto3" json:"cumulative_gas_used,omitempty"` + // Only available in DetailLevel: EXTENDED + LogsBloom []byte `protobuf:"bytes,3,opt,name=logs_bloom,json=logsBloom,proto3" json:"logs_bloom,omitempty"` + Logs []*Log `protobuf:"bytes,4,rep,name=logs,proto3" json:"logs,omitempty"` +} + +func (x *TransactionReceipt) Reset() { + *x = TransactionReceipt{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionReceipt) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionReceipt) ProtoMessage() {} + +func (x *TransactionReceipt) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[7] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionReceipt.ProtoReflect.Descriptor instead. +func (*TransactionReceipt) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{7} +} + +func (x *TransactionReceipt) GetStateRoot() []byte { + if x != nil { + return x.StateRoot + } + return nil +} + +func (x *TransactionReceipt) GetCumulativeGasUsed() uint64 { + if x != nil { + return x.CumulativeGasUsed + } + return 0 +} + +func (x *TransactionReceipt) GetLogsBloom() []byte { + if x != nil { + return x.LogsBloom + } + return nil +} + +func (x *TransactionReceipt) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +type Log struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Topics [][]byte `protobuf:"bytes,2,rep,name=topics,proto3" json:"topics,omitempty"` + Data []byte `protobuf:"bytes,3,opt,name=data,proto3" json:"data,omitempty"` + // Index is the index of the log relative to the transaction. This index + // is always populated regardless of the state revertion of the the call + // that emitted this log. + // + // Only available in DetailLevel: EXTENDED + Index uint32 `protobuf:"varint,4,opt,name=index,proto3" json:"index,omitempty"` + // BlockIndex represents the index of the log relative to the Block. + // + // An **important** notice is that this field will be 0 when the call + // that emitted the log has been reverted by the chain. + // + // Currently, there is two locations where a Log can be obtained: + // - block.transaction_traces[].receipt.logs[] + // - block.transaction_traces[].calls[].logs[] + // + // In the `receipt` case, the logs will be populated only when the call + // that emitted them has not been reverted by the chain and when in this + // position, the `blockIndex` is always populated correctly. + // + // In the case of `calls` case, for `call` where `stateReverted == true`, + // the `blockIndex` value will always be 0. + BlockIndex uint32 `protobuf:"varint,6,opt,name=blockIndex,proto3" json:"blockIndex,omitempty"` + Ordinal uint64 `protobuf:"varint,7,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *Log) Reset() { + *x = Log{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Log) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Log) ProtoMessage() {} + +func (x *Log) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Log.ProtoReflect.Descriptor instead. +func (*Log) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{8} +} + +func (x *Log) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *Log) GetTopics() [][]byte { + if x != nil { + return x.Topics + } + return nil +} + +func (x *Log) GetData() []byte { + if x != nil { + return x.Data + } + return nil +} + +func (x *Log) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Log) GetBlockIndex() uint32 { + if x != nil { + return x.BlockIndex + } + return 0 +} + +func (x *Log) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type Call struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Index uint32 `protobuf:"varint,1,opt,name=index,proto3" json:"index,omitempty"` + ParentIndex uint32 `protobuf:"varint,2,opt,name=parent_index,json=parentIndex,proto3" json:"parent_index,omitempty"` + Depth uint32 `protobuf:"varint,3,opt,name=depth,proto3" json:"depth,omitempty"` + CallType CallType `protobuf:"varint,4,opt,name=call_type,json=callType,proto3,enum=sf.ethereum.type.v2.CallType" json:"call_type,omitempty"` + Caller []byte `protobuf:"bytes,5,opt,name=caller,proto3" json:"caller,omitempty"` + Address []byte `protobuf:"bytes,6,opt,name=address,proto3" json:"address,omitempty"` + Value *BigInt `protobuf:"bytes,7,opt,name=value,proto3" json:"value,omitempty"` + GasLimit uint64 `protobuf:"varint,8,opt,name=gas_limit,json=gasLimit,proto3" json:"gas_limit,omitempty"` + GasConsumed uint64 `protobuf:"varint,9,opt,name=gas_consumed,json=gasConsumed,proto3" json:"gas_consumed,omitempty"` + ReturnData []byte `protobuf:"bytes,13,opt,name=return_data,json=returnData,proto3" json:"return_data,omitempty"` + Input []byte `protobuf:"bytes,14,opt,name=input,proto3" json:"input,omitempty"` + ExecutedCode bool `protobuf:"varint,15,opt,name=executed_code,json=executedCode,proto3" json:"executed_code,omitempty"` + Suicide bool `protobuf:"varint,16,opt,name=suicide,proto3" json:"suicide,omitempty"` + // hex representation of the hash -> preimage + KeccakPreimages map[string]string `protobuf:"bytes,20,rep,name=keccak_preimages,json=keccakPreimages,proto3" json:"keccak_preimages,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + StorageChanges []*StorageChange `protobuf:"bytes,21,rep,name=storage_changes,json=storageChanges,proto3" json:"storage_changes,omitempty"` + BalanceChanges []*BalanceChange `protobuf:"bytes,22,rep,name=balance_changes,json=balanceChanges,proto3" json:"balance_changes,omitempty"` + NonceChanges []*NonceChange `protobuf:"bytes,24,rep,name=nonce_changes,json=nonceChanges,proto3" json:"nonce_changes,omitempty"` + Logs []*Log `protobuf:"bytes,25,rep,name=logs,proto3" json:"logs,omitempty"` + CodeChanges []*CodeChange `protobuf:"bytes,26,rep,name=code_changes,json=codeChanges,proto3" json:"code_changes,omitempty"` + GasChanges []*GasChange `protobuf:"bytes,28,rep,name=gas_changes,json=gasChanges,proto3" json:"gas_changes,omitempty"` + // In Ethereum, a call can be either: + // - Successfull, execution passes without any problem encountered + // - Failed, execution failed, and remaining gas should be consumed + // - Reverted, execution failed, but only gas consumed so far is billed, remaining gas is refunded + // + // When a call is either `failed` or `reverted`, the `status_failed` field + // below is set to `true`. If the status is `reverted`, then both `status_failed` + // and `status_reverted` are going to be set to `true`. + StatusFailed bool `protobuf:"varint,10,opt,name=status_failed,json=statusFailed,proto3" json:"status_failed,omitempty"` + StatusReverted bool `protobuf:"varint,12,opt,name=status_reverted,json=statusReverted,proto3" json:"status_reverted,omitempty"` + // Populated when a call either failed or reverted, so when `status_failed == true`, + // see above for details about those flags. + FailureReason string `protobuf:"bytes,11,opt,name=failure_reason,json=failureReason,proto3" json:"failure_reason,omitempty"` + // This field represents wheter or not the state changes performed + // by this call were correctly recorded by the blockchain. + // + // On Ethereum, a transaction can record state changes even if some + // of its inner nested calls failed. This is problematic however since + // a call will invalidate all its state changes as well as all state + // changes performed by its child call. This means that even if a call + // has a status of `SUCCESS`, the chain might have reverted all the state + // changes it performed. + // + // ```text + // + // Trx 1 + // Call #1 + // Call #2 + // Call #3 + // |--- Failure here + // Call #4 + // + // ``` + // + // In the transaction above, while Call #2 and Call #3 would have the + // status `EXECUTED`. + // + // If you check all calls and check only `state_reverted` flag, you might be missing + // some balance changes and nonce changes. This is because when a full transaction fails + // in ethereum (e.g. `calls.all(x.state_reverted == true)`), there is still the transaction + // fee that are recorded to the chain. + // + // Refer to [TransactionTrace#status] field for more details about the handling you must + // perform. + StateReverted bool `protobuf:"varint,30,opt,name=state_reverted,json=stateReverted,proto3" json:"state_reverted,omitempty"` + BeginOrdinal uint64 `protobuf:"varint,31,opt,name=begin_ordinal,json=beginOrdinal,proto3" json:"begin_ordinal,omitempty"` + EndOrdinal uint64 `protobuf:"varint,32,opt,name=end_ordinal,json=endOrdinal,proto3" json:"end_ordinal,omitempty"` + AccountCreations []*AccountCreation `protobuf:"bytes,33,rep,name=account_creations,json=accountCreations,proto3" json:"account_creations,omitempty"` +} + +func (x *Call) Reset() { + *x = Call{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Call) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Call) ProtoMessage() {} + +func (x *Call) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Call.ProtoReflect.Descriptor instead. +func (*Call) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{9} +} + +func (x *Call) GetIndex() uint32 { + if x != nil { + return x.Index + } + return 0 +} + +func (x *Call) GetParentIndex() uint32 { + if x != nil { + return x.ParentIndex + } + return 0 +} + +func (x *Call) GetDepth() uint32 { + if x != nil { + return x.Depth + } + return 0 +} + +func (x *Call) GetCallType() CallType { + if x != nil { + return x.CallType + } + return CallType_UNSPECIFIED +} + +func (x *Call) GetCaller() []byte { + if x != nil { + return x.Caller + } + return nil +} + +func (x *Call) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *Call) GetValue() *BigInt { + if x != nil { + return x.Value + } + return nil +} + +func (x *Call) GetGasLimit() uint64 { + if x != nil { + return x.GasLimit + } + return 0 +} + +func (x *Call) GetGasConsumed() uint64 { + if x != nil { + return x.GasConsumed + } + return 0 +} + +func (x *Call) GetReturnData() []byte { + if x != nil { + return x.ReturnData + } + return nil +} + +func (x *Call) GetInput() []byte { + if x != nil { + return x.Input + } + return nil +} + +func (x *Call) GetExecutedCode() bool { + if x != nil { + return x.ExecutedCode + } + return false +} + +func (x *Call) GetSuicide() bool { + if x != nil { + return x.Suicide + } + return false +} + +func (x *Call) GetKeccakPreimages() map[string]string { + if x != nil { + return x.KeccakPreimages + } + return nil +} + +func (x *Call) GetStorageChanges() []*StorageChange { + if x != nil { + return x.StorageChanges + } + return nil +} + +func (x *Call) GetBalanceChanges() []*BalanceChange { + if x != nil { + return x.BalanceChanges + } + return nil +} + +func (x *Call) GetNonceChanges() []*NonceChange { + if x != nil { + return x.NonceChanges + } + return nil +} + +func (x *Call) GetLogs() []*Log { + if x != nil { + return x.Logs + } + return nil +} + +func (x *Call) GetCodeChanges() []*CodeChange { + if x != nil { + return x.CodeChanges + } + return nil +} + +func (x *Call) GetGasChanges() []*GasChange { + if x != nil { + return x.GasChanges + } + return nil +} + +func (x *Call) GetStatusFailed() bool { + if x != nil { + return x.StatusFailed + } + return false +} + +func (x *Call) GetStatusReverted() bool { + if x != nil { + return x.StatusReverted + } + return false +} + +func (x *Call) GetFailureReason() string { + if x != nil { + return x.FailureReason + } + return "" +} + +func (x *Call) GetStateReverted() bool { + if x != nil { + return x.StateReverted + } + return false +} + +func (x *Call) GetBeginOrdinal() uint64 { + if x != nil { + return x.BeginOrdinal + } + return 0 +} + +func (x *Call) GetEndOrdinal() uint64 { + if x != nil { + return x.EndOrdinal + } + return 0 +} + +func (x *Call) GetAccountCreations() []*AccountCreation { + if x != nil { + return x.AccountCreations + } + return nil +} + +type StorageChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` + OldValue []byte `protobuf:"bytes,3,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue []byte `protobuf:"bytes,4,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Ordinal uint64 `protobuf:"varint,5,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *StorageChange) Reset() { + *x = StorageChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *StorageChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*StorageChange) ProtoMessage() {} + +func (x *StorageChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[10] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use StorageChange.ProtoReflect.Descriptor instead. +func (*StorageChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{10} +} + +func (x *StorageChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *StorageChange) GetKey() []byte { + if x != nil { + return x.Key + } + return nil +} + +func (x *StorageChange) GetOldValue() []byte { + if x != nil { + return x.OldValue + } + return nil +} + +func (x *StorageChange) GetNewValue() []byte { + if x != nil { + return x.NewValue + } + return nil +} + +func (x *StorageChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type BalanceChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + OldValue *BigInt `protobuf:"bytes,2,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue *BigInt `protobuf:"bytes,3,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Reason BalanceChange_Reason `protobuf:"varint,4,opt,name=reason,proto3,enum=sf.ethereum.type.v2.BalanceChange_Reason" json:"reason,omitempty"` + Ordinal uint64 `protobuf:"varint,5,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *BalanceChange) Reset() { + *x = BalanceChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BalanceChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BalanceChange) ProtoMessage() {} + +func (x *BalanceChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BalanceChange.ProtoReflect.Descriptor instead. +func (*BalanceChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{11} +} + +func (x *BalanceChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *BalanceChange) GetOldValue() *BigInt { + if x != nil { + return x.OldValue + } + return nil +} + +func (x *BalanceChange) GetNewValue() *BigInt { + if x != nil { + return x.NewValue + } + return nil +} + +func (x *BalanceChange) GetReason() BalanceChange_Reason { + if x != nil { + return x.Reason + } + return BalanceChange_REASON_UNKNOWN +} + +func (x *BalanceChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type NonceChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + OldValue uint64 `protobuf:"varint,2,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue uint64 `protobuf:"varint,3,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Ordinal uint64 `protobuf:"varint,4,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *NonceChange) Reset() { + *x = NonceChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NonceChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NonceChange) ProtoMessage() {} + +func (x *NonceChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NonceChange.ProtoReflect.Descriptor instead. +func (*NonceChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{12} +} + +func (x *NonceChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *NonceChange) GetOldValue() uint64 { + if x != nil { + return x.OldValue + } + return 0 +} + +func (x *NonceChange) GetNewValue() uint64 { + if x != nil { + return x.NewValue + } + return 0 +} + +func (x *NonceChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type AccountCreation struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Account []byte `protobuf:"bytes,1,opt,name=account,proto3" json:"account,omitempty"` + Ordinal uint64 `protobuf:"varint,2,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *AccountCreation) Reset() { + *x = AccountCreation{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *AccountCreation) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AccountCreation) ProtoMessage() {} + +func (x *AccountCreation) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AccountCreation.ProtoReflect.Descriptor instead. +func (*AccountCreation) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{13} +} + +func (x *AccountCreation) GetAccount() []byte { + if x != nil { + return x.Account + } + return nil +} + +func (x *AccountCreation) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +type CodeChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + OldHash []byte `protobuf:"bytes,2,opt,name=old_hash,json=oldHash,proto3" json:"old_hash,omitempty"` + OldCode []byte `protobuf:"bytes,3,opt,name=old_code,json=oldCode,proto3" json:"old_code,omitempty"` + NewHash []byte `protobuf:"bytes,4,opt,name=new_hash,json=newHash,proto3" json:"new_hash,omitempty"` + NewCode []byte `protobuf:"bytes,5,opt,name=new_code,json=newCode,proto3" json:"new_code,omitempty"` + Ordinal uint64 `protobuf:"varint,6,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *CodeChange) Reset() { + *x = CodeChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CodeChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CodeChange) ProtoMessage() {} + +func (x *CodeChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CodeChange.ProtoReflect.Descriptor instead. +func (*CodeChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{14} +} + +func (x *CodeChange) GetAddress() []byte { + if x != nil { + return x.Address + } + return nil +} + +func (x *CodeChange) GetOldHash() []byte { + if x != nil { + return x.OldHash + } + return nil +} + +func (x *CodeChange) GetOldCode() []byte { + if x != nil { + return x.OldCode + } + return nil +} + +func (x *CodeChange) GetNewHash() []byte { + if x != nil { + return x.NewHash + } + return nil +} + +func (x *CodeChange) GetNewCode() []byte { + if x != nil { + return x.NewCode + } + return nil +} + +func (x *CodeChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +// The gas change model represents the reason why some gas cost has occurred. +// The gas is computed per actual op codes. Doing them completely might prove +// overwhelming in most cases. +// +// Hence, we only index some of them, those that are costy like all the calls +// one, log events, return data, etc. +type GasChange struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + OldValue uint64 `protobuf:"varint,1,opt,name=old_value,json=oldValue,proto3" json:"old_value,omitempty"` + NewValue uint64 `protobuf:"varint,2,opt,name=new_value,json=newValue,proto3" json:"new_value,omitempty"` + Reason GasChange_Reason `protobuf:"varint,3,opt,name=reason,proto3,enum=sf.ethereum.type.v2.GasChange_Reason" json:"reason,omitempty"` + Ordinal uint64 `protobuf:"varint,4,opt,name=ordinal,proto3" json:"ordinal,omitempty"` +} + +func (x *GasChange) Reset() { + *x = GasChange{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GasChange) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GasChange) ProtoMessage() {} + +func (x *GasChange) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[15] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GasChange.ProtoReflect.Descriptor instead. +func (*GasChange) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{15} +} + +func (x *GasChange) GetOldValue() uint64 { + if x != nil { + return x.OldValue + } + return 0 +} + +func (x *GasChange) GetNewValue() uint64 { + if x != nil { + return x.NewValue + } + return 0 +} + +func (x *GasChange) GetReason() GasChange_Reason { + if x != nil { + return x.Reason + } + return GasChange_REASON_UNKNOWN +} + +func (x *GasChange) GetOrdinal() uint64 { + if x != nil { + return x.Ordinal + } + return 0 +} + +// HeaderOnlyBlock is used to optimally unpack the [Block] structure (note the +// corresponding message number for the `header` field) while consuming less +// memory, when only the `header` is desired. +// +// WARN: this is a client-side optimization pattern and should be moved in the +// consuming code. +type HeaderOnlyBlock struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Header *BlockHeader `protobuf:"bytes,5,opt,name=header,proto3" json:"header,omitempty"` +} + +func (x *HeaderOnlyBlock) Reset() { + *x = HeaderOnlyBlock{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *HeaderOnlyBlock) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HeaderOnlyBlock) ProtoMessage() {} + +func (x *HeaderOnlyBlock) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[16] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HeaderOnlyBlock.ProtoReflect.Descriptor instead. +func (*HeaderOnlyBlock) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{16} +} + +func (x *HeaderOnlyBlock) GetHeader() *BlockHeader { + if x != nil { + return x.Header + } + return nil +} + +// BlockWithRefs is a lightweight block, with traces and transactions +// purged from the `block` within, and only. It is used in transports +// to pass block data around. +type BlockWithRefs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Block *Block `protobuf:"bytes,2,opt,name=block,proto3" json:"block,omitempty"` + TransactionTraceRefs *TransactionRefs `protobuf:"bytes,3,opt,name=transaction_trace_refs,json=transactionTraceRefs,proto3" json:"transaction_trace_refs,omitempty"` + Irreversible bool `protobuf:"varint,4,opt,name=irreversible,proto3" json:"irreversible,omitempty"` +} + +func (x *BlockWithRefs) Reset() { + *x = BlockWithRefs{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockWithRefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockWithRefs) ProtoMessage() {} + +func (x *BlockWithRefs) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[17] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockWithRefs.ProtoReflect.Descriptor instead. +func (*BlockWithRefs) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{17} +} + +func (x *BlockWithRefs) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *BlockWithRefs) GetBlock() *Block { + if x != nil { + return x.Block + } + return nil +} + +func (x *BlockWithRefs) GetTransactionTraceRefs() *TransactionRefs { + if x != nil { + return x.TransactionTraceRefs + } + return nil +} + +func (x *BlockWithRefs) GetIrreversible() bool { + if x != nil { + return x.Irreversible + } + return false +} + +type TransactionTraceWithBlockRef struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Trace *TransactionTrace `protobuf:"bytes,1,opt,name=trace,proto3" json:"trace,omitempty"` + BlockRef *BlockRef `protobuf:"bytes,2,opt,name=block_ref,json=blockRef,proto3" json:"block_ref,omitempty"` +} + +func (x *TransactionTraceWithBlockRef) Reset() { + *x = TransactionTraceWithBlockRef{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionTraceWithBlockRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionTraceWithBlockRef) ProtoMessage() {} + +func (x *TransactionTraceWithBlockRef) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[18] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionTraceWithBlockRef.ProtoReflect.Descriptor instead. +func (*TransactionTraceWithBlockRef) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{18} +} + +func (x *TransactionTraceWithBlockRef) GetTrace() *TransactionTrace { + if x != nil { + return x.Trace + } + return nil +} + +func (x *TransactionTraceWithBlockRef) GetBlockRef() *BlockRef { + if x != nil { + return x.BlockRef + } + return nil +} + +type TransactionRefs struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hashes [][]byte `protobuf:"bytes,1,rep,name=hashes,proto3" json:"hashes,omitempty"` +} + +func (x *TransactionRefs) Reset() { + *x = TransactionRefs{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TransactionRefs) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TransactionRefs) ProtoMessage() {} + +func (x *TransactionRefs) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[19] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TransactionRefs.ProtoReflect.Descriptor instead. +func (*TransactionRefs) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{19} +} + +func (x *TransactionRefs) GetHashes() [][]byte { + if x != nil { + return x.Hashes + } + return nil +} + +type BlockRef struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Hash []byte `protobuf:"bytes,1,opt,name=hash,proto3" json:"hash,omitempty"` + Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` +} + +func (x *BlockRef) Reset() { + *x = BlockRef{} + if protoimpl.UnsafeEnabled { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *BlockRef) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*BlockRef) ProtoMessage() {} + +func (x *BlockRef) ProtoReflect() protoreflect.Message { + mi := &file_sf_ethereum_type_v2_type_proto_msgTypes[20] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use BlockRef.ProtoReflect.Descriptor instead. +func (*BlockRef) Descriptor() ([]byte, []int) { + return file_sf_ethereum_type_v2_type_proto_rawDescGZIP(), []int{20} +} + +func (x *BlockRef) GetHash() []byte { + if x != nil { + return x.Hash + } + return nil +} + +func (x *BlockRef) GetNumber() uint64 { + if x != nil { + return x.Number + } + return 0 +} + +var File_sf_ethereum_type_v2_type_proto protoreflect.FileDescriptor + +var file_sf_ethereum_type_v2_type_proto_rawDesc = []byte{ + 0x0a, 0x1e, 0x73, 0x66, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x74, 0x79, + 0x70, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x13, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x2e, 0x76, 0x32, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd0, 0x04, 0x0a, 0x05, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, + 0x12, 0x38, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x06, 0x75, 0x6e, + 0x63, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x75, 0x6e, + 0x63, 0x6c, 0x65, 0x73, 0x12, 0x54, 0x0a, 0x12, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0f, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x0b, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x49, 0x0a, 0x0c, 0x64, 0x65, 0x74, 0x61, 0x69, + 0x6c, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, + 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x44, 0x65, 0x74, 0x61, 0x69, 0x6c, + 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x52, 0x0b, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x4c, 0x65, 0x76, + 0x65, 0x6c, 0x12, 0x42, 0x0a, 0x0c, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, + 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x63, 0x6f, 0x64, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x76, 0x65, 0x72, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x76, 0x65, 0x72, 0x22, 0x3d, 0x0a, 0x0b, 0x44, 0x65, 0x74, 0x61, + 0x69, 0x6c, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x14, 0x44, 0x45, 0x54, 0x41, 0x49, + 0x4c, 0x4c, 0x45, 0x56, 0x45, 0x4c, 0x5f, 0x45, 0x58, 0x54, 0x45, 0x4e, 0x44, 0x45, 0x44, 0x10, + 0x00, 0x12, 0x14, 0x0a, 0x10, 0x44, 0x45, 0x54, 0x41, 0x49, 0x4c, 0x4c, 0x45, 0x56, 0x45, 0x4c, + 0x5f, 0x42, 0x41, 0x53, 0x45, 0x10, 0x02, 0x4a, 0x04, 0x08, 0x28, 0x10, 0x29, 0x4a, 0x04, 0x08, + 0x29, 0x10, 0x2a, 0x4a, 0x04, 0x08, 0x2a, 0x10, 0x2b, 0x22, 0xa8, 0x06, 0x0a, 0x0b, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x70, 0x61, 0x72, + 0x65, 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, + 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x75, 0x6e, + 0x63, 0x6c, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, + 0x75, 0x6e, 0x63, 0x6c, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x6f, 0x69, + 0x6e, 0x62, 0x61, 0x73, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x63, 0x6f, 0x69, + 0x6e, 0x62, 0x61, 0x73, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, + 0x6f, 0x6f, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, + 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x10, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x6f, 0x6f, + 0x74, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x5f, 0x72, 0x6f, 0x6f, + 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, + 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x62, 0x6c, 0x6f, + 0x6f, 0x6d, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x6c, 0x6f, 0x67, 0x73, 0x42, 0x6c, + 0x6f, 0x6f, 0x6d, 0x12, 0x3b, 0x0a, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, + 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, + 0x67, 0x49, 0x6e, 0x74, 0x52, 0x0a, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, + 0x12, 0x46, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, + 0x75, 0x6c, 0x74, 0x79, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, + 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x0a, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x19, 0x0a, + 0x08, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x72, 0x61, 0x5f, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x65, 0x78, 0x74, 0x72, 0x61, 0x44, 0x61, 0x74, + 0x61, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x69, 0x78, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x0e, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x69, 0x78, 0x48, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, + 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x44, 0x0a, 0x10, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x66, + 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x0d, 0x62, + 0x61, 0x73, 0x65, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x29, 0x0a, 0x10, + 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x73, 0x5f, 0x72, 0x6f, 0x6f, 0x74, + 0x18, 0x13, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, + 0x61, 0x6c, 0x73, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x4b, 0x0a, 0x0d, 0x74, 0x78, 0x5f, 0x64, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, + 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x4e, 0x65, 0x73, 0x74, 0x65, + 0x64, 0x41, 0x72, 0x72, 0x61, 0x79, 0x52, 0x0c, 0x74, 0x78, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, + 0x65, 0x6e, 0x63, 0x79, 0x22, 0x47, 0x0a, 0x11, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x4e, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x32, 0x0a, 0x03, 0x76, 0x61, 0x6c, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x55, 0x69, 0x6e, + 0x74, 0x36, 0x34, 0x41, 0x72, 0x72, 0x61, 0x79, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1f, 0x0a, + 0x0b, 0x55, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x41, 0x72, 0x72, 0x61, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x76, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x03, 0x28, 0x04, 0x52, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x1e, + 0x0a, 0x06, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x79, 0x74, 0x65, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x62, 0x79, 0x74, 0x65, 0x73, 0x22, 0xea, + 0x09, 0x0a, 0x10, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x74, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x02, 0x74, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x05, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x67, 0x61, 0x73, + 0x5f, 0x70, 0x72, 0x69, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x08, 0x67, 0x61, 0x73, 0x50, 0x72, + 0x69, 0x63, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x31, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x0c, 0x0a, 0x01, 0x76, 0x18, 0x07, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x01, 0x76, 0x12, 0x0c, 0x0a, 0x01, 0x72, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x01, 0x72, 0x12, 0x0c, 0x0a, 0x01, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x01, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x67, 0x61, 0x73, 0x5f, 0x75, 0x73, 0x65, 0x64, 0x18, + 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x67, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x3e, + 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, + 0x61, 0x63, 0x65, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x41, + 0x0a, 0x0b, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x0e, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x54, 0x75, 0x70, 0x6c, 0x65, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x42, 0x0a, 0x0f, 0x6d, 0x61, 0x78, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, + 0x5f, 0x67, 0x61, 0x73, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x46, 0x65, 0x65, 0x50, + 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x53, 0x0a, 0x18, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x72, 0x69, + 0x6f, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x66, 0x65, 0x65, 0x5f, 0x70, 0x65, 0x72, 0x5f, 0x67, 0x61, + 0x73, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, + 0x67, 0x49, 0x6e, 0x74, 0x52, 0x14, 0x6d, 0x61, 0x78, 0x50, 0x72, 0x69, 0x6f, 0x72, 0x69, 0x74, + 0x79, 0x46, 0x65, 0x65, 0x50, 0x65, 0x72, 0x47, 0x61, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, + 0x64, 0x65, 0x78, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x16, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x75, + 0x72, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x65, 0x67, 0x69, + 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x19, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0c, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x4f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, + 0x0b, 0x65, 0x6e, 0x64, 0x5f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x1a, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x0a, 0x65, 0x6e, 0x64, 0x4f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x43, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, + 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x41, 0x0a, 0x07, 0x72, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x18, 0x1f, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, + 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x52, 0x07, 0x72, + 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x2f, 0x0a, 0x05, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x18, + 0x20, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, + 0x52, 0x05, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x22, 0xb1, 0x02, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x13, 0x0a, 0x0f, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4c, 0x45, 0x47, + 0x41, 0x43, 0x59, 0x10, 0x00, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x41, 0x43, 0x43, 0x45, 0x53, 0x53, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x01, 0x12, + 0x18, 0x0a, 0x14, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x59, 0x4e, 0x41, + 0x4d, 0x49, 0x43, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x02, 0x12, 0x1d, 0x0a, 0x19, 0x54, 0x52, 0x58, + 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x44, + 0x45, 0x50, 0x4f, 0x53, 0x49, 0x54, 0x10, 0x64, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x52, 0x58, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x55, 0x4e, + 0x53, 0x49, 0x47, 0x4e, 0x45, 0x44, 0x10, 0x65, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x52, 0x58, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x43, 0x4f, + 0x4e, 0x54, 0x52, 0x41, 0x43, 0x54, 0x10, 0x66, 0x12, 0x1b, 0x0a, 0x17, 0x54, 0x52, 0x58, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x52, 0x45, + 0x54, 0x52, 0x59, 0x10, 0x68, 0x12, 0x26, 0x0a, 0x22, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, 0x55, 0x4d, 0x5f, 0x53, 0x55, 0x42, 0x4d, 0x49, + 0x54, 0x5f, 0x52, 0x45, 0x54, 0x52, 0x59, 0x41, 0x42, 0x4c, 0x45, 0x10, 0x69, 0x12, 0x1e, 0x0a, + 0x1a, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, + 0x55, 0x4d, 0x5f, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x10, 0x6a, 0x12, 0x1c, 0x0a, + 0x18, 0x54, 0x52, 0x58, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x52, 0x42, 0x49, 0x54, 0x52, + 0x55, 0x4d, 0x5f, 0x4c, 0x45, 0x47, 0x41, 0x43, 0x59, 0x10, 0x78, 0x22, 0x4a, 0x0a, 0x0b, 0x41, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x54, 0x75, 0x70, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, + 0x6b, 0x65, 0x79, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0b, 0x73, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x4b, 0x65, 0x79, 0x73, 0x22, 0xb0, 0x01, 0x0a, 0x12, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x12, 0x1d, + 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x09, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x2e, 0x0a, + 0x13, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x67, 0x61, 0x73, 0x5f, + 0x75, 0x73, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x63, 0x75, 0x6d, 0x75, + 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x47, 0x61, 0x73, 0x55, 0x73, 0x65, 0x64, 0x12, 0x1d, 0x0a, + 0x0a, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x09, 0x6c, 0x6f, 0x67, 0x73, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x12, 0x2c, 0x0a, 0x04, + 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x22, 0x9b, 0x01, 0x0a, 0x03, 0x4c, + 0x6f, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, + 0x74, 0x6f, 0x70, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x06, 0x74, 0x6f, + 0x70, 0x69, 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, + 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1e, + 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x18, + 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0xb2, 0x0a, 0x0a, 0x04, 0x43, 0x61, 0x6c, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x61, 0x72, 0x65, 0x6e, + 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x70, + 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x14, 0x0a, 0x05, 0x64, 0x65, + 0x70, 0x74, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x64, 0x65, 0x70, 0x74, 0x68, + 0x12, 0x3a, 0x0a, 0x09, 0x63, 0x61, 0x6c, 0x6c, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1d, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x54, 0x79, + 0x70, 0x65, 0x52, 0x08, 0x63, 0x61, 0x6c, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, + 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x61, + 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x31, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x67, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x67, 0x61, 0x73, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x21, + 0x0a, 0x0c, 0x67, 0x61, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x18, 0x09, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x67, 0x61, 0x73, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, + 0x64, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x5f, 0x64, 0x61, 0x74, 0x61, + 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0c, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x73, 0x75, 0x69, 0x63, 0x69, 0x64, 0x65, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x73, 0x75, 0x69, 0x63, 0x69, 0x64, 0x65, 0x12, 0x59, 0x0a, 0x10, 0x6b, 0x65, 0x63, 0x63, 0x61, + 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x14, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x2e, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x61, 0x6c, 0x6c, 0x2e, 0x4b, 0x65, 0x63, + 0x63, 0x61, 0x6b, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x52, 0x0f, 0x6b, 0x65, 0x63, 0x63, 0x61, 0x6b, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, + 0x65, 0x73, 0x12, 0x4b, 0x0a, 0x0f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x15, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, + 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, + 0x32, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x0e, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, + 0x4b, 0x0a, 0x0f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x18, 0x16, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, + 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0e, 0x62, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x45, 0x0a, 0x0d, + 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x18, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0c, 0x6e, 0x6f, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, + 0x67, 0x65, 0x73, 0x12, 0x2c, 0x0a, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x19, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x4c, 0x6f, 0x67, 0x52, 0x04, 0x6c, 0x6f, 0x67, + 0x73, 0x12, 0x42, 0x0a, 0x0c, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x18, 0x1a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x43, 0x6f, + 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0b, 0x63, 0x6f, 0x64, 0x65, 0x43, 0x68, + 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x0b, 0x67, 0x61, 0x73, 0x5f, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x73, 0x18, 0x1c, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x66, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, + 0x2e, 0x47, 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x67, 0x61, 0x73, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x5f, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x12, 0x27, 0x0a, 0x0f, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x0c, + 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x76, 0x65, + 0x72, 0x74, 0x65, 0x64, 0x12, 0x25, 0x0a, 0x0e, 0x66, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x5f, + 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x66, 0x61, + 0x69, 0x6c, 0x75, 0x72, 0x65, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x25, 0x0a, 0x0e, 0x73, + 0x74, 0x61, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x18, 0x1e, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x74, 0x65, 0x52, 0x65, 0x76, 0x65, 0x72, 0x74, + 0x65, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x5f, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x6c, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x62, 0x65, 0x67, 0x69, 0x6e, + 0x4f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x65, 0x6e, 0x64, 0x5f, 0x6f, + 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x20, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x65, 0x6e, + 0x64, 0x4f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x51, 0x0a, 0x11, 0x61, 0x63, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x5f, 0x63, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x21, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, + 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, + 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x10, 0x61, 0x63, 0x63, 0x6f, 0x75, + 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x42, 0x0a, 0x14, 0x4b, + 0x65, 0x63, 0x63, 0x61, 0x6b, 0x50, 0x72, 0x65, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, + 0x04, 0x08, 0x1b, 0x10, 0x1c, 0x4a, 0x04, 0x08, 0x1d, 0x10, 0x1e, 0x4a, 0x04, 0x08, 0x32, 0x10, + 0x33, 0x4a, 0x04, 0x08, 0x33, 0x10, 0x34, 0x4a, 0x04, 0x08, 0x3c, 0x10, 0x3d, 0x22, 0x8f, 0x01, + 0x0a, 0x0d, 0x53, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1b, 0x0a, 0x09, 0x6f, + 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, + 0x6f, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6e, 0x65, 0x77, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, + 0xcc, 0x05, 0x0a, 0x0d, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x38, 0x0a, 0x09, 0x6f, + 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, + 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x6c, 0x64, + 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, + 0x69, 0x67, 0x49, 0x6e, 0x74, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, + 0x41, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, + 0x29, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0xcf, 0x03, 0x0a, + 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x1c, 0x0a, 0x18, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x4d, 0x49, 0x4e, + 0x45, 0x5f, 0x55, 0x4e, 0x43, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x4d, 0x49, 0x4e, 0x45, 0x5f, + 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x10, 0x02, 0x12, 0x1e, 0x0a, 0x1a, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x44, 0x41, 0x4f, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, 0x5f, 0x43, 0x4f, 0x4e, + 0x54, 0x52, 0x41, 0x43, 0x54, 0x10, 0x03, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x44, 0x41, 0x4f, 0x5f, 0x41, 0x44, 0x4a, 0x55, 0x53, 0x54, 0x5f, 0x42, 0x41, 0x4c, + 0x41, 0x4e, 0x43, 0x45, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x46, 0x45, 0x52, 0x10, 0x05, 0x12, 0x1a, 0x0a, 0x16, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x47, 0x45, 0x4e, 0x45, 0x53, 0x49, 0x53, 0x5f, 0x42, 0x41, + 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x06, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x42, 0x55, 0x59, 0x10, 0x07, 0x12, 0x21, 0x0a, 0x1d, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, 0x54, 0x52, 0x41, + 0x4e, 0x53, 0x41, 0x43, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x46, 0x45, 0x45, 0x10, 0x08, 0x12, 0x1b, + 0x0a, 0x17, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x57, 0x41, 0x52, 0x44, 0x5f, + 0x46, 0x45, 0x45, 0x5f, 0x52, 0x45, 0x53, 0x45, 0x54, 0x10, 0x0e, 0x12, 0x15, 0x0a, 0x11, 0x52, + 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x47, 0x41, 0x53, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, + 0x10, 0x09, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x4f, 0x55, + 0x43, 0x48, 0x5f, 0x41, 0x43, 0x43, 0x4f, 0x55, 0x4e, 0x54, 0x10, 0x0a, 0x12, 0x19, 0x0a, 0x15, + 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x55, 0x49, 0x43, 0x49, 0x44, 0x45, 0x5f, 0x52, + 0x45, 0x46, 0x55, 0x4e, 0x44, 0x10, 0x0b, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x53, 0x55, 0x49, 0x43, 0x49, 0x44, 0x45, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x44, 0x52, + 0x41, 0x57, 0x10, 0x0d, 0x12, 0x20, 0x0a, 0x1c, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, + 0x41, 0x4c, 0x4c, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x4f, 0x56, 0x45, 0x52, + 0x52, 0x49, 0x44, 0x45, 0x10, 0x0c, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x42, 0x55, 0x52, 0x4e, 0x10, 0x0f, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x57, 0x49, 0x54, 0x48, 0x44, 0x52, 0x41, 0x57, 0x41, 0x4c, 0x10, 0x10, 0x22, 0x7b, + 0x0a, 0x0b, 0x4e, 0x6f, 0x6e, 0x63, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x18, 0x0a, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, + 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x22, 0x45, 0x0a, 0x0f, 0x41, + 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x43, 0x72, 0x65, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x22, 0xac, 0x01, 0x0a, 0x0a, 0x43, 0x6f, 0x64, 0x65, 0x43, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x6f, + 0x6c, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, + 0x6c, 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6c, 0x64, 0x5f, 0x63, 0x6f, + 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6f, 0x6c, 0x64, 0x43, 0x6f, 0x64, + 0x65, 0x12, 0x19, 0x0a, 0x08, 0x6e, 0x65, 0x77, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6e, 0x65, 0x77, 0x48, 0x61, 0x73, 0x68, 0x12, 0x19, 0x0a, 0x08, + 0x6e, 0x65, 0x77, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, + 0x6e, 0x65, 0x77, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, + 0x6c, 0x22, 0xe0, 0x06, 0x0a, 0x09, 0x47, 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, + 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x09, + 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x72, 0x65, 0x61, + 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x65, + 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, + 0x47, 0x61, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x61, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x61, 0x6c, 0x22, 0xbf, 0x05, 0x0a, 0x06, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, + 0x0e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, + 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, + 0x10, 0x01, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, + 0x4c, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x52, 0x45, 0x41, 0x53, + 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x43, 0x4f, 0x50, + 0x59, 0x10, 0x03, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, + 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x50, 0x59, 0x10, 0x04, 0x12, 0x17, 0x0a, 0x13, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x54, 0x4f, 0x52, 0x41, 0x47, 0x45, + 0x10, 0x05, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x4e, + 0x54, 0x52, 0x41, 0x43, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x06, + 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, + 0x41, 0x43, 0x54, 0x5f, 0x43, 0x52, 0x45, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x32, 0x10, 0x07, 0x12, + 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x44, 0x45, 0x4c, 0x45, 0x47, 0x41, + 0x54, 0x45, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x08, 0x12, 0x14, 0x0a, 0x10, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x09, 0x12, + 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x45, 0x58, 0x54, 0x5f, 0x43, 0x4f, + 0x44, 0x45, 0x5f, 0x43, 0x4f, 0x50, 0x59, 0x10, 0x0a, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, + 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x0b, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x49, 0x4e, 0x54, 0x52, 0x49, 0x4e, 0x53, 0x49, 0x43, 0x5f, 0x47, 0x41, 0x53, 0x10, 0x0c, + 0x12, 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x50, 0x52, 0x45, 0x43, 0x4f, + 0x4d, 0x50, 0x49, 0x4c, 0x45, 0x44, 0x5f, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x54, 0x10, + 0x0d, 0x12, 0x21, 0x0a, 0x1d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, 0x45, 0x46, 0x55, + 0x4e, 0x44, 0x5f, 0x41, 0x46, 0x54, 0x45, 0x52, 0x5f, 0x45, 0x58, 0x45, 0x43, 0x55, 0x54, 0x49, + 0x4f, 0x4e, 0x10, 0x0e, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, + 0x45, 0x54, 0x55, 0x52, 0x4e, 0x10, 0x0f, 0x12, 0x1b, 0x0a, 0x17, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x5f, 0x44, 0x41, 0x54, 0x41, 0x5f, 0x43, 0x4f, + 0x50, 0x59, 0x10, 0x10, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x52, + 0x45, 0x56, 0x45, 0x52, 0x54, 0x10, 0x11, 0x12, 0x18, 0x0a, 0x14, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x53, 0x45, 0x4c, 0x46, 0x5f, 0x44, 0x45, 0x53, 0x54, 0x52, 0x55, 0x43, 0x54, 0x10, + 0x12, 0x12, 0x16, 0x0a, 0x12, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, + 0x49, 0x43, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x13, 0x12, 0x1c, 0x0a, 0x18, 0x52, 0x45, 0x41, + 0x53, 0x4f, 0x4e, 0x5f, 0x53, 0x54, 0x41, 0x54, 0x45, 0x5f, 0x43, 0x4f, 0x4c, 0x44, 0x5f, 0x41, + 0x43, 0x43, 0x45, 0x53, 0x53, 0x10, 0x14, 0x12, 0x1d, 0x0a, 0x19, 0x52, 0x45, 0x41, 0x53, 0x4f, + 0x4e, 0x5f, 0x54, 0x58, 0x5f, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x4c, + 0x41, 0x4e, 0x43, 0x45, 0x10, 0x15, 0x12, 0x15, 0x0a, 0x11, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, + 0x5f, 0x54, 0x58, 0x5f, 0x52, 0x45, 0x46, 0x55, 0x4e, 0x44, 0x53, 0x10, 0x16, 0x12, 0x20, 0x0a, + 0x1c, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x54, 0x58, 0x5f, 0x4c, 0x45, 0x46, 0x54, 0x5f, + 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, 0x45, 0x44, 0x10, 0x17, 0x12, + 0x1f, 0x0a, 0x1b, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, 0x49, + 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x42, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x45, 0x10, 0x18, + 0x12, 0x22, 0x0a, 0x1e, 0x52, 0x45, 0x41, 0x53, 0x4f, 0x4e, 0x5f, 0x43, 0x41, 0x4c, 0x4c, 0x5f, + 0x4c, 0x45, 0x46, 0x54, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x52, 0x45, 0x54, 0x55, 0x52, 0x4e, + 0x45, 0x44, 0x10, 0x19, 0x22, 0x4b, 0x0a, 0x0f, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x4f, 0x6e, + 0x6c, 0x79, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x38, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, + 0x72, 0x22, 0xd1, 0x01, 0x0a, 0x0d, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x57, 0x69, 0x74, 0x68, 0x52, + 0x65, 0x66, 0x73, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x02, 0x69, 0x64, 0x12, 0x30, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, + 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x5a, 0x0a, 0x16, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x66, 0x73, 0x52, 0x14, 0x74, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x65, 0x66, + 0x73, 0x12, 0x22, 0x0a, 0x0c, 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x22, 0x97, 0x01, 0x0a, 0x1c, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x57, 0x69, 0x74, 0x68, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x66, 0x12, 0x3b, 0x0a, 0x05, 0x74, 0x72, 0x61, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x52, 0x05, 0x74, 0x72, + 0x61, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x72, 0x65, 0x66, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, + 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x76, 0x32, 0x2e, 0x42, 0x6c, 0x6f, + 0x63, 0x6b, 0x52, 0x65, 0x66, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x66, 0x22, + 0x29, 0x0a, 0x0f, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x66, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0c, 0x52, 0x06, 0x68, 0x61, 0x73, 0x68, 0x65, 0x73, 0x22, 0x36, 0x0a, 0x08, 0x42, 0x6c, + 0x6f, 0x63, 0x6b, 0x52, 0x65, 0x66, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, + 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x2a, 0x4e, 0x0a, 0x16, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x54, 0x72, 0x61, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, + 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x55, 0x43, + 0x43, 0x45, 0x45, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, + 0x45, 0x44, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x52, 0x45, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, + 0x10, 0x03, 0x2a, 0x59, 0x0a, 0x08, 0x43, 0x61, 0x6c, 0x6c, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0f, + 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, + 0x08, 0x0a, 0x04, 0x43, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x43, 0x41, 0x4c, + 0x4c, 0x43, 0x4f, 0x44, 0x45, 0x10, 0x02, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x45, 0x4c, 0x45, 0x47, + 0x41, 0x54, 0x45, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x54, 0x41, 0x54, 0x49, 0x43, 0x10, + 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x05, 0x42, 0x4f, 0x5a, + 0x4d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x73, 0x74, 0x2f, 0x66, 0x69, 0x72, 0x65, 0x68, 0x6f, + 0x73, 0x65, 0x2d, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x66, 0x2f, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, + 0x2f, 0x74, 0x79, 0x70, 0x65, 0x2f, 0x76, 0x32, 0x3b, 0x70, 0x62, 0x65, 0x74, 0x68, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_sf_ethereum_type_v2_type_proto_rawDescOnce sync.Once + file_sf_ethereum_type_v2_type_proto_rawDescData = file_sf_ethereum_type_v2_type_proto_rawDesc +) + +func file_sf_ethereum_type_v2_type_proto_rawDescGZIP() []byte { + file_sf_ethereum_type_v2_type_proto_rawDescOnce.Do(func() { + file_sf_ethereum_type_v2_type_proto_rawDescData = protoimpl.X.CompressGZIP(file_sf_ethereum_type_v2_type_proto_rawDescData) + }) + return file_sf_ethereum_type_v2_type_proto_rawDescData +} + +var file_sf_ethereum_type_v2_type_proto_enumTypes = make([]protoimpl.EnumInfo, 6) +var file_sf_ethereum_type_v2_type_proto_msgTypes = make([]protoimpl.MessageInfo, 22) +var file_sf_ethereum_type_v2_type_proto_goTypes = []interface{}{ + (TransactionTraceStatus)(0), // 0: sf.ethereum.type.v2.TransactionTraceStatus + (CallType)(0), // 1: sf.ethereum.type.v2.CallType + (Block_DetailLevel)(0), // 2: sf.ethereum.type.v2.Block.DetailLevel + (TransactionTrace_Type)(0), // 3: sf.ethereum.type.v2.TransactionTrace.Type + (BalanceChange_Reason)(0), // 4: sf.ethereum.type.v2.BalanceChange.Reason + (GasChange_Reason)(0), // 5: sf.ethereum.type.v2.GasChange.Reason + (*Block)(nil), // 6: sf.ethereum.type.v2.Block + (*BlockHeader)(nil), // 7: sf.ethereum.type.v2.BlockHeader + (*Uint64NestedArray)(nil), // 8: sf.ethereum.type.v2.Uint64NestedArray + (*Uint64Array)(nil), // 9: sf.ethereum.type.v2.Uint64Array + (*BigInt)(nil), // 10: sf.ethereum.type.v2.BigInt + (*TransactionTrace)(nil), // 11: sf.ethereum.type.v2.TransactionTrace + (*AccessTuple)(nil), // 12: sf.ethereum.type.v2.AccessTuple + (*TransactionReceipt)(nil), // 13: sf.ethereum.type.v2.TransactionReceipt + (*Log)(nil), // 14: sf.ethereum.type.v2.Log + (*Call)(nil), // 15: sf.ethereum.type.v2.Call + (*StorageChange)(nil), // 16: sf.ethereum.type.v2.StorageChange + (*BalanceChange)(nil), // 17: sf.ethereum.type.v2.BalanceChange + (*NonceChange)(nil), // 18: sf.ethereum.type.v2.NonceChange + (*AccountCreation)(nil), // 19: sf.ethereum.type.v2.AccountCreation + (*CodeChange)(nil), // 20: sf.ethereum.type.v2.CodeChange + (*GasChange)(nil), // 21: sf.ethereum.type.v2.GasChange + (*HeaderOnlyBlock)(nil), // 22: sf.ethereum.type.v2.HeaderOnlyBlock + (*BlockWithRefs)(nil), // 23: sf.ethereum.type.v2.BlockWithRefs + (*TransactionTraceWithBlockRef)(nil), // 24: sf.ethereum.type.v2.TransactionTraceWithBlockRef + (*TransactionRefs)(nil), // 25: sf.ethereum.type.v2.TransactionRefs + (*BlockRef)(nil), // 26: sf.ethereum.type.v2.BlockRef + nil, // 27: sf.ethereum.type.v2.Call.KeccakPreimagesEntry + (*timestamppb.Timestamp)(nil), // 28: google.protobuf.Timestamp +} +var file_sf_ethereum_type_v2_type_proto_depIdxs = []int32{ + 7, // 0: sf.ethereum.type.v2.Block.header:type_name -> sf.ethereum.type.v2.BlockHeader + 7, // 1: sf.ethereum.type.v2.Block.uncles:type_name -> sf.ethereum.type.v2.BlockHeader + 11, // 2: sf.ethereum.type.v2.Block.transaction_traces:type_name -> sf.ethereum.type.v2.TransactionTrace + 17, // 3: sf.ethereum.type.v2.Block.balance_changes:type_name -> sf.ethereum.type.v2.BalanceChange + 2, // 4: sf.ethereum.type.v2.Block.detail_level:type_name -> sf.ethereum.type.v2.Block.DetailLevel + 20, // 5: sf.ethereum.type.v2.Block.code_changes:type_name -> sf.ethereum.type.v2.CodeChange + 10, // 6: sf.ethereum.type.v2.BlockHeader.difficulty:type_name -> sf.ethereum.type.v2.BigInt + 10, // 7: sf.ethereum.type.v2.BlockHeader.total_difficulty:type_name -> sf.ethereum.type.v2.BigInt + 28, // 8: sf.ethereum.type.v2.BlockHeader.timestamp:type_name -> google.protobuf.Timestamp + 10, // 9: sf.ethereum.type.v2.BlockHeader.base_fee_per_gas:type_name -> sf.ethereum.type.v2.BigInt + 8, // 10: sf.ethereum.type.v2.BlockHeader.tx_dependency:type_name -> sf.ethereum.type.v2.Uint64NestedArray + 9, // 11: sf.ethereum.type.v2.Uint64NestedArray.val:type_name -> sf.ethereum.type.v2.Uint64Array + 10, // 12: sf.ethereum.type.v2.TransactionTrace.gas_price:type_name -> sf.ethereum.type.v2.BigInt + 10, // 13: sf.ethereum.type.v2.TransactionTrace.value:type_name -> sf.ethereum.type.v2.BigInt + 3, // 14: sf.ethereum.type.v2.TransactionTrace.type:type_name -> sf.ethereum.type.v2.TransactionTrace.Type + 12, // 15: sf.ethereum.type.v2.TransactionTrace.access_list:type_name -> sf.ethereum.type.v2.AccessTuple + 10, // 16: sf.ethereum.type.v2.TransactionTrace.max_fee_per_gas:type_name -> sf.ethereum.type.v2.BigInt + 10, // 17: sf.ethereum.type.v2.TransactionTrace.max_priority_fee_per_gas:type_name -> sf.ethereum.type.v2.BigInt + 0, // 18: sf.ethereum.type.v2.TransactionTrace.status:type_name -> sf.ethereum.type.v2.TransactionTraceStatus + 13, // 19: sf.ethereum.type.v2.TransactionTrace.receipt:type_name -> sf.ethereum.type.v2.TransactionReceipt + 15, // 20: sf.ethereum.type.v2.TransactionTrace.calls:type_name -> sf.ethereum.type.v2.Call + 14, // 21: sf.ethereum.type.v2.TransactionReceipt.logs:type_name -> sf.ethereum.type.v2.Log + 1, // 22: sf.ethereum.type.v2.Call.call_type:type_name -> sf.ethereum.type.v2.CallType + 10, // 23: sf.ethereum.type.v2.Call.value:type_name -> sf.ethereum.type.v2.BigInt + 27, // 24: sf.ethereum.type.v2.Call.keccak_preimages:type_name -> sf.ethereum.type.v2.Call.KeccakPreimagesEntry + 16, // 25: sf.ethereum.type.v2.Call.storage_changes:type_name -> sf.ethereum.type.v2.StorageChange + 17, // 26: sf.ethereum.type.v2.Call.balance_changes:type_name -> sf.ethereum.type.v2.BalanceChange + 18, // 27: sf.ethereum.type.v2.Call.nonce_changes:type_name -> sf.ethereum.type.v2.NonceChange + 14, // 28: sf.ethereum.type.v2.Call.logs:type_name -> sf.ethereum.type.v2.Log + 20, // 29: sf.ethereum.type.v2.Call.code_changes:type_name -> sf.ethereum.type.v2.CodeChange + 21, // 30: sf.ethereum.type.v2.Call.gas_changes:type_name -> sf.ethereum.type.v2.GasChange + 19, // 31: sf.ethereum.type.v2.Call.account_creations:type_name -> sf.ethereum.type.v2.AccountCreation + 10, // 32: sf.ethereum.type.v2.BalanceChange.old_value:type_name -> sf.ethereum.type.v2.BigInt + 10, // 33: sf.ethereum.type.v2.BalanceChange.new_value:type_name -> sf.ethereum.type.v2.BigInt + 4, // 34: sf.ethereum.type.v2.BalanceChange.reason:type_name -> sf.ethereum.type.v2.BalanceChange.Reason + 5, // 35: sf.ethereum.type.v2.GasChange.reason:type_name -> sf.ethereum.type.v2.GasChange.Reason + 7, // 36: sf.ethereum.type.v2.HeaderOnlyBlock.header:type_name -> sf.ethereum.type.v2.BlockHeader + 6, // 37: sf.ethereum.type.v2.BlockWithRefs.block:type_name -> sf.ethereum.type.v2.Block + 25, // 38: sf.ethereum.type.v2.BlockWithRefs.transaction_trace_refs:type_name -> sf.ethereum.type.v2.TransactionRefs + 11, // 39: sf.ethereum.type.v2.TransactionTraceWithBlockRef.trace:type_name -> sf.ethereum.type.v2.TransactionTrace + 26, // 40: sf.ethereum.type.v2.TransactionTraceWithBlockRef.block_ref:type_name -> sf.ethereum.type.v2.BlockRef + 41, // [41:41] is the sub-list for method output_type + 41, // [41:41] is the sub-list for method input_type + 41, // [41:41] is the sub-list for extension type_name + 41, // [41:41] is the sub-list for extension extendee + 0, // [0:41] is the sub-list for field type_name +} + +func init() { file_sf_ethereum_type_v2_type_proto_init() } +func file_sf_ethereum_type_v2_type_proto_init() { + if File_sf_ethereum_type_v2_type_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_sf_ethereum_type_v2_type_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Block); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockHeader); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Uint64NestedArray); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Uint64Array); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BigInt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionTrace); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AccessTuple); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionReceipt); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Log); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Call); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*StorageChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BalanceChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NonceChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*AccountCreation); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CodeChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GasChange); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*HeaderOnlyBlock); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockWithRefs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionTraceWithBlockRef); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TransactionRefs); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_sf_ethereum_type_v2_type_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*BlockRef); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_sf_ethereum_type_v2_type_proto_rawDesc, + NumEnums: 6, + NumMessages: 22, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_sf_ethereum_type_v2_type_proto_goTypes, + DependencyIndexes: file_sf_ethereum_type_v2_type_proto_depIdxs, + EnumInfos: file_sf_ethereum_type_v2_type_proto_enumTypes, + MessageInfos: file_sf_ethereum_type_v2_type_proto_msgTypes, + }.Build() + File_sf_ethereum_type_v2_type_proto = out.File + file_sf_ethereum_type_v2_type_proto_rawDesc = nil + file_sf_ethereum_type_v2_type_proto_goTypes = nil + file_sf_ethereum_type_v2_type_proto_depIdxs = nil +} diff --git a/firehose/firehose-core/types.go b/firehose/firehose-core/types.go new file mode 100644 index 0000000..af051e7 --- /dev/null +++ b/firehose/firehose-core/types.go @@ -0,0 +1,173 @@ +package firecore + +import ( + "fmt" + "time" + + "github.com/spf13/cobra" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + "github.com/streamingfast/bstream/transform" + "github.com/streamingfast/dstore" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/anypb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +// Block represents the chain-specific Protobuf block. Chain specific's block +// model must implement this interface so that Firehose core is able to properly +// marshal/unmarshal your block into/to the Firehose block envelope binary format. +// +// All the methods are prefixed with `GetFirehoseBlock` to avoid any potential +// conflicts with the fields/getters of your chain's block model that would +// prevent you from implementing this interface. +// +// Consumer of your chain's protobuf block model don't need to be aware of those +// details, they are internal Firehose core information that are required to function +// properly. +// +// The value you return for each of those methods must be done respecting Firehose rules +// which are enumarated in the documentation of each method. +type Block interface { + proto.Message + + // GetFirehoseBlockID returns the block ID as a string, usually in the representation + // used by your chain (hex, base58, base64, etc.). The block ID must be unique across + // all blocks that will ever exist on your chain. + GetFirehoseBlockID() string + + // GetFirehoseBlockNumber returns the block number as an unsigned integer. The block + // number could be shared by multiple blocks in which case one is the canonical one + // and the others are forks (resolution of forks is handled by Firehose core later in the + // block processing pipeline). + // + // The value should be sequentially ordered which means that a block with block number 10 + // has come before block 11. Firehose core will deal with block skips without problem though + // (e.g. block 1, is produced then block 3 where block 3's parent is block 1). + GetFirehoseBlockNumber() uint64 + + // GetFirehoseBlockParentID returns the block ID of the parent block as a string. All blocks + // ever produced must have a parent block ID except for the genesis block which is the first + // one. The value must be the same as the one returned by GetFirehoseBlockID() of the parent. + // + // If it's the genesis block, return an empty string. + GetFirehoseBlockParentID() string + + // GetFirehoseBlockParentNumber returns the block number of the parent block as a uint64. + // The value must be the same as the one returned by GetFirehoseBlockNumber() of the parent + // or `0` if the block has no parent + // + // This is useful on chains that have holes. On other chains, this is as simple as "BlockNumber - 1". + GetFirehoseBlockParentNumber() uint64 + + // GetFirehoseBlockTime returns the block timestamp as a time.Time of when the block was + // produced. This should the consensus agreed time of the block. + GetFirehoseBlockTime() time.Time +} + +// BlockLIBNumDerivable is an optional interface that can be implemented by your chain's block model Block +// if the LIB can be derived from the Block model directly. +// +// Implementing this make some Firehose core process more convenient since less configuration are +// necessary. +type BlockLIBNumDerivable interface { + // GetFirehoseBlockLIBNum returns the last irreversible block number as an unsigned integer + // of this block. This is one of the most important piece of information for Firehose core. + // as it determines when "forks" are now stalled and should be removed from memory and it + // drives a bunch of important write processes that will write the block to disk only when the + // block is now irreversible. + // + // The value returned should be the oldest block that should turned to be irreversible when this + // block was produced. Assume for example the current block is 100. If finality rule of a chain + // is that a block become irreversible after 12 blocks has been produced, then the value returned + // in this case should be 88 (100 - 12) which means that when block 100 was produced, block 88 + // can now be considered irreversible. + // + // Irreversibility is chain specific and how the value here is returned depends on the chain. On + // probabilistic irreversible chains, like Bitcoin, the value returned here is usually the current + // block number - where is choosen to be safe enough in all situations (ensure + // that is block number < , then you properly cap to 0). + // + // On deterministic irreversible chains, usually the last irreversible block number if part of the + // consensus and as such should be part of the Protobuf block model somewhere. In those cases, this + // value should be returned here. + GetFirehoseBlockLIBNum() uint64 +} + +var _ BlockLIBNumDerivable = BlockEnveloppe{} + +type BlockEnveloppe struct { + Block + LIBNum uint64 +} + +// GetFirehoseBlockLIBNum implements LIBDerivable. +func (b BlockEnveloppe) GetFirehoseBlockLIBNum() uint64 { + return b.LIBNum +} + +// BlockEncoder is the interface of an object that is going to a chain specific +// block implementing [Block] interface that will be encoded into [bstream.Block] +// type which is the type used by Firehose core to "envelope" the block. +type BlockEncoder interface { + Encode(block Block) (blk *pbbstream.Block, err error) +} + +type BlockEncoderFunc func(block Block) (blk *pbbstream.Block, err error) + +func (f BlockEncoderFunc) Encode(block Block) (blk *pbbstream.Block, err error) { + return f(block) +} + +type CommandExecutor func(cmd *cobra.Command, args []string) (err error) + +func NewBlockEncoder() BlockEncoder { + return BlockEncoderFunc(func(block Block) (blk *pbbstream.Block, err error) { + return EncodeBlock(block) + }) +} + +func EncodeBlock(b Block) (blk *pbbstream.Block, err error) { + v, ok := b.(BlockLIBNumDerivable) + if !ok { + return nil, fmt.Errorf( + "block %T does not implement 'firecore.BlockLIBNumDerivable' which is mandatory, "+ + "if you transmit the LIBNum through a side channel, wrap your block with "+ + "'firecore.BlockEnveloppe{Block: b, LIBNum: }' to send the LIBNum "+ + "to use for encoding ('firecore.BlockEnveloppe' implements 'firecore.BlockLIBNumDerivable')", + b, + ) + } + + anyBlock, err := anypb.New(b) + if err != nil { + return nil, fmt.Errorf("create any block: %w", err) + } + + bstreamBlock := &pbbstream.Block{ + Id: b.GetFirehoseBlockID(), + Number: b.GetFirehoseBlockNumber(), + ParentId: b.GetFirehoseBlockParentID(), + Timestamp: timestamppb.New(b.GetFirehoseBlockTime()), + LibNum: v.GetFirehoseBlockLIBNum(), + Payload: anyBlock, + } + + return bstreamBlock, nil +} + +type BlockIndexerFactory[B Block] func(indexStore dstore.Store, indexSize uint64) (BlockIndexer[B], error) + +type BlockIndexer[B Block] interface { + ProcessBlock(block B) error +} + +// BlockTransformerFactory is a bit convoluted, but yes it's a function acting as a factory that returns itself +// a factory. The reason for this is that the factory needs to be able to access the index store and the index +// size to be able to create the actual factory. +// +// In the context of `firehose-core` transform registration, this function will be called exactly once +// for the overall process. The returns [transform.Factory] will be used multiple times (one per request +// requesting this transform). +type BlockTransformerFactory func(indexStore dstore.Store, indexPossibleSizes []uint64) (*transform.Factory, error) + +type ReaderNodeArgumentResolver = func(in string) string diff --git a/firehose/firehose-core/types/block_range.go b/firehose/firehose-core/types/block_range.go new file mode 100644 index 0000000..a51d3da --- /dev/null +++ b/firehose/firehose-core/types/block_range.go @@ -0,0 +1,146 @@ +package types + +import ( + "fmt" + "math" + + "github.com/streamingfast/bstream" +) + +//go:generate go-enum -f=$GOFILE --marshal --names --nocase + +// ENUM( +// +// Inclusive +// Exclusive +// +// ) +type RangeBoundary int + +const ( + EndBoundaryInclusive RangeBoundary = RangeBoundaryInclusive + EndBoundaryExclusive = RangeBoundaryExclusive +) + +// BlockRange is actually an UnresolvedBlockRange so both the start and end could be +// negative values. +// +// This is in opposition to `bstream.Range` which is a resolved range meaning that start/stop +// values will never be negative. +type BlockRange struct { + Start int64 + Stop *uint64 +} + +func NewOpenRange(start int64) BlockRange { + return BlockRange{Start: int64(start), Stop: nil} +} + +func NewClosedRange(start int64, stop uint64) BlockRange { + return BlockRange{Start: start, Stop: &stop} +} + +// IsResolved returns true if the range is both closed and fully +// resolved (e.g. both start and stop are positive values). Returns +// false otherwise. +func (b BlockRange) IsResolved() bool { + return b.Start >= 0 && b.IsClosed() +} + +func (b BlockRange) IsOpen() bool { + return b.Stop == nil +} + +func (b BlockRange) IsClosed() bool { + return b.Stop != nil +} + +func (b BlockRange) GetStartBlock() int64 { + return b.Start +} + +func (b BlockRange) BlockCount() int64 { + if !b.IsResolved() { + return math.MaxInt64 + } + + return int64(*b.Stop) - b.Start + 1 +} + +func (b BlockRange) GetStopBlockOr(defaultIfOpenRange uint64) uint64 { + if b.IsOpen() { + return defaultIfOpenRange + } + + return *b.Stop +} + +func (b BlockRange) ReprocRange() string { + if !b.IsClosed() { + return "" + } + + if !b.IsResolved() { + return "" + } + + return fmt.Sprintf("%d:%d", b.Start, *b.Stop+1) +} + +func (b BlockRange) Contains(blockNum uint64, endBoundary RangeBoundary) bool { + if blockNum < uint64(b.Start) { + return false + } + + if b.Stop == nil { + return true + } + + endBlock := *b.Stop + if blockNum > endBlock { + return false + } + if endBoundary == RangeBoundaryExclusive && blockNum == endBlock { + return false + } + + return true +} + +func (b BlockRange) Split(chunkSize uint64, endBoundary RangeBoundary) ([]BlockRange, error) { + segments, err := b.ToBstreamRange(endBoundary).Split(chunkSize) + if err != nil { + return nil, fmt.Errorf("splitting ranges: %w", err) + } + + out := make([]BlockRange, len(segments)) + for i, segment := range segments { + out[i] = NewClosedRange(int64(segment.StartBlock()), *segment.EndBlock()) + } + + return out, nil +} + +func (b BlockRange) ToBstreamRange(endBoundary RangeBoundary) *bstream.Range { + if b.Start < 0 { + panic(fmt.Errorf("cannot convert unresolved block range to bstream.Range: %s", b)) + } + + if b.IsOpen() { + return bstream.NewOpenRange(uint64(b.Start)) + } + + if endBoundary == RangeBoundaryExclusive { + return bstream.NewRangeExcludingEnd(uint64(b.Start), *b.Stop) + } + + return bstream.NewInclusiveRange(uint64(b.Start), *b.Stop) +} + +func (b BlockRange) String() string { + if b.IsOpen() { + return fmt.Sprintf("[%s, +∞]", BlockNum(b.Start)) + } + + return fmt.Sprintf("[%s, %s]", BlockNum(b.Start), BlockNum(*b.Stop)) +} diff --git a/firehose/firehose-core/types/block_range_enum.go b/firehose/firehose-core/types/block_range_enum.go new file mode 100644 index 0000000..668a645 --- /dev/null +++ b/firehose/firehose-core/types/block_range_enum.go @@ -0,0 +1,81 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: +// Revision: +// Build Date: +// Built By: + +package types + +import ( + "fmt" + "strings" +) + +const ( + // RangeBoundaryInclusive is a RangeBoundary of type Inclusive. + RangeBoundaryInclusive RangeBoundary = iota + // RangeBoundaryExclusive is a RangeBoundary of type Exclusive. + RangeBoundaryExclusive +) + +const _RangeBoundaryName = "InclusiveExclusive" + +var _RangeBoundaryNames = []string{ + _RangeBoundaryName[0:9], + _RangeBoundaryName[9:18], +} + +// RangeBoundaryNames returns a list of possible string values of RangeBoundary. +func RangeBoundaryNames() []string { + tmp := make([]string, len(_RangeBoundaryNames)) + copy(tmp, _RangeBoundaryNames) + return tmp +} + +var _RangeBoundaryMap = map[RangeBoundary]string{ + RangeBoundaryInclusive: _RangeBoundaryName[0:9], + RangeBoundaryExclusive: _RangeBoundaryName[9:18], +} + +// String implements the Stringer interface. +func (x RangeBoundary) String() string { + if str, ok := _RangeBoundaryMap[x]; ok { + return str + } + return fmt.Sprintf("RangeBoundary(%d)", x) +} + +var _RangeBoundaryValue = map[string]RangeBoundary{ + _RangeBoundaryName[0:9]: RangeBoundaryInclusive, + strings.ToLower(_RangeBoundaryName[0:9]): RangeBoundaryInclusive, + _RangeBoundaryName[9:18]: RangeBoundaryExclusive, + strings.ToLower(_RangeBoundaryName[9:18]): RangeBoundaryExclusive, +} + +// ParseRangeBoundary attempts to convert a string to a RangeBoundary +func ParseRangeBoundary(name string) (RangeBoundary, error) { + if x, ok := _RangeBoundaryValue[name]; ok { + return x, nil + } + // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. + if x, ok := _RangeBoundaryValue[strings.ToLower(name)]; ok { + return x, nil + } + return RangeBoundary(0), fmt.Errorf("%s is not a valid RangeBoundary, try [%s]", name, strings.Join(_RangeBoundaryNames, ", ")) +} + +// MarshalText implements the text marshaller method +func (x RangeBoundary) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method +func (x *RangeBoundary) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseRangeBoundary(name) + if err != nil { + return err + } + *x = tmp + return nil +} diff --git a/firehose/firehose-core/types/flags.go b/firehose/firehose-core/types/flags.go new file mode 100644 index 0000000..53fd793 --- /dev/null +++ b/firehose/firehose-core/types/flags.go @@ -0,0 +1,50 @@ +package types + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" + "github.com/streamingfast/bstream" + "github.com/streamingfast/cli/sflags" +) + +// GetBlockRangeFromArg returns the block range from the given argument or the range +// [HEAD, +∞] if the argument is empty. +func GetBlockRangeFromArg(in string) (out BlockRange, err error) { + return ParseBlockRangeDefault(in, bstream.GetProtocolFirstStreamableBlock, NewOpenRange(-1)) +} + +// GetBlockRangeFromArgDefault returns a block range from a string argument, using the default block range +// `defaultRange` if the input is empty. +func GetBlockRangeFromArgDefault(in string, defaultRange BlockRange) (out BlockRange, err error) { + return ParseBlockRangeDefault(in, bstream.GetProtocolFirstStreamableBlock, defaultRange) +} + +// GetBlockRangeFromFlag returns the block range from the given flag name or the range +// [HEAD, +∞] if the flag is not set. +func GetBlockRangeFromFlag(cmd *cobra.Command, flagName string) (out BlockRange, err error) { + return GetBlockRangeFromFlagDefault(cmd, flagName, NewOpenRange(-1)) +} + +// GetBlockRangeFromFlagDefault returns a block range from a flag, using the default block range +// `defaultRange` if the flag is not set at all. +func GetBlockRangeFromFlagDefault(cmd *cobra.Command, flagName string, defaultRange BlockRange) (out BlockRange, err error) { + stringRange := sflags.MustGetString(cmd, flagName) + + rawRanges := strings.Split(stringRange, ",") + if len(rawRanges) == 0 { + return defaultRange, nil + } + + if len(rawRanges) > 1 { + return out, fmt.Errorf("accepting a single range for now, got %d", len(rawRanges)) + } + + out, err = ParseBlockRangeDefault(rawRanges[0], bstream.GetProtocolFirstStreamableBlock, defaultRange) + if err != nil { + return out, fmt.Errorf("decode range: %w", err) + } + + return +} diff --git a/firehose/firehose-core/types/types.go b/firehose/firehose-core/types/types.go new file mode 100644 index 0000000..e872b13 --- /dev/null +++ b/firehose/firehose-core/types/types.go @@ -0,0 +1,25 @@ +package types + +import ( + "fmt" + "math" + "strings" + + "github.com/dustin/go-humanize" +) + +type BlockNum int64 + +var HeadBlockNum BlockNum = -1 + +func (b BlockNum) String() string { + if b < 0 { + if b == HeadBlockNum { + return "HEAD" + } + + return fmt.Sprintf("HEAD - %d", uint64(math.Abs(float64(b)))) + } + + return "#" + strings.ReplaceAll(humanize.Comma(int64(b)), ",", " ") +} diff --git a/firehose/firehose-core/types/types_test.go b/firehose/firehose-core/types/types_test.go new file mode 100644 index 0000000..01afea7 --- /dev/null +++ b/firehose/firehose-core/types/types_test.go @@ -0,0 +1,25 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBlockRange_String(t *testing.T) { + tests := []struct { + name string + blockRange BlockRange + want string + }{ + {"open range", NewOpenRange(5000), "[#5 000, +∞]"}, + {"open range head", NewOpenRange(-1), "[HEAD, +∞]"}, + {"open range head - 2", NewOpenRange(-2), "[HEAD - 2, +∞]"}, + {"closed range", NewClosedRange(5000, 10000), "[#5 000, #10 000]"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, tt.blockRange.String()) + }) + } +} diff --git a/firehose/firehose-core/types/utils.go b/firehose/firehose-core/types/utils.go new file mode 100644 index 0000000..48ce0cf --- /dev/null +++ b/firehose/firehose-core/types/utils.go @@ -0,0 +1,136 @@ +package types + +import ( + "fmt" + "strconv" + "strings" + + "github.com/dustin/go-humanize" +) + +func RoundToBundleStartBlock(block, fileBlockSize uint64) uint64 { + // From a non-rounded block `1085` and size of `100`, we remove from it the value of + // `modulo % fileblock` (`85`) making it flush (`1000`). + return block - (block % fileBlockSize) +} + +func RoundToBundleEndBlock(block, fileBlockSize uint64) uint64 { + // From a non-rounded block `1085` and size of `100`, we remove from it the value of + // `modulo % fileblock` (`85`) making it flush (`1000`) than adding to it the last + // merged block num value for this size which simply `size - 1` (`99`) giving us + // a resolved formulae of `1085 - (1085 % 100) + (100 - 1) = 1085 - (85) + (99)`. + return block - (block % fileBlockSize) + (fileBlockSize - 1) +} + +func PrettyBlockNum(b uint64) string { + return "#" + strings.ReplaceAll(humanize.Comma(int64(b)), ",", " ") +} + +// Deprecated: use ParseBlockRangeDefault instead and provide the default range when the input is +// empty. +func ParseBlockRange(input string, firstStreamableBlock uint64) (out BlockRange, err error) { + return ParseBlockRangeDefault(input, firstStreamableBlock, NewOpenRange(-1)) +} + +// ParseBlockRangeDefault parses a block range from a string, using the default block range +// `defaultRange` if the input is empty. The input "-1" is interpreted as an open range from [HEAD, +∞]. +// +// The accepted inputs are: +// - ":" (an open range from 0 to +∞) +// - "-1" (an open range from HEAD to +∞, equivalent to "-1:") +// - "123" (a single block leading to a closed range from 123 to 123) +// - "123:456" (a range of blocks) +// - "123:" (a range of blocks from 123 to +∞) +// - ":456" (a range of blocks from to 456) +// - "-1:456" (a range of blocks from HEAD to +∞ (assuming HEAD is before 456)) +func ParseBlockRangeDefault(input string, firstStreamableBlock uint64, defaultRange BlockRange) (out BlockRange, err error) { + if input == "" { + return defaultRange, nil + } + + if input == "-1" { + return NewOpenRange(-1), nil + } + + before, after, rangeHasStartAndStop := strings.Cut(input, ":") + + beforeAsInt64, beforeIsEmpty, beforeIsPositiveRelative, err := parseNumber(before) + if err != nil { + return out, fmt.Errorf("parse number %q: %w", before, err) + } + + afterAsInt64, afterIsEmpty, afterIsPositiveRelative := int64(0), false, false + if rangeHasStartAndStop { + afterAsInt64, afterIsEmpty, afterIsPositiveRelative, err = parseNumber(after) + if err != nil { + return out, fmt.Errorf("parse number %q: %w", after, err) + } + } + + if !rangeHasStartAndStop { + // If there is no `:` we assume it's a stop block value right away + if beforeIsPositiveRelative { + return out, fmt.Errorf("invalid range: a single block cannot be positively relative (so starting with a + sign)") + } + + return NewOpenRange(resolveBlockNumber(int64(beforeAsInt64), int64(firstStreamableBlock), beforeIsEmpty)), nil + } else { + // Otherwise, we have a `:` sign so we assume it's a start/stop range + if beforeIsPositiveRelative { + return out, fmt.Errorf("invalid range: start block of a range cannot be positively relative (so starting with a + sign)") + } + + start := resolveBlockNumber(int64(beforeAsInt64), int64(firstStreamableBlock), beforeIsEmpty) + + if afterIsEmpty { + return BlockRange{Start: start}, nil + } + + if start < 0 && afterIsPositiveRelative { + return out, fmt.Errorf("invalid range: stop block of a range cannot be positively relative (so starting with a + sign) if start position is negative") + } + + if afterAsInt64 < 0 { + if afterAsInt64 == -1 { + return NewOpenRange(start), nil + } + + return out, fmt.Errorf("invalid range: stop block of a range cannot be negative") + } + + stop := uint64(afterAsInt64) + if afterIsPositiveRelative { + stop += uint64(start) + } + + if start >= 0 && uint64(start) > stop { + return out, fmt.Errorf("invalid range: start block %d is above stop block %d (inclusive)", start, stop) + } + + return NewClosedRange(start, stop), nil + } +} + +// parseNumber parses a number and indicates whether the number is relative, meaning it starts with a + +func parseNumber(number string) (numberInt64 int64, numberIsEmpty bool, numberIsPositiveRelative bool, err error) { + if number == "" { + numberIsEmpty = true + return + } + + numberIsPositiveRelative = strings.HasPrefix(number, "+") + numberInt64, err = strconv.ParseInt(strings.TrimPrefix(number, "+"), 0, 64) + if err != nil { + return 0, false, false, fmt.Errorf("invalid block number value: %w", err) + } + + return +} + +func resolveBlockNumber(value int64, defaultIfEmpty int64, valueIsEmpty bool) int64 { + if valueIsEmpty { + return defaultIfEmpty + } + + return value +} diff --git a/firehose/firehose-core/types/utils_test.go b/firehose/firehose-core/types/utils_test.go new file mode 100644 index 0000000..25f4c8b --- /dev/null +++ b/firehose/firehose-core/types/utils_test.go @@ -0,0 +1,70 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// func Test_BlockRange_String(t *testing.T) { +// } + +func Test_readBlockRange(t *testing.T) { + errorIs := func(errString string) require.ErrorAssertionFunc { + return func(tt require.TestingT, err error, i ...interface{}) { + require.EqualError(tt, err, errString, i...) + } + } + + headBlock := int64(HeadBlockNum) + noRange := BlockRange{} + + openRange := NewOpenRange + closedRange := NewClosedRange + + type args struct { + chainFirstStreamableBlock uint64 + blockRangeArg string + } + tests := []struct { + name string + args args + want BlockRange + assertion require.ErrorAssertionFunc + }{ + // Single + {"single empty is full range", args{5, ""}, openRange(headBlock), nil}, + {"single -1 is full range", args{5, "-1"}, openRange(headBlock), nil}, + {"single : is open range from chain genesis", args{5, ":"}, openRange(5), nil}, + {"single is start block", args{5, "11"}, openRange(11), nil}, + {"single is start block and can be negative", args{5, "-2"}, openRange(-2), nil}, + + {"range start, stop", args{5, "10:12"}, closedRange(10, 12), nil}, + {"range , stop", args{5, ":12"}, closedRange(5, 12), nil}, + {"range start, ", args{5, "10:"}, openRange(10), nil}, + {"range start, stop+", args{5, ":+10"}, closedRange(5, 15), nil}, + {"range start, stop+", args{5, "10:+10"}, closedRange(10, 20), nil}, + {"range, equal start & end", args{0, "10:10"}, closedRange(10, 10), nil}, + {"range start, stop == -1", args{5, "10:-1"}, openRange(10), nil}, + + {"error range start, stop-", args{5, "10:-2"}, noRange, errorIs("invalid range: stop block of a range cannot be negative")}, + {"error range start+, ", args{5, "+10:"}, noRange, errorIs("invalid range: start block of a range cannot be positively relative (so starting with a + sign)")}, + {"error range start+, stop", args{5, "+10:20"}, noRange, errorIs("invalid range: start block of a range cannot be positively relative (so starting with a + sign)")}, + {"error single +relative is stop block, start inferred", args{5, "+10"}, noRange, errorIs("invalid range: a single block cannot be positively relative (so starting with a + sign)")}, + {"error range start+, stop+", args{5, "+10:+10"}, noRange, errorIs("invalid range: start block of a range cannot be positively relative (so starting with a + sign)")}, + {"error invalid range, over", args{0, "11:10"}, noRange, errorIs("invalid range: start block 11 is above stop block 10 (inclusive)")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseBlockRange(tt.args.blockRangeArg, tt.args.chainFirstStreamableBlock) + + if tt.assertion == nil { + tt.assertion = require.NoError + } + + tt.assertion(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/firehose/firehose-core/unsafe_extensions.go b/firehose/firehose-core/unsafe_extensions.go new file mode 100644 index 0000000..cd92e8b --- /dev/null +++ b/firehose/firehose-core/unsafe_extensions.go @@ -0,0 +1,21 @@ +package firecore + +import ( + "context" + + "github.com/streamingfast/firehose-core/launcher" + "go.uber.org/zap" +) + +// UnsafeRunningFromFirecore is used internally and should not be altered. +var UnsafeRunningFromFirecore = false + +// UnsafeAllowedExecutableNameToBeEmpty is used internally and should not be altered. +var UnsafeAllowExecutableNameToBeEmpty = false + +// UnsafeResolveReaderNodeStartBlock is a function that resolved the reader node start block num, by default it simply +// returns the value of the 'reader-node-start-block-num'. However, the function may be overwritten in certain chains +// to perform a more complex resolution logic. +var UnsafeResolveReaderNodeStartBlock = func(ctx context.Context, startBlockNum uint64, firstStreamableBlock uint64, runtime *launcher.Runtime, rootLog *zap.Logger) (uint64, error) { + return startBlockNum, nil +} diff --git a/firehose/firehose-core/utils.go b/firehose/firehose-core/utils.go new file mode 100644 index 0000000..458e66e --- /dev/null +++ b/firehose/firehose-core/utils.go @@ -0,0 +1,82 @@ +package firecore + +import ( + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/streamingfast/cli" +) + +func mkdirStorePathIfLocal(storeURL string) (err error) { + if dirs := getDirsToMake(storeURL); len(dirs) > 0 { + err = MakeDirs(dirs) + } + + return +} + +func getDirsToMake(storeURL string) []string { + parts := strings.Split(storeURL, "://") + if len(parts) > 1 { + if parts[0] != "file" { + // Not a local store, nothing to do + return nil + } + storeURL = parts[1] + } + + // Some of the store URL are actually a file directly, let's try our best to cope for that case + filename := filepath.Base(storeURL) + if strings.Contains(filename, ".") { + storeURL = filepath.Dir(storeURL) + } + + // If we reach here, it's a local store path + return []string{storeURL} +} + +func MakeDirs(directories []string) error { + for _, directory := range directories { + err := os.MkdirAll(directory, 0755) + if err != nil { + return fmt.Errorf("failed to create directory %q: %w", directory, err) + } + } + + return nil +} + +// MustReplaceDataDir replaces `{data-dir}` from within the `in` received argument by the +// `dataDir` argument +func MustReplaceDataDir(dataDir, in string) string { + d, err := filepath.Abs(dataDir) + if err != nil { + panic(fmt.Errorf("file path abs: %w", err)) + } + + in = strings.Replace(in, "{data-dir}", d, -1) + + // Some legacy code still uses '{sf-data-dir}' (firehose-ethereum/firehose-near for example), so let's replace it + // also to keep it compatible even though it's not advertised anymore + in = strings.Replace(in, "{sf-data-dir}", d, -1) + + return in +} + +var Example = func(in string) string { + return string(cli.Example(in)) +} + +func ExamplePrefixed[B Block](chain *Chain[B], prefix, in string) string { + return string(cli.ExamplePrefixed(chain.BinaryName()+" "+prefix, in)) +} + +func MustParseUint64(s string) uint64 { + i, err := strconv.Atoi(s) + cli.NoError(err, "Unable to parse %q as uint64", s) + + return uint64(i) +} diff --git a/firehose/firehose-extract/src/main.rs b/firehose/firehose-extract/src/main.rs index 59ac5a3..7767c31 100644 --- a/firehose/firehose-extract/src/main.rs +++ b/firehose/firehose-extract/src/main.rs @@ -35,7 +35,7 @@ async fn main() -> anyhow::Result<()> { loop { let page_req = PaginationRequest:: { cursor, - results: 50, + results: 32, // 42 direction: PageDirection::Forward, }; let query = FullBlocksQuery::build(page_req.into()); diff --git a/firehose/firehose-fuel/.gitignore b/firehose/firehose-fuel/.gitignore deleted file mode 100644 index 28e8b2c..0000000 --- a/firehose/firehose-fuel/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -.idea/ -.DS_Store - -# To facilitate development, you can use [direnv](https://direnv.net/) to auto-load -# some environment variables. We have in `devel` a shim script called directly `firefuel` -# (that should have been renamed to your specific chain like `fireeth`). This small shim -# compiles the project and then invoke resulting binary. This means that when using this -# .envrc file: -# -# ``` -# export PATH="`pwd`/devel:$PATH" -# ``` -# -# Each time you enter the project in your terminal (e.g. `cd ~/work/firehose-fuel`) and then -# do `firehose start --help`, then it first compiles all the Go source code and then invoke the -# resulting just compiled binary, meaning you are "always" freshly compiled, it's more or less -# equivalent to doing directly `go run ./cmd/firefuel start --help` (which is totally fine). -.envrc* -*.spkg - -/firefuel -/build -/dist -/build - -firehose-data \ No newline at end of file diff --git a/firehose/firehose-fuel/.sfreleaser b/firehose/firehose-fuel/.sfreleaser deleted file mode 100755 index 6723790..0000000 --- a/firehose/firehose-fuel/.sfreleaser +++ /dev/null @@ -1,26 +0,0 @@ -global: - owner: streamingfast - project: firehose-fuel - binary: firefuel - language: golang - variant: application - sfreleaser-min-version: v0.7.0 -release: - pre-build-hooks: - - substreams pack -o "{{ .buildDir }}/substreams-fuel-{{ .release.Version }}.spkg" substreams.yaml - upload-extra-assets: - - "{{ .buildDir }}/substreams-fuel-{{ .release.Version }}.spkg" - - # By default we disable Brew publishing because it's assumed that most owner - # of this repository will not have the a Homebrew tap repository created which - # fails the release. - # - # To enable Brew publishing, create an empty repository `/homebrew-tap` - # which will hold the Homebrew formula. - # - # If you would like to use a different name than `homebrew-tap`, you can define - # brew-tap-repo: below this. - # - # Once available, you can enable the Brew publishing by removing the `brew-disabled` - # line. - brew-disabled: true diff --git a/firehose/firehose-fuel/CHANGELOG.md b/firehose/firehose-fuel/CHANGELOG.md deleted file mode 100755 index 01b779c..0000000 --- a/firehose/firehose-fuel/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## Next - diff --git a/firehose/firehose-fuel/Dockerfile.bundle b/firehose/firehose-fuel/Dockerfile.bundle deleted file mode 100644 index fa54a51..0000000 --- a/firehose/firehose-fuel/Dockerfile.bundle +++ /dev/null @@ -1,17 +0,0 @@ -ARG CHAIN_VERSION=latest -ARG SF_VERSION=latest - -# Here you would actually pull from your own chain image, but for the sake of the demo, we use the dummy-blockchain. -# The idea is that you bring together your chain's binary at the right and the firehose-fuel's binary into a single -# image. -FROM ghcr.io/streamingfast/dummy-blockchain:$CHAIN_VERSION as chain - -# The 'ghcr.io/FuelLabs/firehose-fuel' image is the one published by -# Dockerfile found at the root of this project. -FROM ghcr.io/FuelLabs/firehose-fuel:$SF_VERSION - -# Adjusted first element of copy to match the path of the binary in the chain image -COPY --from=chain /app/dummy-blockchain /app/dummy-blockchain - -COPY tools/docker/motd_node_manager /etc/motd -COPY tools/docker/scripts/. /usr/local/bin/ diff --git a/firehose/firehose-fuel/INTEGRATION.md b/firehose/firehose-fuel/INTEGRATION.md deleted file mode 100644 index 6cbb544..0000000 --- a/firehose/firehose-fuel/INTEGRATION.md +++ /dev/null @@ -1,309 +0,0 @@ -# Chain Integration Document - -## Concepts -Blockchain data extraction occurs by a process `Reader`, and a firehose enabled node. We run an instrumented version of a process (usually a node) to sync the chain referred to as `Firehose`. -The 'Firehose' process instruments the blockchain and outputs logs over the standard output pipe, which is subsequently read and processed by the `Reader` process. -The Reader process will read, and stitch together the output of `Firehose` to create rich blockchain data models, which it will subsequently write to -files. The data models in question are [Google Protobuf Structures](https://developers.google.com/protocol-buffers). - -#### Data Modeling - -Designing the [Google Protobuf Structures](https://developers.google.com/protocol-buffers) for your given blockchain is one of the most important steps in an integrators journey. -The data structures needs to represent as precisely as possible the on chain data and concepts. By carefully crafting the Protobuf structure, the next steps will be a lot simpler. -The data model need. - -As a reference, here is Ethereum's Protobuf Structure: -[https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto) - -# Running the Demo Chain - -We have built an end-to-end template, to start the on-boarding process of new chains. This solution consist of: - -*firehose-fuel* -As mentioned above, the `Reader` process consumes the data that is extracted and streamed from `Firehose`. In Actuality the Reader -is one process out of multiple ones that creates the _Firehose_. These processes are launched by one application. This application is -chain specific and by convention, we name is "firehose-". Though this application is chain specific, the structure of the application -is standardized and is quite similar from chain to chain. For convenience, we have create a boiler plate app to help you get started. -We named our chain `Acme` this the app is [firehose-fuel](https://github.com/FuelLabs/firehose-fuel) - -*Firehose Logs* -Firehose logs consist of an instrumented syncing node. We have created a "dummy-blockchain" chain to simulate a node process syncing that can be found [https://github.com/streamingfast/dummy-blockchain](https://github.com/streamingfast/dummy-blockchain). - -## Setting up the dummy chain - -Clone the repository: -```bash -git clone https://github.com/streamingfast/dummy-blockchain.git -cd dummy-blockchain -``` - -Then build the binary: -```bash -go install ./cmd/dummy-blockchain -``` - -Ensure the build was successful -```bash -./dummy-blockchain --version -``` - -Take note of the location of the built `dummy-blockchain` binary, you will need to configure `firehose-fuel` with it. - -## Setting up firehose-fuel - -Clone the repository: - -```bash -git clone git@github.com:FuelLabs/firehose-fuel.git -cd firehose-fuel -``` - -Configure firehose test setup - -```bash -cd devel/standard/ -vi standard.yaml -``` - -modify the flag `reader-node-path: "dummy-blockchain"` to point to the path of your `dummy-blockchain` binary you compiled above - -## Starting and testing Firehose - -*all subsequent commands are run from the `devel/standard/` directory* - -Start `firefuel` -```bash -./start.sh -``` - -This will launch `firefuel` application. Behind the scenes we are starting 3 sub processes: `reader-node`, `relayer`, `firehose` - -*reader-node* - -The reader-node is a process that runs and manages the blockchain node Geth. It consumes the blockchain data that is -extracted from our instrumented Geth node. The instrumented Geth node outputs individual block data. The reader-node -process will either write individual block data into separate files called one-block files or merge 100 blocks data -together and write into a file called 100-block file. - -This behaviour is configurable with the reader-node-merge-and-store-directly flag. When running the reader-node -process with reader-node-merge-and-store-directly flag enable, we say the reader is running in merged mode”. -When the flag is disabled, we will refer to the reader as running in its normal mode of operation. - -In the scenario where the reader-node process stores one-block files. We can run a merger process on the side which -would merge the one-block files into 100-block files. When we are syncing the chain we will run the reader-node process -in merged mode. When we are synced we will run the reader-node in it’s regular mode of operation (storing one-block files) - -The one-block files and 100-block files will be store in data-dir/storage/merged-blocks and data-dir/storage/one-blocks respectively. -The naming convention of the file is the number of the first block in the file. - -As the instrumented node process outputs blocks, you can see the merged block files in the working dir - -```bash -ls -las ./firehose-data/storage/merged-blocks -``` - -We have also built tools that allow you to introspect block files: - -```bash -go install ../../cmd/firefuel && firefuel tools print blocks --store ./firehose-data/storage/merged-blocks 100 -``` - -At this point we have `reader-node` process running as well a `relayer` & `firehose` process. Both of these processes work together to provide the Firehose data stream. -Once the firehose process is running, it will be listening on port 18015. At it’s core the firehose is a gRPC stream. We can list the available gRPC service - -```bash -grpcurl -plaintext localhost:18015 list -``` - -We can start streaming blocks with `sf.firehose.v2.Stream` Service: - -```bash -grpcurl -plaintext -d '{"start_block_num": 10}' -import-path ./proto -proto sf/acme/type/v1/type.proto localhost:18015 sf.firehose.v2.Stream.Blocks -``` - -# Using `firehose-fuel` as a template - -One of the main reason we provide a `firehose-fuel` repository is to act as a template element that integrators can use to bootstrap -creating the required Firehose chain specific code. - -We purposely used `Acme` (and also `acme` and `ACME`) throughout this repository so that integrators can simply copy everything and perform -a global search/replace of this word and use their chain name instead. - -As well as this, there is a few files that requires a renaming. Would will find below the instructions to properly make the search/replace -as well as the list of files that should be renamed. - -## Cloning - -First step is to clone again `firehose-fuel` this time to a dedicated repository that will be the one of your chain: - -``` -git clone git@github.com:FuelLabs/firehose-fuel.git firehose- -``` - -> Don't forget to change `` by the name of your exact chain like `ethereum` so it would became `firehose-ethereum` - -Then we are going to remove the `.git` folder to start fresh: - -``` -cd firehose- -rm -rf .git -git init -``` - -While not required, I suggest to create an initial commit so it's easier to revert back if you make a mistake -or delete a wrong file: - -``` -git add -A . -git commit -m "Initial commit" -``` - -## Renaming & Modifications - -> **Note** For example purposes, we will use Ethereum as the example target chain. Every Ethereum mentions when you run the command should be replaced by an equivalent value for your own chain! - -### Renames - -Perform a **case-sensitive** search/replace for the following terms, order is important: - -- `github.com/FuelLabs/firehose-fuel` -> `github.com//firehose-` -- `ghcr.io/FuelLabs/firehose-fuel` -> `ghcr.io//firehose-` -- `owner: streamingfast` -> `owner: ` -- `firefuel` -> `fire` (for the final binary produced) -- `acme` -> `` (for variable, identifier and other place not meant for display, `camelCase`) -- `Acme` -> `` (for title(s) and display of chain's full name, `titleCase`) -- `ACME` -> `` (for constants) - -> **Note** Don't forget to change `` (and their variants) by the name of your exact chain like `ethereum` so it would become `ethereum`, `Ethereum` and `ETHEREUM` respectively. The `` should be a shorter version if `` if you find it too long or have a known short version of it. For example, `ethereum` `` is actually `eth` while `NEAR` chain is the same as `` so `near`. The `` value needs to be replaced by GitHub organisation/user that is going to host the `firehose-` repository, for example if `firehose-ethereum` is going to be hosted at `github.com/ethereum-core/firehose-ethereum`, the `` here would be `ethereum-core`. - -#### Using [sd](https://github.com/chmln/sd) - -Here the commands to perform the replacement if you have installed (or install) `sd` tool: - -```bash -export owner=org # Change me! -export chain=ethereum # Change me! -export chain_short=eth # Change me! -export chain_title=Ethereum # Change me! -export chain_constant=ETHEREUM # Change me! - -find . -type f -not -path "./.git/*" -exec sd -f c "github.com/FuelLabs/firehose-fuel" "github.com/$owner/firehose-$chain" {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c "ghcr.io/FuelLabs/firehose-fuel" "ghcr.io/$owner/firehose-$chain" {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c "buf.build/FuelLabs/firehose-fuel" "buf.build/$owner/firehose-$chain" {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c "owner: streamingfast" "owner: $owner" {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c firefuel fire$chain_short {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c acme $owner {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c Acme $chain_title {} \;` -find . -type f -not -path "./.git/*" -exec sd -f c ACME $chain_constant {} \;` - -``` - -> **Warning** Don't forget to chain `owner`, `chain`, `chain_short`, `chain_title` and `change_constant` by respectively your own GitHub organisation/user, chain name and its variation short, title and constant. - -### Files - -``` -git mv ./devel/firefuel ./devel/fireeth -git mv ./cmd/firefuel ./cmd/fireeth -git mv ./pb/sf/acme ./pb/sf/ethereum -git mv ./proto/sf/acme ./proto/sf/ethereum -``` - -### Re-generate Protobuf - -Once you have performed the renamed of all 3 terms above and file renames, you should re-generate the Protobuf code: - -``` -cd firehose- -./types/pb/generate.sh -``` - -> **Note** You will require `protoc`, `protoc-gen-go` and `protoc-gen-go-grpc`. The former can be installed following https://grpc.io/docs/protoc-installation/, the last two can be installed respectively with `go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.25.0` and `go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1.0`. - -> **Note** If you see the error message `Could not make proto path relative: sf//type/v1/type.proto: No such file or directory`, you probably forgot to renamed `proto/sf/acme` to `proto/sf/`. - -### Commit & Compile - -It's time to test our previous steps to ensure it compiles and everything is good. At this point, you need to have the repository created on GitHub, so go ahead, create the repository on GitHub and push the first initial commit we had before performing the modifications. - -#### Update `types` dependency - -A quirks of the current setup is that `types` folder is actually a dedicated Golang module separated from the main module. This creates some small problem when updating the `types` dependency within the main module. First, ensure that `types` compile: - -``` -cd types -go test ./... -cd .. -``` - -Everything should be good, ensure you have perform all the renames. Here the steps to do then update the `types` dependency in the core project. - -1. `git add -A types` -1. `git commit -m "Re-generated Protobuf types"` -1. `git push` -1. Remove the line starting with `github.com//firehose-/types` from the `go.mod` file -1. `go get github.com//firehose-/types@master` -1. Test everything with `go test ./...`, that should pass now. - -Now the main module has its `types` dependency updated with the newly generated Golang Protobuf code. - -> **Note** Change `` and `firehose-` by correct values where you project is hosted at. - -### Node - -Doing a Firehose integration means there is an instrumented node that emits Firehose logs (or if not a node directly, definitely a process that reads and emits Firehose logs). - -#### [cmd/firefuel/cli/constants.go](cmd/firefuel/cli/constants.go) - -- Replace `ChainExecutableName = "dummy-blockchain"` by the `ChainExecutableName = ""` where `` is the node's binary name that should be launched. - -#### [devel/standard/standard.yaml](devel/standard/standard.yaml) - -- Replace `dummy-blockchain` by the node's binary name that should be launched. -- In string `reader-node-arguments: +--firehose-enabled --block-rate=60` replace `--firehose-enabled --block-rate=60` by the required arguments to launch the Firehose instrumented logs and enable Firehose logs. This will be specific to your chain's integration. - -### Dockerfile(s) & GitHub Actions - -There is two Docker image created by a build of `firehose-fuel`. First, a version described as _vanilla_ where only `firefuel` Golang binary is included and another one described as _bundle_ which includes both the `firacme` binary and the chain's binary that `reader-node` launches. - -Here the files that needs to be modified for this. The Dockerfile are all built on Ubuntu 20.04 images. - -#### [.github/workflows/docker.yaml](.github/workflows/docker.yaml) - -- Replace `ghcr.io/streamingfast/dummy-blockchain` by the node Docker image containing the node's binary (ensures it's an Ubuntu/Debian image you are using). -- Replace `dummy-blockchain` by the node's binary name. - -#### [Dockerfile](Dockerfile) - -- Replace `ghcr.io/streamingfast/dummy-blockchain` by the node Docker image containing the node's binary (ensures it's an Ubuntu/Debian image you are using). -- Replace `dummy-blockchain` by the node's binary name. -- Change `--from=chain /app/dummy-blockchain` to `--from=chain ////` is. - -### CHANGELOG - -The changelog file `CHANGELOG.md` is used as part of GitHub CI Actions to generate the the correct release notes. It works by matching the first `## ` Markdown error an accumulating everything up to the following `## ` header (if any). It takes the full text of that and uses it as the changelog notes as well as generating the array of built files. - -Major information should be listed in there about any public changes that could affect operators. - -### Testing - -Once everything is done, normally tests should be all good and everything should compile properly: - -``` -go test ./... -``` - -### Commit - -If everything is fine at that point, you are ready to commit everything and push - -``` -git add -A . -git commit -m "Renamed Acme to " -git add remote origin -git push -``` - -## License - -[Apache 2.0](LICENSE) diff --git a/firehose/firehose-fuel/README.md b/firehose/firehose-fuel/README.md deleted file mode 100644 index 923757b..0000000 --- a/firehose/firehose-fuel/README.md +++ /dev/null @@ -1,44 +0,0 @@ -**# Firehose Starter for new blockchain integrations -[![reference](https://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](https://pkg.go.dev/github.com/FuelLabs/firehose-fuel) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) - -What's [ACME](https://en.wikipedia.org/wiki/Acme_Corporation)? - -# Usage - -See [documentation on the Firehose docs website](https://firehose.streamingfast.io/integrate-new-chains/firehose-starter). - -## Release - -Use https://github.com/streamingfast/sfreleaser to perform a new release. You can install from source https://github.com/streamingfast/sfreleaser/releases downloading the binary. - -However if for now suggest to install from source (the tool `sfreleaser` is still getting fixes/features): - -```bash -go install github.com/streamingfast/sfreleaser@latest -``` - -It will ask you questions as well as driving all the required commands, performing the necessary operation automatically. The release is pushed in draft mode by default, so you can test check the whole flow before publishing (See configuration file [.sfreleaser](.sfreleaser) for some extra details). - -You will need to have for releases: -- [Docker](https://docs.docker.com/get-docker/) installed and running -- [GitHub CLI tool](https://cli.github.com/) installed and authenticated with GitHub - -The `sfreleaser` binary checks that those tools exist before doing any work. - -## Contributing - -**Issues and PR in this repo related strictly to the Firehose on Dummy Blockchain.** - -Report any protocol-specific issues in their -[respective repositories](https://github.com/streamingfast/streamingfast#protocols) - -**Please first refer to the general -[StreamingFast contribution guide](https://github.com/streamingfast/streamingfast/blob/master/CONTRIBUTING.md)**, -if you wish to contribute to this code base. - -This codebase uses unit tests extensively, please write and run tests. - -## License - -[Apache 2.0](LICENSE)** diff --git a/firehose/firehose-fuel/cmd/firefuel/main.go b/firehose/firehose-fuel/cmd/firefuel/main.go deleted file mode 100644 index c0b0dd6..0000000 --- a/firehose/firehose-fuel/cmd/firefuel/main.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import ( - "github.com/FuelLabs/firehose-fuel/codec" - pbfuel "github.com/FuelLabs/firehose-fuel/pb/sf/fuel/type/v1" - "github.com/spf13/pflag" - firecore "github.com/streamingfast/firehose-core" -) - -func main() { - firecore.Main(&firecore.Chain[*pbfuel.Block]{ - ShortName: "fuel", - LongName: "Fuel", - ExecutableName: "fuel-todo", - FullyQualifiedModule: "github.com/FuelLabs/firehose-fuel", - Version: version, - - Protocol: "FLC", - ProtocolVersion: 1, - - FirstStreamableBlock: 1, - - BlockFactory: func() firecore.Block { return new(pbfuel.Block) }, - ConsoleReaderFactory: codec.NewConsoleReader, - - Tools: &firecore.ToolsConfig[*pbfuel.Block]{ - BlockPrinter: printBlock, - }, - - RegisterExtraStartFlags: func(flags *pflag.FlagSet) { - flags.String("reader-node-bootstrap-data-url", "", "URL (file or gs) to either a genesis.json file or a .tar.zst archive to decompress in the datadir. Only used when bootstrapping (no prior data)") - flags.StringArray("substreams-rpc-endpoints", nil, "Remote endpoints to contact to satisfy Substreams 'eth_call's") - flags.String("substreams-rpc-cache-store-url", "{data-dir}/rpc-cache", "where rpc cache will be store call responses") - flags.Uint64("substreams-rpc-cache-chunk-size", uint64(1_000), "RPC cache chunk size in block") - }, - }) -} - -// Version value, injected via go build `ldflags` at build time, **must** not be removed or inlined -var version = "dev" diff --git a/firehose/firehose-fuel/cmd/firefuel/printer.go b/firehose/firehose-fuel/cmd/firefuel/printer.go deleted file mode 100644 index e5cd643..0000000 --- a/firehose/firehose-fuel/cmd/firefuel/printer.go +++ /dev/null @@ -1,32 +0,0 @@ -package main - -import ( - "fmt" - "io" - - "github.com/streamingfast/bstream" - pbfuel "github.com/FuelLabs/firehose-fuel/pb/sf/fuel/type/v1" -) - -func printBlock(blk *bstream.Block, alsoPrintTransactions bool, out io.Writer) error { - block := blk.ToProtocol().(*pbfuel.Block) - - if _, err := fmt.Fprintf(out, "Block #%d (%s) (prev: %s): %d transactions\n", - block.Height, - block.Id, - block.PrevId[0:7], - len(block.Transactions), - ); err != nil { - return err - } - - if alsoPrintTransactions { - for i, _ := range block.Transactions { - if _, err := fmt.Fprintf(out, "- Transaction %d\n", i); err != nil { - return err - } - } - } - - return nil -} diff --git a/firehose/firehose-fuel/codec/console_reader.go b/firehose/firehose-fuel/codec/console_reader.go deleted file mode 100644 index 9bcbd8f..0000000 --- a/firehose/firehose-fuel/codec/console_reader.go +++ /dev/null @@ -1,197 +0,0 @@ -package codec - -import ( - "bufio" - "encoding/hex" - "fmt" - pbfuel "github.com/FuelLabs/firehose-fuel/pb/sf/fuel/type/v1" - "github.com/golang/protobuf/proto" - "github.com/streamingfast/bstream" - firecore "github.com/streamingfast/firehose-core" - "github.com/streamingfast/logging" - "github.com/streamingfast/node-manager/mindreader" - "go.uber.org/zap" - "io" - "os" - "strings" -) - -// ConsoleReader is what reads the `fuel-core` output directly -type ConsoleReader struct { - lines chan string - blockEncoder firecore.BlockEncoder - close func() - - done chan interface{} - activeBlock *pbfuel.Block - - logger *zap.Logger - tracer logging.Tracer -} - -func NewConsoleReader(lines chan string, blockEncoder firecore.BlockEncoder, logger *zap.Logger, tracer logging.Tracer) (mindreader.ConsolerReader, error) { - return &ConsoleReader{ - lines: lines, - blockEncoder: blockEncoder, - close: func() {}, - done: make(chan interface{}), - logger: logger, - tracer: tracer, - }, nil -} - -func (r *ConsoleReader) Done() <-chan interface{} { - return r.done -} - -func (r *ConsoleReader) Close() { - r.close() -} - -func (r *ConsoleReader) readBlock() (out *pbfuel.Block, err error) { - block, err := r.next() - if err != nil { - return nil, err - } - - return block, nil -} - -func (r *ConsoleReader) ReadBlock() (out *bstream.Block, err error) { - block, err := r.readBlock() - if err != nil { - return nil, err - } - - blockEncoded, err := r.blockEncoder.Encode(block) - if err != nil { - return nil, err - } - - err = writeUint32ToFile("last_height.txt", block.Height) - if err != nil { - return nil, err - } - - return blockEncoded, err -} - -const ( - LogPrefix = "FIRE " - FuelProto = "PROTO" -) - -func (r *ConsoleReader) next() (out *pbfuel.Block, err error) { - for line := range r.lines { - if !strings.HasPrefix(line, LogPrefix) { - continue - } - - args := strings.Split(line[len(LogPrefix):], " ") - - if len(args) < 2 { - return nil, fmt.Errorf("invalid log line %q", line) - } - - // Order the case from most occurring line prefix to least occurring - switch args[0] { - - case FuelProto: - block, err := r.readFuelProto(args[1:]) - if err != nil { - return nil, lineError(line, err) - } - return block, nil - - default: - if r.logger.Core().Enabled(zap.DebugLevel) { - r.logger.Debug("skipping unknown log line", zap.String("line", line)) - } - continue - } - } - - r.logger.Info("lines channel has been closed") - return nil, io.EOF -} - -func (r *ConsoleReader) readFuelProto(params []string) (*pbfuel.Block, error) { - block := &pbfuel.Block{} - - out, err := hex.DecodeString(params[0]) - if err != nil { - return nil, fmt.Errorf("read block %d: invalid string value: %w. Unable to decode the string", r.activeBlock.Height, err) - } - - if err := proto.Unmarshal(out, block); err != nil { - return nil, fmt.Errorf("read block %d: invalid proto: %w", r.activeBlock.Height, err) - } - - r.logger.Info("console reader node block", - zap.String("id", block.GetFirehoseBlockID()), - zap.Uint64("height", block.GetFirehoseBlockNumber()), - zap.Time("timestamp", block.GetFirehoseBlockTime()), - ) - - return block, nil -} - -func (r *ConsoleReader) processData(reader io.Reader) error { - scanner := r.buildScanner(reader) - for scanner.Scan() { - line := scanner.Text() - r.lines <- line - } - - if scanner.Err() == nil { - close(r.lines) - return io.EOF - } - - return scanner.Err() -} - -func (r *ConsoleReader) buildScanner(reader io.Reader) *bufio.Scanner { - buf := make([]byte, 50*1024*1024) - scanner := bufio.NewScanner(reader) - scanner.Buffer(buf, 50*1024*1024) - - return scanner -} - -func (r *ConsoleReader) resetActiveBlock() { - r.activeBlock = nil -} - -func validateChunk(params []string, count int) error { - if len(params) != count { - return fmt.Errorf("%d fields required but found %d", count, len(params)) - } - return nil -} - -func lineError(line string, source error) error { - return fmt.Errorf("%w (on line %q)", source, line) -} - -func writeUint32ToFile(filePath string, num uint32) error { - file, err := os.Create(filePath) - - if err != nil { - return fmt.Errorf("error creating file: %w", err) - } - - defer func() { - if closeErr := file.Close(); closeErr != nil && err == nil { - err = fmt.Errorf("error closing file: %w", closeErr) - } - }() - - _, writeErr := fmt.Fprint(file, num) - - if writeErr != nil { - return fmt.Errorf("error writing to file: %w", writeErr) - } - - return nil -} diff --git a/firehose/firehose-fuel/codec/console_reader_test.go b/firehose/firehose-fuel/codec/console_reader_test.go deleted file mode 100644 index db8828d..0000000 --- a/firehose/firehose-fuel/codec/console_reader_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2021 dfuse Platform Inc. -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package codec - -import ( - "bytes" - "encoding/json" - "io" - "io/ioutil" - "os" - "os/exec" - "strings" - "testing" - - firecore "github.com/streamingfast/firehose-core" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestParseFromFile(t *testing.T) { - tests := []struct { - firehoseLogsFile string - expectedPanicErr error - }{ - {"testdata/full.firelog", nil}, - } - - for _, test := range tests { - t.Run(strings.Replace(test.firehoseLogsFile, "testdata/", "", 1), func(t *testing.T) { - defer func() { - if r := recover(); r != nil { - require.Equal(t, test.expectedPanicErr, r) - } - }() - - cr := testFileConsoleReader(t, test.firehoseLogsFile) - buf := &bytes.Buffer{} - buf.Write([]byte("[")) - - for first := true; true; first = false { - block, err := cr.readBlock() - if err == io.EOF { - break - } - require.NoError(t, err) - - if !first { - buf.Write([]byte(",")) - } - - // FIXMME: jsonpb needs to be updated to latest version of used gRPC - // elements. We are disaligned and using that breaks now. - // Needs to check what is the latest way to properly serialize - // Proto generated struct to JSON. - // value, err := jsonpb.MarshalIndentToString(v, " ") - // require.NoError(t, err) - - value, err := json.MarshalIndent(block, "", " ") - require.NoError(t, err) - - buf.Write(value) - } - - if len(buf.Bytes()) != 0 { - buf.Write([]byte("\n")) - } - - buf.Write([]byte("]")) - - goldenFile := test.firehoseLogsFile + ".golden.json" - if os.Getenv("GOLDEN_UPDATE") == "true" { - ioutil.WriteFile(goldenFile, buf.Bytes(), os.ModePerm) - } - - cnt, err := ioutil.ReadFile(goldenFile) - require.NoError(t, err) - - if !assert.Equal(t, string(cnt), buf.String()) { - t.Error("previous diff:\n" + unifiedDiff(t, cnt, buf.Bytes())) - } - }) - } -} - -func testFileConsoleReader(t *testing.T, filename string) *ConsoleReader { - t.Helper() - - fl, err := os.Open(filename) - require.NoError(t, err) - - cr := testReaderConsoleReader(t, make(chan string, 10000), func() { fl.Close() }) - - go cr.processData(fl) - - return cr -} - -func testReaderConsoleReader(t *testing.T, lines chan string, closer func()) *ConsoleReader { - t.Helper() - - l := &ConsoleReader{ - lines: lines, - blockEncoder: firecore.NewBlockEncoder(), - close: closer, - logger: zlog, - } - - return l -} - -func unifiedDiff(t *testing.T, cnt1, cnt2 []byte) string { - file1 := "/tmp/gotests-linediff-1" - file2 := "/tmp/gotests-linediff-2" - err := ioutil.WriteFile(file1, cnt1, 0600) - require.NoError(t, err) - - err = ioutil.WriteFile(file2, cnt2, 0600) - require.NoError(t, err) - - cmd := exec.Command("diff", "-u", file1, file2) - out, _ := cmd.Output() - - return string(out) -} diff --git a/firehose/firehose-fuel/codec/init_test.go b/firehose/firehose-fuel/codec/init_test.go deleted file mode 100644 index 4cf5077..0000000 --- a/firehose/firehose-fuel/codec/init_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package codec - -import ( - "github.com/streamingfast/bstream" - "github.com/streamingfast/logging" -) - -var zlog, _ = logging.PackageLogger("codec", "github.com/FuelLabs/firehose-fuel/codec_test") - -func init() { - logging.InstantiateLoggers() - - bstream.GetBlockPayloadSetter = bstream.MemoryBlockPayloadSetter -} diff --git a/firehose/firehose-fuel/codec/testdata/full.firelog b/firehose/firehose-fuel/codec/testdata/full.firelog deleted file mode 100644 index 0c409a5..0000000 --- a/firehose/firehose-fuel/codec/testdata/full.firelog +++ /dev/null @@ -1,20 +0,0 @@ -FIRE BLOCK_BEGIN 6 -FIRE BEGIN_TRX 8aa57a7aecd32837f2b0ea5e3e7713559df949c19220870e9432c1546b9c1ba1 transfer 0xDEADBEEF 0xBAAAAAAD 0 2710 true -FIRE TRX_BEGIN_EVENT 8aa57a7aecd32837f2b0ea5e3e7713559df949c19220870e9432c1546b9c1ba1 token_transfer -FIRE TRX_EVENT_ATTR 8aa57a7aecd32837f2b0ea5e3e7713559df949c19220870e9432c1546b9c1ba1 0 foo bar -FIRE BEGIN_TRX c19f98d87a551217e84c7ab02e6579c1e75f4cffbbbd55f52f8ef6e0164b8b71 transfer 0xDEADBEEF 0xBAAAAAAD 3b9aca00 2710 true -FIRE TRX_BEGIN_EVENT c19f98d87a551217e84c7ab02e6579c1e75f4cffbbbd55f52f8ef6e0164b8b71 token_transfer -FIRE TRX_EVENT_ATTR c19f98d87a551217e84c7ab02e6579c1e75f4cffbbbd55f52f8ef6e0164b8b71 0 foo bar -FIRE BEGIN_TRX 3254289f54b12adc378f2ec18db51254bfa28931ac3c8655769537701744944a transfer 0xDEADBEEF 0xBAAAAAAD 77359400 2710 true -FIRE TRX_BEGIN_EVENT 3254289f54b12adc378f2ec18db51254bfa28931ac3c8655769537701744944a token_transfer -FIRE TRX_EVENT_ATTR 3254289f54b12adc378f2ec18db51254bfa28931ac3c8655769537701744944a 0 foo bar -FIRE BEGIN_TRX 90cddac96f703d56f8a523a6e10230d0e1a9cc6af42c7fe87f8d785976047164 transfer 0xDEADBEEF 0xBAAAAAAD b2d05e00 2710 true -FIRE TRX_BEGIN_EVENT 90cddac96f703d56f8a523a6e10230d0e1a9cc6af42c7fe87f8d785976047164 token_transfer -FIRE TRX_EVENT_ATTR 90cddac96f703d56f8a523a6e10230d0e1a9cc6af42c7fe87f8d785976047164 0 foo bar -FIRE BEGIN_TRX 13bfb5f01b6e4ef41527bc951655e80c7b57d08e3ff98c1ff34483b555e379c8 transfer 0xDEADBEEF 0xBAAAAAAD ee6b2800 2710 true -FIRE TRX_BEGIN_EVENT 13bfb5f01b6e4ef41527bc951655e80c7b57d08e3ff98c1ff34483b555e379c8 token_transfer -FIRE TRX_EVENT_ATTR 13bfb5f01b6e4ef41527bc951655e80c7b57d08e3ff98c1ff34483b555e379c8 0 foo bar -FIRE BEGIN_TRX 49486453e3c4ce9b3a784140041390e3cf7963cb5186b01047ee2bed241920ab transfer 0xDEADBEEF 0xBAAAAAAD 012a05f200 2710 true -FIRE TRX_BEGIN_EVENT 49486453e3c4ce9b3a784140041390e3cf7963cb5186b01047ee2bed241920ab token_transfer -FIRE TRX_EVENT_ATTR 49486453e3c4ce9b3a784140041390e3cf7963cb5186b01047ee2bed241920ab 0 foo bar -FIRE BLOCK_END 6 e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683 ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d 1647087903319636000 6 \ No newline at end of file diff --git a/firehose/firehose-fuel/codec/testdata/full.firelog.golden.json b/firehose/firehose-fuel/codec/testdata/full.firelog.golden.json deleted file mode 100755 index 445470d..0000000 --- a/firehose/firehose-fuel/codec/testdata/full.firelog.golden.json +++ /dev/null @@ -1,151 +0,0 @@ -[{ - "height": 6, - "hash": "e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683", - "prevHash": "ef2d127de37b942baad06145e54b0c619a1f22327b2ebbcfbec78f5564afe39d", - "timestamp": 1647087903319636000, - "transactions": [ - { - "type": "transfer", - "hash": "8aa57a7aecd32837f2b0ea5e3e7713559df949c19220870e9432c1546b9c1ba1", - "sender": "0xDEADBEEF", - "receiver": "0xBAAAAAAD", - "amount": {}, - "fee": { - "bytes": "JxA=" - }, - "success": true, - "events": [ - { - "type": "token_transfer", - "attributes": [ - { - "key": "foo", - "value": "bar" - } - ] - } - ] - }, - { - "type": "transfer", - "hash": "c19f98d87a551217e84c7ab02e6579c1e75f4cffbbbd55f52f8ef6e0164b8b71", - "sender": "0xDEADBEEF", - "receiver": "0xBAAAAAAD", - "amount": { - "bytes": "O5rKAA==" - }, - "fee": { - "bytes": "JxA=" - }, - "success": true, - "events": [ - { - "type": "token_transfer", - "attributes": [ - { - "key": "foo", - "value": "bar" - } - ] - } - ] - }, - { - "type": "transfer", - "hash": "3254289f54b12adc378f2ec18db51254bfa28931ac3c8655769537701744944a", - "sender": "0xDEADBEEF", - "receiver": "0xBAAAAAAD", - "amount": { - "bytes": "dzWUAA==" - }, - "fee": { - "bytes": "JxA=" - }, - "success": true, - "events": [ - { - "type": "token_transfer", - "attributes": [ - { - "key": "foo", - "value": "bar" - } - ] - } - ] - }, - { - "type": "transfer", - "hash": "90cddac96f703d56f8a523a6e10230d0e1a9cc6af42c7fe87f8d785976047164", - "sender": "0xDEADBEEF", - "receiver": "0xBAAAAAAD", - "amount": { - "bytes": "stBeAA==" - }, - "fee": { - "bytes": "JxA=" - }, - "success": true, - "events": [ - { - "type": "token_transfer", - "attributes": [ - { - "key": "foo", - "value": "bar" - } - ] - } - ] - }, - { - "type": "transfer", - "hash": "13bfb5f01b6e4ef41527bc951655e80c7b57d08e3ff98c1ff34483b555e379c8", - "sender": "0xDEADBEEF", - "receiver": "0xBAAAAAAD", - "amount": { - "bytes": "7msoAA==" - }, - "fee": { - "bytes": "JxA=" - }, - "success": true, - "events": [ - { - "type": "token_transfer", - "attributes": [ - { - "key": "foo", - "value": "bar" - } - ] - } - ] - }, - { - "type": "transfer", - "hash": "49486453e3c4ce9b3a784140041390e3cf7963cb5186b01047ee2bed241920ab", - "sender": "0xDEADBEEF", - "receiver": "0xBAAAAAAD", - "amount": { - "bytes": "ASoF8gA=" - }, - "fee": { - "bytes": "JxA=" - }, - "success": true, - "events": [ - { - "type": "token_transfer", - "attributes": [ - { - "key": "foo", - "value": "bar" - } - ] - } - ] - } - ] -} -] \ No newline at end of file diff --git a/firehose/firehose-fuel/devel/.gitignore b/firehose/firehose-fuel/devel/.gitignore deleted file mode 100644 index a3eff2f..0000000 --- a/firehose/firehose-fuel/devel/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -sf-data/ -perso -fuelfire/firehose-data -fuelfire/*.yaml -fuelfire/last_height.txt \ No newline at end of file diff --git a/firehose/firehose-fuel/devel/firefuel b/firehose/firehose-fuel/devel/firefuel deleted file mode 100755 index 8a45ca7..0000000 --- a/firehose/firehose-fuel/devel/firefuel +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" - -active_pid= - -main() { - set -e - - pushd "$ROOT" &> /dev/null - version="unknown" - if [[ -f .version ]]; then - version=`cat .version` - fi - - go install -ldflags "-X main.Version=$version -X main.Commit=`git rev-list -1 HEAD`" ./cmd/firefuel - popd &> /dev/null - - if [[ $KILL_AFTER != "" ]]; then - ${GOPATH:-$HOME/go}/bin/firefuel "$@" & - active_pid=$! - - sleep $KILL_AFTER - kill -s TERM $active_pid &> /dev/null || true - else - exec ${GOPATH:-$HOME/go}/bin/firefuel "$@" - fi -} - -main "$@" diff --git a/firehose/firehose-fuel/devel/fuelfire/bootstrap.sh b/firehose/firehose-fuel/devel/fuelfire/bootstrap.sh deleted file mode 100755 index 19517a1..0000000 --- a/firehose/firehose-fuel/devel/fuelfire/bootstrap.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env bash - -set -e - -FIREHOSE_EXTRACT_BIN="/app/firehose-extract" -FIREFUEL_BIN="/app/firefuel" -STORAGE_DIR="/data/storage_dir/" -CHAIN_ID="$1" - -if [ -z "$CHAIN_ID" ]; then - echo "Usage: $0 CHAIN_ID" - return 1 -fi - -HEIGHT_FILE="$STORAGE_DIR/last_height.txt" - -if [[ -f "$HEIGHT_FILE" ]]; then - LAST_HEIGHT="$(cat "$HEIGHT_FILE")" -else - LAST_HEIGHT=0 -fi - -TEMPFILE="$(mktemp)" - -cat <"$TEMPFILE" -start: - args: - - firehose - - merger - - reader-node - - substreams-tier1 - - substreams-tier2 - - relayer - flags: - reader-node-path: "$FIREHOSE_EXTRACT_BIN" - reader-node-arguments: $CHAIN_ID $LAST_HEIGHT - common-live-blocks-addr: "" - merger-prune-forked-blocks-after: 50 - common-first-streamable-block: 1 -# reader-node-start-block-num: -# reader-node-stop-block-num: 500 -# merger-time-between-store-pruning: 1s -END - -cd "$STORAGE_DIR" -exec "$FIREFUEL_BIN" -c "$TEMPFILE" start diff --git a/firehose/firehose-fuel/devel/fuelfire/start.sh b/firehose/firehose-fuel/devel/fuelfire/start.sh deleted file mode 100755 index a2b4f2e..0000000 --- a/firehose/firehose-fuel/devel/fuelfire/start.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -FIREFUEL="$ROOT/../firefuel" -BOOTSTRAP="$ROOT/bootstrap.sh" - -main() { - pushd "$ROOT" &> /dev/null - set -e - - while [[ "$#" -gt 0 ]]; do - case $1 in - -h| --help) usage && exit 0;; - --chain-id) $BOOTSTRAP "$@"; shift ;; - -c|--cleanup) cleanup; $BOOTSTRAP "$@"; shift ;; - *) usage_error "Invalid option: $1" ;; - esac - shift - done - - exec "$FIREFUEL" -c "$ROOT/firehose.yaml" start -} - -cleanup () { - rm -rf firehose-data last_height.txt &> /dev/null -} - -usage_error() { - message="$1" - exit_code="$2" - - echo "ERROR: $message" - echo "" - usage - exit "${exit_code:-1}" -} - -usage() { - echo "usage: start.sh [-c]" - echo "" - echo "Start $(basename "$ROOT") environment." - echo "" - echo "Options" - echo " -c Clean local data" -} - -main "$@" \ No newline at end of file diff --git a/firehose/firehose-fuel/devel/fuelfire_s/bootstrap.sh b/firehose/firehose-fuel/devel/fuelfire_s/bootstrap.sh deleted file mode 100755 index 8ca75fe..0000000 --- a/firehose/firehose-fuel/devel/fuelfire_s/bootstrap.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash - -set -o errexit -set -o pipefail - -#CHAIN_ID=${CHAIN_ID:-"beta-5.fuel.network"} -CHAIN_ID=${CHAIN_ID:-"http://127.0.0.1:4000"} -HEIGHT_FILE="last_height.txt" - -if [[ -f "$HEIGHT_FILE" ]]; then - LAST_HEIGHT="$(cat "$HEIGHT_FILE")" -else - LAST_HEIGHT=0 -fi - -if [[ $CLEANUP -eq "1" ]]; then - echo "Deleting all local data" - rm -rf firehose.yaml > /dev/null -fi - -cat << END > firehose.yaml -start: - args: - - firehose - - merger - - reader-node - - substreams-tier1 - - substreams-tier2 - - relayer - flags: - reader-node-path: "./../../../firehose-extract/target/debug/firehose-extract" - reader-node-arguments: $CHAIN_ID $LAST_HEIGHT - common-live-blocks-addr: "" - common-first-streamable-block: 0 -# reader-node-start-block-num: -# reader-node-stop-block-num: 120 -# merger-prune-forked-blocks-after: 2 -# merger-time-between-store-pruning: 1s -END diff --git a/firehose/firehose-fuel/devel/fuelfire_s/start.sh b/firehose/firehose-fuel/devel/fuelfire_s/start.sh deleted file mode 100755 index a2b4f2e..0000000 --- a/firehose/firehose-fuel/devel/fuelfire_s/start.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env bash - -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -FIREFUEL="$ROOT/../firefuel" -BOOTSTRAP="$ROOT/bootstrap.sh" - -main() { - pushd "$ROOT" &> /dev/null - set -e - - while [[ "$#" -gt 0 ]]; do - case $1 in - -h| --help) usage && exit 0;; - --chain-id) $BOOTSTRAP "$@"; shift ;; - -c|--cleanup) cleanup; $BOOTSTRAP "$@"; shift ;; - *) usage_error "Invalid option: $1" ;; - esac - shift - done - - exec "$FIREFUEL" -c "$ROOT/firehose.yaml" start -} - -cleanup () { - rm -rf firehose-data last_height.txt &> /dev/null -} - -usage_error() { - message="$1" - exit_code="$2" - - echo "ERROR: $message" - echo "" - usage - exit "${exit_code:-1}" -} - -usage() { - echo "usage: start.sh [-c]" - echo "" - echo "Start $(basename "$ROOT") environment." - echo "" - echo "Options" - echo " -c Clean local data" -} - -main "$@" \ No newline at end of file diff --git a/firehose/firehose-fuel/devel/standard/standard.yaml b/firehose/firehose-fuel/devel/standard/standard.yaml deleted file mode 100644 index 889b653..0000000 --- a/firehose/firehose-fuel/devel/standard/standard.yaml +++ /dev/null @@ -1,14 +0,0 @@ -start: - args: - - firehose - - merger -# - reader-node - - relayer - flags: - # Specifies the path to the binary - reader-node-path: "dummy-blockchain" - # Flags that will be added to the dummy chain process command - reader-node-arguments: --firehose-enabled --block-rate 200 start - # Flags that will be added to the dummy chain process command - common-live-blocks-addr: "" -# add block merger to merge on lower number of blocks diff --git a/firehose/firehose-fuel/devel/standard/start.sh b/firehose/firehose-fuel/devel/standard/start.sh deleted file mode 100755 index 3394ffe..0000000 --- a/firehose/firehose-fuel/devel/standard/start.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash - -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -clean= -firefuel="$ROOT/../firefuel" - -main() { - pushd "$ROOT" &> /dev/null - - while getopts "hc" opt; do - case $opt in - h) usage && exit 0;; - c) clean=true;; - \?) usage_error "Invalid option: -$OPTARG";; - esac - done - shift $((OPTIND-1)) - [[ $1 = "--" ]] && shift - - set -e - - if [[ $clean == "true" ]]; then - rm -rf firehose-data &> /dev/null || true - fi - - exec $firefuel -c $(basename $ROOT).yaml start "$@" -} - -usage_error() { - message="$1" - exit_code="$2" - - echo "ERROR: $message" - echo "" - usage - exit ${exit_code:-1} -} - -usage() { - echo "usage: start.sh [-c]" - echo "" - echo "Start $(basename $ROOT) environment." - echo "" - echo "Options" - echo " -c Clean actual data directory first" -} - -main "$@" \ No newline at end of file diff --git a/firehose/firehose-fuel/devel/stdin/start.sh b/firehose/firehose-fuel/devel/stdin/start.sh deleted file mode 100755 index 6c03f26..0000000 --- a/firehose/firehose-fuel/devel/stdin/start.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env bash - -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -clean= -firefuel="$ROOT/../firefuel" - -main() { - pushd "$ROOT" &> /dev/null - - while getopts "hc" opt; do - case $opt in - h) usage && exit 0;; - c) clean=true;; - \?) usage_error "Invalid option: -$OPTARG";; - esac - done - shift $((OPTIND-1)) - [[ $1 = "--" ]] && shift - - set -e - - if [[ $clean == "true" ]]; then - rm -rf firehose-data &> /dev/null || true - fi - - chain_data="$ROOT/firehose-data/dummy-blockchain/data" - if [[ ! -d "$chain_data" ]]; then - mkdir -p "$chain_data" - fi - - if ! command -v dummy-blockchain >/dev/null 2>&1; then - usage_error "The 'dummy-blockchain' executable must be found within your PATH, install it from source of 'https://github.com/streamingfast/dummy-blockchain'" - fi - - exec dummy-blockchain --firehose-enabled --block-rate 60 start --store-dir "$chain_data" | $firefuel -c $(basename $ROOT).yaml start "$@" -} - -usage_error() { - message="$1" - exit_code="$2" - - echo "ERROR: $message" - echo "" - usage - exit ${exit_code:-1} -} - -usage() { - echo "usage: start.sh [-c]" - echo "" - echo "Start $(basename $ROOT) environment." - echo "" - echo "Options" - echo " -c Clean actual data directory first" -} - -main "$@" \ No newline at end of file diff --git a/firehose/firehose-fuel/devel/stdin/stdin.yaml b/firehose/firehose-fuel/devel/stdin/stdin.yaml deleted file mode 100644 index 4a6b965..0000000 --- a/firehose/firehose-fuel/devel/stdin/stdin.yaml +++ /dev/null @@ -1,8 +0,0 @@ -start: - args: - - firehose - - merger - - reader-node-stdin - - relayer - flags: - # Invoked like `dummy-blockchain --firehose-enabled --block-rate=6 | ./devel/stdin/start.sh` diff --git a/firehose/firehose-fuel/example_input.txt b/firehose/firehose-fuel/example_input.txt deleted file mode 100644 index 0b3cc5c..0000000 --- a/firehose/firehose-fuel/example_input.txt +++ /dev/null @@ -1,2 +0,0 @@ -FIRE a b c -FIRE 1 2 3 diff --git a/firehose/firehose-fuel/go.mod b/firehose/firehose-fuel/go.mod deleted file mode 100644 index 2027a7e..0000000 --- a/firehose/firehose-fuel/go.mod +++ /dev/null @@ -1,232 +0,0 @@ -module github.com/FuelLabs/firehose-fuel - -go 1.21 - -toolchain go1.21.2 - -require ( - github.com/golang/protobuf v1.5.3 - github.com/streamingfast/bstream v0.0.2-0.20231109200242-92c3eea7aaba - github.com/streamingfast/firehose-core v0.2.3 - github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 - github.com/streamingfast/node-manager v0.0.2-0.20230406142433-692298a8b8d2 - github.com/stretchr/testify v1.8.4 - go.uber.org/zap v1.26.0 - google.golang.org/protobuf v1.31.0 -) - -require ( - cloud.google.com/go v0.110.4 // indirect - cloud.google.com/go/compute v1.21.0 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v1.1.1 // indirect - cloud.google.com/go/monitoring v1.15.1 // indirect - cloud.google.com/go/storage v1.30.1 // indirect - cloud.google.com/go/trace v1.10.1 // indirect - contrib.go.opencensus.io/exporter/stackdriver v0.13.10 // indirect - contrib.go.opencensus.io/exporter/zipkin v0.1.1 // indirect - github.com/Azure/azure-pipeline-go v0.2.3 // indirect - github.com/Azure/azure-storage-blob-go v0.14.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.32.3 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.15.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.39.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.0.0-20221018185641-36f91511cfd7 // indirect - github.com/KimMachineGun/automemlimit v0.2.4 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/RoaringBitmap/roaring v0.9.4 // indirect - github.com/ShinyTrinkets/meta-logger v0.2.0 // indirect - github.com/ShinyTrinkets/overseer v0.3.0 // indirect - github.com/abourget/llerrgroup v0.2.0 // indirect - github.com/aws/aws-sdk-go v1.44.325 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.3.1 // indirect - github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d // indirect - github.com/bobg/go-generics/v2 v2.1.1 // indirect - github.com/bufbuild/connect-go v1.10.0 // indirect - github.com/bufbuild/connect-grpchealth-go v1.1.1 // indirect - github.com/bufbuild/connect-grpcreflect-go v1.0.0 // indirect - github.com/bufbuild/connect-opentelemetry-go v0.3.0 // indirect - github.com/cenkalti/backoff/v4 v4.2.1 // indirect - github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/chzyer/readline v1.5.0 // indirect - github.com/cilium/ebpf v0.4.0 // indirect - github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe // indirect - github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 // indirect - github.com/containerd/cgroups v1.0.4 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/docker/go-units v0.5.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.8.0 // indirect - github.com/envoyproxy/go-control-plane v0.11.1 // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.2 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.21.1 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/s2a-go v0.1.4 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.11.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ipfs/boxo v0.8.0 // indirect - github.com/ipfs/go-cid v0.4.0 // indirect - github.com/ipfs/go-ipfs-api v0.6.0 // indirect - github.com/jhump/protoreflect v1.14.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/josephburnett/jd v1.7.1 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect - github.com/klauspost/compress v1.16.5 // indirect - github.com/klauspost/cpuid/v2 v2.2.3 // indirect - github.com/libp2p/go-buffer-pool v0.1.0 // indirect - github.com/libp2p/go-flow-metrics v0.1.0 // indirect - github.com/libp2p/go-libp2p v0.26.3 // indirect - github.com/lithammer/dedent v1.1.0 // indirect - github.com/logrusorgru/aurora v2.0.3+incompatible // indirect - github.com/magiconair/properties v1.8.7 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/manifoldco/promptui v0.9.0 // indirect - github.com/mattn/go-ieproxy v0.0.1 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/minio/sha256-simd v1.0.0 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/mostynb/go-grpc-compression v1.1.17 // indirect - github.com/mr-tron/base58 v1.2.0 // indirect - github.com/mschoch/smat v0.2.0 // indirect - github.com/multiformats/go-base32 v0.1.0 // indirect - github.com/multiformats/go-base36 v0.2.0 // indirect - github.com/multiformats/go-multiaddr v0.8.0 // indirect - github.com/multiformats/go-multibase v0.1.1 // indirect - github.com/multiformats/go-multicodec v0.8.1 // indirect - github.com/multiformats/go-multihash v0.2.1 // indirect - github.com/multiformats/go-multistream v0.4.1 // indirect - github.com/multiformats/go-varint v0.0.7 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/opencontainers/runtime-spec v1.0.2 // indirect - github.com/openzipkin/zipkin-go v0.4.1 // indirect - github.com/paulbellamy/ratecounter v0.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_golang v1.16.0 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.44.0 // indirect - github.com/prometheus/procfs v0.11.0 // indirect - github.com/rs/cors v1.10.0 // indirect - github.com/schollz/closestmatch v2.1.0+incompatible // indirect - github.com/sethvargo/go-retry v0.2.3 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.7.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.15.0 // indirect - github.com/streamingfast/atm v0.0.0-20220131151839-18c87005e680 // indirect - github.com/streamingfast/cli v0.0.4-0.20230825151644-8cc84512cd80 // indirect - github.com/streamingfast/dauth v0.0.0-20230929180355-921f9c9be330 // indirect - github.com/streamingfast/dbin v0.9.1-0.20220513054835-1abebbb944ad // indirect - github.com/streamingfast/derr v0.0.0-20230515163924-8570aaa43fe1 // indirect - github.com/streamingfast/dgrpc v0.0.0-20230929132851-893fc52687fa // indirect - github.com/streamingfast/dlauncher v0.0.0-20230607184145-76399faad89e // indirect - github.com/streamingfast/dmetering v0.0.0-20230731155453-e1df53e362aa // indirect - github.com/streamingfast/dmetrics v0.0.0-20230919161904-206fa8ebd545 // indirect - github.com/streamingfast/dstore v0.1.1-0.20230620124109-3924b3b36c77 // indirect - github.com/streamingfast/dtracing v0.0.0-20220305214756-b5c0e8699839 // indirect - github.com/streamingfast/firehose v0.1.1-0.20231109192301-ebfed7417cf6 // indirect - github.com/streamingfast/index-builder v0.0.0-20221031203737-fa2e70f09dc2 // indirect - github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0 // indirect - github.com/streamingfast/merger v0.0.3-0.20231027161314-209c2ddd8d96 // indirect - github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308 // indirect - github.com/streamingfast/pbgo v0.0.6-0.20221020131607-255008258d28 // indirect - github.com/streamingfast/relayer v0.0.2-0.20220909122435-e67fbc964fd9 // indirect - github.com/streamingfast/sf-tracing v0.0.0-20230616174903-cd2ade641ca9 // indirect - github.com/streamingfast/shutter v1.5.0 // indirect - github.com/streamingfast/snapshotter v0.0.0-20230316190750-5bcadfde44d0 // indirect - github.com/streamingfast/substreams v1.1.20 // indirect - github.com/subosito/gotenv v1.4.2 // indirect - github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf // indirect - github.com/tetratelabs/wazero v1.1.0 // indirect - github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c // indirect - github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 // indirect - go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/contrib/detectors/gcp v1.9.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0 // indirect - go.opentelemetry.io/otel v1.18.0 // indirect - go.opentelemetry.io/otel/exporters/jaeger v1.16.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 // indirect - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 // indirect - go.opentelemetry.io/otel/exporters/zipkin v1.15.1 // indirect - go.opentelemetry.io/otel/metric v1.18.0 // indirect - go.opentelemetry.io/otel/sdk v1.18.0 // indirect - go.opentelemetry.io/otel/trace v1.18.0 // indirect - go.opentelemetry.io/proto/otlp v1.0.0 // indirect - go.uber.org/atomic v1.10.0 // indirect - go.uber.org/automaxprocs v1.5.1 // indirect - go.uber.org/multierr v1.10.0 // indirect - golang.org/x/crypto v0.13.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.15.0 // indirect - golang.org/x/oauth2 v0.10.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect - golang.org/x/text v0.13.0 // indirect - golang.org/x/time v0.1.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.126.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect - google.golang.org/grpc v1.58.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/olivere/elastic.v3 v3.0.75 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.25.0 // indirect - k8s.io/apimachinery v0.25.0 // indirect - k8s.io/client-go v0.25.0 // indirect - k8s.io/klog/v2 v2.70.1 // indirect - k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect - k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed // indirect - lukechampine.com/blake3 v1.1.7 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect -) - -replace ( - github.com/ShinyTrinkets/overseer => github.com/streamingfast/overseer v0.2.1-0.20210326144022-ee491780e3ef - github.com/bytecodealliance/wasmtime-go/v4 => github.com/streamingfast/wasmtime-go/v4 v4.0.0-freemem3 - -) diff --git a/firehose/firehose-fuel/go.sum b/firehose/firehose-fuel/go.sum deleted file mode 100644 index c1e2275..0000000 --- a/firehose/firehose-fuel/go.sum +++ /dev/null @@ -1,1233 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.110.4 h1:1JYyxKMN9hd5dR2MYTPWkGUgcoxVVhg0LKNKEo0qvmk= -cloud.google.com/go v0.110.4/go.mod h1:+EYjdK8e5RME/VY/qLCAtuyALQ9q67dvuum8i+H5xsI= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.21.0 h1:JNBsyXVoOoNJtTQcnEY5uYpZIbeCTYIeDe0Xh1bySMk= -cloud.google.com/go/compute v1.21.0/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.1.1 h1:lW7fzj15aVIXYHREOqjRBV9PsH0Z6u8Y46a1YGvQP4Y= -cloud.google.com/go/iam v1.1.1/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+KUZOU= -cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I= -cloud.google.com/go/logging v1.7.0/go.mod h1:3xjP2CjkM3ZkO73aj4ASA5wRPGGCRrPIAeNqVNkzY8M= -cloud.google.com/go/longrunning v0.5.1 h1:Fr7TXftcqTudoyRJa113hyaqlGdiBQkp0Gq7tErFDWI= -cloud.google.com/go/longrunning v0.5.1/go.mod h1:spvimkwdz6SPWKEt/XBij79E9fiTkHSQl/fRUUQJYJc= -cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4= -cloud.google.com/go/monitoring v1.15.1 h1:65JhLMd+JiYnXr6j5Z63dUYCuOg770p8a/VC+gil/58= -cloud.google.com/go/monitoring v1.15.1/go.mod h1:lADlSAlFdbqQuwwpaImhsJXu1QSdd3ojypXrFSMr2rM= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM= -cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= -cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= -cloud.google.com/go/trace v1.10.1 h1:EwGdOLCNfYOOPtgqo+D2sDLZmRCEO1AagRTJCU6ztdg= -cloud.google.com/go/trace v1.10.1/go.mod h1:gbtL94KE5AJLH3y+WVpfWILmqgc6dXcqgNXdOPAQTYk= -contrib.go.opencensus.io/exporter/stackdriver v0.12.6/go.mod h1:8x999/OcIPy5ivx/wDiV7Gx4D+VUPODf0mWRGRc5kSk= -contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys= -contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8= -contrib.go.opencensus.io/exporter/zipkin v0.1.1 h1:PR+1zWqY8ceXs1qDQQIlgXe+sdiwCf0n32bH4+Epk8g= -contrib.go.opencensus.io/exporter/zipkin v0.1.1/go.mod h1:GMvdSl3eJ2gapOaLKzTKE3qDgUkJ86k9k3yY2eqwkzc= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= -github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.32.3 h1:fiyErF/p5fz79DvMCca9ayvYiWYsFP1oJbskt9fjo8I= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v0.32.3/go.mod h1:s7Gpwj0tk7XnVCm4BQEmx/mbS36SuTCY/vMB2SNxe8o= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.15.0 h1:5uR5WqunMUqN5Z+USN/N25aM7zWd0JUCIfz1B/w0HtA= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.15.0/go.mod h1:LTScD9l1w6+z1IB3FKtXxS4oenRlIJQQrIiV/Iq1Bsw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.39.0 h1:RDD62LpQbuv4rpLOm0w1zlLIcIo7k+zi3EZV5nVyAo8= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.39.0/go.mod h1:PV+bUv9S+/W9PmZECvnC39uIEYnDL9veytwZrMqPexc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.39.0 h1:uZvy89rOd+9ryIir65RO7BmKYxQ9uBbFcnNcslu6RIM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.39.0/go.mod h1:lz6DEePTxmjvYMtusOoS3qDAErC0STi/wmvqJucKY28= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.0.0-20221018185641-36f91511cfd7 h1:4cXY9jZO7UoRYKyD+CssnBlwn2HTeUzCQ1b44PJijzc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/propagator v0.0.0-20221018185641-36f91511cfd7/go.mod h1:FwtSi1M0P8cuMlHxVso1vcivukprUr1bBwf15CRypOI= -github.com/KimMachineGun/automemlimit v0.2.4 h1:GBty8TK8k0aJer1Pq5/3Vdt2ef+YpLhcqNo+PSD5CoI= -github.com/KimMachineGun/automemlimit v0.2.4/go.mod h1:38QAnnnNhnFuAIW3+aPlaVUHqzE9buJYZK3m/jsra8E= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/RoaringBitmap/roaring v0.9.4 h1:ckvZSX5gwCRaJYBNe7syNawCU5oruY9gQmjXlp4riwo= -github.com/RoaringBitmap/roaring v0.9.4/go.mod h1:icnadbWcNyfEHlYdr+tDlOTih1Bf/h+rzPpv4sbomAA= -github.com/ShinyTrinkets/meta-logger v0.2.0 h1:oR533+wuhSJ+vLsnSq1CBSGQygNv8nDsvuRUVcOls0g= -github.com/ShinyTrinkets/meta-logger v0.2.0/go.mod h1:cY1KnpPfpLIopR+arZXHYVrVGO6AETrhi3HmRGFjU+U= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/abourget/llerrgroup v0.2.0 h1:2nPXy6Owo/KOKDQYvjMmS8rsjtitvuP2OEGrqgpj428= -github.com/abourget/llerrgroup v0.2.0/go.mod h1:QukSa1Sim/0R4aRlWdiBdAy+0i1PBfOd1WHpfYM1ngA= -github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aws/aws-sdk-go v1.22.1/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.44.325 h1:jF/L99fJSq/BfiLmUOflO/aM+LwcqBm0Fe/qTK5xxuI= -github.com/aws/aws-sdk-go v1.44.325/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/azer/is-terminal v1.0.0 h1:COvj8jmg2xMz0CqHn4Uu8X1m7Dmzmu0CpciBaLtJQBg= -github.com/azer/is-terminal v1.0.0/go.mod h1:5geuIpRQvdv6g/Q1MwXHbmNUlFLg8QcheGk4dZOmxQU= -github.com/azer/logger v1.0.0 h1:3T4BnTLyndJWHajOyECt2kAhnvP30KCrVAkYcMjHrXk= -github.com/azer/logger v1.0.0/go.mod h1:iaDID7UeBTyUh31bjGFlLkr87k23z/mHMMLzt6YQQHU= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.3.1 h1:y+qrlmq3XsWi+xZqSaueaE8ry8Y127iMxlMfqcK8p0g= -github.com/bits-and-blooms/bitset v1.3.1/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/blendle/zapdriver v1.3.1/go.mod h1:mdXfREi6u5MArG4j9fewC+FGnXaBR+T4Ox4J2u4eHCc= -github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d h1:fSlGu5ePbkjBidXuj2O5j9EcYrVB5Cr6/wdkYyDgxZk= -github.com/blendle/zapdriver v1.3.2-0.20200203083823-9200777f8a3d/go.mod h1:yCBkgASmKHgUOFjK9h1sOytUVgA+JkQjqj3xYP4AdWY= -github.com/bobg/go-generics/v2 v2.1.1 h1:4rN9upY6Xm4TASSMeH+NzUghgO4h/SbNrQphIjRd/R0= -github.com/bobg/go-generics/v2 v2.1.1/go.mod h1:iPMSRVFlzkJSYOCXQ0n92RA3Vxw0RBv2E8j9ZODXgHk= -github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg= -github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8= -github.com/bufbuild/connect-grpchealth-go v1.1.1 h1:ldceS3m7+Qvl3GI4yzB4oCg3uOdD+Y1bytc/5xuMpqo= -github.com/bufbuild/connect-grpchealth-go v1.1.1/go.mod h1:9KbkogLoUIxOTPKyWDv5evkawr1IYXaHax4XoUHCgoQ= -github.com/bufbuild/connect-grpcreflect-go v1.0.0 h1:zWsLFYqrT1O2sNJFYfTXI5WxbAyiY2dvevvnJHPtV5A= -github.com/bufbuild/connect-grpcreflect-go v1.0.0/go.mod h1:825I20H8bfE9rLnBH/046JSpmm3uwpNYdG4duCARetc= -github.com/bufbuild/connect-opentelemetry-go v0.3.0 h1:AuZi3asTDKmjGtd2aqpyP4p5QvBFG/YEaHopViLatnk= -github.com/bufbuild/connect-opentelemetry-go v0.3.0/go.mod h1:r1ppyTtu1EWeRodk4Q/JbyQhIWtO7eR3GoRDzjeEcNU= -github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= -github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= -github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/logex v1.2.0 h1:+eqR0HfOetur4tgnC8ftU5imRnhi4te+BadWS95c5AM= -github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/readline v1.5.0 h1:lSwwFrbNviGePhkewF1az4oLmcwqCZijQ2/Wi3BGHAI= -github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23 h1:dZ0/VyGgQdVGAss6Ju0dt5P0QltE0SFY5Woh6hbIfiQ= -github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.4.0 h1:QlHdikaxALkqWasW8hAC1mfR0jdmvbfaBdBPFmRSglA= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nCtnAiZdYFd45cYZPs8vOOIYKfk= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/cgroups v1.0.4 h1:jN/mbWBEaz+T1pi5OFtnkQ+8qnmEbAr1Oo1FRm5B0dA= -github.com/containerd/cgroups v1.0.4/go.mod h1:nLNQtsF7Sl2HxNebu77i1R0oDlhiTG+kO4JTrUzo6IA= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg= -github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= -github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= -github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= -github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM= -github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= -github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/frankban/quicktest v1.14.0/go.mod h1:NeW+ay9A/U67EYXNFA1nPE8e/tnQv/09mUdL/ijj8og= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d h1:zqfo2jECgX5eYQseB/X+uV4Y5ocGOG/vG/LTztUCyPA= -github.com/go-json-experiment/json v0.0.0-20231013223334-54c864be5b8d/go.mod h1:6daplAwHHGbUGib4990V3Il26O0OC4aRyvewaaAihaA= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= -github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.4 h1:1kZ/sQM3srePvKs3tXAvQzo66XfcReoqFpIpIccE7Oc= -github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.11.0 h1:9V9PWXEsWnPpQhu/PeQIkS4eGzMlTLGgt80cUUI8Ki4= -github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ipfs/boxo v0.8.0 h1:UdjAJmHzQHo/j3g3b1bAcAXCj/GM6iTwvSlBDvPBNBs= -github.com/ipfs/boxo v0.8.0/go.mod h1:RIsi4CnTyQ7AUsNn5gXljJYZlQrHBMnJp94p73liFiA= -github.com/ipfs/go-cid v0.4.0 h1:a4pdZq0sx6ZSxbCizebnKiMCx/xI/aBBFlB73IgH4rA= -github.com/ipfs/go-cid v0.4.0/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk= -github.com/ipfs/go-ipfs-api v0.6.0 h1:JARgG0VTbjyVhO5ZfesnbXv9wTcMvoKRBLF1SzJqzmg= -github.com/ipfs/go-ipfs-api v0.6.0/go.mod h1:iDC2VMwN9LUpQV/GzEeZ2zNqd8NUdRmWcFM+K/6odf0= -github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= -github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= -github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= -github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= -github.com/jhump/protoreflect v1.14.0 h1:MBbQK392K3u8NTLbKOCIi3XdI+y+c6yt5oMq0X3xviw= -github.com/jhump/protoreflect v1.14.0/go.mod h1:JytZfP5d0r8pVNLZvai7U/MCuTWITgrI4tTg7puQFKI= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/josephburnett/jd v1.7.1 h1:oXBPMS+SNnILTMGj1fWLK9pexpeJUXtbVFfRku/PjBU= -github.com/josephburnett/jd v1.7.1/go.mod h1:R8ZnZnLt2D4rhW4NvBc/USTo6mzyNT6fYNIIWOJA9GY= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= -github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= -github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= -github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= -github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= -github.com/libp2p/go-flow-metrics v0.1.0 h1:0iPhMI8PskQwzh57jB9WxIuIOQ0r+15PChFGkx3Q3WM= -github.com/libp2p/go-flow-metrics v0.1.0/go.mod h1:4Xi8MX8wj5aWNDAZttg6UPmc0ZrnFNsMtpsYUClFtro= -github.com/libp2p/go-libp2p v0.26.3 h1:6g/psubqwdaBqNNoidbRKSTBEYgaOuKBhHl8Q5tO+PM= -github.com/libp2p/go-libp2p v0.26.3/go.mod h1:x75BN32YbwuY0Awm2Uix4d4KOz+/4piInkp4Wr3yOo8= -github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= -github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= -github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/manifoldco/promptui v0.3.2/go.mod h1:8JU+igZ+eeiiRku4T5BjtKh2ms8sziGpSYl1gN8Bazw= -github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= -github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y= -github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= -github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mostynb/go-grpc-compression v1.1.17 h1:N9t6taOJN3mNTTi0wDf4e3lp/G/ON1TP67Pn0vTUA9I= -github.com/mostynb/go-grpc-compression v1.1.17/go.mod h1:FUSBr0QjKqQgoDG/e0yiqlR6aqyXC39+g/hFLDfSsEY= -github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= -github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/mschoch/smat v0.2.0 h1:8imxQsjDm8yFEAVBe7azKmKSgzSkZXDuKkSq9374khM= -github.com/mschoch/smat v0.2.0/go.mod h1:kc9mz7DoBKqDyiRL7VZN8KvXQMWeTaVnttLRXOlotKw= -github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE= -github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI= -github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0= -github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4= -github.com/multiformats/go-multiaddr v0.8.0 h1:aqjksEcqK+iD/Foe1RRFsGZh8+XFiGo7FgUCZlpv3LU= -github.com/multiformats/go-multiaddr v0.8.0/go.mod h1:Fs50eBDWvZu+l3/9S6xAE7ZYj6yhxlvaVZjakWN7xRs= -github.com/multiformats/go-multibase v0.1.1 h1:3ASCDsuLX8+j4kx58qnJ4YFq/JWTJpCyDW27ztsVTOI= -github.com/multiformats/go-multibase v0.1.1/go.mod h1:ZEjHE+IsUrgp5mhlEAYjMtZwK1k4haNkcaPg9aoe1a8= -github.com/multiformats/go-multicodec v0.8.1 h1:ycepHwavHafh3grIbR1jIXnKCsFm0fqsfEOsJ8NtKE8= -github.com/multiformats/go-multicodec v0.8.1/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k= -github.com/multiformats/go-multihash v0.2.1 h1:aem8ZT0VA2nCHHk7bPJ1BjUbHNciqZC/d16Vve9l108= -github.com/multiformats/go-multihash v0.2.1/go.mod h1:WxoMcYG85AZVQUyRyo9s4wULvW5qrI9vb2Lt6evduFc= -github.com/multiformats/go-multistream v0.4.1 h1:rFy0Iiyn3YT0asivDUIR05leAdwZq3de4741sbiSdfo= -github.com/multiformats/go-multistream v0.4.1/go.mod h1:Mz5eykRVAjJWckE2U78c6xqdtyNUEhKSM0Lwar2p77Q= -github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8= -github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.5.1 h1:auzK7OI497k6x4OvWq+TKAcpcSAlod0doAH72oIN0Jw= -github.com/onsi/ginkgo/v2 v2.5.1/go.mod h1:63DOGlLAH8+REH8jUGdL3YpCpu7JODesutUjdENfUAc= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfPcEO5A= -github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= -github.com/paulbellamy/ratecounter v0.2.0 h1:2L/RhJq+HA8gBQImDXtLPrDXK5qAj6ozWVK/zFXVJGs= -github.com/paulbellamy/ratecounter v0.2.0/go.mod h1:Hfx1hDpSGoqxkVVpBi/IlYD7kChlfo5C6hzIHwPqfFE= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= -github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= -github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= -github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk= -github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/rs/cors v1.10.0 h1:62NOS1h+r8p1mW6FM0FSB0exioXLhd/sh15KpjWBZ+8= -github.com/rs/cors v1.10.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= -github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sethvargo/go-retry v0.2.3 h1:oYlgvIvsju3jNbottWABtbnoLC+GDtLdBHxKWxQm/iU= -github.com/sethvargo/go-retry v0.2.3/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= -github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/streamingfast/atm v0.0.0-20220131151839-18c87005e680 h1:fGJnUx0shX9Y312QOlz+/+yLquihXRhNqctJ26jtZZM= -github.com/streamingfast/atm v0.0.0-20220131151839-18c87005e680/go.mod h1:iISPGAstbUsPgyC3auLLi7PYUTi9lHv5z0COam0OPOY= -github.com/streamingfast/bstream v0.0.2-0.20231109200242-92c3eea7aaba h1:Ms7P4CTImBrfUsc+ULL3qitVZ1pHUWooF8qHjPlhYU0= -github.com/streamingfast/bstream v0.0.2-0.20231109200242-92c3eea7aaba/go.mod h1:Njkx972HcZiz0djWBylxqO/eq686eDGr+egQ1lePj3Q= -github.com/streamingfast/cli v0.0.4-0.20230825151644-8cc84512cd80 h1:UxJUTcEVkdZy8N77E3exz0iNlgQuxl4m220GPvzdZ2s= -github.com/streamingfast/cli v0.0.4-0.20230825151644-8cc84512cd80/go.mod h1:QxjVH73Lkqk+mP8bndvhMuQDUINfkgsYhdCH/5TJFKI= -github.com/streamingfast/dauth v0.0.0-20230929180355-921f9c9be330 h1:49JYZkn8ALGe+LhcACZyX3L9B8tIxRZ3F3l+OxmNMhY= -github.com/streamingfast/dauth v0.0.0-20230929180355-921f9c9be330/go.mod h1:zfq+mtesfbaZnNeh1BF+vo+zEFP1sat4pm3lvt40nRw= -github.com/streamingfast/dbin v0.9.1-0.20220513054835-1abebbb944ad h1:6z4uS6TlD9KoHdyE1vzoGsELVCCcviTFT/3/vqCylh8= -github.com/streamingfast/dbin v0.9.1-0.20220513054835-1abebbb944ad/go.mod h1:YStE7K5/GH47JsWpY7LMKsDaXXpMLU/M26vYFzXHYRk= -github.com/streamingfast/derr v0.0.0-20230515163924-8570aaa43fe1 h1:xJB7rXnOHLesosMjfwWsEL2i/40mFSkzenEb3M0qTyM= -github.com/streamingfast/derr v0.0.0-20230515163924-8570aaa43fe1/go.mod h1:QSm/AfaDsE0k1xBYi0lW580YJ/WDV/FKZI628tkZR0Y= -github.com/streamingfast/dgrpc v0.0.0-20230929132851-893fc52687fa h1:L/Ipge5pkZtyHucT7c8F/PiCitiNqQxjoUuxyzWKZew= -github.com/streamingfast/dgrpc v0.0.0-20230929132851-893fc52687fa/go.mod h1:AcY2kk28XswihgU6z37288a3ZF4gGGO7nNwlTI/vET4= -github.com/streamingfast/dlauncher v0.0.0-20230607184145-76399faad89e h1:Nh/gLDv8rOMIidb/gpO4rZOYVe09k+tof/trezkpku4= -github.com/streamingfast/dlauncher v0.0.0-20230607184145-76399faad89e/go.mod h1:xErlHEDd5+4NlR+Mg3ZtW7BTTLB0yZBxZAjHPrkk8X4= -github.com/streamingfast/dmetering v0.0.0-20230731155453-e1df53e362aa h1:bM6iy5X7Gtw1oh1bMxFmtroouKZu4K4BHXaFvR96jNw= -github.com/streamingfast/dmetering v0.0.0-20230731155453-e1df53e362aa/go.mod h1:3XggUfQMyciaue133qhbIkFqJQqNzozGpa/gI3sdwac= -github.com/streamingfast/dmetrics v0.0.0-20230919161904-206fa8ebd545 h1:SUl04bZKGAv207lp7/6CHOJIRpjUKunwItrno3K463Y= -github.com/streamingfast/dmetrics v0.0.0-20230919161904-206fa8ebd545/go.mod h1:JbxEDbzWRG1dHdNIPrYfuPllEkktZMgm40AwVIBENcw= -github.com/streamingfast/dstore v0.1.1-0.20230620124109-3924b3b36c77 h1:u7FWLqz3Uwff609Ja9M+3aGOWqBCVU7dx9i6R6Qc4qI= -github.com/streamingfast/dstore v0.1.1-0.20230620124109-3924b3b36c77/go.mod h1:ngKU7WzHwVjOFpt2g+Wtob5mX4IvN90HYlnARcTRbmQ= -github.com/streamingfast/dtracing v0.0.0-20220305214756-b5c0e8699839 h1:K6mJPvh1jAL+/gBS7Bh9jyzWaTib6N47m06gZOTUPwQ= -github.com/streamingfast/dtracing v0.0.0-20220305214756-b5c0e8699839/go.mod h1:huOJyjMYS6K8upTuxDxaNd+emD65RrXoVBvh8f1/7Ns= -github.com/streamingfast/firehose v0.1.1-0.20231109192301-ebfed7417cf6 h1:hcSx7R9f1y+wWoAkJc3XBUXi2p9bYlc2dbt+mZUwdbQ= -github.com/streamingfast/firehose v0.1.1-0.20231109192301-ebfed7417cf6/go.mod h1:lGC1T6mpAAApjBQNF5COSXb3SbrYRI3dBR1f6/PZE54= -github.com/streamingfast/firehose-core v0.2.3 h1:7wI73/VjVfpon9GRI/3Orrq9tjc0XY1Ng3ZxwjYjZII= -github.com/streamingfast/firehose-core v0.2.3/go.mod h1:1589JEzvmovM4wHbstbR4w7UI+tpw4bNI4I+LTE5mH8= -github.com/streamingfast/index-builder v0.0.0-20221031203737-fa2e70f09dc2 h1:dgYLhP3STiPi30fISAijFPEB11D4r1fQFc8D3cpgV5s= -github.com/streamingfast/index-builder v0.0.0-20221031203737-fa2e70f09dc2/go.mod h1:OYv1UX/kRsV9aP4SEwa9zpt34qGzdtJzOvdGn+n56as= -github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0 h1:g8eEYbFSykyzIyuxNMmHEUGGUvJE0ivmqZagLDK42gw= -github.com/streamingfast/jsonpb v0.0.0-20210811021341-3670f0aa02d0/go.mod h1:cTNObq2Uofb330y05JbbZZ6RwE6QUXw5iVcHk1Fx3fk= -github.com/streamingfast/logging v0.0.0-20210811175431-f3b44b61606a/go.mod h1:4GdqELhZOXj4xwc4IaBmzofzdErGynnaSzuzxy0ZIBo= -github.com/streamingfast/logging v0.0.0-20210908162127-bdc5856d5341/go.mod h1:4GdqELhZOXj4xwc4IaBmzofzdErGynnaSzuzxy0ZIBo= -github.com/streamingfast/logging v0.0.0-20220304183711-ddba33d79e27/go.mod h1:4GdqELhZOXj4xwc4IaBmzofzdErGynnaSzuzxy0ZIBo= -github.com/streamingfast/logging v0.0.0-20220304214715-bc750a74b424/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= -github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091 h1:RN5mrigyirb8anBEtdjtHFIufXdacyTi6i4KBfeNXeo= -github.com/streamingfast/logging v0.0.0-20230608130331-f22c91403091/go.mod h1:VlduQ80JcGJSargkRU4Sg9Xo63wZD/l8A5NC/Uo1/uU= -github.com/streamingfast/merger v0.0.3-0.20231027161314-209c2ddd8d96 h1:aq5hUjo+Y+3OUH2z1egyJ9fSepRvOzxgR+TYICdSEgE= -github.com/streamingfast/merger v0.0.3-0.20231027161314-209c2ddd8d96/go.mod h1:WGMs+zwpPQNfzRnOqnyNdQfyGSG4lXYWQacicAGiP4s= -github.com/streamingfast/node-manager v0.0.2-0.20230406142433-692298a8b8d2 h1:6Jdu6LBwaW38n2jjInFk1fM460cq+5paEAHGPPRWWN0= -github.com/streamingfast/node-manager v0.0.2-0.20230406142433-692298a8b8d2/go.mod h1:R5WwJuyNueq0QXKAFinTGU8zaON0hWJBFHX6KA9WZqk= -github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308 h1:xlWSfi1BoPfsHtPb0VEHGUcAdBF208LUiFCwfaVPfLA= -github.com/streamingfast/opaque v0.0.0-20210811180740-0c01d37ea308/go.mod h1:K1p8Bj/wG34KJvYzPUqtzpndffmpkrVY11u2hkyxCWQ= -github.com/streamingfast/overseer v0.2.1-0.20210326144022-ee491780e3ef h1:9IVFHRsqvI+vKJwgF1OMV6L55jHbaV/ZLoU4IAG/dME= -github.com/streamingfast/overseer v0.2.1-0.20210326144022-ee491780e3ef/go.mod h1:cq8CvbZ3ioFmGrHokSAJalS0lC+pVXLKhITScItUGXY= -github.com/streamingfast/pbgo v0.0.6-0.20221020131607-255008258d28 h1:wmQg8T0rIFl/R3dy97OWRi8OSdM3llvRw2p3TPFVKZQ= -github.com/streamingfast/pbgo v0.0.6-0.20221020131607-255008258d28/go.mod h1:huKwfgTGFIFZMKSVbD5TywClM7zAeBUG/zePZMqvXQQ= -github.com/streamingfast/relayer v0.0.2-0.20220909122435-e67fbc964fd9 h1:V3LPBmTofZbmT46qQsr0lFa+0qDHZNJXgqLRo9iZBHY= -github.com/streamingfast/relayer v0.0.2-0.20220909122435-e67fbc964fd9/go.mod h1:55E/1g+ojZoX86Odp48LFgceJVyh1xx9ZuhknKfmc/o= -github.com/streamingfast/sf-tracing v0.0.0-20230616174903-cd2ade641ca9 h1:YRwpVvLYa+FEJlTy0S7mk4UptYjk5zac+A+ZE1phOeA= -github.com/streamingfast/sf-tracing v0.0.0-20230616174903-cd2ade641ca9/go.mod h1:ktzt1BUj3GF+SKQHEmn3ShryJ7y87JeCHtaTGaDVATs= -github.com/streamingfast/shutter v1.5.0 h1:NpzDYzj0HVpSiDJVO/FFSL6QIK/YKOxY0gJAtyaTOgs= -github.com/streamingfast/shutter v1.5.0/go.mod h1:B/T6efqdeMGbGwjzPS1ToXzYZI4kDzI5/u4I+7qbjY8= -github.com/streamingfast/snapshotter v0.0.0-20230316190750-5bcadfde44d0 h1:Y15G1Z4fpEdm2b+/70owI7TLuXadlqBtGM7rk4Hxrzk= -github.com/streamingfast/snapshotter v0.0.0-20230316190750-5bcadfde44d0/go.mod h1:/Rnz2TJvaShjUct0scZ9kKV2Jr9/+KBAoWy4UMYxgv4= -github.com/streamingfast/substreams v1.1.20 h1:61k/HKti9xo7vDAu5zew/VL8qzY+ye/9Zzt1om+tVks= -github.com/streamingfast/substreams v1.1.20/go.mod h1:Ak7a+EM8MRehep0ZaQD1NwG27ZE9auZY9+/VLbhBnDU= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= -github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= -github.com/test-go/testify v1.1.4 h1:Tf9lntrKUMHiXQ07qBScBTSA0dhYQlu83hswqelv1iE= -github.com/test-go/testify v1.1.4/go.mod h1:rH7cfJo/47vWGdi4GPj16x3/t1xGOj2YxzmNQzk2ghU= -github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ= -github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= -github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= -github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c h1:GGsyl0dZ2jJgVT+VvWBf/cNijrHRhkrTjkmp5wg7li0= -github.com/whyrusleeping/tar-utils v0.0.0-20180509141711-8c6c8ba81d5c/go.mod h1:xxcJeBb7SIUl/Wzkz1eVKJE/CB34YNrqX2TQI6jY9zs= -github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8= -github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/detectors/gcp v1.9.0 h1:en6EnI47A3nrVtKCIgwFS5SUAhYW8LHn4Rkmm6HGhzg= -go.opentelemetry.io/contrib/detectors/gcp v1.9.0/go.mod h1:OqG0FEnmWeJWYVrEovaHXHXY4bVTnp/WfTzhwrsGWlw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0 h1:b8xjZxHbLrXAum4SxJd1Rlm7Y/fKaB+6ACI7/e5EfSA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.44.0/go.mod h1:1ei0a32xOGkFoySu7y1DAHfcuIhC0pNZpvY2huXuMy4= -go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= -go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= -go.opentelemetry.io/otel/exporters/jaeger v1.16.0 h1:YhxxmXZ011C0aDZKoNw+juVWAmEfv/0W2XBOv9aHTaA= -go.opentelemetry.io/otel/exporters/jaeger v1.16.0/go.mod h1:grYbBo/5afWlPpdPZYhyn78Bk04hnvxn2+hvxQhKIQM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0 h1:IAtl+7gua134xcV3NieDhJHjjOVeJhXAnYf/0hswjUY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.18.0/go.mod h1:w+pXobnBzh95MNIkeIuAKcHe/Uu/CX2PKIvBP6ipKRA= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0 h1:yE32ay7mJG2leczfREEhoW3VfSZIvHaB+gvVo1o8DQ8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.18.0/go.mod h1:G17FHPDLt74bCI7tJ4CMitEk4BXTYG4FW6XUpkPBXa4= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1 h1:2PunuO5SbkN5MhCbuHCd3tC6qrcaj+uDAkX/qBU5BAs= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.15.1/go.mod h1:q8+Tha+5LThjeSU8BW93uUC5w5/+DnYHMKBMpRCsui0= -go.opentelemetry.io/otel/exporters/zipkin v1.15.1 h1:B6s/o48bx00ayJu7F+jIMJfhPTyxW+S8vthjTZMNBj0= -go.opentelemetry.io/otel/exporters/zipkin v1.15.1/go.mod h1:EjjV7/YfYXG+khxCOfG6PPeRGoOmtcSusyW66qPqpRQ= -go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= -go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= -go.opentelemetry.io/otel/sdk v1.18.0 h1:e3bAB0wB3MljH38sHzpV/qWrOTCFrdZF2ct9F8rBkcY= -go.opentelemetry.io/otel/sdk v1.18.0/go.mod h1:1RCygWV7plY2KmdskZEDDBs4tJeHG92MdHZIluiYs/M= -go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= -go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= -go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= -go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I= -go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= -go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/automaxprocs v1.5.1 h1:e1YG66Lrk73dn4qhg8WFSvhF0JuFQF0ERIp4rpuV8Qk= -go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.14.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= -golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= -golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190804053845-51ab0e2deafa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= -golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA= -golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= -google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= -google.golang.org/api v0.126.0 h1:q4GJq+cAdMAC7XP7njvQ4tvohGLiSlytuL4BQxbIZ+o= -google.golang.org/api v0.126.0/go.mod h1:mBwVAtz+87bEN6CbA1GtZPDOqY2R5ONPqJeIlvyo4Aw= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g= -google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98/go.mod h1:S7mY02OqCJTD0E1OiQy1F72PWFB4bZJ87cAtLPYgDR0= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw= -google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o= -google.golang.org/grpc v1.58.0/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/olivere/elastic.v3 v3.0.75 h1:u3B8p1VlHF3yNLVOlhIWFT3F1ICcHfM5V6FFJe6pPSo= -gopkg.in/olivere/elastic.v3 v3.0.75/go.mod h1:yDEuSnrM51Pc8dM5ov7U8aI/ToR3PG0llA8aRv2qmw0= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= -k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= -k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= -k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= -k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= -k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= -k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= -k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= -k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -lukechampine.com/blake3 v1.1.7 h1:GgRMhmdsuK8+ii6UZFDL8Nb+VyMwadAgcJyfYHxG6n0= -lukechampine.com/blake3 v1.1.7/go.mod h1:tkKEOtDkNtklkXtLNEOGNq5tcV90tJiA1vAA12R78LA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/firehose/firehose-fuel/pb/sf/fuel/type/v1/type.go b/firehose/firehose-fuel/pb/sf/fuel/type/v1/type.go deleted file mode 100644 index 9b69114..0000000 --- a/firehose/firehose-fuel/pb/sf/fuel/type/v1/type.go +++ /dev/null @@ -1,41 +0,0 @@ -package pbfuel - -import ( - "encoding/hex" - "time" -) - -func (b *Block) GetFirehoseBlockID() string { - return hex.EncodeToString(b.Id) -} - -func (b *Block) GetFirehoseBlockNumber() uint64 { - return uint64(b.Height) -} - -func (b *Block) GetFirehoseBlockParentNumber() uint64 { - // TODO: This needs to be adapted for your own chain rules! - return b.GetFirehoseBlockNumber() - 1 -} - -func (b *Block) GetFirehoseBlockParentID() string { - return hex.EncodeToString(b.PrevId) -} - -func (b *Block) GetFirehoseBlockTime() time.Time { - return time.Unix(0, int64(b.Timestamp)).UTC() -} - -func (b *Block) GetFirehoseBlockVersion() int32 { - // TODO: This needs to be adapted for your own version used in pbbstream - return 1 -} - -func (b *Block) GetFirehoseBlockLIBNum() uint64 { - if b.Height == 0 { - return 0 - } - - // TODO: This needs to be adapted for your own chain rules! - return b.GetFirehoseBlockNumber() - 1 -} diff --git a/firehose/firehose-fuel/proto/buf.md b/firehose/firehose-fuel/proto/buf.md deleted file mode 100644 index d4af218..0000000 --- a/firehose/firehose-fuel/proto/buf.md +++ /dev/null @@ -1,11 +0,0 @@ -### StreamingFast Firehose Fuel Types - -Protobuf definitions when dealing with Firehose Fuel chain(s). - -Foremost, this provides [sf.fuel.types.v1.Block](https://buf.build/FuelLabs/firehose-fuel/docs/main:sf.fuel.type.v1#sf.fuel.type.v1.Block) which is used for consuming Firehose & Substreams fuel block model. - -This is probably the only model you should really care about, the rest are meant for internal communications for Firehose operators. - -Useful links: -- Documentation: [https://substreams.streamingfast.io/](https://substreams.streamingfast.io/) ([Firehose Docs](https://firehose.streamingfast.io/)) -- Source: [https://github.com/FuelLabs/firehose-fuel](https://github.com/FuelLabs/firehose-fuel) \ No newline at end of file diff --git a/firehose/firehose-fuel/proto/buf.yaml b/firehose/firehose-fuel/proto/buf.yaml deleted file mode 100644 index 37f952d..0000000 --- a/firehose/firehose-fuel/proto/buf.yaml +++ /dev/null @@ -1,10 +0,0 @@ -version: v1 -name: buf.build/Fuellabs/firehose-fuel - -lint: - use: - - DEFAULT - -breaking: - use: - - FILE diff --git a/firehose/firehose-fuel/proto/last_generate.txt b/firehose/firehose-fuel/proto/last_generate.txt deleted file mode 100644 index 8ed97a9..0000000 --- a/firehose/firehose-fuel/proto/last_generate.txt +++ /dev/null @@ -1,2 +0,0 @@ -generate.sh - Mon Dec 11 09:40:08 EET 2023 - hannes -FuelLabs/firehose-fuel/proto revision: 1e24bd4 diff --git a/firehose/firehose-fuel/proto/sf/fuel/type/v1/type.proto b/firehose/firehose-fuel/proto/sf/fuel/type/v1/type.proto deleted file mode 100644 index 354da9d..0000000 --- a/firehose/firehose-fuel/proto/sf/fuel/type/v1/type.proto +++ /dev/null @@ -1,290 +0,0 @@ -syntax = "proto3"; - -package sf.fuel.type.v1; - -option go_package = "github.com/FuelLabs/firehose-fuel/pb/sf/fuel/type/v1;pbfuel"; - -message Block { - bytes id = 1; - uint32 height = 2; - uint64 da_height = 3; - uint64 msg_receipt_count = 4; - bytes tx_root = 5; - bytes msg_receipt_root = 6; - bytes prev_id = 7; - bytes prev_root = 8; - fixed64 timestamp = 9; - bytes application_hash = 10; - repeated Transaction transactions = 11; -} - -message Transaction { - bytes id = 1; - repeated Receipt receipts = 2; - oneof kind { - Script script = 3; - Create create = 4; - Mint mint = 5; - } -} - -message Script { - uint64 script_gas_limit = 1; - bytes script = 2; - bytes script_data = 3; - Policies policies = 4; - repeated Input inputs = 5; - repeated Output outputs = 6; - repeated bytes witnesses = 7; - bytes receipts_root = 8; -} - -message Create { - uint64 bytecode_length = 1; - uint32 bytecode_witness_index = 2; - Policies policies = 3; - repeated StorageSlot storage_slots = 4; - repeated Input inputs = 5; - repeated Output outputs = 6; - repeated bytes witnesses = 7; - bytes salt = 8; -} - -message Mint { - TxPointer tx_pointer = 1; - InputContract input_contract = 2; - OutputContract output_contract = 3; - uint64 mint_amount = 4; - bytes mint_asset_id = 5; -} - -message Input { - oneof kind { - Coin coin_signed = 1; - Coin coin_predicate = 2; - InputContract contract = 3; - Message message_coin_signed = 4; - Message message_coin_predicate = 5; - Message message_data_signed = 6; - Message message_data_predicate = 7; - } -} - -message Coin { - UtxoId utxo_id = 1; - bytes owner = 2; - uint64 amount = 3; - bytes asset_id = 4; - TxPointer tx_pointer = 5; - uint32 witness_index = 6; - uint32 maturity = 7; - uint64 predicate_gas_used = 8; - bytes predicate = 9; - bytes predicate_data = 10; -} - -message Message { - bytes sender = 1; - bytes recipient = 2; - uint64 amount = 3; - bytes nonce = 4; - uint32 witness_index = 5; - uint64 predicate_gas_used = 6; - bytes data = 7; - bytes predicate = 8; - bytes predicate_data = 9; -} - -message Output { - oneof kind { - OutputCoin coin = 1; - OutputContract contract = 2; - OutputCoin change = 3; - OutputCoin variable = 4; - OutputContractCreated contract_created = 5; - } -} - -message OutputCoin { - bytes to = 1; - uint64 amount = 2; - bytes asset_id = 3; -} - -message OutputContractCreated { - bytes contract_id = 1; - bytes state_root = 2; -} - -message InputContract { - UtxoId utxo_id = 1; - bytes balance_root = 2; - bytes state_root = 3; - TxPointer tx_pointer = 4; - bytes contract_id = 5; -} - -message OutputContract { - uint32 input_index = 1; - bytes balance_root = 2; - bytes state_root = 3; -} - -message StorageSlot { - bytes key = 1; - bytes value = 2; -} - -message UtxoId { - bytes tx_id = 1; - uint32 output_index = 2; -} - -message TxPointer { - uint32 block_height = 1; - uint32 tx_index = 2; -} - -message Policies { - repeated uint64 values = 1; -} - -message PanicInstruction { - uint32 reason = 1; - uint32 raw_instruction = 2; -} - -message Receipt { - oneof kind { - CallReceipt call = 1; - ReturnReceipt return_value = 2; - ReturnDataReceipt return_data = 3; - PanicReceipt panic = 4; - RevertReceipt revert = 5; - LogReceipt log = 6; - LogDataReceipt log_data = 7; - TransferReceipt transfer = 8; - TransferOutReceipt transfer_out = 9; - ScriptResultReceipt script_result = 10; - MessageOutReceipt message_out = 11; - MintReceipt mint = 12; - BurnReceipt burn = 13; - } -} - -message CallReceipt { - bytes id = 1; - bytes to = 2; - uint64 amount = 3; - bytes asset_id = 4; - uint64 gas = 5; - uint64 param1 = 6; - uint64 param2 = 7; - uint64 pc = 8; - uint64 is = 9; -} - -message ReturnReceipt { - bytes id = 1; - uint64 val = 2; - uint64 pc = 3; - uint64 is = 4; -} - -message ReturnDataReceipt { - bytes id = 1; - uint64 ptr = 2; - uint64 len = 3; - bytes digest = 4; - uint64 pc = 5; - uint64 is = 6; - bytes data = 7; -} - -message PanicReceipt { - bytes id = 1; - PanicInstruction reason = 2; - uint64 pc = 3; - uint64 is = 4; - bytes contract_id = 5; -} - -message RevertReceipt { - bytes id = 1; - uint64 ra = 2; - uint64 pc = 3; - uint64 is = 4; -} - -message LogReceipt { - bytes id = 1; - uint64 ra = 2; - uint64 rb = 3; - uint64 rc = 4; - uint64 rd = 5; - uint64 pc = 6; - uint64 is = 7; -} - -message LogDataReceipt { - bytes id = 1; - uint64 ra = 2; - uint64 rb = 3; - uint64 ptr = 4; - uint64 len = 5; - bytes digest = 6; - uint64 pc = 7; - uint64 is = 8; - bytes data = 9; -} - -message TransferReceipt { - bytes id = 1; - bytes to = 2; - uint64 amount = 3; - bytes asset_id = 4; - uint64 pc = 5; - uint64 is = 6; -} - -message TransferOutReceipt { - bytes id = 1; - bytes to = 2; - uint64 amount = 3; - bytes asset_id = 4; - uint64 pc = 5; - uint64 is = 6; -} - -message ScriptResultReceipt { - // Values: Success = 0, Revert = 1, Panic = 2, others allowed - uint64 result = 1; - uint64 gas_used = 2; -} - -message MessageOutReceipt { - bytes sender = 1; - bytes recipient = 2; - uint64 amount = 3; - bytes nonce = 4; - uint64 len = 5; - bytes digest = 6; - bytes data = 7; -} - -message MintReceipt { - bytes sub_id = 1; - bytes contract_id = 2; - uint64 val = 3; - uint64 pc = 4; - uint64 is = 5; -} - -message BurnReceipt { - bytes sub_id = 1; - bytes contract_id = 2; - uint64 val = 3; - uint64 pc = 4; - uint64 is = 5; -} - diff --git a/firehose/firehose-fuel/substreams.yaml b/firehose/firehose-fuel/substreams.yaml deleted file mode 100644 index 08523b1..0000000 --- a/firehose/firehose-fuel/substreams.yaml +++ /dev/null @@ -1,15 +0,0 @@ -specVersion: v0.1.0 -package: - name: fuel - version: v1.0.0 - url: https://github.com/FuelLabs/firehose-fuel - doc: | - Protobuf definitions for developing Substreams Fuel modules. - - This package does not include any modules. - -protobuf: - files: - - sf/fuel/type/v1/type.proto - importPaths: - - ./proto diff --git a/firehose/firehose-fuel/tools/docker/99-firehose.sh b/firehose/firehose-fuel/tools/docker/99-firehose.sh deleted file mode 100644 index 09f18c8..0000000 --- a/firehose/firehose-fuel/tools/docker/99-firehose.sh +++ /dev/null @@ -1,7 +0,0 @@ -## -# This is place inside `/etc/profile.d/99-firehose-fuel.sh` -# on built system an executed to provide message to use when they -# connect on the box. -export PATH=$PATH:/app - -cat /etc/motd diff --git a/firehose/firehose-fuel/tools/docker/motd_generic b/firehose/firehose-fuel/tools/docker/motd_generic deleted file mode 100644 index 78f2c17..0000000 --- a/firehose/firehose-fuel/tools/docker/motd_generic +++ /dev/null @@ -1,8 +0,0 @@ - _____ __ - / __(_)______ / / ___ ___ ___ - / _// / __/ -_) _ \/ _ \(_- to show all): - - Check if running reader-is-running - - - Start or restart instance reader-resume - - Stop instance reader-maintenance - - Show final start command used reader-start-command - - Debug Firehose logs for 30s reader-debug-firehose-logs-30s diff --git a/firehose/firehose-fuel/tools/docker/scripts/reader-debug-fire-logs-30s b/firehose/firehose-fuel/tools/docker/scripts/reader-debug-fire-logs-30s deleted file mode 100755 index 7c3adbc..0000000 --- a/firehose/firehose-fuel/tools/docker/scripts/reader-debug-fire-logs-30s +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -port=${MANAGER_API_PORT:-8080} - -# description: Stop geth for maintenance, start it back with debug firehose enabled, wait 60s and remove the flag -curl -sS -XPOST localhost:$port/v1/maintenance?sync=true -curl -sS -XPOST -d "debug-firehose-logs=true" localhost:$port/v1/resume?sync=true - -# We give 10s more than 30s to account for startup time of geth -sleep 40s - -curl -sS -XPOST localhost:$port/v1/maintenance?sync=true -curl -sS -XPOST -d "debug-firehose-logs=false" localhost:$port/v1/resume?sync=true diff --git a/firehose/firehose-fuel/tools/docker/scripts/reader-is-running b/firehose/firehose-fuel/tools/docker/scripts/reader-is-running deleted file mode 100755 index 5ca0419..0000000 --- a/firehose/firehose-fuel/tools/docker/scripts/reader-is-running +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -port=${MANAGER_API_PORT:-8080} - -# description: prints either 'syncing' if this node is catching up with the network -if curl "localhost:${port}/v1/is_running" 2> /dev/null | grep -q "true"; then - echo Running -else - echo "Not running" -fi diff --git a/firehose/firehose-fuel/tools/docker/scripts/reader-maintenance b/firehose/firehose-fuel/tools/docker/scripts/reader-maintenance deleted file mode 100755 index 5d12091..0000000 --- a/firehose/firehose-fuel/tools/docker/scripts/reader-maintenance +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# description: Stop geth for maintenance - -port=${MANAGER_API_PORT:-8080} - -curl -sS -XPOST localhost:$port/v1/maintenance?sync=true diff --git a/firehose/firehose-fuel/tools/docker/scripts/reader-resume b/firehose/firehose-fuel/tools/docker/scripts/reader-resume deleted file mode 100755 index 36c6701..0000000 --- a/firehose/firehose-fuel/tools/docker/scripts/reader-resume +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -# description: Start geth after maintenance - -port=${MANAGER_API_PORT:-8080} - -curl -sS -XPOST localhost:$port/v1/resume?sync=true diff --git a/firehose/firehose-fuel/tools/docker/scripts/reader-start-command b/firehose/firehose-fuel/tools/docker/scripts/reader-start-command deleted file mode 100755 index 25b6620..0000000 --- a/firehose/firehose-fuel/tools/docker/scripts/reader-start-command +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -port=${MANAGER_API_PORT:-8080} - -curl -sS -XGET localhost:$port/v1/start_command diff --git a/firehose/substreams/.dockerignore b/firehose/substreams/.dockerignore new file mode 100644 index 0000000..20aae9c --- /dev/null +++ b/firehose/substreams/.dockerignore @@ -0,0 +1,6 @@ +.git +target +**/target +dist +rust + diff --git a/firehose/substreams/.gitbook.yaml b/firehose/substreams/.gitbook.yaml new file mode 100644 index 0000000..b0976c5 --- /dev/null +++ b/firehose/substreams/.gitbook.yaml @@ -0,0 +1,70 @@ +root: ./docs/ + +structure: + summary: SUMMARY.md + +redirects: + concepts-and-fundamentals/benefits: ./README.md + concepts-and-fundamentals/fundamentals: ./README.md + concepts-and-fundamentals/modules: ./new/common/manifest-modules.md + developers-guide/creating-protobuf-schema: ./new/develop/creating-protobuf-schemas.md + developers-guide/creating-your-manifest: ./new/common/manifest-modules.md + developers-guide/gui: ./new/references/gui.md + developers-guide/installation-requirements: ./new/common/installing-the-cli.md + developers-guide/overview: ./new/develop/init-project.md + developers-guide/parallel-execution: ./new/develop/architecture.md + developers-guide/running-substreams: ./new/common/running-substreams.md + developers-guide/modules/inputs: ./new/develop/modules/inputs.md + developers-guide/modules/outputs: ./new/develop/modules/outputs.md + developers-guide/modules/setting-up-handlers: ./new/develop/modules/setting-up-handlers.md + developers-guide/modules/types: ./new/develop/modules/types.md + developers-guide/modules/writing-module-handlers: ./new/develop/modules/writing-module-handlers.md + developers-guide/cookbook/advanced-params: ./new/develop/parameterized-modules.md + developers-guide/cookbook/aggregation-windows: ./new/develop/modules/aggregation-windows.md + developers-guide/cookbook/dynamic-data-sources: ./new/develop/modules/dynamic-data-sources.md + developers-guide/cookbook/factory-params: ./new/develop/parameterized-modules.md + developers-guide/cookbook/keys-in-stores: ./new/develop/modules/keys-in-stores.md + developers-guide/sink-targets/substreams-powered-subgraph: ./new/consume/subgraph/subgraph.md + developers-guide/sink-targets/substreams-sink-sql: ./new/consume/sql/sql.md + developers-guide/sink-targets/substreams-sink-files: ./new/consume/other-sinks/files.md + developers-guide/sink-targets/substreams-sink-kv: ./new/consume/other-sinks/kv.md + developers-guide/sink-targets/substreams-sink-mongodb: ./new/consume/other-sinks/mongodb.md + developers-guide/sink-targets/substreams-sink-prometheus: ./new/consume/other-sinks/prometheus.md + getting-started/installing-the-cli: ./new/common/installing-the-cli.md + getting-started/quickstart: ./new/develop/init-project.md + references-and-specs/chains-and-endpoints: ./new/references/chains-and-endpoints.md + references-and-specs/command-line-interface: ./new/references/command-line-interface.md + references-and-specs/examples: ./new/tutorials/overview.md + references-and-specs/gui: ./new/references/gui.md + references-and-specs/faq: ./new/references/faq.md + references-and-specs/manifests: ./new/references/manifests.md + references-and-specs/packages: ./new/common/packages.md + references-and-specs/rust-crates: ./new/develop/rust-crates.md + glossary/glossary: ./new/references/glossary.md + quick-access/change-log: ./new/references/change-log.md + tutorials/from-ethereum-address-to-sql: ./new/consume/sql/deployable-services/remote-service.md + tutorials/substreams-sql: ./new/consume/sql/deployable-services/local-service.md + tutorials/exploring-ethereum: ./new/tutorials/evm/exploring-ethereum.md + tutorials/exploring-ethereum/overview: ./new/tutorials/evm/exploring-ethereum.md + tutorials/exploring-ethereum/map_block_meta_module: ./new/tutorials/evm/exploring-ethereum/map_block_meta_module.md + tutorials/exploring-ethereum/map_filter_transactions_module: ./new/tutorials/evm/exploring-ethereum/map_filter_transactions_module.md + tutorials/exploring-ethereum/map_contract_events_module: ./new/tutorials/evm/exploring-ethereum/map_contract_events_module.md + tutorials/rust/overview: ./new/tutorials/rust/overview.md + tutorials/rust: ./new/tutorials/rust/overview.md + tutorials/rust/option: ./new/tutorials/rust/option.md + tutorials/rust/result: ./new/tutorials/rust/result.md + tutorials/eth-calls: ./new/develop/chain-specific/evm/eth-calls.md + documentation/consume/other-ways-of-consuming: ./new/consume/other-sinks/README.md + documentation/consume/other-ways-of-consuming/kv: ./new/consume/other-sinks/kv.md + documentation/consume/other-ways-of-consuming/mongodb: ./new/consume/other-sinks/mongodb.md + documentation/consume/other-ways-of-consuming/files: ./new/consume/other-sinks/files.md + documentation/consume/other-ways-of-consuming/prometheus: ./new/consume/other-sinks/prometheus.md + documentation/consume/other-ways-of-consuming/pubsub: ./new/consume/other-sinks/pubsub.md + + + + + + + + diff --git a/firehose/substreams/.github/workflows/buf.yml b/firehose/substreams/.github/workflows/buf.yml new file mode 100644 index 0000000..7084d2a --- /dev/null +++ b/firehose/substreams/.github/workflows/buf.yml @@ -0,0 +1,44 @@ +name: Buf + +on: + pull_request: + push: + tags: + - v* + branches: + - develop + - release/v* + - feature/* + +jobs: + buf: + runs-on: ubuntu-20.04 + if: "${{ !startsWith(github.event.head_commit.message, 'GitBook: [#') }}" + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup buf + uses: bufbuild/buf-setup-action@v1 + + - name: Lint protos + # TODO: Consider enabling this after fixing or ignoring the current linting errors? + if: false + uses: bufbuild/buf-lint-action@v1 + + - name: Check for breaking changes + if: github.event_name == 'pull_request' + uses: bufbuild/buf-breaking-action@v1 + with: + input: proto + against: 'https://github.com/${{ github.repository }}.git#branch=${{ github.base_ref }}' + + - uses: bufbuild/buf-push-action@v1 + if: github.event_name != 'pull_request' + with: + input: proto + buf_token: ${{ secrets.BUF_TOKEN }} + draft: ${{ !startsWith(github.ref, 'refs/tags/v') }} diff --git a/firehose/substreams/.github/workflows/docker.yml b/firehose/substreams/.github/workflows/docker.yml new file mode 100644 index 0000000..07c9aa1 --- /dev/null +++ b/firehose/substreams/.github/workflows/docker.yml @@ -0,0 +1,74 @@ +name: Build docker image + +on: + push: + tags: + - "[a-z0-9]+-v*" + branches: + - "dockerbuild" + - "develop" + - "release/v*" + - "feature/*" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-20.04 + if: "${{ !startsWith(github.event.head_commit.message, 'GitBook: [#') }}" + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.21.x + + - uses: actions/cache@v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Build Binary + run: go build -v -o ./substreams ./cmd/substreams + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate docker tags/labels from github build context + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=tag + type=sha,prefix= + type=raw,enable=${{ github.ref == 'refs/heads/develop' }},value=develop + type=raw,enable=${{ startsWith(github.ref, 'refs/heads/release/v') }},value=${{ steps.extract_branch.outputs.release_train }} + flavor: | + latest=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + file: ./Dockerfile.github + push: true + tags: ${{ steps.meta.outputs.tags }} + # org.opencontainers.image.version will match the tag name + labels: ${{ steps.meta.outputs.labels }} diff --git a/firehose/substreams/.github/workflows/gitpod.yml b/firehose/substreams/.github/workflows/gitpod.yml new file mode 100644 index 0000000..51cec18 --- /dev/null +++ b/firehose/substreams/.github/workflows/gitpod.yml @@ -0,0 +1,45 @@ +name: gitpod-docker + +on: + push: + tags: + - "v*" + branches: + - "develop" + +jobs: + docker: + runs-on: ubuntu-latest + if: "${{ !startsWith(github.event.head_commit.message, 'GitBook: [#') }}" + steps: + - uses: actions/checkout@v2 + + - name: Docker meta + id: docker_meta + uses: crazy-max/ghaction-docker-meta@v1 + with: + images: ghcr.io/streamingfast/substreams-gitpod + tag-sha: true + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: Dockerfile.gitpod + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{steps.docker_meta.outputs.tags}} + labels: ${{steps.docker_meta.outputs.labels}} diff --git a/firehose/substreams/.github/workflows/main.yml b/firehose/substreams/.github/workflows/main.yml new file mode 100644 index 0000000..75353ae --- /dev/null +++ b/firehose/substreams/.github/workflows/main.yml @@ -0,0 +1,50 @@ +name: Build and Test + +on: + push: + branches: + - master + - develop + - "release/v*" + pull_request: + branches: + - "**" + +jobs: + test: + name: Test + runs-on: ${{ matrix.os }} + if: "${{ !startsWith(github.event.head_commit.message, 'GitBook: [#') }}" + strategy: + matrix: + go-version: [ 1.21.x ] + os: [ubuntu-latest, macos-latest] + steps: + - name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Check out code + uses: actions/checkout@v3 + + - name: Cache Go modules + uses: actions/cache@v3 + with: + # In order: + # * Module download cache + # * Build cache (Linux) + # * Build cache (Mac) + path: | + ~/go/pkg/mod + ~/.cache/go-build + ~/Library/Caches/go-build + key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go- + + - name: Run Tests + run: ./bin/test.sh + + - name: Build + run: go build ./... diff --git a/firehose/substreams/.gitignore b/firehose/substreams/.gitignore new file mode 100644 index 0000000..c06a27e --- /dev/null +++ b/firehose/substreams/.gitignore @@ -0,0 +1,15 @@ +*~ +.envrc +.env.release +.DS_Store +.idea +.vscode +target +.goreleaser.SOURCE_ME.sh +/dist +/substreams +.fleet +/codegen/test_substreams/src/ +/build +/sink-data +replay.log diff --git a/firehose/substreams/.sfreleaser b/firehose/substreams/.sfreleaser new file mode 100755 index 0000000..2ece294 --- /dev/null +++ b/firehose/substreams/.sfreleaser @@ -0,0 +1,6 @@ +global: + binary: substreams + language: golang + variant: application +release: + changelog-path: "./docs/release-notes/change-log.md" diff --git a/firehose/substreams/CHANGELOG.md b/firehose/substreams/CHANGELOG.md new file mode 100644 index 0000000..d93c521 --- /dev/null +++ b/firehose/substreams/CHANGELOG.md @@ -0,0 +1,3 @@ +# Change log + +The change log can be found [here](./docs/release-notes/change-log.md). \ No newline at end of file diff --git a/firehose/substreams/Dockerfile b/firehose/substreams/Dockerfile new file mode 100644 index 0000000..3c2fd14 --- /dev/null +++ b/firehose/substreams/Dockerfile @@ -0,0 +1,31 @@ +# syntax=docker/dockerfile:1.2 + +FROM rust:1.60-bullseye as rust + +FROM ubuntu:20.04 + +RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ + DEBIAN_FRONTEND=noninteractive apt-get -y install \ + gcc libssl-dev pkg-config protobuf-compiler \ + ca-certificates libssl1.1 vim strace lsof curl jq && \ + rm -rf /var/cache/apt /var/lib/apt/lists/* + +ENV RUSTUP_HOME=/usr/local/rustup \ + CARGO_HOME=/usr/local/cargo \ + PATH=/usr/local/cargo/bin:$PATH \ + RUST_VERSION=1.60.0 + +COPY --from=rust /usr/local/cargo /usr/local/cargo/ +COPY --from=rust /usr/local/rustup /usr/local/rustup/ + +# The `cargo install rustfmt || true` part serves the purposes of updating the crate registry, it's really +# hard to update the registry standalone without a package, so we take a detour by installing a component +# that will requires to update the crate registry +RUN rustup target install wasm32-unknown-unknown && rustup component add rustfmt && cargo install rustfmt || true + +ADD /substreams /app/substreams + +# ENV PATH "/app:$HOME/.cargo/bin:$PATH" +ENV PATH "/app:/usr/local/cargo/bin:$PATH" + +ENTRYPOINT ["/app/substreams"] diff --git a/firehose/substreams/Dockerfile.github b/firehose/substreams/Dockerfile.github new file mode 100644 index 0000000..f76b691 --- /dev/null +++ b/firehose/substreams/Dockerfile.github @@ -0,0 +1,7 @@ +FROM ubuntu:20.04 + +RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ + apt-get -y install -y ca-certificates libssl1.1 + +ADD ./substreams /app/substreams +ENTRYPOINT /app/substreams diff --git a/firehose/substreams/Dockerfile.gitpod b/firehose/substreams/Dockerfile.gitpod new file mode 100644 index 0000000..7799cbe --- /dev/null +++ b/firehose/substreams/Dockerfile.gitpod @@ -0,0 +1,14 @@ +FROM gitpod/workspace-full:2023-01-02-17-16-30 + +# Install the buf cli +RUN wget https://github.com/bufbuild/buf/releases/download/v1.11.0/buf-Linux-x86_64 +RUN sudo mv buf-Linux-x86_64 /usr/local/bin/buf && sudo chmod +x /usr/local/bin/buf + +# Install the substreams cli +RUN wget -c https://github.com/streamingfast/substreams/releases/download/v0.1.0/substreams_linux_x86_64.tar.gz -O - | tar xzf - substreams +RUN sudo mv substreams /usr/local/bin/substreams && sudo chmod +x /usr/local/bin/substreams + +# Install protoc-gen-prost +RUN cargo install protoc-gen-prost protoc-gen-prost-crate + +ENTRYPOINT ["/bin/bash"] diff --git a/firehose/substreams/LICENSE b/firehose/substreams/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/firehose/substreams/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/firehose/substreams/README.md b/firehose/substreams/README.md new file mode 100644 index 0000000..c5e76db --- /dev/null +++ b/firehose/substreams/README.md @@ -0,0 +1,28 @@ + + StreamingFast Substreams Banner + + +# Substreams + +> Developer preview + +Substreams is a powerful blockchain indexing technology, developed for The Graph Network. + +Substreams enables developers to write Rust modules, composing data streams alongside the community, and provides extremely high performance indexing by virtue of parallelization, in a streaming-first fashion. + +Substreams has all the benefits of StreamingFast Firehose, like low-cost caching and archiving of blockchain data, high throughput processing, and cursor-based reorgs handling. + +## Documentation + +Full documentation for installing, running and working with Substreams is available at: https://substreams.streamingfast.io. + +## Contributing + +**Please first refer to the general +[StreamingFast contribution guide](https://github.com/streamingfast/streamingfast/blob/master/CONTRIBUTING.md)**, +if you wish to contribute to this code base. + + +## License + +[Apache 2.0](LICENSE) diff --git a/firehose/substreams/RELEASE.md b/firehose/substreams/RELEASE.md new file mode 100644 index 0000000..b470618 --- /dev/null +++ b/firehose/substreams/RELEASE.md @@ -0,0 +1,20 @@ +## Substreams CLI & Lib Release + +## Instructions + +> *Important* Do not forget to replace `${version}` by your real version like `v0.0.21` in the commands below! + +Ensure you have [sfreleaser](https://github.com/streamingfast/sfreleaser) CLI, install with using Go with `go install github.com/streamingfast/sfreleaser/cmd/sfreleaser@latest`. + +### Preparing for a release + +- Ensure tests past `go test ./...` +- Ensure you are in a clean and pushed Git state +- Update the [./docs/release-notes/change-log.md](./docs/release-notes/change-log.md) to update the `## Unreleased` header to become `## [${version}](https://github.com/streamingfast/substreams/releases/tag/v${version})` +- Commit everything with message `Preparing release of ${version}`. + +## Generating a draft release + +Run `sfreleaser release` it's going to ask you questions about the release, just answer them. If you are missing some dependencies, the tool will also give you instructions to install them + +The `release` command publishes in `draft` mode by default, it will ask you to preview you it and also ask if you want to publish it right away. diff --git a/firehose/substreams/app/tier1.go b/firehose/substreams/app/tier1.go new file mode 100644 index 0000000..1b39966 --- /dev/null +++ b/firehose/substreams/app/tier1.go @@ -0,0 +1,248 @@ +package app + +import ( + "context" + "fmt" + "net/url" + "time" + + "github.com/streamingfast/substreams/reqctx" + + "github.com/streamingfast/bstream" + "github.com/streamingfast/bstream/blockstream" + "github.com/streamingfast/bstream/hub" + pbbstream "github.com/streamingfast/bstream/pb/sf/bstream/v1" + dauth "github.com/streamingfast/dauth" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/dstore" + "github.com/streamingfast/shutter" + "github.com/streamingfast/substreams/client" + "github.com/streamingfast/substreams/metrics" + "github.com/streamingfast/substreams/service" + "github.com/streamingfast/substreams/wasm" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +type Tier1Modules struct { + // Required dependencies + Authenticator dauth.Authenticator + HeadTimeDriftMetric *dmetrics.HeadTimeDrift + HeadBlockNumberMetric *dmetrics.HeadBlockNum + CheckPendingShutDown func() bool +} + +type Tier1Config struct { + MeteringConfig string + + MergedBlocksStoreURL string + OneBlocksStoreURL string + ForkedBlocksStoreURL string + BlockStreamAddr string // gRPC endpoint to get real-time blocks, can be "" in which live streams is disabled + GRPCListenAddr string // gRPC address where this app will listen to + GRPCShutdownGracePeriod time.Duration // The duration we allow for gRPC connections to terminate gracefully prior forcing shutdown + ServiceDiscoveryURL *url.URL + + StateStoreURL string + StateStoreDefaultTag string + BlockType string + StateBundleSize uint64 + + MaxSubrequests uint64 + SubrequestsEndpoint string + SubrequestsInsecure bool + SubrequestsPlaintext bool + + WASMExtensions wasm.WASMExtensioner + + Tracing bool +} + +type Tier1App struct { + *shutter.Shutter + config *Tier1Config + modules *Tier1Modules + logger *zap.Logger + isReady *atomic.Bool +} + +func NewTier1(logger *zap.Logger, config *Tier1Config, modules *Tier1Modules) *Tier1App { + return &Tier1App{ + Shutter: shutter.New(), + config: config, + modules: modules, + logger: logger, + + isReady: atomic.NewBool(false), + } +} + +func (a *Tier1App) Run() error { + + dmetrics.Register(metrics.MetricSet) + + a.logger.Info("running substreams-tier1", zap.Reflect("config", a.config)) + if err := a.config.Validate(); err != nil { + return fmt.Errorf("invalid app config: %w", err) + } + + mergedBlocksStore, err := dstore.NewDBinStore(a.config.MergedBlocksStoreURL) + if err != nil { + return fmt.Errorf("failed setting up block store from url %q: %w", a.config.MergedBlocksStoreURL, err) + } + + oneBlocksStore, err := dstore.NewDBinStore(a.config.OneBlocksStoreURL) + if err != nil { + return fmt.Errorf("failed setting up one-block store from url %q: %w", a.config.OneBlocksStoreURL, err) + } + + stateStore, err := dstore.NewStore(a.config.StateStoreURL, "zst", "zstd", true) + if err != nil { + return fmt.Errorf("failed setting up state store from url %q: %w", a.config.StateStoreURL, err) + } + + // set to empty store interface if URL is "" + var forkedBlocksStore dstore.Store + if a.config.ForkedBlocksStoreURL != "" { + forkedBlocksStore, err = dstore.NewDBinStore(a.config.ForkedBlocksStoreURL) + if err != nil { + return fmt.Errorf("failed setting up block store from url %q: %w", a.config.ForkedBlocksStoreURL, err) + } + } + + withLive := a.config.BlockStreamAddr != "" + + var forkableHub *hub.ForkableHub + + if withLive { + liveSourceFactory := bstream.SourceFactory(func(h bstream.Handler) bstream.Source { + return blockstream.NewSource( + context.Background(), + a.config.BlockStreamAddr, + 2, + bstream.HandlerFunc(func(blk *pbbstream.Block, obj interface{}) error { + a.modules.HeadBlockNumberMetric.SetUint64(blk.Number) + a.modules.HeadTimeDriftMetric.SetBlockTime(blk.Time()) + return h.ProcessBlock(blk, obj) + }), + blockstream.WithRequester("substreams-tier1"), + ) + }) + + oneBlocksSourceFactory := bstream.SourceFromNumFactoryWithSkipFunc(func(num uint64, h bstream.Handler, skipFunc func(string) bool) bstream.Source { + src, err := bstream.NewOneBlocksSource(num, oneBlocksStore, h, bstream.OneBlocksSourceWithSkipperFunc(skipFunc)) + if err != nil { + return nil + } + return src + }) + + forkableHub = hub.NewForkableHub(liveSourceFactory, oneBlocksSourceFactory, 500) + forkableHub.OnTerminated(a.Shutdown) + + go forkableHub.Run() + } + + subrequestsClientConfig := client.NewSubstreamsClientConfig( + a.config.SubrequestsEndpoint, + "", + client.None, + a.config.SubrequestsInsecure, + a.config.SubrequestsPlaintext, + ) + var opts []service.Option + if a.config.WASMExtensions != nil { + opts = append(opts, service.WithWASMExtensioner(a.config.WASMExtensions)) + } + + if a.config.Tracing { + opts = append(opts, service.WithModuleExecutionTracing()) + } + + var wasmModules map[string]string + if a.config.WASMExtensions != nil { + wasmModules = a.config.WASMExtensions.Params() + } + + tier2RequestParameters := reqctx.Tier2RequestParameters{ + MeteringConfig: a.config.MeteringConfig, + FirstStreamableBlock: bstream.GetProtocolFirstStreamableBlock, + MergedBlockStoreURL: a.config.MergedBlocksStoreURL, + StateStoreURL: a.config.StateStoreURL, + StateBundleSize: a.config.StateBundleSize, + StateStoreDefaultTag: a.config.StateStoreDefaultTag, + WASMModules: wasmModules, + } + + svc, err := service.NewTier1( + a.logger, + mergedBlocksStore, + forkedBlocksStore, + forkableHub, + stateStore, + a.config.StateStoreDefaultTag, + a.config.MaxSubrequests, + a.config.StateBundleSize, + a.config.BlockType, + subrequestsClientConfig, + tier2RequestParameters, + opts..., + ) + if err != nil { + return err + } + + a.OnTerminating(func(err error) { + metrics.AppReadinessTier1.SetNotReady() + + svc.Shutdown(err) + time.Sleep(2 * time.Second) // enough time to send termination grpc responses + }) + + go func() { + if withLive { + a.logger.Info("waiting until hub is real-time synced") + select { + case <-forkableHub.Ready: + metrics.AppReadinessTier1.SetReady() + case <-a.Terminating(): + return + } + } + + a.logger.Info("launching gRPC server", zap.Bool("live_support", withLive)) + a.isReady.CompareAndSwap(false, true) + + err := service.ListenTier1(a.config.GRPCListenAddr, svc, a.modules.Authenticator, a.logger, a.HealthCheck) + a.Shutdown(err) + }() + + return nil +} + +func (a *Tier1App) HealthCheck(ctx context.Context) (bool, interface{}, error) { + return a.IsReady(ctx), nil, nil +} + +// IsReady return `true` if the apps is ready to accept requests, `false` is returned +// otherwise. +func (a *Tier1App) IsReady(ctx context.Context) bool { + if a.IsTerminating() { + return false + } + if !a.modules.Authenticator.Ready(ctx) { + return false + } + + if a.modules.CheckPendingShutDown != nil && a.modules.CheckPendingShutDown() { + return false + } + + return a.isReady.Load() +} + +// Validate inspects itself to determine if the current config is valid according to +// substreams rules. +func (config *Tier1Config) Validate() error { + return nil +} diff --git a/firehose/substreams/app/tier2.go b/firehose/substreams/app/tier2.go new file mode 100644 index 0000000..9812337 --- /dev/null +++ b/firehose/substreams/app/tier2.go @@ -0,0 +1,134 @@ +package app + +import ( + "context" + "fmt" + "net/url" + + dauth "github.com/streamingfast/dauth" + "github.com/streamingfast/dmetrics" + "github.com/streamingfast/shutter" + "github.com/streamingfast/substreams/metrics" + "github.com/streamingfast/substreams/pipeline" + "github.com/streamingfast/substreams/service" + "github.com/streamingfast/substreams/wasm" + "go.uber.org/atomic" + "go.uber.org/zap" +) + +type Tier2Config struct { + GRPCListenAddr string // gRPC address where this app will listen to + ServiceDiscoveryURL *url.URL + + PipelineOptions []pipeline.Option + + MaximumConcurrentRequests uint64 + WASMExtensions wasm.WASMExtensioner + + Tracing bool +} + +type Tier2App struct { + *shutter.Shutter + config *Tier2Config + modules *Tier2Modules + logger *zap.Logger + isReady *atomic.Bool +} + +type Tier2Modules struct { + CheckPendingShutDown func() bool +} + +func NewTier2(logger *zap.Logger, config *Tier2Config, modules *Tier2Modules) *Tier2App { + return &Tier2App{ + Shutter: shutter.New(), + config: config, + modules: modules, + logger: logger, + + isReady: atomic.NewBool(false), + } +} + +func (a *Tier2App) Run() error { + dmetrics.Register(metrics.MetricSet) + + a.logger.Info("running substreams-tier2", zap.Reflect("config", a.config)) + if err := a.config.Validate(); err != nil { + return fmt.Errorf("invalid app config: %w", err) + } + + var opts []service.Option + //for _, opt := range a.config.PipelineOptions { + // opts = append(opts, service.WithPipelineOptions(opt)) + //} + + if a.config.Tracing { + opts = append(opts, service.WithModuleExecutionTracing()) + } + + if a.config.MaximumConcurrentRequests > 0 { + opts = append(opts, service.WithMaxConcurrentRequests(a.config.MaximumConcurrentRequests)) + } + opts = append(opts, service.WithReadinessFunc(a.setReadiness)) + + if a.config.WASMExtensions != nil { + opts = append(opts, service.WithWASMExtensioner(a.config.WASMExtensions)) + } + + svc, err := service.NewTier2( + a.logger, + opts..., + ) + if err != nil { + return err + } + + // tier2 always trusts the headers sent from tier1 + trustAuth, err := dauth.New("trust://", a.logger) + if err != nil { + return fmt.Errorf("failed to setup trust authenticator: %w", err) + } + + a.OnTerminating(func(_ error) { metrics.AppReadinessTier2.SetNotReady() }) + + go func() { + a.logger.Info("launching gRPC server") + a.isReady.CompareAndSwap(false, true) + metrics.AppReadinessTier2.SetReady() + + err := service.ListenTier2(a.config.GRPCListenAddr, a.config.ServiceDiscoveryURL, svc, trustAuth, a.logger, a.HealthCheck) + a.Shutdown(err) + }() + + return nil +} + +func (a *Tier2App) HealthCheck(ctx context.Context) (bool, interface{}, error) { + return a.IsReady(ctx), nil, nil +} + +// IsReady return `true` if the apps is ready to accept requests, `false` is returned +// otherwise. +func (a *Tier2App) IsReady(ctx context.Context) bool { + if a.IsTerminating() { + return false + } + + if a.modules.CheckPendingShutDown != nil && a.modules.CheckPendingShutDown() { + return false + } + + return a.isReady.Load() +} + +func (a *Tier2App) setReadiness(ready bool) { + a.isReady.Store(ready) +} + +// Validate inspects itself to determine if the current config is valid according to +// substreams rules. +func (config *Tier2Config) Validate() error { + return nil +} diff --git a/firehose/substreams/bigdecimal/init_test.go b/firehose/substreams/bigdecimal/init_test.go new file mode 100644 index 0000000..48bf45e --- /dev/null +++ b/firehose/substreams/bigdecimal/init_test.go @@ -0,0 +1,7 @@ +package bigdecimal + +import "github.com/streamingfast/logging" + +func init() { + logging.InstantiateLoggers() +} diff --git a/firehose/substreams/bin/test.sh b/firehose/substreams/bin/test.sh new file mode 100755 index 0000000..56c79b9 --- /dev/null +++ b/firehose/substreams/bin/test.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" + +export SUBSTREAMS_INTEGRATION_TESTS=true + +main() { + pushd "$ROOT" &> /dev/null + + while getopts "h" opt; do + case $opt in + h) usage && exit 0;; + \?) usage_error "Invalid option: -$OPTARG";; + esac + done + shift $((OPTIND-1)) + + set -e + + go test ./... "$@" +} + +usage_error() { + message="$1" + exit_code="$2" + + echo "ERROR: $message" + echo "" + usage + exit ${exit_code:-1} +} + +usage() { + echo "usage: test.sh" + echo "" + echo "Runs the Go tests in all sub-packages of this repository. Normal 'go test'" + echo "does run test just under the current Go package, sub-package are not traversed." + echo "" + echo "Options" + echo " -h Display help about this script" +} + +main "$@" diff --git a/firehose/substreams/block/range.go b/firehose/substreams/block/range.go new file mode 100644 index 0000000..f85891c --- /dev/null +++ b/firehose/substreams/block/range.go @@ -0,0 +1,113 @@ +package block + +import ( + "fmt" + "strconv" + "strings" + + "go.uber.org/zap/zapcore" +) + +func ParseRange(in string) *Range { + if in == "" { + return nil + } + ch := strings.Split(in, "-") + lo, err := strconv.ParseInt(ch[0], 10, 64) + if err != nil { + panic(err) + } + hi, err := strconv.ParseInt(ch[1], 10, 64) + if err != nil { + panic(err) + } + return NewRange(uint64(lo), uint64(hi)) +} + +type Range struct { + StartBlock uint64 `json:"start_block"` + ExclusiveEndBlock uint64 `json:"exclusive_end_block"` +} + +func NewRange(startBlock, exclusiveEndBlock uint64) *Range { + return &Range{startBlock, exclusiveEndBlock} +} + +func (r *Range) String() string { + if r == nil { + return "[nil)" + } + return fmt.Sprintf("[%d, %d)", r.StartBlock, r.ExclusiveEndBlock) +} + +func (r *Range) MarshalLogObject(enc zapcore.ObjectEncoder) error { + if r == nil { + enc.AddBool("nil", true) + } else { + enc.AddUint64("start_block", r.StartBlock) + enc.AddUint64("exclusive_end_block", r.ExclusiveEndBlock) + } + return nil +} + +func (r *Range) IsEmpty() bool { + return r.StartBlock == r.ExclusiveEndBlock +} + +func (r *Range) Contains(blockNum uint64) bool { + return blockNum >= r.StartBlock && blockNum < r.ExclusiveEndBlock +} + +func (r *Range) IsAbove(blockNum uint64) bool { + return blockNum > r.ExclusiveEndBlock +} + +func (r *Range) IsBelow(blockNum uint64) bool { + return blockNum < r.StartBlock +} + +func (r *Range) IsOutOfBounds(blockNum uint64) bool { + return !r.Contains(blockNum) +} + +func (r *Range) Equals(other *Range) bool { + return r.StartBlock == other.StartBlock && r.ExclusiveEndBlock == other.ExclusiveEndBlock +} + +func (r *Range) Size() uint64 { + return r.ExclusiveEndBlock - r.StartBlock +} + +func (r *Range) Split(chunkSize uint64) []*Range { + var res []*Range + if r.ExclusiveEndBlock-r.StartBlock <= chunkSize { + res = append(res, r) + return res + } + + currentEnd := (r.StartBlock + chunkSize) - (r.StartBlock+chunkSize)%chunkSize + currentStart := r.StartBlock + + for { + res = append(res, &Range{ + StartBlock: currentStart, + ExclusiveEndBlock: currentEnd, + }) + + if currentEnd >= r.ExclusiveEndBlock { + break + } + + currentStart = currentEnd + currentEnd = currentStart + chunkSize + if currentEnd > r.ExclusiveEndBlock { + currentEnd = r.ExclusiveEndBlock + } + } + + return res +} + +func (r *Range) Len() uint64 { + return r.ExclusiveEndBlock - r.StartBlock +} diff --git a/firehose/substreams/block/range_test.go b/firehose/substreams/block/range_test.go new file mode 100644 index 0000000..faf2508 --- /dev/null +++ b/firehose/substreams/block/range_test.go @@ -0,0 +1,25 @@ +package block + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRange_Split(t *testing.T) { + og := &Range{ + StartBlock: 706, + ExclusiveEndBlock: 1250, + } + + expected := []*Range{ + {706, 800}, + {800, 1000}, + {1000, 1200}, + {1200, 1250}, + } + + actual := og.Split(200) + + require.Equal(t, expected, actual) +} diff --git a/firehose/substreams/block/ranges.go b/firehose/substreams/block/ranges.go new file mode 100644 index 0000000..39af362 --- /dev/null +++ b/firehose/substreams/block/ranges.go @@ -0,0 +1,140 @@ +package block + +import ( + "sort" + "strings" +) + +func ParseRanges(in string) (out Ranges) { + for _, e := range strings.Split(in, ",") { + newRange := ParseRange(strings.Trim(e, " ")) + if newRange != nil { + out = append(out, newRange) + } + } + return +} + +type Ranges []*Range + +func (r Ranges) String() string { + var rs []string + for _, i := range r { + rs = append(rs, i.String()) + } + return strings.Join(rs, ",") +} + +func (r Ranges) Len() int { + return len(r) +} + +func (r Ranges) Less(i, j int) bool { + return r[i].StartBlock < r[j].StartBlock +} + +func (r Ranges) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +func (r Ranges) Contains(input *Range) bool { + for _, el := range r { + if el.Equals(input) { + return true + } + } + return false +} + +func (r Ranges) SortAndDedupe() (out Ranges) { + if r == nil { + return nil + } + + m := make(map[string]*Range) + + for _, rr := range r { + m[rr.String()] = rr + } + + out = make(Ranges, len(m)) + i := 0 + for _, v := range m { + out[i] = v + i++ + } + + sort.Sort(out) + return +} + +func (r Ranges) Merged() (out Ranges) { + if r == nil { + return nil + } + + for i := 0; i < len(r); i++ { + curRange := r[i] + if i == len(r)-1 { + out = append(out, curRange) + break + } + nextRange := r[i+1] + if curRange.ExclusiveEndBlock != nextRange.StartBlock { + out = append(out, curRange) + continue + } + + i++ + + // Loop to squash all the next ones and create a new Range + // from `curRange` and the latest matching `nextRange`. + for j := i + 1; j < len(r); j++ { + nextNextRange := r[j] + if nextRange.ExclusiveEndBlock != nextNextRange.StartBlock { + break + } + i++ + nextRange = nextNextRange + } + out = append(out, NewRange(curRange.StartBlock, nextRange.ExclusiveEndBlock)) + } + return out +} + +func (r Ranges) MergedBuckets(maxBucketSize uint64) (out Ranges) { + for i := 0; i < len(r); i++ { + currentRange := r[i] + isLast := i == len(r)-1 + if isLast { + out = append(out, currentRange) + break + } + + if currentRange.Size() >= maxBucketSize-1 { + out = append(out, currentRange) + continue + } + + nextRange := r[i+1] + if currentRange.ExclusiveEndBlock != nextRange.StartBlock || nextRange.ExclusiveEndBlock-currentRange.StartBlock > maxBucketSize { + out = append(out, currentRange) + continue + } + + i++ + + // Loop to squash all the next ones and create a new Range + // from `currentRange` and the latest matching `nextRange`. + for j := i + 1; j < len(r); j++ { + nextNextRange := r[j] + if nextRange.ExclusiveEndBlock != nextNextRange.StartBlock || nextNextRange.ExclusiveEndBlock-currentRange.StartBlock > maxBucketSize { + break + } + i++ + nextRange = nextNextRange + } + out = append(out, NewRange(currentRange.StartBlock, nextRange.ExclusiveEndBlock)) + } + return out +} diff --git a/firehose/substreams/block/ranges_test.go b/firehose/substreams/block/ranges_test.go new file mode 100644 index 0000000..adeec4f --- /dev/null +++ b/firehose/substreams/block/ranges_test.go @@ -0,0 +1,53 @@ +package block + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRangeMerged(t *testing.T) { + assert.Equal(t, ParseRanges("10-40,50-70").String(), ParseRanges("10-20,20-30,30-40,50-60,60-70").Merged().String()) + assert.Equal(t, ParseRanges("10-40,60-70").String(), ParseRanges("10-20,20-30,30-40,60-70").Merged().String()) + assert.Equal(t, ParseRanges("10-40").String(), ParseRanges("10-20,20-30,30-40").Merged().String()) + assert.Equal(t, ParseRanges("1-5,10-12,13-14").String(), ParseRanges("1-2,2-3,3-4,4-5,10-12,13-14").Merged().String()) +} + +func TestRangeMergedBuckets(t *testing.T) { + assert.Equal(t, + ParseRanges("1-10,10-11").String(), + ParseRanges("1-10,10-11").MergedBuckets(10).String(), + ) + assert.Equal(t, + ParseRanges("1-10,10-12").String(), + ParseRanges("1-10,10-12").MergedBuckets(10).String(), + ) + assert.Equal(t, + ParseRanges("10-30,30-40,50-70").String(), + ParseRanges("10-20,20-30,30-40,50-60,60-70").MergedBuckets(20).String(), + ) + assert.Equal(t, + ParseRanges("10-30,30-50,50-60,80-100").String(), + ParseRanges("10-20,20-30,30-40,40-50,50-60,80-90,90-100").MergedBuckets(20).String(), + ) + assert.Equal(t, + ParseRanges("10-20,20-30,30-40").String(), + ParseRanges("10-20,20-30,30-40").MergedBuckets(5).String(), + ) + assert.Equal(t, + ParseRanges("10-20,20-30,30-40,40-50").String(), + ParseRanges("10-20,20-30,30-40,40-50").MergedBuckets(11).String(), + ) + assert.Equal(t, + ParseRanges("10-20,20-30,30-40,40-50").String(), + ParseRanges("10-20,20-30,30-40,40-50").MergedBuckets(19).String(), + ) + assert.Equal(t, + ParseRanges("10-30,30-50").String(), + ParseRanges("10-20,20-30,30-40,40-50").MergedBuckets(20).String(), + ) + assert.Equal(t, + ParseRanges("1-4,4-5,10-12,13-14").String(), + ParseRanges("1-2,2-3,3-4,4-5,10-12,13-14").MergedBuckets(3).String(), + ) +} diff --git a/firehose/substreams/block/segmenter.go b/firehose/substreams/block/segmenter.go new file mode 100644 index 0000000..19ccbca --- /dev/null +++ b/firehose/substreams/block/segmenter.go @@ -0,0 +1,96 @@ +package block + +// TODO(abourget): The Segmenter is a new SegmentedRange system, that takes an index so +// the caller can always keep track of just one number, and we can obtain the corresponding +// Range for the segment. We can obtain info on the Segment too (if it's Partial, Complete, etc..) + +type Segmenter struct { + interval uint64 + initialBlock uint64 + exclusiveEndBlock uint64 +} + +func NewSegmenter(interval uint64, initialBlock uint64, exclusiveEndBlock uint64) *Segmenter { + s := &Segmenter{ + interval: interval, + initialBlock: initialBlock, + exclusiveEndBlock: exclusiveEndBlock, + } + return s +} + +func (s *Segmenter) InitialBlock() uint64 { + return s.initialBlock +} + +func (s *Segmenter) ExclusiveEndBlock() uint64 { + return s.exclusiveEndBlock +} + +func (s *Segmenter) WithInitialBlock(newInitialBlock uint64) *Segmenter { + return NewSegmenter(s.interval, newInitialBlock, s.exclusiveEndBlock) +} + +func (s *Segmenter) WithExclusiveEndBlock(newExclusiveEndBlock uint64) *Segmenter { + return NewSegmenter(s.interval, s.initialBlock, newExclusiveEndBlock) +} + +// Count returns the number of valid segments for the internal range. +// Use LastIndex to know about the highest index. +func (s *Segmenter) Count() int { + return int(s.LastIndex() - s.FirstIndex() + 1) +} + +func (s *Segmenter) FirstIndex() int { + initSegment := s.initialBlock / s.interval + return int(initSegment) +} + +func (s *Segmenter) LastIndex() int { + lastSegment := (s.exclusiveEndBlock - 1) / s.interval + return int(lastSegment) +} + +func (s *Segmenter) Range(idx int) *Range { + first := s.FirstIndex() + if idx < first { + return nil + } + if idx == first { + return s.firstRange() + } + return s.followingRange(idx) +} + +func (s *Segmenter) firstRange() *Range { + if s.exclusiveEndBlock != 0 && s.exclusiveEndBlock < s.initialBlock { + return nil + } + floorLowerBound := s.initialBlock - s.initialBlock%s.interval + upperBound := floorLowerBound + s.interval + return NewRange(s.initialBlock, min(upperBound, s.exclusiveEndBlock)) +} + +func (s *Segmenter) followingRange(idx int) *Range { + if idx > s.LastIndex() { + return nil + } + baseBlock := uint64(idx) * s.interval + upperBound := baseBlock + s.interval + return NewRange(baseBlock, min(upperBound, s.exclusiveEndBlock)) +} + +func (s *Segmenter) IndexForStartBlock(blockNum uint64) int { + return int(blockNum / s.interval) +} + +func (s *Segmenter) IndexForEndBlock(blockNum uint64) int { + return int((blockNum - 1) / s.interval) /* exclusive of the given blockNum */ +} + +func (s *Segmenter) EndsOnInterval(segmentIndex int) bool { + if segmentIndex > s.LastIndex() { + panic("segment index out of range") + } + return s.Range(segmentIndex).ExclusiveEndBlock%s.interval == 0 +} diff --git a/firehose/substreams/block/segmenter_test.go b/firehose/substreams/block/segmenter_test.go new file mode 100644 index 0000000..e440da5 --- /dev/null +++ b/firehose/substreams/block/segmenter_test.go @@ -0,0 +1,137 @@ +package block + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSegmenter_Count(t *testing.T) { + type fields struct { + interval int + initialBlock int + linearHandoffBlock int + } + tests := []struct { + name string + fields fields + count int + }{ + { + "beginning", + fields{10, 12, 31}, + 3, // the 10-20, 20-30, 30-31 segments + }, + { + "further down", + fields{10, 112, 131}, + 3, // the 110-120, 120-130, 130-131 segments + }, + { + "first module segment", + fields{10, 112, 129}, + 2, + }, + { + "first module segment is further down", + fields{10, 112, 135}, + 3, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := NewSegmenter(uint64(tt.fields.interval), uint64(tt.fields.initialBlock), uint64(tt.fields.linearHandoffBlock)) + assert.Equalf(t, tt.count, s.Count(), "Count()") + }) + } +} + +func TestSegmenter_IndexForStartBlock(t *testing.T) { + s := Segmenter{interval: 10} + assert.Equal(t, 0, s.IndexForStartBlock(5)) + assert.Equal(t, 0, s.IndexForStartBlock(9)) + assert.Equal(t, 1, s.IndexForStartBlock(10)) + assert.Equal(t, 1, s.IndexForStartBlock(11)) + assert.Equal(t, 1, s.IndexForStartBlock(19)) + assert.Equal(t, 2, s.IndexForStartBlock(20)) + assert.Equal(t, 2, s.IndexForStartBlock(21)) + assert.Equal(t, 4, s.IndexForStartBlock(45)) +} + +func TestSegmenter_IndexForEndBlock(t *testing.T) { + s := Segmenter{ + interval: 10, + } + assert.Equal(t, 0, s.IndexForEndBlock(5)) + assert.Equal(t, 0, s.IndexForEndBlock(9)) + assert.Equal(t, 0, s.IndexForEndBlock(10)) + assert.Equal(t, 1, s.IndexForEndBlock(11)) + assert.Equal(t, 1, s.IndexForEndBlock(19)) + assert.Equal(t, 1, s.IndexForEndBlock(20)) + assert.Equal(t, 2, s.IndexForEndBlock(21)) + assert.Equal(t, 4, s.IndexForEndBlock(45)) +} + +func TestSegmenter_firstRange(t *testing.T) { + s := &Segmenter{interval: 10, initialBlock: 1, exclusiveEndBlock: 100} + assert.Equal(t, NewRange(1, 10), s.firstRange()) + s = &Segmenter{interval: 10, initialBlock: 0, exclusiveEndBlock: 100} + assert.Equal(t, NewRange(0, 10), s.firstRange()) + s = &Segmenter{interval: 10, initialBlock: 9, exclusiveEndBlock: 100} + assert.Equal(t, NewRange(9, 10), s.firstRange()) + s = &Segmenter{interval: 10, initialBlock: 10, exclusiveEndBlock: 100} + assert.Equal(t, NewRange(10, 20), s.firstRange()) + s = &Segmenter{interval: 10, initialBlock: 11, exclusiveEndBlock: 100} + assert.Equal(t, NewRange(11, 20), s.firstRange()) + s = &Segmenter{interval: 10, initialBlock: 11, exclusiveEndBlock: 15} + assert.Equal(t, NewRange(11, 15), s.firstRange()) + + s = &Segmenter{interval: 10, initialBlock: 11, exclusiveEndBlock: 10} + assert.Nil(t, s.firstRange()) +} + +func TestSegmenter_followingRange(t *testing.T) { + s := NewSegmenter(10, 1, 100) + assert.Equal(t, ParseRange("0-10"), s.followingRange(0)) + s = NewSegmenter(10, 1, 100) + assert.Equal(t, ParseRange("10-20"), s.followingRange(1)) + s = NewSegmenter(10, 1, 15) + assert.Equal(t, ParseRange("10-15"), s.followingRange(1)) + s = NewSegmenter(10, 1, 25) + assert.Equal(t, ParseRange("20-25"), s.followingRange(2)) + s = NewSegmenter(10, 15, 25) + assert.Equal(t, ParseRange("20-25"), s.followingRange(2)) + s = NewSegmenter(10, 15, 25) + assert.Equal(t, ParseRange("10-20"), s.followingRange(1)) +} + +func TestSegmenter_Range(t *testing.T) { + s := NewSegmenter(10, 1, 100) + assert.Nil(t, s.Range(-1)) + + s = NewSegmenter(10, 15, 25) + assert.Nil(t, s.Range(0)) + assert.Equal(t, 1, s.FirstIndex()) + assert.Equal(t, 2, s.LastIndex()) + assert.Equal(t, ParseRange("15-20"), s.Range(1)) + assert.Equal(t, ParseRange("20-25"), s.Range(2)) + assert.Nil(t, s.Range(3)) + + s = NewSegmenter(10, 1, 99) + assert.Equal(t, 10, s.Count()) + assert.Equal(t, 0, s.FirstIndex()) + assert.Equal(t, 9, s.LastIndex()) + assert.Equal(t, ParseRange("90-99"), s.Range(9)) + assert.False(t, s.EndsOnInterval(9)) + + s = NewSegmenter(10, 1, 15) + assert.Equal(t, NewRange(10, 15), s.Range(1)) + + s = NewSegmenter(10, 1, 20) + assert.Equal(t, 2, s.Count()) + assert.Equal(t, 0, s.FirstIndex()) + assert.Equal(t, 1, s.LastIndex()) + assert.Equal(t, ParseRange("10-20"), s.Range(1)) + assert.True(t, s.EndsOnInterval(1)) + +} diff --git a/firehose/substreams/buf.gen.yaml b/firehose/substreams/buf.gen.yaml new file mode 100644 index 0000000..61c61cc --- /dev/null +++ b/firehose/substreams/buf.gen.yaml @@ -0,0 +1,18 @@ +version: v1 +managed: + # We are now using managed mode for now because I did not find how to support `;pbsubstreams` package + # which we use currently. It's not a big problem for now but I didn't want to change anything. We + # might revisit that later. + enabled: false +plugins: + - name: go + out: pb + opt: paths=source_relative + - plugin: go-grpc + out: pb + opt: + - paths=source_relative + - require_unimplemented_servers=false + - plugin: buf.build/connectrpc/go:v1.15.0 + out: pb + opt: paths=source_relative \ No newline at end of file diff --git a/firehose/substreams/buf.work.yaml b/firehose/substreams/buf.work.yaml new file mode 100644 index 0000000..1878b34 --- /dev/null +++ b/firehose/substreams/buf.work.yaml @@ -0,0 +1,3 @@ +version: v1 +directories: + - proto diff --git a/firehose/substreams/client/client.go b/firehose/substreams/client/client.go new file mode 100644 index 0000000..9756ac5 --- /dev/null +++ b/firehose/substreams/client/client.go @@ -0,0 +1,239 @@ +package client + +import ( + "crypto/tls" + "fmt" + "log" + "os" + "regexp" + + "github.com/streamingfast/dgrpc" + pbssinternal "github.com/streamingfast/substreams/pb/sf/substreams/intern/v2" + pbsubstreamsrpc "github.com/streamingfast/substreams/pb/sf/substreams/rpc/v2" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/oauth2" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/credentials/oauth" + xdscreds "google.golang.org/grpc/credentials/xds" + _ "google.golang.org/grpc/xds" +) + +type AuthType int + +const ( + None AuthType = iota + JWT + ApiKey +) + +type SubstreamsClientConfig struct { + endpoint string + authToken string + authType AuthType + insecure bool + plaintext bool +} + +func (c *SubstreamsClientConfig) Endpoint() string { + return c.endpoint +} + +func (c *SubstreamsClientConfig) Insecure() bool { + return c.insecure +} + +func (c *SubstreamsClientConfig) PlainText() bool { + return c.plaintext +} + +func (c *SubstreamsClientConfig) AuthToken() string { + return c.authToken +} + +func (c *SubstreamsClientConfig) AuthType() AuthType { + return c.authType +} + +func (c *SubstreamsClientConfig) MarshalLogObject(encoder zapcore.ObjectEncoder) error { + encoder.AddString("client_endpoint", c.endpoint) + encoder.AddBool("client_plaintext", c.plaintext) + encoder.AddBool("client_insecure", c.insecure) + encoder.AddBool("jwt_set", c.authToken != "" && c.authType == JWT) + encoder.AddBool("api_key_set", c.authToken != "" && c.authType == ApiKey) + + return nil +} + +type InternalClientFactory = func() (cli pbssinternal.SubstreamsClient, closeFunc func() error, callOpts []grpc.CallOption, headers Headers, err error) + +func NewSubstreamsClientConfig(endpoint string, authToken string, authType AuthType, insecure bool, plaintext bool) *SubstreamsClientConfig { + return &SubstreamsClientConfig{ + endpoint: endpoint, + authToken: authToken, + authType: authType, + insecure: insecure, + plaintext: plaintext, + } +} + +var portSuffixRegex = regexp.MustCompile(":[0-9]{2,5}$") + +func NewInternalClientFactory(config *SubstreamsClientConfig) InternalClientFactory { + bootStrapFilename := os.Getenv("GRPC_XDS_BOOTSTRAP") + + if bootStrapFilename == "" { + zlog.Info("setting up basic grpc client factory (no XDS bootstrap)") + + return func() (cli pbssinternal.SubstreamsClient, closeFunc func() error, callOpts []grpc.CallOption, headers Headers, err error) { + return NewSubstreamsInternalClient(config) + } + } + + zlog.Info("setting up xds grpc client factory", zap.String("GRPC_XDS_BOOTSTRAP", bootStrapFilename)) + + noop := func() error { return nil } + cli, _, callOpts, headers, err := NewSubstreamsInternalClient(config) + return func() (pbssinternal.SubstreamsClient, func() error, []grpc.CallOption, Headers, error) { + return cli, noop, callOpts, headers, err + } +} + +func NewSubstreamsInternalClient(config *SubstreamsClientConfig) (cli pbssinternal.SubstreamsClient, closeFunc func() error, callOpts []grpc.CallOption, headers Headers, err error) { + if config == nil { + return nil, nil, nil, nil, fmt.Errorf("substreams client config not set") + } + endpoint := config.endpoint + authToken := config.authToken + authType := config.authType + usePlainTextConnection := config.plaintext + useInsecureTLSConnection := config.insecure + + if !portSuffixRegex.MatchString(endpoint) { + return nil, nil, nil, nil, fmt.Errorf("invalid endpoint %q: endpoint's suffix must be a valid port in the form ':', port 443 is usually the right one to use", endpoint) + } + + bootStrapFilename := os.Getenv("GRPC_XDS_BOOTSTRAP") + + var dialOptions []grpc.DialOption + skipAuth := authType == None || usePlainTextConnection + if bootStrapFilename != "" { + log.Println("Using xDS credentials...") + creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to create xDS credentials: %v", err) + } + dialOptions = append(dialOptions, grpc.WithTransportCredentials(creds)) + } else { + if useInsecureTLSConnection && usePlainTextConnection { + return nil, nil, nil, nil, fmt.Errorf("option --insecure and --plaintext are mutually exclusive, they cannot be both specified at the same time") + } + switch { + case usePlainTextConnection: + zlog.Debug("setting plain text option") + + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + + case useInsecureTLSConnection: + zlog.Debug("setting insecure tls connection option") + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} + } + } + + dialOptions = append(dialOptions, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor())) + dialOptions = append(dialOptions, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor())) + + zlog.Debug("getting connection", zap.String("endpoint", endpoint)) + conn, err := dgrpc.NewExternalClient(endpoint, dialOptions...) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("unable to create external gRPC client: %w", err) + } + closeFunc = conn.Close + + if !skipAuth { + if authType == JWT { + zlog.Debug("creating oauth access", zap.String("endpoint", endpoint)) + tokenSource := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: authToken, TokenType: "Bearer"})} + callOpts = append(callOpts, grpc.PerRPCCredentials(tokenSource)) + } else if authType == ApiKey { + zlog.Debug("creating api key access", zap.String("endpoint", endpoint)) + headers = map[string]string{ApiKeyHeader: authToken} + } + } + + zlog.Debug("creating new client", zap.String("endpoint", endpoint)) + cli = pbssinternal.NewSubstreamsClient(conn) + zlog.Debug("client created") + return +} + +func NewSubstreamsClient(config *SubstreamsClientConfig) (cli pbsubstreamsrpc.StreamClient, closeFunc func() error, callOpts []grpc.CallOption, headers Headers, err error) { + if config == nil { + return nil, nil, nil, nil, fmt.Errorf("substreams client config not set") + } + endpoint := config.endpoint + authToken := config.authToken + authType := config.authType + usePlainTextConnection := config.plaintext + useInsecureTLSConnection := config.insecure + + if !portSuffixRegex.MatchString(endpoint) { + return nil, nil, nil, nil, fmt.Errorf("invalid endpoint %q: endpoint's suffix must be a valid port in the form ':', port 443 is usually the right one to use", endpoint) + } + + bootStrapFilename := os.Getenv("GRPC_XDS_BOOTSTRAP") + + var dialOptions []grpc.DialOption + skipAuth := authType == None || usePlainTextConnection + if bootStrapFilename != "" { + log.Println("Using xDS credentials...") + creds, err := xdscreds.NewClientCredentials(xdscreds.ClientOptions{FallbackCreds: insecure.NewCredentials()}) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("failed to create xDS credentials: %v", err) + } + dialOptions = append(dialOptions, grpc.WithTransportCredentials(creds)) + } else { + if useInsecureTLSConnection && usePlainTextConnection { + return nil, nil, nil, nil, fmt.Errorf("option --insecure and --plaintext are mutually exclusive, they cannot be both specified at the same time") + } + switch { + case usePlainTextConnection: + zlog.Debug("setting plain text option") + + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())} + + case useInsecureTLSConnection: + zlog.Debug("setting insecure tls connection option") + dialOptions = []grpc.DialOption{grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{InsecureSkipVerify: true}))} + } + } + + dialOptions = append(dialOptions, grpc.WithUnaryInterceptor(otelgrpc.UnaryClientInterceptor())) + dialOptions = append(dialOptions, grpc.WithStreamInterceptor(otelgrpc.StreamClientInterceptor())) + + zlog.Debug("getting connection", zap.String("endpoint", endpoint)) + conn, err := dgrpc.NewExternalClient(endpoint, dialOptions...) + if err != nil { + return nil, nil, nil, nil, fmt.Errorf("unable to create external gRPC client: %w", err) + } + closeFunc = conn.Close + + if !skipAuth { + if authType == JWT { + zlog.Debug("creating oauth access", zap.String("endpoint", endpoint)) + tokenSource := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: authToken, TokenType: "Bearer"})} + callOpts = append(callOpts, grpc.PerRPCCredentials(tokenSource)) + } else if authType == ApiKey { + zlog.Debug("creating api key access", zap.String("endpoint", endpoint)) + headers = map[string]string{ApiKeyHeader: authToken} + } + } + + zlog.Debug("creating new client", zap.String("endpoint", endpoint)) + cli = pbsubstreamsrpc.NewStreamClient(conn) + zlog.Debug("client created") + return +} diff --git a/firehose/substreams/client/headers.go b/firehose/substreams/client/headers.go new file mode 100644 index 0000000..5901982 --- /dev/null +++ b/firehose/substreams/client/headers.go @@ -0,0 +1,24 @@ +package client + +type Headers map[string]string + +const ApiKeyHeader = "x-api-key" + +func (h Headers) Append(headers map[string]string) map[string]string { + for key, value := range headers { + h[key] = value + } + return h +} + +func (h Headers) ToArray() []string { + res := make([]string, 0, len(h)*2) + for key, value := range h { + res = append(res, key, value) + } + return res +} + +func (h Headers) IsSet() bool { + return len(h) > 0 +} diff --git a/firehose/substreams/client/logging.go b/firehose/substreams/client/logging.go new file mode 100644 index 0000000..6a99fb0 --- /dev/null +++ b/firehose/substreams/client/logging.go @@ -0,0 +1,7 @@ +package client + +import ( + "github.com/streamingfast/logging" +) + +var zlog, tracer = logging.PackageLogger("substreams-clients", "github.com/streamingfast/substreams/client") diff --git a/firehose/substreams/cmd/substreams/alpha.go b/firehose/substreams/cmd/substreams/alpha.go new file mode 100644 index 0000000..efe0982 --- /dev/null +++ b/firehose/substreams/cmd/substreams/alpha.go @@ -0,0 +1,13 @@ +package main + +import "github.com/spf13/cobra" + +var alphaCmd = &cobra.Command{ + Use: "alpha", + Short: "Group of commands that are currently being available for testing but could change at any time", + SilenceUsage: true, +} + +func init() { + rootCmd.AddCommand(alphaCmd) +} diff --git a/firehose/substreams/cmd/substreams/codegen.go b/firehose/substreams/cmd/substreams/codegen.go new file mode 100644 index 0000000..b2352b8 --- /dev/null +++ b/firehose/substreams/cmd/substreams/codegen.go @@ -0,0 +1,80 @@ +package main + +import ( + "fmt" + "os" + "path" + "path/filepath" + + "github.com/streamingfast/cli" + + "github.com/jhump/protoreflect/desc" + "github.com/spf13/cobra" + "github.com/streamingfast/substreams/codegen" + "github.com/streamingfast/substreams/manifest" +) + +var ( + devSubstreamsCodegenGenerateTo = os.Getenv("SUBSTREAMS_DEV_CODEGEN_GENERATE_TO") +) + +var codegenCmd = &cobra.Command{ + Use: "codegen []", + Short: "Generate a Rust trait and boilerplate code from your 'substreams.yaml' for nicer development", + Long: cli.Dedent(` + Generate a Rust trait and boilerplate code from your 'substreams.yaml' for nicer development. + The manifest is optional as it will try to find a file named 'substreams.yaml' in current working directory if nothing entered. + You may enter a directory that contains a 'substreams.yaml' file in place of '', or a link to a remote .spkg file, + using urls gs://, http(s)://, ipfs://, etc.'. + `), + RunE: runCodeGen, + Args: cobra.RangeArgs(0, 1), +} + +func init() { + alphaCmd.AddCommand(codegenCmd) +} + +func runCodeGen(cmd *cobra.Command, args []string) error { + manifestPath := "" + if len(args) == 1 { + manifestPath = args[0] + } + + var protoDefinitions []*desc.FileDescriptor + + manifestReader, err := manifest.NewReader(manifestPath, manifest.SkipSourceCodeReader(), manifest.WithCollectProtoDefinitions(func(pd []*desc.FileDescriptor) { protoDefinitions = pd })) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + manifestAbsPath, err := filepath.Abs(manifestPath) + if err != nil { + return fmt.Errorf("computing working directory: %w", err) + } + workingDir := filepath.Dir(manifestAbsPath) + manif, err := manifest.LoadManifestFile(manifestAbsPath, workingDir) + if err != nil { + return fmt.Errorf("loading manifest: %w", err) + } + pkg, _, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("reading manifest %q: %w", manifestPath, err) + } + + srcDir := path.Join(workingDir, "src") + if devSubstreamsCodegenGenerateTo != "" { + srcDir, err = filepath.Abs(devSubstreamsCodegenGenerateTo) + if err != nil { + panic(fmt.Errorf("generate to folder %q should be able to be made absolute: %w", devSubstreamsCodegenGenerateTo, err)) + } + } + + gen := codegen.NewGenerator(pkg, manif, protoDefinitions, srcDir) + err = gen.Generate() + if err != nil { + return fmt.Errorf("generating code: %w", err) + } + + return nil +} diff --git a/firehose/substreams/cmd/substreams/flags.go b/firehose/substreams/cmd/substreams/flags.go new file mode 100644 index 0000000..3963e97 --- /dev/null +++ b/firehose/substreams/cmd/substreams/flags.go @@ -0,0 +1,117 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func init() { + cobra.OnInitialize(func() { + autoBind(rootCmd, "SUBSTREAMS") + }) +} + +func autoBind(root *cobra.Command, prefix string) { + recurseCommands(root, prefix, nil) // []string{strings.ToLower(prefix)}) how does it wweeeerrkk? +} + +func recurseCommands(root *cobra.Command, prefix string, segments []string) { + var segmentPrefix string + if len(segments) > 0 { + segmentPrefix = strings.ToUpper(strings.Join(segments, "_")) + "_" + } + + root.PersistentFlags().VisitAll(func(f *pflag.Flag) { + newName := strings.Replace(strings.ToUpper(f.Name), "-", "_", -1) + varName := prefix + "_" + segmentPrefix + "GLOBAL_" + newName + if val := os.Getenv(varName); val != "" { + f.Usage += " [LOADED FROM ENV]" // Until we have a better template for our usage. + if !f.Changed { + f.Value.Set(val) + } + } + }) + + root.Flags().VisitAll(func(f *pflag.Flag) { + newName := strings.Replace(strings.ToUpper(f.Name), "-", "_", -1) + varName := prefix + "_" + segmentPrefix + "CMD_" + newName + if val := os.Getenv(varName); val != "" { + f.Usage += " [LOADED FROM ENV]" + if !f.Changed { + f.Value.Set(val) + } + } + }) + + for _, cmd := range root.Commands() { + recurseCommands(cmd, prefix, append(segments, cmd.Name())) + } +} + +func mustGetString(cmd *cobra.Command, flagName string) string { + val, err := cmd.Flags().GetString(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + return val +} + +func mustGetStringArray(cmd *cobra.Command, flagName string) []string { + val, err := cmd.Flags().GetStringArray(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + return val +} +func mustGetStringSlice(cmd *cobra.Command, flagName string) []string { + val, err := cmd.Flags().GetStringSlice(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + if len(val) == 0 { + return nil + } + return val +} +func mustGetInt64(cmd *cobra.Command, flagName string) int64 { + val, err := cmd.Flags().GetInt64(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + return val +} +func mustGetUint64(cmd *cobra.Command, flagName string) uint64 { + val, err := cmd.Flags().GetUint64(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + return val +} +func mustGetBool(cmd *cobra.Command, flagName string) bool { + val, err := cmd.Flags().GetBool(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + return val +} + +func maybeGetString(cmd *cobra.Command, flagName string) string { + val, _ := cmd.Flags().GetString(flagName) + return val +} +func maybeGetInt64(cmd *cobra.Command, flagName string) int64 { + val, _ := cmd.Flags().GetInt64(flagName) + return val +} +func maybeGetUint64(cmd *cobra.Command, flagName string) uint64 { + val, _ := cmd.Flags().GetUint64(flagName) + return val +} +func maybeGetBool(cmd *cobra.Command, flagName string) bool { + val, _ := cmd.Flags().GetBool(flagName) + return val +} diff --git a/firehose/substreams/cmd/substreams/graph.go b/firehose/substreams/cmd/substreams/graph.go new file mode 100644 index 0000000..65e901a --- /dev/null +++ b/firehose/substreams/cmd/substreams/graph.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + + "github.com/streamingfast/substreams/manifest" +) + +var graphCmd = &cobra.Command{ + Use: "graph []", + Short: "Generate mermaid-js graph document", + RunE: runManifestGraph, + Long: cli.Dedent(` + Generate mermaid-js graph document. The manifest is optional as it will try to find a file named + 'substreams.yaml' in current working directory if nothing entered. You may enter a directory that contains a + 'substreams.yaml' file in place of '', or a link to a remote .spkg file, using urls gs://, http(s)://, ipfs://, etc.'. + `), + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func init() { + rootCmd.AddCommand(graphCmd) +} + +func runManifestGraph(cmd *cobra.Command, args []string) error { + manifestPath := "" + if len(args) == 1 { + manifestPath = args[0] + } + + manifestReader, err := manifest.NewReader(manifestPath) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + pkg, _, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("read manifest %q: %w", manifestPath, err) + } + + manifest.PrintMermaid(pkg.Modules) + + fmt.Println("") + fmt.Println("Here is a quick link to see the graph:") + fmt.Println("") + fmt.Println(manifest.GenerateMermaidLiveURL(pkg.Modules)) + + return nil +} diff --git a/firehose/substreams/cmd/substreams/gui.go b/firehose/substreams/cmd/substreams/gui.go new file mode 100644 index 0000000..95960ad --- /dev/null +++ b/firehose/substreams/cmd/substreams/gui.go @@ -0,0 +1,233 @@ +package main + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + + tea "github.com/charmbracelet/bubbletea" + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/substreams/client" + "github.com/streamingfast/substreams/manifest" + "github.com/streamingfast/substreams/tools" + "github.com/streamingfast/substreams/tui2" + "github.com/streamingfast/substreams/tui2/pages/request" +) + +func init() { + guiCmd.Flags().String("substreams-api-token-envvar", "SUBSTREAMS_API_TOKEN", "name of variable containing Substreams Authentication token") + guiCmd.Flags().String("substreams-api-key-envvar", "SUBSTREAMS_API_KEY", "Name of variable containing Substreams Api Key") + guiCmd.Flags().StringP("substreams-endpoint", "e", "", "Substreams gRPC endpoint. If empty, will be replaced by the SUBSTREAMS_ENDPOINT_{network_name} environment variable, where `network_name` is determined from the substreams manifest. Some network names have default endpoints.") + guiCmd.Flags().String("network", "", "Specify the network to use for params and initialBlocks, overriding the 'network' field in the substreams package") + guiCmd.Flags().Bool("insecure", false, "Skip certificate validation on GRPC connection") + guiCmd.Flags().Bool("plaintext", false, "Establish GRPC connection in plaintext") + guiCmd.Flags().StringSliceP("header", "H", nil, "Additional headers to be sent in the substreams request") + guiCmd.Flags().StringP("start-block", "s", "", "Start block to stream from. If empty, will be replaced by initialBlock of the first module you are streaming. If negative, will be resolved by the server relative to the chain head") + guiCmd.Flags().StringP("cursor", "c", "", "Cursor to stream from. Leave blank for no cursor") + guiCmd.Flags().StringP("stop-block", "t", "0", "Stop block to end stream at, inclusively.") + guiCmd.Flags().Bool("final-blocks-only", false, "Only process blocks that have pass finality, to prevent any reorg and undo signal by staying further away from the chain HEAD") + guiCmd.Flags().StringSlice("debug-modules-initial-snapshot", nil, "List of 'store' modules from which to print the initial data snapshot (Unavailable in Production Mode") + guiCmd.Flags().StringSlice("debug-modules-output", nil, "List of extra modules from which to print outputs, deltas and logs (Unavailable in Production Mode)") + guiCmd.Flags().Bool("production-mode", false, "Enable Production Mode, with high-speed parallel processing") + guiCmd.Flags().StringArrayP("params", "p", nil, "Set a params for parameterizable modules. Can be specified multiple times. Ex: -p module1=valA -p module2=valX&valY") + guiCmd.Flags().Bool("replay", false, "Replay saved session into GUI from replay.bin") + guiCmd.Flags().Bool("skip-package-validation", false, "Do not perform any validation when reading substreams package") + rootCmd.AddCommand(guiCmd) +} + +// guiCmd represents the command to run substreams remotely +var guiCmd = &cobra.Command{ + Use: "gui [] ", + Short: "Stream module outputs from a given package on a remote endpoint", + Long: cli.Dedent(` + Stream module outputs from a given package on a remote endpoint. The manifest is optional as it will try to find a file named + 'substreams.yaml' in current working directory if nothing entered. You may enter a directory that contains a 'substreams.yaml' + file in place of ', or a link to a remote .spkg file, using urls gs://, http(s)://, ipfs://, etc.'. + `), + RunE: runGui, + Args: cobra.RangeArgs(1, 2), + SilenceUsage: true, +} + +func runGui(cmd *cobra.Command, args []string) error { + // TODO: DRY up this and `run` .. such duplication here. + + manifestPath := "" + var err error + if len(args) == 2 { + manifestPath = args[0] + args = args[1:] + } else { + // Check common error where manifest is provided by module name is missing + if manifest.IsLikelyManifestInput(args[0]) { + return fmt.Errorf("missing argument, check 'substreams run --help' for more information") + } + + // At this point, we assume the user invoked `substreams run ` so we `resolveManifestFile` using the empty string since no argument has been passed. + manifestPath, err = resolveManifestFile("") + if err != nil { + return fmt.Errorf("resolving manifest: %w", err) + } + } + + productionMode := mustGetBool(cmd, "production-mode") + debugModulesOutput := mustGetStringSlice(cmd, "debug-modules-output") + if debugModulesOutput != nil && productionMode { + return fmt.Errorf("cannot set 'debug-modules-output' in 'production-mode'") + } + debugModulesInitialSnapshot := mustGetStringSlice(cmd, "debug-modules-initial-snapshot") + + outputModule := args[0] + network := sflags.MustGetString(cmd, "network") + paramsString := sflags.MustGetStringArray(cmd, "params") + params, err := manifest.ParseParams(paramsString) + if err != nil { + return fmt.Errorf("parsing params: %w", err) + } + + readerOptions := []manifest.Option{ + manifest.WithOverrideOutputModule(outputModule), + manifest.WithOverrideNetwork(network), + manifest.WithParams(params), + } + if sflags.MustGetBool(cmd, "skip-package-validation") { + readerOptions = append(readerOptions, manifest.SkipPackageValidationReader()) + } + + manifestReader, err := manifest.NewReader(manifestPath, readerOptions...) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + pkg, graph, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("read manifest %q: %w", manifestPath, err) + } + + endpoint, err := manifest.ExtractNetworkEndpoint(pkg.Network, mustGetString(cmd, "substreams-endpoint"), zlog) + if err != nil { + return fmt.Errorf("extracting endpoint: %w", err) + } + + authToken, authType := tools.GetAuth(cmd, "substreams-api-key-envvar", "substreams-api-token-envvar") + substreamsClientConfig := client.NewSubstreamsClientConfig( + endpoint, + authToken, + authType, + mustGetBool(cmd, "insecure"), + mustGetBool(cmd, "plaintext"), + ) + + homeDir, err := os.UserHomeDir() + if err != nil { + homeDir = "." + } else { + err = os.MkdirAll(filepath.Join(homeDir, ".config", "substreams"), 0755) + if err != nil { + return fmt.Errorf("creating config directory: %w", err) + } + + homeDir = filepath.Join(homeDir, ".config", "substreams") + } + + cursor := mustGetString(cmd, "cursor") + + fmt.Println("Launching Substreams GUI...") + + startBlock, readFromModule, err := readStartBlockFlag(cmd, "start-block") + if err != nil { + return fmt.Errorf("start block: %w", err) + } + + stopBlock, err := readStopBlockFlag(cmd, startBlock, "stop-block", cursor != "") + if err != nil { + return fmt.Errorf("stop block: %w", err) + } + + if readFromModule { // need to tweak the stop block here + sb, err := graph.ModuleInitialBlock(outputModule) + if err != nil { + return fmt.Errorf("getting module start block: %w", err) + } + startBlock := int64(sb) + stopBlock, err = readStopBlockFlag(cmd, startBlock, "stop-block", cursor != "") + if err != nil { + return fmt.Errorf("stop block: %w", err) + } + } + + requestConfig := &request.Config{ + ManifestPath: manifestPath, + Pkg: pkg, + Graph: graph, + ReadFromModule: readFromModule, + ProdMode: productionMode, + DebugModulesOutput: debugModulesOutput, + DebugModulesInitialSnapshot: debugModulesInitialSnapshot, + OutputModule: outputModule, + SubstreamsClientConfig: substreamsClientConfig, + HomeDir: homeDir, + Vcr: mustGetBool(cmd, "replay"), + Headers: parseHeaders(mustGetStringSlice(cmd, "header")), + Cursor: cursor, + StartBlock: startBlock, + StopBlock: stopBlock, + FinalBlocksOnly: mustGetBool(cmd, "final-blocks-only"), + Params: params, + ReaderOptions: readerOptions, + } + + ui, err := tui2.New(requestConfig) + if err != nil { + return err + } + prog := tea.NewProgram(ui, tea.WithAltScreen()) + if _, err := prog.Run(); err != nil { + return fmt.Errorf("gui error: %w", err) + } + + return nil +} + +// resolveManifestFile is solely nowadays by `substreams gui`. That is because manifest.Reader +// now has the ability to resolve itself to the correct location. +// +// However `substreams gui` displays the value, so we want to display the resolved +// value to the user. +// +// FIXME: Find a way to share this with manifest.Reader somehow. Maybe as a method on +// on the reader which would resolve the file, sharing the internal logic. +func resolveManifestFile(input string) (manifestName string, err error) { + if input == "" { + _, err := os.Stat("substreams.yaml") + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("no manifest entered in directory without a manifest") + } + return "", fmt.Errorf("finding manifest: %w", err) + } + + return "substreams.yaml", nil + } else if strings.HasSuffix(input, ".spkg") { + return input, nil + } + + inputInfo, err := os.Stat(input) + if err != nil { + return "", fmt.Errorf("read input file info: %w", err) + } + + if inputInfo.IsDir() { + potentialManifest := filepath.Join(inputInfo.Name(), "substreams.yaml") + _, err := os.Stat(potentialManifest) + if err != nil { + return "", fmt.Errorf("finding manifest in directory: %w", err) + } + return filepath.Join(input, "substreams.yaml"), nil + } + return input, nil +} diff --git a/firehose/substreams/cmd/substreams/gui_test.go b/firehose/substreams/cmd/substreams/gui_test.go new file mode 100644 index 0000000..0489350 --- /dev/null +++ b/firehose/substreams/cmd/substreams/gui_test.go @@ -0,0 +1,107 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_resolveManifestFile(t *testing.T) { + type args struct { + input string + dirToMake string + filesOnDisk []string + } + tests := []struct { + name string + args args + wantManifestName string + assertion require.ErrorAssertionFunc + }{ + { + "no input provided", + args{"", "", []string{"substreams.yaml"}}, + "substreams.yaml", + require.NoError, + }, + { + "no input provided and not substreams.yaml present", + args{"", "", []string{}}, + "", + errorEqual("no manifest entered in directory without a manifest"), + }, + { + "input provided, valid manifest file", + args{"substreams-custom.yaml", "", []string{"substreams-custom.yaml"}}, + "substreams-custom.yaml", + require.NoError, + }, + { + "input provided, invalid manifest file", + args{"substreams-custom.yaml", "", []string{}}, + "", + errorEqual("read input file info: stat substreams-custom.yaml: no such file or directory"), + }, + { + "input provided, valid dir", + args{"manifests-dir", "manifests-dir", []string{"substreams.yaml"}}, + "manifests-dir/substreams.yaml", + require.NoError, + }, + { + "input provided, invalid dir", + args{"manifests-dir", "manifests-dir", []string{}}, + "", + errorEqual("read input file info: stat manifests-dir: no such file or directory"), + }, + { + "input provided, valid spkg", + args{"https://github.com/org/repo/file.spkg", "", []string{}}, + "https://github.com/org/repo/file.spkg", + require.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + root := filepath.Join(t.TempDir(), tt.args.dirToMake) + + for _, fileOnDisk := range tt.args.filesOnDisk { + directory := filepath.Join(root, filepath.Dir(fileOnDisk)) + filename := filepath.Join(directory, filepath.Base(fileOnDisk)) + + err := os.MkdirAll(directory, os.ModePerm) + require.NoError(t, err) + + err = os.WriteFile(filename, []byte{}, os.ModePerm) + require.NoError(t, err) + } + + cwd, err := os.Getwd() + require.NoError(t, err) + + defer func() { + err := os.Chdir(cwd) + require.NoError(t, err) + }() + + if tt.args.dirToMake != "" { + root = filepath.Dir(root) + } + err = os.Chdir(root) + require.NoError(t, err) + + gotManifestName, err := resolveManifestFile(tt.args.input) + tt.assertion(t, err) + assert.Equal(t, tt.wantManifestName, gotManifestName) + }) + } +} + +func errorEqual(expectedErrString string) require.ErrorAssertionFunc { + return func(t require.TestingT, err error, msgAndArgs ...interface{}) { + require.EqualError(t, err, expectedErrString, msgAndArgs...) + } +} diff --git a/firehose/substreams/cmd/substreams/headers.go b/firehose/substreams/cmd/substreams/headers.go new file mode 100644 index 0000000..7dc743d --- /dev/null +++ b/firehose/substreams/cmd/substreams/headers.go @@ -0,0 +1,22 @@ +package main + +import ( + "log" + "strings" +) + +// util to parse headers flags +func parseHeaders(headers []string) map[string]string { + if headers == nil { + return nil + } + result := make(map[string]string) + for _, header := range headers { + parts := strings.Split(header, ":") + if len(parts) != 2 { + log.Fatalf("invalid header format: %s", header) + } + result[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1]) + } + return result +} diff --git a/firehose/substreams/cmd/substreams/info.go b/firehose/substreams/cmd/substreams/info.go new file mode 100644 index 0000000..30eae0d --- /dev/null +++ b/firehose/substreams/cmd/substreams/info.go @@ -0,0 +1,172 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/substreams/info" + + "github.com/spf13/cobra" +) + +func init() { + infoCmd.Flags().String("output-sinkconfig-files-path", "", "if non-empty, any sinkconfig field of type 'bytes' that was packed from a file will be written to that path") + infoCmd.Flags().Bool("skip-package-validation", false, "Do not perform any validation when reading substreams package") +} + +var infoCmd = &cobra.Command{ + Use: "info [ []]", + Short: "Display package modules and docs", + Long: cli.Dedent(` + Display package modules and docs. The manifest is optional as it will try to find a file named + 'substreams.yaml' in current working directory if nothing entered. You may enter a directory that contains + a 'substreams.yaml' file in place of ', or a link to a remote .spkg file, using urls gs://, http(s)://, ipfs://, etc.'. + Specify an "output_module" to see how processing can be divided in different stages to produce the requested output. + `), + RunE: runInfo, + Args: cobra.RangeArgs(0, 2), + SilenceUsage: true, +} + +func init() { + infoCmd.Flags().Bool("json", false, "Output as JSON") + rootCmd.AddCommand(infoCmd) +} + +func runInfo(cmd *cobra.Command, args []string) error { + manifestPath := "" + if len(args) != 0 { + manifestPath = args[0] + } + + var outputModule string + if len(args) == 2 { + outputModule = args[1] + } + + outputSinkconfigFilesPath := mustGetString(cmd, "output-sinkconfig-files-path") + + info, err := info.Extended(manifestPath, outputModule, sflags.MustGetBool(cmd, "skip-package-validation")) + if err != nil { + return err + } + + if mustGetBool(cmd, "json") { + res, err := json.MarshalIndent(info, "", " ") + if err != nil { + return err + } + fmt.Println(string(res)) + return nil + } + + fmt.Println("Package name:", info.Name) + fmt.Println("Version:", info.Version) + if doc := info.Documentation; doc != nil && *doc != "" { + fmt.Println("Doc: " + strings.Replace(*doc, "\n", "\n ", -1)) + } + if info.Image != nil { + fmt.Printf("Image: [embedded image: %d bytes]\n", len(info.Image)) + } + + fmt.Println("Modules:") + fmt.Println("----") + for _, mod := range info.Modules { + fmt.Println("Name:", mod.Name) + fmt.Println("Initial block:", mod.InitialBlock) + fmt.Println("Kind:", mod.Kind) + for _, input := range mod.Inputs { + fmt.Printf("Input: %s: %s\n", input.Type, input.Name) + } + + switch mod.Kind { + case "map": + fmt.Println("Output Type:", *mod.OutputType) + case "store": + fmt.Println("Value Type:", *mod.ValueType) + fmt.Println("Update Policy:", *mod.UpdatePolicy) + default: + fmt.Println("Kind: Unknown") + } + + fmt.Println("Hash:", mod.Hash) + if doc := mod.Documentation; doc != nil && *doc != "" { + fmt.Println("Doc: ", *doc) + } + fmt.Println("") + } + + if info.Network != "" { + fmt.Printf("Network: %s\n", info.Network) + fmt.Println("") + } + + if info.Networks != nil { + fmt.Println("Networks:") + for network, params := range info.Networks { + fmt.Printf(" %s:\n", network) + if params.InitialBlocks != nil { + fmt.Println(" Initial Blocks:") + } + for mod, start := range params.InitialBlocks { + fmt.Printf(" - %s: %d\n", mod, start) + } + if params.Params != nil { + fmt.Println(" Params:") + } + for mod, p := range params.Params { + fmt.Printf(" - %s: %q\n", mod, p) + } + fmt.Println("") + } + } + + if outputModule != "" { + stages := info.ExecutionStages + for i, layers := range stages { + var layerDefs []string + for _, l := range layers { + var mods []string + for _, m := range l { + mods = append(mods, m) + } + layerDefs = append(layerDefs, fmt.Sprintf(`["%s"]`, strings.Join(mods, `","`))) + } + fmt.Printf("Stage %d: [%s]\n", i, strings.Join(layerDefs, `,`)) + } + } + + if info.SinkInfo != nil { + fmt.Println("Sink config:") + fmt.Println("----") + fmt.Println("type:", info.SinkInfo.TypeUrl) + + fmt.Println("configs:") + fmt.Println(info.SinkInfo.Configs) + + if outputSinkconfigFilesPath != "" && info.SinkInfo.Files != nil { + if err := os.MkdirAll(outputSinkconfigFilesPath, 0755); err != nil { + return err + } + fmt.Println("output files:") + for k, v := range info.SinkInfo.Files { + filename := filepath.Join(outputSinkconfigFilesPath, k) + f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return err + } + if _, err := f.Write(v); err != nil { + return err + } + fmt.Printf(" - %q written to %q\n", k, filename) + } + } + } + + return nil +} diff --git a/firehose/substreams/cmd/substreams/init.go b/firehose/substreams/cmd/substreams/init.go new file mode 100644 index 0000000..d397f22 --- /dev/null +++ b/firehose/substreams/cmd/substreams/init.go @@ -0,0 +1,885 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "strconv" + "strings" + "time" + + "github.com/manifoldco/promptui" + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/dhttp" + "github.com/streamingfast/eth-go" + "github.com/streamingfast/substreams/codegen" + "github.com/streamingfast/substreams/codegen/templates" +) + +// Some developers centric environment override to make it faster to iterate on `substreams init` command +var ( + devInitSourceDirectory = os.Getenv("SUBSTREAMS_DEV_INIT_SOURCE_DIRECTORY") + devInitProjectName = os.Getenv("SUBSTREAMS_DEV_INIT_PROJECT_NAME") + devInitProtocol = os.Getenv("SUBSTREAMS_DEV_INIT_PROTOCOL") + devInitEthereumTrackedContract = os.Getenv("SUBSTREAMS_DEV_INIT_ETHEREUM_TRACKED_CONTRACT") + devInitEthereumChain = os.Getenv("SUBSTREAMS_DEV_INIT_ETHEREUM_CHAIN") +) + +var errInitUnsupportedChain = errors.New("unsupported chain") +var errInitUnsupportedProtocol = errors.New("unsupported protocol") + +var initCmd = &cobra.Command{ + Use: "init []", + Short: "Initialize a new, working Substreams project from scratch", + Long: cli.Dedent(` + Initialize a new, working Substreams project from scratch. The path parameter is optional, + with your current working directory being the default value. + + If you have an Etherscan API Key, you can set it to "ETHERSCAN_API_KEY" environment variable, it will be used to + fetch the ABIs and contract information. + `), + RunE: runSubstreamsInitE, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +var etherscanAPIKey = "YourApiKeyToken" + +func init() { + if x := os.Getenv("ETHERSCAN_API_KEY"); x != "" { + etherscanAPIKey = x + } + rootCmd.AddCommand(initCmd) +} + +func runSubstreamsInitE(cmd *cobra.Command, args []string) error { + relativeWorkingDir := "." + if len(args) == 1 { + relativeWorkingDir = args[0] + } + + if devInitSourceDirectory != "" { + relativeWorkingDir = devInitSourceDirectory + } + + absoluteWorkingDir, err := filepath.Abs(relativeWorkingDir) + if err != nil { + return fmt.Errorf("getting absolute path of %q: %w", relativeWorkingDir, err) + } + + projectName, moduleName, err := promptProjectName(absoluteWorkingDir) + if err != nil { + return fmt.Errorf("running project name prompt: %w", err) + } + + absoluteProjectDir := filepath.Join(absoluteWorkingDir, projectName) + + protocol, err := promptProtocol() + if err != nil { + return fmt.Errorf("running protocol prompt: %w", err) + } + + switch protocol { + case codegen.ProtocolEthereum: + chainSelected, err := promptEthereumChain() + if err != nil { + return fmt.Errorf("running chain prompt: %w", err) + } + if chainSelected == codegen.EthereumChainOther { + fmt.Println() + fmt.Println("We haven't added any templates for your selected chain quite yet") + fmt.Println() + fmt.Println("Come join us in discord at https://discord.gg/u8amUbGBgF and suggest templates/chains you want to see!") + fmt.Println() + return errInitUnsupportedChain + } + + chain := templates.EthereumChainsByID[chainSelected.String()] + if chain == nil { + return fmt.Errorf("unknown chain: %s", chainSelected.String()) + } + + ethereumContracts, err := promptEthereumVerifiedContracts(eth.MustNewAddress(chain.DefaultContractAddress), chain.DefaultContractName) + if err != nil { + return fmt.Errorf("running contract prompt: %w", err) + } + + fmt.Printf("Tracking %d contract(s), let's define a short name for each contract\n", len(ethereumContracts)) + ethereumContracts, err = promptEthereumContractShortNames(ethereumContracts) + if err != nil { + return fmt.Errorf("running short name contract prompt: %w", err) + } + + fmt.Printf("Retrieving %s contract information (ABI & creation block)\n", chain.DisplayName) + // Get contract abiContents & parse them + ethereumContracts, err = getAndSetContractABIs(cmd.Context(), ethereumContracts, chain) + if err != nil { + return fmt.Errorf("getting %s contract ABI: %w", chain.DisplayName, err) + } + + // Get contract creation block + lowestStartBlock, err := getContractCreationBlock(cmd.Context(), ethereumContracts, chain) + if err != nil { + // FIXME: not sure if we should simplify set the contract block num to zero by default + return fmt.Errorf("getting %s contract creating block: %w", chain.DisplayName, err) + } + + for _, contract := range ethereumContracts { + fmt.Printf("Generating ABI Event models for %s\n", contract.GetName()) + events, err := templates.BuildEventModels(contract.GetAbi()) + if err != nil { + return fmt.Errorf("build ABI event models for contract [%s - %s]: %w", contract.GetAddress(), contract.GetName(), err) + } + contract.SetEvents(events) + if contract.GetWithCalls() { + fmt.Printf("Generating ABI Call models for %s\n", contract.GetName()) + calls, err := templates.BuildCallModels(contract.GetAbi()) + if err != nil { + return fmt.Errorf("build ABI call models for contract [%s - %s]: %w", contract.GetAddress(), contract.GetName(), err) + } + contract.SetCalls(calls) + } + } + + // Ask for any dynamic datasources + ethereumContracts, err = promptEthereumDynamicDataSources(cmd.Context(), ethereumContracts, chain) + if err != nil { + return fmt.Errorf("running dynamic datasources prompt, %s, %w", chain.DisplayName, err) + } + + fmt.Println("Writing project files") + project, err := templates.NewEthereumProject( + projectName, + moduleName, + chain, + ethereumContracts, + lowestStartBlock, + ) + if err != nil { + return fmt.Errorf("new Ethereum %s project: %w", chain.DisplayName, err) + } + + if err := renderProjectFilesIn(project, absoluteProjectDir); err != nil { + return fmt.Errorf("render Ethereum %s project: %w", chain.DisplayName, err) + } + + case codegen.ProtocolOther: + fmt.Println() + fmt.Println("We haven't added any templates for your selected protocol quite yet") + fmt.Println() + fmt.Println("Come join us in discord at https://discord.gg/u8amUbGBgF and suggest templates/chains you want to see!") + fmt.Println() + return errInitUnsupportedProtocol + } + + fmt.Println("Generating Protobuf Rust code") + if err := protogenSubstreams(absoluteProjectDir); err != nil { + return fmt.Errorf("protobuf generation: %w", err) + } + + fmt.Printf("Project %q initialized at %q\n", projectName, absoluteWorkingDir) + fmt.Println() + fmt.Println("Run 'make build' to build the wasm code.") + fmt.Println() + fmt.Println("The following substreams.yaml files have been created with different sink targets:") + fmt.Println(" * substreams.yaml: no sink target") + fmt.Println(" * substreams.sql.yaml: PostgreSQL sink") + fmt.Println(" * substreams.clickhouse.yaml: Clickhouse sink") + fmt.Println(" * substreams.subgraph.yaml: Sink into Substreams-based subgraph") + + return nil +} + +func protogenSubstreams(absoluteProjectDir string) error { + cmd := exec.Command("substreams", "protogen", `--exclude-paths`, `sf/substreams,google`) + cmd.Dir = absoluteProjectDir + + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(string(output)) + return fmt.Errorf("running %q failed: %w", cmd, err) + } + + return nil +} + +func renderProjectFilesIn(project templates.Project, absoluteProjectDir string) error { + files, err := project.Render() + if err != nil { + return fmt.Errorf("render project: %w", err) + } + + for relativeFile, content := range files { + file := filepath.Join(absoluteProjectDir, strings.ReplaceAll(relativeFile, "/", string(os.PathSeparator))) + + directory := filepath.Dir(file) + if err := os.MkdirAll(directory, os.ModePerm); err != nil { + return fmt.Errorf("create directory %q: %w", directory, err) + } + + if err := os.WriteFile(file, content, 0644); err != nil { + // remove directory, we want a complete e2e file generation + e := os.RemoveAll(directory) + if e != nil { + return fmt.Errorf("removing directory %s: %w and write file: %w", directory, e, err) + } + return fmt.Errorf("write file: %w", err) + } + } + + return nil +} + +// We accept _ here because they are used across developers. we sanitize it later when +// used within Substreams module. +var moduleNameRegexp = regexp.MustCompile(`^([a-z][a-z0-9_]{0,63})$`) + +func promptProjectName(absoluteSrcDir string) (string, string, error) { + if name := devInitProjectName; name != "" { + return name, projectNameToModuleName(name), nil + } + + projectName, err := prompt("Project name (lowercase, numbers, undescores)", &promptOptions{ + Validate: func(input string) error { + ok := moduleNameRegexp.MatchString(input) + if !ok { + return fmt.Errorf("invalid name: must match %s", moduleNameRegexp) + } + + if cli.DirectoryExists(input) { + return fmt.Errorf("project %q already exist in %q", input, absoluteSrcDir) + } + + return nil + }, + }) + if err != nil { + return "", "", err + } + + return projectName, projectNameToModuleName(projectName), nil +} + +func projectNameToModuleName(in string) string { + return strings.ReplaceAll(in, "-", "_") +} + +func promptEthereumVerifiedContracts(defaultAddress eth.Address, defaultContractName string) ([]*templates.EthereumContract, error) { + if devInitEthereumTrackedContract != "" { + // It's ok to panic, we expect the dev to put in a valid Ethereum address + return []*templates.EthereumContract{ + templates.NewEthereumContract("", eth.MustNewAddress(devInitEthereumTrackedContract), nil, ""), + }, nil + } + + var ethContracts []*templates.EthereumContract + + inputOrDefaultFunc := func(input string) (eth.Address, error) { + if input == "" { + return defaultAddress, nil + } + return eth.NewAddress(input) + } + + inputOrEmptyFunc := func(input string) (eth.Address, error) { + if input == "" { + return nil, nil + } + return eth.NewAddress(input) + } + + firstContractAddress, err := promptContractAddress(fmt.Sprintf("Contract address to track (leave empty to use %q)", defaultContractName), inputOrDefaultFunc) + if err != nil { + return nil, err + } + + ethContracts = append(ethContracts, templates.NewEthereumContract("", firstContractAddress, nil, "")) + + if bytes.Equal(firstContractAddress, defaultAddress) { + return ethContracts, nil + } + + for { + contractAddr, err := promptContractAddress("Would you like to track another contract? (Leave empty if not)", inputOrEmptyFunc) + if err != nil { + return nil, err + } + + if contractAddr == nil { + return ethContracts, nil + } + ethContracts = append(ethContracts, templates.NewEthereumContract("", contractAddr, nil, "")) + } +} + +func promptContractAddress(message string, inputFuncCheck func(input string) (eth.Address, error)) (eth.Address, error) { + return promptT(message, inputFuncCheck, &promptOptions{ + Validate: func(input string) error { + if input == "" { + return nil + } + _, err := eth.NewAddress(input) + if err != nil { + return fmt.Errorf("invalid address: %w", err) + } + return nil + }, + }) +} + +var shortNameRegexp = regexp.MustCompile(`^([a-z][a-z0-9]{0,63})$`) + +func promptEthereumContractShortNames(ethereumContracts []*templates.EthereumContract) ([]*templates.EthereumContract, error) { + for _, contract := range ethereumContracts { + shortName, err := prompt(fmt.Sprintf("Choose a short name for %s (lowercase and numbers only)", contract.GetAddress()), &promptOptions{ + Validate: func(input string) error { + ok := shortNameRegexp.MatchString(input) + if !ok { + return fmt.Errorf("invalid name: must match %s", shortNameRegexp) + } + + return nil + }, + }) + if err != nil { + return nil, err + } + contract.SetName(shortName) + + withCalls, err := promptCallOrEvents() + if err != nil { + return nil, err + } + contract.SetWithCalls(withCalls) + } + + return ethereumContracts, nil +} + +func promptEthereumDynamicDataSources(ctx context.Context, ethereumContracts []*templates.EthereumContract, chain *templates.EthereumChain) ([]*templates.EthereumContract, error) { + dds, err := promptConfirm("Would you like to track a dynamic datasource", &promptOptions{ + PromptTemplates: &promptui.PromptTemplates{ + Success: `{{ "Track a dynamic datasource:" | faint }} `, + }, + }) + if err != nil { + return nil, err + } + + if !dds { + return ethereumContracts, nil + } + + selected := ethereumContracts[0] + if len(ethereumContracts) != 1 { + contractNames := make([]string, len(ethereumContracts)) + for i := range ethereumContracts { + contractNames[i] = ethereumContracts[i].GetName() + } + + choice := promptui.Select{ + Label: "Select the factory contract", + Items: contractNames, + Templates: &promptui.SelectTemplates{ + Selected: `{{ "Contract:" | faint }} {{ . }}`, + }, + HideHelp: true, + } + idx, _, err := choice.Run() + if err != nil { + return nil, err + } + selected = ethereumContracts[idx] + } + + events := selected.GetEvents() + fmt.Println("Select the event on the factory that triggers the creation of a dynamic datasource:") + eventNames := make([]string, len(events)) + for i := range events { + eventNames[i] = events[i].Rust.ABIStructName + } + + choice := promptui.Select{ + Label: "Select the event", + Items: eventNames, + Templates: &promptui.SelectTemplates{ + Selected: `{{ "Event:" | faint }} {{ . }}`, + }, + HideHelp: true, + } + idx, _, err := choice.Run() + if err != nil { + return nil, err + } + selectedEventName := events[idx].Rust.ABIStructName + + fmt.Println("Select the field on the factory event that provides the address of the dynamic datasource:") + eventFields := events[idx].Proto.Fields + eventFieldNames := make([]string, len(eventFields)) + for i, eventField := range eventFields { + eventFieldNames[i] = eventField.Name + } + + choice = promptui.Select{ + Label: "Select the event field", + Items: eventFieldNames, + Templates: &promptui.SelectTemplates{ + Selected: `{{ "Field:" | faint }} {{ . }}`, + }, + HideHelp: true, + } + _, selectedEventField, err := choice.Run() + if err != nil { + return nil, err + } + + shortName, err := prompt("Choose a short name for the created datasource, (lowercase and numbers only)", &promptOptions{ + Default: selectedEventField, + Validate: func(input string) error { + ok := shortNameRegexp.MatchString(input) + if !ok { + return fmt.Errorf("invalid name: must match %s", shortNameRegexp) + } + + return nil + }, + }) + if err != nil { + return nil, err + } + + withCalls, err := promptCallOrEvents() + if err != nil { + return nil, err + } + + referenceContractAddress, err := promptContractAddress( + "Enter a reference contract address to fetch the ABI", + func(input string) (eth.Address, error) { + return eth.NewAddress(input) + }) + if err != nil { + return nil, err + } + abi, abiContent, err := getContractABIFollowingProxy(ctx, referenceContractAddress, chain) + if err != nil { + return nil, fmt.Errorf("getting contract ABI for %s: %w", referenceContractAddress, err) + } + + fmt.Println("adding dynamic datasource", shortName, selectedEventName, selectedEventField) + selected.AddDynamicDataSource(shortName, abi, abiContent, selectedEventName, selectedEventField, withCalls) + + return ethereumContracts, nil +} + +func promptCallOrEvents() (calls bool, err error) { + choice := promptui.Select{ + Label: "Select the type of data that you want to extract from this contract:", + Items: []string{"Events only", "Events + Calls"}, + HideHelp: true, + } + resp, _, err := choice.Run() + if err != nil { + return false, err + } + + switch resp { + case 0: + return false, nil + case 1: + return true, nil + } + + panic("impossible choice") +} + +func promptProtocol() (codegen.Protocol, error) { + if devInitProtocol != "" { + // It's ok to panic, we expect the dev to put in a valid Ethereum address + protocol, err := codegen.ParseProtocol(devInitProtocol) + if err != nil { + panic(fmt.Errorf("invalid protocol: %w", err)) + } + + return protocol, nil + } + + choice := promptui.Select{ + Label: "Select protocol", + Items: codegen.ProtocolNames(), + Templates: &promptui.SelectTemplates{ + Selected: `{{ "Protocol:" | faint }} {{ . }}`, + }, + HideHelp: true, + } + + _, selection, err := choice.Run() + if err != nil { + if errors.Is(err, promptui.ErrInterrupt) { + // We received Ctrl-C, users wants to abort, nothing else to do, quit immediately + os.Exit(1) + } + + return codegen.ProtocolOther, fmt.Errorf("running protocol prompt: %w", err) + } + + var protocol codegen.Protocol + if err := protocol.UnmarshalText([]byte(selection)); err != nil { + panic(fmt.Errorf("impossible, selecting hard-coded value from enum itself, something is really wrong here")) + } + + return protocol, nil +} + +func promptEthereumChain() (codegen.EthereumChain, error) { + if devInitEthereumChain != "" { + // It's ok to panic, we expect the dev to put in a valid Ethereum chain + chain, err := codegen.ParseEthereumChain(devInitEthereumChain) + if err != nil { + panic(fmt.Errorf("invalid chain: %w", err)) + } + + return chain, nil + } + + choice := promptui.Select{ + Label: "Select Ethereum chain", + Items: codegen.EthereumChainNames(), + Templates: &promptui.SelectTemplates{ + Selected: `{{ "Ethereum chain:" | faint }} {{ . }}`, + }, + HideHelp: true, + } + + _, selection, err := choice.Run() + if err != nil { + if errors.Is(err, promptui.ErrInterrupt) { + // We received Ctrl-C, users wants to abort, nothing else to do, quit immediately + os.Exit(1) + } + + return codegen.EthereumChainOther, fmt.Errorf("running chain prompt: %w", err) + } + + var chain codegen.EthereumChain + if err := chain.UnmarshalText([]byte(selection)); err != nil { + panic(fmt.Errorf("impossible, selecting hard-coded value from enum itself, something is really wrong here")) + } + + return chain, nil +} + +type promptOptions struct { + Validate promptui.ValidateFunc + IsConfirm bool + PromptTemplates *promptui.PromptTemplates + Default string +} + +var confirmPromptRegex = regexp.MustCompile("(y|Y|n|N|No|Yes|YES|NO)") + +func prompt(label string, opts *promptOptions) (string, error) { + var templates *promptui.PromptTemplates + + if opts != nil { + templates = opts.PromptTemplates + } + + if templates == nil { + templates = &promptui.PromptTemplates{ + Success: `{{ . | faint }}{{ ":" | faint}} `, + } + } + + if opts != nil && opts.IsConfirm { + // We have no differences + templates.Valid = `{{ "?" | blue}} {{ . | bold }} {{ "[y/N]" | faint}} ` + templates.Invalid = templates.Valid + } + + prompt := promptui.Prompt{ + Label: label, + Templates: templates, + Default: opts.Default, + } + if opts != nil && opts.Validate != nil { + prompt.Validate = opts.Validate + } + + if opts != nil && opts.IsConfirm { + prompt.Validate = func(in string) error { + if !confirmPromptRegex.MatchString(in) { + return errors.New("answer with y/yes/Yes or n/no/No") + } + + return nil + } + } + + choice, err := prompt.Run() + if err != nil { + if errors.Is(err, promptui.ErrInterrupt) { + // We received Ctrl-C, users wants to abort, nothing else to do, quit immediately + os.Exit(1) + } + + if prompt.IsConfirm && errors.Is(err, promptui.ErrAbort) { + return "false", nil + } + + return "", fmt.Errorf("running prompt: %w", err) + } + + return choice, nil +} + +// promptT is just like [prompt] but accepts a transformer that transform the `string` into the generic type T. +func promptT[T any](label string, transformer func(string) (T, error), opts *promptOptions) (T, error) { + choice, err := prompt(label, opts) + if err == nil { + return transformer(choice) + } + + var empty T + return empty, err +} + +// promptConfirm is just like [prompt] but enforce `IsConfirm` and returns a boolean which is either +// `true` for yes answer or `false` for a no answer. +func promptConfirm(label string, opts *promptOptions) (bool, error) { + if opts == nil { + opts = &promptOptions{} + } + + opts.IsConfirm = true + transform := func(in string) (bool, error) { + in = strings.ToLower(in) + return in == "y" || in == "yes", nil + } + + return promptT(label, transform, opts) +} + +var httpClient = http.Client{ + Transport: dhttp.NewLoggingRoundTripper(zlog, tracer, http.DefaultTransport), + Timeout: 30 * time.Second, +} + +// getProxyContractImplementation returns the implementation address and a timer to wait before next call +func getProxyContractImplementation(ctx context.Context, address eth.Address, endpoint string) (*eth.Address, *time.Timer, error) { + // check for proxy contract's implementation + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/api?module=contract&action=getsourcecode&address=%s&apiKey=%s", endpoint, address.Pretty(), etherscanAPIKey), nil) + + if err != nil { + return nil, nil, fmt.Errorf("new request: %w", err) + } + + res, err := httpClient.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("getting contract abi from etherscan: %w", err) + } + defer res.Body.Close() + + type Response struct { + Message string `json:"message"` // ex: `OK-Missing/Invalid API Key, rate limit of 1/5sec applied` + Result []struct { + Implementation string `json:"Implementation"` + // ContractName string `json:"ContractName"` + } `json:"result"` + } + + var response Response + + bod, err := io.ReadAll(res.Body) + if err != nil { + return nil, nil, err + } + if err := json.NewDecoder(bytes.NewReader(bod)).Decode(&response); err != nil { + return nil, nil, fmt.Errorf("unmarshaling %s: %w", string(bod), err) + } + + timer := timerUntilNextCall(response.Message) + + if len(response.Result) == 0 { + return nil, timer, nil + } + + if len(response.Result[0].Implementation) != 42 { + return nil, timer, nil + } + + addr, err := eth.NewAddress(response.Result[0].Implementation) + if err != nil { + return nil, timer, err + } + return &addr, timer, nil +} + +func timerUntilNextCall(msg string) *time.Timer { + // etherscan-specific + if strings.HasPrefix(msg, "OK-Missing/Invalid API Key") { + return time.NewTimer(time.Second * 5) + } + return time.NewTimer(time.Millisecond * 400) +} + +func getContractABI(ctx context.Context, address eth.Address, endpoint string) (*eth.ABI, string, *time.Timer, error) { + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/api?module=contract&action=getabi&address=%s&apiKey=%s", endpoint, address.Pretty(), etherscanAPIKey), nil) + if err != nil { + return nil, "", nil, fmt.Errorf("new request: %w", err) + } + + res, err := httpClient.Do(req) + if err != nil { + return nil, "", nil, fmt.Errorf("getting contract abi from etherscan: %w", err) + } + defer res.Body.Close() + + type Response struct { + Message string `json:"message"` // ex: `OK-Missing/Invalid API Key, rate limit of 1/5sec applied` + Result interface{} `json:"result"` + } + + var response Response + if err := json.NewDecoder(res.Body).Decode(&response); err != nil { + return nil, "", nil, fmt.Errorf("unmarshaling: %w", err) + } + + timer := timerUntilNextCall(response.Message) + + abiContent, ok := response.Result.(string) + if !ok { + return nil, "", timer, fmt.Errorf(`invalid response "Result" field type, expected "string" got "%T"`, response.Result) + } + + ethABI, err := eth.ParseABIFromBytes([]byte(abiContent)) + if err != nil { + return nil, "", timer, fmt.Errorf("parsing abi %q: %w", abiContent, err) + } + return ethABI, abiContent, timer, err +} + +func getContractABIFollowingProxy(ctx context.Context, contractAddress eth.Address, chain *templates.EthereumChain) (*eth.ABI, string, error) { + abi, abiContent, wait, err := getContractABI(ctx, contractAddress, chain.ApiEndpoint) + if err != nil { + return nil, "", err + } + + <-wait.C + implementationAddress, wait, err := getProxyContractImplementation(ctx, contractAddress, chain.ApiEndpoint) + if err != nil { + return nil, "", err + } + <-wait.C + + if implementationAddress != nil { + implementationABI, implementationABIContent, wait, err := getContractABI(ctx, *implementationAddress, chain.ApiEndpoint) + if err != nil { + return nil, "", err + } + for k, v := range implementationABI.LogEventsMap { + abi.LogEventsMap[k] = append(abi.LogEventsMap[k], v...) + } + + for k, v := range implementationABI.LogEventsByNameMap { + abi.LogEventsByNameMap[k] = append(abi.LogEventsByNameMap[k], v...) + } + + abiAsArray := []map[string]interface{}{} + if err := json.Unmarshal([]byte(abiContent), &abiAsArray); err != nil { + return nil, "", fmt.Errorf("unmarshalling abiContent as array: %w", err) + } + + implementationABIAsArray := []map[string]interface{}{} + if err := json.Unmarshal([]byte(implementationABIContent), &implementationABIAsArray); err != nil { + return nil, "", fmt.Errorf("unmarshalling implementationABIContent as array: %w", err) + } + + abiAsArray = append(abiAsArray, implementationABIAsArray...) + + content, err := json.Marshal(abiAsArray) + if err != nil { + return nil, "", fmt.Errorf("re-marshalling ABI") + } + abiContent = string(content) + + fmt.Printf("Fetched contract ABI for Implementation %s of Proxy %s\n", *implementationAddress, contractAddress) + <-wait.C + } + return abi, abiContent, nil + +} + +func getAndSetContractABIs(ctx context.Context, contracts []*templates.EthereumContract, chain *templates.EthereumChain) ([]*templates.EthereumContract, error) { + for _, contract := range contracts { + abi, abiContent, err := getContractABIFollowingProxy(ctx, contract.GetAddress(), chain) + if err != nil { + return nil, fmt.Errorf("getting contract ABI for %s: %w", contract.GetAddress(), err) + } + + //fmt.Println("this is the complete abiContent after merge", abiContent) + contract.SetAbiContent(abiContent) + contract.SetAbi(abi) + + fmt.Printf("Fetched contract ABI for %s\n", contract.GetAddress()) + } + + return contracts, nil +} + +func getContractCreationBlock(ctx context.Context, contracts []*templates.EthereumContract, chain *templates.EthereumChain) (uint64, error) { + var lowestStartBlock uint64 = math.MaxUint64 + for _, contract := range contracts { + req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/api?module=account&action=txlist&address=%s&page=1&offset=1&sort=asc&apikey=%s", chain.ApiEndpoint, contract.GetAddress().Pretty(), etherscanAPIKey), nil) + if err != nil { + return 0, fmt.Errorf("new request: %w", err) + } + + res, err := httpClient.Do(req) + if err != nil { + return 0, fmt.Errorf("failed request to etherscan: %w", err) + } + defer res.Body.Close() + + type Response struct { + Status string `json:"status"` + Message string `json:"message"` + Result []struct { + BlockNumber string `json:"blockNumber"` + } `json:"result"` + } + + var response Response + if err := json.NewDecoder(res.Body).Decode(&response); err != nil { + return 0, fmt.Errorf("unmarshaling: %w", err) + } + + if len(response.Result) == 0 { + return 0, fmt.Errorf("empty result from response %v", response) + } + + <-timerUntilNextCall(response.Message).C + + blockNum, err := strconv.ParseUint(response.Result[0].BlockNumber, 10, 64) + if err != nil { + return 0, fmt.Errorf("parsing block number: %w", err) + } + + if blockNum < lowestStartBlock { + lowestStartBlock = blockNum + } + + fmt.Printf("Fetched initial block %d for %s (lowest %d)\n", blockNum, contract.GetAddress(), lowestStartBlock) + } + return lowestStartBlock, nil +} diff --git a/firehose/substreams/cmd/substreams/inspect.go b/firehose/substreams/cmd/substreams/inspect.go new file mode 100644 index 0000000..59ec475 --- /dev/null +++ b/firehose/substreams/cmd/substreams/inspect.go @@ -0,0 +1,64 @@ +package main + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/substreams/manifest" + "google.golang.org/protobuf/proto" +) + +var inspectCmd = &cobra.Command{ + Use: "inspect ", + Short: "Display low-level package structure", + RunE: runInspect, + Args: cobra.ExactArgs(1), + SilenceUsage: true, +} + +func init() { + rootCmd.AddCommand(inspectCmd) + inspectCmd.Flags().Bool("skip-package-validation", false, "Do not perform any validation when reading substreams package") +} + +func runInspect(cmd *cobra.Command, args []string) error { + manifestPath := args[0] + + var readerOptions []manifest.Option + if sflags.MustGetBool(cmd, "skip-package-validation") { + readerOptions = append(readerOptions, manifest.SkipPackageValidationReader()) + } + + manifestReader, err := manifest.NewReader(manifestPath, readerOptions...) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + pkg, _, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("reading manifest %q: %w", manifestPath, err) + } + + filename := filepath.Join(os.TempDir(), "package.spkg") + + cnt, err := proto.Marshal(pkg) + if err != nil { + return fmt.Errorf("marshalling package: %w", err) + } + + if err := os.WriteFile(filename, cnt, 0644); err != nil { + fmt.Println("") + return fmt.Errorf("writing %q: %w", filename, err) + } + + c := exec.Command("protoc", "--decode=sf.substreams.v1.Package", "--descriptor_set_in="+filename) + c.Stdin = bytes.NewBuffer(cnt) + c.Stdout = os.Stdout + c.Stderr = os.Stderr + return c.Run() +} diff --git a/firehose/substreams/cmd/substreams/logging.go b/firehose/substreams/cmd/substreams/logging.go new file mode 100644 index 0000000..455f2f3 --- /dev/null +++ b/firehose/substreams/cmd/substreams/logging.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/streamingfast/logging" +) + +var zlog, tracer = logging.RootLogger("substreams", "github.com/streamingfast/substreams/cmd/substreams") + +// loggers are instantiated in 'setup()' func diff --git a/firehose/substreams/cmd/substreams/main.go b/firehose/substreams/cmd/substreams/main.go new file mode 100644 index 0000000..308a9f7 --- /dev/null +++ b/firehose/substreams/cmd/substreams/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "os" + "runtime/debug" + "strings" +) + +// Version value, injected via go build `ldflags` at build time +var version = "dev" + +func main() { + info, ok := debug.ReadBuildInfo() + if !ok { + panic("we should have been able to retrieve info from 'runtime/debug#ReadBuildInfo'") + } + + rootCmd.Version = computeVersionString(version, info.Settings) + + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func computeVersionString(version string, settings []debug.BuildSetting) string { + commit := findSetting("vcs.revision", settings) + date := findSetting("vcs.time", settings) + + var labels []string + if len(commit) >= 7 { + labels = append(labels, fmt.Sprintf("Commit %s", commit[0:7])) + } + + if date != "" { + labels = append(labels, fmt.Sprintf("Built %s", date)) + } + + if len(labels) == 0 { + return version + } + + return fmt.Sprintf("%s (%s)", version, strings.Join(labels, ", ")) +} + +func findSetting(key string, settings []debug.BuildSetting) (value string) { + for _, setting := range settings { + if setting.Key == key { + return setting.Value + } + } + + return "" +} diff --git a/firehose/substreams/cmd/substreams/pack.go b/firehose/substreams/cmd/substreams/pack.go new file mode 100644 index 0000000..b710c02 --- /dev/null +++ b/firehose/substreams/cmd/substreams/pack.go @@ -0,0 +1,97 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/substreams/manifest" + "go.uber.org/zap" + "google.golang.org/protobuf/proto" +) + +var packCmd = &cobra.Command{ + Use: "pack []", + Short: "Build an .spkg out of a .yaml manifest", + Long: cli.Dedent(` + Build an .spkg out of a .yaml manifest. The manifest is optional as it will try to find a file named + 'substreams.yaml' in current working directory if nothing entered. You may enter a directory that contains a + 'substreams.yaml' file in place of '', or a link to a remote .spkg file, using urls gs://, http(s)://, ipfs://, etc.'. + `), + RunE: runPack, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func init() { + rootCmd.AddCommand(packCmd) + packCmd.Flags().StringP("output-file", "o", "{manifestDir}/{spkgDefaultName}", cli.FlagDescription(` + Specifies output file where the generated "spkg" file will be written. You can use template directives when + specifying the value of the flag. You can use "{manifestDir}" which resolves to manifest's + directory. You can use "{spkgDefaultName}" which is the pre-computed default name in the form + "-" where "" is the manifest's "package.name" value ("_" values in the name are + replaced by "-") and "" is "package.version" value. You can use "{version}" which resolves + to "package.version". + `)) + //packCmd.Flags().StringArrayP("config", "c", []string{}, cli.FlagDescription(`path to a configuration file that contains overrides for the manifest`)) +} + +func runPack(cmd *cobra.Command, args []string) error { + manifestPath := "" + if len(args) == 1 { + manifestPath = args[0] + } + + manifestReader, err := manifest.NewReader(manifestPath) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + if !manifestReader.IsLocalManifest() { + return fmt.Errorf(`"pack" can only be used to pack local manifest file`) + } + + pkg, _, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("reading manifest %q: %w", manifestPath, err) + } + + originalOutputFile := maybeGetString(cmd, "output-file") + resolvedOutputFile := resolveOutputFile(originalOutputFile, map[string]string{ + "manifestDir": filepath.Dir(manifestPath), + "spkgDefaultName": fmt.Sprintf("%s-%s.spkg", strings.Replace(pkg.PackageMeta[0].Name, "_", "-", -1), pkg.PackageMeta[0].Version), + "version": pkg.PackageMeta[0].Version, + }) + + zlog.Debug("resolved output file", zap.String("original", originalOutputFile), zap.String("resolved", resolvedOutputFile)) + + outputDir := filepath.Dir(resolvedOutputFile) + if err := os.MkdirAll(outputDir, os.ModePerm); err != nil { + return fmt.Errorf("create output directories: %w", err) + } + + cnt, err := proto.Marshal(pkg) + if err != nil { + return fmt.Errorf("marshalling package: %w", err) + } + + if err := os.WriteFile(resolvedOutputFile, cnt, 0644); err != nil { + fmt.Println("") + return fmt.Errorf("writing file: %w", err) + } + + fmt.Printf("Successfully wrote %q.\n", resolvedOutputFile) + + return nil +} + +func resolveOutputFile(input string, bindings map[string]string) string { + for k, v := range bindings { + input = strings.ReplaceAll(input, `{`+k+`}`, v) + } + + return input +} diff --git a/firehose/substreams/cmd/substreams/protogen.go b/firehose/substreams/cmd/substreams/protogen.go new file mode 100644 index 0000000..b608400 --- /dev/null +++ b/firehose/substreams/cmd/substreams/protogen.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "path/filepath" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/substreams/codegen" + "github.com/streamingfast/substreams/manifest" + "go.uber.org/zap" +) + +var protogenCmd = &cobra.Command{ + Use: "protogen []", + Short: "Generate Rust bindings from a package", + Long: cli.Dedent(` + Generate Rust bindings from a package. The manifest is optional as it will try to find a file named + 'substreams.yaml' in current working directory if nothing entered. You may enter a directory that contains a 'substreams.yaml' + file in place of '', or a link to a remote .spkg file, using urls gs://, http(s)://, ipfs://, etc.'. + + Note: if you have a data structure with an attribute that starts with an underscore, buf generate will remove the underscore. + `), + RunE: runProtogen, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func init() { + rootCmd.AddCommand(protogenCmd) + + flags := protogenCmd.Flags() + flags.StringP("output-path", "o", "src/pb", cli.FlagDescription(` + Directory to output generated .rs files, if the received argument is a local Substreams manifest file + (e.g. a local file ending with .yaml), the output path will be made relative to it + `)) + flags.StringArrayP("exclude-paths", "x", []string{}, "Exclude specific files or directories, for example \"proto/a/a.proto\" or \"proto/a\"") + flags.Bool("generate-mod-rs", true, cli.FlagDescription(` + Generate the protobuf 'mod.rs' file alongside the rust bindings. Include '--generate-mod-rs=false' If you wish to disable this generation. + If there is a present 'buf.gen.yaml', consult https://github.com/neoeinstein/protoc-gen-prost/blob/main/protoc-gen-prost-crate/README.md to add 'mod.rs' generation functionality. + `)) + flags.Bool("show-generated-buf-gen", false, "Whether to show the generated buf.gen.yaml file or not") +} + +func runProtogen(cmd *cobra.Command, args []string) error { + outputPath := mustGetString(cmd, "output-path") + excludePaths := mustGetStringArray(cmd, "exclude-paths") + generateMod := mustGetBool(cmd, "generate-mod-rs") + showGeneratedBufGen := mustGetBool(cmd, "show-generated-buf-gen") + + manifestPath := "" + if len(args) == 1 { + manifestPath = args[0] + } + + readerOptions := []manifest.Option{ + manifest.SkipSourceCodeReader(), + manifest.SkipModuleOutputTypeValidationReader(), + } + manifestReader, err := manifest.NewReader(manifestPath, readerOptions...) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + if manifestReader.IsLocalManifest() && !filepath.IsAbs(outputPath) { + newOutputPath := filepath.Join(filepath.Dir(manifestPath), outputPath) + + zlog.Debug("manifest path is a local manifest, making output path relative to it", zap.String("old", outputPath), zap.String("new", newOutputPath)) + outputPath = newOutputPath + } + + pkg, _, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("reading manifest %q: %w", manifestPath, err) + } + + generator := codegen.NewProtoGenerator(outputPath, excludePaths, generateMod) + return generator.GenerateProto(pkg, showGeneratedBufGen) +} diff --git a/firehose/substreams/cmd/substreams/proxy.go b/firehose/substreams/cmd/substreams/proxy.go new file mode 100644 index 0000000..d42f8ec --- /dev/null +++ b/firehose/substreams/cmd/substreams/proxy.go @@ -0,0 +1,191 @@ +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "time" + + "connectrpc.com/connect" + grpcreflect "connectrpc.com/grpcreflect" + "github.com/rs/cors" + "github.com/spf13/cobra" + "github.com/streamingfast/substreams/client" + "github.com/streamingfast/substreams/manifest" + pbrpcsubstreams "github.com/streamingfast/substreams/pb/sf/substreams/rpc/v2" + ssconnect "github.com/streamingfast/substreams/pb/sf/substreams/rpc/v2/pbsubstreamsrpcconnect" + "github.com/streamingfast/substreams/tools" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "google.golang.org/grpc/metadata" +) + +var proxyCmd = &cobra.Command{ + Use: "proxy ", + Short: "A tool to proxy substreams requests from a web browser (connect-web protocol)", + RunE: runProxy, + SilenceUsage: true, +} + +type ConnectServer struct { + ssconnect.UnimplementedStreamHandler + Manifest string + StartBlock uint64 + SubstreamsClientConfig *client.SubstreamsClientConfig +} + +func (cs *ConnectServer) Blocks( + ctx context.Context, + req *connect.Request[pbrpcsubstreams.Request], + stream *connect.ServerStream[pbrpcsubstreams.Response], +) error { + newReq := &pbrpcsubstreams.Request{ + StartBlockNum: req.Msg.StartBlockNum, + StopBlockNum: req.Msg.StopBlockNum, + StartCursor: req.Msg.StartCursor, + FinalBlocksOnly: req.Msg.FinalBlocksOnly, + OutputModule: req.Msg.OutputModule, + Modules: req.Msg.Modules, + DebugInitialStoreSnapshotForModules: req.Msg.DebugInitialStoreSnapshotForModules, + } + + if cs.Manifest != "" { + manifestReader, err := manifest.NewReader(cs.Manifest) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + pkg, _, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("read manifest %q: %w", cs.Manifest, err) + } + newReq.Modules = pkg.Modules + } + + if cs.StartBlock != 0 { + newReq.StartBlockNum = int64(cs.StartBlock) + } + + ssClient, connClose, callOpts, headers, err := client.NewSubstreamsClient(cs.SubstreamsClientConfig) + if err != nil { + return fmt.Errorf("substreams client setup: %w", err) + } + defer connClose() + + if err := newReq.Validate(); err != nil { + return fmt.Errorf("validate request: %w", err) + } + + if headers.IsSet() { + ctx = metadata.AppendToOutgoingContext(ctx, headers.ToArray()...) + } + cli, err := ssClient.Blocks(ctx, newReq, callOpts...) + if err != nil { + return fmt.Errorf("call sf.substreams.rpc.v2.Stream/Blocks: %w", err) + } + + for { + resp, err := cli.Recv() + if resp != nil { + stream.Send(resp) + } + if err != nil { + if err == io.EOF { + return nil + } + return err + } + } +} + +func init() { + proxyCmd.Flags().StringP("substreams-endpoint", "e", "mainnet.eth.streamingfast.io:443", "Substreams gRPC endpoint") + proxyCmd.Flags().String("substreams-api-token-envvar", "SUBSTREAMS_API_TOKEN", "name of variable containing Substreams Authentication token") + proxyCmd.Flags().String("substreams-api-key-envvar", "SUBSTREAMS_API_KEY", "Name of variable containing Substreams Api Key") + proxyCmd.Flags().String("listen-addr", "localhost:8080", "listen on this address (unencrypted)") + proxyCmd.Flags().BoolP("insecure", "k", false, "Skip certificate validation on GRPC connection") + proxyCmd.Flags().BoolP("plaintext", "p", false, "Establish GRPC connection in plaintext") + proxyCmd.Flags().String("force-manifest", "", "if non-empty, the requests' modules will be replaced by the modules loaded from this location. Can be a local spkg or yaml file, or a remote (HTTP) spkg file.") + proxyCmd.Flags().Uint64("force-start-block", 0, "if non-zero, the requests' start-block will be replaced by this value") + + tools.Cmd.AddCommand(proxyCmd) +} + +func runProxy(cmd *cobra.Command, args []string) error { + addr := mustGetString(cmd, "listen-addr") + fmt.Println("listening on", addr) + + authToken, authType := tools.GetAuth(cmd, "substreams-api-key-envvar", "substreams-api-token-envvar") + substreamsClientConfig := client.NewSubstreamsClientConfig( + mustGetString(cmd, "substreams-endpoint"), + authToken, + authType, + mustGetBool(cmd, "insecure"), + mustGetBool(cmd, "plaintext"), + ) + + cs := &ConnectServer{ + Manifest: mustGetString(cmd, "force-manifest"), + SubstreamsClientConfig: substreamsClientConfig, + StartBlock: mustGetUint64(cmd, "force-start-block"), + } + + reflector := grpcreflect.NewStaticReflector( + "sf.substreams.rpc.v2.Stream", + ) + + mux := http.NewServeMux() + // The generated constructors return a path and a plain net/http + // handler. + mux.Handle(ssconnect.NewStreamHandler(cs)) + mux.Handle(grpcreflect.NewHandlerV1(reflector)) + mux.Handle(grpcreflect.NewHandlerV1Alpha(reflector)) + return http.ListenAndServe( + addr, + // For gRPC clients, it's convenient to support HTTP/2 without TLS. You can + // avoid x/net/http2 by using http.ListenAndServeTLS. + h2c.NewHandler( + newCORS().Handler(mux), + &http2.Server{}), + ) +} + +func newCORS() *cors.Cors { + // To let web developers play with the demo service from browsers, we need a + // very permissive CORS setup. + return cors.New(cors.Options{ + AllowedMethods: []string{ + http.MethodHead, + http.MethodGet, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + }, + AllowOriginFunc: func(origin string) bool { + // Allow all origins, which effectively disables CORS. + return true + }, + AllowedHeaders: []string{"*"}, + ExposedHeaders: []string{ + // Content-Type is in the default safelist. + "Accept", + "Accept-Encoding", + "Accept-Post", + "Connect-Accept-Encoding", + "Connect-Content-Encoding", + "Content-Encoding", + "Grpc-Accept-Encoding", + "Grpc-Encoding", + "Grpc-Message", + "Grpc-Status", + "Grpc-Status-Details-Bin", + }, + // Let browsers cache CORS information for longer, which reduces the number + // of preflight requests. Any changes to ExposedHeaders won't take effect + // until the cached data expires. FF caps this value at 24h, and modern + // Chrome caps it at 2h. + MaxAge: int(2 * time.Hour / time.Second), + }) +} diff --git a/firehose/substreams/cmd/substreams/root.go b/firehose/substreams/cmd/substreams/root.go new file mode 100644 index 0000000..eed811e --- /dev/null +++ b/firehose/substreams/cmd/substreams/root.go @@ -0,0 +1,25 @@ +package main + +import ( + "time" + + "github.com/spf13/cobra" + "go.uber.org/zap/zapcore" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "substreams", + Short: "A tool to manipulate Substreams, and process them locally and remotely", + Long: "Any place where is specified, a 'substreams.yaml', a local '.spkg' file or an https://...spkg file can be specified", + SilenceUsage: true, + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + setup(cmd, zapcore.WarnLevel) + }, +} + +func init() { + // From https://thegraph.com/docs/en/operating-graph-node/ + rootCmd.PersistentFlags().String("ipfs-url", "https://ipfs.network.thegraph.com", "IPFS endpoint to resolve substreams-based subgraphs as manifest") + rootCmd.PersistentFlags().Duration("ipfs-timeout", time.Second*10, "IPFS timeout when resolving substreams-based subgraphs as manifest") +} diff --git a/firehose/substreams/cmd/substreams/run.go b/firehose/substreams/cmd/substreams/run.go new file mode 100644 index 0000000..51443bf --- /dev/null +++ b/firehose/substreams/cmd/substreams/run.go @@ -0,0 +1,251 @@ +package main + +import ( + "context" + "fmt" + "io" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/substreams/client" + "github.com/streamingfast/substreams/manifest" + pbsubstreamsrpc "github.com/streamingfast/substreams/pb/sf/substreams/rpc/v2" + "github.com/streamingfast/substreams/tools" + "github.com/streamingfast/substreams/tools/test" + "github.com/streamingfast/substreams/tui" + "go.uber.org/zap" + "google.golang.org/grpc/metadata" +) + +func init() { + runCmd.Flags().StringP("substreams-endpoint", "e", "", "Substreams gRPC endpoint. If empty, will be replaced by the SUBSTREAMS_ENDPOINT_{network_name} environment variable, where `network_name` is determined from the substreams manifest. Some network names have default endpoints.") + runCmd.Flags().String("substreams-api-token-envvar", "SUBSTREAMS_API_TOKEN", "name of variable containing Substreams Authentication token") + runCmd.Flags().String("substreams-api-key-envvar", "SUBSTREAMS_API_KEY", "Name of variable containing Substreams Api Key") + runCmd.Flags().String("network", "", "Specify the network to use for params and initialBlocks, overriding the 'network' field in the substreams package") + runCmd.Flags().StringP("start-block", "s", "", "Start block to stream from. If empty, will be replaced by initialBlock of the first module you are streaming. If negative, will be resolved by the server relative to the chain head") + runCmd.Flags().StringP("cursor", "c", "", "Cursor to stream from. Leave blank for no cursor") + runCmd.Flags().StringP("stop-block", "t", "0", "Stop block to end stream at, exclusively. If the start-block is positive, a '+' prefix can indicate 'relative to start-block'") + runCmd.Flags().Bool("final-blocks-only", false, "Only process blocks that have pass finality, to prevent any reorg and undo signal by staying further away from the chain HEAD") + runCmd.Flags().Bool("insecure", false, "Skip certificate validation on GRPC connection") + runCmd.Flags().Bool("plaintext", false, "Establish GRPC connection in plaintext") + runCmd.Flags().StringP("output", "o", "", "Output mode. Defaults to 'ui' when in a TTY is present, and 'json' otherwise") + runCmd.Flags().StringSlice("debug-modules-initial-snapshot", nil, "List of 'store' modules from which to print the initial data snapshot (Unavailable in Production Mode)") + runCmd.Flags().StringSlice("debug-modules-output", nil, "List of modules from which to print outputs, deltas and logs (Unavailable in Production Mode)") + runCmd.Flags().StringSliceP("header", "H", nil, "Additional headers to be sent in the substreams request") + runCmd.Flags().Bool("production-mode", false, "Enable Production Mode, with high-speed parallel processing") + runCmd.Flags().Bool("skip-package-validation", false, "Do not perform any validation when reading substreams package") + runCmd.Flags().StringArrayP("params", "p", nil, "Set a params for parameterizable modules. Can be specified multiple times. Ex: -p module1=valA -p module2=valX&valY") + runCmd.Flags().String("test-file", "", "runs a test file") + runCmd.Flags().Bool("test-verbose", false, "print out all the results") + rootCmd.AddCommand(runCmd) +} + +// runCmd represents the command to run substreams remotely +var runCmd = &cobra.Command{ + Use: "run [] ", + Short: "Stream module outputs from a given package on a remote endpoint", + Long: cli.Dedent(` + Stream module outputs from a given package on a remote endpoint. The manifest is optional as it will try to find a file named + 'substreams.yaml' in current working directory if nothing entered. You may enter a directory that contains a 'substreams.yaml' + 'substreams.yaml' file in place of '', or a link to a remote .spkg file, using urls gs://, http(s)://, ipfs://, etc.'. + `), + RunE: runRun, + Args: cobra.RangeArgs(1, 2), + SilenceUsage: true, +} + +func runRun(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + var manifestPath, outputModule string + if len(args) == 1 { + outputModule = args[0] + + // Check common error where manifest is provided by module name is missing + if manifest.IsLikelyManifestInput(outputModule) { + return fmt.Errorf("missing argument, check 'substreams run --help' for more information") + } + } else { + manifestPath = args[0] + outputModule = args[1] + } + + outputMode := mustGetString(cmd, "output") + + network := sflags.MustGetString(cmd, "network") + paramsString := sflags.MustGetStringArray(cmd, "params") + params, err := manifest.ParseParams(paramsString) + if err != nil { + return fmt.Errorf("parsing params: %w", err) + } + + readerOptions := []manifest.Option{ + manifest.WithOverrideOutputModule(outputModule), + manifest.WithOverrideNetwork(network), + manifest.WithParams(params), + } + if sflags.MustGetBool(cmd, "skip-package-validation") { + readerOptions = append(readerOptions, manifest.SkipPackageValidationReader()) + } + + manifestReader, err := manifest.NewReader(manifestPath, readerOptions...) + if err != nil { + return fmt.Errorf("manifest reader: %w", err) + } + + pkg, graph, err := manifestReader.Read() + if err != nil { + return fmt.Errorf("read manifest %q: %w", manifestPath, err) + } + + endpoint, err := manifest.ExtractNetworkEndpoint(pkg.Network, mustGetString(cmd, "substreams-endpoint"), zlog) + if err != nil { + return fmt.Errorf("extracting endpoint: %w", err) + } + + msgDescs, err := manifest.BuildMessageDescriptors(pkg) + if err != nil { + return fmt.Errorf("building message descriptors: %w", err) + } + + var testRunner *test.Runner + testFile := mustGetString(cmd, "test-file") + if testFile != "" { + zlog.Info("running test runner", zap.String(testFile, testFile)) + testRunner, err = test.NewRunner(testFile, msgDescs, mustGetBool(cmd, "test-verbose"), zlog) + if err != nil { + return fmt.Errorf("failed to setup test runner: %w", err) + } + } + + productionMode := mustGetBool(cmd, "production-mode") + debugModulesOutput := mustGetStringSlice(cmd, "debug-modules-output") + if debugModulesOutput != nil && productionMode { + return fmt.Errorf("cannot set 'debug-modules-output' in 'production-mode'") + } + + debugModulesInitialSnapshot := mustGetStringSlice(cmd, "debug-modules-initial-snapshot") + + startBlock, readFromModule, err := readStartBlockFlag(cmd, "start-block") + if err != nil { + return fmt.Errorf("stop block: %w", err) + } + + if readFromModule { + sb, err := graph.ModuleInitialBlock(outputModule) + if err != nil { + return fmt.Errorf("getting module start block: %w", err) + } + startBlock = int64(sb) + } + + authToken, authType := tools.GetAuth(cmd, "substreams-api-key-envvar", "substreams-api-token-envvar") + substreamsClientConfig := client.NewSubstreamsClientConfig( + endpoint, + authToken, + authType, + mustGetBool(cmd, "insecure"), + mustGetBool(cmd, "plaintext"), + ) + + ssClient, connClose, callOpts, headers, err := client.NewSubstreamsClient(substreamsClientConfig) + if err != nil { + return fmt.Errorf("substreams client setup: %w", err) + } + defer connClose() + + cursorStr := mustGetString(cmd, "cursor") + + stopBlock, err := readStopBlockFlag(cmd, startBlock, "stop-block", cursorStr != "") + if err != nil { + return fmt.Errorf("stop block: %w", err) + } + + req := &pbsubstreamsrpc.Request{ + StartBlockNum: startBlock, + StartCursor: cursorStr, + StopBlockNum: stopBlock, + FinalBlocksOnly: mustGetBool(cmd, "final-blocks-only"), + Modules: pkg.Modules, + OutputModule: outputModule, + ProductionMode: productionMode, + DebugInitialStoreSnapshotForModules: debugModulesInitialSnapshot, + } + + if err := req.Validate(); err != nil { + return fmt.Errorf("validate request: %w", err) + } + toPrint := debugModulesOutput + if toPrint == nil { + toPrint = []string{outputModule} + } + + ui := tui.New(req, pkg, toPrint) + if err := ui.Init(outputMode); err != nil { + return fmt.Errorf("TUI initialization: %w", err) + } + defer ui.CleanUpTerminal() + + streamCtx, cancel := context.WithCancel(ctx) + ui.OnTerminated(func(err error) { + if err != nil { + fmt.Printf("UI terminated with error %q\n", err) + } + + cancel() + }) + defer cancel() + + // add additional authorization headers + if headers.IsSet() { + streamCtx = metadata.AppendToOutgoingContext(streamCtx, headers.ToArray()...) + } + //parse additional-headers flag + additionalHeaders := mustGetStringSlice(cmd, "header") + if additionalHeaders != nil { + res := parseHeaders(additionalHeaders) + headerArray := make([]string, 0, len(res)*2) + for k, v := range res { + headerArray = append(headerArray, k, v) + } + streamCtx = metadata.AppendToOutgoingContext(streamCtx, headerArray...) + } + + ui.SetRequest(req) + ui.Connecting() + cli, err := ssClient.Blocks(streamCtx, req, callOpts...) + if err != nil && streamCtx.Err() != context.Canceled { + return fmt.Errorf("call sf.substreams.rpc.v2.Stream/Blocks: %w", err) + } + ui.Connected() + + for { + resp, err := cli.Recv() + if resp != nil { + if err := ui.IncomingMessage(ctx, resp, testRunner); err != nil { + fmt.Printf("RETURN HANDLER ERROR: %s\n", err) + } + } + if err != nil { + if err == io.EOF { + ui.Cancel() + fmt.Println("Total Read Bytes (server-side consumption):", ui.TotalReadBytes) + fmt.Println("all done") + if testRunner != nil { + testRunner.LogResults() + } + + return nil + } + + // Special handling if interrupted the context ourselves, no error + if streamCtx.Err() == context.Canceled { + ui.Cancel() + return nil + } + + return err + } + } +} diff --git a/firehose/substreams/cmd/substreams/service-deploy.go b/firehose/substreams/cmd/substreams/service-deploy.go new file mode 100644 index 0000000..77dfa74 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-deploy.go @@ -0,0 +1,114 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/substreams/manifest" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" +) + +func init() { + serviceCmd.AddCommand(deployCmd) + deployCmd.Flags().StringArray("deployment-params", []string{}, "Extra parameters to pass to the deployment endpoint") + deployCmd.Flags().StringArrayP("params", "p", []string{}, "Parameters to pass to the substreams (ex: module2=key1=valX&key2=valY)") + deployCmd.Flags().StringP("network", "n", "", "Network to deploy to (overrides the 'network' field in the manifest)") + deployCmd.Flags().Bool("prod", false, "Enable production mode (default: false)") +} + +var deployCmd = &cobra.Command{ + Use: "deploy ", + Short: "Deploy a substreams package with a sink", + Long: cli.Dedent(` + Sends a "deploy" request to a server. By default, it will talk to a local "substreams alpha service serve" instance. + The substreams must contain a "SinkConfig" section to be deployable. + `), + RunE: deployE, + Args: cobra.ExactArgs(1), + SilenceUsage: true, +} + +func deployE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + file := args[0] + + paramsString := sflags.MustGetStringArray(cmd, "params") + params, err := manifest.ParseParams(paramsString) + if err != nil { + return fmt.Errorf("parsing params: %w", err) + } + + network := sflags.MustGetString(cmd, "network") + readerOptions := []manifest.Option{ + manifest.WithOverrideNetwork(network), + manifest.WithParams(params), + } + + reader, err := manifest.NewReader(file, readerOptions...) + if err != nil { + return err + } + pkg, _, err := reader.Read() + if err != nil { + return err + } + + if pkg.SinkConfig == nil { + return fmt.Errorf("cannot deploy package %q: it does not contain a sink_config section", file) + } + if pkg.SinkModule == "" { + return fmt.Errorf("cannot deploy package %q: it does not specify a sink_module", file) + } + + pkg.Networks = nil // we don't want to send this to the server, so it does not apply network values again, possibly losing the overriden params + + paramsMap := make(map[string]string) + for _, param := range mustGetStringArray(cmd, "deployment-params") { + parts := strings.SplitN(param, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("invalid parameter format: %q", param) + } + paramsMap[parts[0]] = parts[1] + } + + deployParams := []*pbsinksvc.Parameter{} + for k, v := range paramsMap { + deployParams = append(deployParams, &pbsinksvc.Parameter{ + Key: k, + Value: v, + }) + } + + req := connect.NewRequest(&pbsinksvc.DeployRequest{ + SubstreamsPackage: pkg, + Parameters: deployParams, + DevelopmentMode: !sflags.MustGetBool(cmd, "prod"), + }) + if err := addHeaders(cmd, req); err != nil { + return err + } + + fmt.Printf("Deploying... (creating services, please wait)\n") + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + + resp, err := cli.Deploy(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + + reason := "" + if resp.Msg.Reason != "" { + reason = " (" + resp.Msg.Reason + ")" + } + fmt.Printf("Deployed substreams sink %q:\n Status: %v%s\n", resp.Msg.DeploymentId, resp.Msg.Status, reason) + fmt.Print(resp.Msg.Motd) + printServices(resp.Msg.Services) + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-info.go b/firehose/substreams/cmd/substreams/service-info.go new file mode 100644 index 0000000..05f413a --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-info.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" + server "github.com/streamingfast/substreams/sink-server" +) + +func init() { + serviceCmd.AddCommand(sinkInfoCmd) +} + +var sinkInfoCmd = &cobra.Command{ + Use: "info [deployment-id]", + Short: "Get info on a deployed substreams sink", + Long: cli.Dedent(` + Sends an "Info" request to a server. By default, it will talk to a local "substreams alpha sink-serve" instance. + It returns information about the status of the substreams and its available services. + If deploymentID is not set or is incomplete, the CLI will try to guess (unless --strict is set). + `), + RunE: serviceInfoE, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func serviceInfoE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + var id string + if len(args) == 1 { + id = args[0] + } + + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + if len(id) < server.DeploymentIDLength { + if sflags.MustGetBool(cmd, "strict") { + return fmt.Errorf("invalid ID provided: %q and '--strict' is set", id) + } + matching, err := fuzzyMatchDeployment(ctx, id, cli, cmd, fuzzyMatchPreferredStatusOrder) + if err != nil { + return err + } + id = matching.Id + } + + req := connect.NewRequest(&pbsinksvc.InfoRequest{ + DeploymentId: id, + }) + if err := addHeaders(cmd, req); err != nil { + return err + } + + resp, err := cli.Info(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + reason := "" + if resp.Msg.Reason != "" { + reason = " (" + resp.Msg.Reason + ")" + } + fmt.Printf("Response for deployment %q:\n Name: %s (%s)\n Output module: %s (%s)\n Status: %v%s\n ", id, resp.Msg.PackageInfo.Name, resp.Msg.PackageInfo.Version, resp.Msg.PackageInfo.OutputModuleName, resp.Msg.PackageInfo.OutputModuleHash, resp.Msg.Status, reason) + if resp.Msg.Progress != nil { + fmt.Printf("Last processed block: %d\n", resp.Msg.Progress.LastProcessedBlock) + } + fmt.Print(resp.Msg.Motd) + printServices(resp.Msg.Services) + + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-list.go b/firehose/substreams/cmd/substreams/service-list.go new file mode 100644 index 0000000..a46c33f --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-list.go @@ -0,0 +1,74 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "strings" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" +) + +func init() { + serviceCmd.AddCommand(listCmd) +} + +var listCmd = &cobra.Command{ + Use: "list", + Short: "Get list of deployed services", + Long: cli.Dedent(` + Sends a "List" request to a server. By default, it will talk to a local "substreams alpha service serve" instance. + It returns the id and the status of the substreams. + `), + RunE: listE, + Args: cobra.ExactArgs(0), + SilenceUsage: true, +} + +func addHeaders(cmd *cobra.Command, req connect.AnyRequest) error { + envVar := sflags.MustGetString(cmd, "substreams-api-token-envvar") + if value := os.Getenv(envVar); value != "" { + req.Header().Add("authorization", fmt.Sprintf("bearer %s", value)) + } + + for _, header := range sflags.MustGetStringSlice(cmd, "header") { + parts := strings.Split(header, ": ") + if len(parts) != 2 { + return fmt.Errorf("invalid value for header: %s. Only one occurence of ': ' is permitted", header) + } + req.Header().Add(parts[0], parts[1]) + } + return nil +} + +func listE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + + req := connect.NewRequest(&pbsinksvc.ListRequest{}) + if err := addHeaders(cmd, req); err != nil { + return err + } + + resp, err := cli.List(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + + if len(resp.Msg.Deployments) == 0 { + fmt.Printf("No deployments found.\n") + return nil + } + + fmt.Printf("List of deployments:\n") + for _, v := range resp.Msg.Deployments { + fmt.Printf(" - %s (%s-%s): %s (%s)\n", v.Id, v.PackageInfo.Name, v.PackageInfo.Version, v.Status.String(), v.Reason) + } + + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-pause.go b/firehose/substreams/cmd/substreams/service-pause.go new file mode 100644 index 0000000..3d97782 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-pause.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" + server "github.com/streamingfast/substreams/sink-server" +) + +func init() { + serviceCmd.AddCommand(pauseCmd) +} + +var pauseCmd = &cobra.Command{ + Use: "pause [deployment-id]", + Short: "Pause a running service", + Long: cli.Dedent(` + Sends an "Pause" request to a server. By default, it will talk to a local "substreams service serve" instance. + It will pause a substreams and returns information about the change of status. + If deploymentID is not set or is incomplete, the CLI will try to guess (unless --strict is set). + `), + RunE: pauseE, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func pauseE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + var id string + if len(args) == 1 { + id = args[0] + } + + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + if len(id) < server.DeploymentIDLength { + if sflags.MustGetBool(cmd, "strict") { + return fmt.Errorf("invalid ID provided: %q and '--strict' is set", id) + } + matching, err := fuzzyMatchDeployment(ctx, id, cli, cmd, fuzzyMatchPreferredStatusOrder) + if err != nil { + return err + } + id = matching.Id + } + + req := connect.NewRequest(&pbsinksvc.PauseRequest{ + DeploymentId: id, + }) + if err := addHeaders(cmd, req); err != nil { + return err + } + + fmt.Printf("Pausing... please wait\n") + resp, err := cli.Pause(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + fmt.Printf("Response for deployment %q:\n Previous Status: %v, New Status: %v\n", id, resp.Msg.PreviousStatus, resp.Msg.NewStatus) + + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-remove.go b/firehose/substreams/cmd/substreams/service-remove.go new file mode 100644 index 0000000..67f0702 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-remove.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" + server "github.com/streamingfast/substreams/sink-server" +) + +func init() { + serviceCmd.AddCommand(removeCmd) +} + +var removeCmd = &cobra.Command{ + Use: "remove [deployment-id]", + Short: "Remove a deployed service", + Long: cli.Dedent(` + Sends an "Remove" request to a server. By default, it will talk to a local "substreams alpha service serve" instance. + It will remove a service completely, including its data. Use "pause" instead if you want to keep the data. + If deploymentID is not set or is incomplete, the CLI will try to guess (unless --strict is set). + `), + RunE: removeE, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func removeE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + var id string + if len(args) == 1 { + id = args[0] + } + + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + if len(id) < server.DeploymentIDLength { + if sflags.MustGetBool(cmd, "strict") { + return fmt.Errorf("invalid ID provided: %q and '--strict' is set", id) + } + matching, err := fuzzyMatchDeployment(ctx, id, cli, cmd, []pbsinksvc.DeploymentStatus{ + pbsinksvc.DeploymentStatus_STOPPED, + pbsinksvc.DeploymentStatus_PAUSED, + pbsinksvc.DeploymentStatus_FAILING, + pbsinksvc.DeploymentStatus_RUNNING, + pbsinksvc.DeploymentStatus_UNKNOWN, + }) + if err != nil { + return err + } + fmt.Printf("Found deployment %q (%s-%s) from 'fuzzy search'. Do you really want to delete its data ? (y/n): ", matching.Id, matching.PackageInfo.Name, matching.PackageInfo.Version) + if !userConfirm() { + return fmt.Errorf("cancelled by user") + } + id = matching.Id + } + + req := connect.NewRequest(&pbsinksvc.RemoveRequest{ + DeploymentId: id, + }) + if err := addHeaders(cmd, req); err != nil { + return err + } + + fmt.Printf("Stopping... (shutting down services and removing data, please wait)\n") + resp, err := cli.Remove(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + fmt.Printf("Deployment %q successfully deleted.\nPrevious Status: %v\nNew Status: DELETED.", id, resp.Msg.PreviousStatus) + + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-resume.go b/firehose/substreams/cmd/substreams/service-resume.go new file mode 100644 index 0000000..4a970c8 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-resume.go @@ -0,0 +1,72 @@ +package main + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" + server "github.com/streamingfast/substreams/sink-server" +) + +func init() { + serviceCmd.AddCommand(resumeCmd) +} + +var resumeCmd = &cobra.Command{ + Use: "resume [deployment-id]", + Short: "Resume a paused service", + Long: cli.Dedent(` + Sends an "Resume" request to a server. By default, it will talk to a local "substreams alpha service serve" instance. + It will resume a paused substreams and returns information about the change of status. + If deploymentID is not set or is incomplete, the CLI will try to guess (unless --strict is set). + `), + RunE: resumeE, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func resumeE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + var id string + if len(args) == 1 { + id = args[0] + } + + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + if len(id) < server.DeploymentIDLength { + if sflags.MustGetBool(cmd, "strict") { + return fmt.Errorf("invalid ID provided: %q and '--strict' is set", id) + } + matching, err := fuzzyMatchDeployment(ctx, id, cli, cmd, []pbsinksvc.DeploymentStatus{ + pbsinksvc.DeploymentStatus_PAUSED, + pbsinksvc.DeploymentStatus_STOPPED, + }) + if err != nil { + return err + } + id = matching.Id + } + + req := connect.NewRequest(&pbsinksvc.ResumeRequest{ + DeploymentId: id, + }) + if err := addHeaders(cmd, req); err != nil { + return err + } + + fmt.Printf("Resuming... (creating services, please wait)\n") + + resp, err := cli.Resume(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + fmt.Printf("Response for deployment %q:\n Previous Status: %v, New Status: %v\n", id, resp.Msg.PreviousStatus, resp.Msg.NewStatus) + + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-serve.go b/firehose/substreams/cmd/substreams/service-serve.go new file mode 100644 index 0000000..7c32e61 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-serve.go @@ -0,0 +1,106 @@ +package main + +import ( + "context" + "fmt" + "os" + "regexp" + + "github.com/streamingfast/dauth" + dauthnull "github.com/streamingfast/dauth/null" + "github.com/streamingfast/substreams/sink-server/docker" + + "github.com/spf13/cobra" + "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/derr" + server "github.com/streamingfast/substreams/sink-server" + "go.uber.org/zap/zapcore" +) + +func init() { + serviceCmd.AddCommand(serveCmd) + + serveCmd.Flags().StringP("endpoint", "e", "", "Substreams endpoint to connect to") + serveCmd.Flags().String("data-dir", "./sink-data", "Store data to this folder") + serveCmd.Flags().String("listen-addr", "localhost:8000", "Listen for GRPC connections on this address") + serveCmd.Flags().String("cors-host-regex-allow", "^localhost", "Regex to allow CORS origin requests from, defaults to localhost only") + serveCmd.Flags().String("engine", "docker", "Engine to use for deployments, defaults to docker") + serveCmd.Flags().String("kubernetes-config-path", "", "Path to the kubeconfig file for kubernetes engine. If empty, will use InClusterConfig") + serveCmd.Flags().String("kubernetes-namespace", "hosted-substreams-sinks", "Namespace to use for kubernetes engine") + dauthnull.Register() +} + +var serveCmd = &cobra.Command{ + Use: "serve", + Short: "Serve local service deployments using docker-compose", + Long: cli.Dedent(` + Listens for "deploy" requests, allowing you to test your deployable units to a local docker-based dev environment. + `), + PersistentPreRun: func(cmd *cobra.Command, _ []string) { + setup(cmd, zapcore.InfoLevel) + }, + RunE: serveE, + Args: cobra.ExactArgs(0), + SilenceUsage: true, +} + +func serveE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + listenAddr := sflags.MustGetString(cmd, "listen-addr") + corsHostRegexAllow := sflags.MustGetString(cmd, "cors-host-regex-allow") + dataDir := sflags.MustGetString(cmd, "data-dir") + endpoint := sflags.MustGetString(cmd, "endpoint") + + var cors *regexp.Regexp + if corsHostRegexAllow != "" { + hostRegex, err := regexp.Compile(corsHostRegexAllow) + if err != nil { + return fmt.Errorf("faild to compile cors host regex: %w", err) + } + cors = hostRegex + } + + token := os.Getenv("SUBSTREAMS_API_TOKEN") + if token == "" { + return fmt.Errorf("error: please set SUBSTREAMS_API_TOKEN environment variable to a valid substreams API token") + } + + engineType := sflags.MustGetString(cmd, "engine") + var engine server.Engine + var err error + switch engineType { + case "docker": + engine, err = docker.NewEngine(dataDir, token, endpoint) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupported engine: %q", engine) + } + + // local service serve does not support auth, so we disable by using null:// + auth, err := dauth.New("null://", zlog) + if err != nil { + return err + } + + srv, err := server.New(ctx, engine, dataDir, listenAddr, cors, auth, zlog) + if err != nil { + return fmt.Errorf("initializing server: %w", err) + } + + signal := derr.SetupSignalHandler(0) + go func() { + <-signal + srv.Shutdown(nil) + cancel() + }() + + srv.Run(ctx) + <-srv.Terminated() + return srv.Err() +} diff --git a/firehose/substreams/cmd/substreams/service-stop.go b/firehose/substreams/cmd/substreams/service-stop.go new file mode 100644 index 0000000..068d47a --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-stop.go @@ -0,0 +1,68 @@ +package main + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" + server "github.com/streamingfast/substreams/sink-server" +) + +func init() { + serviceCmd.AddCommand(stopCmd) +} + +var stopCmd = &cobra.Command{ + Use: "stop [deployment-id]", + Short: "Stop a running service", + Long: cli.Dedent(` + Sends an "Stop" request to a server. By default, it will talk to a local "substreams alpha service serve" instance. + It will stop a service and returns information about the change of status. + If deploymentID is not set or is incomplete, the CLI will try to guess (unless --strict is set). + `), + RunE: stopE, + Args: cobra.RangeArgs(0, 1), + SilenceUsage: true, +} + +func stopE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + var id string + if len(args) == 1 { + id = args[0] + } + + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + if len(id) < server.DeploymentIDLength { + if sflags.MustGetBool(cmd, "strict") { + return fmt.Errorf("invalid ID provided: %q and '--strict' is set", id) + } + matching, err := fuzzyMatchDeployment(ctx, id, cli, cmd, fuzzyMatchPreferredStatusOrder) + if err != nil { + return err + } + id = matching.Id + } + + req := connect.NewRequest(&pbsinksvc.StopRequest{ + DeploymentId: id, + }) + if err := addHeaders(cmd, req); err != nil { + return err + } + + fmt.Printf("Stopping... (shutting down services, please wait)\n") + resp, err := cli.Stop(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + fmt.Printf("Response for deployment %q:\n Previous Status: %v, New Status: %v\n", id, resp.Msg.PreviousStatus, resp.Msg.NewStatus) + + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-update.go b/firehose/substreams/cmd/substreams/service-update.go new file mode 100644 index 0000000..9cb1153 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-update.go @@ -0,0 +1,100 @@ +package main + +import ( + "fmt" + "net/http" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + cli "github.com/streamingfast/cli" + "github.com/streamingfast/cli/sflags" + "github.com/streamingfast/substreams/manifest" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" + server "github.com/streamingfast/substreams/sink-server" +) + +func init() { + serviceCmd.AddCommand(updateCmd) + updateCmd.Flags().BoolP("reset", "r", false, "Reset the deployment by DELETING ALL ITS DATA") +} + +var updateCmd = &cobra.Command{ + Use: "update [deploymentID]", + Short: "Update a deployed service with a substreams package", + Long: cli.Dedent(` + Sends a "update" request to a server. By default, it will talk to a local "substreams alpha service serve" instance. + The substreams must contain a "SinkConfig" section to be deployable. + If deploymentID is not set or is incomplete, the CLI will try to guess (unless --strict is set). + `), + RunE: updateE, + Args: cobra.RangeArgs(1, 2), + SilenceUsage: true, +} + +func updateE(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + reader, err := manifest.NewReader(args[0]) + if err != nil { + return err + } + pkg, _, err := reader.Read() + if err != nil { + return err + } + + cli := pbsinksvcconnect.NewProviderClient(http.DefaultClient, sflags.MustGetString(cmd, "endpoint")) + + var id string + if len(args) == 2 { + id = args[1] + } + + if len(id) < server.DeploymentIDLength { + if sflags.MustGetBool(cmd, "strict") { + return fmt.Errorf("invalid ID provided: %q and '--strict' is set", id) + } + matching, err := fuzzyMatchDeployment(ctx, id, cli, cmd, fuzzyMatchPreferredStatusOrder) + if err != nil { + return err + } + fmt.Printf("Found deployment %q (%s-%s) from 'fuzzy search'. Do you really want to update this one ? (y/n): ", matching.Id, matching.PackageInfo.Name, matching.PackageInfo.Version) + if !userConfirm() { + return fmt.Errorf("cancelled by user") + } + id = matching.Id + } + + reset := sflags.MustGetBool(cmd, "reset") + + req := connect.NewRequest(&pbsinksvc.UpdateRequest{ + SubstreamsPackage: pkg, + DeploymentId: id, + Reset_: reset, + }) + + deletingString := "" + if reset { + deletingString = " deleting data," + } + + if err := addHeaders(cmd, req); err != nil { + return err + } + + fmt.Printf("Updating service %q... (restarting services,%s please wait)\n", id, deletingString) + resp, err := cli.Update(ctx, req) + if err != nil { + return interceptConnectionError(err) + } + + reason := "" + if resp.Msg.Reason != "" { + reason = " (" + resp.Msg.Reason + ")" + } + fmt.Printf("Update complete for service %q:\n Status: %v%s\n", id, resp.Msg.Status, reason) + fmt.Print(resp.Msg.Motd) + printServices(resp.Msg.Services) + return nil +} diff --git a/firehose/substreams/cmd/substreams/service-utils.go b/firehose/substreams/cmd/substreams/service-utils.go new file mode 100644 index 0000000..f94a322 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service-utils.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "fmt" + "sort" + "strings" + + "connectrpc.com/connect" + "github.com/spf13/cobra" + pbsinksvc "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1" + "github.com/streamingfast/substreams/pb/sf/substreams/sink/service/v1/pbsinksvcconnect" +) + +var fuzzyMatchPreferredStatusOrder = []pbsinksvc.DeploymentStatus{ + pbsinksvc.DeploymentStatus_RUNNING, + pbsinksvc.DeploymentStatus_PAUSED, + pbsinksvc.DeploymentStatus_FAILING, + pbsinksvc.DeploymentStatus_STOPPED, + pbsinksvc.DeploymentStatus_UNKNOWN, +} + +func fuzzyMatchDeployment(ctx context.Context, q string, cli pbsinksvcconnect.ProviderClient, cmd *cobra.Command, preferredStatusOrder []pbsinksvc.DeploymentStatus) (*pbsinksvc.DeploymentWithStatus, error) { + req := connect.NewRequest(&pbsinksvc.ListRequest{}) + if err := addHeaders(cmd, req); err != nil { + return nil, err + } + resp, err := cli.List(ctx, req) + if err != nil { + return nil, fmt.Errorf("error fetching existing deployments: %w", err) + } + + matching := make(map[*pbsinksvc.DeploymentWithStatus]pbsinksvc.DeploymentStatus) + for _, dep := range resp.Msg.Deployments { + if strings.HasPrefix(dep.Id, q) { + matching[dep] = dep.Status + } + } + if len(matching) == 0 { + return nil, fmt.Errorf("cannot find any deployment matching %q", q) + } + if len(matching) == 1 { + for k := range matching { + return k, nil + } + } + // more than one match, take the best status... + + for i := len(preferredStatusOrder) - 1; i >= 0; i-- { + for k, v := range matching { + if v == preferredStatusOrder[i] { + delete(matching, k) + } + } + if len(matching) == 1 { + for k := range matching { + return k, nil + } + } + if len(matching) == 0 { + break + } + } + return nil, fmt.Errorf("cannot determine which deployment should match %q", q) +} + +func printServices(outputs map[string]string) { + fmt.Printf("Services:\n") + var keys []string + for k := range outputs { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + lines := strings.Split(outputs[k], "\n") + prefixLen := len(k) + 6 + var withMargin string + for i, line := range lines { + if i == 0 { + withMargin = line + "\n" + continue + } + withMargin += strings.Repeat(" ", prefixLen) + line + "\n" + } + fmt.Printf(" - %s: %s", k, withMargin) + } + +} + +func userConfirm() bool { + var resp string + _, err := fmt.Scan(&resp) + if err != nil { + panic(err) + } + + switch strings.ToLower(resp) { + case "y", "yes": + return true + } + return false +} + +func interceptConnectionError(err error) error { + if connectError, ok := err.(*connect.Error); ok { + if connectError.Code() == connect.CodeUnavailable { + return fmt.Errorf("cannot connect to sink service: %w. Are you running `substreams alpha service serve` ?", err) + } + } + return err +} diff --git a/firehose/substreams/cmd/substreams/service.go b/firehose/substreams/cmd/substreams/service.go new file mode 100644 index 0000000..4733a06 --- /dev/null +++ b/firehose/substreams/cmd/substreams/service.go @@ -0,0 +1,15 @@ +package main + +import "github.com/spf13/cobra" + +func init() { + alphaCmd.AddCommand(serviceCmd) + serviceCmd.PersistentFlags().StringP("endpoint", "e", "http://localhost:8000", "specify the endpoint to connect to.") + serviceCmd.PersistentFlags().Bool("strict", false, "Require deploymentID parameter to be set and complete") + serviceCmd.PersistentFlags().StringSliceP("header", "H", nil, "Additional headers to be sent in the substreams request (ex: 'X-Substreams-my-var: my-value')") + serviceCmd.PersistentFlags().String("substreams-api-token-envvar", "SUBSTREAMS_API_TOKEN", "Name of variable containing Substreams Authentication token, will be passed as 'authorization: bearer {}' flag") +} + +var serviceCmd = &cobra.Command{ + Use: "service", +} diff --git a/firehose/substreams/cmd/substreams/setup.go b/firehose/substreams/cmd/substreams/setup.go new file mode 100644 index 0000000..70b1a69 --- /dev/null +++ b/firehose/substreams/cmd/substreams/setup.go @@ -0,0 +1,31 @@ +package main + +import ( + "net/http" + _ "net/http/pprof" + + "github.com/spf13/cobra" + "github.com/streamingfast/logging" + "github.com/streamingfast/substreams/manifest" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +func setup(cmd *cobra.Command, loglevel zapcore.Level) { + setupProfiler() + manifest.IPFSURL = mustGetString(cmd, "ipfs-url") + logging.InstantiateLoggers(logging.WithLogLevelSwitcherServerAutoStart(), logging.WithDefaultLevel(loglevel)) +} + +var ( + pprofListenAddr = "localhost:6060" +) + +func setupProfiler() { + go func() { + err := http.ListenAndServe(pprofListenAddr, nil) + if err != nil { + zlog.Debug("unable to start profiling server", zap.Error(err), zap.String("listen_addr", pprofListenAddr)) + } + }() +} diff --git a/firehose/substreams/cmd/substreams/state.go b/firehose/substreams/cmd/substreams/state.go new file mode 100644 index 0000000..64d3a8f --- /dev/null +++ b/firehose/substreams/cmd/substreams/state.go @@ -0,0 +1,50 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "os" + + "github.com/spf13/cobra" + "github.com/streamingfast/substreams/tools" +) + +func init() { + stateCmd.Flags().String("state-store-url", "./localdata", "URL of state store") + + tools.Cmd.AddCommand(stateCmd) +} + +// localCmd represents the base command when called without any subcommands +var stateCmd = &cobra.Command{ + Use: "state ", + RunE: runState, + Args: cobra.ExactArgs(1), + SilenceUsage: true, +} + +func runState(cmd *cobra.Command, args []string) error { + + kvFileNamePath := args[0] + file, err := os.Open(kvFileNamePath) + if err != nil { + log.Panicf("failed reading file: %s", err) + } + defer file.Close() + data, err := io.ReadAll(file) + if err != nil { + panic(err) + } + + fmt.Printf("\nLength: %d bytes", len(data)) + fmt.Printf("\nData: %s", data) + + kv := map[string]string{} + if err = json.Unmarshal(data, &kv); err != nil { + panic(err) + } + fmt.Println("entry count", len(kv)) + return nil +} diff --git a/firehose/substreams/cmd/substreams/tools.go b/firehose/substreams/cmd/substreams/tools.go new file mode 100644 index 0000000..a707835 --- /dev/null +++ b/firehose/substreams/cmd/substreams/tools.go @@ -0,0 +1,7 @@ +package main + +import "github.com/streamingfast/substreams/tools" + +func init() { + rootCmd.AddCommand(tools.Cmd) +} diff --git a/firehose/substreams/cmd/substreams/utils.go b/firehose/substreams/cmd/substreams/utils.go new file mode 100644 index 0000000..d45e597 --- /dev/null +++ b/firehose/substreams/cmd/substreams/utils.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "github.com/spf13/cobra" + "strconv" + "strings" +) + +func readStartBlockFlag(cmd *cobra.Command, flagName string) (int64, bool, error) { + val, err := cmd.Flags().GetString(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + if val == "" { + return 0, true, nil + } + + startBlock, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return 0, false, fmt.Errorf("start block is invalid: %w", err) + } + + return startBlock, false, nil +} + +func readStopBlockFlag(cmd *cobra.Command, startBlock int64, flagName string, withCursor bool) (uint64, error) { + val, err := cmd.Flags().GetString(flagName) + if err != nil { + panic(fmt.Sprintf("flags: couldn't find flag %q", flagName)) + } + + isRelative := strings.HasPrefix(val, "+") + if isRelative { + if withCursor { + return 0, fmt.Errorf("relative stop block is not supported with a cursor") + } + + if startBlock < 0 { + return 0, fmt.Errorf("relative end block is supported only with an absolute start block") + } + + val = strings.TrimPrefix(val, "+") + } + + endBlock, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return 0, fmt.Errorf("end block is invalid: %w", err) + } + + if isRelative { + return uint64(startBlock) + endBlock, nil + } + + return endBlock, nil +} diff --git a/firehose/substreams/codegen/ethereum_chain.go b/firehose/substreams/codegen/ethereum_chain.go new file mode 100644 index 0000000..71de936 --- /dev/null +++ b/firehose/substreams/codegen/ethereum_chain.go @@ -0,0 +1,16 @@ +package codegen + +//go:generate go-enum -f=$GOFILE --marshal --names --nocase + +// ENUM( +// +// Mainnet +// BNB +// Polygon +// Goerli +// Mumbai +// // Sepolia +// Other +// +// ) +type EthereumChain uint diff --git a/firehose/substreams/codegen/ethereum_chain_enum.go b/firehose/substreams/codegen/ethereum_chain_enum.go new file mode 100644 index 0000000..6ad0846 --- /dev/null +++ b/firehose/substreams/codegen/ethereum_chain_enum.go @@ -0,0 +1,120 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: 0.5.6 +// Revision: 97611fddaa414f53713597918c5e954646cb8623 +// Build Date: 2023-03-26T21:38:06Z +// Built By: goreleaser + +package codegen + +import ( + "fmt" + "strings" +) + +const ( + // EthereumChainMainnet is a EthereumChain of type Mainnet. + EthereumChainMainnet EthereumChain = iota + // EthereumChainBNB is a EthereumChain of type BNB. + EthereumChainBNB + // EthereumChainPolygon is a EthereumChain of type Polygon. + EthereumChainPolygon + // EthereumChainArbitrum is a EthereumChain of type Arbitrum. + EthereumChainArbitrum + // EthereumChainGoerli is a EthereumChain of type Goerli. + EthereumChainGoerli + // EthereumChainMumbai is a EthereumChain of type Mumbai. + EthereumChainMumbai + // EthereumChainOther is a EthereumChain of type Other. + EthereumChainOther +) + +var ErrInvalidEthereumChain = fmt.Errorf("not a valid EthereumChain, try [%s]", strings.Join(_EthereumChainNames, ", ")) + +const _EthereumChainName = "MainnetBNBPolygonArbitrumGoerliMumbaiOther" + +var _EthereumChainNames = []string{ + _EthereumChainName[0:7], + _EthereumChainName[7:10], + _EthereumChainName[10:17], + _EthereumChainName[17:25], + _EthereumChainName[25:31], + _EthereumChainName[31:36], + _EthereumChainName[36:41], +} + +// EthereumChainNames returns a list of possible string values of EthereumChain. +func EthereumChainNames() []string { + tmp := make([]string, len(_EthereumChainNames)) + copy(tmp, _EthereumChainNames) + return tmp +} + +var _EthereumChainMap = map[EthereumChain]string{ + EthereumChainMainnet: _EthereumChainName[0:7], + EthereumChainBNB: _EthereumChainName[7:10], + EthereumChainPolygon: _EthereumChainName[10:17], + EthereumChainArbitrum: _EthereumChainName[17:25], + EthereumChainGoerli: _EthereumChainName[25:31], + EthereumChainMumbai: _EthereumChainName[31:36], + EthereumChainOther: _EthereumChainName[36:41], +} + +// String implements the Stringer interface. +func (x EthereumChain) String() string { + if str, ok := _EthereumChainMap[x]; ok { + return str + } + return fmt.Sprintf("EthereumChain(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x EthereumChain) IsValid() bool { + _, ok := _EthereumChainMap[x] + return ok +} + +var _EthereumChainValue = map[string]EthereumChain{ + _EthereumChainName[0:7]: EthereumChainMainnet, + strings.ToLower(_EthereumChainName[0:7]): EthereumChainMainnet, + _EthereumChainName[7:10]: EthereumChainBNB, + strings.ToLower(_EthereumChainName[7:10]): EthereumChainBNB, + _EthereumChainName[10:17]: EthereumChainPolygon, + strings.ToLower(_EthereumChainName[10:17]): EthereumChainPolygon, + _EthereumChainName[17:25]: EthereumChainArbitrum, + strings.ToLower(_EthereumChainName[17:25]): EthereumChainArbitrum, + _EthereumChainName[25:31]: EthereumChainGoerli, + strings.ToLower(_EthereumChainName[25:31]): EthereumChainGoerli, + _EthereumChainName[31:36]: EthereumChainMumbai, + strings.ToLower(_EthereumChainName[31:36]): EthereumChainMumbai, + _EthereumChainName[36:41]: EthereumChainOther, + strings.ToLower(_EthereumChainName[36:41]): EthereumChainOther, +} + +// ParseEthereumChain attempts to convert a string to a EthereumChain. +func ParseEthereumChain(name string) (EthereumChain, error) { + if x, ok := _EthereumChainValue[name]; ok { + return x, nil + } + // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. + if x, ok := _EthereumChainValue[strings.ToLower(name)]; ok { + return x, nil + } + return EthereumChain(0), fmt.Errorf("%s is %w", name, ErrInvalidEthereumChain) +} + +// MarshalText implements the text marshaller method. +func (x EthereumChain) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *EthereumChain) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseEthereumChain(name) + if err != nil { + return err + } + *x = tmp + return nil +} \ No newline at end of file diff --git a/firehose/substreams/codegen/generator.go b/firehose/substreams/codegen/generator.go new file mode 100644 index 0000000..9c56291 --- /dev/null +++ b/firehose/substreams/codegen/generator.go @@ -0,0 +1,499 @@ +package codegen + +import ( + _ "embed" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "text/template" + + "github.com/jhump/protoreflect/desc" + "github.com/streamingfast/substreams/manifest" + pbsubstreams "github.com/streamingfast/substreams/pb/sf/substreams/v1" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/reflect/protoregistry" + "google.golang.org/protobuf/runtime/protoimpl" + "google.golang.org/protobuf/types/descriptorpb" +) + +var E_RUST_MODULE = &protoimpl.ExtensionInfo{ + ExtendedType: (*descriptorpb.FileOptions)(nil), + ExtensionType: (*string)(nil), + Field: 56781, + Name: "rust_module", + Tag: "bytes", +} + +func init() { + err := protoregistry.GlobalTypes.RegisterExtension(E_RUST_MODULE) + if err != nil { + panic(fmt.Errorf("registering proto extension rust_module: %w", err)) + } +} + +//go:embed templates/generator/lib.gotmpl +var tplLibRs string + +//go:embed templates/generator/externs.gotmpl +var tplExterns string + +//go:embed templates/generator/substreams.gotmpl +var tplSubstreams string + +//go:embed templates/generator/mod.gotmpl +var tplMod string + +//go:embed templates/generator/pb_mod.gotmpl +var tplPbMod string + +//go:embed templates/generator/buildsh.gotmpl +var tplBuildSh string + +//go:embed templates/generator/cargotoml.gotmpl +var tplCargoToml string + +//go:embed templates/generator/manifestyaml.gotmpl +var tplManifestYaml string + +//go:embed templates/generator/rusttoolchain.gotmpl +var tplRustToolchain string + +var StoreType = map[string]string{ + "bytes": "Raw", + "string": "String", + "bigint": "BigInt", + "bigdecimal": "BigDecimal", + "bigfloat": "BigDecimal", + "int64": "Int64", + "i64": "Int64", + "float64": "Float64", + "boolean": "bool", +} + +func maybeTranslateType(t string) string { + u, ok := StoreType[t] + if !ok { + return t + } + return u +} + +var UpdatePoliciesMap = map[string]string{ + "": "Unset", + manifest.UpdatePolicySet: "Set", + manifest.UpdatePolicySetIfNotExists: "SetIfNotExists", + manifest.UpdatePolicyAdd: "Add", + manifest.UpdatePolicyMin: "Min", + manifest.UpdatePolicyMax: "Max", + manifest.UpdatePolicyAppend: "Append", +} + +type Generator struct { + pkg *pbsubstreams.Package + manifest *manifest.Manifest + srcPath string + protoDefinitions []*desc.FileDescriptor + writer io.Writer + engine *Engine +} + +func NewGenerator(pkg *pbsubstreams.Package, manifest *manifest.Manifest, protoDefinitions []*desc.FileDescriptor, srcPath string) *Generator { + engine := &Engine{Manifest: manifest} + utils["getEngine"] = engine.GetEngine + + return &Generator{ + pkg: pkg, + manifest: manifest, + srcPath: srcPath, + protoDefinitions: protoDefinitions, + engine: engine, + } +} + +func (g *Generator) Generate() (err error) { + if _, err := os.Stat(g.srcPath); errors.Is(err, os.ErrNotExist) { + fmt.Printf("Creating missing %q folder\n", g.srcPath) + if err := os.MkdirAll(g.srcPath, os.ModePerm); err != nil { + return fmt.Errorf("creating src directory %v: %w", g.srcPath, err) + } + } + fmt.Printf("Generating files in %q\n", g.srcPath) + + generatedFolder := filepath.Join(g.srcPath, "generated") + if err := os.MkdirAll(generatedFolder, os.ModePerm); err != nil { + return fmt.Errorf("creating generated directory %v: %w", g.srcPath, err) + } + + pbFolder := filepath.Join(g.srcPath, "pb") + if err := os.MkdirAll(pbFolder, os.ModePerm); err != nil { + return fmt.Errorf("creating pb directory %v: %w", g.srcPath, err) + } + + protoGenerator := NewProtoGenerator(pbFolder, nil, false) + err = protoGenerator.GenerateProto(g.pkg, false) + if err != nil { + return fmt.Errorf("generating protobuf code: %w", err) + } + + err = generate("externs", tplExterns, g.engine, filepath.Join(generatedFolder, "externs.rs")) + if err != nil { + return fmt.Errorf("generating externs.rs: %w", err) + } + fmt.Println("Externs generated") + + err = generate("Substream", tplSubstreams, g.engine, filepath.Join(generatedFolder, "substreams.rs")) + if err != nil { + return fmt.Errorf("generating substreams.rs: %w", err) + } + + err = generate("mod", tplMod, g.engine, filepath.Join(generatedFolder, "mod.rs")) + if err != nil { + return fmt.Errorf("generating mod.rs: %w", err) + } + fmt.Println("Substreams Trait and base struct generated") + + pbModFilePath := filepath.Join(filepath.Join(pbFolder, "mod.rs")) + if _, err := os.Stat(pbModFilePath); errors.Is(err, os.ErrNotExist) { + err = generate("pb/mod", tplPbMod, protoPackages(g.protoDefinitions), pbModFilePath) + if err != nil { + return fmt.Errorf("generating pb/mod.rs: %w", err) + } + fmt.Println("Protobuf pb/mod.rs generated") + } + + libFilePath := filepath.Join(g.srcPath, "lib.rs") + if _, err := os.Stat(libFilePath); errors.Is(err, os.ErrNotExist) { + fmt.Printf("Generating src/lib.rs\n") + err = generate("lib", tplLibRs, g.engine, filepath.Join(g.srcPath, "lib.rs")) + if err != nil { + return fmt.Errorf("generating lib.rs: %w", err) + } + } else { + fmt.Printf("Skipping existing src/lib.rs\n") + } + + return nil +} + +func protoPackages(protoDefinitions []*desc.FileDescriptor) map[string]string { + protoPackages := map[string]string{} + for _, definition := range protoDefinitions { + p := definition.GetPackage() + options := definition.GetOptions().(*descriptorpb.FileOptions) + rustModule := proto.GetExtension(options, E_RUST_MODULE).(string) + if rustModule == "" { + rustModule = strings.ReplaceAll(p, ".", "_") + } + protoPackages[p] = rustModule + } + return protoPackages +} + +type GenerationOptions func(options *generateOptions) +type generateOptions struct { + w io.Writer +} + +func WithTestWriter(w io.Writer) GenerationOptions { + return func(options *generateOptions) { + options.w = w + } +} + +func generate(name, tpl string, data any, outputFile string, options ...GenerationOptions) (err error) { + var w io.Writer + + opts := &generateOptions{} + for _, option := range options { + option(opts) + } + + if opts.w != nil { + w = opts.w + } else { + w, err = os.Create(outputFile) + if err != nil { + return fmt.Errorf("creating file %s: %w", outputFile, err) + } + } + + tmpl, err := template.New(name).Funcs(utils).Parse(tpl) + if err != nil { + return fmt.Errorf("parsing %q template: %w", name, err) + } + + err = tmpl.Execute( + w, + data, + ) + if err != nil { + return fmt.Errorf("executing %q template: %w", name, err) + } + + return nil +} + +var utils = map[string]any{ + "contains": strings.Contains, + "hasPrefix": strings.HasPrefix, + "hasSuffix": strings.HasSuffix, +} + +type Engine struct { + Manifest *manifest.Manifest +} + +func (e *Engine) GetEngine() *Engine { + return e +} + +func (e *Engine) MustModule(moduleName string) *manifest.Module { + for _, module := range e.Manifest.Modules { + if module.Name == moduleName { + return module + } + } + panic(fmt.Sprintf("MustModule %q not found", moduleName)) +} + +func (e *Engine) moduleOutputForName(moduleName string) (string, error) { + //todo: call MustModule ... + for _, module := range e.Manifest.Modules { + if module.Name == moduleName { + return module.Output.Type, nil + } + } + return "", fmt.Errorf("MustModule %q not found", moduleName) +} + +func (e *Engine) FunctionSignature(module *manifest.Module) (*FunctionSignature, error) { + switch module.Kind { + case manifest.ModuleKindMap: + return e.mapFunctionSignature(module) + case manifest.ModuleKindStore: + return e.storeFunctionSignature(module) + default: + return nil, fmt.Errorf("unknown must module kind: %T", module.Kind) + } +} + +func (e *Engine) mapFunctionSignature(module *manifest.Module) (*FunctionSignature, error) { + inputs, err := e.ModuleArgument(module.Inputs) + if err != nil { + return nil, fmt.Errorf("generating must module intputs: %w", err) + } + + outType := module.Output.Type + if strings.HasPrefix(outType, "proto:") { + outType = mustTransformProtoType(outType, e.Manifest) + } + + fn := NewFunctionSignature(module.Name, "map", maybeTranslateType(outType), "", inputs) + + return fn, nil +} + +func (e *Engine) storeFunctionSignature(module *manifest.Module) (*FunctionSignature, error) { + arguments, err := e.ModuleArgument(module.Inputs) + if err != nil { + return nil, fmt.Errorf("generating MustModule intputs: %w", err) + } + + fn := NewFunctionSignature(module.Name, "store", "", module.UpdatePolicy, arguments) + + return fn, nil +} + +func (e *Engine) ModuleArgument(inputs []*manifest.Input) (Arguments, error) { + var out Arguments + for _, input := range inputs { + switch { + case input.IsMap(): + inputType, err := e.moduleOutputForName(input.Map) + if err != nil { + return nil, fmt.Errorf("getting map type: %w", err) + } + if strings.HasPrefix(inputType, "proto:") { + inputType = mustTransformProtoType(inputType, e.Manifest) + } + out = append(out, NewArgument(input.Map, inputType, input)) + case input.IsStore(): + inputType := e.MustModule(input.Store).ValueType + if strings.HasPrefix(inputType, "proto:") { + inputType = mustTransformProtoType(inputType, e.Manifest) + } + out = append(out, NewArgument(input.Store, inputType, input)) + case input.IsSource(): + inputType := mustTransformProtoType(input.Source, e.Manifest) + + parts := strings.Split(input.Source, ".") + name := parts[len(parts)-1] + name = strings.ToLower(name) + + out = append(out, NewArgument(name, inputType, input)) + case input.IsParams(): + inputType := strings.Trim(input.Params, " ") + out = append(out, NewArgument("params", inputType, input)) + default: + return nil, fmt.Errorf("unknown MustModule kind: %T", input) + } + } + return out, nil +} + +func (e *Engine) ReadableStoreType(store *manifest.Module, input *manifest.Input) string { + t := store.ValueType + p := store.UpdatePolicy + + //TODO(colin): split out deltas code into a separate function + if input.Mode == "deltas" { + if strings.HasPrefix(t, "proto") { + t = mustTransformProtoType(t, e.Manifest) + return fmt.Sprintf("substreams::store::Deltas>", t) + } + if p == manifest.UpdatePolicyAppend { + return fmt.Sprintf("substreams::store::Deltas>", maybeTranslateType(t)) + } + + t = maybeTranslateType(t) + return fmt.Sprintf("substreams::store::Deltas", t) + } + + if strings.HasPrefix(t, "proto") { + t = mustTransformProtoType(t, e.Manifest) + return fmt.Sprintf("substreams::store::StoreGetProto<%s>", t) + } + + if p == manifest.UpdatePolicyAppend { + return fmt.Sprintf("substreams::store::StoreGetRaw") + } + + t = maybeTranslateType(t) + return fmt.Sprintf("substreams::store::StoreGet%s", t) +} + +func (e *Engine) WritableStoreType(store *manifest.Module) string { + t := store.ValueType + p := store.UpdatePolicy + + if p == manifest.UpdatePolicyAppend { + return fmt.Sprintf("substreams::store::StoreAppend<%s>", maybeTranslateType(t)) + } + + p = UpdatePoliciesMap[p] + if strings.HasPrefix(t, "proto") { + t = mustTransformProtoType(t, e.Manifest) + return fmt.Sprintf("substreams::store::Store%sProto<%s>", p, t) + } + + return fmt.Sprintf("substreams::store::Store%s%s", p, maybeTranslateType(t)) +} + +func (e *Engine) WritableStoreDeclaration(store *manifest.Module) string { + t := store.ValueType + p := store.UpdatePolicy + + if p == manifest.UpdatePolicyAppend { + return fmt.Sprintf("let store: substreams::store::StoreAppend<%s> = substreams::store::StoreAppend::new();", maybeTranslateType(t)) + } + + p = UpdatePoliciesMap[p] + + if strings.HasPrefix(t, "proto") { + t = mustTransformProtoType(t, e.Manifest) + return fmt.Sprintf("let store: substreams::store::Store%sProto<%s> = substreams::store::StoreSetProto::new();", p, t) + } + t = maybeTranslateType(t) + return fmt.Sprintf("let store: substreams::store::Store%s%s = substreams::store::Store%s%s::new();", p, t, p, t) +} + +func (e *Engine) ReadableStoreDeclaration(name string, store *manifest.Module, input *manifest.Input) string { + t := store.ValueType + p := store.UpdatePolicy + isProto := strings.HasPrefix(t, "proto") + if isProto { + t = mustTransformProtoType(t, e.Manifest) + } + + if input.Mode == "deltas" { + + raw := fmt.Sprintf("let raw_%s_deltas = substreams::proto::decode_ptr::(%s_deltas_ptr, %s_deltas_len).unwrap().deltas;", name, name, name) + delta := fmt.Sprintf("let %s_deltas: substreams::store::Deltas = substreams::store::Deltas::new(raw_%s_deltas);", name, maybeTranslateType(t), name) + + if p == manifest.UpdatePolicyAppend { + delta = fmt.Sprintf("let %s_deltas: substreams::store::Deltas> = substreams::store::Deltas::new(raw_%s_deltas);", name, maybeTranslateType(t), name) + } + + if isProto { + delta = fmt.Sprintf("let %s_deltas: substreams::store::Deltas> = substreams::store::Deltas::new(raw_%s_deltas);", name, t, name) + } + return raw + "\n\t\t" + delta + } + + if isProto { + return fmt.Sprintf("let %s: substreams::store::StoreGetProto<%s> = substreams::store::StoreGetProto::new(%s_ptr);", name, t, name) + } + + t = maybeTranslateType(t) + if p == manifest.UpdatePolicyAppend { + return fmt.Sprintf("let %s: substreams::store::StoreGetRaw = substreams::store::StoreGetRaw::new(%s_ptr);", name, name) + } + + return fmt.Sprintf("let %s: substreams::store::StoreGet%s = substreams::store::StoreGet%s::new(%s_ptr);", name, t, t, name) + +} + +func mustTransformProtoType(t string, manif *manifest.Manifest) string { + t = strings.TrimPrefix(t, "proto:") + + parts := strings.Split(t, ".") + entityName := parts[len(parts)-1] + + if len(parts) == 1 { + return entityName + } + protoPackage := strings.Join(parts[:len(parts)-1], ".") + if resolved, ok := manif.Binaries["default"].ProtoPackageMapping[protoPackage]; ok { + protoPackage = resolved + "::" + parts[len(parts)-1] + return protoPackage + } + + panic(fmt.Errorf("missing binaries.default.protoPackageMapping value for %q", protoPackage)) +} + +type FunctionSignature struct { + Name string + Type string + OutputType string + StorePolicy string + Arguments Arguments +} + +func NewFunctionSignature(name string, t string, outType string, storePolicy string, arguments Arguments) *FunctionSignature { + return &FunctionSignature{ + Name: name, + Type: t, + OutputType: maybeTranslateType(outType), + StorePolicy: storePolicy, + Arguments: arguments, + } +} + +type Arguments []*Argument + +type Argument struct { + Name string + Type string + ModuleInput *manifest.Input +} + +func NewArgument(name string, argType string, moduleInput *manifest.Input) *Argument { + return &Argument{ + Name: name, + Type: maybeTranslateType(argType), + ModuleInput: moduleInput, + } +} diff --git a/firehose/substreams/codegen/generator_test.go b/firehose/substreams/codegen/generator_test.go new file mode 100644 index 0000000..a943a2d --- /dev/null +++ b/firehose/substreams/codegen/generator_test.go @@ -0,0 +1,199 @@ +package codegen + +import ( + "io" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +//func TestGenerator_ModRs(t *testing.T) { +// manifestPath := "./substreams.yaml" +// manifestReader := manifest.NewReader(manifestPath, manifest.SkipSourceCodeReader()) +// +// pkg, err := manifestReader.Read() +// require.NoError(t, err) +// +// expectedValue := "pub mod generated;\n" +// buf := new(bytes.Buffer) +// +// g := NewGenerator(pkg, buf) +// err = g.GenerateModRs() +// require.NoError(t, err) +// +// require.Equal(t, expectedValue, buf.String()) +//} + +//todo: add expected rs tests +// 1- mod.rs +// 2. + +func TestGenerator_Generate(t *testing.T) { + t.Skip() + g := InitTestGenerator(t) + err := g.Generate() + require.NoError(t, err) +} + +func TestGenerate_GenerateMod(t *testing.T) { + t.Skip() + + g := InitTestGenerator(t) + + r, w := io.Pipe() + var out []byte + var err error + + done := make(chan bool) + + go func() { + out, err = io.ReadAll(r) + err = r.Close() + require.NoError(t, err) + close(done) + }() + g.writer = w + + protoPackages := map[string]string{} + for _, definition := range g.protoDefinitions { + p := definition.GetPackage() + protoPackages[p] = strings.ReplaceAll(p, ".", "_") + } + + err = generate("", tplMod, protoPackages, "", WithTestWriter(w)) + + require.NoError(t, err) + err = w.Close() + require.NoError(t, err) + + <-done + + expectedMod, err := os.ReadFile(filepath.Join("./test_substreams/expected_test_outputs/generated/mod.rs")) + require.NoError(t, err) + require.Equal(t, string(expectedMod), string(out)) +} + +func TestGenerate_GeneratePbMod(t *testing.T) { + t.Skip() + + g := InitTestGenerator(t) + + r, w := io.Pipe() + var out []byte + var err error + + done := make(chan bool) + + go func() { + out, err = io.ReadAll(r) + err = r.Close() + require.NoError(t, err) + close(done) + }() + err = generate("", tplPbMod, protoPackages(g.protoDefinitions), "use std.out", WithTestWriter(w)) + require.NoError(t, err) + err = w.Close() + require.NoError(t, err) + + <-done + + expectedMod, err := os.ReadFile(filepath.Join("./test_substreams/expected_test_outputs/pb/mod.rs")) + require.NoError(t, err) + require.Equal(t, string(expectedMod), string(out)) +} + +func TestGenerate_GenerateExterns(t *testing.T) { + t.Skip() + + g := InitTestGenerator(t) + + r, w := io.Pipe() + var out []byte + var err error + + done := make(chan bool) + + go func() { + out, err = io.ReadAll(r) + err = r.Close() + require.NoError(t, err) + close(done) + }() + + err = generate("GenerateExterns", tplExterns, g.engine, "use std.out", WithTestWriter(w)) + + require.NoError(t, err) + err = w.Close() + require.NoError(t, err) + + <-done + + expectedMod, err := os.ReadFile(filepath.Join("./test_substreams/expected_test_outputs/generated/externs.rs")) + require.NoError(t, err) + require.Equal(t, string(expectedMod), string(out)) +} + +func TestGenerate_GenerateLib(t *testing.T) { + t.Skip() + + g := InitTestGenerator(t) + + r, w := io.Pipe() + var out []byte + var err error + + done := make(chan bool) + + go func() { + out, err = io.ReadAll(r) + err = r.Close() + require.NoError(t, err) + close(done) + }() + + err = generate("Lib", tplLibRs, g.engine, "use std.out", WithTestWriter(w)) + + require.NoError(t, err) + err = w.Close() + require.NoError(t, err) + + <-done + + expectedMod, err := os.ReadFile(filepath.Join("./test_substreams/expected_test_outputs/lib.rs")) + require.NoError(t, err) + require.Equal(t, string(expectedMod), string(out)) +} + +func TestGenerate_GenerateSubstreams(t *testing.T) { + t.Skip() + + g := InitTestGenerator(t) + + r, w := io.Pipe() + var out []byte + var err error + + done := make(chan bool) + + go func() { + out, err = io.ReadAll(r) + err = r.Close() + require.NoError(t, err) + close(done) + }() + + err = generate("Substreams", tplSubstreams, g.engine, "use std.out", WithTestWriter(w)) + + require.NoError(t, err) + err = w.Close() + require.NoError(t, err) + + <-done + + expectedMod, err := os.ReadFile(filepath.Join("./test_substreams/expected_test_outputs/generated/substreams.rs")) + require.NoError(t, err) + require.Equal(t, string(expectedMod), string(out)) +} diff --git a/firehose/substreams/codegen/init_test.go b/firehose/substreams/codegen/init_test.go new file mode 100644 index 0000000..50cf971 --- /dev/null +++ b/firehose/substreams/codegen/init_test.go @@ -0,0 +1,30 @@ +package codegen + +import ( + "fmt" + "testing" + + "github.com/jhump/protoreflect/desc" + "github.com/streamingfast/substreams/manifest" + "github.com/stretchr/testify/require" +) + +func InitTestGenerator(t *testing.T) *Generator { + t.Helper() + + var protoDefinitions []*desc.FileDescriptor + manifestPath := "./test_substreams/substreams.yaml" + manifestReader := manifest.MustNewReader(manifestPath, manifest.SkipSourceCodeReader(), manifest.WithCollectProtoDefinitions(func(pd []*desc.FileDescriptor) { + protoDefinitions = pd + })) + + pkg, _, err := manifestReader.Read() + if err != nil { + panic(fmt.Errorf("reading manifest file %s :%w", manifestPath, err)) + } + + manif, err := manifest.LoadManifestFile(manifestPath, ".") + require.NoError(t, err) + + return NewGenerator(pkg, manif, protoDefinitions, "") +} diff --git a/firehose/substreams/codegen/logging.go b/firehose/substreams/codegen/logging.go new file mode 100644 index 0000000..4b7d4a4 --- /dev/null +++ b/firehose/substreams/codegen/logging.go @@ -0,0 +1,7 @@ +package codegen + +import ( + "github.com/streamingfast/logging" +) + +var zlog, tracer = logging.PackageLogger("substreams", "github.com/streamingfast/substreams/codegen") diff --git a/firehose/substreams/codegen/proto_generator.go b/firehose/substreams/codegen/proto_generator.go new file mode 100644 index 0000000..f3152a8 --- /dev/null +++ b/firehose/substreams/codegen/proto_generator.go @@ -0,0 +1,105 @@ +package codegen + +import ( + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/lithammer/dedent" + pbsubstreams "github.com/streamingfast/substreams/pb/sf/substreams/v1" + "google.golang.org/protobuf/proto" +) + +type ProtoGenerator struct { + excludedPaths []string + outputPath string + generateMod bool +} + +func NewProtoGenerator(outputPath string, excludedPaths []string, generateMod bool) *ProtoGenerator { + return &ProtoGenerator{ + outputPath: outputPath, + excludedPaths: excludedPaths, + generateMod: generateMod, + } +} + +func (g *ProtoGenerator) GenerateProto(pkg *pbsubstreams.Package, showBufContent bool) error { + spkgTemporaryFilePath := filepath.Join(os.TempDir(), pkg.PackageMeta[0].Name+".tmp.spkg") + cnt, err := proto.Marshal(pkg) + if err != nil { + return fmt.Errorf("marshalling package: %w", err) + } + + if err := os.WriteFile(spkgTemporaryFilePath, cnt, 0644); err != nil { + fmt.Println("") + return fmt.Errorf("writing %q: %w", spkgTemporaryFilePath, err) + } + + _, err = os.Stat("buf.gen.yaml") + bufFileNotFound := errors.Is(err, os.ErrNotExist) + + if bufFileNotFound { + // Beware, the indentation after initial column is important, it's 2 spaces! + content := dedent.Dedent(` + version: v1 + plugins: + - plugin: buf.build/community/neoeinstein-prost:v0.2.2 + out: ` + g.outputPath + ` + opt: + - file_descriptor_set=false + `) + + if g.generateMod { + // Beware, the indentation after initial column is important, it's 2 spaces! + content += dedent.Dedent(` + - plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1 + out: ` + g.outputPath + ` + opt: + - no_features + `) + } + + if showBufContent { + fmt.Println("Writing to temporary 'buf.gen.yaml'") + fmt.Println("---") + fmt.Println(content) + fmt.Println("---") + } + + if err := os.WriteFile("buf.gen.yaml", []byte(content), 0644); err != nil { + return fmt.Errorf("error writing buf.gen.yaml: %w", err) + } + //defer func() { + // // Too bad if there is an error, nothing we can do + // _ = os.Remove("buf.gen.yaml") + //}() + } + + cmdArgs := []string{ + "generate", spkgTemporaryFilePath + "#format=bin", + } + for _, excludePath := range g.excludedPaths { + cmdArgs = append(cmdArgs, "--exclude-path", excludePath) + } + + fmt.Printf("Running: buf %s\n", strings.Join(cmdArgs, " ")) + c := exec.Command("buf", cmdArgs...) + c.Stdin = os.Stdin + c.Stdout = os.Stdout + c.Stderr = os.Stderr + if err := c.Run(); err != nil { + if strings.Contains(err.Error(), "not authenticated") { + return fmt.Errorf("error executing 'buf':: %w. Make sure that you don't have expired credentials in $HOME/.netrc (You do not need to be authenticated, but you cannot have wrong or expired credentials)", err) + } + if strings.Contains(err.Error(), "not found") { + return fmt.Errorf("error executing 'buf':: %w. Make sure that you have the 'buf' CLI installed: https://buf.build/product/cli", err) + } + return fmt.Errorf("error executing 'buf':: %w", err) + } + + return nil +} diff --git a/firehose/substreams/codegen/protocol.go b/firehose/substreams/codegen/protocol.go new file mode 100644 index 0000000..7fd5001 --- /dev/null +++ b/firehose/substreams/codegen/protocol.go @@ -0,0 +1,11 @@ +package codegen + +//go:generate go-enum -f=$GOFILE --marshal --names --nocase + +// ENUM( +// +// Ethereum +// Other +// +// ) +type Protocol uint diff --git a/firehose/substreams/codegen/protocol_enum.go b/firehose/substreams/codegen/protocol_enum.go new file mode 100644 index 0000000..4513f37 --- /dev/null +++ b/firehose/substreams/codegen/protocol_enum.go @@ -0,0 +1,90 @@ +// Code generated by go-enum DO NOT EDIT. +// Version: +// Revision: +// Build Date: +// Built By: + +package codegen + +import ( + "fmt" + "strings" +) + +const ( + // ProtocolEthereum is a Protocol of type Ethereum. + ProtocolEthereum Protocol = iota + // ProtocolOther is a Protocol of type Other. + ProtocolOther +) + +var ErrInvalidProtocol = fmt.Errorf("not a valid Protocol, try [%s]", strings.Join(_ProtocolNames, ", ")) + +const _ProtocolName = "EthereumOther" + +var _ProtocolNames = []string{ + _ProtocolName[0:8], + _ProtocolName[8:13], +} + +// ProtocolNames returns a list of possible string values of Protocol. +func ProtocolNames() []string { + tmp := make([]string, len(_ProtocolNames)) + copy(tmp, _ProtocolNames) + return tmp +} + +var _ProtocolMap = map[Protocol]string{ + ProtocolEthereum: _ProtocolName[0:8], + ProtocolOther: _ProtocolName[8:13], +} + +// String implements the Stringer interface. +func (x Protocol) String() string { + if str, ok := _ProtocolMap[x]; ok { + return str + } + return fmt.Sprintf("Protocol(%d)", x) +} + +// IsValid provides a quick way to determine if the typed value is +// part of the allowed enumerated values +func (x Protocol) IsValid() bool { + _, ok := _ProtocolMap[x] + return ok +} + +var _ProtocolValue = map[string]Protocol{ + _ProtocolName[0:8]: ProtocolEthereum, + strings.ToLower(_ProtocolName[0:8]): ProtocolEthereum, + _ProtocolName[8:13]: ProtocolOther, + strings.ToLower(_ProtocolName[8:13]): ProtocolOther, +} + +// ParseProtocol attempts to convert a string to a Protocol. +func ParseProtocol(name string) (Protocol, error) { + if x, ok := _ProtocolValue[name]; ok { + return x, nil + } + // Case insensitive parse, do a separate lookup to prevent unnecessary cost of lowercasing a string if we don't need to. + if x, ok := _ProtocolValue[strings.ToLower(name)]; ok { + return x, nil + } + return Protocol(0), fmt.Errorf("%s is %w", name, ErrInvalidProtocol) +} + +// MarshalText implements the text marshaller method. +func (x Protocol) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method. +func (x *Protocol) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParseProtocol(name) + if err != nil { + return err + } + *x = tmp + return nil +} diff --git a/firehose/substreams/codegen/templates/ethereum/.gitignore b/firehose/substreams/codegen/templates/ethereum/.gitignore new file mode 100644 index 0000000..be47f65 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/.gitignore @@ -0,0 +1,6 @@ +.vscode +.idea +.DS_Store +buf.gen.yaml + +*.spkg \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/Cargo.lock b/firehose/substreams/codegen/templates/ethereum/Cargo.lock new file mode 100644 index 0000000..e63c903 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/Cargo.lock @@ -0,0 +1,1108 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "home" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" + +[[package]] +name = "linux-raw-sys" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312270ee71e1cd70289dacf597cab7b207aa107d2f28191c2ae45b2ece18a260" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 1.0.109", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustix" +version = "0.38.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91d3c334ca1ee894a2c6f6ad698fe8c435b76d504b13d436f0685d648d6d96f7" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c5609f394e5c2bd7fc51efda478004ea80ef42fee983d5c67a65e34f32c0e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "substreams" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3524a4e2931ff6cd58783e62adbd7e44f461752eca0c423793cfb462351f24" +dependencies = [ + "anyhow", + "bigdecimal", + "hex", + "hex-literal", + "num-bigint", + "num-integer", + "num-traits", + "pad", + "prost", + "prost-build", + "prost-types", + "substreams-macro", + "thiserror", +] + +[[package]] +name = "substreams-database-change" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed32ca6fc7fa4b7a684d3abd5bb0545aadd2df82402e7336443cdbb6f8b350c3" +dependencies = [ + "prost", + "prost-types", + "substreams", +] + +[[package]] +name = "substreams-entity-change" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e07421917bd53518cb65b03e03671ecda0653995c71e5a2be815c3c755ea23c0" +dependencies = [ + "base64", + "prost", + "prost-types", + "substreams", +] + +[[package]] +name = "substreams-ethereum" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f45dc04be50b7ca08d6d5c4560ee3eeba16ccaa1c124d0361bb30b5b84e28b" +dependencies = [ + "getrandom", + "num-bigint", + "substreams", + "substreams-ethereum-abigen", + "substreams-ethereum-core", + "substreams-ethereum-derive", +] + +[[package]] +name = "substreams-ethereum-abigen" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c04307913a355aaf2a1bb7186d4bc7e36875f3d4aff77b47e83f1b63b24da55" +dependencies = [ + "anyhow", + "ethabi", + "heck", + "hex", + "prettyplease", + "proc-macro2", + "quote", + "substreams-ethereum-core", + "syn 1.0.109", +] + +[[package]] +name = "substreams-ethereum-core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9048cc9a66873ab7069ef958c2684994e6ee323da49c186b19156fdb4ca131" +dependencies = [ + "bigdecimal", + "ethabi", + "getrandom", + "num-bigint", + "prost", + "prost-build", + "prost-types", + "substreams", +] + +[[package]] +name = "substreams-ethereum-derive" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e862928bee8653f5c9291ac619c8dc0da14ca61d8cd8d89b3acdbbde4d0bf304" +dependencies = [ + "ethabi", + "heck", + "hex", + "num-bigint", + "proc-macro2", + "quote", + "substreams-ethereum-abigen", + "syn 1.0.109", +] + +[[package]] +name = "substreams-init-test" +version = "0.0.1" +dependencies = [ + "anyhow", + "ethabi", + "getrandom", + "hex-literal", + "num-bigint", + "num-traits", + "prost", + "prost-types", + "regex", + "substreams", + "substreams-database-change", + "substreams-entity-change", + "substreams-ethereum", +] + +[[package]] +name = "substreams-macro" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63c2b15adf5b4d7a6d1a73c73df951a6b2df6fbb4f0b41304dc28c5550ce0ed0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "176b6138793677221d420fd2f0aeeced263f197688b36484660da767bca2fa32" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/firehose/substreams/codegen/templates/ethereum/Cargo.toml b/firehose/substreams/codegen/templates/ethereum/Cargo.toml new file mode 100644 index 0000000..7ad90ff --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "substreams-init-test" +version = "0.0.1" +edition = "2021" + +[lib] +name = "substreams" +crate-type = ["cdylib"] + +[dependencies] +ethabi = "17" +hex-literal = "0.3.4" +num-bigint = "0.4" +num-traits = "0.2.15" +prost = "0.11" +prost-types = "0.11" +substreams = "0.5" +substreams-ethereum = "0.9" +substreams-database-change = "1" +substreams-entity-change = "1" + +# Required so that ethabi > ethereum-types build correctly under wasm32-unknown-unknown +[target.wasm32-unknown-unknown.dependencies] +getrandom = { version = "0.2", features = ["custom"] } + +[build-dependencies] +anyhow = "1" +substreams-ethereum = "0.9" +regex = "1.8" + +[profile.release] +lto = true +opt-level = 's' +strip = "debuginfo" diff --git a/firehose/substreams/codegen/templates/ethereum/Cargo.toml.gotmpl b/firehose/substreams/codegen/templates/ethereum/Cargo.toml.gotmpl new file mode 100644 index 0000000..b6e42ce --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/Cargo.toml.gotmpl @@ -0,0 +1,34 @@ +[package] +name = "{{ .name }}" +version = "0.0.1" +edition = "2021" + +[lib] +name = "substreams" +crate-type = ["cdylib"] + +[dependencies] +ethabi = "17" +hex-literal = "0.3.4" +num-bigint = "0.4" +num-traits = "0.2.15" +prost = "0.11" +prost-types = "0.11" +substreams = "0.5" +substreams-ethereum = "0.9" +substreams-database-change = "1" +substreams-entity-change = "1" + +# Required so that ethabi > ethereum-types build correctly under wasm32-unknown-unknown +[target.wasm32-unknown-unknown.dependencies] +getrandom = { version = "0.2", features = ["custom"] } + +[build-dependencies] +anyhow = "1" +substreams-ethereum = "0.9" +regex = "1.8" + +[profile.release] +lto = true +opt-level = 's' +strip = "debuginfo" diff --git a/firehose/substreams/codegen/templates/ethereum/Makefile b/firehose/substreams/codegen/templates/ethereum/Makefile new file mode 100644 index 0000000..168d701 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/Makefile @@ -0,0 +1,26 @@ +CARGO_VERSION := $(shell cargo version 2>/dev/null) + +.PHONY: build +build: +ifdef CARGO_VERSION + cargo build --target wasm32-unknown-unknown --release +else + @echo "Building substreams target using Docker. To speed up this step, install a Rust development environment." + docker run --rm -ti --init -v ${PWD}:/usr/src --workdir /usr/src/ rust:bullseye cargo build --target wasm32-unknown-unknown --release +endif + +.PHONY: run +run: build + substreams run substreams.yaml $(if $(MODULE),$(MODULE),map_events) $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: gui +gui: build + substreams gui substreams.yaml $(if $(MODULE),$(MODULE),map_events) $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: protogen +protogen: + substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" + +.PHONY: pack +pack: build + substreams pack substreams.yaml diff --git a/firehose/substreams/codegen/templates/ethereum/Makefile.gotmpl b/firehose/substreams/codegen/templates/ethereum/Makefile.gotmpl new file mode 100644 index 0000000..168d701 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/Makefile.gotmpl @@ -0,0 +1,26 @@ +CARGO_VERSION := $(shell cargo version 2>/dev/null) + +.PHONY: build +build: +ifdef CARGO_VERSION + cargo build --target wasm32-unknown-unknown --release +else + @echo "Building substreams target using Docker. To speed up this step, install a Rust development environment." + docker run --rm -ti --init -v ${PWD}:/usr/src --workdir /usr/src/ rust:bullseye cargo build --target wasm32-unknown-unknown --release +endif + +.PHONY: run +run: build + substreams run substreams.yaml $(if $(MODULE),$(MODULE),map_events) $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: gui +gui: build + substreams gui substreams.yaml $(if $(MODULE),$(MODULE),map_events) $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: protogen +protogen: + substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" + +.PHONY: pack +pack: build + substreams pack substreams.yaml diff --git a/firehose/substreams/codegen/templates/ethereum/abi/bayc_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/abi/bayc_contract.abi.json new file mode 100644 index 0000000..6913809 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/abi/bayc_contract.abi.json @@ -0,0 +1,670 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "uint256", + "name": "maxNftSupply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "saleStart", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "BAYC_PROVENANCE", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_APES", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REVEAL_TIMESTAMP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "apePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "emergencySetStartingIndexBlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "flipSaleState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxApePurchase", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "numberOfTokens", + "type": "uint256" + } + ], + "name": "mintApe", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reserveApes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "saleIsActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "baseURI", + "type": "string" + } + ], + "name": "setBaseURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "provenanceHash", + "type": "string" + } + ], + "name": "setProvenanceHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "revealTimeStamp", + "type": "uint256" + } + ], + "name": "setRevealTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "setStartingIndex", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startingIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingIndexBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/firehose/substreams/codegen/templates/ethereum/build.rs b/firehose/substreams/codegen/templates/ethereum/build.rs new file mode 100644 index 0000000..4ab59f5 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/build.rs @@ -0,0 +1,31 @@ +use anyhow::{Ok, Result}; +use regex::Regex; +use substreams_ethereum::Abigen; +use std::fs; + +fn main() -> Result<(), anyhow::Error> { + let file_names = [ + "abi/bayc_contract.abi.json", + ]; + let file_output_names = [ + "src/abi/bayc_contract.rs", + ]; + + let mut i = 0; + for f in file_names { + let contents = fs::read_to_string(f) + .expect("Should have been able to read the file"); + + // sanitize fields and attributes starting with an underscore + let regex = Regex::new(r#"("\w+"\s?:\s?")_(\w+")"#).unwrap(); + let sanitized_abi_file = regex.replace_all(contents.as_str(), "${1}u_${2}"); + + Abigen::from_bytes("Contract", sanitized_abi_file.as_bytes())? + .generate()? + .write_to_file(file_output_names[i])?; + + i = i+1; + } + + Ok(()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/build.rs.gotmpl b/firehose/substreams/codegen/templates/ethereum/build.rs.gotmpl new file mode 100644 index 0000000..4ef35c7 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/build.rs.gotmpl @@ -0,0 +1,41 @@ +use anyhow::{Ok, Result}; +use regex::Regex; +use substreams_ethereum::Abigen; +use std::fs; + +fn main() -> Result<(), anyhow::Error> { + let file_names = [ + {{- range $i, $contract := .ethereumContracts }} + "abi/{{ $contract.GetName }}_contract.abi.json", + {{- range $ddsContract := $contract.GetDDS }} + "abi/{{ $ddsContract.GetName }}_contract.abi.json", + {{- end }} + {{- end }} + ]; + let file_output_names = [ + {{- range $i, $contract := .ethereumContracts }} + "src/abi/{{ $contract.GetName }}_contract.rs", + {{- range $ddsContract := $contract.GetDDS }} + "src/abi/{{ $ddsContract.GetName }}_contract.rs", + {{- end }} + {{- end }} + ]; + + let mut i = 0; + for f in file_names { + let contents = fs::read_to_string(f) + .expect("Should have been able to read the file"); + + // sanitize fields and attributes starting with an underscore + let regex = Regex::new(r#"("\w+"\s?:\s?")_(\w+")"#).unwrap(); + let sanitized_abi_file = regex.replace_all(contents.as_str(), "${1}u_${2}"); + + Abigen::from_bytes("Contract", sanitized_abi_file.as_bytes())? + .generate()? + .write_to_file(file_output_names[i])?; + + i = i+1; + } + + Ok(()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/generate.go b/firehose/substreams/codegen/templates/ethereum/generate.go new file mode 100644 index 0000000..ecfa850 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/generate.go @@ -0,0 +1,52 @@ +package main + +import ( + "fmt" + "os" + + "github.com/streamingfast/cli" + "github.com/streamingfast/eth-go" + "github.com/streamingfast/substreams/codegen/templates" +) + +//go:generate go run . + +func main() { + abiContent, err := os.ReadFile("./abi/contract.abi.json") + cli.NoError(err, "Unable to read ABI file content") + + abi, err := eth.ParseABIFromBytes(abiContent) + cli.NoError(err, "Unable to parse ABI file content") + + chain := templates.EthereumChainsByID["Mainnet"] + + ethereumContracts := []*templates.EthereumContract{templates.NewEthereumContract( + "substreams-init-tests", + eth.MustNewAddress("0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"), + abi, + string(abiContent), + )} + + project, err := templates.NewEthereumProject( + "substreams-init-test", + "substreams_init_test", + chain, + ethereumContracts, + 123, + ) + + cli.NoError(err, "Unable to create Ethereum project") + + files, err := project.Render() + cli.NoError(err, "Unable to render Ethereum project") + + for _, fileToWrite := range []string{"proto/contract.proto", "src/lib.rs", "Cargo.toml", "substreams.yaml", "Makefile"} { + content, found := files[fileToWrite] + cli.Ensure(found, "The file %q is not rendered by Ethereum project", fileToWrite) + + err = os.WriteFile(fileToWrite, content, os.ModePerm) + cli.NoError(err, "Unable to write Ethereum rendered file %q: %w", fileToWrite, err) + + fmt.Printf("Ethereum project template file %q rendered\n", fileToWrite) + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/proto/contract.proto b/firehose/substreams/codegen/templates/ethereum/proto/contract.proto new file mode 100755 index 0000000..4a2c63b --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/proto/contract.proto @@ -0,0 +1,51 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package contract.v1; + +message Events { + repeated bayc_Approval bayc_approvals = 1; + repeated bayc_ApprovalForAll bayc_approval_for_alls = 2; + repeated bayc_OwnershipTransferred bayc_ownership_transferreds = 3; + repeated bayc_Transfer bayc_transfers = 4; +} + +message bayc_Approval { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes owner = 5; + bytes approved = 6; + string token_id = 7; +} + +message bayc_ApprovalForAll { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes owner = 5; + bytes operator = 6; + bool approved = 7; +} + +message bayc_OwnershipTransferred { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes previous_owner = 5; + bytes new_owner = 6; +} + +message bayc_Transfer { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes from = 5; + bytes to = 6; + string token_id = 7; +} diff --git a/firehose/substreams/codegen/templates/ethereum/proto/contract.proto.gotmpl b/firehose/substreams/codegen/templates/ethereum/proto/contract.proto.gotmpl new file mode 100644 index 0000000..654ec01 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/proto/contract.proto.gotmpl @@ -0,0 +1,101 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package contract.v1; +{{ $eventsCounter := 0 }} +message Events { + {{- range $i, $contract := .ethereumContracts }} + {{- range $index, $event := $contract.GetEvents }} + {{- $proto := $event.Proto }} + {{- $eventsCounter = add $eventsCounter 1 }} + repeated {{ $contract.GetName }}_{{$proto.MessageName}} {{ $contract.GetName }}_{{$proto.OutputModuleFieldName}} = {{ $eventsCounter }}; + {{- end}} + {{- range $ddsContract := $contract.GetDDS -}} + {{- range $index, $event := $ddsContract.GetEvents -}} + {{- $proto := $event.Proto }} + {{- $eventsCounter = add $eventsCounter 1 }} + repeated {{ $ddsContract.GetName }}_{{$proto.MessageName}} {{ $ddsContract.GetName }}_{{$proto.OutputModuleFieldName}} = {{ $eventsCounter }}; + {{- end}} + {{- end}} + {{- end}} +} +{{- if .withCalls }} +{{ $callsCounter := 0 }} +message Calls { + {{- range $i, $contract := .ethereumContracts }} + {{- range $index, $call := $contract.GetCalls }} + {{- $proto := $call.Proto }} + {{- $callsCounter = add $callsCounter 1 }} + repeated {{ $contract.GetName }}_{{$proto.MessageName}} {{ $contract.GetName }}_{{$proto.OutputModuleFieldName}} = {{ $callsCounter }}; + {{- end}} + {{- range $ddsContract := $contract.GetDDS -}} + {{- range $index, $event := $ddsContract.GetCalls -}} + {{- $proto := $event.Proto }} + {{- $callsCounter = add $callsCounter 1 }} + repeated {{ $ddsContract.GetName }}_{{$proto.MessageName}} {{ $ddsContract.GetName }}_{{$proto.OutputModuleFieldName}} = {{ $callsCounter }}; + {{- end}} + {{- end}} + {{- end}} +} +{{ end }} +{{- range $i, $contract := .ethereumContracts}} +{{- range $event := $contract.GetEvents }} +{{ $proto := $event.Proto }} +message {{ $contract.GetName }}_{{ $proto.MessageName }} { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + {{- range $index, $protoField := $proto.Fields }} + {{ $protoField.Type }} {{ sanitizeProtoFieldName $protoField.Name }} = {{ add $index 5 }}; + {{- end}} +} +{{- end}} +{{- range $call := $contract.GetCalls }} +{{ $proto := $call.Proto }} +message {{ $contract.GetName }}_{{ $proto.MessageName }} { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + {{- range $index, $protoField := $proto.Fields }} + {{ $protoField.Type }} {{ sanitizeProtoFieldName $protoField.Name }} = {{ add $index 6 }}; + {{- end}} +} +{{- end}} + +{{- range $i, $ddsContract := $contract.GetDDS }} + +{{- range $event := $ddsContract.GetEvents }} +{{ $proto := $event.Proto }} +message {{ $ddsContract.GetName }}_{{ $proto.MessageName }} { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + {{- range $index, $protoField := $proto.Fields }} + {{ $protoField.Type }} {{ sanitizeProtoFieldName $protoField.Name }} = {{ add $index 6 }}; + {{- end}} +} +{{- end}} + +{{- range $call := $ddsContract.GetCalls }} +{{ $proto := $call.Proto }} +message {{ $ddsContract.GetName }}_{{ $proto.MessageName }} { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + {{- range $index, $protoField := $proto.Fields }} + {{ $protoField.Type }} {{ sanitizeProtoFieldName $protoField.Name }} = {{ add $index 7 }}; + {{- end}} +} +{{- end}} + +{{- end}} +{{- end }} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/Makefile b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/Makefile new file mode 100644 index 0000000..5fca602 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/Makefile @@ -0,0 +1,26 @@ +CARGO_VERSION := $(shell cargo version 2>/dev/null) + +.PHONY: build +build: +ifdef CARGO_VERSION + cargo build --target wasm32-unknown-unknown --release +else + @echo "Building substreams target using Docker. To speed up this step, install a Rust development environment." + docker run --rm -ti --init -v ${PWD}:/usr/src --workdir /usr/src/ rust:bullseye cargo build --target wasm32-unknown-unknown --release +endif + +.PHONY: run +run: build + substreams run substreams.yaml map_events $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: gui +gui: build + substreams gui substreams.yaml map_events $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: protogen +protogen: + substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" + +.PHONY: pack +pack: build + substreams pack substreams.yaml diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/abi/factory_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/abi/factory_contract.abi.json new file mode 100644 index 0000000..e2354a6 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/abi/factory_contract.abi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint24","name":"fee","type":"uint24"},{"indexed":true,"internalType":"int24","name":"tickSpacing","type":"int24"}],"name":"FeeAmountEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":true,"internalType":"uint24","name":"fee","type":"uint24"},{"indexed":false,"internalType":"int24","name":"tickSpacing","type":"int24"},{"indexed":false,"internalType":"address","name":"pool","type":"address"}],"name":"PoolCreated","type":"event"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"}],"name":"createPool","outputs":[{"internalType":"address","name":"pool","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"}],"name":"enableFeeAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"","type":"uint24"}],"name":"feeAmountTickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint24","name":"","type":"uint24"}],"name":"getPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"parameters","outputs":[{"internalType":"address","name":"factory","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/abi/pool_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/abi/pool_contract.abi.json new file mode 100644 index 0000000..bd99dfb --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/abi/pool_contract.abi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"Collect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"CollectProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid1","type":"uint256"}],"name":"Flash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextOld","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextNew","type":"uint16"}],"name":"IncreaseObservationCardinalityNext","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Initialize","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"feeProtocol0Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol0New","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1New","type":"uint8"}],"name":"SetFeeProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"int256","name":"amount0","type":"int256"},{"indexed":false,"internalType":"int256","name":"amount1","type":"int256"},{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Swap","type":"event"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collect","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collectProtocol","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal0X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal1X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"flash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"}],"name":"increaseObservationCardinalityNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liquidity","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLiquidityPerTick","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"observations","outputs":[{"internalType":"uint32","name":"blockTimestamp","type":"uint32"},{"internalType":"int56","name":"tickCumulative","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityCumulativeX128","type":"uint160"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32[]","name":"secondsAgos","type":"uint32[]"}],"name":"observe","outputs":[{"internalType":"int56[]","name":"tickCumulatives","type":"int56[]"},{"internalType":"uint160[]","name":"secondsPerLiquidityCumulativeX128s","type":"uint160[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"positions","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"},{"internalType":"uint128","name":"tokensOwed0","type":"uint128"},{"internalType":"uint128","name":"tokensOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFees","outputs":[{"internalType":"uint128","name":"token0","type":"uint128"},{"internalType":"uint128","name":"token1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"feeProtocol0","type":"uint8"},{"internalType":"uint8","name":"feeProtocol1","type":"uint8"}],"name":"setFeeProtocol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tick","type":"int24"},{"internalType":"uint16","name":"observationIndex","type":"uint16"},{"internalType":"uint16","name":"observationCardinality","type":"uint16"},{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"},{"internalType":"uint8","name":"feeProtocol","type":"uint8"},{"internalType":"bool","name":"unlocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"}],"name":"snapshotCumulativesInside","outputs":[{"internalType":"int56","name":"tickCumulativeInside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityInsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsInside","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"zeroForOne","type":"bool"},{"internalType":"int256","name":"amountSpecified","type":"int256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[{"internalType":"int256","name":"amount0","type":"int256"},{"internalType":"int256","name":"amount1","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int16","name":"","type":"int16"}],"name":"tickBitmap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"","type":"int24"}],"name":"ticks","outputs":[{"internalType":"uint128","name":"liquidityGross","type":"uint128"},{"internalType":"int128","name":"liquidityNet","type":"int128"},{"internalType":"uint256","name":"feeGrowthOutside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthOutside1X128","type":"uint256"},{"internalType":"int56","name":"tickCumulativeOutside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityOutsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsOutside","type":"uint32"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/build.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/build.rs new file mode 100644 index 0000000..140844b --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/build.rs @@ -0,0 +1,33 @@ +use anyhow::{Ok, Result}; +use regex::Regex; +use substreams_ethereum::Abigen; +use std::fs; + +fn main() -> Result<(), anyhow::Error> { + let file_names = [ + "abi/factory_contract.abi.json", + "abi/pool_contract.abi.json", + ]; + let file_output_names = [ + "src/abi/factory_contract.rs", + "src/abi/pool_contract.rs", + ]; + + let mut i = 0; + for f in file_names { + let contents = fs::read_to_string(f) + .expect("Should have been able to read the file"); + + // sanitize fields and attributes starting with an underscore + let regex = Regex::new(r#"("\w+"\s?:\s?")_(\w+")"#).unwrap(); + let sanitized_abi_file = regex.replace_all(contents.as_str(), "${1}u_${2}"); + + Abigen::from_bytes("Contract", sanitized_abi_file.as_bytes())? + .generate()? + .write_to_file(file_output_names[i])?; + + i = i+1; + } + + Ok(()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/proto/contract.proto b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/proto/contract.proto new file mode 100644 index 0000000..1afbba3 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/proto/contract.proto @@ -0,0 +1,166 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package contract.v1; + +message Events { + repeated factory_FeeAmountEnabled factory_fee_amount_enableds = 1; + repeated factory_OwnerChanged factory_owner_changeds = 2; + repeated factory_PoolCreated factory_pool_createds = 3; + repeated pool_Burn pool_burns = 4; + repeated pool_Collect pool_collects = 5; + repeated pool_CollectProtocol pool_collect_protocols = 6; + repeated pool_Flash pool_flashes = 7; + repeated pool_IncreaseObservationCardinalityNext pool_increase_observation_cardinality_nexts = 8; + repeated pool_Initialize pool_initializes = 9; + repeated pool_Mint pool_mints = 10; + repeated pool_SetFeeProtocol pool_set_fee_protocols = 11; + repeated pool_Swap pool_swaps = 12; +} + +message factory_FeeAmountEnabled { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + uint64 fee = 5; + int64 tick_spacing = 6; +} + +message factory_OwnerChanged { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes old_owner = 5; + bytes new_owner = 6; +} + +message factory_PoolCreated { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes token0 = 5; + bytes token1 = 6; + uint64 fee = 7; + int64 tick_spacing = 8; + bytes pool = 9; +} + +message pool_Burn { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes owner = 6; + int64 tick_lower = 7; + int64 tick_upper = 8; + string amount = 9; + string amount0 = 10; + string amount1 = 11; +} + +message pool_Collect { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes owner = 6; + bytes recipient = 7; + int64 tick_lower = 8; + int64 tick_upper = 9; + string amount0 = 10; + string amount1 = 11; +} + +message pool_CollectProtocol { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; +} + +message pool_Flash { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; + string paid0 = 10; + string paid1 = 11; +} + +message pool_IncreaseObservationCardinalityNext { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + uint64 observation_cardinality_next_old = 6; + uint64 observation_cardinality_next_new = 7; +} + +message pool_Initialize { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + string sqrt_price_x96 = 6; + int64 tick = 7; +} + +message pool_Mint { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes owner = 7; + int64 tick_lower = 8; + int64 tick_upper = 9; + string amount = 10; + string amount0 = 11; + string amount1 = 12; +} + +message pool_SetFeeProtocol { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + uint64 fee_protocol0_old = 6; + uint64 fee_protocol1_old = 7; + uint64 fee_protocol0_new = 8; + uint64 fee_protocol1_new = 9; +} + +message pool_Swap { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; + string sqrt_price_x96 = 10; + string liquidity = 11; + int64 tick = 12; +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/rust-toolchain.toml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/rust-toolchain.toml new file mode 100644 index 0000000..ec334c0 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.65" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.clickhouse.sql b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.clickhouse.sql new file mode 100644 index 0000000..da70e4b --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.clickhouse.sql @@ -0,0 +1,135 @@ +CREATE TABLE IF NOT EXISTS factory_fee_amount_enabled ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "fee" UInt32, + "tick_spacing" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS factory_owner_changed ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "new_owner" VARCHAR(40), + "old_owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS factory_pool_created ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "fee" UInt32, + "pool" VARCHAR(40), + "tick_spacing" Int32, + "token0" VARCHAR(40), + "token1" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); + +CREATE TABLE IF NOT EXISTS pool_burn ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount" UInt128, + "amount0" UInt256, + "amount1" UInt256, + "owner" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_collect ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" UInt128, + "amount1" UInt128, + "owner" VARCHAR(40), + "recipient" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_collect_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" UInt128, + "amount1" UInt128, + "recipient" VARCHAR(40), + "sender" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_flash ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" UInt256, + "amount1" UInt256, + "paid0" UInt256, + "paid1" UInt256, + "recipient" VARCHAR(40), + "sender" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_increase_observation_cardinality_next ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "observation_cardinality_next_new" UInt16, + "observation_cardinality_next_old" UInt16 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_initialize ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "sqrt_price_x96" UInt256, + "tick" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_mint ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount" UInt128, + "amount0" UInt256, + "amount1" UInt256, + "owner" VARCHAR(40), + "sender" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_set_fee_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "fee_protocol0_new" UInt8, + "fee_protocol0_old" UInt8, + "fee_protocol1_new" UInt8, + "fee_protocol1_old" UInt8 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_swap ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" Int256, + "amount1" Int256, + "liquidity" UInt128, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + "sqrt_price_x96" UInt256, + "tick" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.graphql b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.graphql new file mode 100644 index 0000000..0e8cbd2 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.graphql @@ -0,0 +1,147 @@ +type factory_fee_amount_enabled @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + fee: Int! + tick_spacing: Int! +} +type factory_owner_changed @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + new_owner: String! + old_owner: String! +} +type factory_pool_created @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + fee: Int! + pool: String! + tick_spacing: Int! + token0: String! + token1: String! +} + +type pool_burn @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount: BigDecimal! + amount0: BigDecimal! + amount1: BigDecimal! + owner: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_collect @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + owner: String! + recipient: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_collect_protocol @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + recipient: String! + sender: String! +} +type pool_flash @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + paid0: BigDecimal! + paid1: BigDecimal! + recipient: String! + sender: String! +} +type pool_increase_observation_cardinality_next @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + observation_cardinality_next_new: Int! + observation_cardinality_next_old: Int! +} +type pool_initialize @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + sqrt_price_x96: BigDecimal! + tick: Int! +} +type pool_mint @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount: BigDecimal! + amount0: BigDecimal! + amount1: BigDecimal! + owner: String! + sender: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_set_fee_protocol @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + fee_protocol0_new: Int! + fee_protocol0_old: Int! + fee_protocol1_new: Int! + fee_protocol1_old: Int! +} +type pool_swap @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + liquidity: BigDecimal! + recipient: String! + sender: String! + sqrt_price_x96: BigDecimal! + tick: Int! +} \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.sql b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.sql new file mode 100644 index 0000000..a9fecfc --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/schema.sql @@ -0,0 +1,147 @@ +CREATE TABLE IF NOT EXISTS factory_fee_amount_enabled ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "fee" INT, + "tick_spacing" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS factory_owner_changed ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "new_owner" VARCHAR(40), + "old_owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS factory_pool_created ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "fee" INT, + "pool" VARCHAR(40), + "tick_spacing" INT, + "token0" VARCHAR(40), + "token1" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); + +CREATE TABLE IF NOT EXISTS pool_burn ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount" DECIMAL, + "amount0" DECIMAL, + "amount1" DECIMAL, + "owner" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_collect ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "owner" VARCHAR(40), + "recipient" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_collect_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_flash ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "paid0" DECIMAL, + "paid1" DECIMAL, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_increase_observation_cardinality_next ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "observation_cardinality_next_new" INT, + "observation_cardinality_next_old" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_initialize ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "sqrt_price_x96" DECIMAL, + "tick" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_mint ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount" DECIMAL, + "amount0" DECIMAL, + "amount1" DECIMAL, + "owner" VARCHAR(40), + "sender" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_set_fee_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "fee_protocol0_new" INT, + "fee_protocol0_old" INT, + "fee_protocol1_new" INT, + "fee_protocol1_old" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_swap ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "liquidity" DECIMAL, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + "sqrt_price_x96" DECIMAL, + "tick" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/factory_contract.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/factory_contract.rs new file mode 100644 index 0000000..7d59bf6 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/factory_contract.rs @@ -0,0 +1,1021 @@ + +const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; +/// Contract's functions. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct CreatePool { + pub token_a: Vec, + pub token_b: Vec, + pub fee: substreams::scalar::BigInt, + } + impl CreatePool { + const METHOD_ID: [u8; 4] = [161u8, 103u8, 18u8, 149u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_a: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_b: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.token_a)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.token_b)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for CreatePool { + const NAME: &'static str = "createPool"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for CreatePool { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct EnableFeeAmount { + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + } + impl EnableFeeAmount { + const METHOD_ID: [u8; 4] = [138u8, 124u8, 25u8, 95u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + { + let non_full_signed_bytes = self.tick_spacing.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for EnableFeeAmount { + const NAME: &'static str = "enableFeeAmount"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FeeAmountTickSpacing { + pub param0: substreams::scalar::BigInt, + } + impl FeeAmountTickSpacing { + const METHOD_ID: [u8; 4] = [34u8, 175u8, 204u8, 203u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(24usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Int(24usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for FeeAmountTickSpacing { + const NAME: &'static str = "feeAmountTickSpacing"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for FeeAmountTickSpacing { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetPool { + pub param0: Vec, + pub param1: Vec, + pub param2: substreams::scalar::BigInt, + } + impl GetPool { + const METHOD_ID: [u8; 4] = [22u8, 152u8, 238u8, 130u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param2: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param2.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetPool { + const NAME: &'static str = "getPool"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for GetPool { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Owner {} + impl Owner { + const METHOD_ID: [u8; 4] = [141u8, 165u8, 203u8, 91u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Owner { + const NAME: &'static str = "owner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Owner { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Parameters {} + impl Parameters { + const METHOD_ID: [u8; 4] = [137u8, 3u8, 87u8, 48u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Parameters { + const NAME: &'static str = "parameters"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Parameters + { + fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetOwner { + pub u_owner: Vec, + } + impl SetOwner { + const METHOD_ID: [u8; 4] = [19u8, 175u8, 64u8, 53u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode(&[ethabi::ParamType::Address], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Address(ethabi::Address::from_slice( + &self.u_owner, + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetOwner { + const NAME: &'static str = "setOwner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } +} +/// Contract's events. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct FeeAmountEnabled { + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + } + impl FeeAmountEnabled { + const TOPIC_ID: [u8; 32] = [ + 198u8, 106u8, 63u8, 223u8, 7u8, 35u8, 44u8, 221u8, 24u8, 95u8, 235u8, 204u8, 101u8, + 121u8, 212u8, 8u8, 194u8, 65u8, 180u8, 122u8, 226u8, 249u8, 144u8, 125u8, 132u8, 190u8, + 101u8, 81u8, 65u8, 238u8, 174u8, 204u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Ok(Self { + fee: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(24usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'fee' from topic of type 'uint24': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + }) + } + } + impl substreams_ethereum::Event for FeeAmountEnabled { + const NAME: &'static str = "FeeAmountEnabled"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnerChanged { + pub old_owner: Vec, + pub new_owner: Vec, + } + impl OwnerChanged { + const TOPIC_ID: [u8; 32] = [ + 181u8, 50u8, 7u8, 59u8, 56u8, 200u8, 49u8, 69u8, 227u8, 229u8, 19u8, 83u8, 119u8, + 160u8, 139u8, 249u8, 170u8, 181u8, 91u8, 192u8, 253u8, 124u8, 17u8, 121u8, 205u8, 79u8, + 185u8, 149u8, 210u8, 165u8, 21u8, 156u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Ok(Self { + old_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'old_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + new_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for OwnerChanged { + const NAME: &'static str = "OwnerChanged"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct PoolCreated { + pub token0: Vec, + pub token1: Vec, + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + pub pool: Vec, + } + impl PoolCreated { + const TOPIC_ID: [u8; 32] = [ + 120u8, 60u8, 202u8, 28u8, 4u8, 18u8, 221u8, 13u8, 105u8, 94u8, 120u8, 69u8, 104u8, + 201u8, 109u8, 162u8, 233u8, 194u8, 47u8, 249u8, 137u8, 53u8, 122u8, 46u8, 139u8, 29u8, + 155u8, 43u8, 78u8, 107u8, 113u8, 24u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(24usize), ethabi::ParamType::Address], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + token0: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'token0' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token1: ethabi::decode(&[ethabi::ParamType::Address], log.topics[2usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'token1' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + fee: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(24usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'fee' from topic of type 'uint24': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + pool: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for PoolCreated { + const NAME: &'static str = "PoolCreated"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/mod.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/mod.rs new file mode 100644 index 0000000..2932aa6 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/mod.rs @@ -0,0 +1,3 @@ + +pub mod factory_contract; +pub mod pool_contract; \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/pool_contract.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/pool_contract.rs new file mode 100644 index 0000000..a4f6b47 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/abi/pool_contract.rs @@ -0,0 +1,4433 @@ + +const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; +/// Contract's functions. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Burn { + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Burn { + const METHOD_ID: [u8; 4] = [163u8, 65u8, 35u8, 167u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Burn { + const NAME: &'static str = "burn"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Burn + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Collect { + pub recipient: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount0_requested: substreams::scalar::BigInt, + pub amount1_requested: substreams::scalar::BigInt, + } + impl Collect { + const METHOD_ID: [u8; 4] = [79u8, 30u8, 179u8, 216u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount0_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount0_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount1_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Collect { + const NAME: &'static str = "collect"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Collect + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct CollectProtocol { + pub recipient: Vec, + pub amount0_requested: substreams::scalar::BigInt, + pub amount1_requested: substreams::scalar::BigInt, + } + impl CollectProtocol { + const METHOD_ID: [u8; 4] = [133u8, 182u8, 103u8, 41u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount0_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount1_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for CollectProtocol { + const NAME: &'static str = "collectProtocol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for CollectProtocol + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Factory {} + impl Factory { + const METHOD_ID: [u8; 4] = [196u8, 90u8, 1u8, 85u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Factory { + const NAME: &'static str = "factory"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Factory { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Fee {} + impl Fee { + const METHOD_ID: [u8; 4] = [221u8, 202u8, 63u8, 67u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(24usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Fee { + const NAME: &'static str = "fee"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Fee { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FeeGrowthGlobal0X128 {} + impl FeeGrowthGlobal0X128 { + const METHOD_ID: [u8; 4] = [243u8, 5u8, 131u8, 153u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(256usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for FeeGrowthGlobal0X128 { + const NAME: &'static str = "feeGrowthGlobal0X128"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for FeeGrowthGlobal0X128 { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FeeGrowthGlobal1X128 {} + impl FeeGrowthGlobal1X128 { + const METHOD_ID: [u8; 4] = [70u8, 20u8, 19u8, 25u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(256usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for FeeGrowthGlobal1X128 { + const NAME: &'static str = "feeGrowthGlobal1X128"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for FeeGrowthGlobal1X128 { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Flash { + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub data: Vec, + } + impl Flash { + const METHOD_ID: [u8; 4] = [73u8, 14u8, 108u8, 188u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount1.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Bytes(self.data.clone()), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Flash { + const NAME: &'static str = "flash"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IncreaseObservationCardinalityNext { + pub observation_cardinality_next: substreams::scalar::BigInt, + } + impl IncreaseObservationCardinalityNext { + const METHOD_ID: [u8; 4] = [50u8, 20u8, 143u8, 103u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(16usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + observation_cardinality_next: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.observation_cardinality_next.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for IncreaseObservationCardinalityNext { + const NAME: &'static str = "increaseObservationCardinalityNext"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Initialize { + pub sqrt_price_x96: substreams::scalar::BigInt, + } + impl Initialize { + const METHOD_ID: [u8; 4] = [246u8, 55u8, 115u8, 29u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(160usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.sqrt_price_x96.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Initialize { + const NAME: &'static str = "initialize"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Liquidity {} + impl Liquidity { + const METHOD_ID: [u8; 4] = [26u8, 104u8, 101u8, 2u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(128usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Liquidity { + const NAME: &'static str = "liquidity"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Liquidity { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MaxLiquidityPerTick {} + impl MaxLiquidityPerTick { + const METHOD_ID: [u8; 4] = [112u8, 207u8, 117u8, 74u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(128usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for MaxLiquidityPerTick { + const NAME: &'static str = "maxLiquidityPerTick"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for MaxLiquidityPerTick { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Mint { + pub recipient: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + pub data: Vec, + } + impl Mint { + const METHOD_ID: [u8; 4] = [60u8, 138u8, 125u8, 141u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Bytes(self.data.clone()), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Mint { + const NAME: &'static str = "mint"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Mint + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Observations { + pub param0: substreams::scalar::BigInt, + } + impl Observations { + const METHOD_ID: [u8; 4] = [37u8, 44u8, 9u8, 215u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(256usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(32usize), + ethabi::ParamType::Int(56usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Observations { + const NAME: &'static str = "observations"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> for Observations + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Observe { + pub seconds_agos: Vec, + } + impl Observe { + const METHOD_ID: [u8; 4] = [136u8, 59u8, 219u8, 253u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint( + 32usize, + )))], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + seconds_agos: values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + .collect(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[{ + let v = self + .seconds_agos + .iter() + .map(|inner| { + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match inner.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )) + }) + .collect(); + ethabi::Token::Array(v) + }]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array(Box::new(ethabi::ParamType::Int(56usize))), + ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint(160usize))), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + .collect(), + values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + .collect(), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + Vec, + Vec, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Observe { + const NAME: &'static str = "observe"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + Vec, + Vec, + )> for Observe + { + fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Positions { + pub param0: [u8; 32usize], + } + impl Positions { + const METHOD_ID: [u8; 4] = [81u8, 78u8, 164u8, 191u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::FixedBytes(self.param0.as_ref().to_vec())]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Positions { + const NAME: &'static str = "positions"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Positions + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProtocolFees {} + impl ProtocolFees { + const METHOD_ID: [u8; 4] = [26u8, 216u8, 176u8, 59u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProtocolFees { + const NAME: &'static str = "protocolFees"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for ProtocolFees + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetFeeProtocol { + pub fee_protocol0: substreams::scalar::BigInt, + pub fee_protocol1: substreams::scalar::BigInt, + } + impl SetFeeProtocol { + const METHOD_ID: [u8; 4] = [130u8, 6u8, 164u8, 209u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + fee_protocol0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee_protocol0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee_protocol1.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetFeeProtocol { + const NAME: &'static str = "setFeeProtocol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Slot0 {} + impl Slot0 { + const METHOD_ID: [u8; 4] = [56u8, 80u8, 199u8, 189u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Slot0 { + const NAME: &'static str = "slot0"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> for Slot0 + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SnapshotCumulativesInside { + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + } + impl SnapshotCumulativesInside { + const METHOD_ID: [u8; 4] = [163u8, 136u8, 7u8, 242u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(56usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(32usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SnapshotCumulativesInside { + const NAME: &'static str = "snapshotCumulativesInside"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for SnapshotCumulativesInside + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub recipient: Vec, + pub zero_for_one: bool, + pub amount_specified: substreams::scalar::BigInt, + pub sqrt_price_limit_x96: substreams::scalar::BigInt, + pub data: Vec, + } + impl Swap { + const METHOD_ID: [u8; 4] = [18u8, 138u8, 203u8, 8u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Bool, + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + zero_for_one: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + amount_specified: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + sqrt_price_limit_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Bool(self.zero_for_one.clone()), + { + let non_full_signed_bytes = self.amount_specified.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.sqrt_price_limit_x96.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Bytes(self.data.clone()), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Int(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Swap { + const NAME: &'static str = "swap"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Swap + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TickBitmap { + pub param0: substreams::scalar::BigInt, + } + impl TickBitmap { + const METHOD_ID: [u8; 4] = [83u8, 57u8, 194u8, 150u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Int(16usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[{ + let non_full_signed_bytes = self.param0.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(256usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TickBitmap { + const NAME: &'static str = "tickBitmap"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TickBitmap { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TickSpacing {} + impl TickSpacing { + const METHOD_ID: [u8; 4] = [208u8, 201u8, 58u8, 124u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Int(24usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TickSpacing { + const NAME: &'static str = "tickSpacing"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TickSpacing { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Ticks { + pub param0: substreams::scalar::BigInt, + } + impl Ticks { + const METHOD_ID: [u8; 4] = [243u8, 13u8, 186u8, 147u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Int(24usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[{ + let non_full_signed_bytes = self.param0.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Int(56usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(32usize), + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Ticks { + const NAME: &'static str = "ticks"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> for Ticks + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Token0 {} + impl Token0 { + const METHOD_ID: [u8; 4] = [13u8, 254u8, 22u8, 129u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Token0 { + const NAME: &'static str = "token0"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Token0 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Token1 {} + impl Token1 { + const METHOD_ID: [u8; 4] = [210u8, 18u8, 32u8, 167u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Token1 { + const NAME: &'static str = "token1"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Token1 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } +} +/// Contract's events. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Burn { + pub owner: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Burn { + const TOPIC_ID: [u8; 32] = [ + 12u8, 57u8, 108u8, 217u8, 137u8, 163u8, 159u8, 68u8, 89u8, 181u8, 250u8, 26u8, 237u8, + 106u8, 154u8, 141u8, 205u8, 188u8, 69u8, 144u8, 138u8, 207u8, 214u8, 126u8, 2u8, 140u8, + 213u8, 104u8, 218u8, 152u8, 152u8, 44u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 96usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + tick_upper: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[3usize].as_ref(), + ), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Burn { + const NAME: &'static str = "Burn"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Collect { + pub owner: Vec, + pub recipient: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Collect { + const TOPIC_ID: [u8; 32] = [ + 112u8, 147u8, 83u8, 56u8, 230u8, 151u8, 117u8, 69u8, 106u8, 133u8, 221u8, 239u8, 34u8, + 108u8, 57u8, 95u8, 182u8, 104u8, 182u8, 63u8, 160u8, 17u8, 95u8, 95u8, 32u8, 97u8, + 11u8, 56u8, 142u8, 108u8, 169u8, 192u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 96usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + tick_upper: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[3usize].as_ref(), + ), + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Collect { + const NAME: &'static str = "Collect"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct CollectProtocol { + pub sender: Vec, + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl CollectProtocol { + const TOPIC_ID: [u8; 32] = [ + 89u8, 107u8, 87u8, 57u8, 6u8, 33u8, 141u8, 52u8, 17u8, 133u8, 11u8, 38u8, 166u8, 180u8, + 55u8, 214u8, 196u8, 82u8, 47u8, 219u8, 67u8, 210u8, 210u8, 56u8, 98u8, 99u8, 248u8, + 109u8, 80u8, 184u8, 177u8, 81u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'recipient' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for CollectProtocol { + const NAME: &'static str = "CollectProtocol"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Flash { + pub sender: Vec, + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub paid0: substreams::scalar::BigInt, + pub paid1: substreams::scalar::BigInt, + } + impl Flash { + const TOPIC_ID: [u8; 32] = [ + 189u8, 189u8, 183u8, 29u8, 120u8, 96u8, 55u8, 107u8, 165u8, 43u8, 37u8, 165u8, 2u8, + 139u8, 238u8, 162u8, 53u8, 129u8, 54u8, 74u8, 64u8, 82u8, 47u8, 107u8, 207u8, 184u8, + 107u8, 177u8, 242u8, 220u8, 166u8, 51u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'recipient' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + paid0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + paid1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Flash { + const NAME: &'static str = "Flash"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IncreaseObservationCardinalityNext { + pub observation_cardinality_next_old: substreams::scalar::BigInt, + pub observation_cardinality_next_new: substreams::scalar::BigInt, + } + impl IncreaseObservationCardinalityNext { + const TOPIC_ID: [u8; 32] = [ + 172u8, 73u8, 229u8, 24u8, 249u8, 10u8, 53u8, 143u8, 101u8, 46u8, 68u8, 0u8, 22u8, 79u8, + 5u8, 165u8, 216u8, 247u8, 227u8, 94u8, 119u8, 71u8, 39u8, 155u8, 195u8, 169u8, 61u8, + 191u8, 88u8, 78u8, 18u8, 90u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(16usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + observation_cardinality_next_old: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + observation_cardinality_next_new: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for IncreaseObservationCardinalityNext { + const NAME: &'static str = "IncreaseObservationCardinalityNext"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Initialize { + pub sqrt_price_x96: substreams::scalar::BigInt, + pub tick: substreams::scalar::BigInt, + } + impl Initialize { + const TOPIC_ID: [u8; 32] = [ + 152u8, 99u8, 96u8, 54u8, 203u8, 102u8, 169u8, 193u8, 154u8, 55u8, 67u8, 94u8, 252u8, + 30u8, 144u8, 20u8, 33u8, 144u8, 33u8, 78u8, 138u8, 190u8, 184u8, 33u8, 189u8, 186u8, + 63u8, 41u8, 144u8, 221u8, 76u8, 149u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Int(24usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Initialize { + const NAME: &'static str = "Initialize"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Mint { + pub sender: Vec, + pub owner: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Mint { + const TOPIC_ID: [u8; 32] = [ + 122u8, 83u8, 8u8, 11u8, 164u8, 20u8, 21u8, 139u8, 231u8, 236u8, 105u8, 185u8, 135u8, + 181u8, 251u8, 125u8, 7u8, 222u8, 225u8, 1u8, 254u8, 133u8, 72u8, 143u8, 8u8, 83u8, + 174u8, 22u8, 35u8, 157u8, 11u8, 222u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + tick_upper: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[3usize].as_ref(), + ), + sender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Mint { + const NAME: &'static str = "Mint"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetFeeProtocol { + pub fee_protocol0_old: substreams::scalar::BigInt, + pub fee_protocol1_old: substreams::scalar::BigInt, + pub fee_protocol0_new: substreams::scalar::BigInt, + pub fee_protocol1_new: substreams::scalar::BigInt, + } + impl SetFeeProtocol { + const TOPIC_ID: [u8; 32] = [ + 151u8, 61u8, 141u8, 146u8, 187u8, 41u8, 159u8, 74u8, 246u8, 206u8, 73u8, 181u8, 42u8, + 138u8, 219u8, 133u8, 174u8, 70u8, 185u8, 242u8, 20u8, 196u8, 196u8, 252u8, 6u8, 172u8, + 119u8, 64u8, 18u8, 55u8, 177u8, 51u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + fee_protocol0_old: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol1_old: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol0_new: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol1_new: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for SetFeeProtocol { + const NAME: &'static str = "SetFeeProtocol"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub sender: Vec, + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub sqrt_price_x96: substreams::scalar::BigInt, + pub liquidity: substreams::scalar::BigInt, + pub tick: substreams::scalar::BigInt, + } + impl Swap { + const TOPIC_ID: [u8; 32] = [ + 196u8, 32u8, 121u8, 249u8, 74u8, 99u8, 80u8, 215u8, 230u8, 35u8, 95u8, 41u8, 23u8, + 73u8, 36u8, 249u8, 40u8, 204u8, 42u8, 200u8, 24u8, 235u8, 100u8, 254u8, 216u8, 0u8, + 78u8, 17u8, 95u8, 188u8, 202u8, 103u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 160usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Int(24usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'recipient' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + liquidity: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Swap { + const NAME: &'static str = "Swap"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/lib.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/lib.rs new file mode 100644 index 0000000..f336cb7 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/lib.rs @@ -0,0 +1,691 @@ +mod abi; +mod pb; +use hex_literal::hex; +use pb::contract::v1 as contract; +use substreams::prelude::*; +use substreams::store; +use substreams::Hex; +use substreams_database_change::pb::database::DatabaseChanges; +use substreams_database_change::tables::Tables as DatabaseChangeTables; +use substreams_entity_change::pb::entity::EntityChanges; +use substreams_entity_change::tables::Tables as EntityChangesTables; +use substreams_ethereum::pb::eth::v2 as eth; +use substreams_ethereum::Event; + +#[allow(unused_imports)] +use num_traits::cast::ToPrimitive; +use std::str::FromStr; +use substreams::scalar::BigDecimal; + +substreams_ethereum::init!(); + +const FACTORY_TRACKED_CONTRACT: [u8; 20] = hex!("1f98431c8ad98523631ae4a59f267346ea31f984"); + +fn map_factory_events(blk: ð::Block, events: &mut contract::Events) { + events.factory_fee_amount_enableds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::factory_contract::events::FeeAmountEnabled::match_and_decode(log) { + return Some(contract::FactoryFeeAmountEnabled { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + fee: event.fee.to_u64(), + tick_spacing: Into::::into(event.tick_spacing).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + events.factory_owner_changeds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::factory_contract::events::OwnerChanged::match_and_decode(log) { + return Some(contract::FactoryOwnerChanged { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + new_owner: event.new_owner, + old_owner: event.old_owner, + }); + } + + None + }) + }) + .collect()); + events.factory_pool_createds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::factory_contract::events::PoolCreated::match_and_decode(log) { + return Some(contract::FactoryPoolCreated { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + fee: event.fee.to_u64(), + pool: event.pool, + tick_spacing: Into::::into(event.tick_spacing).to_i64().unwrap(), + token0: event.token0, + token1: event.token1, + }); + } + + None + }) + }) + .collect()); +} + +fn is_declared_dds_address(addr: &Vec, ordinal: u64, dds_store: &store::StoreGetInt64) -> bool { + // substreams::log::info!("Checking if address {} is declared dds address", Hex(addr).to_string()); + if dds_store.get_at(ordinal, Hex(addr).to_string()).is_some() { + return true; + } + return false; +} + +fn map_pool_events( + blk: ð::Block, + dds_store: &store::StoreGetInt64, + events: &mut contract::Events, +) { + + events.pool_burns.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Burn::match_and_decode(log) { + return Some(contract::PoolBurn { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount: event.amount.to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + owner: event.owner, + tick_lower: Into::::into(event.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(event.tick_upper).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_collects.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Collect::match_and_decode(log) { + return Some(contract::PoolCollect { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + owner: event.owner, + recipient: event.recipient, + tick_lower: Into::::into(event.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(event.tick_upper).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_collect_protocols.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::CollectProtocol::match_and_decode(log) { + return Some(contract::PoolCollectProtocol { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + recipient: event.recipient, + sender: event.sender, + }); + } + + None + }) + }) + .collect()); + + events.pool_flashes.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Flash::match_and_decode(log) { + return Some(contract::PoolFlash { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + paid0: event.paid0.to_string(), + paid1: event.paid1.to_string(), + recipient: event.recipient, + sender: event.sender, + }); + } + + None + }) + }) + .collect()); + + events.pool_increase_observation_cardinality_nexts.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::IncreaseObservationCardinalityNext::match_and_decode(log) { + return Some(contract::PoolIncreaseObservationCardinalityNext { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + observation_cardinality_next_new: event.observation_cardinality_next_new.to_u64(), + observation_cardinality_next_old: event.observation_cardinality_next_old.to_u64(), + }); + } + + None + }) + }) + .collect()); + + events.pool_initializes.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Initialize::match_and_decode(log) { + return Some(contract::PoolInitialize { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + sqrt_price_x96: event.sqrt_price_x96.to_string(), + tick: Into::::into(event.tick).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_mints.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Mint::match_and_decode(log) { + return Some(contract::PoolMint { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount: event.amount.to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + owner: event.owner, + sender: event.sender, + tick_lower: Into::::into(event.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(event.tick_upper).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_set_fee_protocols.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::SetFeeProtocol::match_and_decode(log) { + return Some(contract::PoolSetFeeProtocol { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + fee_protocol0_new: event.fee_protocol0_new.to_u64(), + fee_protocol0_old: event.fee_protocol0_old.to_u64(), + fee_protocol1_new: event.fee_protocol1_new.to_u64(), + fee_protocol1_old: event.fee_protocol1_old.to_u64(), + }); + } + + None + }) + }) + .collect()); + + events.pool_swaps.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Swap::match_and_decode(log) { + return Some(contract::PoolSwap { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + liquidity: event.liquidity.to_string(), + recipient: event.recipient, + sender: event.sender, + sqrt_price_x96: event.sqrt_price_x96.to_string(), + tick: Into::::into(event.tick).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); +} + + +fn db_factory_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.factory_fee_amount_enableds.iter().for_each(|evt| { + tables + .create_row("factory_fee_amount_enabled", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("tick_spacing", evt.tick_spacing); + }); + events.factory_owner_changeds.iter().for_each(|evt| { + tables + .create_row("factory_owner_changed", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("old_owner", Hex(&evt.old_owner).to_string()); + }); + events.factory_pool_createds.iter().for_each(|evt| { + tables + .create_row("factory_pool_created", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("pool", Hex(&evt.pool).to_string()) + .set("tick_spacing", evt.tick_spacing) + .set("token0", Hex(&evt.token0).to_string()) + .set("token1", Hex(&evt.token1).to_string()); + }); +} +fn db_pool_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.pool_burns.iter().for_each(|evt| { + tables + .create_row("pool_burn", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collects.iter().for_each(|evt| { + tables + .create_row("pool_collect", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collect_protocols.iter().for_each(|evt| { + tables + .create_row("pool_collect_protocol", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_flashes.iter().for_each(|evt| { + tables + .create_row("pool_flash", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("paid0", BigDecimal::from_str(&evt.paid0).unwrap()) + .set("paid1", BigDecimal::from_str(&evt.paid1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_increase_observation_cardinality_nexts.iter().for_each(|evt| { + tables + .create_row("pool_increase_observation_cardinality_next", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("observation_cardinality_next_new", evt.observation_cardinality_next_new) + .set("observation_cardinality_next_old", evt.observation_cardinality_next_old); + }); + events.pool_initializes.iter().for_each(|evt| { + tables + .create_row("pool_initialize", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); + events.pool_mints.iter().for_each(|evt| { + tables + .create_row("pool_mint", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_set_fee_protocols.iter().for_each(|evt| { + tables + .create_row("pool_set_fee_protocol", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("fee_protocol0_new", evt.fee_protocol0_new) + .set("fee_protocol0_old", evt.fee_protocol0_old) + .set("fee_protocol1_new", evt.fee_protocol1_new) + .set("fee_protocol1_old", evt.fee_protocol1_old); + }); + events.pool_swaps.iter().for_each(|evt| { + tables + .create_row("pool_swap", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("liquidity", BigDecimal::from_str(&evt.liquidity).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); +} + + +fn graph_factory_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.factory_fee_amount_enableds.iter().for_each(|evt| { + tables + .create_row("factory_fee_amount_enabled", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("tick_spacing", evt.tick_spacing); + }); + events.factory_owner_changeds.iter().for_each(|evt| { + tables + .create_row("factory_owner_changed", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("old_owner", Hex(&evt.old_owner).to_string()); + }); + events.factory_pool_createds.iter().for_each(|evt| { + tables + .create_row("factory_pool_created", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("pool", Hex(&evt.pool).to_string()) + .set("tick_spacing", evt.tick_spacing) + .set("token0", Hex(&evt.token0).to_string()) + .set("token1", Hex(&evt.token1).to_string()); + }); +} +fn graph_pool_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.pool_burns.iter().for_each(|evt| { + tables + .create_row("pool_burn", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collects.iter().for_each(|evt| { + tables + .create_row("pool_collect", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collect_protocols.iter().for_each(|evt| { + tables + .create_row("pool_collect_protocol", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_flashes.iter().for_each(|evt| { + tables + .create_row("pool_flash", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("paid0", BigDecimal::from_str(&evt.paid0).unwrap()) + .set("paid1", BigDecimal::from_str(&evt.paid1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_increase_observation_cardinality_nexts.iter().for_each(|evt| { + tables + .create_row("pool_increase_observation_cardinality_next", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("observation_cardinality_next_new", evt.observation_cardinality_next_new) + .set("observation_cardinality_next_old", evt.observation_cardinality_next_old); + }); + events.pool_initializes.iter().for_each(|evt| { + tables + .create_row("pool_initialize", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); + events.pool_mints.iter().for_each(|evt| { + tables + .create_row("pool_mint", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_set_fee_protocols.iter().for_each(|evt| { + tables + .create_row("pool_set_fee_protocol", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("fee_protocol0_new", evt.fee_protocol0_new) + .set("fee_protocol0_old", evt.fee_protocol0_old) + .set("fee_protocol1_new", evt.fee_protocol1_new) + .set("fee_protocol1_old", evt.fee_protocol1_old); + }); + events.pool_swaps.iter().for_each(|evt| { + tables + .create_row("pool_swap", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("liquidity", BigDecimal::from_str(&evt.liquidity).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); +} +#[substreams::handlers::store] +fn store_factory_pool_created(blk: eth::Block, store: StoreSetInt64) { + for rcpt in blk.receipts() { + for log in rcpt + .receipt + .logs + .iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + { + if let Some(event) = abi::factory_contract::events::PoolCreated::match_and_decode(log) { + store.set(log.ordinal, Hex(event.pool).to_string(), &1); + } + } + } +} + +#[substreams::handlers::map] +fn map_events( + blk: eth::Block, + store_pool: StoreGetInt64, +) -> Result { + let mut events = contract::Events::default(); + map_factory_events(&blk, &mut events); + map_pool_events(&blk, &store_pool, &mut events); + Ok(events) +} + +#[substreams::handlers::map] +fn db_out(events: contract::Events) -> Result { + // Initialize Database Changes container + let mut tables = DatabaseChangeTables::new(); + db_factory_out(&events, &mut tables); + db_pool_out(&events, &mut tables); + Ok(tables.to_database_changes()) +} + +#[substreams::handlers::map] +fn graph_out(events: contract::Events) -> Result { + // Initialize Database Changes container + let mut tables = EntityChangesTables::new(); + graph_factory_out(&events, &mut tables); + graph_pool_out(&events, &mut tables); + Ok(tables.to_entity_changes()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/pb/contract.v1.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/pb/contract.v1.rs new file mode 100644 index 0000000..13bc61f --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/pb/contract.v1.rs @@ -0,0 +1,280 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Events { + #[prost(message, repeated, tag="1")] + pub factory_fee_amount_enableds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub factory_owner_changeds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub factory_pool_createds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub pool_burns: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub pool_collects: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="6")] + pub pool_collect_protocols: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="7")] + pub pool_flashes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="8")] + pub pool_increase_observation_cardinality_nexts: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="9")] + pub pool_initializes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="10")] + pub pool_mints: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="11")] + pub pool_set_fee_protocols: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="12")] + pub pool_swaps: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FactoryFeeAmountEnabled { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(uint64, tag="5")] + pub fee: u64, + #[prost(int64, tag="6")] + pub tick_spacing: i64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FactoryOwnerChanged { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub old_owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub new_owner: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FactoryPoolCreated { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub token0: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub token1: ::prost::alloc::vec::Vec, + #[prost(uint64, tag="7")] + pub fee: u64, + #[prost(int64, tag="8")] + pub tick_spacing: i64, + #[prost(bytes="vec", tag="9")] + pub pool: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolBurn { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(int64, tag="6")] + pub tick_lower: i64, + #[prost(int64, tag="7")] + pub tick_upper: i64, + #[prost(string, tag="8")] + pub amount: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolCollect { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(int64, tag="7")] + pub tick_lower: i64, + #[prost(int64, tag="8")] + pub tick_upper: i64, + #[prost(string, tag="9")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolCollectProtocol { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolFlash { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub amount1: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub paid0: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub paid1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolIncreaseObservationCardinalityNext { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(uint64, tag="5")] + pub observation_cardinality_next_old: u64, + #[prost(uint64, tag="6")] + pub observation_cardinality_next_new: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolInitialize { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(string, tag="5")] + pub sqrt_price_x96: ::prost::alloc::string::String, + #[prost(int64, tag="6")] + pub tick: i64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolMint { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(int64, tag="7")] + pub tick_lower: i64, + #[prost(int64, tag="8")] + pub tick_upper: i64, + #[prost(string, tag="9")] + pub amount: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="11")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolSetFeeProtocol { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(uint64, tag="5")] + pub fee_protocol0_old: u64, + #[prost(uint64, tag="6")] + pub fee_protocol1_old: u64, + #[prost(uint64, tag="7")] + pub fee_protocol0_new: u64, + #[prost(uint64, tag="8")] + pub fee_protocol1_new: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolSwap { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub amount1: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub sqrt_price_x96: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub liquidity: ::prost::alloc::string::String, + #[prost(int64, tag="11")] + pub tick: i64, +} +// @@protoc_insertion_point(module) diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/pb/mod.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/pb/mod.rs new file mode 100644 index 0000000..611ea83 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/src/pb/mod.rs @@ -0,0 +1,8 @@ +// @generated +pub mod contract { + // @@protoc_insertion_point(attribute:contract.v1) + pub mod v1 { + include!("contract.v1.rs"); + // @@protoc_insertion_point(contract.v1) + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/subgraph.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/subgraph.yaml new file mode 100644 index 0000000..dd54391 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/subgraph.yaml @@ -0,0 +1,17 @@ +specVersion: 0.0.6 +description: substreams_init_test substreams based subgraph +repository: # fill in with git remote url +schema: + file: ./schema.graphql + +dataSources: + - kind: substreams + name: + network: mainnet + source: + package: + moduleName: graph_out + file: substreams_init_test-v0.1.0.spkg + mapping: + kind: substreams/graph-entities + apiVersion: 0.0.5 diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.clickhouse.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.clickhouse.yaml new file mode 100644 index 0000000..fd6533c --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.clickhouse.yaml @@ -0,0 +1,68 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.clickhouse.sql" + engine: clickhouse + postgraphile_frontend: + enabled: false + rest_frontend: + enabled: false diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.sql.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.sql.yaml new file mode 100644 index 0000000..22e56ff --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.sql.yaml @@ -0,0 +1,66 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + engine: postgres + postgraphile_frontend: + enabled: true diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.subgraph.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.subgraph.yaml new file mode 100644 index 0000000..dc50b2c --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.subgraph.yaml @@ -0,0 +1,65 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: graph_out + type: sf.substreams.sink.subgraph.v1.Service + config: + schema: "./schema.graphql" + subgraph_yaml: "./subgraph.yaml" + postgres_direct_protocol_access: true diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.yaml new file mode 100644 index 0000000..2115589 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource/substreams.yaml @@ -0,0 +1,57 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/Makefile b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/Makefile new file mode 100644 index 0000000..5fca602 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/Makefile @@ -0,0 +1,26 @@ +CARGO_VERSION := $(shell cargo version 2>/dev/null) + +.PHONY: build +build: +ifdef CARGO_VERSION + cargo build --target wasm32-unknown-unknown --release +else + @echo "Building substreams target using Docker. To speed up this step, install a Rust development environment." + docker run --rm -ti --init -v ${PWD}:/usr/src --workdir /usr/src/ rust:bullseye cargo build --target wasm32-unknown-unknown --release +endif + +.PHONY: run +run: build + substreams run substreams.yaml map_events $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: gui +gui: build + substreams gui substreams.yaml map_events $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK)) + +.PHONY: protogen +protogen: + substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google" + +.PHONY: pack +pack: build + substreams pack substreams.yaml diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/abi/factory_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/abi/factory_contract.abi.json new file mode 100644 index 0000000..e2354a6 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/abi/factory_contract.abi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint24","name":"fee","type":"uint24"},{"indexed":true,"internalType":"int24","name":"tickSpacing","type":"int24"}],"name":"FeeAmountEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token0","type":"address"},{"indexed":true,"internalType":"address","name":"token1","type":"address"},{"indexed":true,"internalType":"uint24","name":"fee","type":"uint24"},{"indexed":false,"internalType":"int24","name":"tickSpacing","type":"int24"},{"indexed":false,"internalType":"address","name":"pool","type":"address"}],"name":"PoolCreated","type":"event"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"}],"name":"createPool","outputs":[{"internalType":"address","name":"pool","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"}],"name":"enableFeeAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint24","name":"","type":"uint24"}],"name":"feeAmountTickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint24","name":"","type":"uint24"}],"name":"getPool","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"parameters","outputs":[{"internalType":"address","name":"factory","type":"address"},{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickSpacing","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/abi/pool_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/abi/pool_contract.abi.json new file mode 100644 index 0000000..bd99dfb --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/abi/pool_contract.abi.json @@ -0,0 +1 @@ +[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"Collect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"CollectProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid1","type":"uint256"}],"name":"Flash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextOld","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextNew","type":"uint16"}],"name":"IncreaseObservationCardinalityNext","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Initialize","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"feeProtocol0Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol0New","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1New","type":"uint8"}],"name":"SetFeeProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"int256","name":"amount0","type":"int256"},{"indexed":false,"internalType":"int256","name":"amount1","type":"int256"},{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Swap","type":"event"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collect","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collectProtocol","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal0X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal1X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"flash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"}],"name":"increaseObservationCardinalityNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liquidity","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLiquidityPerTick","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"observations","outputs":[{"internalType":"uint32","name":"blockTimestamp","type":"uint32"},{"internalType":"int56","name":"tickCumulative","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityCumulativeX128","type":"uint160"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32[]","name":"secondsAgos","type":"uint32[]"}],"name":"observe","outputs":[{"internalType":"int56[]","name":"tickCumulatives","type":"int56[]"},{"internalType":"uint160[]","name":"secondsPerLiquidityCumulativeX128s","type":"uint160[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"positions","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"},{"internalType":"uint128","name":"tokensOwed0","type":"uint128"},{"internalType":"uint128","name":"tokensOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFees","outputs":[{"internalType":"uint128","name":"token0","type":"uint128"},{"internalType":"uint128","name":"token1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"feeProtocol0","type":"uint8"},{"internalType":"uint8","name":"feeProtocol1","type":"uint8"}],"name":"setFeeProtocol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tick","type":"int24"},{"internalType":"uint16","name":"observationIndex","type":"uint16"},{"internalType":"uint16","name":"observationCardinality","type":"uint16"},{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"},{"internalType":"uint8","name":"feeProtocol","type":"uint8"},{"internalType":"bool","name":"unlocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"}],"name":"snapshotCumulativesInside","outputs":[{"internalType":"int56","name":"tickCumulativeInside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityInsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsInside","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"zeroForOne","type":"bool"},{"internalType":"int256","name":"amountSpecified","type":"int256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[{"internalType":"int256","name":"amount0","type":"int256"},{"internalType":"int256","name":"amount1","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int16","name":"","type":"int16"}],"name":"tickBitmap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"","type":"int24"}],"name":"ticks","outputs":[{"internalType":"uint128","name":"liquidityGross","type":"uint128"},{"internalType":"int128","name":"liquidityNet","type":"int128"},{"internalType":"uint256","name":"feeGrowthOutside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthOutside1X128","type":"uint256"},{"internalType":"int56","name":"tickCumulativeOutside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityOutsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsOutside","type":"uint32"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/build.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/build.rs new file mode 100644 index 0000000..140844b --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/build.rs @@ -0,0 +1,33 @@ +use anyhow::{Ok, Result}; +use regex::Regex; +use substreams_ethereum::Abigen; +use std::fs; + +fn main() -> Result<(), anyhow::Error> { + let file_names = [ + "abi/factory_contract.abi.json", + "abi/pool_contract.abi.json", + ]; + let file_output_names = [ + "src/abi/factory_contract.rs", + "src/abi/pool_contract.rs", + ]; + + let mut i = 0; + for f in file_names { + let contents = fs::read_to_string(f) + .expect("Should have been able to read the file"); + + // sanitize fields and attributes starting with an underscore + let regex = Regex::new(r#"("\w+"\s?:\s?")_(\w+")"#).unwrap(); + let sanitized_abi_file = regex.replace_all(contents.as_str(), "${1}u_${2}"); + + Abigen::from_bytes("Contract", sanitized_abi_file.as_bytes())? + .generate()? + .write_to_file(file_output_names[i])?; + + i = i+1; + } + + Ok(()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/proto/contract.proto b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/proto/contract.proto new file mode 100644 index 0000000..2b4cf29 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/proto/contract.proto @@ -0,0 +1,333 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package contract.v1; + +message Events { + repeated factory_FeeAmountEnabled factory_fee_amount_enableds = 1; + repeated factory_OwnerChanged factory_owner_changeds = 2; + repeated factory_PoolCreated factory_pool_createds = 3; + repeated pool_Burn pool_burns = 4; + repeated pool_Collect pool_collects = 5; + repeated pool_CollectProtocol pool_collect_protocols = 6; + repeated pool_Flash pool_flashes = 7; + repeated pool_IncreaseObservationCardinalityNext pool_increase_observation_cardinality_nexts = 8; + repeated pool_Initialize pool_initializes = 9; + repeated pool_Mint pool_mints = 10; + repeated pool_SetFeeProtocol pool_set_fee_protocols = 11; + repeated pool_Swap pool_swaps = 12; +} + +message Calls { + repeated factory_CreatePoolCall factory_call_create_pools = 1; + repeated factory_EnableFeeAmountCall factory_call_enable_fee_amounts = 2; + repeated factory_SetOwnerCall factory_call_set_owners = 3; + repeated pool_BurnCall pool_call_burns = 4; + repeated pool_CollectCall pool_call_collects = 5; + repeated pool_CollectProtocolCall pool_call_collect_protocols = 6; + repeated pool_FlashCall pool_call_flashes = 7; + repeated pool_IncreaseObservationCardinalityNextCall pool_call_increase_observation_cardinality_nexts = 8; + repeated pool_InitializeCall pool_call_initializes = 9; + repeated pool_MintCall pool_call_mints = 10; + repeated pool_SetFeeProtocolCall pool_call_set_fee_protocols = 11; + repeated pool_SwapCall pool_call_swaps = 12; +} + + +message factory_FeeAmountEnabled { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + uint64 fee = 5; + int64 tick_spacing = 6; +} + +message factory_OwnerChanged { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes old_owner = 5; + bytes new_owner = 6; +} + +message factory_PoolCreated { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes token0 = 5; + bytes token1 = 6; + uint64 fee = 7; + int64 tick_spacing = 8; + bytes pool = 9; +} + +message factory_CreatePoolCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + bytes token_a = 6; + bytes token_b = 7; + uint64 fee = 8; + bytes output_pool = 9; +} + +message factory_EnableFeeAmountCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + uint64 fee = 6; + int64 tick_spacing = 7; +} + +message factory_SetOwnerCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + bytes u_owner = 6; +} + +message pool_Burn { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes owner = 6; + int64 tick_lower = 7; + int64 tick_upper = 8; + string amount = 9; + string amount0 = 10; + string amount1 = 11; +} + +message pool_Collect { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes owner = 6; + bytes recipient = 7; + int64 tick_lower = 8; + int64 tick_upper = 9; + string amount0 = 10; + string amount1 = 11; +} + +message pool_CollectProtocol { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; +} + +message pool_Flash { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; + string paid0 = 10; + string paid1 = 11; +} + +message pool_IncreaseObservationCardinalityNext { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + uint64 observation_cardinality_next_old = 6; + uint64 observation_cardinality_next_new = 7; +} + +message pool_Initialize { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + string sqrt_price_x96 = 6; + int64 tick = 7; +} + +message pool_Mint { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes owner = 7; + int64 tick_lower = 8; + int64 tick_upper = 9; + string amount = 10; + string amount0 = 11; + string amount1 = 12; +} + +message pool_SetFeeProtocol { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + uint64 fee_protocol0_old = 6; + uint64 fee_protocol1_old = 7; + uint64 fee_protocol0_new = 8; + uint64 fee_protocol1_new = 9; +} + +message pool_Swap { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string evt_address = 5; + bytes sender = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; + string sqrt_price_x96 = 10; + string liquidity = 11; + int64 tick = 12; +} + +message pool_BurnCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + int64 tick_lower = 7; + int64 tick_upper = 8; + string amount = 9; + string output_amount0 = 10; + string output_amount1 = 11; +} + +message pool_CollectCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + bytes recipient = 7; + int64 tick_lower = 8; + int64 tick_upper = 9; + string amount0_requested = 10; + string amount1_requested = 11; + string output_amount0 = 12; + string output_amount1 = 13; +} + +message pool_CollectProtocolCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + bytes recipient = 7; + string amount0_requested = 8; + string amount1_requested = 9; + string output_amount0 = 10; + string output_amount1 = 11; +} + +message pool_FlashCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + bytes recipient = 7; + string amount0 = 8; + string amount1 = 9; + bytes data = 10; +} + +message pool_IncreaseObservationCardinalityNextCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + uint64 observation_cardinality_next = 7; +} + +message pool_InitializeCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + string sqrt_price_x96 = 7; +} + +message pool_MintCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + bytes recipient = 7; + int64 tick_lower = 8; + int64 tick_upper = 9; + string amount = 10; + bytes data = 11; + string output_amount0 = 12; + string output_amount1 = 13; +} + +message pool_SetFeeProtocolCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + uint64 fee_protocol0 = 7; + uint64 fee_protocol1 = 8; +} + +message pool_SwapCall { + string call_tx_hash = 1; + google.protobuf.Timestamp call_block_time = 2; + uint64 call_block_number = 3; + uint64 call_ordinal = 4; + bool call_success = 5; + string call_address = 6; + bytes recipient = 7; + bool zero_for_one = 8; + string amount_specified = 9; + string sqrt_price_limit_x96 = 10; + bytes data = 11; + string output_amount0 = 12; + string output_amount1 = 13; +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/rust-toolchain.toml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/rust-toolchain.toml new file mode 100644 index 0000000..ec334c0 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.65" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.clickhouse.sql b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.clickhouse.sql new file mode 100644 index 0000000..f1a9061 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.clickhouse.sql @@ -0,0 +1,274 @@ +CREATE TABLE IF NOT EXISTS factory_fee_amount_enabled ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "fee" UInt32, + "tick_spacing" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS factory_owner_changed ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "new_owner" VARCHAR(40), + "old_owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS factory_pool_created ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "fee" UInt32, + "pool" VARCHAR(40), + "tick_spacing" Int32, + "token0" VARCHAR(40), + "token1" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); + +CREATE TABLE IF NOT EXISTS factory_call_create_pool ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "fee" UInt32, + "output_pool" VARCHAR(40), + "token_a" VARCHAR(40), + "token_b" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS factory_call_enable_fee_amount ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "fee" UInt32, + "tick_spacing" Int32 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS factory_call_set_owner ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "u_owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_burn ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount" UInt128, + "amount0" UInt256, + "amount1" UInt256, + "owner" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_collect ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" UInt128, + "amount1" UInt128, + "owner" VARCHAR(40), + "recipient" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_collect_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" UInt128, + "amount1" UInt128, + "recipient" VARCHAR(40), + "sender" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_flash ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" UInt256, + "amount1" UInt256, + "paid0" UInt256, + "paid1" UInt256, + "recipient" VARCHAR(40), + "sender" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_increase_observation_cardinality_next ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "observation_cardinality_next_new" UInt16, + "observation_cardinality_next_old" UInt16 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_initialize ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "sqrt_price_x96" UInt256, + "tick" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_mint ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount" UInt128, + "amount0" UInt256, + "amount1" UInt256, + "owner" VARCHAR(40), + "sender" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_set_fee_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "fee_protocol0_new" UInt8, + "fee_protocol0_old" UInt8, + "fee_protocol1_new" UInt8, + "fee_protocol1_old" UInt8 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_swap ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40), + "amount0" Int256, + "amount1" Int256, + "liquidity" UInt128, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + "sqrt_price_x96" UInt256, + "tick" Int32 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS pool_call_burn ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount" UInt128, + "output_amount0" UInt256, + "output_amount1" UInt256, + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_collect ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount0_requested" UInt128, + "amount1_requested" UInt128, + "output_amount0" UInt128, + "output_amount1" UInt128, + "recipient" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_collect_protocol ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount0_requested" UInt128, + "amount1_requested" UInt128, + "output_amount0" UInt128, + "output_amount1" UInt128, + "recipient" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_flash ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount0" UInt256, + "amount1" UInt256, + "data" TEXT, + "recipient" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_increase_observation_cardinality_next ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "observation_cardinality_next" UInt16 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_initialize ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "sqrt_price_x96" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_mint ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount" UInt128, + "data" TEXT, + "output_amount0" UInt256, + "output_amount1" UInt256, + "recipient" VARCHAR(40), + "tick_lower" Int32, + "tick_upper" Int32 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_set_fee_protocol ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "fee_protocol0" UInt8, + "fee_protocol1" UInt8 +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +CREATE TABLE IF NOT EXISTS pool_call_swap ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount_specified" Int256, + "data" TEXT, + "output_amount0" Int256, + "output_amount1" Int256, + "recipient" VARCHAR(40), + "sqrt_price_limit_x96" UInt256, + "zero_for_one" BOOL +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.graphql b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.graphql new file mode 100644 index 0000000..e725321 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.graphql @@ -0,0 +1,299 @@ +type factory_fee_amount_enabled @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + fee: Int! + tick_spacing: Int! +} +type factory_owner_changed @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + new_owner: String! + old_owner: String! +} +type factory_pool_created @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + fee: Int! + pool: String! + tick_spacing: Int! + token0: String! + token1: String! +} +type factory_call_create_pool @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + fee: Int! + output_pool: String! + token_a: String! + token_b: String! +} +type factory_call_enable_fee_amount @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + fee: Int! + tick_spacing: Int! +} +type factory_call_set_owner @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + u_owner: String! +} + + +type pool_burn @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount: BigDecimal! + amount0: BigDecimal! + amount1: BigDecimal! + owner: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_collect @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + owner: String! + recipient: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_collect_protocol @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + recipient: String! + sender: String! +} +type pool_flash @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + paid0: BigDecimal! + paid1: BigDecimal! + recipient: String! + sender: String! +} +type pool_increase_observation_cardinality_next @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + observation_cardinality_next_new: Int! + observation_cardinality_next_old: Int! +} +type pool_initialize @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + sqrt_price_x96: BigDecimal! + tick: Int! +} +type pool_mint @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount: BigDecimal! + amount0: BigDecimal! + amount1: BigDecimal! + owner: String! + sender: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_set_fee_protocol @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + fee_protocol0_new: Int! + fee_protocol0_old: Int! + fee_protocol1_new: Int! + fee_protocol1_old: Int! +} +type pool_swap @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + liquidity: BigDecimal! + recipient: String! + sender: String! + sqrt_price_x96: BigDecimal! + tick: Int! +}type pool_call_burn @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + amount: BigDecimal! + output_amount0: BigDecimal! + output_amount1: BigDecimal! + tick_lower: Int! + tick_upper: Int! +} +type pool_call_collect @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + amount0_requested: BigDecimal! + amount1_requested: BigDecimal! + output_amount0: BigDecimal! + output_amount1: BigDecimal! + recipient: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_call_collect_protocol @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + amount0_requested: BigDecimal! + amount1_requested: BigDecimal! + output_amount0: BigDecimal! + output_amount1: BigDecimal! + recipient: String! +} +type pool_call_flash @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + amount0: BigDecimal! + amount1: BigDecimal! + data: String! + recipient: String! +} +type pool_call_increase_observation_cardinality_next @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + observation_cardinality_next: Int! +} +type pool_call_initialize @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + sqrt_price_x96: BigDecimal! +} +type pool_call_mint @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + amount: BigDecimal! + data: String! + output_amount0: BigDecimal! + output_amount1: BigDecimal! + recipient: String! + tick_lower: Int! + tick_upper: Int! +} +type pool_call_set_fee_protocol @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + fee_protocol0: Int! + fee_protocol1: Int! +} +type pool_call_swap @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + amount_specified: BigDecimal! + data: String! + output_amount0: BigDecimal! + output_amount1: BigDecimal! + recipient: String! + sqrt_price_limit_x96: BigDecimal! + zero_for_one: Boolean! +} + diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.sql b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.sql new file mode 100644 index 0000000..9769804 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/schema.sql @@ -0,0 +1,300 @@ +CREATE TABLE IF NOT EXISTS factory_fee_amount_enabled ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "fee" INT, + "tick_spacing" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS factory_owner_changed ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "new_owner" VARCHAR(40), + "old_owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS factory_pool_created ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "fee" INT, + "pool" VARCHAR(40), + "tick_spacing" INT, + "token0" VARCHAR(40), + "token1" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS factory_call_create_pool ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "fee" INT, + "output_pool" VARCHAR(40), + "token_a" VARCHAR(40), + "token_b" VARCHAR(40), + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS factory_call_enable_fee_amount ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "fee" INT, + "tick_spacing" INT, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS factory_call_set_owner ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "u_owner" VARCHAR(40), + PRIMARY KEY(call_tx_hash,call_ordinal) +); + + +CREATE TABLE IF NOT EXISTS pool_burn ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount" DECIMAL, + "amount0" DECIMAL, + "amount1" DECIMAL, + "owner" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_collect ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "owner" VARCHAR(40), + "recipient" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_collect_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_flash ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "paid0" DECIMAL, + "paid1" DECIMAL, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_increase_observation_cardinality_next ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "observation_cardinality_next_new" INT, + "observation_cardinality_next_old" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_initialize ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "sqrt_price_x96" DECIMAL, + "tick" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_mint ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount" DECIMAL, + "amount0" DECIMAL, + "amount1" DECIMAL, + "owner" VARCHAR(40), + "sender" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_set_fee_protocol ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "fee_protocol0_new" INT, + "fee_protocol0_old" INT, + "fee_protocol1_new" INT, + "fee_protocol1_old" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS pool_swap ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "liquidity" DECIMAL, + "recipient" VARCHAR(40), + "sender" VARCHAR(40), + "sqrt_price_x96" DECIMAL, + "tick" INT, + PRIMARY KEY(evt_tx_hash,evt_index) +);CREATE TABLE IF NOT EXISTS pool_call_burn ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount" DECIMAL, + "output_amount0" DECIMAL, + "output_amount1" DECIMAL, + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_collect ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount0_requested" DECIMAL, + "amount1_requested" DECIMAL, + "output_amount0" DECIMAL, + "output_amount1" DECIMAL, + "recipient" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_collect_protocol ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount0_requested" DECIMAL, + "amount1_requested" DECIMAL, + "output_amount0" DECIMAL, + "output_amount1" DECIMAL, + "recipient" VARCHAR(40), + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_flash ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount0" DECIMAL, + "amount1" DECIMAL, + "data" TEXT, + "recipient" VARCHAR(40), + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_increase_observation_cardinality_next ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "observation_cardinality_next" INT, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_initialize ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "sqrt_price_x96" DECIMAL, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_mint ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount" DECIMAL, + "data" TEXT, + "output_amount0" DECIMAL, + "output_amount1" DECIMAL, + "recipient" VARCHAR(40), + "tick_lower" INT, + "tick_upper" INT, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_set_fee_protocol ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "fee_protocol0" INT, + "fee_protocol1" INT, + PRIMARY KEY(call_tx_hash,call_ordinal) +); +CREATE TABLE IF NOT EXISTS pool_call_swap ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + "amount_specified" DECIMAL, + "data" TEXT, + "output_amount0" DECIMAL, + "output_amount1" DECIMAL, + "recipient" VARCHAR(40), + "sqrt_price_limit_x96" DECIMAL, + "zero_for_one" BOOL, + PRIMARY KEY(call_tx_hash,call_ordinal) +); + + diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/factory_contract.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/factory_contract.rs new file mode 100644 index 0000000..7d59bf6 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/factory_contract.rs @@ -0,0 +1,1021 @@ + +const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; +/// Contract's functions. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct CreatePool { + pub token_a: Vec, + pub token_b: Vec, + pub fee: substreams::scalar::BigInt, + } + impl CreatePool { + const METHOD_ID: [u8; 4] = [161u8, 103u8, 18u8, 149u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_a: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_b: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.token_a)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.token_b)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for CreatePool { + const NAME: &'static str = "createPool"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for CreatePool { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct EnableFeeAmount { + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + } + impl EnableFeeAmount { + const METHOD_ID: [u8; 4] = [138u8, 124u8, 25u8, 95u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + fee: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + { + let non_full_signed_bytes = self.tick_spacing.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for EnableFeeAmount { + const NAME: &'static str = "enableFeeAmount"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FeeAmountTickSpacing { + pub param0: substreams::scalar::BigInt, + } + impl FeeAmountTickSpacing { + const METHOD_ID: [u8; 4] = [34u8, 175u8, 204u8, 203u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(24usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Int(24usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for FeeAmountTickSpacing { + const NAME: &'static str = "feeAmountTickSpacing"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for FeeAmountTickSpacing { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetPool { + pub param0: Vec, + pub param1: Vec, + pub param2: substreams::scalar::BigInt, + } + impl GetPool { + const METHOD_ID: [u8; 4] = [22u8, 152u8, 238u8, 130u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param1: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + param2: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.param0)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.param1)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param2.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetPool { + const NAME: &'static str = "getPool"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for GetPool { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Owner {} + impl Owner { + const METHOD_ID: [u8; 4] = [141u8, 165u8, 203u8, 91u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Owner { + const NAME: &'static str = "owner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Owner { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Parameters {} + impl Parameters { + const METHOD_ID: [u8; 4] = [137u8, 3u8, 87u8, 48u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(24usize), + ethabi::ParamType::Int(24usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Parameters { + const NAME: &'static str = "parameters"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Parameters + { + fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + Vec, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetOwner { + pub u_owner: Vec, + } + impl SetOwner { + const METHOD_ID: [u8; 4] = [19u8, 175u8, 64u8, 53u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode(&[ethabi::ParamType::Address], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Address(ethabi::Address::from_slice( + &self.u_owner, + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetOwner { + const NAME: &'static str = "setOwner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } +} +/// Contract's events. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct FeeAmountEnabled { + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + } + impl FeeAmountEnabled { + const TOPIC_ID: [u8; 32] = [ + 198u8, 106u8, 63u8, 223u8, 7u8, 35u8, 44u8, 221u8, 24u8, 95u8, 235u8, 204u8, 101u8, + 121u8, 212u8, 8u8, 194u8, 65u8, 180u8, 122u8, 226u8, 249u8, 144u8, 125u8, 132u8, 190u8, + 101u8, 81u8, 65u8, 238u8, 174u8, 204u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Ok(Self { + fee: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(24usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'fee' from topic of type 'uint24': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + }) + } + } + impl substreams_ethereum::Event for FeeAmountEnabled { + const NAME: &'static str = "FeeAmountEnabled"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnerChanged { + pub old_owner: Vec, + pub new_owner: Vec, + } + impl OwnerChanged { + const TOPIC_ID: [u8; 32] = [ + 181u8, 50u8, 7u8, 59u8, 56u8, 200u8, 49u8, 69u8, 227u8, 229u8, 19u8, 83u8, 119u8, + 160u8, 139u8, 249u8, 170u8, 181u8, 91u8, 192u8, 253u8, 124u8, 17u8, 121u8, 205u8, 79u8, + 185u8, 149u8, 210u8, 165u8, 21u8, 156u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Ok(Self { + old_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'old_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + new_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for OwnerChanged { + const NAME: &'static str = "OwnerChanged"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct PoolCreated { + pub token0: Vec, + pub token1: Vec, + pub fee: substreams::scalar::BigInt, + pub tick_spacing: substreams::scalar::BigInt, + pub pool: Vec, + } + impl PoolCreated { + const TOPIC_ID: [u8; 32] = [ + 120u8, 60u8, 202u8, 28u8, 4u8, 18u8, 221u8, 13u8, 105u8, 94u8, 120u8, 69u8, 104u8, + 201u8, 109u8, 162u8, 233u8, 194u8, 47u8, 249u8, 137u8, 53u8, 122u8, 46u8, 139u8, 29u8, + 155u8, 43u8, 78u8, 107u8, 113u8, 24u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Int(24usize), ethabi::ParamType::Address], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + token0: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'token0' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token1: ethabi::decode(&[ethabi::ParamType::Address], log.topics[2usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'token1' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + fee: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(24usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'fee' from topic of type 'uint24': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick_spacing: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + pool: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for PoolCreated { + const NAME: &'static str = "PoolCreated"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/mod.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/mod.rs new file mode 100644 index 0000000..2932aa6 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/mod.rs @@ -0,0 +1,3 @@ + +pub mod factory_contract; +pub mod pool_contract; \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/pool_contract.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/pool_contract.rs new file mode 100644 index 0000000..a4f6b47 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/abi/pool_contract.rs @@ -0,0 +1,4433 @@ + +const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; +/// Contract's functions. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Burn { + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Burn { + const METHOD_ID: [u8; 4] = [163u8, 65u8, 35u8, 167u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Burn { + const NAME: &'static str = "burn"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Burn + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Collect { + pub recipient: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount0_requested: substreams::scalar::BigInt, + pub amount1_requested: substreams::scalar::BigInt, + } + impl Collect { + const METHOD_ID: [u8; 4] = [79u8, 30u8, 179u8, 216u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount0_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount0_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount1_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Collect { + const NAME: &'static str = "collect"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Collect + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct CollectProtocol { + pub recipient: Vec, + pub amount0_requested: substreams::scalar::BigInt, + pub amount1_requested: substreams::scalar::BigInt, + } + impl CollectProtocol { + const METHOD_ID: [u8; 4] = [133u8, 182u8, 103u8, 41u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1_requested: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount0_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount1_requested.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for CollectProtocol { + const NAME: &'static str = "collectProtocol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for CollectProtocol + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Factory {} + impl Factory { + const METHOD_ID: [u8; 4] = [196u8, 90u8, 1u8, 85u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Factory { + const NAME: &'static str = "factory"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Factory { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Fee {} + impl Fee { + const METHOD_ID: [u8; 4] = [221u8, 202u8, 63u8, 67u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(24usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Fee { + const NAME: &'static str = "fee"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Fee { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FeeGrowthGlobal0X128 {} + impl FeeGrowthGlobal0X128 { + const METHOD_ID: [u8; 4] = [243u8, 5u8, 131u8, 153u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(256usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for FeeGrowthGlobal0X128 { + const NAME: &'static str = "feeGrowthGlobal0X128"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for FeeGrowthGlobal0X128 { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FeeGrowthGlobal1X128 {} + impl FeeGrowthGlobal1X128 { + const METHOD_ID: [u8; 4] = [70u8, 20u8, 19u8, 25u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(256usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for FeeGrowthGlobal1X128 { + const NAME: &'static str = "feeGrowthGlobal1X128"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for FeeGrowthGlobal1X128 { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Flash { + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub data: Vec, + } + impl Flash { + const METHOD_ID: [u8; 4] = [73u8, 14u8, 108u8, 188u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount1.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Bytes(self.data.clone()), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Flash { + const NAME: &'static str = "flash"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IncreaseObservationCardinalityNext { + pub observation_cardinality_next: substreams::scalar::BigInt, + } + impl IncreaseObservationCardinalityNext { + const METHOD_ID: [u8; 4] = [50u8, 20u8, 143u8, 103u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(16usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + observation_cardinality_next: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.observation_cardinality_next.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for IncreaseObservationCardinalityNext { + const NAME: &'static str = "increaseObservationCardinalityNext"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Initialize { + pub sqrt_price_x96: substreams::scalar::BigInt, + } + impl Initialize { + const METHOD_ID: [u8; 4] = [246u8, 55u8, 115u8, 29u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(160usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.sqrt_price_x96.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Initialize { + const NAME: &'static str = "initialize"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Liquidity {} + impl Liquidity { + const METHOD_ID: [u8; 4] = [26u8, 104u8, 101u8, 2u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(128usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Liquidity { + const NAME: &'static str = "liquidity"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Liquidity { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MaxLiquidityPerTick {} + impl MaxLiquidityPerTick { + const METHOD_ID: [u8; 4] = [112u8, 207u8, 117u8, 74u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(128usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for MaxLiquidityPerTick { + const NAME: &'static str = "maxLiquidityPerTick"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for MaxLiquidityPerTick { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Mint { + pub recipient: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + pub data: Vec, + } + impl Mint { + const METHOD_ID: [u8; 4] = [60u8, 138u8, 125u8, 141u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.amount.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Bytes(self.data.clone()), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Mint { + const NAME: &'static str = "mint"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Mint + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Observations { + pub param0: substreams::scalar::BigInt, + } + impl Observations { + const METHOD_ID: [u8; 4] = [37u8, 44u8, 9u8, 215u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Uint(256usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.param0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ))]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(32usize), + ethabi::ParamType::Int(56usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Observations { + const NAME: &'static str = "observations"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> for Observations + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Observe { + pub seconds_agos: Vec, + } + impl Observe { + const METHOD_ID: [u8; 4] = [136u8, 59u8, 219u8, 253u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint( + 32usize, + )))], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + seconds_agos: values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + .collect(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[{ + let v = self + .seconds_agos + .iter() + .map(|inner| { + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match inner.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )) + }) + .collect(); + ethabi::Token::Array(v) + }]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array(Box::new(ethabi::ParamType::Int(56usize))), + ethabi::ParamType::Array(Box::new(ethabi::ParamType::Uint(160usize))), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + .collect(), + values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + .collect(), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + Vec, + Vec, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Observe { + const NAME: &'static str = "observe"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + Vec, + Vec, + )> for Observe + { + fn output( + data: &[u8], + ) -> Result< + ( + Vec, + Vec, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Positions { + pub param0: [u8; 32usize], + } + impl Positions { + const METHOD_ID: [u8; 4] = [81u8, 78u8, 164u8, 191u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::FixedBytes(self.param0.as_ref().to_vec())]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Positions { + const NAME: &'static str = "positions"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Positions + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProtocolFees {} + impl ProtocolFees { + const METHOD_ID: [u8; 4] = [26u8, 216u8, 176u8, 59u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProtocolFees { + const NAME: &'static str = "protocolFees"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for ProtocolFees + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetFeeProtocol { + pub fee_protocol0: substreams::scalar::BigInt, + pub fee_protocol1: substreams::scalar::BigInt, + } + impl SetFeeProtocol { + const METHOD_ID: [u8; 4] = [130u8, 6u8, 164u8, 209u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + fee_protocol0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee_protocol0.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.fee_protocol1.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetFeeProtocol { + const NAME: &'static str = "setFeeProtocol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Slot0 {} + impl Slot0 { + const METHOD_ID: [u8; 4] = [56u8, 80u8, 199u8, 189u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Slot0 { + const NAME: &'static str = "slot0"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> for Slot0 + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SnapshotCumulativesInside { + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + } + impl SnapshotCumulativesInside { + const METHOD_ID: [u8; 4] = [163u8, 136u8, 7u8, 242u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(24usize), + ethabi::ParamType::Int(24usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + tick_lower: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + tick_upper: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + { + let non_full_signed_bytes = self.tick_lower.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + { + let non_full_signed_bytes = self.tick_upper.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(56usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(32usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SnapshotCumulativesInside { + const NAME: &'static str = "snapshotCumulativesInside"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for SnapshotCumulativesInside + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub recipient: Vec, + pub zero_for_one: bool, + pub amount_specified: substreams::scalar::BigInt, + pub sqrt_price_limit_x96: substreams::scalar::BigInt, + pub data: Vec, + } + impl Swap { + const METHOD_ID: [u8; 4] = [18u8, 138u8, 203u8, 8u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Bool, + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + zero_for_one: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + amount_specified: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + sqrt_price_limit_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.recipient)), + ethabi::Token::Bool(self.zero_for_one.clone()), + { + let non_full_signed_bytes = self.amount_specified.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }, + ethabi::Token::Uint(ethabi::Uint::from_big_endian( + match self.sqrt_price_limit_x96.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + )), + ethabi::Token::Bytes(self.data.clone()), + ]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Int(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Swap { + const NAME: &'static str = "swap"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + )> for Swap + { + fn output( + data: &[u8], + ) -> Result<(substreams::scalar::BigInt, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TickBitmap { + pub param0: substreams::scalar::BigInt, + } + impl TickBitmap { + const METHOD_ID: [u8; 4] = [83u8, 57u8, 194u8, 150u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Int(16usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[{ + let non_full_signed_bytes = self.param0.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Uint(256usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TickBitmap { + const NAME: &'static str = "tickBitmap"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TickBitmap { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TickSpacing {} + impl TickSpacing { + const METHOD_ID: [u8; 4] = [208u8, 201u8, 58u8, 124u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode(&[ethabi::ParamType::Int(24usize)], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TickSpacing { + const NAME: &'static str = "tickSpacing"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TickSpacing { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Ticks { + pub param0: substreams::scalar::BigInt, + } + impl Ticks { + const METHOD_ID: [u8; 4] = [243u8, 13u8, 186u8, 147u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = + ethabi::decode(&[ethabi::ParamType::Int(24usize)], maybe_data.unwrap()) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[{ + let non_full_signed_bytes = self.param0.to_signed_bytes_be(); + let mut full_signed_bytes = [0xff as u8; 32]; + non_full_signed_bytes + .into_iter() + .rev() + .enumerate() + .for_each(|(i, byte)| full_signed_bytes[31 - i] = byte); + ethabi::Token::Int(ethabi::Int::from_big_endian(full_signed_bytes.as_ref())) + }]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Int(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Int(56usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(32usize), + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Ticks { + const NAME: &'static str = "ticks"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl + substreams_ethereum::rpc::RPCDecodable<( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + )> for Ticks + { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Token0 {} + impl Token0 { + const METHOD_ID: [u8; 4] = [13u8, 254u8, 22u8, 129u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Token0 { + const NAME: &'static str = "token0"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Token0 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Token1 {} + impl Token1 { + const METHOD_ID: [u8; 4] = [210u8, 18u8, 32u8, 167u8]; + pub fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode(&[ethabi::ParamType::Address], data.as_ref()) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok(values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec()) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![rpc::RpcCall { + to_addr: address, + data: self.encode(), + }], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses.get(0).expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, + err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Token1 { + const NAME: &'static str = "token1"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode(call: &substreams_ethereum::pb::eth::v2::Call) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Token1 { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } +} +/// Contract's events. +#[allow(dead_code, unused_imports, unused_variables)] +pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Burn { + pub owner: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Burn { + const TOPIC_ID: [u8; 32] = [ + 12u8, 57u8, 108u8, 217u8, 137u8, 163u8, 159u8, 68u8, 89u8, 181u8, 250u8, 26u8, 237u8, + 106u8, 154u8, 141u8, 205u8, 188u8, 69u8, 144u8, 138u8, 207u8, 214u8, 126u8, 2u8, 140u8, + 213u8, 104u8, 218u8, 152u8, 152u8, 44u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 96usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + tick_upper: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[3usize].as_ref(), + ), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Burn { + const NAME: &'static str = "Burn"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Collect { + pub owner: Vec, + pub recipient: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Collect { + const TOPIC_ID: [u8; 32] = [ + 112u8, 147u8, 83u8, 56u8, 230u8, 151u8, 117u8, 69u8, 106u8, 133u8, 221u8, 239u8, 34u8, + 108u8, 57u8, 95u8, 182u8, 104u8, 182u8, 63u8, 160u8, 17u8, 95u8, 95u8, 32u8, 97u8, + 11u8, 56u8, 142u8, 108u8, 169u8, 192u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 96usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + tick_upper: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[3usize].as_ref(), + ), + recipient: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Collect { + const NAME: &'static str = "Collect"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct CollectProtocol { + pub sender: Vec, + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl CollectProtocol { + const TOPIC_ID: [u8; 32] = [ + 89u8, 107u8, 87u8, 57u8, 6u8, 33u8, 141u8, 52u8, 17u8, 133u8, 11u8, 38u8, 166u8, 180u8, + 55u8, 214u8, 196u8, 82u8, 47u8, 219u8, 67u8, 210u8, 210u8, 56u8, 98u8, 99u8, 248u8, + 109u8, 80u8, 184u8, 177u8, 81u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(128usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'recipient' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for CollectProtocol { + const NAME: &'static str = "CollectProtocol"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Flash { + pub sender: Vec, + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub paid0: substreams::scalar::BigInt, + pub paid1: substreams::scalar::BigInt, + } + impl Flash { + const TOPIC_ID: [u8; 32] = [ + 189u8, 189u8, 183u8, 29u8, 120u8, 96u8, 55u8, 107u8, 165u8, 43u8, 37u8, 165u8, 2u8, + 139u8, 238u8, 162u8, 53u8, 129u8, 54u8, 74u8, 64u8, 82u8, 47u8, 107u8, 207u8, 184u8, + 107u8, 177u8, 242u8, 220u8, 166u8, 51u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'recipient' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + paid0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + paid1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Flash { + const NAME: &'static str = "Flash"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IncreaseObservationCardinalityNext { + pub observation_cardinality_next_old: substreams::scalar::BigInt, + pub observation_cardinality_next_new: substreams::scalar::BigInt, + } + impl IncreaseObservationCardinalityNext { + const TOPIC_ID: [u8; 32] = [ + 172u8, 73u8, 229u8, 24u8, 249u8, 10u8, 53u8, 143u8, 101u8, 46u8, 68u8, 0u8, 22u8, 79u8, + 5u8, 165u8, 216u8, 247u8, 227u8, 94u8, 119u8, 71u8, 39u8, 155u8, 195u8, 169u8, 61u8, + 191u8, 88u8, 78u8, 18u8, 90u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(16usize), + ethabi::ParamType::Uint(16usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + observation_cardinality_next_old: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + observation_cardinality_next_new: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for IncreaseObservationCardinalityNext { + const NAME: &'static str = "IncreaseObservationCardinalityNext"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Initialize { + pub sqrt_price_x96: substreams::scalar::BigInt, + pub tick: substreams::scalar::BigInt, + } + impl Initialize { + const TOPIC_ID: [u8; 32] = [ + 152u8, 99u8, 96u8, 54u8, 203u8, 102u8, 169u8, 193u8, 154u8, 55u8, 67u8, 94u8, 252u8, + 30u8, 144u8, 20u8, 33u8, 144u8, 33u8, 78u8, 138u8, 190u8, 184u8, 33u8, 189u8, 186u8, + 63u8, 41u8, 144u8, 221u8, 76u8, 149u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Int(24usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Initialize { + const NAME: &'static str = "Initialize"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Mint { + pub sender: Vec, + pub owner: Vec, + pub tick_lower: substreams::scalar::BigInt, + pub tick_upper: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + } + impl Mint { + const TOPIC_ID: [u8; 32] = [ + 122u8, 83u8, 8u8, 11u8, 164u8, 20u8, 21u8, 139u8, 231u8, 236u8, 105u8, 185u8, 135u8, + 181u8, 251u8, 125u8, 7u8, 222u8, 225u8, 1u8, 254u8, 133u8, 72u8, 143u8, 8u8, 83u8, + 174u8, 22u8, 35u8, 157u8, 11u8, 222u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + tick_lower: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[2usize].as_ref(), + ), + tick_upper: substreams::scalar::BigInt::from_signed_bytes_be( + log.topics[3usize].as_ref(), + ), + sender: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Mint { + const NAME: &'static str = "Mint"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetFeeProtocol { + pub fee_protocol0_old: substreams::scalar::BigInt, + pub fee_protocol1_old: substreams::scalar::BigInt, + pub fee_protocol0_new: substreams::scalar::BigInt, + pub fee_protocol1_new: substreams::scalar::BigInt, + } + impl SetFeeProtocol { + const TOPIC_ID: [u8; 32] = [ + 151u8, 61u8, 141u8, 146u8, 187u8, 41u8, 159u8, 74u8, 246u8, 206u8, 73u8, 181u8, 42u8, + 138u8, 219u8, 133u8, 174u8, 70u8, 185u8, 242u8, 20u8, 196u8, 196u8, 252u8, 6u8, 172u8, + 119u8, 64u8, 18u8, 55u8, 177u8, 51u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 128usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ethabi::ParamType::Uint(8usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + fee_protocol0_old: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol1_old: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol0_new: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + fee_protocol1_new: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for SetFeeProtocol { + const NAME: &'static str = "SetFeeProtocol"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Swap { + pub sender: Vec, + pub recipient: Vec, + pub amount0: substreams::scalar::BigInt, + pub amount1: substreams::scalar::BigInt, + pub sqrt_price_x96: substreams::scalar::BigInt, + pub liquidity: substreams::scalar::BigInt, + pub tick: substreams::scalar::BigInt, + } + impl Swap { + const TOPIC_ID: [u8; 32] = [ + 196u8, 32u8, 121u8, 249u8, 74u8, 99u8, 80u8, 215u8, 230u8, 35u8, 95u8, 41u8, 23u8, + 73u8, 36u8, 249u8, 40u8, 204u8, 42u8, 200u8, 24u8, 235u8, 100u8, 254u8, 216u8, 0u8, + 78u8, 17u8, 95u8, 188u8, 202u8, 103u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 160usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() == Self::TOPIC_ID; + } + pub fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Int(256usize), + ethabi::ParamType::Uint(160usize), + ethabi::ParamType::Uint(128usize), + ethabi::ParamType::Int(24usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + sender: ethabi::decode(&[ethabi::ParamType::Address], log.topics[1usize].as_ref()) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + recipient: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'recipient' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount0: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + amount1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + sqrt_price_x96: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + liquidity: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tick: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_int() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_signed_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Swap { + const NAME: &'static str = "Swap"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode(log: &substreams_ethereum::pb::eth::v2::Log) -> Result { + Self::decode(log) + } + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/lib.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/lib.rs new file mode 100644 index 0000000..5bbc53e --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/lib.rs @@ -0,0 +1,1366 @@ +mod abi; +mod pb; +use hex_literal::hex; +use pb::contract::v1 as contract; +use substreams::prelude::*; +use substreams::store; +use substreams::Hex; +use substreams_database_change::pb::database::DatabaseChanges; +use substreams_database_change::tables::Tables as DatabaseChangeTables; +use substreams_entity_change::pb::entity::EntityChanges; +use substreams_entity_change::tables::Tables as EntityChangesTables; +use substreams_ethereum::pb::eth::v2 as eth; +use substreams_ethereum::Event; + +#[allow(unused_imports)] +use num_traits::cast::ToPrimitive; +use std::str::FromStr; +use substreams::scalar::BigDecimal; + +substreams_ethereum::init!(); + +const FACTORY_TRACKED_CONTRACT: [u8; 20] = hex!("1f98431c8ad98523631ae4a59f267346ea31f984"); + +fn map_factory_events(blk: ð::Block, events: &mut contract::Events) { + events.factory_fee_amount_enableds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::factory_contract::events::FeeAmountEnabled::match_and_decode(log) { + return Some(contract::FactoryFeeAmountEnabled { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + fee: event.fee.to_u64(), + tick_spacing: Into::::into(event.tick_spacing).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + events.factory_owner_changeds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::factory_contract::events::OwnerChanged::match_and_decode(log) { + return Some(contract::FactoryOwnerChanged { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + new_owner: event.new_owner, + old_owner: event.old_owner, + }); + } + + None + }) + }) + .collect()); + events.factory_pool_createds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::factory_contract::events::PoolCreated::match_and_decode(log) { + return Some(contract::FactoryPoolCreated { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + fee: event.fee.to_u64(), + pool: event.pool, + tick_spacing: Into::::into(event.tick_spacing).to_i64().unwrap(), + token0: event.token0, + token1: event.token1, + }); + } + + None + }) + }) + .collect()); +} + +fn map_factory_calls(blk: ð::Block, calls: &mut contract::Calls) { + calls.factory_call_create_pools.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| call.address == FACTORY_TRACKED_CONTRACT && abi::factory_contract::functions::CreatePool::match_call(call)) + .filter_map(|call| { + match abi::factory_contract::functions::CreatePool::decode(call) { + Ok(decoded_call) => { + let output_pool = match abi::factory_contract::functions::CreatePool::output(&call.return_data) { + Ok(output_pool) => {output_pool} + Err(_) => Default::default(), + }; + + Some(contract::FactoryCreatePoolCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + fee: decoded_call.fee.to_u64(), + output_pool: output_pool, + token_a: decoded_call.token_a, + token_b: decoded_call.token_b, + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.factory_call_enable_fee_amounts.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| call.address == FACTORY_TRACKED_CONTRACT && abi::factory_contract::functions::EnableFeeAmount::match_call(call)) + .filter_map(|call| { + match abi::factory_contract::functions::EnableFeeAmount::decode(call) { + Ok(decoded_call) => { + Some(contract::FactoryEnableFeeAmountCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + fee: decoded_call.fee.to_u64(), + tick_spacing: Into::::into(decoded_call.tick_spacing).to_i64().unwrap(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.factory_call_set_owners.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| call.address == FACTORY_TRACKED_CONTRACT && abi::factory_contract::functions::SetOwner::match_call(call)) + .filter_map(|call| { + match abi::factory_contract::functions::SetOwner::decode(call) { + Ok(decoded_call) => { + Some(contract::FactorySetOwnerCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + u_owner: decoded_call.u_owner, + }) + }, + Err(_) => None, + } + }) + }) + .collect()); +} + +fn is_declared_dds_address(addr: &Vec, ordinal: u64, dds_store: &store::StoreGetInt64) -> bool { + // substreams::log::info!("Checking if address {} is declared dds address", Hex(addr).to_string()); + if dds_store.get_at(ordinal, Hex(addr).to_string()).is_some() { + return true; + } + return false; +} + +fn map_pool_events( + blk: ð::Block, + dds_store: &store::StoreGetInt64, + events: &mut contract::Events, +) { + + events.pool_burns.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Burn::match_and_decode(log) { + return Some(contract::PoolBurn { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount: event.amount.to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + owner: event.owner, + tick_lower: Into::::into(event.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(event.tick_upper).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_collects.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Collect::match_and_decode(log) { + return Some(contract::PoolCollect { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + owner: event.owner, + recipient: event.recipient, + tick_lower: Into::::into(event.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(event.tick_upper).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_collect_protocols.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::CollectProtocol::match_and_decode(log) { + return Some(contract::PoolCollectProtocol { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + recipient: event.recipient, + sender: event.sender, + }); + } + + None + }) + }) + .collect()); + + events.pool_flashes.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Flash::match_and_decode(log) { + return Some(contract::PoolFlash { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + paid0: event.paid0.to_string(), + paid1: event.paid1.to_string(), + recipient: event.recipient, + sender: event.sender, + }); + } + + None + }) + }) + .collect()); + + events.pool_increase_observation_cardinality_nexts.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::IncreaseObservationCardinalityNext::match_and_decode(log) { + return Some(contract::PoolIncreaseObservationCardinalityNext { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + observation_cardinality_next_new: event.observation_cardinality_next_new.to_u64(), + observation_cardinality_next_old: event.observation_cardinality_next_old.to_u64(), + }); + } + + None + }) + }) + .collect()); + + events.pool_initializes.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Initialize::match_and_decode(log) { + return Some(contract::PoolInitialize { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + sqrt_price_x96: event.sqrt_price_x96.to_string(), + tick: Into::::into(event.tick).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_mints.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Mint::match_and_decode(log) { + return Some(contract::PoolMint { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount: event.amount.to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + owner: event.owner, + sender: event.sender, + tick_lower: Into::::into(event.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(event.tick_upper).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); + + events.pool_set_fee_protocols.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::SetFeeProtocol::match_and_decode(log) { + return Some(contract::PoolSetFeeProtocol { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + fee_protocol0_new: event.fee_protocol0_new.to_u64(), + fee_protocol0_old: event.fee_protocol0_old.to_u64(), + fee_protocol1_new: event.fee_protocol1_new.to_u64(), + fee_protocol1_old: event.fee_protocol1_old.to_u64(), + }); + } + + None + }) + }) + .collect()); + + events.pool_swaps.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::pool_contract::events::Swap::match_and_decode(log) { + return Some(contract::PoolSwap { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + amount0: event.amount0.to_string(), + amount1: event.amount1.to_string(), + liquidity: event.liquidity.to_string(), + recipient: event.recipient, + sender: event.sender, + sqrt_price_x96: event.sqrt_price_x96.to_string(), + tick: Into::::into(event.tick).to_i64().unwrap(), + }); + } + + None + }) + }) + .collect()); +} +fn map_pool_calls( + blk: ð::Block, + dds_store: &store::StoreGetInt64, + calls: &mut contract::Calls, +) { + calls.pool_call_burns.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::Burn::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::Burn::decode(call) { + Ok(decoded_call) => { + let (output_amount0, output_amount1) = match abi::pool_contract::functions::Burn::output(&call.return_data) { + Ok((output_amount0, output_amount1)) => {(output_amount0, output_amount1)} + Err(_) => Default::default(), + }; + + Some(contract::PoolBurnCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + amount: decoded_call.amount.to_string(), + output_amount0: output_amount0.to_string(), + output_amount1: output_amount1.to_string(), + tick_lower: Into::::into(decoded_call.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(decoded_call.tick_upper).to_i64().unwrap(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_collects.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::Collect::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::Collect::decode(call) { + Ok(decoded_call) => { + let (output_amount0, output_amount1) = match abi::pool_contract::functions::Collect::output(&call.return_data) { + Ok((output_amount0, output_amount1)) => {(output_amount0, output_amount1)} + Err(_) => Default::default(), + }; + + Some(contract::PoolCollectCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + amount0_requested: decoded_call.amount0_requested.to_string(), + amount1_requested: decoded_call.amount1_requested.to_string(), + output_amount0: output_amount0.to_string(), + output_amount1: output_amount1.to_string(), + recipient: decoded_call.recipient, + tick_lower: Into::::into(decoded_call.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(decoded_call.tick_upper).to_i64().unwrap(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_collect_protocols.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::CollectProtocol::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::CollectProtocol::decode(call) { + Ok(decoded_call) => { + let (output_amount0, output_amount1) = match abi::pool_contract::functions::CollectProtocol::output(&call.return_data) { + Ok((output_amount0, output_amount1)) => {(output_amount0, output_amount1)} + Err(_) => Default::default(), + }; + + Some(contract::PoolCollectProtocolCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + amount0_requested: decoded_call.amount0_requested.to_string(), + amount1_requested: decoded_call.amount1_requested.to_string(), + output_amount0: output_amount0.to_string(), + output_amount1: output_amount1.to_string(), + recipient: decoded_call.recipient, + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_flashes.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::Flash::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::Flash::decode(call) { + Ok(decoded_call) => { + Some(contract::PoolFlashCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + amount0: decoded_call.amount0.to_string(), + amount1: decoded_call.amount1.to_string(), + data: decoded_call.data, + recipient: decoded_call.recipient, + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_increase_observation_cardinality_nexts.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::IncreaseObservationCardinalityNext::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::IncreaseObservationCardinalityNext::decode(call) { + Ok(decoded_call) => { + Some(contract::PoolIncreaseObservationCardinalityNextCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + observation_cardinality_next: decoded_call.observation_cardinality_next.to_u64(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_initializes.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::Initialize::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::Initialize::decode(call) { + Ok(decoded_call) => { + Some(contract::PoolInitializeCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + sqrt_price_x96: decoded_call.sqrt_price_x96.to_string(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_mints.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::Mint::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::Mint::decode(call) { + Ok(decoded_call) => { + let (output_amount0, output_amount1) = match abi::pool_contract::functions::Mint::output(&call.return_data) { + Ok((output_amount0, output_amount1)) => {(output_amount0, output_amount1)} + Err(_) => Default::default(), + }; + + Some(contract::PoolMintCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + amount: decoded_call.amount.to_string(), + data: decoded_call.data, + output_amount0: output_amount0.to_string(), + output_amount1: output_amount1.to_string(), + recipient: decoded_call.recipient, + tick_lower: Into::::into(decoded_call.tick_lower).to_i64().unwrap(), + tick_upper: Into::::into(decoded_call.tick_upper).to_i64().unwrap(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_set_fee_protocols.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::SetFeeProtocol::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::SetFeeProtocol::decode(call) { + Ok(decoded_call) => { + Some(contract::PoolSetFeeProtocolCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + fee_protocol0: decoded_call.fee_protocol0.to_u64(), + fee_protocol1: decoded_call.fee_protocol1.to_u64(), + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + calls.pool_call_swaps.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::pool_contract::functions::Swap::match_call(call)) + .filter_map(|call| { + match abi::pool_contract::functions::Swap::decode(call) { + Ok(decoded_call) => { + let (output_amount0, output_amount1) = match abi::pool_contract::functions::Swap::output(&call.return_data) { + Ok((output_amount0, output_amount1)) => {(output_amount0, output_amount1)} + Err(_) => Default::default(), + }; + + Some(contract::PoolSwapCall { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + amount_specified: decoded_call.amount_specified.to_string(), + data: decoded_call.data, + output_amount0: output_amount0.to_string(), + output_amount1: output_amount1.to_string(), + recipient: decoded_call.recipient, + sqrt_price_limit_x96: decoded_call.sqrt_price_limit_x96.to_string(), + zero_for_one: decoded_call.zero_for_one, + }) + }, + Err(_) => None, + } + }) + }) + .collect()); +} + + + +fn db_factory_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.factory_fee_amount_enableds.iter().for_each(|evt| { + tables + .create_row("factory_fee_amount_enabled", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("tick_spacing", evt.tick_spacing); + }); + events.factory_owner_changeds.iter().for_each(|evt| { + tables + .create_row("factory_owner_changed", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("old_owner", Hex(&evt.old_owner).to_string()); + }); + events.factory_pool_createds.iter().for_each(|evt| { + tables + .create_row("factory_pool_created", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("pool", Hex(&evt.pool).to_string()) + .set("tick_spacing", evt.tick_spacing) + .set("token0", Hex(&evt.token0).to_string()) + .set("token1", Hex(&evt.token1).to_string()); + }); +} +fn db_factory_calls_out(calls: &contract::Calls, tables: &mut DatabaseChangeTables) { + // Loop over all the abis calls to create table changes + calls.factory_call_create_pools.iter().for_each(|call| { + tables + .create_row("factory_call_create_pool", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("fee", call.fee) + .set("output_pool", Hex(&call.output_pool).to_string()) + .set("token_a", Hex(&call.token_a).to_string()) + .set("token_b", Hex(&call.token_b).to_string()); + }); + calls.factory_call_enable_fee_amounts.iter().for_each(|call| { + tables + .create_row("factory_call_enable_fee_amount", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("fee", call.fee) + .set("tick_spacing", call.tick_spacing); + }); + calls.factory_call_set_owners.iter().for_each(|call| { + tables + .create_row("factory_call_set_owner", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("u_owner", Hex(&call.u_owner).to_string()); + }); +} +fn db_pool_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.pool_burns.iter().for_each(|evt| { + tables + .create_row("pool_burn", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collects.iter().for_each(|evt| { + tables + .create_row("pool_collect", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collect_protocols.iter().for_each(|evt| { + tables + .create_row("pool_collect_protocol", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_flashes.iter().for_each(|evt| { + tables + .create_row("pool_flash", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("paid0", BigDecimal::from_str(&evt.paid0).unwrap()) + .set("paid1", BigDecimal::from_str(&evt.paid1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_increase_observation_cardinality_nexts.iter().for_each(|evt| { + tables + .create_row("pool_increase_observation_cardinality_next", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("observation_cardinality_next_new", evt.observation_cardinality_next_new) + .set("observation_cardinality_next_old", evt.observation_cardinality_next_old); + }); + events.pool_initializes.iter().for_each(|evt| { + tables + .create_row("pool_initialize", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); + events.pool_mints.iter().for_each(|evt| { + tables + .create_row("pool_mint", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_set_fee_protocols.iter().for_each(|evt| { + tables + .create_row("pool_set_fee_protocol", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("fee_protocol0_new", evt.fee_protocol0_new) + .set("fee_protocol0_old", evt.fee_protocol0_old) + .set("fee_protocol1_new", evt.fee_protocol1_new) + .set("fee_protocol1_old", evt.fee_protocol1_old); + }); + events.pool_swaps.iter().for_each(|evt| { + tables + .create_row("pool_swap", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("liquidity", BigDecimal::from_str(&evt.liquidity).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); +} +fn db_pool_calls_out(calls: &contract::Calls, tables: &mut DatabaseChangeTables) { + // Loop over all the abis calls to create table changes + calls.pool_call_burns.iter().for_each(|call| { + tables + .create_row("pool_call_burn", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount", BigDecimal::from_str(&call.amount).unwrap()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("tick_lower", call.tick_lower) + .set("tick_upper", call.tick_upper); + }); + calls.pool_call_collects.iter().for_each(|call| { + tables + .create_row("pool_call_collect", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount0_requested", BigDecimal::from_str(&call.amount0_requested).unwrap()) + .set("amount1_requested", BigDecimal::from_str(&call.amount1_requested).unwrap()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()) + .set("tick_lower", call.tick_lower) + .set("tick_upper", call.tick_upper); + }); + calls.pool_call_collect_protocols.iter().for_each(|call| { + tables + .create_row("pool_call_collect_protocol", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount0_requested", BigDecimal::from_str(&call.amount0_requested).unwrap()) + .set("amount1_requested", BigDecimal::from_str(&call.amount1_requested).unwrap()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()); + }); + calls.pool_call_flashes.iter().for_each(|call| { + tables + .create_row("pool_call_flash", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount0", BigDecimal::from_str(&call.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&call.amount1).unwrap()) + .set("data", Hex(&call.data).to_string()) + .set("recipient", Hex(&call.recipient).to_string()); + }); + calls.pool_call_increase_observation_cardinality_nexts.iter().for_each(|call| { + tables + .create_row("pool_call_increase_observation_cardinality_next", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("observation_cardinality_next", call.observation_cardinality_next); + }); + calls.pool_call_initializes.iter().for_each(|call| { + tables + .create_row("pool_call_initialize", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("sqrt_price_x96", BigDecimal::from_str(&call.sqrt_price_x96).unwrap()); + }); + calls.pool_call_mints.iter().for_each(|call| { + tables + .create_row("pool_call_mint", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount", BigDecimal::from_str(&call.amount).unwrap()) + .set("data", Hex(&call.data).to_string()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()) + .set("tick_lower", call.tick_lower) + .set("tick_upper", call.tick_upper); + }); + calls.pool_call_set_fee_protocols.iter().for_each(|call| { + tables + .create_row("pool_call_set_fee_protocol", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("fee_protocol0", call.fee_protocol0) + .set("fee_protocol1", call.fee_protocol1); + }); + calls.pool_call_swaps.iter().for_each(|call| { + tables + .create_row("pool_call_swap", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount_specified", BigDecimal::from_str(&call.amount_specified).unwrap()) + .set("data", Hex(&call.data).to_string()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()) + .set("sqrt_price_limit_x96", BigDecimal::from_str(&call.sqrt_price_limit_x96).unwrap()) + .set("zero_for_one", call.zero_for_one); + }); +} + + +fn graph_factory_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.factory_fee_amount_enableds.iter().for_each(|evt| { + tables + .create_row("factory_fee_amount_enabled", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("tick_spacing", evt.tick_spacing); + }); + events.factory_owner_changeds.iter().for_each(|evt| { + tables + .create_row("factory_owner_changed", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("old_owner", Hex(&evt.old_owner).to_string()); + }); + events.factory_pool_createds.iter().for_each(|evt| { + tables + .create_row("factory_pool_created", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("fee", evt.fee) + .set("pool", Hex(&evt.pool).to_string()) + .set("tick_spacing", evt.tick_spacing) + .set("token0", Hex(&evt.token0).to_string()) + .set("token1", Hex(&evt.token1).to_string()); + }); +} +fn graph_factory_calls_out(calls: &contract::Calls, tables: &mut EntityChangesTables) { + // Loop over all the abis calls to create table changes + calls.factory_call_create_pools.iter().for_each(|call| { + tables + .create_row("factory_call_create_pool", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("fee", call.fee) + .set("output_pool", Hex(&call.output_pool).to_string()) + .set("token_a", Hex(&call.token_a).to_string()) + .set("token_b", Hex(&call.token_b).to_string()); + }); + calls.factory_call_enable_fee_amounts.iter().for_each(|call| { + tables + .create_row("factory_call_enable_fee_amount", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("fee", call.fee) + .set("tick_spacing", call.tick_spacing); + }); + calls.factory_call_set_owners.iter().for_each(|call| { + tables + .create_row("factory_call_set_owner", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("u_owner", Hex(&call.u_owner).to_string()); + }); + } +fn graph_pool_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.pool_burns.iter().for_each(|evt| { + tables + .create_row("pool_burn", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collects.iter().for_each(|evt| { + tables + .create_row("pool_collect", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_collect_protocols.iter().for_each(|evt| { + tables + .create_row("pool_collect_protocol", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_flashes.iter().for_each(|evt| { + tables + .create_row("pool_flash", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("paid0", BigDecimal::from_str(&evt.paid0).unwrap()) + .set("paid1", BigDecimal::from_str(&evt.paid1).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.pool_increase_observation_cardinality_nexts.iter().for_each(|evt| { + tables + .create_row("pool_increase_observation_cardinality_next", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("observation_cardinality_next_new", evt.observation_cardinality_next_new) + .set("observation_cardinality_next_old", evt.observation_cardinality_next_old); + }); + events.pool_initializes.iter().for_each(|evt| { + tables + .create_row("pool_initialize", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); + events.pool_mints.iter().for_each(|evt| { + tables + .create_row("pool_mint", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("owner", Hex(&evt.owner).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("tick_lower", evt.tick_lower) + .set("tick_upper", evt.tick_upper); + }); + events.pool_set_fee_protocols.iter().for_each(|evt| { + tables + .create_row("pool_set_fee_protocol", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("fee_protocol0_new", evt.fee_protocol0_new) + .set("fee_protocol0_old", evt.fee_protocol0_old) + .set("fee_protocol1_new", evt.fee_protocol1_new) + .set("fee_protocol1_old", evt.fee_protocol1_old); + }); + events.pool_swaps.iter().for_each(|evt| { + tables + .create_row("pool_swap", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + .set("amount0", BigDecimal::from_str(&evt.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&evt.amount1).unwrap()) + .set("liquidity", BigDecimal::from_str(&evt.liquidity).unwrap()) + .set("recipient", Hex(&evt.recipient).to_string()) + .set("sender", Hex(&evt.sender).to_string()) + .set("sqrt_price_x96", BigDecimal::from_str(&evt.sqrt_price_x96).unwrap()) + .set("tick", evt.tick); + }); +} +fn graph_pool_calls_out(calls: &contract::Calls, tables: &mut EntityChangesTables) { + // Loop over all the abis calls to create table changes + calls.pool_call_burns.iter().for_each(|call| { + tables + .create_row("pool_call_burn", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount", BigDecimal::from_str(&call.amount).unwrap()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("tick_lower", call.tick_lower) + .set("tick_upper", call.tick_upper); + }); + calls.pool_call_collects.iter().for_each(|call| { + tables + .create_row("pool_call_collect", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount0_requested", BigDecimal::from_str(&call.amount0_requested).unwrap()) + .set("amount1_requested", BigDecimal::from_str(&call.amount1_requested).unwrap()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()) + .set("tick_lower", call.tick_lower) + .set("tick_upper", call.tick_upper); + }); + calls.pool_call_collect_protocols.iter().for_each(|call| { + tables + .create_row("pool_call_collect_protocol", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount0_requested", BigDecimal::from_str(&call.amount0_requested).unwrap()) + .set("amount1_requested", BigDecimal::from_str(&call.amount1_requested).unwrap()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()); + }); + calls.pool_call_flashes.iter().for_each(|call| { + tables + .create_row("pool_call_flash", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount0", BigDecimal::from_str(&call.amount0).unwrap()) + .set("amount1", BigDecimal::from_str(&call.amount1).unwrap()) + .set("data", Hex(&call.data).to_string()) + .set("recipient", Hex(&call.recipient).to_string()); + }); + calls.pool_call_increase_observation_cardinality_nexts.iter().for_each(|call| { + tables + .create_row("pool_call_increase_observation_cardinality_next", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("observation_cardinality_next", call.observation_cardinality_next); + }); + calls.pool_call_initializes.iter().for_each(|call| { + tables + .create_row("pool_call_initialize", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("sqrt_price_x96", BigDecimal::from_str(&call.sqrt_price_x96).unwrap()); + }); + calls.pool_call_mints.iter().for_each(|call| { + tables + .create_row("pool_call_mint", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount", BigDecimal::from_str(&call.amount).unwrap()) + .set("data", Hex(&call.data).to_string()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()) + .set("tick_lower", call.tick_lower) + .set("tick_upper", call.tick_upper); + }); + calls.pool_call_set_fee_protocols.iter().for_each(|call| { + tables + .create_row("pool_call_set_fee_protocol", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("fee_protocol0", call.fee_protocol0) + .set("fee_protocol1", call.fee_protocol1); + }); + calls.pool_call_swaps.iter().for_each(|call| { + tables + .create_row("pool_call_swap", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + .set("amount_specified", BigDecimal::from_str(&call.amount_specified).unwrap()) + .set("data", Hex(&call.data).to_string()) + .set("output_amount0", BigDecimal::from_str(&call.output_amount0).unwrap()) + .set("output_amount1", BigDecimal::from_str(&call.output_amount1).unwrap()) + .set("recipient", Hex(&call.recipient).to_string()) + .set("sqrt_price_limit_x96", BigDecimal::from_str(&call.sqrt_price_limit_x96).unwrap()) + .set("zero_for_one", call.zero_for_one); + }); + } +#[substreams::handlers::store] +fn store_factory_pool_created(blk: eth::Block, store: StoreSetInt64) { + for rcpt in blk.receipts() { + for log in rcpt + .receipt + .logs + .iter() + .filter(|log| log.address == FACTORY_TRACKED_CONTRACT) + { + if let Some(event) = abi::factory_contract::events::PoolCreated::match_and_decode(log) { + store.set(log.ordinal, Hex(event.pool).to_string(), &1); + } + } + } +} + +#[substreams::handlers::map] +fn map_events( + blk: eth::Block, + store_pool: StoreGetInt64, +) -> Result { + let mut events = contract::Events::default(); + map_factory_events(&blk, &mut events); + map_pool_events(&blk, &store_pool, &mut events); + Ok(events) +} +#[substreams::handlers::map] +fn map_calls( + blk: eth::Block, + store_pool: StoreGetInt64, +) -> Result { + let mut calls = contract::Calls::default(); + map_factory_calls(&blk, &mut calls); + map_pool_calls(&blk, &store_pool, &mut calls); + Ok(calls) +} + +#[substreams::handlers::map] +fn db_out(events: contract::Events, calls: contract::Calls) -> Result { + // Initialize Database Changes container + let mut tables = DatabaseChangeTables::new(); + db_factory_out(&events, &mut tables); + db_factory_calls_out(&calls, &mut tables); + db_pool_out(&events, &mut tables); + db_pool_calls_out(&calls, &mut tables); + Ok(tables.to_database_changes()) +} + +#[substreams::handlers::map] +fn graph_out(events: contract::Events, calls: contract::Calls) -> Result { + // Initialize Database Changes container + let mut tables = EntityChangesTables::new(); + graph_factory_out(&events, &mut tables); + graph_factory_calls_out(&calls, &mut tables); + graph_pool_out(&events, &mut tables); + graph_pool_calls_out(&calls, &mut tables); + Ok(tables.to_entity_changes()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/pb/contract.v1.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/pb/contract.v1.rs new file mode 100644 index 0000000..13bc61f --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/pb/contract.v1.rs @@ -0,0 +1,280 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Events { + #[prost(message, repeated, tag="1")] + pub factory_fee_amount_enableds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub factory_owner_changeds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub factory_pool_createds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub pool_burns: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub pool_collects: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="6")] + pub pool_collect_protocols: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="7")] + pub pool_flashes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="8")] + pub pool_increase_observation_cardinality_nexts: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="9")] + pub pool_initializes: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="10")] + pub pool_mints: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="11")] + pub pool_set_fee_protocols: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="12")] + pub pool_swaps: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FactoryFeeAmountEnabled { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(uint64, tag="5")] + pub fee: u64, + #[prost(int64, tag="6")] + pub tick_spacing: i64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FactoryOwnerChanged { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub old_owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub new_owner: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct FactoryPoolCreated { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub token0: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub token1: ::prost::alloc::vec::Vec, + #[prost(uint64, tag="7")] + pub fee: u64, + #[prost(int64, tag="8")] + pub tick_spacing: i64, + #[prost(bytes="vec", tag="9")] + pub pool: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolBurn { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(int64, tag="6")] + pub tick_lower: i64, + #[prost(int64, tag="7")] + pub tick_upper: i64, + #[prost(string, tag="8")] + pub amount: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolCollect { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(int64, tag="7")] + pub tick_lower: i64, + #[prost(int64, tag="8")] + pub tick_upper: i64, + #[prost(string, tag="9")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolCollectProtocol { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolFlash { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub amount1: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub paid0: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub paid1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolIncreaseObservationCardinalityNext { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(uint64, tag="5")] + pub observation_cardinality_next_old: u64, + #[prost(uint64, tag="6")] + pub observation_cardinality_next_new: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolInitialize { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(string, tag="5")] + pub sqrt_price_x96: ::prost::alloc::string::String, + #[prost(int64, tag="6")] + pub tick: i64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolMint { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(int64, tag="7")] + pub tick_lower: i64, + #[prost(int64, tag="8")] + pub tick_upper: i64, + #[prost(string, tag="9")] + pub amount: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="11")] + pub amount1: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolSetFeeProtocol { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(uint64, tag="5")] + pub fee_protocol0_old: u64, + #[prost(uint64, tag="6")] + pub fee_protocol1_old: u64, + #[prost(uint64, tag="7")] + pub fee_protocol0_new: u64, + #[prost(uint64, tag="8")] + pub fee_protocol1_new: u64, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PoolSwap { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub sender: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub recipient: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub amount0: ::prost::alloc::string::String, + #[prost(string, tag="8")] + pub amount1: ::prost::alloc::string::String, + #[prost(string, tag="9")] + pub sqrt_price_x96: ::prost::alloc::string::String, + #[prost(string, tag="10")] + pub liquidity: ::prost::alloc::string::String, + #[prost(int64, tag="11")] + pub tick: i64, +} +// @@protoc_insertion_point(module) diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/pb/mod.rs b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/pb/mod.rs new file mode 100644 index 0000000..611ea83 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/src/pb/mod.rs @@ -0,0 +1,8 @@ +// @generated +pub mod contract { + // @@protoc_insertion_point(attribute:contract.v1) + pub mod v1 { + include!("contract.v1.rs"); + // @@protoc_insertion_point(contract.v1) + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/subgraph.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/subgraph.yaml new file mode 100644 index 0000000..dd54391 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/subgraph.yaml @@ -0,0 +1,17 @@ +specVersion: 0.0.6 +description: substreams_init_test substreams based subgraph +repository: # fill in with git remote url +schema: + file: ./schema.graphql + +dataSources: + - kind: substreams + name: + network: mainnet + source: + package: + moduleName: graph_out + file: substreams_init_test-v0.1.0.spkg + mapping: + kind: substreams/graph-entities + apiVersion: 0.0.5 diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.clickhouse.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.clickhouse.yaml new file mode 100644 index 0000000..7e5396e --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.clickhouse.yaml @@ -0,0 +1,79 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: map_calls + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Calls + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.clickhouse.sql" + engine: clickhouse + postgraphile_frontend: + enabled: false + rest_frontend: + enabled: false diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.sql.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.sql.yaml new file mode 100644 index 0000000..64ce37e --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.sql.yaml @@ -0,0 +1,77 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: map_calls + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Calls + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + engine: postgres + postgraphile_frontend: + enabled: true diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.subgraph.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.subgraph.yaml new file mode 100644 index 0000000..95d900d --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.subgraph.yaml @@ -0,0 +1,76 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: map_calls + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Calls + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: graph_out + type: sf.substreams.sink.subgraph.v1.Service + config: + schema: "./schema.graphql" + subgraph_yaml: "./subgraph.yaml" + postgres_direct_protocol_access: true diff --git a/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.yaml b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.yaml new file mode 100644 index 0000000..b9b272b --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/dynamic_datasource_with_calls/substreams.yaml @@ -0,0 +1,68 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: store_factory_pool_created + kind: store + initialBlock: 12369621 + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block + + - name: map_events + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Events + + - name: map_calls + kind: map + initialBlock: 12369621 + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_factory_pool_created + output: + type: proto:contract.v1.Calls + + - name: db_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 12369621 + inputs: + - map: map_events + - map: map_calls + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/abi/bayc_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/abi/bayc_contract.abi.json new file mode 100644 index 0000000..6913809 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/abi/bayc_contract.abi.json @@ -0,0 +1,670 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "uint256", + "name": "maxNftSupply", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "saleStart", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "BAYC_PROVENANCE", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_APES", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REVEAL_TIMESTAMP", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "apePrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "emergencySetStartingIndexBlock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "flipSaleState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxApePurchase", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "numberOfTokens", + "type": "uint256" + } + ], + "name": "mintApe", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reserveApes", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "saleIsActive", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "baseURI", + "type": "string" + } + ], + "name": "setBaseURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "provenanceHash", + "type": "string" + } + ], + "name": "setProvenanceHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "revealTimeStamp", + "type": "uint256" + } + ], + "name": "setRevealTimestamp", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "setStartingIndex", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startingIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "startingIndexBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/abi/moonbird_contract.abi.json b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/abi/moonbird_contract.abi.json new file mode 100644 index 0000000..183245f --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/abi/moonbird_contract.abi.json @@ -0,0 +1,1467 @@ +[ + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "symbol", + "type": "string" + }, + { + "internalType": "contract IERC721", + "name": "_proof", + "type": "address" + }, + { + "internalType": "address payable", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "address payable", + "name": "royaltyReceiver", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ApprovalCallerNotOwnerNorApproved", + "type": "error" + }, + { + "inputs": [], + "name": "ApprovalQueryForNonexistentToken", + "type": "error" + }, + { + "inputs": [], + "name": "ApprovalToCurrentOwner", + "type": "error" + }, + { + "inputs": [], + "name": "ApproveToCaller", + "type": "error" + }, + { + "inputs": [], + "name": "BalanceQueryForZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "MintToZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "MintZeroQuantity", + "type": "error" + }, + { + "inputs": [], + "name": "OwnerQueryForNonexistentToken", + "type": "error" + }, + { + "inputs": [], + "name": "TransferCallerNotOwnerNorApproved", + "type": "error" + }, + { + "inputs": [], + "name": "TransferFromIncorrectOwner", + "type": "error" + }, + { + "inputs": [], + "name": "TransferToNonERC721ReceiverImplementer", + "type": "error" + }, + { + "inputs": [], + "name": "TransferToZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "URIQueryForNonexistentToken", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Expelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Nested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "buyer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Refund", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "numPurchased", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Revenue", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Unnested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXPULSION_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "addSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + } + ], + "name": "alreadyMinted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "baseTokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "beneficiary", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "n", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "cost", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "expelFromNest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "proofTokenIds", + "type": "uint256[]" + } + ], + "name": "mintPROOF", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "nonce", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sig", + "type": "bytes" + } + ], + "name": "mintPublic", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "n", + "type": "uint256" + } + ], + "name": "mintUnclaimed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nestingOpen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "nestingPeriod", + "outputs": [ + { + "internalType": "bool", + "name": "nesting", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "current", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "price", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proof", + "outputs": [ + { + "internalType": "contract IERC721", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "proofClaimsRemaining", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proofMintingOpen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proofPoolRemaining", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "n", + "type": "uint256" + } + ], + "name": "purchaseFreeOfCharge", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + } + ], + "name": "removeSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renderingContract", + "outputs": [ + { + "internalType": "contract ITokenURIGenerator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_salePrice", + "type": "uint256" + } + ], + "name": "royaltyInfo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferWhileNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sellerConfig", + "outputs": [ + { + "internalType": "uint256", + "name": "totalInventory", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPerAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPerTx", + "type": "uint256" + }, + { + "internalType": "uint248", + "name": "freeQuota", + "type": "uint248" + }, + { + "internalType": "bool", + "name": "reserveFreeQuota", + "type": "bool" + }, + { + "internalType": "bool", + "name": "lockFreeQuota", + "type": "bool" + }, + { + "internalType": "bool", + "name": "lockTotalInventory", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_baseTokenURI", + "type": "string" + } + ], + "name": "setBaseTokenURI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "_beneficiary", + "type": "address" + } + ], + "name": "setBeneficiary", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "open", + "type": "bool" + } + ], + "name": "setNestingOpen", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "open", + "type": "bool" + } + ], + "name": "setPROOFMintingOpen", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_price", + "type": "uint256" + } + ], + "name": "setPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ITokenURIGenerator", + "name": "_contract", + "type": "address" + } + ], + "name": "setRenderingContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint96", + "name": "feeBasisPoints", + "type": "uint96" + } + ], + "name": "setRoyaltyInfo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "totalInventory", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPerAddress", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPerTx", + "type": "uint256" + }, + { + "internalType": "uint248", + "name": "freeQuota", + "type": "uint248" + }, + { + "internalType": "bool", + "name": "reserveFreeQuota", + "type": "bool" + }, + { + "internalType": "bool", + "name": "lockFreeQuota", + "type": "bool" + }, + { + "internalType": "bool", + "name": "lockTotalInventory", + "type": "bool" + } + ], + "internalType": "struct Seller.SellerConfig", + "name": "config", + "type": "tuple" + } + ], + "name": "setSellerConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "tokenIds", + "type": "uint256[]" + } + ], + "name": "toggleNesting", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "usedMessages", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/build.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/build.rs new file mode 100644 index 0000000..e27d366 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/build.rs @@ -0,0 +1,33 @@ +use anyhow::{Ok, Result}; +use regex::Regex; +use substreams_ethereum::Abigen; +use std::fs; + +fn main() -> Result<(), anyhow::Error> { + let file_names = [ + "abi/moonbird_contract.abi.json", + "abi/bayc_contract.abi.json", + ]; + let file_output_names = [ + "src/abi/moonbird_contract.rs", + "src/abi/bayc_contract.rs", + ]; + + let mut i = 0; + for f in file_names { + let contents = fs::read_to_string(f) + .expect("Should have been able to read the file"); + + // sanitize fields and attributes starting with an underscore + let regex = Regex::new(r#"("\w+"\s?:\s?")_(\w+")"#).unwrap(); + let sanitized_abi_file = regex.replace_all(contents.as_str(), "${1}u_${2}"); + + Abigen::from_bytes("Contract", sanitized_abi_file.as_bytes())? + .generate()? + .write_to_file(file_output_names[i])?; + + i = i+1; + } + + Ok(()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/proto/contract.proto b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/proto/contract.proto new file mode 100755 index 0000000..a8cfa9a --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/proto/contract.proto @@ -0,0 +1,193 @@ +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; + +package contract.v1; + +message Events { + repeated moonbird_Approval moonbird_approvals = 1; + repeated moonbird_ApprovalForAll moonbird_approval_for_alls = 2; + repeated moonbird_Expelled moonbird_expelleds = 3; + repeated moonbird_Nested moonbird_nesteds = 4; + repeated moonbird_OwnershipTransferred moonbird_ownership_transferreds = 5; + repeated moonbird_Paused moonbird_pauseds = 6; + repeated moonbird_Refund moonbird_refunds = 7; + repeated moonbird_Revenue moonbird_revenues = 8; + repeated moonbird_RoleAdminChanged moonbird_role_admin_changeds = 9; + repeated moonbird_RoleGranted moonbird_role_granteds = 10; + repeated moonbird_RoleRevoked moonbird_role_revokeds = 11; + repeated moonbird_Transfer moonbird_transfers = 12; + repeated moonbird_Unnested moonbird_unnesteds = 13; + repeated moonbird_Unpaused moonbird_unpauseds = 14; + repeated bayc_Approval bayc_approvals = 15; + repeated bayc_ApprovalForAll bayc_approval_for_alls = 16; + repeated bayc_OwnershipTransferred bayc_ownership_transferreds = 17; + repeated bayc_Transfer bayc_transfers = 18; +} + +message moonbird_Approval { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes owner = 5; + bytes approved = 6; + string token_id = 7; +} + +message moonbird_ApprovalForAll { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes owner = 5; + bytes operator = 6; + bool approved = 7; +} + +message moonbird_Expelled { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string token_id = 5; +} + +message moonbird_Nested { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string token_id = 5; +} + +message moonbird_OwnershipTransferred { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes previous_owner = 5; + bytes new_owner = 6; +} + +message moonbird_Paused { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes account = 5; +} + +message moonbird_Refund { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes buyer = 5; + string amount = 6; +} + +message moonbird_Revenue { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes beneficiary = 5; + string num_purchased = 6; + string amount = 7; +} + +message moonbird_RoleAdminChanged { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes role = 5; + bytes previous_admin_role = 6; + bytes new_admin_role = 7; +} + +message moonbird_RoleGranted { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes role = 5; + bytes account = 6; + bytes sender = 7; +} + +message moonbird_RoleRevoked { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes role = 5; + bytes account = 6; + bytes sender = 7; +} + +message moonbird_Transfer { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes from = 5; + bytes to = 6; + string token_id = 7; +} + +message moonbird_Unnested { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + string token_id = 5; +} + +message moonbird_Unpaused { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes account = 5; +} + +message bayc_Approval { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes owner = 5; + bytes approved = 6; + string token_id = 7; +} + +message bayc_ApprovalForAll { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes owner = 5; + bytes operator = 6; + bool approved = 7; +} + +message bayc_OwnershipTransferred { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes previous_owner = 5; + bytes new_owner = 6; +} + +message bayc_Transfer { + string evt_tx_hash = 1; + uint32 evt_index = 2; + google.protobuf.Timestamp evt_block_time = 3; + uint64 evt_block_number = 4; + bytes from = 5; + bytes to = 6; + string token_id = 7; +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.clickhouse.sql b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.clickhouse.sql new file mode 100755 index 0000000..c8547e2 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.clickhouse.sql @@ -0,0 +1,150 @@ +CREATE TABLE IF NOT EXISTS moonbird_approval ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "approved" VARCHAR(40), + "owner" VARCHAR(40), + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_approval_for_all ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "approved" BOOL, + "operator" VARCHAR(40), + "owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_expelled ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_nested ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_ownership_transferred ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "new_owner" VARCHAR(40), + "previous_owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_paused ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "account" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_refund ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "amount" UInt256, + "buyer" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_revenue ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "amount" UInt256, + "beneficiary" VARCHAR(40), + "num_purchased" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_role_admin_changed ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "new_admin_role" TEXT, + "previous_admin_role" TEXT, + "role" TEXT +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_role_granted ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "account" VARCHAR(40), + "role" TEXT, + "sender" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_role_revoked ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "account" VARCHAR(40), + "role" TEXT, + "sender" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_transfer ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "from" VARCHAR(40), + "to" VARCHAR(40), + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_unnested ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS moonbird_unpaused ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "account" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_approval ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "approved" VARCHAR(40), + "owner" VARCHAR(40), + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_approval_for_all ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "approved" BOOL, + "operator" VARCHAR(40), + "owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_ownership_transferred ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "new_owner" VARCHAR(40), + "previous_owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_transfer ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "from" VARCHAR(40), + "to" VARCHAR(40), + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); + diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.graphql b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.graphql new file mode 100755 index 0000000..e93b978 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.graphql @@ -0,0 +1,167 @@ +type moonbird_approval @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + approved: String! + owner: String! + token_id: BigDecimal! +} +type moonbird_approval_for_all @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + approved: Boolean! + operator: String! + owner: String! +} +type moonbird_expelled @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + token_id: BigDecimal! +} +type moonbird_nested @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + token_id: BigDecimal! +} +type moonbird_ownership_transferred @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + new_owner: String! + previous_owner: String! +} +type moonbird_paused @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + account: String! +} +type moonbird_refund @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + amount: BigDecimal! + buyer: String! +} +type moonbird_revenue @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + amount: BigDecimal! + beneficiary: String! + num_purchased: BigDecimal! +} +type moonbird_role_admin_changed @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + new_admin_role: String! + previous_admin_role: String! + role: String! +} +type moonbird_role_granted @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + account: String! + role: String! + sender: String! +} +type moonbird_role_revoked @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + account: String! + role: String! + sender: String! +} +type moonbird_transfer @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + from: String! + to: String! + token_id: BigDecimal! +} +type moonbird_unnested @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + token_id: BigDecimal! +} +type moonbird_unpaused @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + account: String! +} +type bayc_approval @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + approved: String! + owner: String! + token_id: BigDecimal! +} +type bayc_approval_for_all @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + approved: Boolean! + operator: String! + owner: String! +} +type bayc_ownership_transferred @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + new_owner: String! + previous_owner: String! +} +type bayc_transfer @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + from: String! + to: String! + token_id: BigDecimal! +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.sql b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.sql new file mode 100755 index 0000000..646085e --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/schema.sql @@ -0,0 +1,168 @@ +CREATE TABLE IF NOT EXISTS moonbird_approval ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "approved" VARCHAR(40), + "owner" VARCHAR(40), + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_approval_for_all ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "approved" BOOL, + "operator" VARCHAR(40), + "owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_expelled ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_nested ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_ownership_transferred ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "new_owner" VARCHAR(40), + "previous_owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_paused ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "account" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_refund ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "amount" DECIMAL, + "buyer" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_revenue ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "amount" DECIMAL, + "beneficiary" VARCHAR(40), + "num_purchased" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_role_admin_changed ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "new_admin_role" TEXT, + "previous_admin_role" TEXT, + "role" TEXT, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_role_granted ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "account" VARCHAR(40), + "role" TEXT, + "sender" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_role_revoked ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "account" VARCHAR(40), + "role" TEXT, + "sender" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_transfer ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "from" VARCHAR(40), + "to" VARCHAR(40), + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_unnested ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS moonbird_unpaused ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "account" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_approval ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "approved" VARCHAR(40), + "owner" VARCHAR(40), + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_approval_for_all ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "approved" BOOL, + "operator" VARCHAR(40), + "owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_ownership_transferred ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "new_owner" VARCHAR(40), + "previous_owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_transfer ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "from" VARCHAR(40), + "to" VARCHAR(40), + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); + diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/bayc_contract.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/bayc_contract.rs new file mode 100644 index 0000000..47a94df --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/bayc_contract.rs @@ -0,0 +1,3619 @@ + const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; + /// Contract's functions. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct BaycProvenance {} + impl BaycProvenance { + const METHOD_ID: [u8; 4] = [96u8, 126u8, 32u8, 227u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BaycProvenance { + const NAME: &'static str = "BAYC_PROVENANCE"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for BaycProvenance { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MaxApes {} + impl MaxApes { + const METHOD_ID: [u8; 4] = [187u8, 138u8, 22u8, 189u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for MaxApes { + const NAME: &'static str = "MAX_APES"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for MaxApes { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RevealTimestamp {} + impl RevealTimestamp { + const METHOD_ID: [u8; 4] = [24u8, 226u8, 10u8, 56u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for RevealTimestamp { + const NAME: &'static str = "REVEAL_TIMESTAMP"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for RevealTimestamp { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ApePrice {} + impl ApePrice { + const METHOD_ID: [u8; 4] = [122u8, 63u8, 69u8, 30u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ApePrice { + const NAME: &'static str = "apePrice"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for ApePrice { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Approve { + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Approve { + const METHOD_ID: [u8; 4] = [9u8, 94u8, 167u8, 179u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Approve { + const NAME: &'static str = "approve"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BalanceOf { + pub owner: Vec, + } + impl BalanceOf { + const METHOD_ID: [u8; 4] = [112u8, 160u8, 130u8, 49u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.owner))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BalanceOf { + const NAME: &'static str = "balanceOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for BalanceOf { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BaseUri {} + impl BaseUri { + const METHOD_ID: [u8; 4] = [108u8, 3u8, 96u8, 235u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BaseUri { + const NAME: &'static str = "baseURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for BaseUri { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct EmergencySetStartingIndexBlock {} + impl EmergencySetStartingIndexBlock { + const METHOD_ID: [u8; 4] = [125u8, 23u8, 252u8, 190u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for EmergencySetStartingIndexBlock { + const NAME: &'static str = "emergencySetStartingIndexBlock"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FlipSaleState {} + impl FlipSaleState { + const METHOD_ID: [u8; 4] = [52u8, 145u8, 141u8, 253u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for FlipSaleState { + const NAME: &'static str = "flipSaleState"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetApproved { + pub token_id: substreams::scalar::BigInt, + } + impl GetApproved { + const METHOD_ID: [u8; 4] = [8u8, 24u8, 18u8, 252u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetApproved { + const NAME: &'static str = "getApproved"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for GetApproved { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IsApprovedForAll { + pub owner: Vec, + pub operator: Vec, + } + impl IsApprovedForAll { + const METHOD_ID: [u8; 4] = [233u8, 133u8, 233u8, 197u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.operator), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for IsApprovedForAll { + const NAME: &'static str = "isApprovedForAll"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for IsApprovedForAll { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MaxApePurchase {} + impl MaxApePurchase { + const METHOD_ID: [u8; 4] = [87u8, 29u8, 255u8, 59u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for MaxApePurchase { + const NAME: &'static str = "maxApePurchase"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for MaxApePurchase { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MintApe { + pub number_of_tokens: substreams::scalar::BigInt, + } + impl MintApe { + const METHOD_ID: [u8; 4] = [167u8, 35u8, 83u8, 62u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + number_of_tokens: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.number_of_tokens.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for MintApe { + const NAME: &'static str = "mintApe"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Name {} + impl Name { + const METHOD_ID: [u8; 4] = [6u8, 253u8, 222u8, 3u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Name { + const NAME: &'static str = "name"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Name { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Owner {} + impl Owner { + const METHOD_ID: [u8; 4] = [141u8, 165u8, 203u8, 91u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Owner { + const NAME: &'static str = "owner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Owner { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnerOf { + pub token_id: substreams::scalar::BigInt, + } + impl OwnerOf { + const METHOD_ID: [u8; 4] = [99u8, 82u8, 33u8, 30u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for OwnerOf { + const NAME: &'static str = "ownerOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for OwnerOf { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RenounceOwnership {} + impl RenounceOwnership { + const METHOD_ID: [u8; 4] = [113u8, 80u8, 24u8, 166u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for RenounceOwnership { + const NAME: &'static str = "renounceOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ReserveApes {} + impl ReserveApes { + const METHOD_ID: [u8; 4] = [176u8, 246u8, 116u8, 39u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for ReserveApes { + const NAME: &'static str = "reserveApes"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferFrom1 { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl SafeTransferFrom1 { + const METHOD_ID: [u8; 4] = [66u8, 132u8, 46u8, 14u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferFrom1 { + const NAME: &'static str = "safeTransferFrom1"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferFrom2 { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + pub u_data: Vec, + } + impl SafeTransferFrom2 { + const METHOD_ID: [u8; 4] = [184u8, 141u8, 79u8, 222u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + u_data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Bytes(self.u_data.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferFrom2 { + const NAME: &'static str = "safeTransferFrom2"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SaleIsActive {} + impl SaleIsActive { + const METHOD_ID: [u8; 4] = [235u8, 141u8, 36u8, 68u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SaleIsActive { + const NAME: &'static str = "saleIsActive"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SaleIsActive { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetApprovalForAll { + pub operator: Vec, + pub approved: bool, + } + impl SetApprovalForAll { + const METHOD_ID: [u8; 4] = [162u8, 44u8, 180u8, 101u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Bool], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.operator), + ), + ethabi::Token::Bool(self.approved.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetApprovalForAll { + const NAME: &'static str = "setApprovalForAll"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetBaseUri { + pub base_uri: String, + } + impl SetBaseUri { + const METHOD_ID: [u8; 4] = [85u8, 248u8, 4u8, 179u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + base_uri: values + .pop() + .expect(INTERNAL_ERR) + .into_string() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::String(self.base_uri.clone())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetBaseUri { + const NAME: &'static str = "setBaseURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetProvenanceHash { + pub provenance_hash: String, + } + impl SetProvenanceHash { + const METHOD_ID: [u8; 4] = [16u8, 150u8, 149u8, 35u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + provenance_hash: values + .pop() + .expect(INTERNAL_ERR) + .into_string() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::String(self.provenance_hash.clone())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetProvenanceHash { + const NAME: &'static str = "setProvenanceHash"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetRevealTimestamp { + pub reveal_time_stamp: substreams::scalar::BigInt, + } + impl SetRevealTimestamp { + const METHOD_ID: [u8; 4] = [1u8, 138u8, 44u8, 55u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + reveal_time_stamp: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.reveal_time_stamp.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetRevealTimestamp { + const NAME: &'static str = "setRevealTimestamp"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetStartingIndex {} + impl SetStartingIndex { + const METHOD_ID: [u8; 4] = [233u8, 134u8, 101u8, 80u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetStartingIndex { + const NAME: &'static str = "setStartingIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct StartingIndex {} + impl StartingIndex { + const METHOD_ID: [u8; 4] = [203u8, 119u8, 77u8, 71u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for StartingIndex { + const NAME: &'static str = "startingIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for StartingIndex { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct StartingIndexBlock {} + impl StartingIndexBlock { + const METHOD_ID: [u8; 4] = [227u8, 109u8, 100u8, 152u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for StartingIndexBlock { + const NAME: &'static str = "startingIndexBlock"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for StartingIndexBlock { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SupportsInterface { + pub interface_id: [u8; 4usize], + } + impl SupportsInterface { + const METHOD_ID: [u8; 4] = [1u8, 255u8, 201u8, 167u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(4usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + interface_id: { + let mut result = [0u8; 4]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.interface_id.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SupportsInterface { + const NAME: &'static str = "supportsInterface"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SupportsInterface { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Symbol {} + impl Symbol { + const METHOD_ID: [u8; 4] = [149u8, 216u8, 155u8, 65u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Symbol { + const NAME: &'static str = "symbol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Symbol { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenByIndex { + pub index: substreams::scalar::BigInt, + } + impl TokenByIndex { + const METHOD_ID: [u8; 4] = [79u8, 108u8, 204u8, 231u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + index: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.index.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenByIndex { + const NAME: &'static str = "tokenByIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TokenByIndex { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenOfOwnerByIndex { + pub owner: Vec, + pub index: substreams::scalar::BigInt, + } + impl TokenOfOwnerByIndex { + const METHOD_ID: [u8; 4] = [47u8, 116u8, 92u8, 89u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + index: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.index.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenOfOwnerByIndex { + const NAME: &'static str = "tokenOfOwnerByIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TokenOfOwnerByIndex { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenUri { + pub token_id: substreams::scalar::BigInt, + } + impl TokenUri { + const METHOD_ID: [u8; 4] = [200u8, 123u8, 86u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenUri { + const NAME: &'static str = "tokenURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TokenUri { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TotalSupply {} + impl TotalSupply { + const METHOD_ID: [u8; 4] = [24u8, 22u8, 13u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TotalSupply { + const NAME: &'static str = "totalSupply"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TotalSupply { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferFrom { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl TransferFrom { + const METHOD_ID: [u8; 4] = [35u8, 184u8, 114u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferFrom { + const NAME: &'static str = "transferFrom"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferOwnership { + pub new_owner: Vec, + } + impl TransferOwnership { + const METHOD_ID: [u8; 4] = [242u8, 253u8, 227u8, 139u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + new_owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.new_owner), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferOwnership { + const NAME: &'static str = "transferOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Withdraw {} + impl Withdraw { + const METHOD_ID: [u8; 4] = [60u8, 207u8, 214u8, 11u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Withdraw { + const NAME: &'static str = "withdraw"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + } + /// Contract's events. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Approval { + pub owner: Vec, + pub approved: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Approval { + const TOPIC_ID: [u8; 32] = [ + 140u8, + 91u8, + 225u8, + 229u8, + 235u8, + 236u8, + 125u8, + 91u8, + 209u8, + 79u8, + 113u8, + 66u8, + 125u8, + 30u8, + 132u8, + 243u8, + 221u8, + 3u8, + 20u8, + 192u8, + 247u8, + 178u8, + 41u8, + 30u8, + 91u8, + 32u8, + 10u8, + 200u8, + 199u8, + 195u8, + 185u8, + 37u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'approved' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Approval { + const NAME: &'static str = "Approval"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ApprovalForAll { + pub owner: Vec, + pub operator: Vec, + pub approved: bool, + } + impl ApprovalForAll { + const TOPIC_ID: [u8; 32] = [ + 23u8, + 48u8, + 126u8, + 171u8, + 57u8, + 171u8, + 97u8, + 7u8, + 232u8, + 137u8, + 152u8, + 69u8, + 173u8, + 61u8, + 89u8, + 189u8, + 150u8, + 83u8, + 242u8, + 0u8, + 242u8, + 32u8, + 146u8, + 4u8, + 137u8, + 202u8, + 43u8, + 89u8, + 55u8, + 105u8, + 108u8, + 49u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'operator' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + } + impl substreams_ethereum::Event for ApprovalForAll { + const NAME: &'static str = "ApprovalForAll"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnershipTransferred { + pub previous_owner: Vec, + pub new_owner: Vec, + } + impl OwnershipTransferred { + const TOPIC_ID: [u8; 32] = [ + 139u8, + 224u8, + 7u8, + 156u8, + 83u8, + 22u8, + 89u8, + 20u8, + 19u8, + 68u8, + 205u8, + 31u8, + 208u8, + 164u8, + 242u8, + 132u8, + 25u8, + 73u8, + 127u8, + 151u8, + 34u8, + 163u8, + 218u8, + 175u8, + 227u8, + 180u8, + 24u8, + 111u8, + 107u8, + 100u8, + 87u8, + 224u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + previous_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'previous_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + new_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for OwnershipTransferred { + const NAME: &'static str = "OwnershipTransferred"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Transfer { + const TOPIC_ID: [u8; 32] = [ + 221u8, + 242u8, + 82u8, + 173u8, + 27u8, + 226u8, + 200u8, + 155u8, + 105u8, + 194u8, + 176u8, + 104u8, + 252u8, + 55u8, + 141u8, + 170u8, + 149u8, + 43u8, + 167u8, + 241u8, + 99u8, + 196u8, + 161u8, + 22u8, + 40u8, + 245u8, + 90u8, + 77u8, + 245u8, + 35u8, + 179u8, + 239u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + from: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'from' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'to' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Transfer { + const NAME: &'static str = "Transfer"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + } \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/mod.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/mod.rs new file mode 100755 index 0000000..faed608 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/mod.rs @@ -0,0 +1,3 @@ + +pub mod moonbird_contract; +pub mod bayc_contract; \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/moonbird_contract.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/moonbird_contract.rs new file mode 100644 index 0000000..3c56952 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/abi/moonbird_contract.rs @@ -0,0 +1,7360 @@ + const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; + /// Contract's functions. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct DefaultAdminRole {} + impl DefaultAdminRole { + const METHOD_ID: [u8; 4] = [162u8, 23u8, 253u8, 223u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<[u8; 32usize], String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut result = [0u8; 32]; + let v = values + .pop() + .expect("one output data should have existed") + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option<[u8; 32usize]> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for DefaultAdminRole { + const NAME: &'static str = "DEFAULT_ADMIN_ROLE"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable<[u8; 32usize]> for DefaultAdminRole { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ExpulsionRole {} + impl ExpulsionRole { + const METHOD_ID: [u8; 4] = [64u8, 182u8, 37u8, 192u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<[u8; 32usize], String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut result = [0u8; 32]; + let v = values + .pop() + .expect("one output data should have existed") + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option<[u8; 32usize]> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ExpulsionRole { + const NAME: &'static str = "EXPULSION_ROLE"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable<[u8; 32usize]> for ExpulsionRole { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct AddSigner { + pub signer: Vec, + } + impl AddSigner { + const METHOD_ID: [u8; 4] = [235u8, 18u8, 214u8, 30u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + signer: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.signer))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for AddSigner { + const NAME: &'static str = "addSigner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct AlreadyMinted { + pub to: Vec, + pub nonce: [u8; 32usize], + } + impl AlreadyMinted { + const METHOD_ID: [u8; 4] = [91u8, 142u8, 205u8, 87u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + nonce: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::FixedBytes(self.nonce.as_ref().to_vec()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for AlreadyMinted { + const NAME: &'static str = "alreadyMinted"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for AlreadyMinted { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Approve { + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Approve { + const METHOD_ID: [u8; 4] = [9u8, 94u8, 167u8, 179u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Approve { + const NAME: &'static str = "approve"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BalanceOf { + pub owner: Vec, + } + impl BalanceOf { + const METHOD_ID: [u8; 4] = [112u8, 160u8, 130u8, 49u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.owner))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BalanceOf { + const NAME: &'static str = "balanceOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for BalanceOf { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BaseTokenUri {} + impl BaseTokenUri { + const METHOD_ID: [u8; 4] = [213u8, 71u8, 207u8, 183u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BaseTokenUri { + const NAME: &'static str = "baseTokenURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for BaseTokenUri { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Beneficiary {} + impl Beneficiary { + const METHOD_ID: [u8; 4] = [56u8, 175u8, 62u8, 237u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Beneficiary { + const NAME: &'static str = "beneficiary"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Beneficiary { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Cost { + pub n: substreams::scalar::BigInt, + pub param1: substreams::scalar::BigInt, + } + impl Cost { + const METHOD_ID: [u8; 4] = [62u8, 192u8, 46u8, 20u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + n: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + param1: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.n.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.param1.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Cost { + const NAME: &'static str = "cost"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for Cost { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ExpelFromNest { + pub token_id: substreams::scalar::BigInt, + } + impl ExpelFromNest { + const METHOD_ID: [u8; 4] = [57u8, 21u8, 75u8, 158u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for ExpelFromNest { + const NAME: &'static str = "expelFromNest"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetApproved { + pub token_id: substreams::scalar::BigInt, + } + impl GetApproved { + const METHOD_ID: [u8; 4] = [8u8, 24u8, 18u8, 252u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetApproved { + const NAME: &'static str = "getApproved"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for GetApproved { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetRoleAdmin { + pub role: [u8; 32usize], + } + impl GetRoleAdmin { + const METHOD_ID: [u8; 4] = [36u8, 138u8, 156u8, 163u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.role.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<[u8; 32usize], String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut result = [0u8; 32]; + let v = values + .pop() + .expect("one output data should have existed") + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option<[u8; 32usize]> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetRoleAdmin { + const NAME: &'static str = "getRoleAdmin"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable<[u8; 32usize]> for GetRoleAdmin { + fn output(data: &[u8]) -> Result<[u8; 32usize], String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetRoleMember { + pub role: [u8; 32usize], + pub index: substreams::scalar::BigInt, + } + impl GetRoleMember { + const METHOD_ID: [u8; 4] = [144u8, 16u8, 208u8, 124u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + index: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::FixedBytes(self.role.as_ref().to_vec()), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.index.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetRoleMember { + const NAME: &'static str = "getRoleMember"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for GetRoleMember { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetRoleMemberCount { + pub role: [u8; 32usize], + } + impl GetRoleMemberCount { + const METHOD_ID: [u8; 4] = [202u8, 21u8, 200u8, 115u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.role.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetRoleMemberCount { + const NAME: &'static str = "getRoleMemberCount"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for GetRoleMemberCount { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GrantRole { + pub role: [u8; 32usize], + pub account: Vec, + } + impl GrantRole { + const METHOD_ID: [u8; 4] = [47u8, 47u8, 241u8, 93u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Address, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + account: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::FixedBytes(self.role.as_ref().to_vec()), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.account), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for GrantRole { + const NAME: &'static str = "grantRole"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct HasRole { + pub role: [u8; 32usize], + pub account: Vec, + } + impl HasRole { + const METHOD_ID: [u8; 4] = [145u8, 209u8, 72u8, 84u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Address, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + account: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::FixedBytes(self.role.as_ref().to_vec()), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.account), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for HasRole { + const NAME: &'static str = "hasRole"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for HasRole { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IsApprovedForAll { + pub owner: Vec, + pub operator: Vec, + } + impl IsApprovedForAll { + const METHOD_ID: [u8; 4] = [233u8, 133u8, 233u8, 197u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.operator), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for IsApprovedForAll { + const NAME: &'static str = "isApprovedForAll"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for IsApprovedForAll { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MintProof { + pub proof_token_ids: Vec, + } + impl MintProof { + const METHOD_ID: [u8; 4] = [99u8, 167u8, 130u8, 245u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::Uint(256usize)), + ), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + proof_token_ids: values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + .collect(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + { + let v = self + .proof_token_ids + .iter() + .map(|inner| ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match inner.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + )) + .collect(); + ethabi::Token::Array(v) + }, + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for MintProof { + const NAME: &'static str = "mintPROOF"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MintPublic { + pub to: Vec, + pub nonce: [u8; 32usize], + pub sig: Vec, + } + impl MintPublic { + const METHOD_ID: [u8; 4] = [13u8, 253u8, 2u8, 90u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + nonce: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + sig: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::FixedBytes(self.nonce.as_ref().to_vec()), + ethabi::Token::Bytes(self.sig.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for MintPublic { + const NAME: &'static str = "mintPublic"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MintUnclaimed { + pub to: Vec, + pub n: substreams::scalar::BigInt, + } + impl MintUnclaimed { + const METHOD_ID: [u8; 4] = [158u8, 112u8, 81u8, 64u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + n: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.n.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for MintUnclaimed { + const NAME: &'static str = "mintUnclaimed"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Name {} + impl Name { + const METHOD_ID: [u8; 4] = [6u8, 253u8, 222u8, 3u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Name { + const NAME: &'static str = "name"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Name { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct NestingOpen {} + impl NestingOpen { + const METHOD_ID: [u8; 4] = [32u8, 21u8, 194u8, 145u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for NestingOpen { + const NAME: &'static str = "nestingOpen"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for NestingOpen { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct NestingPeriod { + pub token_id: substreams::scalar::BigInt, + } + impl NestingPeriod { + const METHOD_ID: [u8; 4] = [76u8, 164u8, 253u8, 245u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + (bool, substreams::scalar::BigInt, substreams::scalar::BigInt), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + (bool, substreams::scalar::BigInt, substreams::scalar::BigInt), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Bool, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + values.pop().expect(INTERNAL_ERR).into_bool().expect(INTERNAL_ERR), + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(bool, substreams::scalar::BigInt, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for NestingPeriod { + const NAME: &'static str = "nestingPeriod"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable< + (bool, substreams::scalar::BigInt, substreams::scalar::BigInt), + > for NestingPeriod { + fn output( + data: &[u8], + ) -> Result< + (bool, substreams::scalar::BigInt, substreams::scalar::BigInt), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Owner {} + impl Owner { + const METHOD_ID: [u8; 4] = [141u8, 165u8, 203u8, 91u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Owner { + const NAME: &'static str = "owner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Owner { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnerOf { + pub token_id: substreams::scalar::BigInt, + } + impl OwnerOf { + const METHOD_ID: [u8; 4] = [99u8, 82u8, 33u8, 30u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for OwnerOf { + const NAME: &'static str = "ownerOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for OwnerOf { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Pause {} + impl Pause { + const METHOD_ID: [u8; 4] = [132u8, 86u8, 203u8, 89u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Pause { + const NAME: &'static str = "pause"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Paused {} + impl Paused { + const METHOD_ID: [u8; 4] = [92u8, 151u8, 90u8, 187u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Paused { + const NAME: &'static str = "paused"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Paused { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Price {} + impl Price { + const METHOD_ID: [u8; 4] = [160u8, 53u8, 177u8, 254u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Price { + const NAME: &'static str = "price"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for Price { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Proof {} + impl Proof { + const METHOD_ID: [u8; 4] = [250u8, 249u8, 36u8, 207u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Proof { + const NAME: &'static str = "proof"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Proof { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProofClaimsRemaining { + pub token_id: substreams::scalar::BigInt, + } + impl ProofClaimsRemaining { + const METHOD_ID: [u8; 4] = [163u8, 154u8, 135u8, 11u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProofClaimsRemaining { + const NAME: &'static str = "proofClaimsRemaining"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for ProofClaimsRemaining { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProofMintingOpen {} + impl ProofMintingOpen { + const METHOD_ID: [u8; 4] = [242u8, 3u8, 28u8, 103u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProofMintingOpen { + const NAME: &'static str = "proofMintingOpen"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for ProofMintingOpen { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ProofPoolRemaining {} + impl ProofPoolRemaining { + const METHOD_ID: [u8; 4] = [77u8, 36u8, 167u8, 58u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ProofPoolRemaining { + const NAME: &'static str = "proofPoolRemaining"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for ProofPoolRemaining { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct PurchaseFreeOfCharge { + pub to: Vec, + pub n: substreams::scalar::BigInt, + } + impl PurchaseFreeOfCharge { + const METHOD_ID: [u8; 4] = [191u8, 98u8, 226u8, 29u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + n: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.n.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for PurchaseFreeOfCharge { + const NAME: &'static str = "purchaseFreeOfCharge"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RemoveSigner { + pub signer: Vec, + } + impl RemoveSigner { + const METHOD_ID: [u8; 4] = [14u8, 49u8, 106u8, 183u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + signer: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.signer))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for RemoveSigner { + const NAME: &'static str = "removeSigner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RenderingContract {} + impl RenderingContract { + const METHOD_ID: [u8; 4] = [199u8, 254u8, 203u8, 204u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for RenderingContract { + const NAME: &'static str = "renderingContract"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for RenderingContract { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RenounceOwnership {} + impl RenounceOwnership { + const METHOD_ID: [u8; 4] = [113u8, 80u8, 24u8, 166u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for RenounceOwnership { + const NAME: &'static str = "renounceOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RenounceRole { + pub role: [u8; 32usize], + pub account: Vec, + } + impl RenounceRole { + const METHOD_ID: [u8; 4] = [54u8, 86u8, 138u8, 190u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Address, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + account: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::FixedBytes(self.role.as_ref().to_vec()), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.account), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for RenounceRole { + const NAME: &'static str = "renounceRole"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RevokeRole { + pub role: [u8; 32usize], + pub account: Vec, + } + impl RevokeRole { + const METHOD_ID: [u8; 4] = [213u8, 71u8, 116u8, 31u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::FixedBytes(32usize), + ethabi::ParamType::Address, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + account: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::FixedBytes(self.role.as_ref().to_vec()), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.account), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for RevokeRole { + const NAME: &'static str = "revokeRole"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RoyaltyInfo { + pub u_token_id: substreams::scalar::BigInt, + pub u_sale_price: substreams::scalar::BigInt, + } + impl RoyaltyInfo { + const METHOD_ID: [u8; 4] = [42u8, 85u8, 32u8, 90u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + u_sale_price: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.u_token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.u_sale_price.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result<(Vec, substreams::scalar::BigInt), String> { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result<(Vec, substreams::scalar::BigInt), String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option<(Vec, substreams::scalar::BigInt)> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for RoyaltyInfo { + const NAME: &'static str = "royaltyInfo"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable< + (Vec, substreams::scalar::BigInt), + > for RoyaltyInfo { + fn output( + data: &[u8], + ) -> Result<(Vec, substreams::scalar::BigInt), String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferFrom1 { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl SafeTransferFrom1 { + const METHOD_ID: [u8; 4] = [66u8, 132u8, 46u8, 14u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferFrom1 { + const NAME: &'static str = "safeTransferFrom1"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferFrom2 { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + pub u_data: Vec, + } + impl SafeTransferFrom2 { + const METHOD_ID: [u8; 4] = [184u8, 141u8, 79u8, 222u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + u_data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Bytes(self.u_data.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferFrom2 { + const NAME: &'static str = "safeTransferFrom2"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferWhileNesting { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl SafeTransferWhileNesting { + const METHOD_ID: [u8; 4] = [170u8, 150u8, 120u8, 120u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferWhileNesting { + const NAME: &'static str = "safeTransferWhileNesting"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SellerConfig {} + impl SellerConfig { + const METHOD_ID: [u8; 4] = [187u8, 105u8, 183u8, 239u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + bool, + bool, + ), + String, + > { + Self::output(call.return_data.as_ref()) + } + pub fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + bool, + bool, + ), + String, + > { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(248usize), + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ethabi::ParamType::Bool, + ], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + values.reverse(); + Ok(( + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + values.pop().expect(INTERNAL_ERR).into_bool().expect(INTERNAL_ERR), + values.pop().expect(INTERNAL_ERR).into_bool().expect(INTERNAL_ERR), + values.pop().expect(INTERNAL_ERR).into_bool().expect(INTERNAL_ERR), + )) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call( + &self, + address: Vec, + ) -> Option< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + bool, + bool, + ), + > { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SellerConfig { + const NAME: &'static str = "sellerConfig"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + bool, + bool, + ), + > for SellerConfig { + fn output( + data: &[u8], + ) -> Result< + ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + bool, + bool, + ), + String, + > { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetApprovalForAll { + pub operator: Vec, + pub approved: bool, + } + impl SetApprovalForAll { + const METHOD_ID: [u8; 4] = [162u8, 44u8, 180u8, 101u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Bool], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.operator), + ), + ethabi::Token::Bool(self.approved.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetApprovalForAll { + const NAME: &'static str = "setApprovalForAll"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetBaseTokenUri { + pub u_base_token_uri: String, + } + impl SetBaseTokenUri { + const METHOD_ID: [u8; 4] = [48u8, 23u8, 110u8, 19u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_base_token_uri: values + .pop() + .expect(INTERNAL_ERR) + .into_string() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::String(self.u_base_token_uri.clone())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetBaseTokenUri { + const NAME: &'static str = "setBaseTokenURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetBeneficiary { + pub u_beneficiary: Vec, + } + impl SetBeneficiary { + const METHOD_ID: [u8; 4] = [28u8, 49u8, 247u8, 16u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_beneficiary: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.u_beneficiary), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetBeneficiary { + const NAME: &'static str = "setBeneficiary"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetNestingOpen { + pub open: bool, + } + impl SetNestingOpen { + const METHOD_ID: [u8; 4] = [66u8, 23u8, 69u8, 171u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + open: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Bool(self.open.clone())]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetNestingOpen { + const NAME: &'static str = "setNestingOpen"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetProofMintingOpen { + pub open: bool, + } + impl SetProofMintingOpen { + const METHOD_ID: [u8; 4] = [220u8, 159u8, 243u8, 237u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + open: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[ethabi::Token::Bool(self.open.clone())]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetProofMintingOpen { + const NAME: &'static str = "setPROOFMintingOpen"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetPrice { + pub u_price: substreams::scalar::BigInt, + } + impl SetPrice { + const METHOD_ID: [u8; 4] = [145u8, 183u8, 245u8, 237u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_price: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.u_price.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetPrice { + const NAME: &'static str = "setPrice"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetRenderingContract { + pub u_contract: Vec, + } + impl SetRenderingContract { + const METHOD_ID: [u8; 4] = [183u8, 241u8, 208u8, 114u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + u_contract: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.u_contract), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetRenderingContract { + const NAME: &'static str = "setRenderingContract"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetRoyaltyInfo { + pub receiver: Vec, + pub fee_basis_points: substreams::scalar::BigInt, + } + impl SetRoyaltyInfo { + const METHOD_ID: [u8; 4] = [2u8, 250u8, 124u8, 71u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(96usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + receiver: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + fee_basis_points: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.receiver), + ), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.fee_basis_points.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetRoyaltyInfo { + const NAME: &'static str = "setRoyaltyInfo"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetSellerConfig { + pub config: ( + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + substreams::scalar::BigInt, + bool, + bool, + bool, + ), + } + impl SetSellerConfig { + const METHOD_ID: [u8; 4] = [47u8, 39u8, 75u8, 212u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Tuple( + vec![ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(248usize), ethabi::ParamType::Bool, + ethabi::ParamType::Bool, ethabi::ParamType::Bool + ], + ), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + config: { + let tuple_elements = values + .pop() + .expect(INTERNAL_ERR) + .into_tuple() + .expect(INTERNAL_ERR); + ( + { + let mut v = [0 as u8; 32]; + tuple_elements[0usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[1usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[2usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + { + let mut v = [0 as u8; 32]; + tuple_elements[3usize] + .clone() + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + tuple_elements[4usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[5usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + tuple_elements[6usize] + .clone() + .into_bool() + .expect(INTERNAL_ERR), + ) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Tuple( + vec![ + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .config.0.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .config.1.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .config.2.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), + ethabi::Token::Uint(ethabi::Uint::from_big_endian(match self + .config.3.clone().to_bytes_be() { (num_bigint::Sign::Plus, + bytes) => bytes, (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") }, } + .as_slice(),),), ethabi::Token::Bool(self.config.4.clone()), + ethabi::Token::Bool(self.config.5.clone()), + ethabi::Token::Bool(self.config.6.clone()) + ], + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetSellerConfig { + const NAME: &'static str = "setSellerConfig"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SupportsInterface { + pub interface_id: [u8; 4usize], + } + impl SupportsInterface { + const METHOD_ID: [u8; 4] = [1u8, 255u8, 201u8, 167u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(4usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + interface_id: { + let mut result = [0u8; 4]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.interface_id.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SupportsInterface { + const NAME: &'static str = "supportsInterface"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SupportsInterface { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Symbol {} + impl Symbol { + const METHOD_ID: [u8; 4] = [149u8, 216u8, 155u8, 65u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Symbol { + const NAME: &'static str = "symbol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Symbol { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ToggleNesting { + pub token_ids: Vec, + } + impl ToggleNesting { + const METHOD_ID: [u8; 4] = [70u8, 155u8, 41u8, 205u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Array( + Box::new(ethabi::ParamType::Uint(256usize)), + ), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_ids: values + .pop() + .expect(INTERNAL_ERR) + .into_array() + .expect(INTERNAL_ERR) + .into_iter() + .map(|inner| { + let mut v = [0 as u8; 32]; + inner + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + .collect(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + { + let v = self + .token_ids + .iter() + .map(|inner| ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match inner.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + )) + .collect(); + ethabi::Token::Array(v) + }, + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for ToggleNesting { + const NAME: &'static str = "toggleNesting"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenUri { + pub token_id: substreams::scalar::BigInt, + } + impl TokenUri { + const METHOD_ID: [u8; 4] = [200u8, 123u8, 86u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenUri { + const NAME: &'static str = "tokenURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TokenUri { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TotalSold {} + impl TotalSold { + const METHOD_ID: [u8; 4] = [145u8, 6u8, 215u8, 186u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TotalSold { + const NAME: &'static str = "totalSold"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TotalSold { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TotalSupply {} + impl TotalSupply { + const METHOD_ID: [u8; 4] = [24u8, 22u8, 13u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TotalSupply { + const NAME: &'static str = "totalSupply"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TotalSupply { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferFrom { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl TransferFrom { + const METHOD_ID: [u8; 4] = [35u8, 184u8, 114u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferFrom { + const NAME: &'static str = "transferFrom"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferOwnership { + pub new_owner: Vec, + } + impl TransferOwnership { + const METHOD_ID: [u8; 4] = [242u8, 253u8, 227u8, 139u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + new_owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.new_owner), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferOwnership { + const NAME: &'static str = "transferOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Unpause {} + impl Unpause { + const METHOD_ID: [u8; 4] = [63u8, 75u8, 168u8, 58u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Unpause { + const NAME: &'static str = "unpause"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct UsedMessages { + pub param0: [u8; 32usize], + } + impl UsedMessages { + const METHOD_ID: [u8; 4] = [90u8, 2u8, 132u8, 0u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + param0: { + let mut result = [0u8; 32]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.param0.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for UsedMessages { + const NAME: &'static str = "usedMessages"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for UsedMessages { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + } + /// Contract's events. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Approval { + pub owner: Vec, + pub approved: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Approval { + const TOPIC_ID: [u8; 32] = [ + 140u8, + 91u8, + 225u8, + 229u8, + 235u8, + 236u8, + 125u8, + 91u8, + 209u8, + 79u8, + 113u8, + 66u8, + 125u8, + 30u8, + 132u8, + 243u8, + 221u8, + 3u8, + 20u8, + 192u8, + 247u8, + 178u8, + 41u8, + 30u8, + 91u8, + 32u8, + 10u8, + 200u8, + 199u8, + 195u8, + 185u8, + 37u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'approved' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Approval { + const NAME: &'static str = "Approval"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ApprovalForAll { + pub owner: Vec, + pub operator: Vec, + pub approved: bool, + } + impl ApprovalForAll { + const TOPIC_ID: [u8; 32] = [ + 23u8, + 48u8, + 126u8, + 171u8, + 57u8, + 171u8, + 97u8, + 7u8, + 232u8, + 137u8, + 152u8, + 69u8, + 173u8, + 61u8, + 89u8, + 189u8, + 150u8, + 83u8, + 242u8, + 0u8, + 242u8, + 32u8, + 146u8, + 4u8, + 137u8, + 202u8, + 43u8, + 89u8, + 55u8, + 105u8, + 108u8, + 49u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'operator' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + } + impl substreams_ethereum::Event for ApprovalForAll { + const NAME: &'static str = "ApprovalForAll"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Expelled { + pub token_id: substreams::scalar::BigInt, + } + impl Expelled { + const TOPIC_ID: [u8; 32] = [ + 62u8, + 190u8, + 233u8, + 78u8, + 116u8, + 234u8, + 36u8, + 247u8, + 17u8, + 181u8, + 135u8, + 109u8, + 202u8, + 114u8, + 64u8, + 98u8, + 225u8, + 139u8, + 123u8, + 55u8, + 182u8, + 136u8, + 62u8, + 104u8, + 106u8, + 146u8, + 240u8, + 147u8, + 36u8, + 138u8, + 79u8, + 207u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Expelled { + const NAME: &'static str = "Expelled"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Nested { + pub token_id: substreams::scalar::BigInt, + } + impl Nested { + const TOPIC_ID: [u8; 32] = [ + 132u8, + 188u8, + 206u8, + 223u8, + 95u8, + 186u8, + 213u8, + 200u8, + 2u8, + 134u8, + 76u8, + 45u8, + 100u8, + 228u8, + 86u8, + 42u8, + 97u8, + 10u8, + 70u8, + 139u8, + 162u8, + 129u8, + 115u8, + 189u8, + 117u8, + 40u8, + 88u8, + 142u8, + 68u8, + 41u8, + 234u8, + 247u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Nested { + const NAME: &'static str = "Nested"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnershipTransferred { + pub previous_owner: Vec, + pub new_owner: Vec, + } + impl OwnershipTransferred { + const TOPIC_ID: [u8; 32] = [ + 139u8, + 224u8, + 7u8, + 156u8, + 83u8, + 22u8, + 89u8, + 20u8, + 19u8, + 68u8, + 205u8, + 31u8, + 208u8, + 164u8, + 242u8, + 132u8, + 25u8, + 73u8, + 127u8, + 151u8, + 34u8, + 163u8, + 218u8, + 175u8, + 227u8, + 180u8, + 24u8, + 111u8, + 107u8, + 100u8, + 87u8, + 224u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + previous_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'previous_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + new_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for OwnershipTransferred { + const NAME: &'static str = "OwnershipTransferred"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Paused { + pub account: Vec, + } + impl Paused { + const TOPIC_ID: [u8; 32] = [ + 98u8, + 231u8, + 140u8, + 234u8, + 1u8, + 190u8, + 227u8, + 32u8, + 205u8, + 78u8, + 66u8, + 2u8, + 112u8, + 181u8, + 234u8, + 116u8, + 0u8, + 13u8, + 17u8, + 176u8, + 201u8, + 247u8, + 71u8, + 84u8, + 235u8, + 219u8, + 252u8, + 84u8, + 75u8, + 5u8, + 162u8, + 88u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + account: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for Paused { + const NAME: &'static str = "Paused"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Refund { + pub buyer: Vec, + pub amount: substreams::scalar::BigInt, + } + impl Refund { + const TOPIC_ID: [u8; 32] = [ + 187u8, + 40u8, + 53u8, + 62u8, + 69u8, + 152u8, + 195u8, + 185u8, + 25u8, + 145u8, + 1u8, + 166u8, + 110u8, + 9u8, + 137u8, + 84u8, + 155u8, + 101u8, + 154u8, + 89u8, + 165u8, + 77u8, + 44u8, + 39u8, + 251u8, + 177u8, + 131u8, + 241u8, + 147u8, + 44u8, + 142u8, + 109u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + buyer: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'buyer' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Refund { + const NAME: &'static str = "Refund"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Revenue { + pub beneficiary: Vec, + pub num_purchased: substreams::scalar::BigInt, + pub amount: substreams::scalar::BigInt, + } + impl Revenue { + const TOPIC_ID: [u8; 32] = [ + 1u8, + 245u8, + 27u8, + 153u8, + 189u8, + 28u8, + 60u8, + 202u8, + 48u8, + 24u8, + 54u8, + 23u8, + 142u8, + 93u8, + 238u8, + 19u8, + 170u8, + 223u8, + 228u8, + 78u8, + 255u8, + 6u8, + 220u8, + 61u8, + 220u8, + 191u8, + 60u8, + 157u8, + 5u8, + 132u8, + 84u8, + 248u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 64usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Uint(256usize), + ], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + beneficiary: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'beneficiary' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + num_purchased: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + amount: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Revenue { + const NAME: &'static str = "Revenue"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RoleAdminChanged { + pub role: [u8; 32usize], + pub previous_admin_role: [u8; 32usize], + pub new_admin_role: [u8; 32usize], + } + impl RoleAdminChanged { + const TOPIC_ID: [u8; 32] = [ + 189u8, + 121u8, + 184u8, + 111u8, + 254u8, + 10u8, + 184u8, + 232u8, + 119u8, + 97u8, + 81u8, + 81u8, + 66u8, + 23u8, + 205u8, + 124u8, + 172u8, + 213u8, + 44u8, + 144u8, + 159u8, + 102u8, + 71u8, + 92u8, + 58u8, + 244u8, + 78u8, + 18u8, + 159u8, + 11u8, + 0u8, + 255u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'role' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + previous_admin_role: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'previous_admin_role' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + new_admin_role: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_admin_role' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + } + impl substreams_ethereum::Event for RoleAdminChanged { + const NAME: &'static str = "RoleAdminChanged"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RoleGranted { + pub role: [u8; 32usize], + pub account: Vec, + pub sender: Vec, + } + impl RoleGranted { + const TOPIC_ID: [u8; 32] = [ + 47u8, + 135u8, + 136u8, + 17u8, + 126u8, + 126u8, + 255u8, + 29u8, + 130u8, + 233u8, + 38u8, + 236u8, + 121u8, + 73u8, + 1u8, + 209u8, + 124u8, + 120u8, + 2u8, + 74u8, + 80u8, + 39u8, + 9u8, + 64u8, + 48u8, + 69u8, + 64u8, + 167u8, + 51u8, + 101u8, + 111u8, + 13u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'role' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + account: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'account' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + sender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for RoleGranted { + const NAME: &'static str = "RoleGranted"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RoleRevoked { + pub role: [u8; 32usize], + pub account: Vec, + pub sender: Vec, + } + impl RoleRevoked { + const TOPIC_ID: [u8; 32] = [ + 246u8, + 57u8, + 31u8, + 92u8, + 50u8, + 217u8, + 198u8, + 157u8, + 42u8, + 71u8, + 234u8, + 103u8, + 11u8, + 68u8, + 41u8, + 116u8, + 181u8, + 57u8, + 53u8, + 209u8, + 237u8, + 199u8, + 253u8, + 100u8, + 235u8, + 33u8, + 224u8, + 71u8, + 168u8, + 57u8, + 23u8, + 27u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + role: { + let mut result = [0u8; 32]; + let v = ethabi::decode( + &[ethabi::ParamType::FixedBytes(32usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'role' from topic of type 'bytes32': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + account: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'account' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + sender: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'sender' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for RoleRevoked { + const NAME: &'static str = "RoleRevoked"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Transfer { + const TOPIC_ID: [u8; 32] = [ + 221u8, + 242u8, + 82u8, + 173u8, + 27u8, + 226u8, + 200u8, + 155u8, + 105u8, + 194u8, + 176u8, + 104u8, + 252u8, + 55u8, + 141u8, + 170u8, + 149u8, + 43u8, + 167u8, + 241u8, + 99u8, + 196u8, + 161u8, + 22u8, + 40u8, + 245u8, + 90u8, + 77u8, + 245u8, + 35u8, + 179u8, + 239u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + from: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'from' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'to' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Transfer { + const NAME: &'static str = "Transfer"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Unnested { + pub token_id: substreams::scalar::BigInt, + } + impl Unnested { + const TOPIC_ID: [u8; 32] = [ + 101u8, + 117u8, + 0u8, + 121u8, + 55u8, + 68u8, + 253u8, + 40u8, + 126u8, + 216u8, + 228u8, + 118u8, + 131u8, + 42u8, + 60u8, + 180u8, + 183u8, + 170u8, + 91u8, + 147u8, + 28u8, + 218u8, + 16u8, + 189u8, + 199u8, + 115u8, + 163u8, + 1u8, + 224u8, + 233u8, + 168u8, + 49u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 2usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Unnested { + const NAME: &'static str = "Unnested"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Unpaused { + pub account: Vec, + } + impl Unpaused { + const TOPIC_ID: [u8; 32] = [ + 93u8, + 185u8, + 238u8, + 10u8, + 73u8, + 91u8, + 242u8, + 230u8, + 255u8, + 156u8, + 145u8, + 167u8, + 131u8, + 76u8, + 27u8, + 164u8, + 253u8, + 210u8, + 68u8, + 165u8, + 232u8, + 170u8, + 78u8, + 83u8, + 123u8, + 211u8, + 138u8, + 234u8, + 228u8, + 176u8, + 115u8, + 170u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 1usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + account: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for Unpaused { + const NAME: &'static str = "Unpaused"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + } \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/lib.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/lib.rs new file mode 100644 index 0000000..3db71b4 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/lib.rs @@ -0,0 +1,785 @@ +mod abi; +mod pb; +use hex_literal::hex; +use pb::contract::v1 as contract; +use substreams::Hex; +use substreams_database_change::pb::database::DatabaseChanges; +use substreams_database_change::tables::Tables as DatabaseChangeTables; +use substreams_entity_change::pb::entity::EntityChanges; +use substreams_entity_change::tables::Tables as EntityChangesTables; +use substreams_ethereum::pb::eth::v2 as eth; +use substreams_ethereum::Event; + +#[allow(unused_imports)] +use num_traits::cast::ToPrimitive; +use std::str::FromStr; +use substreams::scalar::BigDecimal; + +substreams_ethereum::init!(); + +const MOONBIRD_TRACKED_CONTRACT: [u8; 20] = hex!("23581767a106ae21c074b2276d25e5c3e136a68b"); +const BAYC_TRACKED_CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); + +fn map_moonbird_events(blk: ð::Block, events: &mut contract::Events) { + events.moonbird_approvals.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Approval::match_and_decode(log) { + return Some(contract::MoonbirdApproval { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + approved: event.approved, + owner: event.owner, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.moonbird_approval_for_alls.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::ApprovalForAll::match_and_decode(log) { + return Some(contract::MoonbirdApprovalForAll { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + approved: event.approved, + operator: event.operator, + owner: event.owner, + }); + } + + None + }) + }) + .collect()); + events.moonbird_expelleds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Expelled::match_and_decode(log) { + return Some(contract::MoonbirdExpelled { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.moonbird_nesteds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Nested::match_and_decode(log) { + return Some(contract::MoonbirdNested { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.moonbird_ownership_transferreds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::OwnershipTransferred::match_and_decode(log) { + return Some(contract::MoonbirdOwnershipTransferred { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + new_owner: event.new_owner, + previous_owner: event.previous_owner, + }); + } + + None + }) + }) + .collect()); + events.moonbird_pauseds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Paused::match_and_decode(log) { + return Some(contract::MoonbirdPaused { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + account: event.account, + }); + } + + None + }) + }) + .collect()); + events.moonbird_refunds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Refund::match_and_decode(log) { + return Some(contract::MoonbirdRefund { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + amount: event.amount.to_string(), + buyer: event.buyer, + }); + } + + None + }) + }) + .collect()); + events.moonbird_revenues.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Revenue::match_and_decode(log) { + return Some(contract::MoonbirdRevenue { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + amount: event.amount.to_string(), + beneficiary: event.beneficiary, + num_purchased: event.num_purchased.to_string(), + }); + } + + None + }) + }) + .collect()); + events.moonbird_role_admin_changeds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::RoleAdminChanged::match_and_decode(log) { + return Some(contract::MoonbirdRoleAdminChanged { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + new_admin_role: Vec::from(event.new_admin_role), + previous_admin_role: Vec::from(event.previous_admin_role), + role: Vec::from(event.role), + }); + } + + None + }) + }) + .collect()); + events.moonbird_role_granteds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::RoleGranted::match_and_decode(log) { + return Some(contract::MoonbirdRoleGranted { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + account: event.account, + role: Vec::from(event.role), + sender: event.sender, + }); + } + + None + }) + }) + .collect()); + events.moonbird_role_revokeds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::RoleRevoked::match_and_decode(log) { + return Some(contract::MoonbirdRoleRevoked { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + account: event.account, + role: Vec::from(event.role), + sender: event.sender, + }); + } + + None + }) + }) + .collect()); + events.moonbird_transfers.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Transfer::match_and_decode(log) { + return Some(contract::MoonbirdTransfer { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + from: event.from, + to: event.to, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.moonbird_unnesteds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Unnested::match_and_decode(log) { + return Some(contract::MoonbirdUnnested { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.moonbird_unpauseds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == MOONBIRD_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::moonbird_contract::events::Unpaused::match_and_decode(log) { + return Some(contract::MoonbirdUnpaused { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + account: event.account, + }); + } + + None + }) + }) + .collect()); +} + +fn map_bayc_events(blk: ð::Block, events: &mut contract::Events) { + events.bayc_approvals.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::Approval::match_and_decode(log) { + return Some(contract::BaycApproval { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + approved: event.approved, + owner: event.owner, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.bayc_approval_for_alls.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::ApprovalForAll::match_and_decode(log) { + return Some(contract::BaycApprovalForAll { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + approved: event.approved, + operator: event.operator, + owner: event.owner, + }); + } + + None + }) + }) + .collect()); + events.bayc_ownership_transferreds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::OwnershipTransferred::match_and_decode(log) { + return Some(contract::BaycOwnershipTransferred { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + new_owner: event.new_owner, + previous_owner: event.previous_owner, + }); + } + + None + }) + }) + .collect()); + events.bayc_transfers.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::Transfer::match_and_decode(log) { + return Some(contract::BaycTransfer { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + from: event.from, + to: event.to, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); +} + +fn db_moonbird_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.moonbird_approvals.iter().for_each(|evt| { + tables + .create_row("moonbird_approval", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", Hex(&evt.approved).to_string()) + .set("owner", Hex(&evt.owner).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_approval_for_alls.iter().for_each(|evt| { + tables + .create_row("moonbird_approval_for_all", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", evt.approved) + .set("operator", Hex(&evt.operator).to_string()) + .set("owner", Hex(&evt.owner).to_string()); + }); + events.moonbird_expelleds.iter().for_each(|evt| { + tables + .create_row("moonbird_expelled", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_nesteds.iter().for_each(|evt| { + tables + .create_row("moonbird_nested", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_ownership_transferreds.iter().for_each(|evt| { + tables + .create_row("moonbird_ownership_transferred", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("previous_owner", Hex(&evt.previous_owner).to_string()); + }); + events.moonbird_pauseds.iter().for_each(|evt| { + tables + .create_row("moonbird_paused", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()); + }); + events.moonbird_refunds.iter().for_each(|evt| { + tables + .create_row("moonbird_refund", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("buyer", Hex(&evt.buyer).to_string()); + }); + events.moonbird_revenues.iter().for_each(|evt| { + tables + .create_row("moonbird_revenue", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("beneficiary", Hex(&evt.beneficiary).to_string()) + .set("num_purchased", BigDecimal::from_str(&evt.num_purchased).unwrap()); + }); + events.moonbird_role_admin_changeds.iter().for_each(|evt| { + tables + .create_row("moonbird_role_admin_changed", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_admin_role", Hex(&evt.new_admin_role).to_string()) + .set("previous_admin_role", Hex(&evt.previous_admin_role).to_string()) + .set("role", Hex(&evt.role).to_string()); + }); + events.moonbird_role_granteds.iter().for_each(|evt| { + tables + .create_row("moonbird_role_granted", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()) + .set("role", Hex(&evt.role).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.moonbird_role_revokeds.iter().for_each(|evt| { + tables + .create_row("moonbird_role_revoked", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()) + .set("role", Hex(&evt.role).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.moonbird_transfers.iter().for_each(|evt| { + tables + .create_row("moonbird_transfer", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("from", Hex(&evt.from).to_string()) + .set("to", Hex(&evt.to).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_unnesteds.iter().for_each(|evt| { + tables + .create_row("moonbird_unnested", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_unpauseds.iter().for_each(|evt| { + tables + .create_row("moonbird_unpaused", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()); + }); +} +fn db_bayc_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.bayc_approvals.iter().for_each(|evt| { + tables + .create_row("bayc_approval", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", Hex(&evt.approved).to_string()) + .set("owner", Hex(&evt.owner).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.bayc_approval_for_alls.iter().for_each(|evt| { + tables + .create_row("bayc_approval_for_all", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", evt.approved) + .set("operator", Hex(&evt.operator).to_string()) + .set("owner", Hex(&evt.owner).to_string()); + }); + events.bayc_ownership_transferreds.iter().for_each(|evt| { + tables + .create_row("bayc_ownership_transferred", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("previous_owner", Hex(&evt.previous_owner).to_string()); + }); + events.bayc_transfers.iter().for_each(|evt| { + tables + .create_row("bayc_transfer", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("from", Hex(&evt.from).to_string()) + .set("to", Hex(&evt.to).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); +} + + +fn graph_moonbird_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.moonbird_approvals.iter().for_each(|evt| { + tables + .create_row("moonbird_approval", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", Hex(&evt.approved).to_string()) + .set("owner", Hex(&evt.owner).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_approval_for_alls.iter().for_each(|evt| { + tables + .create_row("moonbird_approval_for_all", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", evt.approved) + .set("operator", Hex(&evt.operator).to_string()) + .set("owner", Hex(&evt.owner).to_string()); + }); + events.moonbird_expelleds.iter().for_each(|evt| { + tables + .create_row("moonbird_expelled", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_nesteds.iter().for_each(|evt| { + tables + .create_row("moonbird_nested", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_ownership_transferreds.iter().for_each(|evt| { + tables + .create_row("moonbird_ownership_transferred", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("previous_owner", Hex(&evt.previous_owner).to_string()); + }); + events.moonbird_pauseds.iter().for_each(|evt| { + tables + .create_row("moonbird_paused", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()); + }); + events.moonbird_refunds.iter().for_each(|evt| { + tables + .create_row("moonbird_refund", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("buyer", Hex(&evt.buyer).to_string()); + }); + events.moonbird_revenues.iter().for_each(|evt| { + tables + .create_row("moonbird_revenue", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("amount", BigDecimal::from_str(&evt.amount).unwrap()) + .set("beneficiary", Hex(&evt.beneficiary).to_string()) + .set("num_purchased", BigDecimal::from_str(&evt.num_purchased).unwrap()); + }); + events.moonbird_role_admin_changeds.iter().for_each(|evt| { + tables + .create_row("moonbird_role_admin_changed", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_admin_role", Hex(&evt.new_admin_role).to_string()) + .set("previous_admin_role", Hex(&evt.previous_admin_role).to_string()) + .set("role", Hex(&evt.role).to_string()); + }); + events.moonbird_role_granteds.iter().for_each(|evt| { + tables + .create_row("moonbird_role_granted", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()) + .set("role", Hex(&evt.role).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.moonbird_role_revokeds.iter().for_each(|evt| { + tables + .create_row("moonbird_role_revoked", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()) + .set("role", Hex(&evt.role).to_string()) + .set("sender", Hex(&evt.sender).to_string()); + }); + events.moonbird_transfers.iter().for_each(|evt| { + tables + .create_row("moonbird_transfer", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("from", Hex(&evt.from).to_string()) + .set("to", Hex(&evt.to).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_unnesteds.iter().for_each(|evt| { + tables + .create_row("moonbird_unnested", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.moonbird_unpauseds.iter().for_each(|evt| { + tables + .create_row("moonbird_unpaused", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("account", Hex(&evt.account).to_string()); + }); +} +fn graph_bayc_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.bayc_approvals.iter().for_each(|evt| { + tables + .create_row("bayc_approval", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", Hex(&evt.approved).to_string()) + .set("owner", Hex(&evt.owner).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.bayc_approval_for_alls.iter().for_each(|evt| { + tables + .create_row("bayc_approval_for_all", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", evt.approved) + .set("operator", Hex(&evt.operator).to_string()) + .set("owner", Hex(&evt.owner).to_string()); + }); + events.bayc_ownership_transferreds.iter().for_each(|evt| { + tables + .create_row("bayc_ownership_transferred", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("previous_owner", Hex(&evt.previous_owner).to_string()); + }); + events.bayc_transfers.iter().for_each(|evt| { + tables + .create_row("bayc_transfer", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("from", Hex(&evt.from).to_string()) + .set("to", Hex(&evt.to).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); +} + +#[substreams::handlers::map] +fn map_events(blk: eth::Block) -> Result { + let mut events = contract::Events::default(); + map_moonbird_events(&blk, &mut events); + map_bayc_events(&blk, &mut events); + Ok(events) +} + +#[substreams::handlers::map] +fn db_out(events: contract::Events) -> Result { + // Initialize Database Changes container + let mut tables = DatabaseChangeTables::new(); + db_moonbird_out(&events, &mut tables); + db_bayc_out(&events, &mut tables); + Ok(tables.to_database_changes()) +} + +#[substreams::handlers::map] +fn graph_out(events: contract::Events) -> Result { + // Initialize Database Changes container + let mut tables = EntityChangesTables::new(); + graph_moonbird_out(&events, &mut tables); + graph_bayc_out(&events, &mut tables); + Ok(tables.to_entity_changes()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/pb/contract.v1.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/pb/contract.v1.rs new file mode 100755 index 0000000..6e8ab4d --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/pb/contract.v1.rs @@ -0,0 +1,340 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Events { + #[prost(message, repeated, tag="1")] + pub moonbird_approvals: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub moonbird_approval_for_alls: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub moonbird_expelleds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub moonbird_nesteds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="5")] + pub moonbird_ownership_transferreds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="6")] + pub moonbird_pauseds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="7")] + pub moonbird_refunds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="8")] + pub moonbird_revenues: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="9")] + pub moonbird_role_admin_changeds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="10")] + pub moonbird_role_granteds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="11")] + pub moonbird_role_revokeds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="12")] + pub moonbird_transfers: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="13")] + pub moonbird_unnesteds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="14")] + pub moonbird_unpauseds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="15")] + pub bayc_approvals: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="16")] + pub bayc_approval_for_alls: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="17")] + pub bayc_ownership_transferreds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="18")] + pub bayc_transfers: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdApproval { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub approved: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdApprovalForAll { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub operator: ::prost::alloc::vec::Vec, + #[prost(bool, tag="7")] + pub approved: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdExpelled { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(string, tag="5")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdNested { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(string, tag="5")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdOwnershipTransferred { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub previous_owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub new_owner: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdPaused { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub account: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdRefund { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub buyer: ::prost::alloc::vec::Vec, + #[prost(string, tag="6")] + pub amount: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdRevenue { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub beneficiary: ::prost::alloc::vec::Vec, + #[prost(string, tag="6")] + pub num_purchased: ::prost::alloc::string::String, + #[prost(string, tag="7")] + pub amount: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdRoleAdminChanged { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub role: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub previous_admin_role: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="7")] + pub new_admin_role: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdRoleGranted { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub role: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub account: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="7")] + pub sender: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdRoleRevoked { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub role: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub account: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="7")] + pub sender: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdTransfer { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub from: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub to: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdUnnested { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(string, tag="5")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MoonbirdUnpaused { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub account: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycApproval { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub approved: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycApprovalForAll { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub operator: ::prost::alloc::vec::Vec, + #[prost(bool, tag="7")] + pub approved: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycOwnershipTransferred { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub previous_owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub new_owner: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycTransfer { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub from: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub to: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub token_id: ::prost::alloc::string::String, +} +// @@protoc_insertion_point(module) diff --git a/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/pb/mod.rs b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/pb/mod.rs new file mode 100755 index 0000000..611ea83 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/results/multiple_contracts/src/pb/mod.rs @@ -0,0 +1,8 @@ +// @generated +pub mod contract { + // @@protoc_insertion_point(attribute:contract.v1) + pub mod v1 { + include!("contract.v1.rs"); + // @@protoc_insertion_point(contract.v1) + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/rust-toolchain.toml b/firehose/substreams/codegen/templates/ethereum/rust-toolchain.toml new file mode 100644 index 0000000..ec334c0 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.65" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/schema.clickhouse.sql b/firehose/substreams/codegen/templates/ethereum/schema.clickhouse.sql new file mode 100644 index 0000000..3d8db4a --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/schema.clickhouse.sql @@ -0,0 +1,36 @@ +CREATE TABLE IF NOT EXISTS bayc_approval ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "approved" VARCHAR(40), + "owner" VARCHAR(40), + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_approval_for_all ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "approved" BOOL, + "operator" VARCHAR(40), + "owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_ownership_transferred ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "new_owner" VARCHAR(40), + "previous_owner" VARCHAR(40) +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +CREATE TABLE IF NOT EXISTS bayc_transfer ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "from" VARCHAR(40), + "to" VARCHAR(40), + "token_id" UInt256 +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); + diff --git a/firehose/substreams/codegen/templates/ethereum/schema.clickhouse.sql.gotmpl b/firehose/substreams/codegen/templates/ethereum/schema.clickhouse.sql.gotmpl new file mode 100644 index 0000000..af0512c --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/schema.clickhouse.sql.gotmpl @@ -0,0 +1,68 @@ +{{- range $idx, $contract := .ethereumContracts }} +{{- range $event := $contract.GetEvents }} +{{- $rust := $event.Rust }} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap -}} +CREATE TABLE IF NOT EXISTS {{ $contract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64{{ if ne $numberOfAttributes 0 }},{{ end -}} + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldClickhouseMap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}{{ if eq $i $numberOfAttributes }}{{ else }},{{ end }} + {{- end}} +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +{{ end }} +{{- if $contract.HasCalls }} +{{- range $call := $contract.GetCalls }} +{{- $rust := $call.Rust }} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }} +CREATE TABLE IF NOT EXISTS {{ $contract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL{{ if ne $numberOfAttributes 0 }},{{ end -}} + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldClickhouseMap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}{{ if eq $i $numberOfAttributes }}{{ else }},{{ end }} + {{- end}} +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +{{- end }} +{{- end }} +{{- range $ddsContract := $contract.GetDDS }} +{{- range $event := $ddsContract.GetEvents }} +{{- $rust := $event.Rust }} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }} +CREATE TABLE IF NOT EXISTS {{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" UInt64, + "evt_address" VARCHAR(40){{ if ne $numberOfAttributes 0 }},{{ end -}} + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldClickhouseMap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}{{ if eq $i $numberOfAttributes }}{{ else }},{{ end }} + {{- end}} +) ENGINE = MergeTree PRIMARY KEY ("evt_tx_hash","evt_index"); +{{- end }} +{{- if $ddsContract.HasCalls }} +{{- range $call := $ddsContract.GetCalls }} +{{- $rust := $call.Rust }} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }} +CREATE TABLE IF NOT EXISTS {{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" UInt64, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40){{ if ne $numberOfAttributes 0 }},{{ end -}} + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldClickhouseMap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}{{ if eq $i $numberOfAttributes }}{{ else }},{{ end }} + {{- end}} +) ENGINE = MergeTree PRIMARY KEY ("call_tx_hash","call_ordinal"); +{{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/firehose/substreams/codegen/templates/ethereum/schema.graphql b/firehose/substreams/codegen/templates/ethereum/schema.graphql new file mode 100644 index 0000000..2dfe54f --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/schema.graphql @@ -0,0 +1,39 @@ +type bayc_approval @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + approved: String! + owner: String! + token_id: BigDecimal! +} +type bayc_approval_for_all @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + approved: Boolean! + operator: String! + owner: String! +} +type bayc_ownership_transferred @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + new_owner: String! + previous_owner: String! +} +type bayc_transfer @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + from: String! + to: String! + token_id: BigDecimal! +} diff --git a/firehose/substreams/codegen/templates/ethereum/schema.graphql.gotmpl b/firehose/substreams/codegen/templates/ethereum/schema.graphql.gotmpl new file mode 100644 index 0000000..bd005ee --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/schema.graphql.gotmpl @@ -0,0 +1,71 @@ +{{- range $idx, $contract := .ethereumContracts }} +{{- range $event := $contract.GetEvents -}} +{{- $rust := $event.Rust -}} +type {{ $contract.GetName }}_{{ $rust.TableChangeEntityName }} @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + {{- $i := 0 }} + {{- range $fieldName, $graphqlType := $rust.ProtoFieldGraphQLMap }} + {{ $i = add $i 1 }}{{ $fieldName }}: {{ $graphqlType }} + {{- end}} +} +{{ end }} +{{- if $contract.HasCalls -}} +{{- range $call := $contract.GetCalls -}} +{{- $rust := $call.Rust -}} +type {{ $contract.GetName }}_{{ $rust.TableChangeEntityName }} @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + {{- $i := 0 }} + {{- range $fieldName, $graphqlType := $rust.ProtoFieldGraphQLMap }} + {{ $i = add $i 1 }}{{ $fieldName }}: {{ $graphqlType }} + {{- end}} +} +{{ end }} +{{ end }} + +{{- range $ddsContract := $contract.GetDDS -}} +{{- range $event := $ddsContract.GetEvents -}} +{{- $rust := $event.Rust -}} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }} +type {{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }} @entity { + id: ID! + evt_tx_hash: String! + evt_index: BigInt! + evt_block_time: String! + evt_block_number: BigInt! + evt_address: String! + {{- $i := 0 }} + {{- range $fieldName, $graphqlType := $rust.ProtoFieldGraphQLMap }} + {{ $i = add $i 1 }}{{ $fieldName }}: {{ $graphqlType }} + {{- end}} +} +{{- end -}} +{{- if $ddsContract.HasCalls -}} +{{- range $call := $ddsContract.GetCalls -}} +{{- $rust := $call.Rust -}} +type {{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }} @entity { + id: ID! + call_tx_hash: String! + call_block_time: String! + call_block_number: BigInt! + call_ordinal: BigInt! + call_success: Boolean! + call_address: String! + {{- $i := 0 }} + {{- range $fieldName, $graphqlType := $rust.ProtoFieldGraphQLMap }} + {{ $i = add $i 1 }}{{ $fieldName }}: {{ $graphqlType }} + {{- end}} +} +{{ end }} +{{ end }} + +{{- end -}} +{{- end }} \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/schema.sql b/firehose/substreams/codegen/templates/ethereum/schema.sql new file mode 100644 index 0000000..c46f0eb --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/schema.sql @@ -0,0 +1,40 @@ +CREATE TABLE IF NOT EXISTS bayc_approval ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "approved" VARCHAR(40), + "owner" VARCHAR(40), + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_approval_for_all ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "approved" BOOL, + "operator" VARCHAR(40), + "owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_ownership_transferred ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "new_owner" VARCHAR(40), + "previous_owner" VARCHAR(40), + PRIMARY KEY(evt_tx_hash,evt_index) +); +CREATE TABLE IF NOT EXISTS bayc_transfer ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "from" VARCHAR(40), + "to" VARCHAR(40), + "token_id" DECIMAL, + PRIMARY KEY(evt_tx_hash,evt_index) +); + diff --git a/firehose/substreams/codegen/templates/ethereum/schema.sql.gotmpl b/firehose/substreams/codegen/templates/ethereum/schema.sql.gotmpl new file mode 100644 index 0000000..2194962 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/schema.sql.gotmpl @@ -0,0 +1,72 @@ +{{- range $idx, $contract := .ethereumContracts -}} +{{- range $event := $contract.GetEvents -}} +{{- $rust := $event.Rust -}} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap -}} +CREATE TABLE IF NOT EXISTS {{ $contract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldSqlmap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}, + {{- end}} + PRIMARY KEY(evt_tx_hash,evt_index) +); +{{ end }} +{{- if $contract.HasCalls -}} +{{- range $call := $contract.GetCalls -}} +{{- $rust := $call.Rust -}} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap -}} +CREATE TABLE IF NOT EXISTS {{ $contract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldSqlmap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}, + {{- end}} + PRIMARY KEY(call_tx_hash,call_ordinal) +); +{{ end }} +{{ end }} +{{- range $ddsContract := $contract.GetDDS -}} +{{- range $event := $ddsContract.GetEvents -}} +{{- $rust := $event.Rust -}} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }} +CREATE TABLE IF NOT EXISTS {{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "evt_tx_hash" VARCHAR(64), + "evt_index" INT, + "evt_block_time" TIMESTAMP, + "evt_block_number" DECIMAL, + "evt_address" VARCHAR(40), + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldSqlmap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}, + {{- end}} + PRIMARY KEY(evt_tx_hash,evt_index) +); +{{- end -}} +{{- if $ddsContract.HasCalls -}} +{{- range $call := $ddsContract.GetCalls -}} +{{- $rust := $call.Rust -}} +{{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap -}} +CREATE TABLE IF NOT EXISTS {{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }} ( + "call_tx_hash" VARCHAR(64), + "call_block_time" TIMESTAMP, + "call_block_number" DECIMAL, + "call_ordinal" INT, + "call_success" BOOL, + "call_address" VARCHAR(40), + {{- $i := 0 }} + {{- range $fieldName, $sqlType := $rust.ProtoFieldSqlmap }} + {{ $i = add $i 1 }}{{ $fieldName }} {{ $sqlType }}, + {{- end}} + PRIMARY KEY(call_tx_hash,call_ordinal) +); +{{ end }} +{{ end }} +{{- end }} +{{- end }} diff --git a/firehose/substreams/codegen/templates/ethereum/src/abi/bayc_contract.rs b/firehose/substreams/codegen/templates/ethereum/src/abi/bayc_contract.rs new file mode 100644 index 0000000..47a94df --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/abi/bayc_contract.rs @@ -0,0 +1,3619 @@ + const INTERNAL_ERR: &'static str = "`ethabi_derive` internal error"; + /// Contract's functions. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod functions { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct BaycProvenance {} + impl BaycProvenance { + const METHOD_ID: [u8; 4] = [96u8, 126u8, 32u8, 227u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BaycProvenance { + const NAME: &'static str = "BAYC_PROVENANCE"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for BaycProvenance { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MaxApes {} + impl MaxApes { + const METHOD_ID: [u8; 4] = [187u8, 138u8, 22u8, 189u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for MaxApes { + const NAME: &'static str = "MAX_APES"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for MaxApes { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RevealTimestamp {} + impl RevealTimestamp { + const METHOD_ID: [u8; 4] = [24u8, 226u8, 10u8, 56u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for RevealTimestamp { + const NAME: &'static str = "REVEAL_TIMESTAMP"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for RevealTimestamp { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ApePrice {} + impl ApePrice { + const METHOD_ID: [u8; 4] = [122u8, 63u8, 69u8, 30u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for ApePrice { + const NAME: &'static str = "apePrice"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for ApePrice { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Approve { + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Approve { + const METHOD_ID: [u8; 4] = [9u8, 94u8, 167u8, 179u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Approve { + const NAME: &'static str = "approve"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BalanceOf { + pub owner: Vec, + } + impl BalanceOf { + const METHOD_ID: [u8; 4] = [112u8, 160u8, 130u8, 49u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::Address(ethabi::Address::from_slice(&self.owner))], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BalanceOf { + const NAME: &'static str = "balanceOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for BalanceOf { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct BaseUri {} + impl BaseUri { + const METHOD_ID: [u8; 4] = [108u8, 3u8, 96u8, 235u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for BaseUri { + const NAME: &'static str = "baseURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for BaseUri { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct EmergencySetStartingIndexBlock {} + impl EmergencySetStartingIndexBlock { + const METHOD_ID: [u8; 4] = [125u8, 23u8, 252u8, 190u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for EmergencySetStartingIndexBlock { + const NAME: &'static str = "emergencySetStartingIndexBlock"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct FlipSaleState {} + impl FlipSaleState { + const METHOD_ID: [u8; 4] = [52u8, 145u8, 141u8, 253u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for FlipSaleState { + const NAME: &'static str = "flipSaleState"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct GetApproved { + pub token_id: substreams::scalar::BigInt, + } + impl GetApproved { + const METHOD_ID: [u8; 4] = [8u8, 24u8, 18u8, 252u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for GetApproved { + const NAME: &'static str = "getApproved"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for GetApproved { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct IsApprovedForAll { + pub owner: Vec, + pub operator: Vec, + } + impl IsApprovedForAll { + const METHOD_ID: [u8; 4] = [233u8, 133u8, 233u8, 197u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Address( + ethabi::Address::from_slice(&self.operator), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for IsApprovedForAll { + const NAME: &'static str = "isApprovedForAll"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for IsApprovedForAll { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MaxApePurchase {} + impl MaxApePurchase { + const METHOD_ID: [u8; 4] = [87u8, 29u8, 255u8, 59u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for MaxApePurchase { + const NAME: &'static str = "maxApePurchase"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for MaxApePurchase { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct MintApe { + pub number_of_tokens: substreams::scalar::BigInt, + } + impl MintApe { + const METHOD_ID: [u8; 4] = [167u8, 35u8, 83u8, 62u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + number_of_tokens: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.number_of_tokens.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for MintApe { + const NAME: &'static str = "mintApe"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Name {} + impl Name { + const METHOD_ID: [u8; 4] = [6u8, 253u8, 222u8, 3u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Name { + const NAME: &'static str = "name"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Name { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Owner {} + impl Owner { + const METHOD_ID: [u8; 4] = [141u8, 165u8, 203u8, 91u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Owner { + const NAME: &'static str = "owner"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for Owner { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnerOf { + pub token_id: substreams::scalar::BigInt, + } + impl OwnerOf { + const METHOD_ID: [u8; 4] = [99u8, 82u8, 33u8, 30u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result, String> { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result, String> { + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option> { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for OwnerOf { + const NAME: &'static str = "ownerOf"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable> for OwnerOf { + fn output(data: &[u8]) -> Result, String> { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct RenounceOwnership {} + impl RenounceOwnership { + const METHOD_ID: [u8; 4] = [113u8, 80u8, 24u8, 166u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for RenounceOwnership { + const NAME: &'static str = "renounceOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ReserveApes {} + impl ReserveApes { + const METHOD_ID: [u8; 4] = [176u8, 246u8, 116u8, 39u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for ReserveApes { + const NAME: &'static str = "reserveApes"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferFrom1 { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl SafeTransferFrom1 { + const METHOD_ID: [u8; 4] = [66u8, 132u8, 46u8, 14u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferFrom1 { + const NAME: &'static str = "safeTransferFrom1"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SafeTransferFrom2 { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + pub u_data: Vec, + } + impl SafeTransferFrom2 { + const METHOD_ID: [u8; 4] = [184u8, 141u8, 79u8, 222u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ethabi::ParamType::Bytes, + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + u_data: values + .pop() + .expect(INTERNAL_ERR) + .into_bytes() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ethabi::Token::Bytes(self.u_data.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SafeTransferFrom2 { + const NAME: &'static str = "safeTransferFrom2"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SaleIsActive {} + impl SaleIsActive { + const METHOD_ID: [u8; 4] = [235u8, 141u8, 36u8, 68u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SaleIsActive { + const NAME: &'static str = "saleIsActive"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SaleIsActive { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetApprovalForAll { + pub operator: Vec, + pub approved: bool, + } + impl SetApprovalForAll { + const METHOD_ID: [u8; 4] = [162u8, 44u8, 180u8, 101u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Bool], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + operator: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.operator), + ), + ethabi::Token::Bool(self.approved.clone()), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetApprovalForAll { + const NAME: &'static str = "setApprovalForAll"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetBaseUri { + pub base_uri: String, + } + impl SetBaseUri { + const METHOD_ID: [u8; 4] = [85u8, 248u8, 4u8, 179u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + base_uri: values + .pop() + .expect(INTERNAL_ERR) + .into_string() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::String(self.base_uri.clone())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetBaseUri { + const NAME: &'static str = "setBaseURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetProvenanceHash { + pub provenance_hash: String, + } + impl SetProvenanceHash { + const METHOD_ID: [u8; 4] = [16u8, 150u8, 149u8, 35u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + provenance_hash: values + .pop() + .expect(INTERNAL_ERR) + .into_string() + .expect(INTERNAL_ERR), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::String(self.provenance_hash.clone())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetProvenanceHash { + const NAME: &'static str = "setProvenanceHash"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetRevealTimestamp { + pub reveal_time_stamp: substreams::scalar::BigInt, + } + impl SetRevealTimestamp { + const METHOD_ID: [u8; 4] = [1u8, 138u8, 44u8, 55u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + reveal_time_stamp: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.reveal_time_stamp.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetRevealTimestamp { + const NAME: &'static str = "setRevealTimestamp"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SetStartingIndex {} + impl SetStartingIndex { + const METHOD_ID: [u8; 4] = [233u8, 134u8, 101u8, 80u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for SetStartingIndex { + const NAME: &'static str = "setStartingIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct StartingIndex {} + impl StartingIndex { + const METHOD_ID: [u8; 4] = [203u8, 119u8, 77u8, 71u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for StartingIndex { + const NAME: &'static str = "startingIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for StartingIndex { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct StartingIndexBlock {} + impl StartingIndexBlock { + const METHOD_ID: [u8; 4] = [227u8, 109u8, 100u8, 152u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for StartingIndexBlock { + const NAME: &'static str = "startingIndexBlock"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for StartingIndexBlock { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct SupportsInterface { + pub interface_id: [u8; 4usize], + } + impl SupportsInterface { + const METHOD_ID: [u8; 4] = [1u8, 255u8, 201u8, 167u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::FixedBytes(4usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + interface_id: { + let mut result = [0u8; 4]; + let v = values + .pop() + .expect(INTERNAL_ERR) + .into_fixed_bytes() + .expect(INTERNAL_ERR); + result.copy_from_slice(&v); + result + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ethabi::Token::FixedBytes(self.interface_id.as_ref().to_vec())], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_bool() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for SupportsInterface { + const NAME: &'static str = "supportsInterface"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for SupportsInterface { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Symbol {} + impl Symbol { + const METHOD_ID: [u8; 4] = [149u8, 216u8, 155u8, 65u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for Symbol { + const NAME: &'static str = "symbol"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for Symbol { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenByIndex { + pub index: substreams::scalar::BigInt, + } + impl TokenByIndex { + const METHOD_ID: [u8; 4] = [79u8, 108u8, 204u8, 231u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + index: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.index.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenByIndex { + const NAME: &'static str = "tokenByIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TokenByIndex { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenOfOwnerByIndex { + pub owner: Vec, + pub index: substreams::scalar::BigInt, + } + impl TokenOfOwnerByIndex { + const METHOD_ID: [u8; 4] = [47u8, 116u8, 92u8, 89u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address, ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + index: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.owner)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.index.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenOfOwnerByIndex { + const NAME: &'static str = "tokenOfOwnerByIndex"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TokenOfOwnerByIndex { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TokenUri { + pub token_id: substreams::scalar::BigInt, + } + impl TokenUri { + const METHOD_ID: [u8; 4] = [200u8, 123u8, 86u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::String], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok( + values + .pop() + .expect("one output data should have existed") + .into_string() + .expect(INTERNAL_ERR), + ) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TokenUri { + const NAME: &'static str = "tokenURI"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable for TokenUri { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TotalSupply {} + impl TotalSupply { + const METHOD_ID: [u8; 4] = [24u8, 22u8, 13u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn output_call( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::output(call.return_data.as_ref()) + } + pub fn output(data: &[u8]) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + data.as_ref(), + ) + .map_err(|e| format!("unable to decode output data: {:?}", e))?; + Ok({ + let mut v = [0 as u8; 32]; + values + .pop() + .expect("one output data should have existed") + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }) + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + pub fn call(&self, address: Vec) -> Option { + use substreams_ethereum::pb::eth::rpc; + let rpc_calls = rpc::RpcCalls { + calls: vec![ + rpc::RpcCall { to_addr : address, data : self.encode(), } + ], + }; + let responses = substreams_ethereum::rpc::eth_call(&rpc_calls).responses; + let response = responses + .get(0) + .expect("one response should have existed"); + if response.failed { + return None; + } + match Self::output(response.raw.as_ref()) { + Ok(data) => Some(data), + Err(err) => { + use substreams_ethereum::Function; + substreams::log::info!( + "Call output for function `{}` failed to decode with error: {}", + Self::NAME, err + ); + None + } + } + } + } + impl substreams_ethereum::Function for TotalSupply { + const NAME: &'static str = "totalSupply"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + impl substreams_ethereum::rpc::RPCDecodable + for TotalSupply { + fn output(data: &[u8]) -> Result { + Self::output(data) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferFrom { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl TransferFrom { + const METHOD_ID: [u8; 4] = [35u8, 184u8, 114u8, 221u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ + ethabi::ParamType::Address, + ethabi::ParamType::Address, + ethabi::ParamType::Uint(256usize), + ], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + from: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + values + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address(ethabi::Address::from_slice(&self.from)), + ethabi::Token::Address(ethabi::Address::from_slice(&self.to)), + ethabi::Token::Uint( + ethabi::Uint::from_big_endian( + match self.token_id.clone().to_bytes_be() { + (num_bigint::Sign::Plus, bytes) => bytes, + (num_bigint::Sign::NoSign, bytes) => bytes, + (num_bigint::Sign::Minus, _) => { + panic!("negative numbers are not supported") + } + } + .as_slice(), + ), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferFrom { + const NAME: &'static str = "transferFrom"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct TransferOwnership { + pub new_owner: Vec, + } + impl TransferOwnership { + const METHOD_ID: [u8; 4] = [242u8, 253u8, 227u8, 139u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + let maybe_data = call.input.get(4..); + if maybe_data.is_none() { + return Err("no data to decode".to_string()); + } + let mut values = ethabi::decode( + &[ethabi::ParamType::Address], + maybe_data.unwrap(), + ) + .map_err(|e| format!("unable to decode call.input: {:?}", e))?; + values.reverse(); + Ok(Self { + new_owner: values + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode( + &[ + ethabi::Token::Address( + ethabi::Address::from_slice(&self.new_owner), + ), + ], + ); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for TransferOwnership { + const NAME: &'static str = "transferOwnership"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Withdraw {} + impl Withdraw { + const METHOD_ID: [u8; 4] = [60u8, 207u8, 214u8, 11u8]; + pub fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Ok(Self {}) + } + pub fn encode(&self) -> Vec { + let data = ethabi::encode(&[]); + let mut encoded = Vec::with_capacity(4 + data.len()); + encoded.extend(Self::METHOD_ID); + encoded.extend(data); + encoded + } + pub fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + match call.input.get(0..4) { + Some(signature) => Self::METHOD_ID == signature, + None => false, + } + } + } + impl substreams_ethereum::Function for Withdraw { + const NAME: &'static str = "withdraw"; + fn match_call(call: &substreams_ethereum::pb::eth::v2::Call) -> bool { + Self::match_call(call) + } + fn decode( + call: &substreams_ethereum::pb::eth::v2::Call, + ) -> Result { + Self::decode(call) + } + fn encode(&self) -> Vec { + self.encode() + } + } + } + /// Contract's events. + #[allow(dead_code, unused_imports, unused_variables)] + pub mod events { + use super::INTERNAL_ERR; + #[derive(Debug, Clone, PartialEq)] + pub struct Approval { + pub owner: Vec, + pub approved: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Approval { + const TOPIC_ID: [u8; 32] = [ + 140u8, + 91u8, + 225u8, + 229u8, + 235u8, + 236u8, + 125u8, + 91u8, + 209u8, + 79u8, + 113u8, + 66u8, + 125u8, + 30u8, + 132u8, + 243u8, + 221u8, + 3u8, + 20u8, + 192u8, + 247u8, + 178u8, + 41u8, + 30u8, + 91u8, + 32u8, + 10u8, + 200u8, + 199u8, + 195u8, + 185u8, + 37u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'approved' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Approval { + const NAME: &'static str = "Approval"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct ApprovalForAll { + pub owner: Vec, + pub operator: Vec, + pub approved: bool, + } + impl ApprovalForAll { + const TOPIC_ID: [u8; 32] = [ + 23u8, + 48u8, + 126u8, + 171u8, + 57u8, + 171u8, + 97u8, + 7u8, + 232u8, + 137u8, + 152u8, + 69u8, + 173u8, + 61u8, + 89u8, + 189u8, + 150u8, + 83u8, + 242u8, + 0u8, + 242u8, + 32u8, + 146u8, + 4u8, + 137u8, + 202u8, + 43u8, + 89u8, + 55u8, + 105u8, + 108u8, + 49u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 32usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + let mut values = ethabi::decode( + &[ethabi::ParamType::Bool], + log.data.as_ref(), + ) + .map_err(|e| format!("unable to decode log.data: {:?}", e))?; + values.reverse(); + Ok(Self { + owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + operator: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'operator' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + approved: values + .pop() + .expect(INTERNAL_ERR) + .into_bool() + .expect(INTERNAL_ERR), + }) + } + } + impl substreams_ethereum::Event for ApprovalForAll { + const NAME: &'static str = "ApprovalForAll"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct OwnershipTransferred { + pub previous_owner: Vec, + pub new_owner: Vec, + } + impl OwnershipTransferred { + const TOPIC_ID: [u8; 32] = [ + 139u8, + 224u8, + 7u8, + 156u8, + 83u8, + 22u8, + 89u8, + 20u8, + 19u8, + 68u8, + 205u8, + 31u8, + 208u8, + 164u8, + 242u8, + 132u8, + 25u8, + 73u8, + 127u8, + 151u8, + 34u8, + 163u8, + 218u8, + 175u8, + 227u8, + 180u8, + 24u8, + 111u8, + 107u8, + 100u8, + 87u8, + 224u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 3usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + previous_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'previous_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + new_owner: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'new_owner' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + }) + } + } + impl substreams_ethereum::Event for OwnershipTransferred { + const NAME: &'static str = "OwnershipTransferred"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + #[derive(Debug, Clone, PartialEq)] + pub struct Transfer { + pub from: Vec, + pub to: Vec, + pub token_id: substreams::scalar::BigInt, + } + impl Transfer { + const TOPIC_ID: [u8; 32] = [ + 221u8, + 242u8, + 82u8, + 173u8, + 27u8, + 226u8, + 200u8, + 155u8, + 105u8, + 194u8, + 176u8, + 104u8, + 252u8, + 55u8, + 141u8, + 170u8, + 149u8, + 43u8, + 167u8, + 241u8, + 99u8, + 196u8, + 161u8, + 22u8, + 40u8, + 245u8, + 90u8, + 77u8, + 245u8, + 35u8, + 179u8, + 239u8, + ]; + pub fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + if log.topics.len() != 4usize { + return false; + } + if log.data.len() != 0usize { + return false; + } + return log.topics.get(0).expect("bounds already checked").as_ref() + == Self::TOPIC_ID; + } + pub fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Ok(Self { + from: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[1usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'from' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + to: ethabi::decode( + &[ethabi::ParamType::Address], + log.topics[2usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'to' from topic of type 'address': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_address() + .expect(INTERNAL_ERR) + .as_bytes() + .to_vec(), + token_id: { + let mut v = [0 as u8; 32]; + ethabi::decode( + &[ethabi::ParamType::Uint(256usize)], + log.topics[3usize].as_ref(), + ) + .map_err(|e| { + format!( + "unable to decode param 'token_id' from topic of type 'uint256': {:?}", + e + ) + })? + .pop() + .expect(INTERNAL_ERR) + .into_uint() + .expect(INTERNAL_ERR) + .to_big_endian(v.as_mut_slice()); + substreams::scalar::BigInt::from_unsigned_bytes_be(&v) + }, + }) + } + } + impl substreams_ethereum::Event for Transfer { + const NAME: &'static str = "Transfer"; + fn match_log(log: &substreams_ethereum::pb::eth::v2::Log) -> bool { + Self::match_log(log) + } + fn decode( + log: &substreams_ethereum::pb::eth::v2::Log, + ) -> Result { + Self::decode(log) + } + } + } \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/src/abi/mod.rs b/firehose/substreams/codegen/templates/ethereum/src/abi/mod.rs new file mode 100644 index 0000000..cbe2c3a --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/abi/mod.rs @@ -0,0 +1,2 @@ + +pub mod bayc_contract; \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/src/abi/mod.rs.gotmpl b/firehose/substreams/codegen/templates/ethereum/src/abi/mod.rs.gotmpl new file mode 100644 index 0000000..5c18fbc --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/abi/mod.rs.gotmpl @@ -0,0 +1,6 @@ +{{- range $i, $contract := .ethereumContracts }} +pub mod {{ $contract.GetName }}_contract; +{{- range $ddsContract := $contract.GetDDS }} +pub mod {{ $ddsContract.GetName }}_contract; +{{- end }} +{{- end }} \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/ethereum/src/lib.rs b/firehose/substreams/codegen/templates/ethereum/src/lib.rs new file mode 100755 index 0000000..f3f7510 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/lib.rs @@ -0,0 +1,220 @@ +mod abi; +mod pb; +use hex_literal::hex; +use pb::contract::v1 as contract; +use substreams::Hex; +use substreams_database_change::pb::database::DatabaseChanges; +use substreams_database_change::tables::Tables as DatabaseChangeTables; +use substreams_entity_change::pb::entity::EntityChanges; +use substreams_entity_change::tables::Tables as EntityChangesTables; +use substreams_ethereum::pb::eth::v2 as eth; +use substreams_ethereum::Event; + +#[allow(unused_imports)] +use num_traits::cast::ToPrimitive; +use std::str::FromStr; +use substreams::scalar::BigDecimal; + +substreams_ethereum::init!(); + +const BAYC_TRACKED_CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); + +fn map_bayc_events(blk: ð::Block, events: &mut contract::Events) { + events.bayc_approvals.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::Approval::match_and_decode(log) { + return Some(contract::BaycApproval { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + approved: event.approved, + owner: event.owner, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); + events.bayc_approval_for_alls.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::ApprovalForAll::match_and_decode(log) { + return Some(contract::BaycApprovalForAll { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + approved: event.approved, + operator: event.operator, + owner: event.owner, + }); + } + + None + }) + }) + .collect()); + events.bayc_ownership_transferreds.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::OwnershipTransferred::match_and_decode(log) { + return Some(contract::BaycOwnershipTransferred { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + new_owner: event.new_owner, + previous_owner: event.previous_owner, + }); + } + + None + }) + }) + .collect()); + events.bayc_transfers.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == BAYC_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::bayc_contract::events::Transfer::match_and_decode(log) { + return Some(contract::BaycTransfer { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + from: event.from, + to: event.to, + token_id: event.token_id.to_string(), + }); + } + + None + }) + }) + .collect()); +} + +fn db_bayc_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + events.bayc_approvals.iter().for_each(|evt| { + tables + .create_row("bayc_approval", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", Hex(&evt.approved).to_string()) + .set("owner", Hex(&evt.owner).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.bayc_approval_for_alls.iter().for_each(|evt| { + tables + .create_row("bayc_approval_for_all", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", evt.approved) + .set("operator", Hex(&evt.operator).to_string()) + .set("owner", Hex(&evt.owner).to_string()); + }); + events.bayc_ownership_transferreds.iter().for_each(|evt| { + tables + .create_row("bayc_ownership_transferred", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("previous_owner", Hex(&evt.previous_owner).to_string()); + }); + events.bayc_transfers.iter().for_each(|evt| { + tables + .create_row("bayc_transfer", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("from", Hex(&evt.from).to_string()) + .set("to", Hex(&evt.to).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); +} + + +fn graph_bayc_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + events.bayc_approvals.iter().for_each(|evt| { + tables + .create_row("bayc_approval", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", Hex(&evt.approved).to_string()) + .set("owner", Hex(&evt.owner).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); + events.bayc_approval_for_alls.iter().for_each(|evt| { + tables + .create_row("bayc_approval_for_all", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("approved", evt.approved) + .set("operator", Hex(&evt.operator).to_string()) + .set("owner", Hex(&evt.owner).to_string()); + }); + events.bayc_ownership_transferreds.iter().for_each(|evt| { + tables + .create_row("bayc_ownership_transferred", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("new_owner", Hex(&evt.new_owner).to_string()) + .set("previous_owner", Hex(&evt.previous_owner).to_string()); + }); + events.bayc_transfers.iter().for_each(|evt| { + tables + .create_row("bayc_transfer", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("from", Hex(&evt.from).to_string()) + .set("to", Hex(&evt.to).to_string()) + .set("token_id", BigDecimal::from_str(&evt.token_id).unwrap()); + }); +} + +#[substreams::handlers::map] +fn map_events(blk: eth::Block) -> Result { + let mut events = contract::Events::default(); + map_bayc_events(&blk, &mut events); + Ok(events) +} + +#[substreams::handlers::map] +fn db_out(events: contract::Events) -> Result { + // Initialize Database Changes container + let mut tables = DatabaseChangeTables::new(); + db_bayc_out(&events, &mut tables); + Ok(tables.to_database_changes()) +} + +#[substreams::handlers::map] +fn graph_out(events: contract::Events) -> Result { + // Initialize Database Changes container + let mut tables = EntityChangesTables::new(); + graph_bayc_out(&events, &mut tables); + Ok(tables.to_entity_changes()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/src/lib.rs.gotmpl b/firehose/substreams/codegen/templates/ethereum/src/lib.rs.gotmpl new file mode 100644 index 0000000..5cd9a0d --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/lib.rs.gotmpl @@ -0,0 +1,458 @@ +mod abi; +mod pb; +use hex_literal::hex; +use pb::contract::v1 as contract; +{{- if $.hasDDS }} +use substreams::prelude::*; +use substreams::store; +{{- end }} +use substreams::Hex; +use substreams_database_change::pb::database::DatabaseChanges; +use substreams_database_change::tables::Tables as DatabaseChangeTables; +use substreams_entity_change::pb::entity::EntityChanges; +use substreams_entity_change::tables::Tables as EntityChangesTables; +use substreams_ethereum::pb::eth::v2 as eth; +use substreams_ethereum::Event; + +#[allow(unused_imports)] +use num_traits::cast::ToPrimitive; +use std::str::FromStr; +use substreams::scalar::BigDecimal; + +substreams_ethereum::init!(); + +{{ range $i, $contract := .ethereumContracts -}} +const {{ toUpper $contract.GetName }}_TRACKED_CONTRACT: [u8; 20] = hex!("{{ $contract.GetAddress }}"); +{{ end }} + +{{- range $i, $contract := .ethereumContracts }} +fn map_{{ $contract.GetName }}_events(blk: ð::Block, events: &mut contract::Events) { + {{- range $event := $contract.GetEvents }} + {{- $rust := $event.Rust }} + events.{{ $contract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| log.address == {{ toUpper $contract.GetName }}_TRACKED_CONTRACT) + .filter_map(|log| { + if let Some(event) = abi::{{ $contract.GetName }}_contract::events::{{$rust.ABIStructName}}::match_and_decode(log) { + return Some(contract::{{ capitalizeFirst $contract.GetName }}{{$rust.ProtoMessageName}} { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + {{- range $protoField, $abiToProtoConversion := $rust.ProtoFieldABIConversionMap }} + {{$protoField}}: {{$abiToProtoConversion}}, + {{- end}} + }); + } + + None + }) + }) + .collect()); + {{- end }} +} +{{ if $contract.HasCalls }} +fn map_{{ $contract.GetName }}_calls(blk: ð::Block, calls: &mut contract::Calls) { + {{- range $call := $contract.GetCalls }} + {{- $rust := $call.Rust }} + calls.{{ $contract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| call.address == {{ toUpper $contract.GetName }}_TRACKED_CONTRACT && abi::{{ $contract.GetName }}_contract::functions::{{$rust.ABIStructName}}::match_call(call)) + .filter_map(|call| { + match abi::{{ $contract.GetName }}_contract::functions::{{$rust.ABIStructName}}::decode(call) { + Ok(decoded_call) => { + {{- if $rust.OutputFieldsString }} + let {{ $rust.OutputFieldsString }} = match abi::{{ $contract.GetName }}_contract::functions::{{$rust.ABIStructName}}::output(&call.return_data) { + Ok({{ $rust.OutputFieldsString }}) => {{`{`}}{{ $rust.OutputFieldsString }}{{`}`}} + Err(_) => Default::default(), + }; + {{ end }} + Some(contract::{{ capitalizeFirst $contract.GetName }}{{$rust.ProtoMessageName}} { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + {{- range $protoField, $abiToProtoConversion := $rust.ProtoFieldABIConversionMap }} + {{$protoField}}: {{$abiToProtoConversion}}, + {{- end}} + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + {{- end }} +} +{{ end }} + +{{- if $.hasDDS }} +fn is_declared_dds_address(addr: &Vec, ordinal: u64, dds_store: &store::StoreGetInt64) -> bool { + // substreams::log::info!("Checking if address {} is declared dds address", Hex(addr).to_string()); + if dds_store.get_at(ordinal, Hex(addr).to_string()).is_some() { + return true; + } + return false; +} +{{ end -}} + +{{- range $ddsContract := $contract.GetDDS }} +fn map_{{ $ddsContract.GetName }}_events( + blk: ð::Block, + dds_store: &store::StoreGetInt64, + events: &mut contract::Events, +) { + {{- range $event := $ddsContract.GetEvents }} + {{- $rust := $event.Rust }} + + events.{{ $ddsContract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.append(&mut blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter() + .filter(|log| is_declared_dds_address(&log.address, log.ordinal, dds_store)) + .filter_map(|log| { + if let Some(event) = abi::{{ $ddsContract.GetName }}_contract::events::{{$rust.ABIStructName}}::match_and_decode(log) { + return Some(contract::{{ capitalizeFirst $ddsContract.GetName }}{{$rust.ProtoMessageName}} { + evt_tx_hash: Hex(&view.transaction.hash).to_string(), + evt_index: log.block_index, + evt_block_time: Some(blk.timestamp().to_owned()), + evt_block_number: blk.number, + evt_address: Hex(&log.address).to_string(), + {{- range $protoField, $abiToProtoConversion := $rust.ProtoFieldABIConversionMap }} + {{$protoField}}: {{$abiToProtoConversion}}, + {{- end}} + }); + } + + None + }) + }) + .collect()); + {{- end }} +} + +{{- if $ddsContract.HasCalls }} +fn map_{{ $ddsContract.GetName }}_calls( + blk: ð::Block, + dds_store: &store::StoreGetInt64, + calls: &mut contract::Calls, +) { + {{- range $call := $ddsContract.GetCalls }} + {{- $rust := $call.Rust }} + calls.{{ $ddsContract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.append(&mut blk + .transactions() + .flat_map(|tx| { + tx.calls.iter() + .filter(|call| is_declared_dds_address(&call.address, call.begin_ordinal, dds_store) && abi::{{ $ddsContract.GetName }}_contract::functions::{{$rust.ABIStructName}}::match_call(call)) + .filter_map(|call| { + match abi::{{ $ddsContract.GetName }}_contract::functions::{{$rust.ABIStructName}}::decode(call) { + Ok(decoded_call) => { + {{- if $rust.OutputFieldsString }} + let {{ $rust.OutputFieldsString }} = match abi::{{ $ddsContract.GetName }}_contract::functions::{{$rust.ABIStructName}}::output(&call.return_data) { + Ok({{ $rust.OutputFieldsString }}) => {{`{`}}{{ $rust.OutputFieldsString }}{{`}`}} + Err(_) => Default::default(), + }; + {{ end }} + Some(contract::{{ capitalizeFirst $ddsContract.GetName }}{{$rust.ProtoMessageName}} { + call_tx_hash: Hex(&tx.hash).to_string(), + call_block_time: Some(blk.timestamp().to_owned()), + call_block_number: blk.number, + call_ordinal: call.begin_ordinal, + call_success: !call.state_reverted, + call_address: Hex(&call.address).to_string(), + {{- range $protoField, $abiToProtoConversion := $rust.ProtoFieldABIConversionMap }} + {{$protoField}}: {{$abiToProtoConversion}}, + {{- end}} + }) + }, + Err(_) => None, + } + }) + }) + .collect()); + {{- end }} +} +{{ end }} + +{{ end }} +{{- end }} +{{- range $i, $contract := .ethereumContracts }} +fn db_{{ $contract.GetName }}_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + {{- range $event := $contract.GetEvents }} + {{- $rust := $event.Rust }} + events.{{ $contract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|evt| { + tables + .create_row("{{ $contract.GetName }}_{{ $rust.TableChangeEntityName }}", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.{{$changesToProtoConversion.Setter}}("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} +} +{{- if $contract.HasCalls }} +fn db_{{ $contract.GetName }}_calls_out(calls: &contract::Calls, tables: &mut DatabaseChangeTables) { + // Loop over all the abis calls to create table changes + {{- range $call := $contract.GetCalls }} + {{- $rust := $call.Rust }} + calls.{{ $contract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|call| { + tables + .create_row("{{ $contract.GetName }}_{{ $rust.TableChangeEntityName }}", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.{{$changesToProtoConversion.Setter}}("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} +} +{{- end }} + +{{- range $ddsContract := $contract.GetDDS }} +fn db_{{ $ddsContract.GetName }}_out(events: &contract::Events, tables: &mut DatabaseChangeTables) { + // Loop over all the abis events to create table changes + {{- range $event := $ddsContract.GetEvents }} + {{- $rust := $event.Rust }} + events.{{ $ddsContract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|evt| { + tables + .create_row("{{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }}", [("evt_tx_hash", evt.evt_tx_hash.to_string()),("evt_index", evt.evt_index.to_string())]) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.{{$changesToProtoConversion.Setter}}("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} +} +{{- if $ddsContract.HasCalls }} +fn db_{{ $ddsContract.GetName }}_calls_out(calls: &contract::Calls, tables: &mut DatabaseChangeTables) { + // Loop over all the abis calls to create table changes + {{- range $call := $ddsContract.GetCalls }} + {{- $rust := $call.Rust }} + calls.{{ $ddsContract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|call| { + tables + .create_row("{{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }}", [("call_tx_hash", call.call_tx_hash.to_string()),("call_ordinal", call.call_ordinal.to_string())]) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.{{$changesToProtoConversion.Setter}}("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} +} +{{- end }} + +{{- end }} +{{- end }} + +{{ range $i, $contract := .ethereumContracts }} +fn graph_{{ $contract.GetName }}_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + {{- range $event := $contract.GetEvents }} + {{- $rust := $event.Rust }} + events.{{ $contract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|evt| { + tables + .create_row("{{ $contract.GetName }}_{{ $rust.TableChangeEntityName }}", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.set("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} +} +{{- if $contract.HasCalls }} +fn graph_{{ $contract.GetName }}_calls_out(calls: &contract::Calls, tables: &mut EntityChangesTables) { + // Loop over all the abis calls to create table changes + {{- range $call := $contract.GetCalls }} + {{- $rust := $call.Rust }} + calls.{{ $contract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|call| { + tables + .create_row("{{ $contract.GetName }}_{{ $rust.TableChangeEntityName }}", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.set("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} + } +{{- end }} +{{- range $ddsContract := $contract.GetDDS }} +fn graph_{{ $ddsContract.GetName }}_out(events: &contract::Events, tables: &mut EntityChangesTables) { + // Loop over all the abis events to create table changes + {{- range $event := $ddsContract.GetEvents }} + {{- $rust := $event.Rust }} + events.{{ $ddsContract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|evt| { + tables + .create_row("{{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }}", format!("{}-{}", evt.evt_tx_hash, evt.evt_index)) + .set("evt_tx_hash", &evt.evt_tx_hash) + .set("evt_index", evt.evt_index) + .set("evt_block_time", evt.evt_block_time.as_ref().unwrap()) + .set("evt_block_number", evt.evt_block_number) + .set("evt_address", &evt.evt_address) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.set("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} +} + +{{- if $ddsContract.HasCalls }} +fn graph_{{ $ddsContract.GetName }}_calls_out(calls: &contract::Calls, tables: &mut EntityChangesTables) { + // Loop over all the abis calls to create table changes + {{- range $call := $ddsContract.GetCalls }} + {{- $rust := $call.Rust }} + calls.{{ $ddsContract.GetName }}_{{ $rust.ProtoOutputModuleFieldName }}.iter().for_each(|call| { + tables + .create_row("{{ $ddsContract.GetName }}_{{ $rust.TableChangeEntityName }}", format!("{}-{}", call.call_tx_hash, call.call_ordinal)) + .set("call_tx_hash", &call.call_tx_hash) + .set("call_ordinal", call.call_ordinal) + .set("call_block_time", call.call_block_time.as_ref().unwrap()) + .set("call_block_number", call.call_block_number) + .set("call_success", call.call_success) + .set("call_address", &call.call_address) + {{- $numberOfAttributes := len $rust.ProtoFieldTableChangesMap }}{{ if eq $numberOfAttributes 0 }};{{ end }} + {{- $i := 0 }} + {{- range $protoField, $changesToProtoConversion := $rust.ProtoFieldTableChangesMap }} + {{ $i = add $i 1 }}.set("{{$protoField}}", {{$changesToProtoConversion.ValueAccessCode}}){{if eq $i $numberOfAttributes}};{{ end }} + {{- end}} + }); + {{- end}} + } +{{- end }} +{{- end }} +{{- end }} + +{{- range $contract := .ethereumContracts }} +{{- range $ddsContract := $contract.GetDDS }} +#[substreams::handlers::store] +fn store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created(blk: eth::Block, store: StoreSetInt64) { + for rcpt in blk.receipts() { + for log in rcpt + .receipt + .logs + .iter() + .filter(|log| log.address == {{ toUpper $contract.GetName }}_TRACKED_CONTRACT) + { + if let Some(event) = abi::{{ $contract.GetName }}_contract::events::{{ $ddsContract.GetCreationEvent }}::match_and_decode(log) { + store.set(log.ordinal, Hex(event.{{ $ddsContract.GetCreationAddressField }}).to_string(), &1); + } + } + } +} +{{- end -}} +{{- end }} + +#[substreams::handlers::map] +{{- if .hasDDS }} +fn map_events( + blk: eth::Block, +{{- range $contract := .ethereumContracts }}{{ range $ddsContract := $contract.GetDDS }} + store_{{ $ddsContract.GetName }}: StoreGetInt64,{{ end }}{{ end }} +) -> Result { +{{- else }} +fn map_events(blk: eth::Block) -> Result { +{{- end }} + let mut events = contract::Events::default(); + {{- range $i, $contract := .ethereumContracts }} + map_{{ $contract.GetName }}_events(&blk, &mut events); +{{- range $ddsContract := $contract.GetDDS }} + map_{{ $ddsContract.GetName }}_events(&blk, &store_{{ $ddsContract.GetName }}, &mut events);{{ end }} + {{- end }} + Ok(events) +} + +{{- if .withCalls }} +#[substreams::handlers::map] +{{- if .hasDDS }} +fn map_calls( + blk: eth::Block, +{{- range $contract := .ethereumContracts }}{{ range $ddsContract := $contract.GetDDS }}{{ if $ddsContract.HasCalls }} + store_{{ $ddsContract.GetName }}: StoreGetInt64,{{ end }}{{ end }}{{ end }} +) -> Result { +{{- else }} +fn map_calls(blk: eth::Block) -> Result { +{{- end }} + let mut calls = contract::Calls::default(); + {{- range $i, $contract := .ethereumContracts }} +{{- if $contract.HasCalls }} + map_{{ $contract.GetName }}_calls(&blk, &mut calls);{{ end }} +{{- range $ddsContract := $contract.GetDDS }}{{ if $ddsContract.HasCalls }} + map_{{ $ddsContract.GetName }}_calls(&blk, &store_{{ $ddsContract.GetName }}, &mut calls);{{ end }}{{ end }} + {{- end }} + Ok(calls) +} +{{- end }} + +#[substreams::handlers::map] +{{- if .withCalls }} +fn db_out(events: contract::Events, calls: contract::Calls) -> Result { +{{- else }} +fn db_out(events: contract::Events) -> Result { +{{- end }} + // Initialize Database Changes container + let mut tables = DatabaseChangeTables::new(); + {{- range $i, $contract := .ethereumContracts }} + db_{{ $contract.GetName }}_out(&events, &mut tables); + {{- if $contract.HasCalls }} + db_{{ $contract.GetName }}_calls_out(&calls, &mut tables);{{ end }} + {{- range $ddsContract := $contract.GetDDS }} + db_{{ $ddsContract.GetName }}_out(&events, &mut tables); + {{- if $ddsContract.HasCalls }} + db_{{ $ddsContract.GetName }}_calls_out(&calls, &mut tables);{{ end }} + {{- end }} + {{- end }} + Ok(tables.to_database_changes()) +} + +#[substreams::handlers::map] +{{- if .withCalls }} +fn graph_out(events: contract::Events, calls: contract::Calls) -> Result { +{{- else }} +fn graph_out(events: contract::Events) -> Result { +{{- end }} + // Initialize Database Changes container + let mut tables = EntityChangesTables::new(); + {{- range $i, $contract := .ethereumContracts }} + graph_{{ $contract.GetName }}_out(&events, &mut tables); + {{- if $contract.HasCalls }} + graph_{{ $contract.GetName }}_calls_out(&calls, &mut tables);{{ end }} + {{- range $ddsContract := $contract.GetDDS }} + graph_{{ $ddsContract.GetName }}_out(&events, &mut tables); + {{- if $ddsContract.HasCalls }} + graph_{{ $ddsContract.GetName }}_calls_out(&calls, &mut tables);{{ end }} + {{- end }} + {{- end }} + Ok(tables.to_entity_changes()) +} diff --git a/firehose/substreams/codegen/templates/ethereum/src/pb/contract.v1.rs b/firehose/substreams/codegen/templates/ethereum/src/pb/contract.v1.rs new file mode 100644 index 0000000..6654431 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/pb/contract.v1.rs @@ -0,0 +1,84 @@ +// @generated +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Events { + #[prost(message, repeated, tag="1")] + pub bayc_approvals: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub bayc_approval_for_alls: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="3")] + pub bayc_ownership_transferreds: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="4")] + pub bayc_transfers: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycApproval { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub approved: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub token_id: ::prost::alloc::string::String, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycApprovalForAll { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub operator: ::prost::alloc::vec::Vec, + #[prost(bool, tag="7")] + pub approved: bool, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycOwnershipTransferred { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub previous_owner: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub new_owner: ::prost::alloc::vec::Vec, +} +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BaycTransfer { + #[prost(string, tag="1")] + pub evt_tx_hash: ::prost::alloc::string::String, + #[prost(uint32, tag="2")] + pub evt_index: u32, + #[prost(message, optional, tag="3")] + pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>, + #[prost(uint64, tag="4")] + pub evt_block_number: u64, + #[prost(bytes="vec", tag="5")] + pub from: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="6")] + pub to: ::prost::alloc::vec::Vec, + #[prost(string, tag="7")] + pub token_id: ::prost::alloc::string::String, +} +// @@protoc_insertion_point(module) diff --git a/firehose/substreams/codegen/templates/ethereum/src/pb/mod.rs b/firehose/substreams/codegen/templates/ethereum/src/pb/mod.rs new file mode 100644 index 0000000..611ea83 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/src/pb/mod.rs @@ -0,0 +1,8 @@ +// @generated +pub mod contract { + // @@protoc_insertion_point(attribute:contract.v1) + pub mod v1 { + include!("contract.v1.rs"); + // @@protoc_insertion_point(contract.v1) + } +} diff --git a/firehose/substreams/codegen/templates/ethereum/subgraph.yaml b/firehose/substreams/codegen/templates/ethereum/subgraph.yaml new file mode 100644 index 0000000..2b7f519 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/subgraph.yaml @@ -0,0 +1,17 @@ +specVersion: 0.0.6 +description: substreams-init-test substreams based subgraph +repository: # fill in with git remote url +schema: + file: ./schema.graphql + +dataSources: + - kind: substreams + name: substreams-init-test + network: mainnet + source: + package: + moduleName: graph_out + file: substreams-init-test-v0.1.0.spkg + mapping: + kind: substreams/graph-entities + apiVersion: 0.0.5 diff --git a/firehose/substreams/codegen/templates/ethereum/subgraph.yaml.gotmpl b/firehose/substreams/codegen/templates/ethereum/subgraph.yaml.gotmpl new file mode 100644 index 0000000..c98c884 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/subgraph.yaml.gotmpl @@ -0,0 +1,17 @@ +specVersion: 0.0.6 +description: {{ .name }} substreams based subgraph +repository: # fill in with git remote url +schema: + file: ./schema.graphql + +dataSources: + - kind: substreams + name: {{ .name }} + network: mainnet + source: + package: + moduleName: graph_out + file: {{ .name }}-v0.1.0.spkg + mapping: + kind: substreams/graph-entities + apiVersion: 0.0.5 diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.clickhouse.yaml b/firehose/substreams/codegen/templates/ethereum/substreams.clickhouse.yaml new file mode 100644 index 0000000..be06676 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.clickhouse.yaml @@ -0,0 +1,59 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: map_events + kind: map + initialBlock: 123 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.clickhouse.sql" + engine: clickhouse + postgraphile_frontend: + enabled: false + rest_frontend: + enabled: false diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.clickhouse.yaml.gotmpl b/firehose/substreams/codegen/templates/ethereum/substreams.clickhouse.yaml.gotmpl new file mode 100644 index 0000000..7d081f3 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.clickhouse.yaml.gotmpl @@ -0,0 +1,86 @@ +specVersion: v0.1.0 +package: + name: {{ .moduleName }} + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v{{ .sqlImportVersion }}/substreams-sink-sql-protodefs-v{{ .sqlImportVersion }}.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v{{ .graphImportVersion }}/substreams-sink-subgraph-protodefs-v{{ .graphImportVersion }}.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v{{ .databaseChangeImportVersion }}/substreams-database-change-v{{ .databaseChangeImportVersion }}.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v{{ .entityChangeImportVersion }}/substreams-entity-change-v{{ .entityChangeImportVersion }}.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: +{{- range $contract := .ethereumContracts -}} +{{- range $ddsContract := $contract.GetDDS }} + - name: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created + kind: store + initialBlock: {{ $.initialBlock }} + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block +{{ end -}} +{{- end }} + - name: map_events + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Events +{{- if .withCalls }} + + - name: map_calls + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Calls +{{- end }} + + - name: db_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: {{ .network }} + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.clickhouse.sql" + engine: clickhouse + postgraphile_frontend: + enabled: false + rest_frontend: + enabled: false diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.sql.yaml b/firehose/substreams/codegen/templates/ethereum/substreams.sql.yaml new file mode 100644 index 0000000..6163888 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.sql.yaml @@ -0,0 +1,57 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: map_events + kind: map + initialBlock: 123 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + engine: postgres + postgraphile_frontend: + enabled: true diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.sql.yaml.gotmpl b/firehose/substreams/codegen/templates/ethereum/substreams.sql.yaml.gotmpl new file mode 100644 index 0000000..b6f2708 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.sql.yaml.gotmpl @@ -0,0 +1,84 @@ +specVersion: v0.1.0 +package: + name: {{ .moduleName }} + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v{{ .sqlImportVersion }}/substreams-sink-sql-protodefs-v{{ .sqlImportVersion }}.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v{{ .graphImportVersion }}/substreams-sink-subgraph-protodefs-v{{ .graphImportVersion }}.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v{{ .databaseChangeImportVersion }}/substreams-database-change-v{{ .databaseChangeImportVersion }}.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v{{ .entityChangeImportVersion }}/substreams-entity-change-v{{ .entityChangeImportVersion }}.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: +{{- range $contract := .ethereumContracts -}} +{{- range $ddsContract := $contract.GetDDS }} + - name: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created + kind: store + initialBlock: {{ $.initialBlock }} + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block +{{ end -}} +{{- end }} + - name: map_events + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Events +{{- if .withCalls }} + + - name: map_calls + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Calls +{{- end }} + + - name: db_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: {{ .network }} + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + engine: postgres + postgraphile_frontend: + enabled: true diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.subgraph.yaml b/firehose/substreams/codegen/templates/ethereum/substreams.subgraph.yaml new file mode 100644 index 0000000..0f63f04 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.subgraph.yaml @@ -0,0 +1,56 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: map_events + kind: map + initialBlock: 123 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet + +sink: + module: graph_out + type: sf.substreams.sink.subgraph.v1.Service + config: + schema: "./schema.graphql" + subgraph_yaml: "./subgraph.yaml" + postgres_direct_protocol_access: true diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.subgraph.yaml.gotmpl b/firehose/substreams/codegen/templates/ethereum/substreams.subgraph.yaml.gotmpl new file mode 100644 index 0000000..8d72ef4 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.subgraph.yaml.gotmpl @@ -0,0 +1,83 @@ +specVersion: v0.1.0 +package: + name: {{ .moduleName }} + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v{{ .sqlImportVersion }}/substreams-sink-sql-protodefs-v{{ .sqlImportVersion }}.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v{{ .graphImportVersion }}/substreams-sink-subgraph-protodefs-v{{ .graphImportVersion }}.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v{{ .databaseChangeImportVersion }}/substreams-database-change-v{{ .databaseChangeImportVersion }}.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v{{ .entityChangeImportVersion }}/substreams-entity-change-v{{ .entityChangeImportVersion }}.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: +{{- range $contract := .ethereumContracts -}} +{{- range $ddsContract := $contract.GetDDS }} + - name: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created + kind: store + initialBlock: {{ $.initialBlock }} + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block +{{ end -}} +{{- end }} + - name: map_events + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Events +{{- if .withCalls }} + + - name: map_calls + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Calls +{{- end }} + + - name: db_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: {{ .network }} + +sink: + module: graph_out + type: sf.substreams.sink.subgraph.v1.Service + config: + schema: "./schema.graphql" + subgraph_yaml: "./subgraph.yaml" + postgres_direct_protocol_access: true diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.yaml b/firehose/substreams/codegen/templates/ethereum/substreams.yaml new file mode 100644 index 0000000..d6fc7c2 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.yaml @@ -0,0 +1,48 @@ +specVersion: v0.1.0 +package: + name: substreams_init_test + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.7/substreams-sink-sql-protodefs-v1.0.7.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v0.1.0/substreams-sink-subgraph-protodefs-v0.1.0.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v1.2.1/substreams-database-change-v1.2.1.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v1.1.0/substreams-entity-change-v1.1.0.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: map_events + kind: map + initialBlock: 123 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: 123 + inputs: + - map: map_events + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: mainnet diff --git a/firehose/substreams/codegen/templates/ethereum/substreams.yaml.gotmpl b/firehose/substreams/codegen/templates/ethereum/substreams.yaml.gotmpl new file mode 100644 index 0000000..f9412cf --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum/substreams.yaml.gotmpl @@ -0,0 +1,75 @@ +specVersion: v0.1.0 +package: + name: {{ .moduleName }} + version: v0.1.0 + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v{{ .sqlImportVersion }}/substreams-sink-sql-protodefs-v{{ .sqlImportVersion }}.spkg + graph: https://github.com/streamingfast/substreams-sink-subgraph/releases/download/v{{ .graphImportVersion }}/substreams-sink-subgraph-protodefs-v{{ .graphImportVersion }}.spkg + database_change: https://github.com/streamingfast/substreams-sink-database-changes/releases/download/v{{ .databaseChangeImportVersion }}/substreams-database-change-v{{ .databaseChangeImportVersion }}.spkg + entity: https://github.com/streamingfast/substreams-entity-change/releases/download/v{{ .entityChangeImportVersion }}/substreams-entity-change-v{{ .entityChangeImportVersion }}.spkg + +protobuf: + files: + - contract.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: +{{- range $contract := .ethereumContracts -}} +{{- range $ddsContract := $contract.GetDDS }} + - name: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created + kind: store + initialBlock: {{ $.initialBlock }} + updatePolicy: set + valueType: proto:dynamic_datasource + inputs: + - source: sf.ethereum.type.v2.Block +{{ end -}} +{{- end }} + - name: map_events + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Events +{{- if .withCalls }} + + - name: map_calls + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - source: sf.ethereum.type.v2.Block{{ range $contract := .ethereumContracts -}}{{ range $ddsContract := $contract.GetDDS }} + - store: store_{{ $contract.GetName }}_{{ $ddsContract.GetName }}_created{{ end }}{{ end }} + output: + type: proto:contract.v1.Calls +{{- end }} + + - name: db_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges + + - name: graph_out + kind: map + initialBlock: {{ .initialBlock }} + inputs: + - map: map_events +{{- if .withCalls }} + - map: map_calls{{ end }} + output: + type: proto:sf.substreams.entity.v1.EntityChanges + +network: {{ .network }} diff --git a/firehose/substreams/codegen/templates/ethereum_chain.go b/firehose/substreams/codegen/templates/ethereum_chain.go new file mode 100644 index 0000000..0fb6cfe --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum_chain.go @@ -0,0 +1,83 @@ +package templates + +type EthereumChain struct { + ID string + DisplayName string + ExplorerLink string + ApiEndpoint string + DefaultContractAddress string + DefaultContractName string + FirehoseEndpoint string + Network string +} + +var EthereumChainsByID = map[string]*EthereumChain{ + "Mainnet": { + DisplayName: "Ethereum Mainnet", + ExplorerLink: "https://etherscan.io", + ApiEndpoint: "https://api.etherscan.io", + DefaultContractAddress: "bc4ca0eda7647a8ab7c2061c2e118a18a936f13d", + DefaultContractName: "Bored Ape Yacht Club", + FirehoseEndpoint: "mainnet.eth.streamingfast.io:443", + Network: "mainnet", + }, + "BNB": { + DisplayName: "BNB", + ExplorerLink: "https://bscscan.com", + ApiEndpoint: "https://api.bscscan.com", + DefaultContractAddress: "0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82", + DefaultContractName: "CAKE Token", + FirehoseEndpoint: "bnb.streamingfast.io:443", + Network: "bsc", + }, + "Polygon": { + DisplayName: "Polygon", + ExplorerLink: "https://polygonscan.com", + ApiEndpoint: "https://api.polygonscan.com", + DefaultContractAddress: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619", + DefaultContractName: "WETH Token", + FirehoseEndpoint: "polygon.streamingfast.io:443", + Network: "polygon", + }, + "Arbitrum": { + DisplayName: "Arbitrum", + ExplorerLink: "https://arbiscan.io", + ApiEndpoint: "https://api.arbiscan.io", + DefaultContractAddress: "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", + DefaultContractName: "WETH Token", + FirehoseEndpoint: "arb-one.streamingfast.io:443", + Network: "arbitrum", + }, + "Goerli": { + DisplayName: "Goerli Testnet", + ExplorerLink: "https://goerli.etherscan.io", + ApiEndpoint: "https://api-goerli.etherscan.io", + DefaultContractAddress: "0x4f7a67464b5976d7547c860109e4432d50afb38e", + DefaultContractName: "GETH Token", + FirehoseEndpoint: "goerli.eth.streamingfast.io:443", + Network: "goerli", + }, + "Mumbai": { + DisplayName: "Mumbai Testnet", + ExplorerLink: "https://mumbai.polygonscan.com", + ApiEndpoint: "https://api-mumbai.polygonscan.com", + DefaultContractAddress: "0xFCe7187B24FCDc9feFfE428Ec9977240C6F7006D", + DefaultContractName: "USDT Token", + FirehoseEndpoint: "mumbai.streamingfast.io:443", + Network: "mumbai", + }, + // "Sepolia": { + // DisplayName: "Sepolia Testnet", + // ExplorerLink: "https://sepolia.etherscan.io", + // ApiEndpoint: "https://api-sepolia.etherscan.io", + // DefaultContractAddress: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", + // DefaultContractName: "UNI Token", + // FirehoseEndpoint: "sepolia.streamingfast.io:443", + // }, +} + +func init() { + for k, v := range EthereumChainsByID { + v.ID = k + } +} diff --git a/firehose/substreams/codegen/templates/ethereum_project.go b/firehose/substreams/codegen/templates/ethereum_project.go new file mode 100644 index 0000000..9ed08ab --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum_project.go @@ -0,0 +1,1104 @@ +package templates + +import ( + "bytes" + "embed" + "fmt" + "sort" + "strconv" + "strings" + "text/template" + + "github.com/gertd/go-pluralize" + "github.com/huandu/xstrings" + "github.com/streamingfast/eth-go" + "go.uber.org/zap" +) + +//go:embed ethereum/proto +//go:embed ethereum/src +//go:embed ethereum/build.rs +//go:embed ethereum/Cargo.lock +//go:embed ethereum/Cargo.toml.gotmpl +//go:embed ethereum/Makefile.gotmpl +//go:embed ethereum/substreams.yaml.gotmpl +//go:embed ethereum/substreams.sql.yaml.gotmpl +//go:embed ethereum/substreams.clickhouse.yaml.gotmpl +//go:embed ethereum/substreams.subgraph.yaml.gotmpl +//go:embed ethereum/rust-toolchain.toml +//go:embed ethereum/build.rs.gotmpl +//go:embed ethereum/schema.sql.gotmpl +//go:embed ethereum/schema.clickhouse.sql.gotmpl +//go:embed ethereum/schema.graphql.gotmpl +//go:embed ethereum/subgraph.yaml.gotmpl +var ethereumProject embed.FS + +type EthereumContract struct { + name string + address eth.Address + events []codegenEvent + calls []codegenCall + abi *eth.ABI + abiContent string + //withEvents bool + withCalls bool + dynamicDataSources []*DDSContract +} + +type DDSContract struct { + name string + events []codegenEvent + calls []codegenCall + abi *eth.ABI + abiContent string + //withEvents bool + withCalls bool + creationEvent string + creationAddressField string +} + +func (c *DDSContract) GetName() string { + return c.name +} + +func (c *DDSContract) GetEvents() []codegenEvent { + return c.events +} +func (c *DDSContract) GetCalls() []codegenCall { + return c.calls +} + +func (c *DDSContract) HasCalls() bool { + return len(c.calls) != 0 +} + +func (c *DDSContract) GetCreationEvent() string { + return c.creationEvent +} +func (c *DDSContract) GetCreationAddressField() string { + return c.creationAddressField +} + +func NewEthereumContract(name string, address eth.Address, abi *eth.ABI, abiContent string) *EthereumContract { + return &EthereumContract{ + name: name, + address: address, + abi: abi, + abiContent: abiContent, + } +} + +func (e *EthereumContract) AddDynamicDataSource( + name string, + abi *eth.ABI, + abiContent string, + creationEvent string, + creationAddressField string, + //withEvents bool, + withCalls bool, +) (err error) { + + var events []codegenEvent + // if withEvents { + events, err = BuildEventModels(abi) + if err != nil { + return fmt.Errorf("build ABI event models for dynamic datasource contract %s: %w", name, err) + } + //} + + var calls []codegenCall + if withCalls { + calls, err = BuildCallModels(abi) + if err != nil { + return fmt.Errorf("build ABI event models for dynamic datasource contract %s: %w", name, err) + } + } + + e.dynamicDataSources = append(e.dynamicDataSources, &DDSContract{ + name: name, + events: events, + calls: calls, + abi: abi, + abiContent: abiContent, + creationEvent: creationEvent, + creationAddressField: creationAddressField, + }) + return nil +} + +func (e *EthereumContract) GetDDS() []*DDSContract { + return e.dynamicDataSources +} + +func (e *EthereumContract) GetAddress() eth.Address { + return e.address +} + +func (e *EthereumContract) SetWithCalls(v bool) { + e.withCalls = v +} + +func (e *EthereumContract) GetWithCalls() bool { + return e.withCalls +} + +func (e *EthereumContract) SetName(name string) { + e.name = name +} + +func (e *EthereumContract) GetName() string { + return e.name +} + +func (e *EthereumContract) SetEvents(events []codegenEvent) { + e.events = events +} +func (e *EthereumContract) SetCalls(calls []codegenCall) { + e.calls = calls +} + +func (e *EthereumContract) GetEvents() []codegenEvent { + return e.events +} + +func (e *EthereumContract) GetCalls() []codegenCall { + return e.calls +} + +func (e *EthereumContract) HasCalls() bool { + return len(e.calls) != 0 +} + +func (e *EthereumContract) GetAbi() *eth.ABI { + return e.abi +} + +func (e *EthereumContract) SetAbi(abi *eth.ABI) { + e.abi = abi +} + +func (e *EthereumContract) SetAbiContent(abiContent string) { + e.abiContent = abiContent +} + +type EthereumProject struct { + name string + moduleName string + chain *EthereumChain + creationBlockNum uint64 + ethereumContracts []*EthereumContract + sqlImportVersion string + graphImportVersion string + databaseChangeImportVersion string + entityChangeImportVersion string + network string +} + +func NewEthereumProject(name string, moduleName string, chain *EthereumChain, contracts []*EthereumContract, lowestStartBlock uint64) (*EthereumProject, error) { + return &EthereumProject{ + name: name, + moduleName: moduleName, + chain: chain, + ethereumContracts: contracts, + creationBlockNum: lowestStartBlock, + sqlImportVersion: "1.0.7", + graphImportVersion: "0.1.0", + databaseChangeImportVersion: "1.2.1", + entityChangeImportVersion: "1.1.0", + network: chain.Network, + }, nil +} + +func (p *EthereumProject) HasDDS() bool { + for _, contract := range p.ethereumContracts { + if len(contract.dynamicDataSources) > 0 { + return true + } + } + return false +} + +func (p *EthereumProject) Render() (map[string][]byte, error) { + entries := map[string][]byte{} + + for _, ethereumProjectEntry := range []string{ + "proto/contract.proto.gotmpl", + "src/abi/mod.rs.gotmpl", + "src/pb/mod.rs", + "src/lib.rs.gotmpl", + "build.rs.gotmpl", + "Cargo.lock", + "Cargo.toml.gotmpl", + "Makefile.gotmpl", + "substreams.yaml.gotmpl", + "substreams.sql.yaml.gotmpl", + "substreams.clickhouse.yaml.gotmpl", + "substreams.subgraph.yaml.gotmpl", + "rust-toolchain.toml", + "schema.sql.gotmpl", + "schema.clickhouse.sql.gotmpl", + "schema.graphql.gotmpl", + "subgraph.yaml.gotmpl", + } { + // We use directly "/" here as `ethereumProject` is an embed FS and always uses "/" + content, err := ethereumProject.ReadFile("ethereum" + "/" + ethereumProjectEntry) + if err != nil { + return nil, fmt.Errorf("embed read entry %q: %w", ethereumProjectEntry, err) + } + + finalFileName := ethereumProjectEntry + + zlog.Debug("reading ethereum project entry", zap.String("filename", finalFileName)) + + if strings.HasSuffix(finalFileName, ".gotmpl") { + tmpl, err := template.New(finalFileName).Funcs(ProjectGeneratorFuncs).Parse(string(content)) + if err != nil { + return nil, fmt.Errorf("embed parse entry template %q: %w", finalFileName, err) + } + + name := p.name + if finalFileName == "subgraph.yaml.gotmpl" { + name = xstrings.ToKebabCase(p.name) + } + + var withCalls bool + for _, contract := range p.ethereumContracts { + if contract.withCalls { + withCalls = true + break + } + for _, dds := range contract.dynamicDataSources { + if dds.withCalls { + withCalls = true + break + } + } + } + + model := map[string]any{ + "name": name, + "moduleName": p.moduleName, + "chain": p.chain, + "ethereumContracts": p.ethereumContracts, + "initialBlock": strconv.FormatUint(p.creationBlockNum, 10), + "sqlImportVersion": p.sqlImportVersion, + "graphImportVersion": p.graphImportVersion, + "databaseChangeImportVersion": p.databaseChangeImportVersion, + "entityChangeImportVersion": p.entityChangeImportVersion, + "network": p.network, + "hasDDS": p.HasDDS(), + "withCalls": withCalls, + } + + zlog.Debug("rendering templated file", zap.String("filename", finalFileName), zap.Any("model", model)) + + buffer := bytes.NewBuffer(make([]byte, 0, uint64(float64(len(content))*1.10))) + if err := tmpl.Execute(buffer, model); err != nil { + return nil, fmt.Errorf("embed render entry template %q: %w", finalFileName, err) + } + + finalFileName = strings.TrimSuffix(finalFileName, ".gotmpl") + content = buffer.Bytes() + } + + entries[finalFileName] = content + } + + for _, contract := range p.ethereumContracts { + entries[fmt.Sprintf("abi/%s_contract.abi.json", contract.GetName())] = []byte(contract.abiContent) + for _, dds := range contract.dynamicDataSources { + entries[fmt.Sprintf("abi/%s_contract.abi.json", dds.name)] = []byte(dds.abiContent) + } + } + + return entries, nil +} + +func BuildEventModels(abi *eth.ABI) (out []codegenEvent, err error) { + pluralizer := pluralize.NewClient() + + names := keys(abi.LogEventsByNameMap) + sort.StringSlice(names).Sort() + + // We allocate as many names + 16 to potentially account for duplicates + out = make([]codegenEvent, 0, len(names)+16) + for _, name := range names { + events := abi.FindLogsByName(name) + + for i, event := range events { + rustABIStructName := name + if len(events) > 1 { // will result in OriginalName, OriginalName1, OriginalName2 + rustABIStructName = name + strconv.FormatUint(uint64(i+1), 10) + } + for i, param := range event.Parameters { + if param.Name == "" { + if event.Parameters[i].Indexed { + param.Name = fmt.Sprintf("topic%d", i) + } else { + param.Name = fmt.Sprintf("param%d", i) + } + } + } + + protoFieldName := xstrings.ToSnakeCase(pluralizer.Plural(rustABIStructName)) + // prost will do a to_lower_camel_case() on any struct name + rustGeneratedStructName := xstrings.ToCamelCase(xstrings.ToSnakeCase(rustABIStructName)) + + codegenEvent := codegenEvent{ + Rust: &rustEventModel{ + ABIStructName: rustGeneratedStructName, + ProtoMessageName: rustGeneratedStructName, + ProtoOutputModuleFieldName: protoFieldName, + TableChangeEntityName: xstrings.ToSnakeCase(rustABIStructName), + }, + + Proto: &protoEventModel{ + MessageName: rustGeneratedStructName, + OutputModuleFieldName: protoFieldName, + }, + } + + if err := codegenEvent.Rust.populateFields(event); err != nil { + return nil, fmt.Errorf("populating codegen Rust fields: %w", err) + } + + if err := codegenEvent.Proto.populateFields(event); err != nil { + return nil, fmt.Errorf("populating codegen Proto fields: %w", err) + } + + out = append(out, codegenEvent) + } + } + + return +} + +func BuildCallModels(abi *eth.ABI) (out []codegenCall, err error) { + pluralizer := pluralize.NewClient() + + names := keys(abi.FunctionsByNameMap) + sort.StringSlice(names).Sort() + + // We allocate as many names + 16 to potentially account for duplicates + out = make([]codegenCall, 0, len(names)+16) + for _, name := range names { + calls := abi.FindFunctionsByName(name) + + for i, call := range calls { + // We skip "pure" and "view" functions because they don't affect the state of the chain + if call.StateMutability == eth.StateMutabilityPure || call.StateMutability == eth.StateMutabilityView { + continue + } + rustABIStructName := name + if len(calls) > 1 { // will result in OriginalName, OriginalName1, OriginalName2 + rustABIStructName = name + strconv.FormatUint(uint64(i+1), 10) + } + for i, param := range call.Parameters { + if param.Name == "" { + param.Name = fmt.Sprintf("param%d", i) + } + } + for i, param := range call.ReturnParameters { + if param.Name == "" { + param.Name = fmt.Sprintf("param%d", i) + } + } + + protoFieldName := "call_" + xstrings.ToSnakeCase(pluralizer.Plural(rustABIStructName)) + // prost will do a to_lower_camel_case() on any struct name + rustGeneratedStructName := xstrings.ToCamelCase(xstrings.ToSnakeCase(rustABIStructName)) + protoMessageName := xstrings.ToCamelCase(xstrings.ToSnakeCase(rustABIStructName) + "Call") + + codegenCall := codegenCall{ + Rust: &rustCallModel{ + ABIStructName: rustGeneratedStructName, + ProtoMessageName: protoMessageName, + ProtoOutputModuleFieldName: protoFieldName, + TableChangeEntityName: "call_" + xstrings.ToSnakeCase(rustABIStructName), + }, + + Proto: &protoCallModel{ + MessageName: protoMessageName, + OutputModuleFieldName: protoFieldName, + }, + } + + if err := codegenCall.Rust.populateFields(call); err != nil { + return nil, fmt.Errorf("populating codegen Rust fields: %w", err) + } + + if err := codegenCall.Proto.populateFields(call); err != nil { + return nil, fmt.Errorf("populating codegen Proto fields: %w", err) + } + + out = append(out, codegenCall) + } + } + + return +} + +type codegenEvent struct { + Rust *rustEventModel + Proto *protoEventModel +} + +type codegenCall struct { + Rust *rustCallModel + Proto *protoCallModel +} + +type rustEventModel struct { + ABIStructName string + ProtoMessageName string + ProtoOutputModuleFieldName string + TableChangeEntityName string + ProtoFieldABIConversionMap map[string]string + ProtoFieldTableChangesMap map[string]tableChangeSetField + ProtoFieldSqlmap map[string]string + ProtoFieldClickhouseMap map[string]string + ProtoFieldGraphQLMap map[string]string +} + +type rustCallModel struct { + ABIStructName string + ProtoMessageName string + ProtoOutputModuleFieldName string + TableChangeEntityName string + OutputFieldsString string + ProtoFieldABIConversionMap map[string]string + ProtoFieldTableChangesMap map[string]tableChangeSetField + ProtoFieldSqlmap map[string]string + ProtoFieldClickhouseMap map[string]string + ProtoFieldGraphQLMap map[string]string +} + +type tableChangeSetField struct { + Setter string + ValueAccessCode string +} + +func (e *rustEventModel) populateFields(log *eth.LogEventDef) error { + if len(log.Parameters) == 0 { + return nil + } + + e.ProtoFieldABIConversionMap = map[string]string{} + e.ProtoFieldTableChangesMap = map[string]tableChangeSetField{} + e.ProtoFieldSqlmap = map[string]string{} + e.ProtoFieldClickhouseMap = map[string]string{} + e.ProtoFieldGraphQLMap = map[string]string{} + paramNames := make([]string, len(log.Parameters)) + for i := range log.Parameters { + paramNames[i] = log.Parameters[i].Name + } + fmt.Printf(" Generating ABI Events for %s (%s)\n", log.Name, strings.Join(paramNames, ",")) + + for _, parameter := range log.Parameters { + name := xstrings.ToSnakeCase(parameter.Name) + name = sanitizeProtoFieldName(name) + + toProtoCode := generateFieldTransformCode(parameter.Type, "event."+name, false) + if toProtoCode == SKIP_FIELD { + continue + } + if toProtoCode == "" { + return fmt.Errorf("transform - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + toDatabaseChangeSetter, toDatabaseChangeCode := generateFieldTableChangeCode(parameter.Type, "evt."+name, true) + if toDatabaseChangeCode == SKIP_FIELD { + continue + } + if toDatabaseChangeSetter == "" { + return fmt.Errorf("table change - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + toSqlCode := generateFieldSqlTypes(parameter.Type) + if toSqlCode == SKIP_FIELD { + continue + } + if toSqlCode == "" { + return fmt.Errorf("sql - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + toClickhouseCode := generateFieldClickhouseTypes(parameter.Type) + if toClickhouseCode == SKIP_FIELD { + continue + } + if toClickhouseCode == "" { + return fmt.Errorf("clickhouse - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + toGraphQLCode := generateFieldGraphQLTypes(parameter.Type) + if toGraphQLCode == "" { + return fmt.Errorf("graphql - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + columnName := sanitizeTableChangesColumnNames(name) + + e.ProtoFieldABIConversionMap[name] = toProtoCode + e.ProtoFieldTableChangesMap[name] = tableChangeSetField{Setter: toDatabaseChangeSetter, ValueAccessCode: toDatabaseChangeCode} + e.ProtoFieldSqlmap[columnName] = toSqlCode + e.ProtoFieldClickhouseMap[columnName] = toClickhouseCode + e.ProtoFieldGraphQLMap[name] = toGraphQLCode + } + + return nil +} + +func convertMethodParameters(parameters []*eth.MethodParameter, optionalPrefix string) ( + tableChangesMap map[string]tableChangeSetField, + sqlMap map[string]string, + clickhouseMap map[string]string, + graphqlMap map[string]string, + err error, +) { + tableChangesMap = map[string]tableChangeSetField{} + sqlMap = map[string]string{} + clickhouseMap = map[string]string{} + graphqlMap = map[string]string{} + + for _, parameter := range parameters { + name := optionalPrefix + xstrings.ToSnakeCase(parameter.Name) + name = sanitizeProtoFieldName(name) + columnName := sanitizeTableChangesColumnNames(name) + + toDatabaseChangeSetter, toDatabaseChangeCode := generateFieldTableChangeCode(parameter.Type, "call."+name, true) + if toDatabaseChangeCode != SKIP_FIELD { + if toDatabaseChangeSetter == "" { + err = fmt.Errorf("table change - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + return + } + tableChangesMap[name] = tableChangeSetField{Setter: toDatabaseChangeSetter, ValueAccessCode: toDatabaseChangeCode} + } + + toSqlCode := generateFieldSqlTypes(parameter.Type) + if toSqlCode != SKIP_FIELD { + if toSqlCode == "" { + err = fmt.Errorf("sql - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + return + } + sqlMap[columnName] = toSqlCode + } + + toClickhouseCode := generateFieldClickhouseTypes(parameter.Type) + if toClickhouseCode != SKIP_FIELD { + if toClickhouseCode == "" { + err = fmt.Errorf("clickhouse - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + return + } + clickhouseMap[columnName] = toClickhouseCode + } + + toGraphQLCode := generateFieldGraphQLTypes(parameter.Type) + if toGraphQLCode != SKIP_FIELD { + if toGraphQLCode == "" { + err = fmt.Errorf("graphql - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + return + } + graphqlMap[name] = toGraphQLCode + } + } + return +} + +func methodToABIConversionMaps( + parameters []*eth.MethodParameter, + outputParameters []*eth.MethodParameter, +) ( + abiConversionMap map[string]string, + outputString string, + err error, +) { + if len(parameters) != 0 { + abiConversionMap = make(map[string]string) + for _, parameter := range parameters { + name := xstrings.ToSnakeCase(parameter.Name) + name = sanitizeProtoFieldName(name) + + toProtoCode := generateFieldTransformCode(parameter.Type, "decoded_call."+name, false) + if toProtoCode != SKIP_FIELD { + if toProtoCode == "" { + err = fmt.Errorf("transform - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + return + } + abiConversionMap[name] = toProtoCode + } + } + } + + if len(outputParameters) == 0 { + return + } + + outputNames := make([]string, len(outputParameters)) + for i, parameter := range outputParameters { + name := "output_" + xstrings.ToSnakeCase(parameter.Name) + name = sanitizeProtoFieldName(name) + outputNames[i] = name + + toProtoCode := generateFieldTransformCode(parameter.Type, name, false) + if toProtoCode != SKIP_FIELD { + if toProtoCode == "" { + err = fmt.Errorf("transform - field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + return + } + abiConversionMap[name] = toProtoCode + } + } + if len(outputNames) == 1 { + outputString = strings.Join(outputNames, ", ") + } else { + outputString = "(" + strings.Join(outputNames, ", ") + ")" + } + + return +} + +func (e *rustCallModel) populateFields(call *eth.MethodDef) error { + if len(call.Parameters) == 0 && call.ReturnParameters == nil { + return nil + } + + paramNames := make([]string, len(call.Parameters)) + for i := range call.Parameters { + paramNames[i] = call.Parameters[i].Name + } + outputParamNames := make([]string, len(call.ReturnParameters)) + for i := range call.ReturnParameters { + outputParamNames[i] = call.ReturnParameters[i].Name + } + fmt.Printf(" Generating ABI Calls for %s (%s) => (%s)\n", call.Name, strings.Join(paramNames, ","), strings.Join(outputParamNames, ",")) + + var err error + e.ProtoFieldTableChangesMap, e.ProtoFieldSqlmap, e.ProtoFieldClickhouseMap, e.ProtoFieldGraphQLMap, err = convertMethodParameters(call.Parameters, "") + if err != nil { + return err + } + + outputTableChanges, outputSql, outputClickhouse, outputGraphQL, err := convertMethodParameters(call.ReturnParameters, "output_") + if err != nil { + return err + } + for k, v := range outputTableChanges { + e.ProtoFieldTableChangesMap[k] = v + } + for k, v := range outputSql { + e.ProtoFieldSqlmap[k] = v + } + for k, v := range outputClickhouse { + e.ProtoFieldClickhouseMap[k] = v + } + for k, v := range outputGraphQL { + e.ProtoFieldGraphQLMap[k] = v + } + + e.ProtoFieldABIConversionMap, e.OutputFieldsString, err = methodToABIConversionMaps(call.Parameters, call.ReturnParameters) + return err +} + +func sanitizeProtoFieldName(name string) string { + if strings.HasPrefix(name, "_") { + return strings.Replace(name, "_", "u_", 1) + } + return name +} + +func sanitizeTableChangesColumnNames(name string) string { + return fmt.Sprintf("\"%s\"", name) +} + +const SKIP_FIELD = "skip" + +func generateFieldClickhouseTypes(fieldType eth.SolidityType) string { + switch v := fieldType.(type) { + case eth.AddressType: + return "VARCHAR(40)" + + case eth.BooleanType: + return "BOOL" + + case eth.BytesType, eth.FixedSizeBytesType, eth.StringType: + return "TEXT" + + case eth.SignedIntegerType: + switch { + case v.BitsSize <= 8: + return "Int8" + case v.BitsSize <= 16: + return "Int16" + case v.BitsSize <= 32: + return "Int32" + case v.BitsSize <= 64: + return "Int64" + case v.BitsSize <= 128: + return "Int128" + } + return "Int256" + + case eth.UnsignedIntegerType: + switch { + case v.BitsSize <= 8: + return "UInt8" + case v.BitsSize <= 16: + return "UInt16" + case v.BitsSize <= 32: + return "UInt32" + case v.BitsSize <= 64: + return "UInt64" + case v.BitsSize <= 128: + return "UInt128" + } + return "UInt256" + + case eth.SignedFixedPointType: + precision := v.Decimals + if precision > 76 { + precision = 76 + } + switch { + case v.BitsSize <= 32: + return fmt.Sprintf("Decimal128(%d)", precision) + case v.BitsSize <= 64: + return fmt.Sprintf("Decimal128(%d)", precision) + case v.BitsSize <= 128: + return fmt.Sprintf("Decimal128(%d)", precision) + } + return fmt.Sprintf("Decimal256(%d)", precision) + + case eth.UnsignedFixedPointType: + precision := v.Decimals + if precision > 76 { + precision = 76 + } + switch { + case v.BitsSize <= 31: + return fmt.Sprintf("Decimal32(%d)", precision) + case v.BitsSize <= 63: + return fmt.Sprintf("Decimal64(%d)", precision) + case v.BitsSize <= 127: + return fmt.Sprintf("Decimal128(%d)", precision) + } + return fmt.Sprintf("Decimal256(%d)", precision) + + case eth.StructType: + return SKIP_FIELD + + case eth.ArrayType: + elemType := generateFieldClickhouseTypes(v.ElementType) + if elemType == "" || elemType == SKIP_FIELD { + return SKIP_FIELD + } + + return fmt.Sprintf("Array(%s)", elemType) + + default: + return "" + } +} + +func generateFieldSqlTypes(fieldType eth.SolidityType) string { + switch v := fieldType.(type) { + case eth.AddressType: + return "VARCHAR(40)" + + case eth.BooleanType: + return "BOOL" + + case eth.BytesType, eth.FixedSizeBytesType, eth.StringType: + return "TEXT" + + case eth.SignedIntegerType: + if v.ByteSize <= 8 { + return "INT" + } + return "DECIMAL" + + case eth.UnsignedIntegerType: + if v.ByteSize <= 8 { + return "INT" + } + return "DECIMAL" + + case eth.SignedFixedPointType, eth.UnsignedFixedPointType: + return "DECIMAL" + + case eth.StructType: + return SKIP_FIELD + + case eth.ArrayType: + elemType := generateFieldSqlTypes(v.ElementType) + if elemType == "" || elemType == SKIP_FIELD { + return SKIP_FIELD + } + + return elemType + "[]" + + default: + return "" + } +} + +func generateFieldTableChangeCode(fieldType eth.SolidityType, fieldAccess string, byRef bool) (setter string, valueAccessCode string) { + switch v := fieldType.(type) { + case eth.AddressType, eth.BytesType, eth.FixedSizeBytesType: + return "set", fmt.Sprintf("Hex(&%s).to_string()", fieldAccess) + + case eth.BooleanType: + return "set", fieldAccess + + case eth.StringType: + return "set", fmt.Sprintf("&%s", fieldAccess) + + case eth.SignedIntegerType: + if v.ByteSize <= 8 { + return "set", fieldAccess + } + return "set", fmt.Sprintf("BigDecimal::from_str(&%s).unwrap()", fieldAccess) + + case eth.UnsignedIntegerType: + if v.ByteSize <= 8 { + return "set", fieldAccess + } + return "set", fmt.Sprintf("BigDecimal::from_str(&%s).unwrap()", fieldAccess) + + case eth.SignedFixedPointType, eth.UnsignedFixedPointType: + return "set", fmt.Sprintf("BigDecimal::from_str(&%s).unwrap()", fieldAccess) + + case eth.ArrayType: + // FIXME: Implement multiple contract support, check what is the actual semantics there + _, inner := generateFieldTableChangeCode(v.ElementType, "x", byRef) + if inner == SKIP_FIELD { + return SKIP_FIELD, SKIP_FIELD + } + + iter := "into_iter()" + if byRef { + iter = "iter()" + } + + return "set_psql_array", fmt.Sprintf("%s.%s.map(|x| %s).collect::>()", fieldAccess, iter, inner) + + case eth.StructType: + return SKIP_FIELD, SKIP_FIELD + + default: + return "", "" + } +} + +func generateFieldTransformCode(fieldType eth.SolidityType, fieldAccess string, byRef bool) string { + switch v := fieldType.(type) { + case eth.AddressType: + return fieldAccess + + case eth.BooleanType, eth.StringType: + return fieldAccess + + case eth.BytesType: + return fieldAccess + + case eth.FixedSizeBytesType: + return fmt.Sprintf("Vec::from(%s)", fieldAccess) + + case eth.SignedIntegerType: + if v.ByteSize <= 8 { + return fmt.Sprintf("Into::::into(%s).to_i64().unwrap()", fieldAccess) + } + return fmt.Sprintf("%s.to_string()", fieldAccess) + + case eth.UnsignedIntegerType: + if v.ByteSize <= 8 { + return fmt.Sprintf("%s.to_u64()", fieldAccess) + } + return fmt.Sprintf("%s.to_string()", fieldAccess) + + case eth.SignedFixedPointType, eth.UnsignedFixedPointType: + return fmt.Sprintf("%s.to_string()", fieldAccess) + + case eth.ArrayType: + inner := generateFieldTransformCode(v.ElementType, "x", byRef) + if inner == SKIP_FIELD { + return SKIP_FIELD + } + + iter := "into_iter()" + if byRef { + iter = "iter()" + } + + return fmt.Sprintf("%s.%s.map(|x| %s).collect::>()", fieldAccess, iter, inner) + + case eth.StructType: + return SKIP_FIELD + + default: + return "" + } +} + +func generateFieldGraphQLTypes(fieldType eth.SolidityType) string { + switch v := fieldType.(type) { + case eth.AddressType: + return "String!" + + case eth.BooleanType: + return "Boolean!" + + case eth.BytesType, eth.FixedSizeBytesType, eth.StringType: + return "String!" + + case eth.SignedIntegerType: + if v.ByteSize <= 8 { + return "Int!" + } + return "BigDecimal!" + + case eth.UnsignedIntegerType: + if v.ByteSize <= 8 { + return "Int!" + } + return "BigDecimal!" + + case eth.SignedFixedPointType, eth.UnsignedFixedPointType: + return "BigDecimal!" + + case eth.ArrayType: + return "[" + generateFieldGraphQLTypes(v.ElementType) + "]!" + + case eth.StructType: + return SKIP_FIELD + + default: + return "" + } +} + +type protoEventModel struct { + // MessageName is the name of the message representing this specific event + MessageName string + + OutputModuleFieldName string + Fields []protoField +} + +type protoCallModel struct { + // MessageName is the name of the message representing this specific call + MessageName string + + OutputModuleFieldName string + Fields []protoField +} + +func (e *protoEventModel) populateFields(log *eth.LogEventDef) error { + if len(log.Parameters) == 0 { + return nil + } + + e.Fields = make([]protoField, 0, len(log.Parameters)) + for _, parameter := range log.Parameters { + fieldName := xstrings.ToSnakeCase(parameter.Name) + fieldName = sanitizeProtoFieldName(fieldName) + fieldType := getProtoFieldType(parameter.Type) + if fieldType == SKIP_FIELD { + continue + } + + if fieldType == "" { + return fmt.Errorf("field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + e.Fields = append(e.Fields, protoField{Name: fieldName, Type: fieldType}) + } + + return nil +} + +func (e *protoCallModel) populateFields(call *eth.MethodDef) error { + if len(call.Parameters) == 0 && len(call.ReturnParameters) == 0 { + return nil + } + + e.Fields = make([]protoField, 0, len(call.Parameters)+len(call.ReturnParameters)) + + for _, parameter := range call.Parameters { + fieldName := xstrings.ToSnakeCase(parameter.Name) + fieldName = sanitizeProtoFieldName(fieldName) + fieldType := getProtoFieldType(parameter.Type) + if fieldType == SKIP_FIELD { + continue + } + + if fieldType == "" { + return fmt.Errorf("field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + e.Fields = append(e.Fields, protoField{Name: fieldName, Type: fieldType}) + } + for _, parameter := range call.ReturnParameters { + fieldName := xstrings.ToSnakeCase("output_" + parameter.Name) + fieldName = sanitizeProtoFieldName(fieldName) + fieldType := getProtoFieldType(parameter.Type) + if fieldType == SKIP_FIELD { + continue + } + + if fieldType == "" { + return fmt.Errorf("field type %q on parameter with name %q is not supported right now", parameter.TypeName, parameter.Name) + } + + e.Fields = append(e.Fields, protoField{Name: fieldName, Type: fieldType}) + } + + return nil +} + +func getProtoFieldType(solidityType eth.SolidityType) string { + switch v := solidityType.(type) { + case eth.AddressType, eth.BytesType, eth.FixedSizeBytesType: + return "bytes" + + case eth.BooleanType: + return "bool" + + case eth.StringType: + return "string" + + case eth.SignedIntegerType: + if v.ByteSize <= 8 { + return "int64" + } + + return "string" + + case eth.UnsignedIntegerType: + if v.ByteSize <= 8 { + return "uint64" + } + + return "string" + + case eth.SignedFixedPointType, eth.UnsignedFixedPointType: + return "string" + + case eth.ArrayType: + // Flaky, I think we should support a single level of "array" + fieldType := getProtoFieldType(v.ElementType) + if fieldType == SKIP_FIELD { + return SKIP_FIELD + } + return "repeated " + fieldType + + case eth.StructType: + return SKIP_FIELD + + default: + return "" + } +} + +type protoField struct { + Name string + Type string +} diff --git a/firehose/substreams/codegen/templates/ethereum_project_test.go b/firehose/substreams/codegen/templates/ethereum_project_test.go new file mode 100644 index 0000000..e1cc398 --- /dev/null +++ b/firehose/substreams/codegen/templates/ethereum_project_test.go @@ -0,0 +1,347 @@ +package templates + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + + "github.com/streamingfast/eth-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEnsureOurProjectCompiles(t *testing.T) { + abiContent, err := os.ReadFile("./ethereum/abi/bayc_contract.abi.json") + require.NoError(t, err) + + abi, err := eth.ParseABIFromBytes(abiContent) + require.NoError(t, err) + + ethereumContracts := []*EthereumContract{NewEthereumContract( + "bayc", + eth.MustNewAddress("0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"), + abi, + string(abiContent), + )} + + for _, contract := range ethereumContracts { + events, err := BuildEventModels(contract.abi) + require.NoError(t, err) + contract.SetEvents(events) + } + + project, err := NewEthereumProject( + "", + "substreams_tests", + EthereumChainsByID["Mainnet"], + ethereumContracts, + 123, + ) + require.NoError(t, err) + + files, err := project.Render() + require.NoError(t, err) + + for _, fileToWrite := range []string{"src/lib.rs"} { + content, found := files[fileToWrite] + require.True(t, found) + + err = os.WriteFile(filepath.Join("ethereum", fileToWrite), content, os.ModePerm) + require.NoError(t, err) + } + + projectDir, err := filepath.Abs("./ethereum") + require.NoError(t, err) + + cmd := exec.Command("cargo", "build", "--release", "--target", "wasm32-unknown-unknown") + cmd.Dir = projectDir + + output, err := cmd.CombinedOutput() + require.NoError(t, err, "Command %q in %q failed with state %s\n%s", cmd, projectDir, cmd.ProcessState, string(output)) +} + +func TestNewEthereumTemplateProject(t *testing.T) { + abiContent := fileContent(t, "ethereum/abi/bayc_contract.abi.json") + + type dds struct { + targetTypeName string + targetABI []byte + event string + addressField string + withCalls bool + } + type args struct { + address string + abi []byte + shortName string + dynamicDataSources []*dds + withCalls bool + } + tests := []struct { + name string + args []args + startBlock uint64 + want map[string][]byte + assertion require.ErrorAssertionFunc + }{ + { + name: "standard case - all sinks", + args: []args{ + { + address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", + abi: abiContent, + shortName: "bayc", + }, + }, + startBlock: 123, + want: map[string][]byte{ + "abi/bayc_contract.abi.json": abiContent, + "proto/contract.proto": fileContent(t, "./ethereum/proto/contract.proto"), + "src/abi/mod.rs": fileContent(t, "./ethereum/src/abi/mod.rs"), + "src/pb/mod.rs": fileContent(t, "./ethereum/src/pb/mod.rs"), + "src/lib.rs": fileContent(t, "./ethereum/src/lib.rs"), + "build.rs": fileContent(t, "./ethereum/build.rs"), + "Cargo.lock": fileContent(t, "./ethereum/Cargo.lock"), + "Cargo.toml": fileContent(t, "./ethereum/Cargo.toml"), + "Makefile": fileContent(t, "./ethereum/Makefile"), + "substreams.yaml": fileContent(t, "./ethereum/substreams.yaml"), + "substreams.sql.yaml": fileContent(t, "./ethereum/substreams.sql.yaml"), + "substreams.clickhouse.yaml": fileContent(t, "./ethereum/substreams.clickhouse.yaml"), + "substreams.subgraph.yaml": fileContent(t, "./ethereum/substreams.subgraph.yaml"), + "rust-toolchain.toml": fileContent(t, "./ethereum/rust-toolchain.toml"), + "schema.sql": fileContent(t, "./ethereum/schema.sql"), + "schema.clickhouse.sql": fileContent(t, "./ethereum/schema.clickhouse.sql"), + "schema.graphql": fileContent(t, "./ethereum/schema.graphql"), + "subgraph.yaml": fileContent(t, "./ethereum/subgraph.yaml"), + }, + assertion: require.NoError, + }, + { + name: "multiple contracts - all sinks", + args: []args{ + { + address: "0x23581767a106ae21c074b2276d25e5c3e136a68b", + abi: fileContent(t, "ethereum/results/multiple_contracts/abi/moonbird_contract.abi.json"), + shortName: "moonbird", + }, + { + address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", + abi: fileContent(t, "ethereum/results/multiple_contracts/abi/bayc_contract.abi.json"), + shortName: "bayc", + }, + }, + startBlock: 123, + want: map[string][]byte{ + "abi/bayc_contract.abi.json": fileContent(t, "ethereum/results/multiple_contracts/abi/bayc_contract.abi.json"), + "abi/moonbird_contract.abi.json": fileContent(t, "ethereum/results/multiple_contracts/abi/moonbird_contract.abi.json"), + "proto/contract.proto": fileContent(t, "./ethereum/results/multiple_contracts/proto/contract.proto"), + "src/abi/mod.rs": fileContent(t, "./ethereum/results/multiple_contracts/src/abi/mod.rs"), + "src/pb/mod.rs": fileContent(t, "./ethereum/results/multiple_contracts/src/pb/mod.rs"), + "src/lib.rs": fileContent(t, "./ethereum/results/multiple_contracts/src/lib.rs"), + "build.rs": fileContent(t, "./ethereum/results/multiple_contracts/build.rs"), + "Cargo.lock": fileContent(t, "./ethereum/Cargo.lock"), + "Cargo.toml": fileContent(t, "./ethereum/Cargo.toml"), + "Makefile": fileContent(t, "./ethereum/Makefile"), + "substreams.yaml": fileContent(t, "./ethereum/substreams.yaml"), + "substreams.sql.yaml": fileContent(t, "./ethereum/substreams.sql.yaml"), + "substreams.clickhouse.yaml": fileContent(t, "./ethereum/substreams.clickhouse.yaml"), + "substreams.subgraph.yaml": fileContent(t, "./ethereum/substreams.subgraph.yaml"), + "rust-toolchain.toml": fileContent(t, "./ethereum/rust-toolchain.toml"), + "schema.sql": fileContent(t, "./ethereum/results/multiple_contracts/schema.sql"), + "schema.clickhouse.sql": fileContent(t, "./ethereum/results/multiple_contracts/schema.clickhouse.sql"), + "schema.graphql": fileContent(t, "./ethereum/results/multiple_contracts/schema.graphql"), + "subgraph.yaml": fileContent(t, "./ethereum/subgraph.yaml"), + }, + assertion: require.NoError, + }, + { + name: "dynamic datasource", + args: []args{ + { + address: "0x1f98431c8ad98523631ae4a59f267346ea31f984", + abi: fileContent(t, "ethereum/results/dynamic_datasource/abi/factory_contract.abi.json"), + shortName: "factory", + dynamicDataSources: []*dds{ + { + targetTypeName: "pool", + addressField: "pool", + targetABI: fileContent(t, "ethereum/results/dynamic_datasource/abi/pool_contract.abi.json"), + event: "PoolCreated", + }, + }, + }, + }, + startBlock: 12369621, + want: map[string][]byte{ + "abi/factory_contract.abi.json": fileContent(t, "ethereum/results/dynamic_datasource/abi/factory_contract.abi.json"), + "abi/pool_contract.abi.json": fileContent(t, "ethereum/results/dynamic_datasource/abi/pool_contract.abi.json"), + "proto/contract.proto": fileContent(t, "./ethereum/results/dynamic_datasource/proto/contract.proto"), + "src/abi/mod.rs": fileContent(t, "./ethereum/results/dynamic_datasource/src/abi/mod.rs"), + "src/pb/mod.rs": fileContent(t, "./ethereum/results/dynamic_datasource/src/pb/mod.rs"), + "src/lib.rs": fileContent(t, "./ethereum/results/dynamic_datasource/src/lib.rs"), + "build.rs": fileContent(t, "./ethereum/results/dynamic_datasource/build.rs"), + "Cargo.lock": fileContent(t, "./ethereum/Cargo.lock"), + "Cargo.toml": fileContent(t, "./ethereum/Cargo.toml"), + "Makefile": fileContent(t, "./ethereum/Makefile"), + "substreams.yaml": fileContent(t, "./ethereum/results/dynamic_datasource/substreams.yaml"), + "substreams.sql.yaml": fileContent(t, "./ethereum/results/dynamic_datasource/substreams.sql.yaml"), + "substreams.clickhouse.yaml": fileContent(t, "./ethereum/results/dynamic_datasource/substreams.clickhouse.yaml"), + "substreams.subgraph.yaml": fileContent(t, "./ethereum/results/dynamic_datasource/substreams.subgraph.yaml"), + "rust-toolchain.toml": fileContent(t, "./ethereum/rust-toolchain.toml"), + "schema.sql": fileContent(t, "./ethereum/results/dynamic_datasource/schema.sql"), + "schema.clickhouse.sql": fileContent(t, "./ethereum/results/dynamic_datasource/schema.clickhouse.sql"), + "schema.graphql": fileContent(t, "./ethereum/results/dynamic_datasource/schema.graphql"), + "subgraph.yaml": fileContent(t, "./ethereum/subgraph.yaml"), + }, + assertion: require.NoError, + }, + + { + name: "dynamic datasource_with_calls", + args: []args{ + { + address: "0x1f98431c8ad98523631ae4a59f267346ea31f984", + abi: fileContent(t, "ethereum/results/dynamic_datasource/abi/factory_contract.abi.json"), + shortName: "factory", + withCalls: true, + dynamicDataSources: []*dds{ + { + targetTypeName: "pool", + addressField: "pool", + targetABI: fileContent(t, "ethereum/results/dynamic_datasource/abi/pool_contract.abi.json"), + event: "PoolCreated", + withCalls: true, + }, + }, + }, + }, + startBlock: 12369621, + want: map[string][]byte{ + "abi/factory_contract.abi.json": fileContent(t, "ethereum/results/dynamic_datasource_with_calls/abi/factory_contract.abi.json"), + "abi/pool_contract.abi.json": fileContent(t, "ethereum/results/dynamic_datasource_with_calls/abi/pool_contract.abi.json"), + "proto/contract.proto": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/proto/contract.proto"), + "src/abi/mod.rs": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/src/abi/mod.rs"), + "src/pb/mod.rs": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/src/pb/mod.rs"), + "src/lib.rs": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/src/lib.rs"), + "build.rs": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/build.rs"), + "Cargo.lock": fileContent(t, "./ethereum/Cargo.lock"), + "Cargo.toml": fileContent(t, "./ethereum/Cargo.toml"), + "Makefile": fileContent(t, "./ethereum/Makefile"), + "substreams.yaml": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/substreams.yaml"), + "substreams.sql.yaml": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/substreams.sql.yaml"), + "substreams.clickhouse.yaml": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/substreams.clickhouse.yaml"), + "substreams.subgraph.yaml": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/substreams.subgraph.yaml"), + "rust-toolchain.toml": fileContent(t, "./ethereum/rust-toolchain.toml"), + "schema.sql": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/schema.sql"), + "schema.clickhouse.sql": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/schema.clickhouse.sql"), + "schema.graphql": fileContent(t, "./ethereum/results/dynamic_datasource_with_calls/schema.graphql"), + "subgraph.yaml": fileContent(t, "./ethereum/subgraph.yaml"), + }, + assertion: require.NoError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + chain := EthereumChainsByID["Mainnet"] + var ethereumContracts []*EthereumContract + for _, arg := range tt.args { + abi, err := eth.ParseABIFromBytes(arg.abi) + require.NoError(t, err) + + ethContract := NewEthereumContract( + arg.shortName, + eth.MustNewAddress(arg.address), + abi, + string(arg.abi), + ) + if arg.withCalls { + ethContract.withCalls = true + } + for _, dds := range arg.dynamicDataSources { + abi, err := eth.ParseABIFromBytes(dds.targetABI) + require.NoError(t, err) + ethContract.AddDynamicDataSource(dds.targetTypeName, abi, string(dds.targetABI), dds.event, dds.addressField, dds.withCalls) + } + + ethereumContracts = append(ethereumContracts, ethContract) + } + + for _, contract := range ethereumContracts { + events, err := BuildEventModels(contract.abi) + require.NoError(t, err) + contract.SetEvents(events) + + if contract.withCalls { + calls, err := BuildCallModels(contract.abi) + require.NoError(t, err) + contract.SetCalls(calls) + } + } + + project, err := NewEthereumProject( + "substreams-init-test", + "substreams_init_test", + chain, + ethereumContracts, + tt.startBlock, + ) + require.NoError(t, err) + + got, err := project.Render() + require.NoError(t, err) + + keysExpected := keys(tt.want) + keysActual := keys(got) + + assert.ElementsMatch(t, keysExpected, keysActual, "Entries key are different") + for wantEntry, wantContent := range tt.want { + filename := strings.ReplaceAll(wantEntry, string(filepath.Separator), "_") + wantFilename := filepath.Join(os.TempDir(), fmt.Sprintf("want.%s", filename)) + gotFilename := filepath.Join(os.TempDir(), fmt.Sprintf("got.%s", filename)) + + if !assert.Equal(t, string(wantContent), string(got[wantEntry]), "File %q amd %q are different", wantFilename, gotFilename) { + err := os.WriteFile(wantFilename, wantContent, os.ModePerm) + require.NoError(t, err) + + err = os.WriteFile(gotFilename, got[wantEntry], os.ModePerm) + require.NoError(t, err) + } + } + }) + } +} + +func TestProtoFieldName(t *testing.T) { + tests := []struct { + name string + input string + expected string + }{ + { + name: "no starting underscore", + input: "tokenId", + expected: "tokenId", + }, + { + name: "input starting with an underscore", + input: "_tokenId", + expected: "u_tokenId", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require.Equal(t, test.expected, sanitizeProtoFieldName(test.input)) + }) + } +} + +func fileContent(t *testing.T, path string) []byte { + content, err := os.ReadFile(path) + require.NoError(t, err) + + return content +} diff --git a/firehose/substreams/codegen/templates/generator/buildsh.gotmpl b/firehose/substreams/codegen/templates/generator/buildsh.gotmpl new file mode 100644 index 0000000..94209b2 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/buildsh.gotmpl @@ -0,0 +1,3 @@ +#!/bin/bash + +cargo build --target wasm32-unknown-unknown --release \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/generator/cargotoml.gotmpl b/firehose/substreams/codegen/templates/generator/cargotoml.gotmpl new file mode 100644 index 0000000..0a595cb --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/cargotoml.gotmpl @@ -0,0 +1,26 @@ +{{$engine := . -}} +[package] +name = "{{ $engine.ProjectName }}" +version = "{{ $engine.ProjectVersion }}" +description = "" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +ethabi = "17.0" +hex = "0.4.3" +hex-literal = "0.3.4" +prost = "0.10.1" + +# Use latest from https://crates.io/crates/substreams +substreams = "{{ $engine.SubstreamsVersion }}" + +# Use latest from https://crates.io/crates/substreams-ethereum +#substreams-ethereum = "0.1.2" + +[build-dependencies] +anyhow = "1" +prost-build = "0.10.4" +substreams-ethereum = "0.1.0" \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/generator/externs.gotmpl b/firehose/substreams/codegen/templates/generator/externs.gotmpl new file mode 100644 index 0000000..71e92c3 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/externs.gotmpl @@ -0,0 +1,74 @@ +{{$engine := . -}} +use substreams::prelude::*; +use substreams::errors::Error; +use crate::pb; +use crate::generated::substreams::{Substreams, SubstreamsTrait}; + +{{range $engine.Manifest.Modules -}} +{{$module := . -}} +{{$functionSignature := $engine.FunctionSignature $module}} +#[no_mangle] +pub extern "C" fn {{$functionSignature.Name}}( +{{- range $i, $value := $functionSignature.Arguments}} + {{- if $value.ModuleInput.IsStore }} + {{- if eq $value.ModuleInput.Mode "deltas"}} + {{$value.Name}}_deltas_ptr: *mut u8, + {{$value.Name}}_deltas_len: usize, + {{- else}} + {{$value.Name}}_ptr: u32, + {{- end}} + {{- else}} + {{$value.Name}}_ptr: *mut u8, + {{$value.Name}}_len: usize, + {{- end -}} + {{- end }} +) { + substreams::register_panic_hook(); + let func = || {{- if eq $module.Kind "map"}}-> Result<{{$functionSignature.OutputType}}, Error>{{end -}} { + {{if eq $module.Kind "store" -}}{{/* This is the store for a store module*/}} + {{$engine.WritableStoreDeclaration $module }} + {{ end -}} + + {{ range $id, $argument := $functionSignature.Arguments -}} + {{- if $argument.ModuleInput.IsStore -}} + {{- $m := $engine.MustModule $argument.Name }} + {{ $engine.ReadableStoreDeclaration $argument.Name $m $argument.ModuleInput }} + {{- end -}} + {{- if $argument.ModuleInput.IsMap }} + let {{$argument.Name}}: {{$argument.Type}} = substreams::proto::decode_ptr({{$argument.Name}}_ptr, {{$argument.Name}}_len).unwrap(); + {{- end -}} + + {{- if $argument.ModuleInput.IsSource }} + let {{$argument.Name}}: {{$argument.Type}} = substreams::proto::decode_ptr({{$argument.Name}}_ptr, {{$argument.Name}}_len).unwrap(); + {{- end -}} + + {{- if $argument.ModuleInput.IsParams }} + let {{$argument.Name}}: {{$argument.Type}} = std::mem::ManuallyDrop::new(unsafe { String::from_raw_parts({{$argument.Name}}_ptr, {{$argument.Name}}_len, {{$argument.Name}}_len) }).to_string(); + {{- end -}} + {{ end }} + + Substreams::{{.Name}}( + {{- range $id, $argument := $functionSignature.Arguments -}} + {{- if eq $argument.ModuleInput.Mode "deltas" -}} + {{$argument.Name}}_deltas, + {{ else -}} + {{$argument.Name}}, + {{ end -}} + {{end}} + {{- if eq $module.Kind "store" -}} + store, + {{- end }} + ) + }; + + {{- if eq $module.Kind "store"}} + func() + {{- else}} + let result = func(); + if result.is_err() { + panic!("{:?}", &result.err().unwrap()); + } + substreams::output(result.unwrap()); + {{- end}} +} +{{end -}} \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/generator/lib.gotmpl b/firehose/substreams/codegen/templates/generator/lib.gotmpl new file mode 100644 index 0000000..10d9d43 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/lib.gotmpl @@ -0,0 +1,46 @@ +{{$engine := . -}} +mod pb; +mod generated; +use substreams::prelude::*; +use substreams::errors::Error; + +impl generated::substreams::SubstreamsTrait for generated::substreams::Substreams{ +{{range $engine.Manifest.Modules -}} + {{$module := . -}} + {{- with ($engine.FunctionSignature $module) -}} + {{- $functionSignature := . -}} + {{- template "function" $functionSignature -}} + {{end -}} +{{end -}} +} + +{{define "function"}} +{{- $functionSignature := . }} + fn {{$functionSignature.Name}}( + {{- range $id, $arg := $functionSignature.Arguments }} + {{ template "arg" $arg }}, {{- end -}} + {{- if ne $functionSignature.StorePolicy "" -}} + {{- $m := getEngine.MustModule $functionSignature.Name }} + _store: {{ getEngine.WritableStoreType $m }}, + {{- end }} + ) + {{- if ne $functionSignature.OutputType "" }} -> Result<{{$functionSignature.OutputType}}, Error>{{end}} { + todo!() + } +{{end }} + +{{define "arg" -}} +{{- $engine := getEngine -}} +{{- $argument := . -}} + {{- if $argument.ModuleInput.IsStore -}} + {{- $m := $engine.MustModule $argument.Name -}} + {{- if eq $argument.ModuleInput.Mode "deltas" -}} + _{{$argument.Name}}_deltas + {{- else -}} + _{{$argument.Name -}} + {{- end -}} + : {{ getEngine.ReadableStoreType $m $argument.ModuleInput -}} + {{- else -}} + _{{$argument.Name}}: {{$argument.Type -}} + {{- end -}} +{{- end -}} diff --git a/firehose/substreams/codegen/templates/generator/manifestyaml.gotmpl b/firehose/substreams/codegen/templates/generator/manifestyaml.gotmpl new file mode 100644 index 0000000..a050542 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/manifestyaml.gotmpl @@ -0,0 +1,17 @@ +{{$engine := . -}} +specVersion: v0.1.0 +package: + name: {{ $engine.ProjectName }} + version: {{ $engine.ProjectVersion }} + url: # ADD REPOSITORY URL HERE # + doc: | + ##### ADD DOCUMENTATION STRING HERE ##### + +protobuf: + files: [] + importPaths: + - ./proto + +binaries: {} + +modules: [] diff --git a/firehose/substreams/codegen/templates/generator/mod.gotmpl b/firehose/substreams/codegen/templates/generator/mod.gotmpl new file mode 100644 index 0000000..2af8e78 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/mod.gotmpl @@ -0,0 +1,2 @@ +pub mod substreams; +mod externs; diff --git a/firehose/substreams/codegen/templates/generator/pb_mod.gotmpl b/firehose/substreams/codegen/templates/generator/pb_mod.gotmpl new file mode 100644 index 0000000..8986e52 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/pb_mod.gotmpl @@ -0,0 +1,6 @@ +{{- range $k, $v := . -}} +#[allow(unused_imports)] +#[allow(dead_code)] +#[path = "./{{$k}}.rs"] +pub mod {{$v}}; +{{end}} \ No newline at end of file diff --git a/firehose/substreams/codegen/templates/generator/rusttoolchain.gotmpl b/firehose/substreams/codegen/templates/generator/rusttoolchain.gotmpl new file mode 100644 index 0000000..68f28d9 --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/rusttoolchain.gotmpl @@ -0,0 +1,5 @@ +{{$engine := . -}} +[toolchain] +channel = "{{ $engine.RustVersion }}" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] diff --git a/firehose/substreams/codegen/templates/generator/substreams.gotmpl b/firehose/substreams/codegen/templates/generator/substreams.gotmpl new file mode 100644 index 0000000..abe3d2d --- /dev/null +++ b/firehose/substreams/codegen/templates/generator/substreams.gotmpl @@ -0,0 +1,45 @@ +// Code generated by Substreams. DO NOT EDIT. +{{$engine := . -}} +use crate::pb; +use substreams::errors::Error; + +pub struct Substreams{} + +pub trait SubstreamsTrait { +{{range $engine.Manifest.Modules -}} + {{$module := . -}} + {{- with ($engine.FunctionSignature $module) -}} + {{- $functionSignature := . -}} + {{- template "function" $functionSignature -}} + {{end -}} +{{end -}} +} + +{{define "function"}} +{{- $functionSignature := .}} + fn {{$functionSignature.Name}}( + {{- range $id, $arg := $functionSignature.Arguments }} + {{ template "arg" $arg }}, {{- end -}} + {{- if ne $functionSignature.StorePolicy "" -}} + {{- $m := getEngine.MustModule $functionSignature.Name }} + store: {{ getEngine.WritableStoreType $m }}, + {{- end }} + ) + {{- if ne .OutputType "" }} -> Result<{{.OutputType}}, Error>{{end}}; +{{end }} + +{{define "arg" -}} + {{- $engine := getEngine -}} + {{- $argument := . -}} + {{- if $argument.ModuleInput.IsStore -}} + {{- $m := $engine.MustModule $argument.Name -}} + {{- if eq $argument.ModuleInput.Mode "deltas" -}} + {{$argument.Name}}_deltas + {{- else -}} + {{$argument.Name -}} + {{- end -}} + : {{ $engine.ReadableStoreType $m $argument.ModuleInput -}} + {{- else -}} + {{$argument.Name}}: {{$argument.Type -}} + {{- end -}} +{{- end -}} diff --git a/firehose/substreams/codegen/templates/helpers.go b/firehose/substreams/codegen/templates/helpers.go new file mode 100644 index 0000000..8a636d7 --- /dev/null +++ b/firehose/substreams/codegen/templates/helpers.go @@ -0,0 +1,16 @@ +package templates + +func keys[K comparable, V any](entries map[K]V) (out []K) { + if len(entries) == 0 { + return nil + } + + out = make([]K, len(entries)) + i := 0 + for k := range entries { + out[i] = k + i++ + } + + return +} diff --git a/firehose/substreams/codegen/templates/logging.go b/firehose/substreams/codegen/templates/logging.go new file mode 100644 index 0000000..b6109dd --- /dev/null +++ b/firehose/substreams/codegen/templates/logging.go @@ -0,0 +1,7 @@ +package templates + +import ( + "github.com/streamingfast/logging" +) + +var zlog, _ = logging.PackageLogger("substreams", "github.com/streamingfast/substreams/codegen/templates") diff --git a/firehose/substreams/codegen/templates/project.go b/firehose/substreams/codegen/templates/project.go new file mode 100644 index 0000000..d71b939 --- /dev/null +++ b/firehose/substreams/codegen/templates/project.go @@ -0,0 +1,25 @@ +package templates + +import ( + "strings" + "text/template" +) + +type Project interface { + Render() (map[string][]byte, error) +} + +type ProjectFunc func() (map[string][]byte, error) + +func (f ProjectFunc) Render() (map[string][]byte, error) { + return f() +} + +var ProjectGeneratorFuncs = template.FuncMap{ + "add": func(left int, right int) int { + return left + right + }, + "sanitizeProtoFieldName": sanitizeProtoFieldName, + "toUpper": strings.ToUpper, + "capitalizeFirst": strings.Title, +} diff --git a/firehose/substreams/codegen/templates/testdata/rendered/ethereum/src/lib.rs.rendered b/firehose/substreams/codegen/templates/testdata/rendered/ethereum/src/lib.rs.rendered new file mode 100644 index 0000000..1918abd --- /dev/null +++ b/firehose/substreams/codegen/templates/testdata/rendered/ethereum/src/lib.rs.rendered @@ -0,0 +1,80 @@ +mod abi; +mod pb; +use hex_literal::hex; +#[allow(unused_imports)] +use num_traits::ToPrimitive; +use pb::contrack; +use serde_json::json; +use substreams::Hex; +use substreams_ethereum::pb::eth::v2 as eth; +use substreams_ethereum::Event; + +const CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); + +substreams_ethereum::init!(); + +#[substreams::handlers::map] +fn map_events(blk: eth::Block) -> Result { + Ok(contrack::Contrack { + events: blk + .receipts() + .flat_map(|view| { + view.receipt.logs.iter().filter_map(|log| { + if log.address != CONTRACT { + return None; + } + if let Some(event) = abi::contrack::events::Approval::match_and_decode(log) { + return Some( + json!({ + "block_number": blk.number, + "block_hash": Hex(&blk.hash).to_string(), + "trx_hash": Hex(&view.transaction.hash).to_string(), + "log_index": log.block_index, + "event_name": abi::contrack::events::Approval::NAME, + "approved": Hex(&event.approved).to_string(), + "owner": Hex(&event.owner).to_string(), + "token_id": event.token_id.to_string(), + }) + .to_string() + .into_bytes(), + ); + } + if let Some(event) = abi::contrack::events::ApprovalForAll::match_and_decode(log) { + return Some( + json!({ + "block_number": blk.number, + "block_hash": Hex(&blk.hash).to_string(), + "trx_hash": Hex(&view.transaction.hash).to_string(), + "log_index": log.block_index, + "event_name": abi::contrack::events::ApprovalForAll::NAME, + "approved": event.approved, + "operator": Hex(&event.operator).to_string(), + "owner": Hex(&event.owner).to_string(), + }) + .to_string() + .into_bytes(), + ); + } + if let Some(event) = abi::contrack::events::Transfer::match_and_decode(log) { + return Some( + json!({ + "block_number": blk.number, + "block_hash": Hex(&blk.hash).to_string(), + "trx_hash": Hex(&view.transaction.hash).to_string(), + "log_index": log.block_index, + "event_name": abi::contrack::events::Transfer::NAME, + "from": Hex(&event.from).to_string(), + "to": Hex(&event.to).to_string(), + "token_id": event.token_id.to_string(), + }) + .to_string() + .into_bytes(), + ); + } + + None + }) + }) + .collect(), + }) +} diff --git a/firehose/substreams/codegen/test_substreams/Cargo.lock b/firehose/substreams/codegen/test_substreams/Cargo.lock new file mode 100644 index 0000000..4aa01dd --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/Cargo.lock @@ -0,0 +1,1218 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bigdecimal" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake3" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "cc", + "cfg-if 0.1.10", + "constant_time_eq", + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "ethabi" +version = "17.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4966fba78396ff92db3b817ee71143eccd98acf0f876b8d600e585a670c5d1b" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-rlp", + "impl-serde", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-rlp", + "impl-serde", + "primitive-types", + "uint", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "firestorm" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31586bda1b136406162e381a3185a506cdfc1631708dd40cba2f6628d8634499" + +[[package]] +name = "firestorm" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5f6c2c942da57e2aaaa84b8a521489486f14e75e7fa91dab70aba913975f98" + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + +[[package]] +name = "ibig" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1fcc7f316b2c079dde77564a1360639c1a956a23fa96122732e416cb10717bb" +dependencies = [ + "cfg-if 1.0.0", + "num-traits", + "rand", + "static_assertions", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" + +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" + +[[package]] +name = "pad" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "parity-scale-codec" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "366e44391a8af4cfd6002ef6ba072bae071a96aafca98d7d448a34c5dca38b6a" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "prettyplease" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +dependencies = [ + "bytes", + "prost", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "serde" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha3" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +dependencies = [ + "digest 0.10.5", + "keccak", +] + +[[package]] +name = "stable-hash" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10196e68950ed99c0d2db7a30ffaf4dfe0bbf2f9af2ae0457ee8ad396e0a2dd7" +dependencies = [ + "blake3", + "firestorm 0.4.6", + "ibig", + "lazy_static", + "leb128", + "num-traits", +] + +[[package]] +name = "stable-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af75bd21beb162eab69de76abbb803d4111735ead00d5086dcc6f4ddb3b53cc9" +dependencies = [ + "blake3", + "firestorm 0.5.1", + "ibig", + "lazy_static", + "leb128", + "num-traits", + "xxhash-rust", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "substreams" +version = "0.3.2" +dependencies = [ + "bigdecimal", + "hex", + "hex-literal", + "num-bigint", + "num-traits", + "pad", + "prost", + "prost-build", + "prost-types", + "substreams-macro 0.3.2", + "thiserror", + "wee_alloc", +] + +[[package]] +name = "substreams" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02767a4d87afb56de517be75bcfe985a8c0e2694ed9383e09807df29252d3b7" +dependencies = [ + "bigdecimal", + "hex", + "hex-literal", + "num-bigint", + "num-traits", + "pad", + "prost", + "prost-build", + "prost-types", + "substreams-macro 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", + "wee_alloc", +] + +[[package]] +name = "substreams-entity-change" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b5ca1660164eb2208ed2a09256a5e3066bf25b202217d10f42378d3d83c00f9" +dependencies = [ + "base64", + "prost", + "prost-types", + "substreams 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "substreams-ethereum" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b06e9e1389570981b251e304262124f26c30d5c2abc3a07d2cf048634db7cbb" +dependencies = [ + "getrandom", + "substreams 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "substreams-ethereum-abigen", + "substreams-ethereum-core", + "substreams-ethereum-derive", +] + +[[package]] +name = "substreams-ethereum-abigen" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b1318b5c08e443b70de4eb2384d302db9e71661ab9b4f7093eed16cd017494" +dependencies = [ + "anyhow", + "ethabi", + "heck", + "hex", + "prettyplease", + "proc-macro2", + "quote", + "substreams-ethereum-core", + "syn", +] + +[[package]] +name = "substreams-ethereum-core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6666ede4d11990eb6a98e2069299816c2ec3d48cca5596d4e81f2d43fc0936ab" +dependencies = [ + "bigdecimal", + "ethabi", + "getrandom", + "num-bigint", + "prost", + "prost-build", + "prost-types", + "substreams 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "substreams-ethereum-derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32640123b026ecf1b42f5ac1f40b858d07c862f218ecb768809b57ab26f2a1a6" +dependencies = [ + "ethabi", + "heck", + "hex", + "proc-macro2", + "quote", + "substreams-ethereum-abigen", + "syn", +] + +[[package]] +name = "substreams-macro" +version = "0.3.2" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "thiserror", +] + +[[package]] +name = "substreams-macro" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465371d88738a62f6a186abee0ff9ac4c117fe087984cc37b1904bfe6f77cd62" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "thiserror", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "test_substreams" +version = "0.0.1" +dependencies = [ + "anyhow", + "base64", + "bigdecimal", + "ethabi", + "hex", + "num-bigint", + "num-traits", + "pad", + "prost", + "prost-build", + "prost-types", + "stable-hash 0.3.3", + "stable-hash 0.4.2", + "substreams 0.3.2", + "substreams-entity-change", + "substreams-ethereum", + "thiserror", + "wasm-bindgen", +] + +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "which" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" diff --git a/firehose/substreams/codegen/test_substreams/Cargo.toml b/firehose/substreams/codegen/test_substreams/Cargo.toml new file mode 100644 index 0000000..61aa85d --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "test_substreams" +version = "0.0.1" +description = "Code generation Test Substreams" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +ethabi = "17.0" +wasm-bindgen = "0.2.79" +prost = "0.11.0" +prost-types = "0.11.0" +hex = "0.4.3" +#substreams = "^0.3.0" +substreams = { path = "../../../substreams-rs/substreams" } +substreams-ethereum = "0.6.2" +num-bigint = "0.4" +bigdecimal = "0.3" +pad = "0.1" +base64 = "0.13.0" +stable-hash_legacy = { version = "0.3.3", package = "stable-hash" } +stable-hash = { version = "0.4.2"} +thiserror = "1.0.25" +num-traits = "0.2.15" +substreams-entity-change = "0.1.0" + +[build-dependencies] +prost-build = "0.11.0" +anyhow = "1" +substreams-ethereum = "0.6.2" diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/externs.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/externs.rs new file mode 100644 index 0000000..811d22c --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/externs.rs @@ -0,0 +1,159 @@ +use substreams::prelude::*; +use substreams::errors::Error; +use crate::pb; +use crate::generated::substreams::{Substreams, SubstreamsTrait}; + + +#[no_mangle] +pub extern "C" fn map_block( + block_ptr: *mut u8, + block_len: usize, +) { + substreams::register_panic_hook(); + let func = ||-> Result{ + + let block: substreams_ethereum::pb::eth::v2::Block = substreams::proto::decode_ptr(block_ptr, block_len).unwrap(); + + Substreams::map_block(block, + + ) + }; + let result = func(); + if result.is_err() { + panic!("{:?}", &result.err().unwrap()); + } + substreams::output(result.unwrap()); +} + +#[no_mangle] +pub extern "C" fn map_block_i64( + block_ptr: *mut u8, + block_len: usize, +) { + substreams::register_panic_hook(); + let func = ||-> Result{ + + let block: substreams_ethereum::pb::eth::v2::Block = substreams::proto::decode_ptr(block_ptr, block_len).unwrap(); + + Substreams::map_block_i64(block, + + ) + }; + let result = func(); + if result.is_err() { + panic!("{:?}", &result.err().unwrap()); + } + substreams::output(result.unwrap()); +} + +#[no_mangle] +pub extern "C" fn store_test( + block_ptr: *mut u8, + block_len: usize, + map_block_ptr: *mut u8, + map_block_len: usize, +) { + substreams::register_panic_hook(); + let func = ||{ + + let store: substreams::store::StoreSetProto = substreams::store::StoreSetProto::new(); + + let block: substreams_ethereum::pb::eth::v2::Block = substreams::proto::decode_ptr(block_ptr, block_len).unwrap(); + let map_block: pb::my_types_v1::Tests = substreams::proto::decode_ptr(map_block_ptr, map_block_len).unwrap(); + + Substreams::store_test(block, + map_block, + store, + ) + }; + func() +} + +#[no_mangle] +pub extern "C" fn store_append_string( + block_ptr: *mut u8, + block_len: usize, +) { + substreams::register_panic_hook(); + let func = ||{ + + let store: substreams::store::StoreAppend = substreams::store::StoreAppend::new(); + + let block: substreams_ethereum::pb::eth::v2::Block = substreams::proto::decode_ptr(block_ptr, block_len).unwrap(); + + Substreams::store_append_string(block, + store, + ) + }; + func() +} + +#[no_mangle] +pub extern "C" fn store_bigint( + block_ptr: *mut u8, + block_len: usize, +) { + substreams::register_panic_hook(); + let func = ||{ + + let store: substreams::store::StoreSetBigInt = substreams::store::StoreSetBigInt::new(); + + let block: substreams_ethereum::pb::eth::v2::Block = substreams::proto::decode_ptr(block_ptr, block_len).unwrap(); + + Substreams::store_bigint(block, + store, + ) + }; + func() +} + +#[no_mangle] +pub extern "C" fn store_test2( + block_ptr: *mut u8, + block_len: usize, + map_block_ptr: *mut u8, + map_block_len: usize, + store_test_ptr: u32, + store_test_deltas_ptr: *mut u8, + store_test_deltas_len: usize, + map_block_i64_ptr: *mut u8, + map_block_i64_len: usize, + store_bigint_ptr: u32, + store_bigint_deltas_ptr: *mut u8, + store_bigint_deltas_len: usize, + store_append_string_ptr: u32, + store_append_string_deltas_ptr: *mut u8, + store_append_string_deltas_len: usize, +) { + substreams::register_panic_hook(); + let func = ||{ + + let store: substreams::store::StoreSetProto = substreams::store::StoreSetProto::new(); + + let block: substreams_ethereum::pb::eth::v2::Block = substreams::proto::decode_ptr(block_ptr, block_len).unwrap(); + let map_block: pb::my_types_v1::Tests = substreams::proto::decode_ptr(map_block_ptr, map_block_len).unwrap(); + let store_test: substreams::store::StoreGetProto = substreams::store::StoreGetProto::new(store_test_ptr); + let raw_store_test_deltas = substreams::proto::decode_ptr::(store_test_deltas_ptr, store_test_deltas_len).unwrap().deltas; + let store_test_deltas: substreams::store::Deltas> = substreams::store::Deltas::new(raw_store_test_deltas); + let map_block_i64: pb::my_types_v1::Number = substreams::proto::decode_ptr(map_block_i64_ptr, map_block_i64_len).unwrap(); + let store_bigint: substreams::store::StoreGetBigInt = substreams::store::StoreGetBigInt::new(store_bigint_ptr); + let raw_store_bigint_deltas = substreams::proto::decode_ptr::(store_bigint_deltas_ptr, store_bigint_deltas_len).unwrap().deltas; + let store_bigint_deltas: substreams::store::Deltas = substreams::store::Deltas::new(raw_store_bigint_deltas); + let store_append_string: substreams::store::StoreGetRaw = substreams::store::StoreGetRaw::new(store_append_string_ptr); + let raw_store_append_string_deltas = substreams::proto::decode_ptr::(store_append_string_deltas_ptr, store_append_string_deltas_len).unwrap().deltas; + let store_append_string_deltas: substreams::store::Deltas> = substreams::store::Deltas::new(raw_store_append_string_deltas); + + Substreams::store_test2(block, + map_block, + store_test, + store_test_deltas, + map_block_i64, + store_bigint, + store_bigint_deltas, + store_append_string, + store_append_string_deltas, + store, + ) + }; + func() +} diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/mod.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/mod.rs new file mode 100644 index 0000000..2af8e78 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/mod.rs @@ -0,0 +1,2 @@ +pub mod substreams; +mod externs; diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/substreams.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/substreams.rs new file mode 100644 index 0000000..c12ecce --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/generated/substreams.rs @@ -0,0 +1,48 @@ +// Code generated by Substreams. DO NOT EDIT. +use crate::pb; +use substreams::errors::Error; + +pub struct Substreams{} + +pub trait SubstreamsTrait { + + fn map_block( + block: substreams_ethereum::pb::eth::v2::Block, + ) -> Result; + + fn map_block_i64( + block: substreams_ethereum::pb::eth::v2::Block, + ) -> Result; + + fn store_test( + block: substreams_ethereum::pb::eth::v2::Block, + map_block: pb::my_types_v1::Tests, + store: substreams::store::StoreSetProto, + ); + + fn store_append_string( + block: substreams_ethereum::pb::eth::v2::Block, + store: substreams::store::StoreAppend, + ); + + fn store_bigint( + block: substreams_ethereum::pb::eth::v2::Block, + store: substreams::store::StoreSetBigInt, + ); + + fn store_test2( + block: substreams_ethereum::pb::eth::v2::Block, + map_block: pb::my_types_v1::Tests, + store_test: substreams::store::StoreGetProto, + store_test_deltas: substreams::store::Deltas>, + map_block_i64: pb::my_types_v1::Number, + store_bigint: substreams::store::StoreGetBigInt, + store_bigint_deltas: substreams::store::Deltas, + store_append_string: substreams::store::StoreGetRaw, + store_append_string_deltas: substreams::store::Deltas>, + store: substreams::store::StoreSetProto, + ); +} + + + diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/lib.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/lib.rs new file mode 100644 index 0000000..6946725 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/lib.rs @@ -0,0 +1,59 @@ +mod pb; +mod generated; +use substreams::prelude::*; +use substreams::errors::Error; + +impl generated::substreams::SubstreamsTrait for generated::substreams::Substreams{ + + fn map_block( + _block: substreams_ethereum::pb::eth::v2::Block, + ) -> Result { + todo!() + } + + fn map_block_i64( + _block: substreams_ethereum::pb::eth::v2::Block, + ) -> Result { + todo!() + } + + fn store_test( + _block: substreams_ethereum::pb::eth::v2::Block, + _map_block: pb::my_types_v1::Tests, + _store: substreams::store::StoreSetProto, + ) { + todo!() + } + + fn store_append_string( + _block: substreams_ethereum::pb::eth::v2::Block, + _store: substreams::store::StoreAppend, + ) { + todo!() + } + + fn store_bigint( + _block: substreams_ethereum::pb::eth::v2::Block, + _store: substreams::store::StoreSetBigInt, + ) { + todo!() + } + + fn store_test2( + _block: substreams_ethereum::pb::eth::v2::Block, + _map_block: pb::my_types_v1::Tests, + _store_test: substreams::store::StoreGetProto, + _store_test_deltas: substreams::store::Deltas>, + _map_block_i64: pb::my_types_v1::Number, + _store_bigint: substreams::store::StoreGetBigInt, + _store_bigint_deltas: substreams::store::Deltas, + _store_append_string: substreams::store::StoreGetRaw, + _store_append_string_deltas: substreams::store::Deltas>, + _store: substreams::store::StoreSetProto, + ) { + todo!() + } +} + + + diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/google.protobuf.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/google.protobuf.rs new file mode 100644 index 0000000..5eab74f --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/google.protobuf.rs @@ -0,0 +1,3909 @@ +// @generated +/// Encoded file descriptor set for the `google.protobuf` package +pub const FILE_DESCRIPTOR_SET: &[u8] = &[ + 0x0a, 0xc7, 0x31, 0x0a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, 0x3b, 0x0a, 0x09, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x07, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x12, 0x14, 0x0a, 0x05, + 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x6e, 0x61, 0x6e, + 0x6f, 0x73, 0x42, 0x85, 0x01, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x0e, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x32, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x6b, + 0x6e, 0x6f, 0x77, 0x6e, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x70, 0x62, + 0xf8, 0x01, 0x01, 0xa2, 0x02, 0x03, 0x47, 0x50, 0x42, 0xaa, 0x02, 0x1e, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x57, 0x65, 0x6c, 0x6c, + 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x4a, 0xc5, 0x2f, 0x0a, 0x07, 0x12, + 0x05, 0x1e, 0x00, 0x92, 0x01, 0x01, 0x0a, 0xcc, 0x0c, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x1e, 0x00, + 0x12, 0x32, 0xc1, 0x0c, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x42, 0x75, + 0x66, 0x66, 0x65, 0x72, 0x73, 0x20, 0x2d, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x27, 0x73, + 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x63, 0x68, 0x61, 0x6e, 0x67, + 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x0a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, + 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, + 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x2e, 0x0a, 0x20, 0x68, 0x74, 0x74, 0x70, + 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x2d, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2f, 0x0a, 0x0a, 0x20, 0x52, 0x65, + 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2c, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, + 0x0a, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, + 0x61, 0x72, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x72, 0x65, 0x0a, 0x20, 0x6d, 0x65, 0x74, 0x3a, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x52, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x72, 0x65, 0x74, 0x61, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, + 0x72, 0x69, 0x67, 0x68, 0x74, 0x0a, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x2c, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, + 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, + 0x6d, 0x65, 0x72, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x52, 0x65, 0x64, 0x69, + 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x62, + 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x72, 0x65, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, + 0x6f, 0x76, 0x65, 0x0a, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, + 0x6f, 0x74, 0x69, 0x63, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, + 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x69, 0x6e, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x6d, + 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, + 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x64, 0x69, 0x73, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, + 0x20, 0x4e, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x6f, 0x66, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x20, 0x6e, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x69, 0x74, 0x73, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, + 0x6f, 0x72, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, + 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x73, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, + 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x20, 0x64, + 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x0a, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, + 0x75, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x70, 0x72, 0x69, 0x6f, + 0x72, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x20, 0x54, 0x48, 0x49, 0x53, 0x20, 0x53, 0x4f, 0x46, + 0x54, 0x57, 0x41, 0x52, 0x45, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, + 0x44, 0x20, 0x42, 0x59, 0x20, 0x54, 0x48, 0x45, 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, + 0x48, 0x54, 0x20, 0x48, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x53, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x43, + 0x4f, 0x4e, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x4f, 0x52, 0x53, 0x0a, 0x20, 0x22, 0x41, 0x53, + 0x20, 0x49, 0x53, 0x22, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x45, 0x58, 0x50, + 0x52, 0x45, 0x53, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, + 0x55, 0x44, 0x49, 0x4e, 0x47, 0x2c, 0x20, 0x42, 0x55, 0x54, 0x20, 0x4e, 0x4f, 0x54, 0x0a, 0x20, + 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x20, 0x54, 0x4f, 0x2c, 0x20, 0x54, 0x48, 0x45, 0x20, + 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, + 0x45, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, + 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, + 0x53, 0x20, 0x46, 0x4f, 0x52, 0x0a, 0x20, 0x41, 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, 0x43, 0x55, + 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, 0x20, 0x41, 0x52, 0x45, 0x20, + 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x2e, 0x20, 0x49, 0x4e, 0x20, 0x4e, + 0x4f, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x20, 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x20, 0x54, 0x48, + 0x45, 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x0a, 0x20, 0x4f, 0x57, 0x4e, + 0x45, 0x52, 0x20, 0x4f, 0x52, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x4f, + 0x52, 0x53, 0x20, 0x42, 0x45, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x4c, 0x45, 0x20, 0x46, 0x4f, 0x52, + 0x20, 0x41, 0x4e, 0x59, 0x20, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x2c, 0x20, 0x49, 0x4e, 0x44, + 0x49, 0x52, 0x45, 0x43, 0x54, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x41, + 0x4c, 0x2c, 0x0a, 0x20, 0x53, 0x50, 0x45, 0x43, 0x49, 0x41, 0x4c, 0x2c, 0x20, 0x45, 0x58, 0x45, + 0x4d, 0x50, 0x4c, 0x41, 0x52, 0x59, 0x2c, 0x20, 0x4f, 0x52, 0x20, 0x43, 0x4f, 0x4e, 0x53, 0x45, + 0x51, 0x55, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x20, 0x44, 0x41, 0x4d, 0x41, 0x47, 0x45, 0x53, + 0x20, 0x28, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x2c, 0x20, 0x42, 0x55, 0x54, + 0x20, 0x4e, 0x4f, 0x54, 0x0a, 0x20, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x20, 0x54, 0x4f, + 0x2c, 0x20, 0x50, 0x52, 0x4f, 0x43, 0x55, 0x52, 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x20, 0x4f, 0x46, + 0x20, 0x53, 0x55, 0x42, 0x53, 0x54, 0x49, 0x54, 0x55, 0x54, 0x45, 0x20, 0x47, 0x4f, 0x4f, 0x44, + 0x53, 0x20, 0x4f, 0x52, 0x20, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x53, 0x3b, 0x20, 0x4c, + 0x4f, 0x53, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x55, 0x53, 0x45, 0x2c, 0x0a, 0x20, 0x44, 0x41, 0x54, + 0x41, 0x2c, 0x20, 0x4f, 0x52, 0x20, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x54, 0x53, 0x3b, 0x20, 0x4f, + 0x52, 0x20, 0x42, 0x55, 0x53, 0x49, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x49, 0x4e, 0x54, 0x45, 0x52, + 0x52, 0x55, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x29, 0x20, 0x48, 0x4f, 0x57, 0x45, 0x56, 0x45, 0x52, + 0x20, 0x43, 0x41, 0x55, 0x53, 0x45, 0x44, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x4f, 0x4e, 0x20, 0x41, + 0x4e, 0x59, 0x0a, 0x20, 0x54, 0x48, 0x45, 0x4f, 0x52, 0x59, 0x20, 0x4f, 0x46, 0x20, 0x4c, 0x49, + 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x20, 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, 0x52, + 0x20, 0x49, 0x4e, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, 0x43, 0x54, 0x2c, 0x20, 0x53, 0x54, + 0x52, 0x49, 0x43, 0x54, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x20, + 0x4f, 0x52, 0x20, 0x54, 0x4f, 0x52, 0x54, 0x0a, 0x20, 0x28, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, + 0x49, 0x4e, 0x47, 0x20, 0x4e, 0x45, 0x47, 0x4c, 0x49, 0x47, 0x45, 0x4e, 0x43, 0x45, 0x20, 0x4f, + 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x57, 0x49, 0x53, 0x45, 0x29, 0x20, 0x41, 0x52, 0x49, + 0x53, 0x49, 0x4e, 0x47, 0x20, 0x49, 0x4e, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x59, 0x20, + 0x4f, 0x55, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x45, 0x20, 0x55, 0x53, 0x45, 0x0a, 0x20, + 0x4f, 0x46, 0x20, 0x54, 0x48, 0x49, 0x53, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, + 0x2c, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x20, 0x49, 0x46, 0x20, 0x41, 0x44, 0x56, 0x49, 0x53, 0x45, + 0x44, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x45, 0x20, 0x50, 0x4f, 0x53, 0x53, 0x49, 0x42, 0x49, + 0x4c, 0x49, 0x54, 0x59, 0x20, 0x4f, 0x46, 0x20, 0x53, 0x55, 0x43, 0x48, 0x20, 0x44, 0x41, 0x4d, + 0x41, 0x47, 0x45, 0x2e, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x20, 0x00, 0x18, 0x0a, + 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x22, 0x00, 0x3b, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x25, 0x12, + 0x03, 0x22, 0x00, 0x3b, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x23, 0x00, 0x1f, 0x0a, 0x09, + 0x0a, 0x02, 0x08, 0x1f, 0x12, 0x03, 0x23, 0x00, 0x1f, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, + 0x24, 0x00, 0x49, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x24, 0x00, 0x49, 0x0a, 0x08, + 0x0a, 0x01, 0x08, 0x12, 0x03, 0x25, 0x00, 0x2c, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x01, 0x12, 0x03, + 0x25, 0x00, 0x2c, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x26, 0x00, 0x2f, 0x0a, 0x09, 0x0a, + 0x02, 0x08, 0x08, 0x12, 0x03, 0x26, 0x00, 0x2f, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x27, + 0x00, 0x22, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0a, 0x12, 0x03, 0x27, 0x00, 0x22, 0x0a, 0x08, 0x0a, + 0x01, 0x08, 0x12, 0x03, 0x28, 0x00, 0x21, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x24, 0x12, 0x03, 0x28, + 0x00, 0x21, 0x0a, 0xde, 0x1d, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x06, 0x87, 0x01, 0x00, 0x92, 0x01, + 0x01, 0x1a, 0xcf, 0x1d, 0x20, 0x41, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x20, 0x70, 0x6f, + 0x69, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x65, + 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, + 0x69, 0x6d, 0x65, 0x20, 0x7a, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x6c, 0x6f, 0x63, 0x61, + 0x6c, 0x0a, 0x20, 0x63, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0x2c, 0x20, 0x65, 0x6e, 0x63, + 0x6f, 0x64, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, + 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, + 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x20, 0x61, 0x74, 0x0a, 0x20, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x54, + 0x68, 0x65, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x6c, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x70, 0x6f, 0x63, 0x68, + 0x20, 0x61, 0x74, 0x20, 0x55, 0x54, 0x43, 0x20, 0x6d, 0x69, 0x64, 0x6e, 0x69, 0x67, 0x68, 0x74, + 0x20, 0x6f, 0x6e, 0x0a, 0x20, 0x4a, 0x61, 0x6e, 0x75, 0x61, 0x72, 0x79, 0x20, 0x31, 0x2c, 0x20, + 0x31, 0x39, 0x37, 0x30, 0x2c, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, + 0x6c, 0x65, 0x70, 0x74, 0x69, 0x63, 0x20, 0x47, 0x72, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, + 0x20, 0x63, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x73, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x47, 0x72, 0x65, + 0x67, 0x6f, 0x72, 0x69, 0x61, 0x6e, 0x20, 0x63, 0x61, 0x6c, 0x65, 0x6e, 0x64, 0x61, 0x72, 0x20, + 0x62, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x79, 0x65, 0x61, + 0x72, 0x20, 0x6f, 0x6e, 0x65, 0x2e, 0x0a, 0x0a, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x6d, 0x69, 0x6e, + 0x75, 0x74, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x36, 0x30, 0x20, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x73, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x2e, 0x20, 0x4c, 0x65, 0x61, 0x70, 0x20, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x22, 0x73, 0x6d, 0x65, 0x61, + 0x72, 0x65, 0x64, 0x22, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6e, 0x6f, 0x20, + 0x6c, 0x65, 0x61, 0x70, 0x0a, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x74, 0x61, 0x62, + 0x6c, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, + 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x5b, 0x32, 0x34, 0x2d, 0x68, 0x6f, 0x75, + 0x72, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, 0x0a, 0x20, 0x73, 0x6d, 0x65, 0x61, 0x72, 0x5d, + 0x28, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x65, 0x72, 0x73, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, + 0x69, 0x6d, 0x65, 0x2f, 0x73, 0x6d, 0x65, 0x61, 0x72, 0x29, 0x2e, 0x0a, 0x0a, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x69, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, + 0x30, 0x30, 0x30, 0x31, 0x2d, 0x30, 0x31, 0x2d, 0x30, 0x31, 0x54, 0x30, 0x30, 0x3a, 0x30, 0x30, + 0x3a, 0x30, 0x30, 0x5a, 0x20, 0x74, 0x6f, 0x20, 0x39, 0x39, 0x39, 0x39, 0x2d, 0x31, 0x32, 0x2d, + 0x33, 0x31, 0x54, 0x32, 0x33, 0x3a, 0x35, 0x39, 0x3a, 0x35, 0x39, 0x2e, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x5a, 0x2e, 0x20, 0x42, 0x79, 0x0a, 0x20, 0x72, 0x65, 0x73, 0x74, + 0x72, 0x69, 0x63, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, + 0x72, 0x61, 0x6e, 0x67, 0x65, 0x2c, 0x20, 0x77, 0x65, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x63, 0x6f, 0x6e, + 0x76, 0x65, 0x72, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, + 0x20, 0x5b, 0x52, 0x46, 0x43, 0x0a, 0x20, 0x33, 0x33, 0x33, 0x39, 0x5d, 0x28, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, + 0x67, 0x2f, 0x72, 0x66, 0x63, 0x2f, 0x72, 0x66, 0x63, 0x33, 0x33, 0x33, 0x39, 0x2e, 0x74, 0x78, + 0x74, 0x29, 0x20, 0x64, 0x61, 0x74, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x2e, + 0x0a, 0x0a, 0x20, 0x23, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x0a, 0x0a, 0x20, + 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x31, 0x3a, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, + 0x74, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x66, 0x72, 0x6f, + 0x6d, 0x20, 0x50, 0x4f, 0x53, 0x49, 0x58, 0x20, 0x60, 0x74, 0x69, 0x6d, 0x65, 0x28, 0x29, 0x60, + 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x73, 0x65, 0x74, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x28, 0x74, 0x69, 0x6d, 0x65, 0x28, 0x4e, 0x55, 0x4c, + 0x4c, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x2e, 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x28, 0x30, 0x29, + 0x3b, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x32, 0x3a, 0x20, 0x43, + 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x50, 0x4f, 0x53, 0x49, 0x58, 0x20, 0x60, 0x67, 0x65, 0x74, + 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x66, 0x64, 0x61, 0x79, 0x28, 0x29, 0x60, 0x2e, 0x0a, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x76, + 0x61, 0x6c, 0x20, 0x74, 0x76, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x67, 0x65, 0x74, 0x74, + 0x69, 0x6d, 0x65, 0x6f, 0x66, 0x64, 0x61, 0x79, 0x28, 0x26, 0x74, 0x76, 0x2c, 0x20, 0x4e, 0x55, + 0x4c, 0x4c, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x73, + 0x65, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x28, 0x74, 0x76, 0x2e, 0x74, 0x76, + 0x5f, 0x73, 0x65, 0x63, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x28, + 0x74, 0x76, 0x2e, 0x74, 0x76, 0x5f, 0x75, 0x73, 0x65, 0x63, 0x20, 0x2a, 0x20, 0x31, 0x30, 0x30, + 0x30, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x33, 0x3a, + 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x57, 0x69, 0x6e, 0x33, 0x32, 0x20, 0x60, 0x47, + 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x41, 0x73, 0x46, 0x69, + 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x28, 0x29, 0x60, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x46, 0x49, 0x4c, 0x45, 0x54, 0x49, 0x4d, 0x45, 0x20, 0x66, 0x74, 0x3b, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x47, 0x65, 0x74, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, + 0x41, 0x73, 0x46, 0x69, 0x6c, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x28, 0x26, 0x66, 0x74, 0x29, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x20, 0x74, 0x69, 0x63, + 0x6b, 0x73, 0x20, 0x3d, 0x20, 0x28, 0x28, 0x28, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x29, 0x66, + 0x74, 0x2e, 0x64, 0x77, 0x48, 0x69, 0x67, 0x68, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x29, 0x20, 0x3c, 0x3c, 0x20, 0x33, 0x32, 0x29, 0x20, 0x7c, 0x20, 0x66, 0x74, 0x2e, 0x64, 0x77, + 0x4c, 0x6f, 0x77, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x3b, 0x0a, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x41, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x20, + 0x74, 0x69, 0x63, 0x6b, 0x20, 0x69, 0x73, 0x20, 0x31, 0x30, 0x30, 0x20, 0x6e, 0x61, 0x6e, 0x6f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x2e, 0x20, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, + 0x20, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x20, 0x31, 0x36, 0x30, 0x31, 0x2d, 0x30, 0x31, 0x2d, 0x30, + 0x31, 0x54, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x5a, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x2f, 0x2f, 0x20, 0x69, 0x73, 0x20, 0x31, 0x31, 0x36, 0x34, 0x34, 0x34, 0x37, 0x33, 0x36, + 0x30, 0x30, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, + 0x65, 0x20, 0x55, 0x6e, 0x69, 0x78, 0x20, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x20, 0x31, 0x39, 0x37, + 0x30, 0x2d, 0x30, 0x31, 0x2d, 0x30, 0x31, 0x54, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, + 0x5a, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x73, 0x65, 0x74, 0x5f, + 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x28, 0x28, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x29, 0x20, + 0x28, 0x28, 0x74, 0x69, 0x63, 0x6b, 0x73, 0x20, 0x2f, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x29, 0x20, 0x2d, 0x20, 0x31, 0x31, 0x36, 0x34, 0x34, 0x34, 0x37, 0x33, 0x36, 0x30, + 0x30, 0x4c, 0x4c, 0x29, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x28, + 0x28, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x29, 0x20, 0x28, 0x28, 0x74, 0x69, 0x63, 0x6b, 0x73, 0x20, + 0x25, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x20, 0x2a, 0x20, 0x31, 0x30, + 0x30, 0x29, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x34, + 0x3a, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x20, 0x60, 0x53, + 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, + 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x28, 0x29, 0x60, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x20, 0x3d, 0x20, + 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x69, + 0x6d, 0x65, 0x4d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x28, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x3d, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x2e, 0x6e, 0x65, 0x77, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x28, 0x29, 0x2e, 0x73, + 0x65, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x28, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, + 0x20, 0x2f, 0x20, 0x31, 0x30, 0x30, 0x30, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x28, 0x28, 0x69, 0x6e, 0x74, + 0x29, 0x20, 0x28, 0x28, 0x6d, 0x69, 0x6c, 0x6c, 0x69, 0x73, 0x20, 0x25, 0x20, 0x31, 0x30, 0x30, + 0x30, 0x29, 0x20, 0x2a, 0x20, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x29, 0x29, 0x2e, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x28, 0x29, 0x3b, 0x0a, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x20, 0x35, 0x3a, 0x20, 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x54, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x4a, 0x61, 0x76, + 0x61, 0x20, 0x60, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x2e, 0x6e, 0x6f, 0x77, 0x28, 0x29, + 0x60, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, + 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x3d, 0x20, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x74, 0x2e, 0x6e, + 0x6f, 0x77, 0x28, 0x29, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, + 0x3d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x6e, 0x65, 0x77, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x65, 0x72, 0x28, + 0x29, 0x2e, 0x73, 0x65, 0x74, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x28, 0x6e, 0x6f, 0x77, + 0x2e, 0x67, 0x65, 0x74, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x53, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x28, + 0x29, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x28, 0x6e, 0x6f, 0x77, 0x2e, 0x67, 0x65, + 0x74, 0x4e, 0x61, 0x6e, 0x6f, 0x28, 0x29, 0x29, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x28, 0x29, + 0x3b, 0x0a, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x36, 0x3a, 0x20, + 0x43, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x74, + 0x69, 0x6d, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x79, 0x74, 0x68, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x3d, + 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x47, 0x65, 0x74, 0x43, + 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x28, 0x29, 0x0a, 0x0a, 0x20, 0x23, + 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x0a, 0x0a, 0x20, + 0x49, 0x6e, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2c, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x61, 0x73, + 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x0a, 0x20, 0x5b, 0x52, 0x46, 0x43, 0x20, 0x33, 0x33, 0x33, 0x39, 0x5d, 0x28, 0x68, 0x74, 0x74, + 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x69, 0x65, 0x74, 0x66, 0x2e, 0x6f, 0x72, + 0x67, 0x2f, 0x72, 0x66, 0x63, 0x2f, 0x72, 0x66, 0x63, 0x33, 0x33, 0x33, 0x39, 0x2e, 0x74, 0x78, + 0x74, 0x29, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x20, 0x54, 0x68, 0x61, 0x74, 0x20, + 0x69, 0x73, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, + 0x69, 0x73, 0x20, 0x22, 0x7b, 0x79, 0x65, 0x61, 0x72, 0x7d, 0x2d, 0x7b, 0x6d, 0x6f, 0x6e, 0x74, + 0x68, 0x7d, 0x2d, 0x7b, 0x64, 0x61, 0x79, 0x7d, 0x54, 0x7b, 0x68, 0x6f, 0x75, 0x72, 0x7d, 0x3a, + 0x7b, 0x6d, 0x69, 0x6e, 0x7d, 0x3a, 0x7b, 0x73, 0x65, 0x63, 0x7d, 0x5b, 0x2e, 0x7b, 0x66, 0x72, + 0x61, 0x63, 0x5f, 0x73, 0x65, 0x63, 0x7d, 0x5d, 0x5a, 0x22, 0x0a, 0x20, 0x77, 0x68, 0x65, 0x72, + 0x65, 0x20, 0x7b, 0x79, 0x65, 0x61, 0x72, 0x7d, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6c, 0x77, 0x61, + 0x79, 0x73, 0x20, 0x65, 0x78, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x75, 0x72, 0x20, 0x64, 0x69, 0x67, 0x69, 0x74, 0x73, 0x20, 0x77, + 0x68, 0x69, 0x6c, 0x65, 0x20, 0x7b, 0x6d, 0x6f, 0x6e, 0x74, 0x68, 0x7d, 0x2c, 0x20, 0x7b, 0x64, + 0x61, 0x79, 0x7d, 0x2c, 0x0a, 0x20, 0x7b, 0x68, 0x6f, 0x75, 0x72, 0x7d, 0x2c, 0x20, 0x7b, 0x6d, + 0x69, 0x6e, 0x7d, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x7b, 0x73, 0x65, 0x63, 0x7d, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x2d, 0x70, 0x61, 0x64, 0x64, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x64, 0x69, 0x67, 0x69, 0x74, 0x73, 0x20, 0x65, 0x61, 0x63, + 0x68, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x0a, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, + 0x68, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x67, 0x6f, 0x20, 0x75, 0x70, 0x20, 0x74, 0x6f, 0x20, 0x39, + 0x20, 0x64, 0x69, 0x67, 0x69, 0x74, 0x73, 0x20, 0x28, 0x69, 0x2e, 0x65, 0x2e, 0x20, 0x75, 0x70, + 0x20, 0x74, 0x6f, 0x20, 0x31, 0x20, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x20, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x29, 0x2c, 0x0a, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2e, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x22, 0x5a, 0x22, 0x20, 0x73, 0x75, 0x66, 0x66, 0x69, 0x78, 0x20, 0x69, 0x6e, 0x64, 0x69, + 0x63, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, + 0x6e, 0x65, 0x20, 0x28, 0x22, 0x55, 0x54, 0x43, 0x22, 0x29, 0x3b, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x0a, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x71, + 0x75, 0x69, 0x72, 0x65, 0x64, 0x2e, 0x20, 0x41, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x20, + 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x72, 0x20, + 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x55, 0x54, 0x43, 0x20, 0x28, 0x61, 0x73, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x0a, 0x20, 0x22, 0x5a, 0x22, 0x29, 0x20, 0x77, 0x68, 0x65, + 0x6e, 0x20, 0x70, 0x72, 0x69, 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, + 0x0a, 0x20, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, + 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x55, 0x54, 0x43, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x7a, 0x6f, 0x6e, 0x65, 0x73, 0x20, 0x28, 0x61, + 0x73, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x61, + 0x6e, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x29, 0x2e, 0x0a, 0x0a, 0x20, 0x46, 0x6f, 0x72, + 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, 0x20, 0x22, 0x32, 0x30, 0x31, 0x37, 0x2d, + 0x30, 0x31, 0x2d, 0x31, 0x35, 0x54, 0x30, 0x31, 0x3a, 0x33, 0x30, 0x3a, 0x31, 0x35, 0x2e, 0x30, + 0x31, 0x5a, 0x22, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x31, 0x35, 0x2e, 0x30, + 0x31, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x70, 0x61, 0x73, 0x74, 0x0a, 0x20, + 0x30, 0x31, 0x3a, 0x33, 0x30, 0x20, 0x55, 0x54, 0x43, 0x20, 0x6f, 0x6e, 0x20, 0x4a, 0x61, 0x6e, + 0x75, 0x61, 0x72, 0x79, 0x20, 0x31, 0x35, 0x2c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x2e, 0x0a, 0x0a, + 0x20, 0x49, 0x6e, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2c, 0x20, + 0x6f, 0x6e, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x20, + 0x61, 0x20, 0x44, 0x61, 0x74, 0x65, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x74, 0x6f, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x75, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, + 0x0a, 0x20, 0x5b, 0x74, 0x6f, 0x49, 0x53, 0x4f, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x28, 0x29, + 0x5d, 0x28, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, + 0x70, 0x65, 0x72, 0x2e, 0x6d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0x2e, 0x6f, 0x72, 0x67, 0x2f, + 0x65, 0x6e, 0x2d, 0x55, 0x53, 0x2f, 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x57, 0x65, 0x62, 0x2f, 0x4a, + 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2f, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x2f, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x4f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x73, 0x2f, 0x44, 0x61, 0x74, 0x65, 0x2f, 0x74, 0x6f, 0x49, 0x53, 0x4f, 0x53, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x29, 0x0a, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2e, 0x20, 0x49, 0x6e, + 0x20, 0x50, 0x79, 0x74, 0x68, 0x6f, 0x6e, 0x2c, 0x20, 0x61, 0x20, 0x73, 0x74, 0x61, 0x6e, 0x64, + 0x61, 0x72, 0x64, 0x20, 0x60, 0x64, 0x61, 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x64, 0x61, + 0x74, 0x65, 0x74, 0x69, 0x6d, 0x65, 0x60, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x63, + 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x0a, + 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, + 0x75, 0x73, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x5b, 0x60, 0x73, 0x74, 0x72, 0x66, 0x74, 0x69, 0x6d, + 0x65, 0x60, 0x5d, 0x28, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x6f, 0x63, 0x73, + 0x2e, 0x70, 0x79, 0x74, 0x68, 0x6f, 0x6e, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x32, 0x2f, 0x6c, 0x69, + 0x62, 0x72, 0x61, 0x72, 0x79, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x23, + 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x73, 0x74, 0x72, 0x66, 0x74, 0x69, 0x6d, 0x65, 0x29, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x20, 0x27, 0x25, 0x59, 0x2d, 0x25, 0x6d, + 0x2d, 0x25, 0x64, 0x54, 0x25, 0x48, 0x3a, 0x25, 0x4d, 0x3a, 0x25, 0x53, 0x2e, 0x25, 0x66, 0x5a, + 0x27, 0x2e, 0x20, 0x4c, 0x69, 0x6b, 0x65, 0x77, 0x69, 0x73, 0x65, 0x2c, 0x20, 0x69, 0x6e, 0x20, + 0x4a, 0x61, 0x76, 0x61, 0x2c, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x75, 0x73, + 0x65, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4a, 0x6f, 0x64, 0x61, 0x20, 0x54, 0x69, 0x6d, 0x65, + 0x27, 0x73, 0x20, 0x5b, 0x60, 0x49, 0x53, 0x4f, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x28, + 0x29, 0x60, 0x5d, 0x28, 0x0a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, + 0x2e, 0x6a, 0x6f, 0x64, 0x61, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x6a, 0x6f, 0x64, 0x61, 0x2d, 0x74, + 0x69, 0x6d, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x6f, 0x72, 0x67, 0x2f, + 0x6a, 0x6f, 0x64, 0x61, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x2f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x2f, 0x49, 0x53, 0x4f, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x46, 0x6f, 0x72, 0x6d, + 0x61, 0x74, 0x2e, 0x68, 0x74, 0x6d, 0x6c, 0x23, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, + 0x25, 0x32, 0x44, 0x25, 0x32, 0x44, 0x0a, 0x20, 0x29, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x62, 0x74, + 0x61, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x72, 0x20, + 0x63, 0x61, 0x70, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x73, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x04, 0x87, 0x01, 0x08, 0x11, + 0x0a, 0x9d, 0x01, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x04, 0x8b, 0x01, 0x02, 0x14, 0x1a, + 0x8e, 0x01, 0x20, 0x52, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x73, 0x65, + 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x55, 0x54, 0x43, 0x20, 0x74, 0x69, 0x6d, + 0x65, 0x20, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x55, 0x6e, 0x69, 0x78, 0x20, 0x65, 0x70, 0x6f, + 0x63, 0x68, 0x0a, 0x20, 0x31, 0x39, 0x37, 0x30, 0x2d, 0x30, 0x31, 0x2d, 0x30, 0x31, 0x54, 0x30, + 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x5a, 0x2e, 0x20, 0x4d, 0x75, 0x73, 0x74, 0x20, 0x62, + 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x30, 0x30, 0x30, 0x31, 0x2d, 0x30, 0x31, 0x2d, 0x30, + 0x31, 0x54, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x3a, 0x30, 0x30, 0x5a, 0x20, 0x74, 0x6f, 0x0a, 0x20, + 0x39, 0x39, 0x39, 0x39, 0x2d, 0x31, 0x32, 0x2d, 0x33, 0x31, 0x54, 0x32, 0x33, 0x3a, 0x35, 0x39, + 0x3a, 0x35, 0x39, 0x5a, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x2e, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x04, 0x8b, 0x01, 0x02, 0x07, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0x8b, 0x01, 0x08, 0x0f, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x04, 0x8b, 0x01, 0x12, 0x13, 0x0a, 0xe5, 0x01, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x04, 0x91, 0x01, 0x02, 0x12, 0x1a, 0xd6, 0x01, 0x20, + 0x4e, 0x6f, 0x6e, 0x2d, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x66, 0x72, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x73, 0x65, 0x63, 0x6f, + 0x6e, 0x64, 0x20, 0x61, 0x74, 0x20, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, + 0x20, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x4e, 0x65, 0x67, + 0x61, 0x74, 0x69, 0x76, 0x65, 0x0a, 0x20, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x66, 0x72, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x68, + 0x61, 0x76, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x20, 0x6e, 0x61, 0x6e, 0x6f, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x0a, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x77, 0x61, 0x72, + 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x2e, 0x20, 0x4d, 0x75, 0x73, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x30, 0x20, 0x74, 0x6f, 0x20, 0x39, 0x39, 0x39, + 0x2c, 0x39, 0x39, 0x39, 0x2c, 0x39, 0x39, 0x39, 0x0a, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, + 0x69, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x04, + 0x91, 0x01, 0x02, 0x07, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x04, 0x91, + 0x01, 0x08, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x04, 0x91, 0x01, + 0x10, 0x11, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xd2, 0x89, 0x03, 0x0a, 0x20, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, + 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x0f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x22, 0x4d, 0x0a, 0x11, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x53, 0x65, 0x74, 0x12, 0x38, 0x0a, 0x04, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x04, 0x66, 0x69, 0x6c, 0x65, + 0x22, 0xe4, 0x04, 0x0a, 0x13, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, + 0x65, 0x6e, 0x63, 0x79, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x2b, 0x0a, 0x11, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x5f, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0a, 0x20, 0x03, 0x28, + 0x05, 0x52, 0x10, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x79, 0x12, 0x27, 0x0a, 0x0f, 0x77, 0x65, 0x61, 0x6b, 0x5f, 0x64, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x05, 0x52, 0x0e, 0x77, 0x65, + 0x61, 0x6b, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x43, 0x0a, 0x0c, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0b, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x08, 0x65, 0x6e, 0x75, 0x6d, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, + 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x07, + 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x43, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x36, 0x0a, 0x07, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x49, 0x0a, 0x10, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x63, + 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, + 0x0e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, + 0x16, 0x0a, 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x22, 0xb9, 0x06, 0x0a, 0x0f, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x3b, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x43, 0x0a, 0x09, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x12, 0x41, 0x0a, 0x0b, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0a, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x41, 0x0a, 0x09, 0x65, 0x6e, 0x75, 0x6d, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x08, 0x65, + 0x6e, 0x75, 0x6d, 0x54, 0x79, 0x70, 0x65, 0x12, 0x58, 0x0a, 0x0f, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x52, 0x0e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, + 0x65, 0x12, 0x44, 0x0a, 0x0a, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x5f, 0x64, 0x65, 0x63, 0x6c, 0x18, + 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x09, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x44, 0x65, 0x63, 0x6c, 0x12, 0x39, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x72, + 0x61, 0x6e, 0x67, 0x65, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, 0x0d, 0x72, 0x65, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x4e, 0x61, 0x6d, 0x65, 0x1a, 0x7a, + 0x0a, 0x0e, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, + 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, 0x12, 0x40, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x37, 0x0a, 0x0d, 0x52, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x12, 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, + 0x65, 0x6e, 0x64, 0x22, 0x7c, 0x0a, 0x15, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x52, 0x61, 0x6e, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x58, 0x0a, 0x14, + 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, + 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, + 0x02, 0x22, 0xc1, 0x06, 0x0a, 0x14, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, + 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x52, 0x05, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x3e, 0x0a, 0x04, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x54, + 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x74, 0x79, 0x70, + 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x74, 0x79, + 0x70, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, + 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, + 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x6e, 0x65, 0x6f, 0x66, + 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x09, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x1b, 0x0a, 0x09, 0x6a, 0x73, 0x6f, 0x6e, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6a, 0x73, 0x6f, + 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, + 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, + 0x6c, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0xb6, 0x02, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x44, 0x4f, 0x55, 0x42, 0x4c, 0x45, 0x10, + 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x4c, 0x4f, 0x41, 0x54, 0x10, + 0x02, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, + 0x03, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x36, 0x34, + 0x10, 0x04, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x49, 0x4e, 0x54, 0x33, 0x32, + 0x10, 0x05, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x58, 0x45, 0x44, + 0x36, 0x34, 0x10, 0x06, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x46, 0x49, 0x58, + 0x45, 0x44, 0x33, 0x32, 0x10, 0x07, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x42, + 0x4f, 0x4f, 0x4c, 0x10, 0x08, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x54, + 0x52, 0x49, 0x4e, 0x47, 0x10, 0x09, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, + 0x52, 0x4f, 0x55, 0x50, 0x10, 0x0a, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, + 0x45, 0x53, 0x53, 0x41, 0x47, 0x45, 0x10, 0x0b, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x42, 0x59, 0x54, 0x45, 0x53, 0x10, 0x0c, 0x12, 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x55, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x0d, 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x59, 0x50, + 0x45, 0x5f, 0x45, 0x4e, 0x55, 0x4d, 0x10, 0x0e, 0x12, 0x11, 0x0a, 0x0d, 0x54, 0x59, 0x50, 0x45, + 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x33, 0x32, 0x10, 0x0f, 0x12, 0x11, 0x0a, 0x0d, 0x54, + 0x59, 0x50, 0x45, 0x5f, 0x53, 0x46, 0x49, 0x58, 0x45, 0x44, 0x36, 0x34, 0x10, 0x10, 0x12, 0x0f, + 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x10, 0x11, 0x12, + 0x0f, 0x0a, 0x0b, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x10, 0x12, + 0x22, 0x43, 0x0a, 0x05, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, + 0x45, 0x4c, 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41, 0x4c, 0x10, 0x01, 0x12, 0x12, 0x0a, + 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x51, 0x55, 0x49, 0x52, 0x45, 0x44, 0x10, + 0x02, 0x12, 0x12, 0x0a, 0x0e, 0x4c, 0x41, 0x42, 0x45, 0x4c, 0x5f, 0x52, 0x45, 0x50, 0x45, 0x41, + 0x54, 0x45, 0x44, 0x10, 0x03, 0x22, 0x63, 0x0a, 0x14, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x37, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4f, 0x6e, 0x65, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe3, 0x02, 0x0a, 0x13, 0x45, + 0x6e, 0x75, 0x6d, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3f, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x36, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, + 0x5d, 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, 0x6d, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6e, + 0x75, 0x6d, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x52, + 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x23, + 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x4e, + 0x61, 0x6d, 0x65, 0x1a, 0x3b, 0x0a, 0x11, 0x45, 0x6e, 0x75, 0x6d, 0x52, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x73, 0x74, 0x61, 0x72, 0x74, 0x12, 0x10, + 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, 0x64, + 0x22, 0x83, 0x01, 0x0a, 0x18, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x05, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x3b, 0x0a, 0x07, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6e, 0x75, + 0x6d, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xa7, 0x01, 0x0a, 0x16, 0x53, 0x65, 0x72, 0x76, 0x69, + 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3e, 0x0a, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, + 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x06, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x39, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x22, 0x89, 0x02, 0x0a, 0x15, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, + 0x0a, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x38, + 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1e, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x10, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0f, 0x63, 0x6c, 0x69, 0x65, 0x6e, + 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x30, 0x0a, 0x10, 0x73, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0f, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x22, 0x91, 0x09, 0x0a, + 0x0b, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x0a, 0x0c, + 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0b, 0x6a, 0x61, 0x76, 0x61, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, + 0x30, 0x0a, 0x14, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x6a, + 0x61, 0x76, 0x61, 0x4f, 0x75, 0x74, 0x65, 0x72, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x35, 0x0a, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x6a, 0x61, 0x76, 0x61, 0x4d, 0x75, 0x6c, 0x74, 0x69, + 0x70, 0x6c, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12, 0x44, 0x0a, 0x1d, 0x6a, 0x61, 0x76, 0x61, + 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x73, + 0x5f, 0x61, 0x6e, 0x64, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x14, 0x20, 0x01, 0x28, 0x08, 0x42, + 0x02, 0x18, 0x01, 0x52, 0x19, 0x6a, 0x61, 0x76, 0x61, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x45, 0x71, 0x75, 0x61, 0x6c, 0x73, 0x41, 0x6e, 0x64, 0x48, 0x61, 0x73, 0x68, 0x12, 0x3a, + 0x0a, 0x16, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x68, + 0x65, 0x63, 0x6b, 0x5f, 0x75, 0x74, 0x66, 0x38, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, + 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x13, 0x6a, 0x61, 0x76, 0x61, 0x53, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x55, 0x74, 0x66, 0x38, 0x12, 0x53, 0x0a, 0x0c, 0x6f, 0x70, + 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0e, + 0x32, 0x29, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x4f, + 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x4d, 0x6f, 0x64, 0x65, 0x3a, 0x05, 0x53, 0x50, 0x45, + 0x45, 0x44, 0x52, 0x0b, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x46, 0x6f, 0x72, 0x12, + 0x1d, 0x0a, 0x0a, 0x67, 0x6f, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x0b, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x09, 0x67, 0x6f, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x35, + 0x0a, 0x13, 0x63, 0x63, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x52, 0x11, 0x63, 0x63, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x39, 0x0a, 0x15, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x11, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x13, 0x6a, 0x61, 0x76, + 0x61, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x12, 0x35, 0x0a, 0x13, 0x70, 0x79, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, + 0x61, 0x6c, 0x73, 0x65, 0x52, 0x11, 0x70, 0x79, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x12, 0x37, 0x0a, 0x14, 0x70, 0x68, 0x70, 0x5f, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x18, + 0x2a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x12, 0x70, 0x68, + 0x70, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x17, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, + 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x10, 0x63, 0x63, 0x5f, 0x65, 0x6e, + 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x61, 0x72, 0x65, 0x6e, 0x61, 0x73, 0x18, 0x1f, 0x20, 0x01, 0x28, + 0x08, 0x3a, 0x04, 0x74, 0x72, 0x75, 0x65, 0x52, 0x0e, 0x63, 0x63, 0x45, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x41, 0x72, 0x65, 0x6e, 0x61, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6f, 0x62, 0x6a, 0x63, 0x5f, + 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x24, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0f, 0x6f, 0x62, 0x6a, 0x63, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x12, 0x29, 0x0a, 0x10, 0x63, 0x73, 0x68, 0x61, 0x72, 0x70, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x25, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x63, + 0x73, 0x68, 0x61, 0x72, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, + 0x0a, 0x0c, 0x73, 0x77, 0x69, 0x66, 0x74, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x27, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x77, 0x69, 0x66, 0x74, 0x50, 0x72, 0x65, 0x66, 0x69, + 0x78, 0x12, 0x28, 0x0a, 0x10, 0x70, 0x68, 0x70, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x18, 0x28, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x68, 0x70, + 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x12, 0x23, 0x0a, 0x0d, 0x70, + 0x68, 0x70, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x29, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0c, 0x70, 0x68, 0x70, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x12, 0x34, 0x0a, 0x16, 0x70, 0x68, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, + 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x14, 0x70, 0x68, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x4e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x75, 0x62, 0x79, 0x5f, 0x70, + 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x75, + 0x62, 0x79, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, + 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x22, 0x3a, 0x0a, 0x0c, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x4d, + 0x6f, 0x64, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x50, 0x45, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, + 0x0a, 0x09, 0x43, 0x4f, 0x44, 0x45, 0x5f, 0x53, 0x49, 0x5a, 0x45, 0x10, 0x02, 0x12, 0x10, 0x0a, + 0x0c, 0x4c, 0x49, 0x54, 0x45, 0x5f, 0x52, 0x55, 0x4e, 0x54, 0x49, 0x4d, 0x45, 0x10, 0x03, 0x2a, + 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x26, 0x10, 0x27, + 0x22, 0xe3, 0x02, 0x0a, 0x0e, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x0a, 0x17, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, + 0x65, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x14, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x57, 0x69, 0x72, 0x65, 0x46, 0x6f, 0x72, 0x6d, 0x61, + 0x74, 0x12, 0x4c, 0x0a, 0x1f, 0x6e, 0x6f, 0x5f, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, + 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x5f, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, + 0x65, 0x52, 0x1c, 0x6e, 0x6f, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, 0x64, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x12, + 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, + 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x61, 0x70, 0x5f, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x6d, 0x61, 0x70, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, + 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, + 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, + 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x04, + 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, + 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x22, 0x92, 0x04, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x63, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x43, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x06, 0x53, 0x54, 0x52, + 0x49, 0x4e, 0x47, 0x52, 0x05, 0x63, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x70, 0x61, + 0x63, 0x6b, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x70, 0x61, 0x63, 0x6b, + 0x65, 0x64, 0x12, 0x47, 0x0a, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x4a, 0x53, 0x54, 0x79, 0x70, 0x65, 0x3a, 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x4f, 0x52, + 0x4d, 0x41, 0x4c, 0x52, 0x06, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x12, 0x19, 0x0a, 0x04, 0x6c, + 0x61, 0x7a, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x52, 0x04, 0x6c, 0x61, 0x7a, 0x79, 0x12, 0x2e, 0x0a, 0x0f, 0x75, 0x6e, 0x76, 0x65, 0x72, 0x69, + 0x66, 0x69, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x7a, 0x79, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x08, 0x3a, + 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0e, 0x75, 0x6e, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, + 0x65, 0x64, 0x4c, 0x61, 0x7a, 0x79, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, + 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, + 0x04, 0x77, 0x65, 0x61, 0x6b, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x52, 0x04, 0x77, 0x65, 0x61, 0x6b, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, + 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x22, 0x2f, 0x0a, 0x05, 0x43, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x53, + 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x43, 0x4f, 0x52, 0x44, 0x10, + 0x01, 0x12, 0x10, 0x0a, 0x0c, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x5f, 0x50, 0x49, 0x45, 0x43, + 0x45, 0x10, 0x02, 0x22, 0x35, 0x0a, 0x06, 0x4a, 0x53, 0x54, 0x79, 0x70, 0x65, 0x12, 0x0d, 0x0a, + 0x09, 0x4a, 0x53, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, + 0x4a, 0x53, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x4a, + 0x53, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x10, 0x02, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, + 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0x73, 0x0a, 0x0c, 0x4f, + 0x6e, 0x65, 0x6f, 0x66, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x58, 0x0a, 0x14, 0x75, + 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, + 0x22, 0xc0, 0x01, 0x0a, 0x0b, 0x45, 0x6e, 0x75, 0x6d, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x41, 0x6c, 0x69, 0x61, + 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, + 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, + 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x4a, 0x04, 0x08, + 0x05, 0x10, 0x06, 0x22, 0x9e, 0x01, 0x0a, 0x10, 0x45, 0x6e, 0x75, 0x6d, 0x56, 0x61, 0x6c, 0x75, + 0x65, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, + 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, + 0x6c, 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, + 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, + 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, + 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, + 0x80, 0x80, 0x80, 0x02, 0x22, 0x9c, 0x01, 0x0a, 0x0e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, + 0x63, 0x61, 0x74, 0x65, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, + 0x73, 0x65, 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x58, + 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, + 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2a, 0x09, 0x08, 0xe8, 0x07, 0x10, 0x80, 0x80, + 0x80, 0x80, 0x02, 0x22, 0xe0, 0x02, 0x0a, 0x0d, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x25, 0x0a, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x3a, 0x05, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x52, 0x0a, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x71, 0x0a, 0x11, + 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x5f, 0x6c, 0x65, 0x76, 0x65, + 0x6c, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, + 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x49, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, + 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x3a, 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, + 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x10, 0x69, + 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, + 0x58, 0x0a, 0x14, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, + 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe7, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x24, + 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x55, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x13, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, + 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x50, 0x0a, 0x10, 0x49, 0x64, 0x65, + 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x17, 0x0a, + 0x13, 0x49, 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x4b, + 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x13, 0x0a, 0x0f, 0x4e, 0x4f, 0x5f, 0x53, 0x49, 0x44, + 0x45, 0x5f, 0x45, 0x46, 0x46, 0x45, 0x43, 0x54, 0x53, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x49, + 0x44, 0x45, 0x4d, 0x50, 0x4f, 0x54, 0x45, 0x4e, 0x54, 0x10, 0x02, 0x2a, 0x09, 0x08, 0xe8, 0x07, + 0x10, 0x80, 0x80, 0x80, 0x80, 0x02, 0x22, 0x9a, 0x03, 0x0a, 0x13, 0x55, 0x6e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x41, + 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x55, + 0x6e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, 0x52, 0x04, 0x6e, 0x61, 0x6d, + 0x65, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x5f, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, + 0x76, 0x65, 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x6e, 0x65, + 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x69, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x10, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, + 0x49, 0x6e, 0x74, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x75, 0x62, + 0x6c, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x01, 0x52, 0x0b, + 0x64, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, + 0x0c, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x27, + 0x0a, 0x0f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x4a, 0x0a, 0x08, 0x4e, 0x61, 0x6d, 0x65, 0x50, + 0x61, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x74, + 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x08, 0x6e, 0x61, 0x6d, 0x65, 0x50, 0x61, 0x72, 0x74, + 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x18, 0x02, 0x20, 0x02, 0x28, 0x08, 0x52, 0x0b, 0x69, 0x73, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x22, 0xa7, 0x02, 0x0a, 0x0e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, + 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x44, 0x0a, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x08, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0xce, 0x01, 0x0a, + 0x08, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, 0x10, 0x01, 0x52, 0x04, 0x70, 0x61, 0x74, + 0x68, 0x12, 0x16, 0x0a, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x05, 0x42, + 0x02, 0x10, 0x01, 0x52, 0x04, 0x73, 0x70, 0x61, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x6c, 0x65, 0x61, + 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0f, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, + 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x10, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x73, 0x12, 0x3a, 0x0a, 0x19, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x74, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x17, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x44, 0x65, 0x74, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xd1, 0x01, + 0x0a, 0x11, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x49, + 0x6e, 0x66, 0x6f, 0x12, 0x4d, 0x0a, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x64, 0x43, 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x41, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x0a, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x1a, 0x6d, 0x0a, 0x0a, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x16, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x03, 0x28, 0x05, 0x42, 0x02, + 0x10, 0x01, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x65, 0x67, + 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x12, + 0x10, 0x0a, 0x03, 0x65, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x03, 0x65, 0x6e, + 0x64, 0x42, 0x7e, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x10, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x48, 0x01, 0x5a, 0x2d, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x70, 0x62, 0xf8, 0x01, 0x01, 0xa2, 0x02, + 0x03, 0x47, 0x50, 0x42, 0xaa, 0x02, 0x1a, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x4a, 0xdb, 0xcd, 0x02, 0x0a, 0x07, 0x12, 0x05, 0x27, 0x00, 0x98, 0x07, 0x01, 0x0a, 0xaa, + 0x0f, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x27, 0x00, 0x12, 0x32, 0xc1, 0x0c, 0x20, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x20, 0x2d, 0x20, + 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x0a, 0x20, 0x43, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x30, 0x38, + 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x20, 0x41, 0x6c, + 0x6c, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x64, 0x2e, 0x0a, 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, + 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2d, 0x62, 0x75, 0x66, 0x66, 0x65, + 0x72, 0x73, 0x2f, 0x0a, 0x0a, 0x20, 0x52, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x69, 0x6e, 0x20, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, + 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x73, 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x0a, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x61, 0x72, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, + 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, + 0x67, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x72, 0x65, + 0x0a, 0x20, 0x6d, 0x65, 0x74, 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x52, + 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x6d, 0x75, + 0x73, 0x74, 0x20, 0x72, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, + 0x6f, 0x76, 0x65, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x0a, 0x20, 0x6e, + 0x6f, 0x74, 0x69, 0x63, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, + 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, + 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x2a, 0x20, 0x52, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x66, 0x6f, + 0x72, 0x6d, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x0a, 0x20, 0x63, 0x6f, 0x70, + 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x2c, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x64, + 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, + 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, + 0x6d, 0x65, 0x72, 0x0a, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, + 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, + 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, + 0x68, 0x65, 0x0a, 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x4e, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x47, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x6e, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x74, 0x73, 0x0a, 0x20, 0x63, + 0x6f, 0x6e, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, + 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x6f, 0x72, + 0x73, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x20, 0x70, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x73, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x0a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, + 0x72, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, + 0x66, 0x69, 0x63, 0x20, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, + 0x6e, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x20, + 0x54, 0x48, 0x49, 0x53, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x20, 0x49, 0x53, + 0x20, 0x50, 0x52, 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x42, 0x59, 0x20, 0x54, 0x48, 0x45, + 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x48, 0x4f, 0x4c, 0x44, 0x45, + 0x52, 0x53, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, + 0x4f, 0x52, 0x53, 0x0a, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x20, 0x41, 0x4e, 0x44, + 0x20, 0x41, 0x4e, 0x59, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x20, 0x4f, 0x52, 0x20, + 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, + 0x45, 0x53, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x2c, 0x20, 0x42, + 0x55, 0x54, 0x20, 0x4e, 0x4f, 0x54, 0x0a, 0x20, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x20, + 0x54, 0x4f, 0x2c, 0x20, 0x54, 0x48, 0x45, 0x20, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x20, + 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x4d, 0x45, + 0x52, 0x43, 0x48, 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, + 0x44, 0x20, 0x46, 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x0a, 0x20, 0x41, + 0x20, 0x50, 0x41, 0x52, 0x54, 0x49, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, + 0x4f, 0x53, 0x45, 0x20, 0x41, 0x52, 0x45, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, + 0x45, 0x44, 0x2e, 0x20, 0x49, 0x4e, 0x20, 0x4e, 0x4f, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x20, + 0x53, 0x48, 0x41, 0x4c, 0x4c, 0x20, 0x54, 0x48, 0x45, 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, + 0x47, 0x48, 0x54, 0x0a, 0x20, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x20, 0x4f, 0x52, 0x20, 0x43, 0x4f, + 0x4e, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x4f, 0x52, 0x53, 0x20, 0x42, 0x45, 0x20, 0x4c, 0x49, + 0x41, 0x42, 0x4c, 0x45, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x44, 0x49, 0x52, + 0x45, 0x43, 0x54, 0x2c, 0x20, 0x49, 0x4e, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x2c, 0x20, 0x49, + 0x4e, 0x43, 0x49, 0x44, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x2c, 0x0a, 0x20, 0x53, 0x50, 0x45, 0x43, + 0x49, 0x41, 0x4c, 0x2c, 0x20, 0x45, 0x58, 0x45, 0x4d, 0x50, 0x4c, 0x41, 0x52, 0x59, 0x2c, 0x20, + 0x4f, 0x52, 0x20, 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x51, 0x55, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, + 0x20, 0x44, 0x41, 0x4d, 0x41, 0x47, 0x45, 0x53, 0x20, 0x28, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, + 0x49, 0x4e, 0x47, 0x2c, 0x20, 0x42, 0x55, 0x54, 0x20, 0x4e, 0x4f, 0x54, 0x0a, 0x20, 0x4c, 0x49, + 0x4d, 0x49, 0x54, 0x45, 0x44, 0x20, 0x54, 0x4f, 0x2c, 0x20, 0x50, 0x52, 0x4f, 0x43, 0x55, 0x52, + 0x45, 0x4d, 0x45, 0x4e, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x53, 0x55, 0x42, 0x53, 0x54, 0x49, 0x54, + 0x55, 0x54, 0x45, 0x20, 0x47, 0x4f, 0x4f, 0x44, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x53, 0x45, 0x52, + 0x56, 0x49, 0x43, 0x45, 0x53, 0x3b, 0x20, 0x4c, 0x4f, 0x53, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x55, + 0x53, 0x45, 0x2c, 0x0a, 0x20, 0x44, 0x41, 0x54, 0x41, 0x2c, 0x20, 0x4f, 0x52, 0x20, 0x50, 0x52, + 0x4f, 0x46, 0x49, 0x54, 0x53, 0x3b, 0x20, 0x4f, 0x52, 0x20, 0x42, 0x55, 0x53, 0x49, 0x4e, 0x45, + 0x53, 0x53, 0x20, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x52, 0x55, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x29, + 0x20, 0x48, 0x4f, 0x57, 0x45, 0x56, 0x45, 0x52, 0x20, 0x43, 0x41, 0x55, 0x53, 0x45, 0x44, 0x20, + 0x41, 0x4e, 0x44, 0x20, 0x4f, 0x4e, 0x20, 0x41, 0x4e, 0x59, 0x0a, 0x20, 0x54, 0x48, 0x45, 0x4f, + 0x52, 0x59, 0x20, 0x4f, 0x46, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, + 0x20, 0x57, 0x48, 0x45, 0x54, 0x48, 0x45, 0x52, 0x20, 0x49, 0x4e, 0x20, 0x43, 0x4f, 0x4e, 0x54, + 0x52, 0x41, 0x43, 0x54, 0x2c, 0x20, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x20, 0x4c, 0x49, 0x41, + 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x20, 0x4f, 0x52, 0x20, 0x54, 0x4f, 0x52, 0x54, 0x0a, + 0x20, 0x28, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4e, 0x45, 0x47, 0x4c, + 0x49, 0x47, 0x45, 0x4e, 0x43, 0x45, 0x20, 0x4f, 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x57, + 0x49, 0x53, 0x45, 0x29, 0x20, 0x41, 0x52, 0x49, 0x53, 0x49, 0x4e, 0x47, 0x20, 0x49, 0x4e, 0x20, + 0x41, 0x4e, 0x59, 0x20, 0x57, 0x41, 0x59, 0x20, 0x4f, 0x55, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x54, + 0x48, 0x45, 0x20, 0x55, 0x53, 0x45, 0x0a, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x49, 0x53, 0x20, + 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x2c, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x20, 0x49, + 0x46, 0x20, 0x41, 0x44, 0x56, 0x49, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x45, + 0x20, 0x50, 0x4f, 0x53, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x4f, 0x46, 0x20, + 0x53, 0x55, 0x43, 0x48, 0x20, 0x44, 0x41, 0x4d, 0x41, 0x47, 0x45, 0x2e, 0x0a, 0x32, 0xdb, 0x02, + 0x20, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x3a, 0x20, 0x6b, 0x65, 0x6e, 0x74, 0x6f, 0x6e, 0x40, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x20, 0x28, 0x4b, 0x65, 0x6e, 0x74, + 0x6f, 0x6e, 0x20, 0x56, 0x61, 0x72, 0x64, 0x61, 0x29, 0x0a, 0x20, 0x20, 0x42, 0x61, 0x73, 0x65, + 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x50, 0x72, + 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x20, 0x64, + 0x65, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x62, 0x79, 0x0a, 0x20, 0x20, 0x53, 0x61, 0x6e, 0x6a, 0x61, + 0x79, 0x20, 0x47, 0x68, 0x65, 0x6d, 0x61, 0x77, 0x61, 0x74, 0x2c, 0x20, 0x4a, 0x65, 0x66, 0x66, + 0x20, 0x44, 0x65, 0x61, 0x6e, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, 0x6f, 0x75, 0x6e, 0x64, 0x20, 0x69, 0x6e, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x0a, 0x20, + 0x41, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, + 0x6c, 0x61, 0x74, 0x65, 0x64, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x74, + 0x6f, 0x20, 0x61, 0x20, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x0a, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, + 0x20, 0x61, 0x6e, 0x79, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x65, 0x2e, 0x67, 0x2e, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x6f, 0x75, 0x74, 0x20, 0x72, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x73, + 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x29, 0x2e, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, + 0x12, 0x03, 0x29, 0x00, 0x18, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x2b, 0x00, 0x44, 0x0a, + 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x2b, 0x00, 0x44, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, + 0x03, 0x2c, 0x00, 0x2c, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x01, 0x12, 0x03, 0x2c, 0x00, 0x2c, 0x0a, + 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x2d, 0x00, 0x31, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x08, 0x12, + 0x03, 0x2d, 0x00, 0x31, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x2e, 0x00, 0x37, 0x0a, 0x09, + 0x0a, 0x02, 0x08, 0x25, 0x12, 0x03, 0x2e, 0x00, 0x37, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, + 0x2f, 0x00, 0x21, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x24, 0x12, 0x03, 0x2f, 0x00, 0x21, 0x0a, 0x08, + 0x0a, 0x01, 0x08, 0x12, 0x03, 0x30, 0x00, 0x1f, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x1f, 0x12, 0x03, + 0x30, 0x00, 0x1f, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x34, 0x00, 0x1c, 0x0a, 0x7f, 0x0a, + 0x02, 0x08, 0x09, 0x12, 0x03, 0x34, 0x00, 0x1c, 0x1a, 0x74, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x6d, 0x75, 0x73, 0x74, + 0x20, 0x62, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x73, 0x70, 0x65, 0x65, 0x64, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, + 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x62, 0x61, 0x73, 0x65, 0x64, + 0x0a, 0x20, 0x61, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x73, 0x20, 0x64, 0x6f, 0x6e, + 0x27, 0x74, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x20, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x62, + 0x6f, 0x6f, 0x74, 0x73, 0x74, 0x72, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x6a, + 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x38, 0x00, 0x3a, 0x01, 0x1a, 0x5e, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x72, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x61, 0x20, + 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x53, 0x65, + 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x0a, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x69, + 0x74, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, + 0x01, 0x12, 0x03, 0x38, 0x08, 0x19, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, + 0x39, 0x02, 0x28, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x39, 0x02, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x39, 0x0b, 0x1e, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x39, 0x1f, 0x23, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x39, 0x26, 0x27, 0x0a, 0x2f, 0x0a, 0x02, 0x04, + 0x01, 0x12, 0x04, 0x3d, 0x00, 0x5a, 0x01, 0x1a, 0x23, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, + 0x04, 0x01, 0x01, 0x12, 0x03, 0x3d, 0x08, 0x1b, 0x0a, 0x39, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, + 0x12, 0x03, 0x3e, 0x02, 0x1b, 0x22, 0x2c, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x2c, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x72, + 0x6f, 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x74, 0x72, + 0x65, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x3e, 0x02, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x3e, 0x0b, 0x11, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x3e, 0x12, 0x16, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x3e, 0x19, 0x1a, 0x0a, 0x2a, 0x0a, 0x04, 0x04, + 0x01, 0x02, 0x01, 0x12, 0x03, 0x3f, 0x02, 0x1e, 0x22, 0x1d, 0x20, 0x65, 0x2e, 0x67, 0x2e, 0x20, + 0x22, 0x66, 0x6f, 0x6f, 0x22, 0x2c, 0x20, 0x22, 0x66, 0x6f, 0x6f, 0x2e, 0x62, 0x61, 0x72, 0x22, + 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x04, + 0x12, 0x03, 0x3f, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x05, 0x12, 0x03, + 0x3f, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x3f, 0x12, + 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x3f, 0x1c, 0x1d, 0x0a, + 0x34, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x42, 0x02, 0x21, 0x1a, 0x27, 0x20, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6d, + 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, + 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x04, 0x12, 0x03, + 0x42, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x42, 0x0b, + 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x42, 0x12, 0x1c, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x42, 0x1f, 0x20, 0x0a, 0x51, 0x0a, + 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x44, 0x02, 0x28, 0x1a, 0x44, 0x20, 0x49, 0x6e, 0x64, + 0x65, 0x78, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, + 0x6e, 0x63, 0x79, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x04, 0x12, 0x03, 0x44, 0x02, 0x0a, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x05, 0x12, 0x03, 0x44, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x01, 0x02, 0x03, 0x01, 0x12, 0x03, 0x44, 0x11, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, + 0x02, 0x03, 0x03, 0x12, 0x03, 0x44, 0x25, 0x27, 0x0a, 0x7a, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x04, + 0x12, 0x03, 0x47, 0x02, 0x26, 0x1a, 0x6d, 0x20, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x73, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x65, 0x61, 0x6b, 0x20, 0x69, 0x6d, 0x70, 0x6f, + 0x72, 0x74, 0x65, 0x64, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, 0x20, 0x6c, 0x69, 0x73, + 0x74, 0x2e, 0x0a, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2d, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x2e, 0x20, 0x44, 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, + 0x73, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x04, 0x12, 0x03, 0x47, + 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x05, 0x12, 0x03, 0x47, 0x0b, 0x10, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x01, 0x12, 0x03, 0x47, 0x11, 0x20, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, 0x47, 0x23, 0x25, 0x0a, 0x36, 0x0a, 0x04, + 0x04, 0x01, 0x02, 0x05, 0x12, 0x03, 0x4a, 0x02, 0x2c, 0x1a, 0x29, 0x20, 0x41, 0x6c, 0x6c, 0x20, + 0x74, 0x6f, 0x70, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, + 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x04, 0x12, 0x03, 0x4a, + 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x06, 0x12, 0x03, 0x4a, 0x0b, 0x1a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x01, 0x12, 0x03, 0x4a, 0x1b, 0x27, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x05, 0x03, 0x12, 0x03, 0x4a, 0x2a, 0x2b, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x01, 0x02, 0x06, 0x12, 0x03, 0x4b, 0x02, 0x2d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x06, 0x04, 0x12, 0x03, 0x4b, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x06, 0x06, + 0x12, 0x03, 0x4b, 0x0b, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x06, 0x01, 0x12, 0x03, + 0x4b, 0x1f, 0x28, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x06, 0x03, 0x12, 0x03, 0x4b, 0x2b, + 0x2c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x07, 0x12, 0x03, 0x4c, 0x02, 0x2e, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x07, 0x04, 0x12, 0x03, 0x4c, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x01, 0x02, 0x07, 0x06, 0x12, 0x03, 0x4c, 0x0b, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, + 0x02, 0x07, 0x01, 0x12, 0x03, 0x4c, 0x22, 0x29, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x07, + 0x03, 0x12, 0x03, 0x4c, 0x2c, 0x2d, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x08, 0x12, 0x03, + 0x4d, 0x02, 0x2e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x08, 0x04, 0x12, 0x03, 0x4d, 0x02, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x08, 0x06, 0x12, 0x03, 0x4d, 0x0b, 0x1f, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x08, 0x01, 0x12, 0x03, 0x4d, 0x20, 0x29, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x08, 0x03, 0x12, 0x03, 0x4d, 0x2c, 0x2d, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x01, 0x02, 0x09, 0x12, 0x03, 0x4f, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x09, + 0x04, 0x12, 0x03, 0x4f, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x09, 0x06, 0x12, + 0x03, 0x4f, 0x0b, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x09, 0x01, 0x12, 0x03, 0x4f, + 0x17, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x09, 0x03, 0x12, 0x03, 0x4f, 0x21, 0x22, + 0x0a, 0xf4, 0x01, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x0a, 0x12, 0x03, 0x55, 0x02, 0x2f, 0x1a, 0xe6, + 0x01, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x69, + 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x2e, 0x0a, 0x20, 0x59, 0x6f, 0x75, 0x20, + 0x6d, 0x61, 0x79, 0x20, 0x73, 0x61, 0x66, 0x65, 0x6c, 0x79, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, + 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x68, 0x61, 0x72, 0x6d, + 0x69, 0x6e, 0x67, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x0a, 0x20, 0x66, 0x75, 0x6e, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x2d, 0x2d, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x69, 0x73, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, + 0x62, 0x79, 0x0a, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, + 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x0a, 0x04, + 0x12, 0x03, 0x55, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x0a, 0x06, 0x12, 0x03, + 0x55, 0x0b, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x0a, 0x01, 0x12, 0x03, 0x55, 0x1a, + 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x0a, 0x03, 0x12, 0x03, 0x55, 0x2d, 0x2e, 0x0a, + 0x5d, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x0b, 0x12, 0x03, 0x59, 0x02, 0x1e, 0x1a, 0x50, 0x20, 0x54, + 0x68, 0x65, 0x20, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x22, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x22, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x22, 0x2e, 0x0a, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x0b, 0x04, 0x12, 0x03, 0x59, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x01, 0x02, 0x0b, 0x05, 0x12, 0x03, 0x59, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, + 0x02, 0x0b, 0x01, 0x12, 0x03, 0x59, 0x12, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x0b, + 0x03, 0x12, 0x03, 0x59, 0x1b, 0x1d, 0x0a, 0x27, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x5d, 0x00, + 0x7d, 0x01, 0x1a, 0x1b, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x61, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, + 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x5d, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x02, 0x02, 0x00, 0x12, 0x03, 0x5e, 0x02, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, + 0x04, 0x12, 0x03, 0x5e, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, 0x12, + 0x03, 0x5e, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x5e, + 0x12, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x5e, 0x19, 0x1a, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x60, 0x02, 0x2a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x02, 0x02, 0x01, 0x04, 0x12, 0x03, 0x60, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x60, 0x0b, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x01, 0x01, 0x12, 0x03, 0x60, 0x20, 0x25, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, + 0x12, 0x03, 0x60, 0x28, 0x29, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x02, 0x12, 0x03, 0x61, + 0x02, 0x2e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x04, 0x12, 0x03, 0x61, 0x02, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x06, 0x12, 0x03, 0x61, 0x0b, 0x1f, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x01, 0x12, 0x03, 0x61, 0x20, 0x29, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x02, 0x03, 0x12, 0x03, 0x61, 0x2c, 0x2d, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, + 0x02, 0x03, 0x12, 0x03, 0x63, 0x02, 0x2b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x04, + 0x12, 0x03, 0x63, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x06, 0x12, 0x03, + 0x63, 0x0b, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x01, 0x12, 0x03, 0x63, 0x1b, + 0x26, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x03, 0x12, 0x03, 0x63, 0x29, 0x2a, 0x0a, + 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x04, 0x12, 0x03, 0x64, 0x02, 0x2d, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x04, 0x04, 0x12, 0x03, 0x64, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, + 0x02, 0x04, 0x06, 0x12, 0x03, 0x64, 0x0b, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x04, + 0x01, 0x12, 0x03, 0x64, 0x1f, 0x28, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x04, 0x03, 0x12, + 0x03, 0x64, 0x2b, 0x2c, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x00, 0x12, 0x04, 0x66, 0x02, + 0x6b, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x66, 0x0a, 0x18, + 0x0a, 0x1b, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x12, 0x03, 0x67, 0x04, 0x1d, 0x22, + 0x0c, 0x20, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x67, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x67, 0x0d, 0x12, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x67, 0x13, 0x18, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x67, 0x1b, 0x1c, 0x0a, 0x1b, 0x0a, + 0x06, 0x04, 0x02, 0x03, 0x00, 0x02, 0x01, 0x12, 0x03, 0x68, 0x04, 0x1b, 0x22, 0x0c, 0x20, 0x45, + 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x01, 0x04, 0x12, 0x03, 0x68, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x68, 0x0d, 0x12, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x68, 0x13, 0x16, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x68, 0x19, 0x1a, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, + 0x03, 0x00, 0x02, 0x02, 0x12, 0x03, 0x6a, 0x04, 0x2f, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x02, 0x04, 0x12, 0x03, 0x6a, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x02, 0x06, 0x12, 0x03, 0x6a, 0x0d, 0x22, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x6a, 0x23, 0x2a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x6a, 0x2d, 0x2e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, + 0x05, 0x12, 0x03, 0x6c, 0x02, 0x2e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x04, 0x12, + 0x03, 0x6c, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x06, 0x12, 0x03, 0x6c, + 0x0b, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x01, 0x12, 0x03, 0x6c, 0x1a, 0x29, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x03, 0x12, 0x03, 0x6c, 0x2c, 0x2d, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x02, 0x02, 0x06, 0x12, 0x03, 0x6e, 0x02, 0x2f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x02, 0x02, 0x06, 0x04, 0x12, 0x03, 0x6e, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x06, 0x06, 0x12, 0x03, 0x6e, 0x0b, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x06, 0x01, + 0x12, 0x03, 0x6e, 0x20, 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x06, 0x03, 0x12, 0x03, + 0x6e, 0x2d, 0x2e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x07, 0x12, 0x03, 0x70, 0x02, 0x26, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x07, 0x04, 0x12, 0x03, 0x70, 0x02, 0x0a, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x02, 0x07, 0x06, 0x12, 0x03, 0x70, 0x0b, 0x19, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x07, 0x01, 0x12, 0x03, 0x70, 0x1a, 0x21, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, + 0x02, 0x07, 0x03, 0x12, 0x03, 0x70, 0x24, 0x25, 0x0a, 0xaa, 0x01, 0x0a, 0x04, 0x04, 0x02, 0x03, + 0x01, 0x12, 0x04, 0x75, 0x02, 0x78, 0x03, 0x1a, 0x9b, 0x01, 0x20, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x20, 0x6f, 0x66, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x74, 0x61, 0x67, + 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2e, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, + 0x65, 0x64, 0x20, 0x74, 0x61, 0x67, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x6d, + 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x62, + 0x79, 0x0a, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x74, + 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x2e, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x72, 0x61, 0x6e, 0x67, + 0x65, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x0a, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x76, 0x65, 0x72, + 0x6c, 0x61, 0x70, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, 0x01, 0x12, 0x03, + 0x75, 0x0a, 0x17, 0x0a, 0x1b, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x76, + 0x04, 0x1d, 0x22, 0x0c, 0x20, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x2e, 0x0a, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x04, 0x12, 0x03, 0x76, 0x04, 0x0c, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x76, 0x0d, 0x12, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x76, 0x13, 0x18, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x76, 0x1b, 0x1c, + 0x0a, 0x1b, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x12, 0x03, 0x77, 0x04, 0x1b, 0x22, + 0x0c, 0x20, 0x45, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x04, 0x12, 0x03, 0x77, 0x04, 0x0c, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x05, 0x12, 0x03, 0x77, 0x0d, 0x12, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x77, 0x13, 0x16, 0x0a, 0x0e, 0x0a, + 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x77, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, + 0x04, 0x04, 0x02, 0x02, 0x08, 0x12, 0x03, 0x79, 0x02, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, + 0x02, 0x08, 0x04, 0x12, 0x03, 0x79, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x08, + 0x06, 0x12, 0x03, 0x79, 0x0b, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x08, 0x01, 0x12, + 0x03, 0x79, 0x19, 0x27, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x08, 0x03, 0x12, 0x03, 0x79, + 0x2a, 0x2b, 0x0a, 0x82, 0x01, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x09, 0x12, 0x03, 0x7c, 0x02, 0x25, + 0x1a, 0x75, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x6d, + 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x62, + 0x79, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x73, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x0a, 0x20, 0x41, + 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6d, 0x61, 0x79, 0x20, + 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, + 0x20, 0x6f, 0x6e, 0x63, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x09, 0x04, + 0x12, 0x03, 0x7c, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x09, 0x05, 0x12, 0x03, + 0x7c, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x09, 0x01, 0x12, 0x03, 0x7c, 0x12, + 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x09, 0x03, 0x12, 0x03, 0x7c, 0x22, 0x24, 0x0a, + 0x0b, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x05, 0x7f, 0x00, 0x86, 0x01, 0x01, 0x0a, 0x0a, 0x0a, 0x03, + 0x04, 0x03, 0x01, 0x12, 0x03, 0x7f, 0x08, 0x1d, 0x0a, 0x4f, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x00, + 0x12, 0x04, 0x81, 0x01, 0x02, 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, + 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x72, 0x65, + 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x20, 0x53, 0x65, + 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x03, 0x02, + 0x00, 0x04, 0x12, 0x04, 0x81, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, + 0x06, 0x12, 0x04, 0x81, 0x01, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, + 0x12, 0x04, 0x81, 0x01, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, + 0x04, 0x81, 0x01, 0x36, 0x39, 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x03, 0x05, 0x12, 0x04, 0x85, 0x01, + 0x02, 0x19, 0x1a, 0x4d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x63, 0x61, 0x6e, + 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, + 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x03, 0x05, 0x00, 0x12, 0x04, 0x85, 0x01, 0x0d, 0x18, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x03, 0x05, 0x00, 0x01, 0x12, 0x04, 0x85, 0x01, 0x0d, 0x11, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x03, 0x05, 0x00, 0x02, 0x12, 0x04, 0x85, 0x01, 0x15, 0x18, 0x0a, 0x33, 0x0a, + 0x02, 0x04, 0x04, 0x12, 0x06, 0x89, 0x01, 0x00, 0xed, 0x01, 0x01, 0x1a, 0x25, 0x20, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x61, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x04, 0x01, 0x12, 0x04, 0x89, 0x01, 0x08, 0x1c, 0x0a, + 0x0e, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x00, 0x12, 0x06, 0x8a, 0x01, 0x02, 0xa9, 0x01, 0x03, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x04, 0x00, 0x01, 0x12, 0x04, 0x8a, 0x01, 0x07, 0x0b, 0x0a, 0x53, + 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x04, 0x8d, 0x01, 0x04, 0x14, 0x1a, 0x43, + 0x20, 0x30, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x2e, 0x0a, 0x20, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x20, 0x69, 0x73, 0x20, 0x77, 0x65, 0x69, 0x72, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x68, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x20, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x73, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, + 0x8d, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, + 0x04, 0x8d, 0x01, 0x12, 0x13, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, + 0x04, 0x8e, 0x01, 0x04, 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, 0x01, + 0x12, 0x04, 0x8e, 0x01, 0x04, 0x0e, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x01, + 0x02, 0x12, 0x04, 0x8e, 0x01, 0x11, 0x12, 0x0a, 0x77, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, + 0x02, 0x12, 0x04, 0x91, 0x01, 0x04, 0x13, 0x1a, 0x67, 0x20, 0x4e, 0x6f, 0x74, 0x20, 0x5a, 0x69, + 0x67, 0x5a, 0x61, 0x67, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x2e, 0x20, 0x20, 0x4e, + 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, + 0x74, 0x61, 0x6b, 0x65, 0x20, 0x31, 0x30, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0x20, 0x20, + 0x55, 0x73, 0x65, 0x20, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x36, 0x34, 0x20, + 0x69, 0x66, 0x0a, 0x20, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x6c, 0x79, 0x2e, 0x0a, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x04, 0x91, 0x01, 0x04, + 0x0e, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x04, 0x91, 0x01, + 0x11, 0x12, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x04, 0x92, 0x01, + 0x04, 0x14, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x04, 0x92, + 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x04, + 0x92, 0x01, 0x12, 0x13, 0x0a, 0x77, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x04, 0x12, 0x04, + 0x95, 0x01, 0x04, 0x13, 0x1a, 0x67, 0x20, 0x4e, 0x6f, 0x74, 0x20, 0x5a, 0x69, 0x67, 0x5a, 0x61, + 0x67, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x2e, 0x20, 0x20, 0x4e, 0x65, 0x67, 0x61, + 0x74, 0x69, 0x76, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x74, 0x61, 0x6b, + 0x65, 0x20, 0x31, 0x30, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x2e, 0x20, 0x20, 0x55, 0x73, 0x65, + 0x20, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, 0x49, 0x4e, 0x54, 0x33, 0x32, 0x20, 0x69, 0x66, 0x0a, + 0x20, 0x6e, 0x65, 0x67, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, + 0x20, 0x61, 0x72, 0x65, 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x6c, 0x79, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x04, 0x01, 0x12, 0x04, 0x95, 0x01, 0x04, 0x0e, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x04, 0x02, 0x12, 0x04, 0x95, 0x01, 0x11, 0x12, 0x0a, + 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x05, 0x12, 0x04, 0x96, 0x01, 0x04, 0x15, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x05, 0x01, 0x12, 0x04, 0x96, 0x01, 0x04, 0x10, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x05, 0x02, 0x12, 0x04, 0x96, 0x01, 0x13, + 0x14, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x06, 0x12, 0x04, 0x97, 0x01, 0x04, + 0x15, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x06, 0x01, 0x12, 0x04, 0x97, 0x01, + 0x04, 0x10, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x06, 0x02, 0x12, 0x04, 0x97, + 0x01, 0x13, 0x14, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x07, 0x12, 0x04, 0x98, + 0x01, 0x04, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x07, 0x01, 0x12, 0x04, + 0x98, 0x01, 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x07, 0x02, 0x12, + 0x04, 0x98, 0x01, 0x10, 0x11, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x08, 0x12, + 0x04, 0x99, 0x01, 0x04, 0x14, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x08, 0x01, + 0x12, 0x04, 0x99, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x08, + 0x02, 0x12, 0x04, 0x99, 0x01, 0x12, 0x13, 0x0a, 0xe2, 0x01, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, + 0x02, 0x09, 0x12, 0x04, 0x9e, 0x01, 0x04, 0x14, 0x1a, 0xd1, 0x01, 0x20, 0x54, 0x61, 0x67, 0x2d, + 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, + 0x61, 0x74, 0x65, 0x2e, 0x0a, 0x20, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x69, 0x73, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2e, 0x20, 0x48, 0x6f, 0x77, 0x65, + 0x76, 0x65, 0x72, 0x2c, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0x20, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x61, 0x62, 0x6c, + 0x65, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, + 0x72, 0x6f, 0x75, 0x70, 0x20, 0x77, 0x69, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, + 0x20, 0x61, 0x6e, 0x64, 0x0a, 0x20, 0x74, 0x72, 0x65, 0x61, 0x74, 0x20, 0x67, 0x72, 0x6f, 0x75, + 0x70, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x61, 0x73, 0x20, 0x75, 0x6e, 0x6b, 0x6e, + 0x6f, 0x77, 0x6e, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x04, 0x04, 0x00, 0x02, 0x09, 0x01, 0x12, 0x04, 0x9e, 0x01, 0x04, 0x0e, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x09, 0x02, 0x12, 0x04, 0x9e, 0x01, 0x11, 0x13, 0x0a, 0x2d, + 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0a, 0x12, 0x04, 0x9f, 0x01, 0x04, 0x16, 0x22, 0x1d, + 0x20, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2d, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, + 0x64, 0x20, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0a, 0x01, 0x12, 0x04, 0x9f, 0x01, 0x04, 0x10, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0a, 0x02, 0x12, 0x04, 0x9f, 0x01, 0x13, 0x15, 0x0a, + 0x23, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0b, 0x12, 0x04, 0xa2, 0x01, 0x04, 0x14, 0x1a, + 0x13, 0x20, 0x4e, 0x65, 0x77, 0x20, 0x69, 0x6e, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x32, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0b, 0x01, 0x12, + 0x04, 0xa2, 0x01, 0x04, 0x0e, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0b, 0x02, + 0x12, 0x04, 0xa2, 0x01, 0x11, 0x13, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0c, + 0x12, 0x04, 0xa3, 0x01, 0x04, 0x15, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0c, + 0x01, 0x12, 0x04, 0xa3, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, + 0x0c, 0x02, 0x12, 0x04, 0xa3, 0x01, 0x12, 0x14, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, + 0x02, 0x0d, 0x12, 0x04, 0xa4, 0x01, 0x04, 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, + 0x02, 0x0d, 0x01, 0x12, 0x04, 0xa4, 0x01, 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, + 0x00, 0x02, 0x0d, 0x02, 0x12, 0x04, 0xa4, 0x01, 0x10, 0x12, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, + 0x04, 0x00, 0x02, 0x0e, 0x12, 0x04, 0xa5, 0x01, 0x04, 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, + 0x04, 0x00, 0x02, 0x0e, 0x01, 0x12, 0x04, 0xa5, 0x01, 0x04, 0x11, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x04, 0x04, 0x00, 0x02, 0x0e, 0x02, 0x12, 0x04, 0xa5, 0x01, 0x14, 0x16, 0x0a, 0x0e, 0x0a, 0x06, + 0x04, 0x04, 0x04, 0x00, 0x02, 0x0f, 0x12, 0x04, 0xa6, 0x01, 0x04, 0x17, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x04, 0x04, 0x00, 0x02, 0x0f, 0x01, 0x12, 0x04, 0xa6, 0x01, 0x04, 0x11, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x0f, 0x02, 0x12, 0x04, 0xa6, 0x01, 0x14, 0x16, 0x0a, 0x27, + 0x0a, 0x06, 0x04, 0x04, 0x04, 0x00, 0x02, 0x10, 0x12, 0x04, 0xa7, 0x01, 0x04, 0x15, 0x22, 0x17, + 0x20, 0x55, 0x73, 0x65, 0x73, 0x20, 0x5a, 0x69, 0x67, 0x5a, 0x61, 0x67, 0x20, 0x65, 0x6e, 0x63, + 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, + 0x10, 0x01, 0x12, 0x04, 0xa7, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, + 0x02, 0x10, 0x02, 0x12, 0x04, 0xa7, 0x01, 0x12, 0x14, 0x0a, 0x27, 0x0a, 0x06, 0x04, 0x04, 0x04, + 0x00, 0x02, 0x11, 0x12, 0x04, 0xa8, 0x01, 0x04, 0x15, 0x22, 0x17, 0x20, 0x55, 0x73, 0x65, 0x73, + 0x20, 0x5a, 0x69, 0x67, 0x5a, 0x61, 0x67, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, + 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x11, 0x01, 0x12, 0x04, 0xa8, + 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x00, 0x02, 0x11, 0x02, 0x12, 0x04, + 0xa8, 0x01, 0x12, 0x14, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x04, 0x04, 0x01, 0x12, 0x06, 0xab, 0x01, + 0x02, 0xb0, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x04, 0x01, 0x01, 0x12, 0x04, 0xab, + 0x01, 0x07, 0x0c, 0x0a, 0x2a, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x04, 0xad, + 0x01, 0x04, 0x17, 0x1a, 0x1a, 0x20, 0x30, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x73, 0x0a, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x04, 0xad, 0x01, 0x04, 0x12, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x01, 0x02, 0x00, 0x02, 0x12, 0x04, 0xad, 0x01, 0x15, + 0x16, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x04, 0xae, 0x01, 0x04, + 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x04, 0xae, 0x01, + 0x04, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x01, 0x02, 0x01, 0x02, 0x12, 0x04, 0xae, + 0x01, 0x15, 0x16, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x04, 0xaf, + 0x01, 0x04, 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x04, + 0xaf, 0x01, 0x04, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x04, 0x04, 0x01, 0x02, 0x02, 0x02, 0x12, + 0x04, 0xaf, 0x01, 0x15, 0x16, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x00, 0x12, 0x04, 0xb2, + 0x01, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x04, 0x12, 0x04, 0xb2, 0x01, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x05, 0x12, 0x04, 0xb2, 0x01, 0x0b, + 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x01, 0x12, 0x04, 0xb2, 0x01, 0x12, 0x16, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x03, 0x12, 0x04, 0xb2, 0x01, 0x19, 0x1a, 0x0a, + 0x0c, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x01, 0x12, 0x04, 0xb3, 0x01, 0x02, 0x1c, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x04, 0x02, 0x01, 0x04, 0x12, 0x04, 0xb3, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x04, 0x02, 0x01, 0x05, 0x12, 0x04, 0xb3, 0x01, 0x0b, 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x04, 0x02, 0x01, 0x01, 0x12, 0x04, 0xb3, 0x01, 0x11, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, + 0x02, 0x01, 0x03, 0x12, 0x04, 0xb3, 0x01, 0x1a, 0x1b, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x04, 0x02, + 0x02, 0x12, 0x04, 0xb4, 0x01, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x04, + 0x12, 0x04, 0xb4, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x06, 0x12, + 0x04, 0xb4, 0x01, 0x0b, 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x04, + 0xb4, 0x01, 0x11, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x03, 0x12, 0x04, 0xb4, + 0x01, 0x19, 0x1a, 0x0a, 0x9c, 0x01, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x03, 0x12, 0x04, 0xb8, 0x01, + 0x02, 0x19, 0x1a, 0x8d, 0x01, 0x20, 0x49, 0x66, 0x20, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x6e, 0x65, 0x65, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, 0x2e, + 0x20, 0x20, 0x49, 0x66, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x0a, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, + 0x20, 0x62, 0x65, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x45, 0x4e, 0x55, 0x4d, 0x2c, 0x20, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x4d, 0x45, 0x53, 0x53, 0x41, + 0x47, 0x45, 0x20, 0x6f, 0x72, 0x20, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x47, 0x52, 0x4f, 0x55, 0x50, + 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x04, 0x12, 0x04, 0xb8, 0x01, 0x02, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x06, 0x12, 0x04, 0xb8, 0x01, 0x0b, 0x0f, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x01, 0x12, 0x04, 0xb8, 0x01, 0x10, 0x14, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x03, 0x12, 0x04, 0xb8, 0x01, 0x17, 0x18, 0x0a, 0xb7, + 0x02, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x04, 0x12, 0x04, 0xbf, 0x01, 0x02, 0x20, 0x1a, 0xa8, 0x02, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2c, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x0a, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x73, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x20, 0x27, 0x2e, 0x27, 0x2c, 0x20, 0x69, 0x74, 0x20, 0x69, + 0x73, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x2d, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x2e, 0x20, 0x20, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x2c, 0x20, 0x43, + 0x2b, 0x2b, 0x2d, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x73, 0x63, 0x6f, 0x70, 0x69, 0x6e, 0x67, 0x0a, + 0x20, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, + 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x28, 0x69, 0x2e, 0x65, 0x2e, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x0a, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, 0x73, 0x65, 0x61, 0x72, 0x63, 0x68, 0x65, 0x64, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x2c, 0x20, 0x6f, 0x6e, 0x20, 0x75, 0x70, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x6f, 0x6f, 0x74, 0x0a, 0x20, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x29, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x04, + 0x04, 0x12, 0x04, 0xbf, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x04, 0x05, + 0x12, 0x04, 0xbf, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x04, 0x01, 0x12, + 0x04, 0xbf, 0x01, 0x12, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x04, 0x03, 0x12, 0x04, + 0xbf, 0x01, 0x1e, 0x1f, 0x0a, 0x7e, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x05, 0x12, 0x04, 0xc3, 0x01, + 0x02, 0x1f, 0x1a, 0x70, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, + 0x65, 0x20, 0x62, 0x65, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, + 0x2e, 0x20, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x0a, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x6d, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x61, 0x73, 0x20, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x05, 0x04, 0x12, 0x04, 0xc3, + 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x05, 0x05, 0x12, 0x04, 0xc3, 0x01, + 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x05, 0x01, 0x12, 0x04, 0xc3, 0x01, 0x12, + 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x05, 0x03, 0x12, 0x04, 0xc3, 0x01, 0x1d, 0x1e, + 0x0a, 0x91, 0x02, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x06, 0x12, 0x04, 0xc9, 0x01, 0x02, 0x24, 0x1a, + 0x82, 0x02, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x74, 0x65, 0x78, 0x74, + 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x0a, 0x20, 0x46, + 0x6f, 0x72, 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x65, 0x61, 0x6e, 0x73, 0x2c, 0x20, 0x22, 0x74, 0x72, + 0x75, 0x65, 0x22, 0x20, 0x6f, 0x72, 0x20, 0x22, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x22, 0x2e, 0x0a, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x73, 0x2c, 0x20, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, + 0x20, 0x28, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x69, 0x6e, + 0x20, 0x61, 0x6e, 0x79, 0x20, 0x77, 0x61, 0x79, 0x29, 0x2e, 0x0a, 0x20, 0x46, 0x6f, 0x72, 0x20, + 0x62, 0x79, 0x74, 0x65, 0x73, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x43, 0x20, 0x65, 0x73, 0x63, 0x61, 0x70, 0x65, 0x64, 0x20, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x2e, 0x20, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, + 0x3e, 0x3d, 0x20, 0x31, 0x32, 0x38, 0x20, 0x61, 0x72, 0x65, 0x20, 0x65, 0x73, 0x63, 0x61, 0x70, + 0x65, 0x64, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x06, 0x04, 0x12, 0x04, 0xc9, + 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x06, 0x05, 0x12, 0x04, 0xc9, 0x01, + 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x06, 0x01, 0x12, 0x04, 0xc9, 0x01, 0x12, + 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x06, 0x03, 0x12, 0x04, 0xc9, 0x01, 0x22, 0x23, + 0x0a, 0x84, 0x01, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x07, 0x12, 0x04, 0xcd, 0x01, 0x02, 0x21, 0x1a, + 0x76, 0x20, 0x49, 0x66, 0x20, 0x73, 0x65, 0x74, 0x2c, 0x20, 0x67, 0x69, 0x76, 0x65, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x6f, + 0x6e, 0x65, 0x6f, 0x66, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x79, 0x70, 0x65, 0x27, 0x73, 0x20, 0x6f, 0x6e, + 0x65, 0x6f, 0x66, 0x5f, 0x64, 0x65, 0x63, 0x6c, 0x0a, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x20, + 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, + 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x07, 0x04, + 0x12, 0x04, 0xcd, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x07, 0x05, 0x12, + 0x04, 0xcd, 0x01, 0x0b, 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x07, 0x01, 0x12, 0x04, + 0xcd, 0x01, 0x11, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x07, 0x03, 0x12, 0x04, 0xcd, + 0x01, 0x1f, 0x20, 0x0a, 0xfa, 0x01, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x08, 0x12, 0x04, 0xd3, 0x01, + 0x02, 0x21, 0x1a, 0xeb, 0x01, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x54, + 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, 0x74, 0x20, + 0x62, 0x79, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x63, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x75, 0x73, + 0x65, 0x72, 0x20, 0x68, 0x61, 0x73, 0x20, 0x73, 0x65, 0x74, 0x20, 0x61, 0x20, 0x22, 0x6a, 0x73, + 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2c, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x73, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x0a, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, + 0x2e, 0x20, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x27, + 0x73, 0x20, 0x64, 0x65, 0x64, 0x75, 0x63, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x27, 0x73, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x69, + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x61, 0x6d, 0x65, 0x6c, 0x43, 0x61, 0x73, 0x65, 0x2e, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x08, 0x04, 0x12, 0x04, 0xd3, 0x01, 0x02, 0x0a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x08, 0x05, 0x12, 0x04, 0xd3, 0x01, 0x0b, 0x11, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x04, 0x02, 0x08, 0x01, 0x12, 0x04, 0xd3, 0x01, 0x12, 0x1b, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x04, 0x02, 0x08, 0x03, 0x12, 0x04, 0xd3, 0x01, 0x1e, 0x20, 0x0a, 0x0c, 0x0a, 0x04, + 0x04, 0x04, 0x02, 0x09, 0x12, 0x04, 0xd5, 0x01, 0x02, 0x24, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, + 0x02, 0x09, 0x04, 0x12, 0x04, 0xd5, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, + 0x09, 0x06, 0x12, 0x04, 0xd5, 0x01, 0x0b, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x09, + 0x01, 0x12, 0x04, 0xd5, 0x01, 0x18, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x09, 0x03, + 0x12, 0x04, 0xd5, 0x01, 0x22, 0x23, 0x0a, 0xb3, 0x09, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x0a, 0x12, + 0x04, 0xec, 0x01, 0x02, 0x25, 0x1a, 0xa4, 0x09, 0x20, 0x49, 0x66, 0x20, 0x74, 0x72, 0x75, 0x65, + 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, 0x20, 0x22, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0x2e, 0x20, 0x57, + 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2c, 0x20, + 0x69, 0x74, 0x0a, 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x73, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x63, 0x65, 0x20, 0x72, 0x65, 0x67, 0x61, 0x72, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, 0x20, + 0x57, 0x68, 0x65, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x5f, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x69, 0x73, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, + 0x20, 0x62, 0x65, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6f, 0x6e, 0x65, + 0x6f, 0x66, 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x6c, 0x20, 0x74, 0x6f, + 0x20, 0x6f, 0x6c, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x20, 0x63, 0x6c, 0x69, 0x65, + 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x0a, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x20, 0x69, 0x73, 0x20, 0x6b, 0x6e, 0x6f, 0x77, + 0x6e, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x22, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, + 0x63, 0x22, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, + 0x20, 0x69, 0x74, 0x73, 0x20, 0x73, 0x6f, 0x6c, 0x65, 0x0a, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, + 0x72, 0x20, 0x28, 0x65, 0x61, 0x63, 0x68, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x20, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x67, 0x65, + 0x74, 0x73, 0x20, 0x69, 0x74, 0x73, 0x20, 0x6f, 0x77, 0x6e, 0x20, 0x73, 0x79, 0x6e, 0x74, 0x68, + 0x65, 0x74, 0x69, 0x63, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x29, 0x2e, 0x20, 0x53, 0x79, 0x6e, + 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x0a, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x20, 0x65, + 0x78, 0x69, 0x73, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x2c, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, + 0x20, 0x61, 0x6e, 0x79, 0x20, 0x41, 0x50, 0x49, 0x2e, 0x20, 0x53, 0x79, 0x6e, 0x74, 0x68, 0x65, + 0x74, 0x69, 0x63, 0x0a, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x73, 0x20, 0x6d, 0x75, 0x73, 0x74, + 0x20, 0x62, 0x65, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x61, 0x66, 0x74, 0x65, + 0x72, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x22, 0x72, 0x65, 0x61, 0x6c, 0x22, 0x20, 0x6f, 0x6e, 0x65, + 0x6f, 0x66, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2c, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, + 0x27, 0x74, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x73, 0x65, + 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x2c, 0x0a, 0x20, + 0x73, 0x69, 0x6e, 0x63, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x20, 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x20, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x2e, 0x20, 0x48, 0x6f, 0x77, 0x65, 0x76, 0x65, 0x72, + 0x20, 0x69, 0x74, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x0a, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, + 0x63, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x20, 0x6f, 0x66, 0x20, 0x77, 0x68, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x73, 0x65, 0x72, 0x20, 0x77, 0x72, 0x6f, + 0x74, 0x65, 0x20, 0x22, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x22, 0x20, 0x6f, 0x72, + 0x20, 0x6e, 0x6f, 0x74, 0x2e, 0x0a, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, + 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x6f, + 0x75, 0x6e, 0x64, 0x2d, 0x74, 0x72, 0x69, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x46, 0x6f, + 0x72, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x20, 0x77, 0x65, + 0x0a, 0x20, 0x67, 0x69, 0x76, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x61, 0x20, 0x73, 0x79, 0x6e, 0x74, 0x68, 0x65, 0x74, 0x69, + 0x63, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x20, 0x61, 0x6c, 0x73, 0x6f, 0x2c, 0x20, 0x65, 0x76, + 0x65, 0x6e, 0x20, 0x74, 0x68, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, + 0x6e, 0x6f, 0x74, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x0a, 0x20, 0x74, 0x6f, + 0x20, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x2e, + 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x65, 0x73, 0x70, 0x65, 0x63, 0x69, 0x61, + 0x6c, 0x6c, 0x79, 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x62, 0x65, + 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, + 0x20, 0x63, 0x61, 0x6e, 0x27, 0x74, 0x0a, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x20, 0x69, 0x66, 0x20, + 0x61, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x2c, + 0x20, 0x73, 0x6f, 0x20, 0x69, 0x74, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x61, 0x6c, 0x77, 0x61, + 0x79, 0x73, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x20, 0x61, 0x0a, 0x20, 0x73, 0x79, 0x6e, + 0x74, 0x68, 0x65, 0x74, 0x69, 0x63, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x2e, 0x0a, 0x0a, 0x20, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x32, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x2c, 0x20, 0x62, 0x65, 0x63, + 0x61, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, + 0x79, 0x20, 0x69, 0x6e, 0x64, 0x69, 0x63, 0x61, 0x74, 0x65, 0x0a, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x60, 0x4c, 0x41, 0x42, 0x45, 0x4c, + 0x5f, 0x4f, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x41, 0x4c, 0x60, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x04, 0x02, 0x0a, 0x04, 0x12, 0x04, 0xec, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x04, 0x02, 0x0a, 0x05, 0x12, 0x04, 0xec, 0x01, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, + 0x02, 0x0a, 0x01, 0x12, 0x04, 0xec, 0x01, 0x10, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x04, 0x02, + 0x0a, 0x03, 0x12, 0x04, 0xec, 0x01, 0x22, 0x24, 0x0a, 0x22, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x06, + 0xf0, 0x01, 0x00, 0xf3, 0x01, 0x01, 0x1a, 0x14, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x6f, 0x6e, 0x65, 0x6f, 0x66, 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, + 0x04, 0x05, 0x01, 0x12, 0x04, 0xf0, 0x01, 0x08, 0x1c, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x05, 0x02, + 0x00, 0x12, 0x04, 0xf1, 0x01, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x04, + 0x12, 0x04, 0xf1, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x05, 0x12, + 0x04, 0xf1, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, 0x04, + 0xf1, 0x01, 0x12, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x03, 0x12, 0x04, 0xf1, + 0x01, 0x19, 0x1a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x01, 0x12, 0x04, 0xf2, 0x01, 0x02, + 0x24, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x04, 0x12, 0x04, 0xf2, 0x01, 0x02, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x06, 0x12, 0x04, 0xf2, 0x01, 0x0b, 0x17, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x01, 0x12, 0x04, 0xf2, 0x01, 0x18, 0x1f, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x03, 0x12, 0x04, 0xf2, 0x01, 0x22, 0x23, 0x0a, 0x27, 0x0a, + 0x02, 0x04, 0x06, 0x12, 0x06, 0xf6, 0x01, 0x00, 0x90, 0x02, 0x01, 0x1a, 0x19, 0x20, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x04, 0xf6, + 0x01, 0x08, 0x1b, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x00, 0x12, 0x04, 0xf7, 0x01, 0x02, + 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, 0x04, 0x12, 0x04, 0xf7, 0x01, 0x02, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, 0x05, 0x12, 0x04, 0xf7, 0x01, 0x0b, 0x11, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, 0x01, 0x12, 0x04, 0xf7, 0x01, 0x12, 0x16, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, 0x03, 0x12, 0x04, 0xf7, 0x01, 0x19, 0x1a, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x06, 0x02, 0x01, 0x12, 0x04, 0xf9, 0x01, 0x02, 0x2e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x06, 0x02, 0x01, 0x04, 0x12, 0x04, 0xf9, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, + 0x02, 0x01, 0x06, 0x12, 0x04, 0xf9, 0x01, 0x0b, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, + 0x01, 0x01, 0x12, 0x04, 0xf9, 0x01, 0x24, 0x29, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x01, + 0x03, 0x12, 0x04, 0xf9, 0x01, 0x2c, 0x2d, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x02, 0x12, + 0x04, 0xfb, 0x01, 0x02, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x04, 0x12, 0x04, + 0xfb, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x06, 0x12, 0x04, 0xfb, + 0x01, 0x0b, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x01, 0x12, 0x04, 0xfb, 0x01, + 0x17, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x03, 0x12, 0x04, 0xfb, 0x01, 0x21, + 0x22, 0x0a, 0xaf, 0x02, 0x0a, 0x04, 0x04, 0x06, 0x03, 0x00, 0x12, 0x06, 0x83, 0x02, 0x02, 0x86, + 0x02, 0x03, 0x1a, 0x9e, 0x02, 0x20, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x72, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x20, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, + 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, + 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x62, 0x79, 0x0a, 0x20, 0x65, 0x6e, 0x74, 0x72, + 0x69, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, + 0x65, 0x6e, 0x75, 0x6d, 0x2e, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x72, + 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6f, 0x76, + 0x65, 0x72, 0x6c, 0x61, 0x70, 0x2e, 0x0a, 0x0a, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, 0x74, 0x69, + 0x6e, 0x63, 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, + 0x74, 0x0a, 0x20, 0x69, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x20, + 0x73, 0x75, 0x63, 0x68, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x63, 0x61, 0x6e, + 0x20, 0x61, 0x70, 0x70, 0x72, 0x6f, 0x70, 0x72, 0x69, 0x61, 0x74, 0x65, 0x6c, 0x79, 0x20, 0x72, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x74, + 0x69, 0x72, 0x65, 0x20, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x0a, 0x20, 0x64, 0x6f, 0x6d, 0x61, 0x69, + 0x6e, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x03, 0x00, 0x01, 0x12, 0x04, 0x83, 0x02, + 0x0a, 0x1b, 0x0a, 0x1c, 0x0a, 0x06, 0x04, 0x06, 0x03, 0x00, 0x02, 0x00, 0x12, 0x04, 0x84, 0x02, + 0x04, 0x1d, 0x22, 0x0c, 0x20, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x2e, 0x0a, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x04, 0x84, 0x02, 0x04, + 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x00, 0x05, 0x12, 0x04, 0x84, 0x02, + 0x0d, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0x84, + 0x02, 0x13, 0x18, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x04, + 0x84, 0x02, 0x1b, 0x1c, 0x0a, 0x1c, 0x0a, 0x06, 0x04, 0x06, 0x03, 0x00, 0x02, 0x01, 0x12, 0x04, + 0x85, 0x02, 0x04, 0x1b, 0x22, 0x0c, 0x20, 0x49, 0x6e, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, + 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x01, 0x04, 0x12, 0x04, 0x85, + 0x02, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x01, 0x05, 0x12, 0x04, + 0x85, 0x02, 0x0d, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x01, 0x01, 0x12, + 0x04, 0x85, 0x02, 0x13, 0x16, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x06, 0x03, 0x00, 0x02, 0x01, 0x03, + 0x12, 0x04, 0x85, 0x02, 0x19, 0x1a, 0x0a, 0xaa, 0x01, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x03, 0x12, + 0x04, 0x8b, 0x02, 0x02, 0x30, 0x1a, 0x9b, 0x01, 0x20, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x6f, + 0x66, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x65, 0x72, + 0x69, 0x63, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x69, 0x63, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, + 0x65, 0x64, 0x0a, 0x20, 0x62, 0x79, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x65, + 0x6e, 0x75, 0x6d, 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, + 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x0a, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, + 0x70, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x03, 0x04, 0x12, 0x04, 0x8b, 0x02, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x03, 0x06, 0x12, 0x04, 0x8b, 0x02, 0x0b, + 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x03, 0x01, 0x12, 0x04, 0x8b, 0x02, 0x1d, 0x2b, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x03, 0x03, 0x12, 0x04, 0x8b, 0x02, 0x2e, 0x2f, 0x0a, + 0x6c, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x04, 0x12, 0x04, 0x8f, 0x02, 0x02, 0x24, 0x1a, 0x5e, 0x20, + 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x76, 0x61, + 0x6c, 0x75, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, + 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x75, 0x73, + 0x65, 0x64, 0x2e, 0x20, 0x41, 0x20, 0x67, 0x69, 0x76, 0x65, 0x6e, 0x20, 0x6e, 0x61, 0x6d, 0x65, + 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, + 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x63, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x06, 0x02, 0x04, 0x04, 0x12, 0x04, 0x8f, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x06, 0x02, 0x04, 0x05, 0x12, 0x04, 0x8f, 0x02, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x06, 0x02, 0x04, 0x01, 0x12, 0x04, 0x8f, 0x02, 0x12, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x06, + 0x02, 0x04, 0x03, 0x12, 0x04, 0x8f, 0x02, 0x22, 0x23, 0x0a, 0x31, 0x0a, 0x02, 0x04, 0x07, 0x12, + 0x06, 0x93, 0x02, 0x00, 0x98, 0x02, 0x01, 0x1a, 0x23, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x73, 0x20, 0x61, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x69, 0x6e, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, + 0x04, 0x07, 0x01, 0x12, 0x04, 0x93, 0x02, 0x08, 0x20, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x07, 0x02, + 0x00, 0x12, 0x04, 0x94, 0x02, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x04, + 0x12, 0x04, 0x94, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x05, 0x12, + 0x04, 0x94, 0x02, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x01, 0x12, 0x04, + 0x94, 0x02, 0x12, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x03, 0x12, 0x04, 0x94, + 0x02, 0x19, 0x1a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x07, 0x02, 0x01, 0x12, 0x04, 0x95, 0x02, 0x02, + 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x04, 0x12, 0x04, 0x95, 0x02, 0x02, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x05, 0x12, 0x04, 0x95, 0x02, 0x0b, 0x10, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x01, 0x12, 0x04, 0x95, 0x02, 0x11, 0x17, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x07, 0x02, 0x01, 0x03, 0x12, 0x04, 0x95, 0x02, 0x1a, 0x1b, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x07, 0x02, 0x02, 0x12, 0x04, 0x97, 0x02, 0x02, 0x28, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x07, 0x02, 0x02, 0x04, 0x12, 0x04, 0x97, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, + 0x02, 0x02, 0x06, 0x12, 0x04, 0x97, 0x02, 0x0b, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, + 0x02, 0x01, 0x12, 0x04, 0x97, 0x02, 0x1c, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x02, + 0x03, 0x12, 0x04, 0x97, 0x02, 0x26, 0x27, 0x0a, 0x24, 0x0a, 0x02, 0x04, 0x08, 0x12, 0x06, 0x9b, + 0x02, 0x00, 0xa0, 0x02, 0x01, 0x1a, 0x16, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, + 0x73, 0x20, 0x61, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, + 0x03, 0x04, 0x08, 0x01, 0x12, 0x04, 0x9b, 0x02, 0x08, 0x1e, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x08, + 0x02, 0x00, 0x12, 0x04, 0x9c, 0x02, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, + 0x04, 0x12, 0x04, 0x9c, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x05, + 0x12, 0x04, 0x9c, 0x02, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x01, 0x12, + 0x04, 0x9c, 0x02, 0x12, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x00, 0x03, 0x12, 0x04, + 0x9c, 0x02, 0x19, 0x1a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x01, 0x12, 0x04, 0x9d, 0x02, + 0x02, 0x2c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x04, 0x12, 0x04, 0x9d, 0x02, 0x02, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x06, 0x12, 0x04, 0x9d, 0x02, 0x0b, 0x20, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x01, 0x12, 0x04, 0x9d, 0x02, 0x21, 0x27, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x03, 0x12, 0x04, 0x9d, 0x02, 0x2a, 0x2b, 0x0a, 0x0c, + 0x0a, 0x04, 0x04, 0x08, 0x02, 0x02, 0x12, 0x04, 0x9f, 0x02, 0x02, 0x26, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x08, 0x02, 0x02, 0x04, 0x12, 0x04, 0x9f, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x08, 0x02, 0x02, 0x06, 0x12, 0x04, 0x9f, 0x02, 0x0b, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, + 0x02, 0x02, 0x01, 0x12, 0x04, 0x9f, 0x02, 0x1a, 0x21, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, + 0x02, 0x03, 0x12, 0x04, 0x9f, 0x02, 0x24, 0x25, 0x0a, 0x30, 0x0a, 0x02, 0x04, 0x09, 0x12, 0x06, + 0xa3, 0x02, 0x00, 0xb1, 0x02, 0x01, 0x1a, 0x22, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x61, + 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x09, + 0x01, 0x12, 0x04, 0xa3, 0x02, 0x08, 0x1d, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x00, 0x12, + 0x04, 0xa4, 0x02, 0x02, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x04, 0x12, 0x04, + 0xa4, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x05, 0x12, 0x04, 0xa4, + 0x02, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x01, 0x12, 0x04, 0xa4, 0x02, + 0x12, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x00, 0x03, 0x12, 0x04, 0xa4, 0x02, 0x19, + 0x1a, 0x0a, 0x97, 0x01, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x01, 0x12, 0x04, 0xa8, 0x02, 0x02, 0x21, + 0x1a, 0x88, 0x01, 0x20, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x2e, + 0x20, 0x20, 0x54, 0x68, 0x65, 0x73, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x73, 0x6f, + 0x6c, 0x76, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, + 0x20, 0x77, 0x61, 0x79, 0x20, 0x61, 0x73, 0x0a, 0x20, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20, 0x6d, 0x75, 0x73, + 0x74, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x09, 0x02, 0x01, 0x04, 0x12, 0x04, 0xa8, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, + 0x02, 0x01, 0x05, 0x12, 0x04, 0xa8, 0x02, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, + 0x01, 0x01, 0x12, 0x04, 0xa8, 0x02, 0x12, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, + 0x03, 0x12, 0x04, 0xa8, 0x02, 0x1f, 0x20, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x02, 0x12, + 0x04, 0xa9, 0x02, 0x02, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x02, 0x04, 0x12, 0x04, + 0xa9, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x02, 0x05, 0x12, 0x04, 0xa9, + 0x02, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x02, 0x01, 0x12, 0x04, 0xa9, 0x02, + 0x12, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x02, 0x03, 0x12, 0x04, 0xa9, 0x02, 0x20, + 0x21, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x03, 0x12, 0x04, 0xab, 0x02, 0x02, 0x25, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x03, 0x04, 0x12, 0x04, 0xab, 0x02, 0x02, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x09, 0x02, 0x03, 0x06, 0x12, 0x04, 0xab, 0x02, 0x0b, 0x18, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x09, 0x02, 0x03, 0x01, 0x12, 0x04, 0xab, 0x02, 0x19, 0x20, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x09, 0x02, 0x03, 0x03, 0x12, 0x04, 0xab, 0x02, 0x23, 0x24, 0x0a, 0x45, 0x0a, 0x04, 0x04, + 0x09, 0x02, 0x04, 0x12, 0x04, 0xae, 0x02, 0x02, 0x37, 0x1a, 0x37, 0x20, 0x49, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x69, 0x66, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, + 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x73, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x04, 0x04, 0x12, 0x04, 0xae, 0x02, 0x02, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x04, 0x05, 0x12, 0x04, 0xae, 0x02, 0x0b, 0x0f, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x04, 0x01, 0x12, 0x04, 0xae, 0x02, 0x10, 0x20, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x04, 0x03, 0x12, 0x04, 0xae, 0x02, 0x23, 0x24, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x09, 0x02, 0x04, 0x08, 0x12, 0x04, 0xae, 0x02, 0x25, 0x36, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x09, 0x02, 0x04, 0x07, 0x12, 0x04, 0xae, 0x02, 0x30, 0x35, 0x0a, 0x45, 0x0a, 0x04, + 0x04, 0x09, 0x02, 0x05, 0x12, 0x04, 0xb0, 0x02, 0x02, 0x37, 0x1a, 0x37, 0x20, 0x49, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x69, 0x66, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, + 0x6c, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x05, 0x04, 0x12, 0x04, 0xb0, 0x02, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x05, 0x05, 0x12, 0x04, 0xb0, 0x02, 0x0b, + 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x05, 0x01, 0x12, 0x04, 0xb0, 0x02, 0x10, 0x20, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x05, 0x03, 0x12, 0x04, 0xb0, 0x02, 0x23, 0x24, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x05, 0x08, 0x12, 0x04, 0xb0, 0x02, 0x25, 0x36, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x09, 0x02, 0x05, 0x07, 0x12, 0x04, 0xb0, 0x02, 0x30, 0x35, 0x0a, 0xaf, 0x0e, + 0x0a, 0x02, 0x04, 0x0a, 0x12, 0x06, 0xd4, 0x02, 0x00, 0xcf, 0x03, 0x01, 0x32, 0x4e, 0x20, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x0a, 0x20, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x32, 0xd0, 0x0d, 0x20, + 0x45, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x6d, 0x61, + 0x79, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x22, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, + 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x65, 0x73, + 0x65, 0x20, 0x61, 0x72, 0x65, 0x0a, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x61, 0x6e, 0x6e, 0x6f, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x6d, 0x61, + 0x79, 0x20, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x6f, 0x20, + 0x62, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x73, 0x6c, 0x69, + 0x67, 0x68, 0x74, 0x6c, 0x79, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x6c, + 0x79, 0x0a, 0x20, 0x6f, 0x72, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x20, 0x68, 0x69, 0x6e, 0x74, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x64, 0x65, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x6e, 0x69, 0x70, 0x75, 0x6c, 0x61, 0x74, 0x65, + 0x73, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x6d, + 0x61, 0x79, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x73, 0x20, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x2a, 0x4f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, + 0x0a, 0x20, 0x54, 0x68, 0x65, 0x73, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x79, 0x65, 0x74, 0x20, 0x62, + 0x65, 0x20, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, + 0x6e, 0x67, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x2c, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x0a, 0x20, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x2e, 0x20, 0x20, 0x49, 0x6e, 0x73, 0x74, 0x65, 0x61, + 0x64, 0x20, 0x69, 0x74, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x6d, + 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x2a, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x75, 0x6e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6d, 0x75, 0x73, + 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x0a, 0x20, 0x61, 0x63, 0x72, 0x6f, 0x73, 0x73, 0x20, 0x61, 0x6c, 0x6c, + 0x20, 0x2a, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x73, 0x2e, 0x20, 0x57, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x6f, + 0x70, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x77, 0x65, 0x20, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x20, 0x61, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x2c, 0x20, 0x61, 0x74, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x70, 0x6f, 0x69, 0x6e, + 0x74, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x20, 0x68, 0x61, 0x76, + 0x65, 0x20, 0x62, 0x65, 0x65, 0x6e, 0x0a, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x64, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x73, 0x6f, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x2e, 0x0a, + 0x0a, 0x20, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x63, 0x68, + 0x6f, 0x73, 0x65, 0x6e, 0x20, 0x61, 0x73, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x3a, + 0x0a, 0x20, 0x2a, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, + 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x20, 0x61, + 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, + 0x78, 0x70, 0x65, 0x72, 0x69, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x6c, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x2c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x35, 0x30, 0x30, 0x30, 0x30, 0x0a, 0x20, 0x20, 0x20, + 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x39, 0x39, 0x39, 0x39, 0x39, 0x2e, 0x20, 0x20, + 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, 0x75, 0x70, 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x20, + 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x64, 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, + 0x65, 0x0a, 0x20, 0x20, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x0a, 0x20, 0x2a, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x77, 0x69, 0x6c, 0x6c, + 0x20, 0x62, 0x65, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x73, 0x68, 0x65, 0x64, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x6c, 0x79, 0x20, + 0x62, 0x79, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x69, + 0x6e, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x69, 0x65, 0x73, 0x2c, 0x20, 0x65, 0x2d, 0x6d, 0x61, 0x69, 0x6c, 0x20, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2d, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x2d, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x2d, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x40, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x20, + 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2e, 0x20, 0x53, 0x69, 0x6d, 0x70, 0x6c, + 0x79, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x70, + 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x28, 0x65, 0x2e, 0x67, + 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x2d, 0x43, + 0x20, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x29, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x79, 0x6f, 0x75, + 0x72, 0x20, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x20, 0x77, 0x65, 0x62, 0x73, 0x69, 0x74, + 0x65, 0x20, 0x28, 0x69, 0x66, 0x20, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x29, + 0x20, 0x2d, 0x2d, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x27, 0x73, 0x20, 0x6e, 0x6f, 0x0a, 0x20, + 0x20, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x70, 0x6c, 0x61, 0x69, + 0x6e, 0x20, 0x68, 0x6f, 0x77, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x64, + 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x2e, 0x20, 0x55, 0x73, + 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6e, + 0x65, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x2e, 0x20, 0x59, 0x6f, 0x75, + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x65, 0x20, 0x6d, 0x75, 0x6c, + 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, + 0x62, 0x79, 0x20, 0x70, 0x75, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, + 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, + 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, + 0x20, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x6f, 0x66, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x73, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x73, 0x3a, 0x0a, 0x20, 0x20, + 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x65, 0x72, 0x73, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2d, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2f, + 0x64, 0x6f, 0x63, 0x73, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x23, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x74, 0x75, + 0x72, 0x6e, 0x73, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x20, 0x70, 0x6f, + 0x70, 0x75, 0x6c, 0x61, 0x72, 0x2c, 0x20, 0x61, 0x20, 0x77, 0x65, 0x62, 0x20, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x73, 0x65, 0x74, + 0x20, 0x75, 0x70, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, + 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2e, 0x0a, 0x0a, + 0x0b, 0x0a, 0x03, 0x04, 0x0a, 0x01, 0x12, 0x04, 0xd4, 0x02, 0x08, 0x13, 0x0a, 0xf4, 0x01, 0x0a, + 0x04, 0x04, 0x0a, 0x02, 0x00, 0x12, 0x04, 0xda, 0x02, 0x02, 0x23, 0x1a, 0xe5, 0x01, 0x20, 0x53, + 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x20, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, + 0x65, 0x73, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, + 0x6d, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x0a, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x2e, 0x20, 0x20, + 0x42, 0x79, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x75, 0x73, 0x65, 0x64, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x69, 0x73, 0x20, 0x6f, 0x66, 0x74, 0x65, 0x6e, 0x0a, 0x20, 0x69, 0x6e, 0x61, 0x70, 0x70, 0x72, + 0x6f, 0x70, 0x72, 0x69, 0x61, 0x74, 0x65, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x20, 0x64, + 0x6f, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x73, + 0x74, 0x61, 0x72, 0x74, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x77, 0x61, + 0x72, 0x64, 0x73, 0x0a, 0x20, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x04, 0x12, 0x04, 0xda, 0x02, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x05, 0x12, 0x04, 0xda, 0x02, 0x0b, + 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x01, 0x12, 0x04, 0xda, 0x02, 0x12, 0x1e, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x00, 0x03, 0x12, 0x04, 0xda, 0x02, 0x21, 0x22, 0x0a, + 0xf1, 0x02, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x01, 0x12, 0x04, 0xe2, 0x02, 0x02, 0x2b, 0x1a, 0xe2, + 0x02, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, + 0x65, 0x72, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x20, 0x54, 0x68, + 0x61, 0x74, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, 0x6c, + 0x77, 0x61, 0x79, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x67, + 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x28, 0x29, 0x20, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x61, 0x73, 0x0a, 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, + 0x73, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x74, 0x6f, 0x70, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x20, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x20, 0x49, 0x66, 0x20, 0x6a, 0x61, 0x76, 0x61, 0x5f, + 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x69, + 0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x6e, + 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x63, + 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x0a, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x73, 0x69, + 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x77, 0x72, + 0x61, 0x70, 0x70, 0x65, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x20, 0x63, 0x6c, 0x61, 0x73, + 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x04, 0x12, 0x04, 0xe2, 0x02, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x05, 0x12, 0x04, 0xe2, 0x02, 0x0b, + 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x01, 0x12, 0x04, 0xe2, 0x02, 0x12, 0x26, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x01, 0x03, 0x12, 0x04, 0xe2, 0x02, 0x29, 0x2a, 0x0a, + 0xa6, 0x03, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x02, 0x12, 0x04, 0xea, 0x02, 0x02, 0x3b, 0x1a, 0x97, + 0x03, 0x20, 0x49, 0x66, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x68, + 0x65, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x20, 0x63, 0x6f, 0x64, 0x65, + 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20, 0x61, 0x20, 0x73, 0x65, 0x70, 0x61, 0x72, + 0x61, 0x74, 0x65, 0x20, 0x2e, 0x6a, 0x61, 0x76, 0x61, 0x0a, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x74, 0x6f, 0x70, 0x2d, 0x6c, 0x65, 0x76, + 0x65, 0x6c, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x65, 0x6e, 0x75, 0x6d, + 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x64, 0x65, + 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x0a, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x75, 0x73, + 0x2c, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x2a, 0x6e, 0x6f, 0x74, 0x2a, 0x20, 0x62, 0x65, 0x20, 0x6e, 0x65, 0x73, 0x74, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, + 0x61, 0x70, 0x70, 0x65, 0x72, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x0a, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x6a, 0x61, 0x76, 0x61, 0x5f, 0x6f, 0x75, 0x74, 0x65, 0x72, + 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x20, 0x20, 0x48, 0x6f, 0x77, + 0x65, 0x76, 0x65, 0x72, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, + 0x72, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x73, 0x74, 0x69, + 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x0a, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x66, 0x69, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x67, 0x65, 0x74, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x6f, 0x72, 0x28, 0x29, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x61, 0x73, + 0x20, 0x77, 0x65, 0x6c, 0x6c, 0x20, 0x61, 0x73, 0x20, 0x61, 0x6e, 0x79, 0x0a, 0x20, 0x74, 0x6f, + 0x70, 0x2d, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, + 0x04, 0x12, 0x04, 0xea, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x05, + 0x12, 0x04, 0xea, 0x02, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x01, 0x12, + 0x04, 0xea, 0x02, 0x10, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x03, 0x12, 0x04, + 0xea, 0x02, 0x26, 0x28, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x08, 0x12, 0x04, 0xea, + 0x02, 0x29, 0x3a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x02, 0x07, 0x12, 0x04, 0xea, 0x02, + 0x34, 0x39, 0x0a, 0x29, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x03, 0x12, 0x04, 0xed, 0x02, 0x02, 0x45, + 0x1a, 0x1b, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, + 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x03, 0x04, 0x12, 0x04, 0xed, 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x03, 0x05, 0x12, 0x04, 0xed, 0x02, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x03, 0x01, 0x12, 0x04, 0xed, 0x02, 0x10, 0x2d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x03, 0x03, 0x12, 0x04, 0xed, 0x02, 0x30, 0x32, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x03, 0x08, 0x12, 0x04, 0xed, 0x02, 0x33, 0x44, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x0a, 0x02, 0x03, + 0x08, 0x03, 0x12, 0x04, 0xed, 0x02, 0x34, 0x43, 0x0a, 0xe6, 0x02, 0x0a, 0x04, 0x04, 0x0a, 0x02, + 0x04, 0x12, 0x04, 0xf5, 0x02, 0x02, 0x3e, 0x1a, 0xd7, 0x02, 0x20, 0x49, 0x66, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x74, 0x72, 0x75, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x4a, 0x61, 0x76, 0x61, 0x32, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x0a, 0x20, 0x74, + 0x68, 0x72, 0x6f, 0x77, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x61, + 0x74, 0x74, 0x65, 0x6d, 0x70, 0x74, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x61, 0x64, 0x65, 0x20, 0x74, + 0x6f, 0x20, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x20, 0x61, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x55, + 0x54, 0x46, 0x2d, 0x38, 0x0a, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x73, 0x65, 0x71, 0x75, 0x65, + 0x6e, 0x63, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x0a, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, + 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, + 0x64, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x2e, 0x0a, 0x20, 0x48, 0x6f, + 0x77, 0x65, 0x76, 0x65, 0x72, 0x2c, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, + 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x73, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x55, 0x54, 0x46, 0x2d, + 0x38, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x65, 0x73, + 0x2e, 0x0a, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x68, + 0x61, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x20, 0x6f, 0x6e, 0x20, + 0x77, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6c, 0x69, 0x74, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x04, 0x04, 0x12, 0x04, 0xf5, 0x02, 0x02, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0xf5, 0x02, 0x0b, 0x0f, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0xf5, 0x02, 0x10, 0x26, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0xf5, 0x02, 0x29, 0x2b, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x04, 0x08, 0x12, 0x04, 0xf5, 0x02, 0x2c, 0x3d, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x04, 0x07, 0x12, 0x04, 0xf5, 0x02, 0x37, 0x3c, 0x0a, 0x4c, 0x0a, 0x04, 0x04, + 0x0a, 0x04, 0x00, 0x12, 0x06, 0xf9, 0x02, 0x02, 0xfe, 0x02, 0x03, 0x1a, 0x3c, 0x20, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x20, + 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x73, 0x70, 0x65, 0x65, 0x64, 0x20, 0x6f, 0x72, 0x20, 0x63, 0x6f, + 0x64, 0x65, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x04, + 0x00, 0x01, 0x12, 0x04, 0xf9, 0x02, 0x07, 0x13, 0x0a, 0x44, 0x0a, 0x06, 0x04, 0x0a, 0x04, 0x00, + 0x02, 0x00, 0x12, 0x04, 0xfa, 0x02, 0x04, 0x0e, 0x22, 0x34, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x64, + 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x2c, 0x20, 0x73, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x0a, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x0a, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0xfa, 0x02, 0x04, 0x09, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x0a, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x04, 0xfa, 0x02, 0x0c, 0x0d, + 0x0a, 0x47, 0x0a, 0x06, 0x04, 0x0a, 0x04, 0x00, 0x02, 0x01, 0x12, 0x04, 0xfc, 0x02, 0x04, 0x12, + 0x1a, 0x06, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x0a, 0x22, 0x2f, 0x20, 0x55, 0x73, 0x65, 0x20, 0x52, + 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x70, 0x73, 0x20, 0x74, 0x6f, 0x20, + 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0a, 0x04, + 0x00, 0x02, 0x01, 0x01, 0x12, 0x04, 0xfc, 0x02, 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0a, + 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x04, 0xfc, 0x02, 0x10, 0x11, 0x0a, 0x47, 0x0a, 0x06, 0x04, + 0x0a, 0x04, 0x00, 0x02, 0x02, 0x12, 0x04, 0xfd, 0x02, 0x04, 0x15, 0x22, 0x37, 0x20, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x75, 0x73, 0x69, 0x6e, + 0x67, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x69, 0x74, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x74, 0x69, + 0x6d, 0x65, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0a, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, + 0x04, 0xfd, 0x02, 0x04, 0x10, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0a, 0x04, 0x00, 0x02, 0x02, 0x02, + 0x12, 0x04, 0xfd, 0x02, 0x13, 0x14, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x05, 0x12, 0x04, + 0xff, 0x02, 0x02, 0x3b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x05, 0x04, 0x12, 0x04, 0xff, + 0x02, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x05, 0x06, 0x12, 0x04, 0xff, 0x02, + 0x0b, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x05, 0x01, 0x12, 0x04, 0xff, 0x02, 0x18, + 0x24, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x05, 0x03, 0x12, 0x04, 0xff, 0x02, 0x27, 0x28, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x05, 0x08, 0x12, 0x04, 0xff, 0x02, 0x29, 0x3a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x05, 0x07, 0x12, 0x04, 0xff, 0x02, 0x34, 0x39, 0x0a, 0xe2, + 0x02, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x06, 0x12, 0x04, 0x86, 0x03, 0x02, 0x22, 0x1a, 0xd3, 0x02, + 0x20, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, 0x6f, 0x20, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x73, 0x74, 0x72, 0x75, 0x63, + 0x74, 0x73, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, + 0x6d, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x0a, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x64, 0x2e, 0x20, 0x49, + 0x66, 0x20, 0x6f, 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x47, + 0x6f, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, + 0x65, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x3a, 0x0a, 0x20, 0x20, + 0x20, 0x2d, 0x20, 0x54, 0x68, 0x65, 0x20, 0x62, 0x61, 0x73, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x69, + 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x70, + 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x2d, 0x20, 0x4f, 0x74, + 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x20, 0x73, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x2c, 0x20, 0x69, 0x66, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x20, + 0x20, 0x20, 0x2d, 0x20, 0x4f, 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x2c, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x62, 0x61, 0x73, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x06, 0x04, 0x12, 0x04, 0x86, 0x03, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x06, 0x05, 0x12, 0x04, 0x86, 0x03, 0x0b, + 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x06, 0x01, 0x12, 0x04, 0x86, 0x03, 0x12, 0x1c, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x06, 0x03, 0x12, 0x04, 0x86, 0x03, 0x1f, 0x21, 0x0a, + 0xd4, 0x04, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x07, 0x12, 0x04, 0x95, 0x03, 0x02, 0x3b, 0x1a, 0xc5, + 0x04, 0x20, 0x53, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, + 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x62, 0x65, 0x20, 0x67, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6c, + 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x3f, 0x20, 0x20, 0x22, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x22, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x0a, 0x20, 0x61, 0x72, + 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x74, + 0x6f, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x75, 0x6c, 0x61, 0x72, + 0x20, 0x52, 0x50, 0x43, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x20, 0x20, 0x54, 0x68, + 0x65, 0x79, 0x20, 0x61, 0x72, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, + 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x6d, 0x61, 0x69, 0x6e, 0x20, 0x63, 0x6f, + 0x64, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x69, 0x6e, + 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, 0x28, + 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x20, 0x70, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x73, 0x29, 0x2e, 0x0a, 0x20, 0x47, 0x65, + 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x77, + 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6b, 0x69, 0x6e, + 0x64, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, 0x67, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, + 0x64, 0x20, 0x62, 0x79, 0x0a, 0x20, 0x65, 0x61, 0x72, 0x6c, 0x79, 0x20, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x0a, 0x0a, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, + 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x6e, 0x6f, 0x77, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x64, 0x65, 0x72, 0x65, 0x64, 0x20, 0x64, + 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x66, 0x61, 0x76, + 0x6f, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x6c, 0x75, 0x67, + 0x69, 0x6e, 0x73, 0x0a, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, + 0x74, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x75, + 0x6c, 0x61, 0x72, 0x20, 0x52, 0x50, 0x43, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x20, + 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x2c, 0x0a, 0x20, 0x74, 0x68, 0x65, + 0x73, 0x65, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x61, + 0x6c, 0x73, 0x65, 0x2e, 0x20, 0x20, 0x4f, 0x6c, 0x64, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x77, + 0x68, 0x69, 0x63, 0x68, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x73, 0x20, 0x6f, 0x6e, 0x20, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, + 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x0a, 0x20, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, + 0x74, 0x6c, 0x79, 0x20, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x74, 0x6f, 0x20, + 0x74, 0x72, 0x75, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x07, 0x04, 0x12, + 0x04, 0x95, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x07, 0x05, 0x12, 0x04, + 0x95, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x07, 0x01, 0x12, 0x04, 0x95, + 0x03, 0x10, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x07, 0x03, 0x12, 0x04, 0x95, 0x03, + 0x26, 0x28, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x07, 0x08, 0x12, 0x04, 0x95, 0x03, 0x29, + 0x3a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x07, 0x07, 0x12, 0x04, 0x95, 0x03, 0x34, 0x39, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x08, 0x12, 0x04, 0x96, 0x03, 0x02, 0x3d, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x08, 0x04, 0x12, 0x04, 0x96, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x08, 0x05, 0x12, 0x04, 0x96, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x08, 0x01, 0x12, 0x04, 0x96, 0x03, 0x10, 0x25, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x08, 0x03, 0x12, 0x04, 0x96, 0x03, 0x28, 0x2a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x08, 0x08, 0x12, 0x04, 0x96, 0x03, 0x2b, 0x3c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x08, 0x07, 0x12, 0x04, 0x96, 0x03, 0x36, 0x3b, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x09, + 0x12, 0x04, 0x97, 0x03, 0x02, 0x3b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x09, 0x04, 0x12, + 0x04, 0x97, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x09, 0x05, 0x12, 0x04, + 0x97, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x09, 0x01, 0x12, 0x04, 0x97, + 0x03, 0x10, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x09, 0x03, 0x12, 0x04, 0x97, 0x03, + 0x26, 0x28, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x09, 0x08, 0x12, 0x04, 0x97, 0x03, 0x29, + 0x3a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x09, 0x07, 0x12, 0x04, 0x97, 0x03, 0x34, 0x39, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x0a, 0x12, 0x04, 0x98, 0x03, 0x02, 0x3c, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0a, 0x04, 0x12, 0x04, 0x98, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x0a, 0x05, 0x12, 0x04, 0x98, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x0a, 0x01, 0x12, 0x04, 0x98, 0x03, 0x10, 0x24, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x0a, 0x03, 0x12, 0x04, 0x98, 0x03, 0x27, 0x29, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x0a, 0x08, 0x12, 0x04, 0x98, 0x03, 0x2a, 0x3b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x0a, 0x07, 0x12, 0x04, 0x98, 0x03, 0x35, 0x3a, 0x0a, 0xf3, 0x01, 0x0a, 0x04, 0x04, 0x0a, 0x02, + 0x0b, 0x12, 0x04, 0x9e, 0x03, 0x02, 0x32, 0x1a, 0xe4, 0x01, 0x20, 0x49, 0x73, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x3f, 0x0a, 0x20, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x70, 0x6c, 0x61, + 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, + 0x65, 0x6d, 0x69, 0x74, 0x20, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, 0x77, + 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, + 0x79, 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x76, 0x65, 0x72, 0x79, 0x0a, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, + 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, + 0x63, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0b, 0x04, 0x12, 0x04, 0x9e, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x0b, 0x05, 0x12, 0x04, 0x9e, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x0b, 0x01, 0x12, 0x04, 0x9e, 0x03, 0x10, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x0b, 0x03, 0x12, 0x04, 0x9e, 0x03, 0x1d, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x0b, 0x08, 0x12, 0x04, 0x9e, 0x03, 0x20, 0x31, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x0b, 0x07, 0x12, 0x04, 0x9e, 0x03, 0x2b, 0x30, 0x0a, 0x7f, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x0c, + 0x12, 0x04, 0xa2, 0x03, 0x02, 0x37, 0x1a, 0x71, 0x20, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x72, 0x65, 0x6e, + 0x61, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x61, 0x70, 0x70, + 0x6c, 0x69, 0x65, 0x73, 0x0a, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x20, + 0x66, 0x6f, 0x72, 0x20, 0x43, 0x2b, 0x2b, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x0c, 0x04, 0x12, 0x04, 0xa2, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0c, + 0x05, 0x12, 0x04, 0xa2, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0c, 0x01, + 0x12, 0x04, 0xa2, 0x03, 0x10, 0x20, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0c, 0x03, 0x12, + 0x04, 0xa2, 0x03, 0x23, 0x25, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0c, 0x08, 0x12, 0x04, + 0xa2, 0x03, 0x26, 0x36, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0c, 0x07, 0x12, 0x04, 0xa2, + 0x03, 0x31, 0x35, 0x0a, 0x92, 0x01, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x0d, 0x12, 0x04, 0xa7, 0x03, + 0x02, 0x29, 0x1a, 0x83, 0x01, 0x20, 0x53, 0x65, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, + 0x62, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x63, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, + 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x69, 0x73, + 0x20, 0x70, 0x72, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6c, + 0x6c, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x69, 0x76, 0x65, 0x20, 0x63, 0x0a, 0x20, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, + 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x20, 0x64, + 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0d, + 0x04, 0x12, 0x04, 0xa7, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0d, 0x05, + 0x12, 0x04, 0xa7, 0x03, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0d, 0x01, 0x12, + 0x04, 0xa7, 0x03, 0x12, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0d, 0x03, 0x12, 0x04, + 0xa7, 0x03, 0x26, 0x28, 0x0a, 0x49, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x0e, 0x12, 0x04, 0xaa, 0x03, + 0x02, 0x28, 0x1a, 0x3b, 0x20, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x65, 0x73, 0x3b, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x20, 0x74, + 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2e, 0x0a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0e, 0x04, 0x12, 0x04, 0xaa, 0x03, 0x02, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0e, 0x05, 0x12, 0x04, 0xaa, 0x03, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x0e, 0x01, 0x12, 0x04, 0xaa, 0x03, 0x12, 0x22, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x0e, 0x03, 0x12, 0x04, 0xaa, 0x03, 0x25, 0x27, 0x0a, 0x91, 0x02, 0x0a, 0x04, + 0x04, 0x0a, 0x02, 0x0f, 0x12, 0x04, 0xb0, 0x03, 0x02, 0x24, 0x1a, 0x82, 0x02, 0x20, 0x42, 0x79, + 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x53, 0x77, 0x69, 0x66, 0x74, 0x20, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x74, + 0x61, 0x6b, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x43, 0x61, 0x6d, 0x65, 0x6c, 0x43, + 0x61, 0x73, 0x65, 0x20, 0x69, 0x74, 0x0a, 0x20, 0x72, 0x65, 0x70, 0x6c, 0x61, 0x63, 0x69, 0x6e, + 0x67, 0x20, 0x27, 0x2e, 0x27, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, + 0x73, 0x63, 0x6f, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x79, 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x0a, 0x20, + 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x2e, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65, 0x61, 0x64, 0x0a, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x72, 0x65, + 0x66, 0x69, 0x78, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x73, 0x79, + 0x6d, 0x62, 0x6f, 0x6c, 0x73, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x2e, 0x0a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0f, 0x04, 0x12, 0x04, 0xb0, 0x03, 0x02, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x0f, 0x05, 0x12, 0x04, 0xb0, 0x03, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0a, 0x02, 0x0f, 0x01, 0x12, 0x04, 0xb0, 0x03, 0x12, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x0f, 0x03, 0x12, 0x04, 0xb0, 0x03, 0x21, 0x23, 0x0a, 0x7e, 0x0a, 0x04, 0x04, + 0x0a, 0x02, 0x10, 0x12, 0x04, 0xb4, 0x03, 0x02, 0x28, 0x1a, 0x70, 0x20, 0x53, 0x65, 0x74, 0x73, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x68, 0x70, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x20, 0x70, + 0x72, 0x65, 0x66, 0x69, 0x78, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x69, 0x73, 0x20, 0x70, + 0x72, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6c, 0x6c, 0x20, + 0x70, 0x68, 0x70, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x0a, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x10, 0x04, 0x12, 0x04, 0xb4, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x10, 0x05, 0x12, 0x04, 0xb4, 0x03, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x10, 0x01, 0x12, 0x04, 0xb4, 0x03, 0x12, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x10, + 0x03, 0x12, 0x04, 0xb4, 0x03, 0x25, 0x27, 0x0a, 0xbe, 0x01, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x11, + 0x12, 0x04, 0xb9, 0x03, 0x02, 0x25, 0x1a, 0xaf, 0x01, 0x20, 0x55, 0x73, 0x65, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x68, 0x61, + 0x6e, 0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, + 0x65, 0x20, 0x6f, 0x66, 0x20, 0x70, 0x68, 0x70, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x20, 0x44, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x0a, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2e, 0x20, 0x57, + 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x69, 0x73, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, + 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x0a, 0x20, 0x64, 0x65, 0x74, + 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x11, + 0x04, 0x12, 0x04, 0xb9, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x11, 0x05, + 0x12, 0x04, 0xb9, 0x03, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x11, 0x01, 0x12, + 0x04, 0xb9, 0x03, 0x12, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x11, 0x03, 0x12, 0x04, + 0xb9, 0x03, 0x22, 0x24, 0x0a, 0xca, 0x01, 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x12, 0x12, 0x04, 0xbe, + 0x03, 0x02, 0x2e, 0x1a, 0xbb, 0x01, 0x20, 0x55, 0x73, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x6f, + 0x66, 0x20, 0x70, 0x68, 0x70, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, + 0x2e, 0x0a, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, + 0x70, 0x74, 0x79, 0x2e, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x2c, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x6e, + 0x61, 0x6d, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x0a, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x6e, + 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x12, 0x04, 0x12, 0x04, 0xbe, 0x03, 0x02, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x12, 0x05, 0x12, 0x04, 0xbe, 0x03, 0x0b, 0x11, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x12, 0x01, 0x12, 0x04, 0xbe, 0x03, 0x12, 0x28, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x12, 0x03, 0x12, 0x04, 0xbe, 0x03, 0x2b, 0x2d, 0x0a, 0xc2, 0x01, + 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x13, 0x12, 0x04, 0xc3, 0x03, 0x02, 0x24, 0x1a, 0xb3, 0x01, 0x20, + 0x55, 0x73, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x74, 0x6f, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x72, 0x75, 0x62, 0x79, 0x20, 0x67, 0x65, + 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, + 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x0a, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6d, 0x70, + 0x74, 0x79, 0x2e, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x73, 0x65, 0x74, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x66, + 0x6f, 0x72, 0x0a, 0x20, 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x72, 0x75, 0x62, 0x79, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, + 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x13, 0x04, 0x12, 0x04, 0xc3, 0x03, 0x02, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x13, 0x05, 0x12, 0x04, 0xc3, 0x03, 0x0b, 0x11, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x13, 0x01, 0x12, 0x04, 0xc3, 0x03, 0x12, 0x1e, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, 0x13, 0x03, 0x12, 0x04, 0xc3, 0x03, 0x21, 0x23, 0x0a, 0x7c, + 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x14, 0x12, 0x04, 0xc8, 0x03, 0x02, 0x3a, 0x1a, 0x6e, 0x20, 0x54, + 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, + 0x6e, 0x27, 0x74, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, + 0x72, 0x65, 0x2e, 0x0a, 0x20, 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x63, + 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x22, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x73, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x14, 0x04, 0x12, 0x04, 0xc8, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x14, 0x06, 0x12, 0x04, 0xc8, 0x03, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x14, 0x01, 0x12, 0x04, 0xc8, 0x03, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x14, 0x03, 0x12, 0x04, 0xc8, 0x03, 0x36, 0x39, 0x0a, 0x87, 0x01, 0x0a, 0x03, 0x04, 0x0a, 0x05, + 0x12, 0x04, 0xcc, 0x03, 0x02, 0x19, 0x1a, 0x7a, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x0a, 0x20, 0x53, 0x65, 0x65, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x22, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x22, 0x20, 0x73, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, + 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0a, 0x05, 0x00, 0x12, 0x04, 0xcc, 0x03, 0x0d, 0x18, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x05, 0x00, 0x01, 0x12, 0x04, 0xcc, 0x03, 0x0d, 0x11, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x05, 0x00, 0x02, 0x12, 0x04, 0xcc, 0x03, 0x15, 0x18, 0x0a, 0x0b, + 0x0a, 0x03, 0x04, 0x0a, 0x09, 0x12, 0x04, 0xce, 0x03, 0x02, 0x0e, 0x0a, 0x0c, 0x0a, 0x04, 0x04, + 0x0a, 0x09, 0x00, 0x12, 0x04, 0xce, 0x03, 0x0b, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x09, + 0x00, 0x01, 0x12, 0x04, 0xce, 0x03, 0x0b, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x09, 0x00, + 0x02, 0x12, 0x04, 0xce, 0x03, 0x0b, 0x0d, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0b, 0x12, 0x06, 0xd1, + 0x03, 0x00, 0x93, 0x04, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x0b, 0x01, 0x12, 0x04, 0xd1, 0x03, + 0x08, 0x16, 0x0a, 0xd8, 0x05, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x00, 0x12, 0x04, 0xe4, 0x03, 0x02, + 0x3e, 0x1a, 0xc9, 0x05, 0x20, 0x53, 0x65, 0x74, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x74, 0x6f, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x6c, 0x64, 0x20, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x31, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x20, 0x77, + 0x69, 0x72, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x0a, 0x20, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, + 0x20, 0x62, 0x61, 0x63, 0x6b, 0x77, 0x61, 0x72, 0x64, 0x73, 0x2d, 0x63, 0x6f, 0x6d, 0x70, 0x61, + 0x74, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x20, 0x77, 0x69, 0x72, + 0x65, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2e, 0x20, 0x20, 0x59, 0x6f, 0x75, 0x20, + 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x3a, 0x20, 0x20, 0x49, 0x74, 0x27, 0x73, 0x20, + 0x6c, 0x65, 0x73, 0x73, 0x0a, 0x20, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x2c, + 0x20, 0x68, 0x61, 0x73, 0x20, 0x66, 0x65, 0x77, 0x65, 0x72, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x6f, 0x72, 0x65, + 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x0a, 0x0a, 0x20, + 0x54, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, + 0x20, 0x62, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x65, 0x78, 0x61, 0x63, + 0x74, 0x6c, 0x79, 0x20, 0x61, 0x73, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x3a, 0x0a, + 0x20, 0x20, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x46, 0x6f, 0x6f, 0x20, 0x7b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x77, 0x69, 0x72, 0x65, 0x5f, 0x66, 0x6f, + 0x72, 0x6d, 0x61, 0x74, 0x20, 0x3d, 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x34, 0x20, 0x74, + 0x6f, 0x20, 0x6d, 0x61, 0x78, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x4e, 0x6f, 0x74, + 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, + 0x6e, 0x79, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x3b, 0x20, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x53, 0x65, 0x74, 0x73, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x0a, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x73, 0x69, 0x6e, 0x67, + 0x75, 0x6c, 0x61, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x3b, 0x20, 0x65, + 0x2e, 0x67, 0x2e, 0x20, 0x74, 0x68, 0x65, 0x79, 0x20, 0x63, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x0a, + 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x73, 0x2c, 0x20, 0x65, 0x6e, 0x75, 0x6d, + 0x73, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x42, 0x65, 0x63, 0x61, 0x75, + 0x73, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, + 0x74, 0x77, 0x6f, 0x20, 0x72, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x61, 0x72, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x65, 0x6e, 0x66, 0x6f, 0x72, 0x63, 0x65, + 0x64, 0x20, 0x62, 0x79, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0b, 0x02, 0x00, 0x04, 0x12, 0x04, 0xe4, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0b, 0x02, 0x00, 0x05, 0x12, 0x04, 0xe4, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0b, 0x02, 0x00, 0x01, 0x12, 0x04, 0xe4, 0x03, 0x10, 0x27, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, + 0x02, 0x00, 0x03, 0x12, 0x04, 0xe4, 0x03, 0x2a, 0x2b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, + 0x00, 0x08, 0x12, 0x04, 0xe4, 0x03, 0x2c, 0x3d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, + 0x07, 0x12, 0x04, 0xe4, 0x03, 0x37, 0x3c, 0x0a, 0xeb, 0x01, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x01, + 0x12, 0x04, 0xe9, 0x03, 0x02, 0x46, 0x1a, 0xdc, 0x01, 0x20, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, + 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x61, 0x72, + 0x64, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x28, 0x29, 0x22, + 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6f, 0x72, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, + 0x20, 0x63, 0x61, 0x6e, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x66, 0x6c, 0x69, 0x63, 0x74, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x20, 0x61, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x20, 0x20, 0x54, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, + 0x6d, 0x61, 0x6b, 0x65, 0x20, 0x6d, 0x69, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, + 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x31, 0x20, 0x65, 0x61, 0x73, 0x69, + 0x65, 0x72, 0x3b, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x64, 0x20, 0x22, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x6f, 0x72, 0x22, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x04, 0x12, 0x04, + 0xe9, 0x03, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x05, 0x12, 0x04, 0xe9, + 0x03, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x01, 0x12, 0x04, 0xe9, 0x03, + 0x10, 0x2f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x03, 0x12, 0x04, 0xe9, 0x03, 0x32, + 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x08, 0x12, 0x04, 0xe9, 0x03, 0x34, 0x45, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x07, 0x12, 0x04, 0xe9, 0x03, 0x3f, 0x44, 0x0a, + 0xee, 0x01, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x02, 0x12, 0x04, 0xef, 0x03, 0x02, 0x31, 0x1a, 0xdf, + 0x01, 0x20, 0x49, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3f, 0x0a, 0x20, 0x44, + 0x65, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2c, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x6d, 0x69, 0x74, 0x20, 0x44, + 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79, + 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x0a, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x0a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x02, 0x04, 0x12, 0x04, 0xef, 0x03, 0x02, 0x0a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x02, 0x05, 0x12, 0x04, 0xef, 0x03, 0x0b, 0x0f, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x02, 0x01, 0x12, 0x04, 0xef, 0x03, 0x10, 0x1a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0b, 0x02, 0x02, 0x03, 0x12, 0x04, 0xef, 0x03, 0x1d, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0b, 0x02, 0x02, 0x08, 0x12, 0x04, 0xef, 0x03, 0x1f, 0x30, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0b, 0x02, 0x02, 0x07, 0x12, 0x04, 0xef, 0x03, 0x2a, 0x2f, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x0b, + 0x09, 0x12, 0x04, 0xf1, 0x03, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x09, 0x00, 0x12, + 0x04, 0xf1, 0x03, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x09, 0x00, 0x01, 0x12, 0x04, + 0xf1, 0x03, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x09, 0x00, 0x02, 0x12, 0x04, 0xf1, + 0x03, 0x0b, 0x0c, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x09, 0x01, 0x12, 0x04, 0xf1, 0x03, 0x0e, + 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x09, 0x01, 0x01, 0x12, 0x04, 0xf1, 0x03, 0x0e, 0x0f, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x09, 0x01, 0x02, 0x12, 0x04, 0xf1, 0x03, 0x0e, 0x0f, 0x0a, + 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x09, 0x02, 0x12, 0x04, 0xf1, 0x03, 0x11, 0x12, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0b, 0x09, 0x02, 0x01, 0x12, 0x04, 0xf1, 0x03, 0x11, 0x12, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0b, 0x09, 0x02, 0x02, 0x12, 0x04, 0xf1, 0x03, 0x11, 0x12, 0x0a, 0xa0, 0x06, 0x0a, 0x04, + 0x04, 0x0b, 0x02, 0x03, 0x12, 0x04, 0x88, 0x04, 0x02, 0x1e, 0x1a, 0x91, 0x06, 0x20, 0x57, 0x68, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, + 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x6d, 0x61, 0x70, 0x20, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x6d, 0x61, 0x70, 0x73, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x2e, 0x0a, 0x0a, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x6d, 0x61, 0x70, 0x73, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x73, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61, 0x70, 0x3c, + 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x2c, 0x20, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, 0x79, + 0x70, 0x65, 0x3e, 0x20, 0x6d, 0x61, 0x70, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x3d, 0x20, + 0x31, 0x3b, 0x0a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x64, 0x20, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x73, 0x20, + 0x6c, 0x69, 0x6b, 0x65, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x6d, 0x61, 0x70, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x20, 0x3d, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x4b, 0x65, 0x79, 0x54, 0x79, 0x70, 0x65, 0x20, 0x6b, + 0x65, 0x79, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x3d, 0x20, 0x32, 0x3b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x20, 0x4d, 0x61, 0x70, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x20, 0x6d, 0x61, 0x70, 0x5f, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x3d, 0x20, 0x31, 0x3b, + 0x0a, 0x0a, 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x63, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x20, 0x6e, 0x6f, + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6d, 0x61, 0x70, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x3d, 0x74, 0x72, 0x75, 0x65, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x0a, 0x20, 0x75, + 0x73, 0x65, 0x20, 0x61, 0x20, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x20, 0x6d, 0x61, 0x70, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x6c, 0x61, + 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x68, 0x6f, 0x6c, 0x64, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x73, 0x2e, 0x0a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x72, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x41, 0x50, 0x49, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x73, 0x75, 0x63, 0x68, + 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x73, 0x74, 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x77, + 0x6f, 0x72, 0x6b, 0x20, 0x61, 0x73, 0x0a, 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x2e, 0x0a, 0x0a, 0x20, 0x4e, 0x4f, 0x54, 0x45, 0x3a, 0x20, 0x44, 0x6f, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x73, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x69, 0x6e, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, + 0x20, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x6d, 0x61, 0x70, 0x73, 0x20, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x0a, 0x20, 0x69, 0x6e, 0x73, + 0x74, 0x65, 0x61, 0x64, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, + 0x69, 0x6d, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x6c, 0x79, 0x20, 0x73, 0x65, 0x74, 0x20, 0x62, + 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x63, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x72, 0x0a, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x2e, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x03, 0x04, 0x12, 0x04, 0x88, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0b, 0x02, 0x03, 0x05, 0x12, 0x04, 0x88, 0x04, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0b, 0x02, 0x03, 0x01, 0x12, 0x04, 0x88, 0x04, 0x10, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0b, 0x02, 0x03, 0x03, 0x12, 0x04, 0x88, 0x04, 0x1c, 0x1d, 0x0a, 0x24, 0x0a, 0x03, 0x04, 0x0b, + 0x09, 0x12, 0x04, 0x8a, 0x04, 0x02, 0x0d, 0x22, 0x17, 0x20, 0x6a, 0x61, 0x76, 0x61, 0x6c, 0x69, + 0x74, 0x65, 0x5f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x62, 0x6c, 0x65, 0x0a, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x09, 0x03, 0x12, 0x04, 0x8a, 0x04, 0x0b, 0x0c, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0b, 0x09, 0x03, 0x01, 0x12, 0x04, 0x8a, 0x04, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0b, 0x09, 0x03, 0x02, 0x12, 0x04, 0x8a, 0x04, 0x0b, 0x0c, 0x0a, 0x1f, 0x0a, 0x03, + 0x04, 0x0b, 0x09, 0x12, 0x04, 0x8b, 0x04, 0x02, 0x0d, 0x22, 0x12, 0x20, 0x6a, 0x61, 0x76, 0x61, + 0x6e, 0x61, 0x6e, 0x6f, 0x5f, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x74, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x0b, 0x09, 0x04, 0x12, 0x04, 0x8b, 0x04, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0b, 0x09, 0x04, 0x01, 0x12, 0x04, 0x8b, 0x04, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, + 0x09, 0x04, 0x02, 0x12, 0x04, 0x8b, 0x04, 0x0b, 0x0c, 0x0a, 0x4f, 0x0a, 0x04, 0x04, 0x0b, 0x02, + 0x04, 0x12, 0x04, 0x8f, 0x04, 0x02, 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x72, + 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x20, 0x53, + 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, + 0x02, 0x04, 0x04, 0x12, 0x04, 0x8f, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, + 0x04, 0x06, 0x12, 0x04, 0x8f, 0x04, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, + 0x01, 0x12, 0x04, 0x8f, 0x04, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, 0x03, + 0x12, 0x04, 0x8f, 0x04, 0x36, 0x39, 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x0b, 0x05, 0x12, 0x04, 0x92, + 0x04, 0x02, 0x19, 0x1a, 0x4d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, + 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x05, 0x00, 0x12, 0x04, 0x92, 0x04, 0x0d, 0x18, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x05, 0x00, 0x01, 0x12, 0x04, 0x92, 0x04, 0x0d, 0x11, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x05, 0x00, 0x02, 0x12, 0x04, 0x92, 0x04, 0x15, 0x18, 0x0a, 0x0c, + 0x0a, 0x02, 0x04, 0x0c, 0x12, 0x06, 0x95, 0x04, 0x00, 0xfb, 0x04, 0x01, 0x0a, 0x0b, 0x0a, 0x03, + 0x04, 0x0c, 0x01, 0x12, 0x04, 0x95, 0x04, 0x08, 0x14, 0x0a, 0xa3, 0x02, 0x0a, 0x04, 0x04, 0x0c, + 0x02, 0x00, 0x12, 0x04, 0x9a, 0x04, 0x02, 0x2e, 0x1a, 0x94, 0x02, 0x20, 0x54, 0x68, 0x65, 0x20, + 0x63, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x73, + 0x74, 0x72, 0x75, 0x63, 0x74, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x43, 0x2b, 0x2b, 0x20, 0x63, + 0x6f, 0x64, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x20, 0x74, 0x6f, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x61, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, + 0x0a, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x74, 0x68, + 0x61, 0x6e, 0x20, 0x69, 0x74, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x77, + 0x6f, 0x75, 0x6c, 0x64, 0x2e, 0x20, 0x20, 0x53, 0x65, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x0a, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x62, 0x65, 0x6c, 0x6f, 0x77, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x79, 0x65, 0x74, 0x20, + 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x6f, 0x70, 0x65, 0x6e, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x0a, 0x20, + 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x2d, 0x2d, 0x20, 0x73, 0x6f, 0x72, 0x72, 0x79, + 0x2c, 0x20, 0x77, 0x65, 0x27, 0x6c, 0x6c, 0x20, 0x74, 0x72, 0x79, 0x20, 0x74, 0x6f, 0x20, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x20, 0x69, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x66, + 0x75, 0x74, 0x75, 0x72, 0x65, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x21, 0x0a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x00, 0x04, 0x12, 0x04, 0x9a, 0x04, 0x02, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x00, 0x06, 0x12, 0x04, 0x9a, 0x04, 0x0b, 0x10, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0c, 0x02, 0x00, 0x01, 0x12, 0x04, 0x9a, 0x04, 0x11, 0x16, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0c, 0x02, 0x00, 0x03, 0x12, 0x04, 0x9a, 0x04, 0x19, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x00, 0x08, 0x12, 0x04, 0x9a, 0x04, 0x1b, 0x2d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, + 0x02, 0x00, 0x07, 0x12, 0x04, 0x9a, 0x04, 0x26, 0x2c, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x0c, 0x04, + 0x00, 0x12, 0x06, 0x9b, 0x04, 0x02, 0xa2, 0x04, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x04, + 0x00, 0x01, 0x12, 0x04, 0x9b, 0x04, 0x07, 0x0c, 0x0a, 0x1f, 0x0a, 0x06, 0x04, 0x0c, 0x04, 0x00, + 0x02, 0x00, 0x12, 0x04, 0x9d, 0x04, 0x04, 0x0f, 0x1a, 0x0f, 0x20, 0x44, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, + 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0x9d, 0x04, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, + 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x04, 0x9d, 0x04, 0x0d, 0x0e, 0x0a, 0x0e, 0x0a, 0x06, 0x04, + 0x0c, 0x04, 0x00, 0x02, 0x01, 0x12, 0x04, 0x9f, 0x04, 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x0c, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x04, 0x9f, 0x04, 0x04, 0x08, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x0c, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x04, 0x9f, 0x04, 0x0b, 0x0c, 0x0a, 0x0e, 0x0a, + 0x06, 0x04, 0x0c, 0x04, 0x00, 0x02, 0x02, 0x12, 0x04, 0xa1, 0x04, 0x04, 0x15, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x0c, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x04, 0xa1, 0x04, 0x04, 0x10, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x04, 0xa1, 0x04, 0x13, 0x14, 0x0a, + 0xda, 0x02, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x01, 0x12, 0x04, 0xa8, 0x04, 0x02, 0x1b, 0x1a, 0xcb, + 0x02, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x20, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x0a, 0x20, 0x61, 0x20, 0x6d, 0x6f, + 0x72, 0x65, 0x20, 0x65, 0x66, 0x66, 0x69, 0x63, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x72, 0x65, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x77, 0x69, 0x72, 0x65, 0x2e, 0x20, 0x52, 0x61, 0x74, 0x68, 0x65, 0x72, 0x20, + 0x74, 0x68, 0x61, 0x6e, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x6c, 0x79, 0x0a, + 0x20, 0x77, 0x72, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x61, 0x67, + 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x61, + 0x63, 0x68, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x20, 0x61, 0x72, 0x72, 0x61, 0x79, 0x20, 0x69, 0x73, 0x20, + 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x61, 0x73, 0x0a, 0x20, 0x61, 0x20, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x2d, 0x64, 0x65, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x65, 0x64, 0x20, 0x62, 0x6c, 0x6f, 0x62, 0x2e, 0x20, 0x49, 0x6e, 0x20, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x2c, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x65, 0x78, 0x70, 0x6c, + 0x69, 0x63, 0x69, 0x74, 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x20, + 0x74, 0x6f, 0x0a, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, + 0x76, 0x6f, 0x69, 0x64, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x65, + 0x64, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0c, 0x02, 0x01, 0x04, 0x12, 0x04, 0xa8, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x01, 0x05, 0x12, 0x04, 0xa8, 0x04, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, + 0x02, 0x01, 0x01, 0x12, 0x04, 0xa8, 0x04, 0x10, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x01, 0x03, 0x12, 0x04, 0xa8, 0x04, 0x19, 0x1a, 0x0a, 0x9a, 0x05, 0x0a, 0x04, 0x04, 0x0c, 0x02, + 0x02, 0x12, 0x04, 0xb5, 0x04, 0x02, 0x33, 0x1a, 0x8b, 0x05, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6a, + 0x73, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x74, + 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x4a, 0x61, 0x76, 0x61, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x0a, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, + 0x74, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x36, 0x34, 0x20, + 0x62, 0x69, 0x74, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x6c, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x0a, 0x20, 0x28, 0x69, + 0x6e, 0x74, 0x36, 0x34, 0x2c, 0x20, 0x75, 0x69, 0x6e, 0x74, 0x36, 0x34, 0x2c, 0x20, 0x73, 0x69, + 0x6e, 0x74, 0x36, 0x34, 0x2c, 0x20, 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x2c, 0x20, 0x73, + 0x66, 0x69, 0x78, 0x65, 0x64, 0x36, 0x34, 0x29, 0x2e, 0x20, 0x20, 0x41, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6a, 0x73, 0x74, 0x79, 0x70, 0x65, 0x20, 0x4a, + 0x53, 0x5f, 0x53, 0x54, 0x52, 0x49, 0x4e, 0x47, 0x0a, 0x20, 0x69, 0x73, 0x20, 0x72, 0x65, 0x70, + 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x4a, 0x61, 0x76, 0x61, + 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x2c, 0x20, 0x77, + 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x73, 0x20, 0x6c, 0x6f, 0x73, 0x73, + 0x20, 0x6f, 0x66, 0x20, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x0a, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x68, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x20, 0x77, + 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x6c, 0x61, 0x72, 0x67, 0x65, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x20, 0x69, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x20, 0x74, + 0x6f, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x2e, 0x0a, 0x20, + 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x4a, 0x53, 0x5f, 0x4e, 0x55, + 0x4d, 0x42, 0x45, 0x52, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6a, 0x73, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x63, 0x61, 0x75, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x75, 0x73, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, + 0x22, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x22, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x20, 0x20, + 0x54, 0x68, 0x65, 0x20, 0x62, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x0a, 0x20, 0x4a, 0x53, 0x5f, 0x4e, 0x4f, 0x52, 0x4d, 0x41, 0x4c, 0x20, 0x69, 0x73, + 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, 0x20, 0x54, 0x68, 0x69, + 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x65, + 0x6e, 0x75, 0x6d, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x20, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, 0x20, 0x74, + 0x6f, 0x20, 0x62, 0x65, 0x20, 0x61, 0x64, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x65, 0x2e, 0x67, 0x2e, + 0x0a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x49, 0x6e, 0x74, 0x65, + 0x67, 0x65, 0x72, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x04, 0x12, 0x04, + 0xb5, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x06, 0x12, 0x04, 0xb5, + 0x04, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x01, 0x12, 0x04, 0xb5, 0x04, + 0x12, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x03, 0x12, 0x04, 0xb5, 0x04, 0x1b, + 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x08, 0x12, 0x04, 0xb5, 0x04, 0x1d, 0x32, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x07, 0x12, 0x04, 0xb5, 0x04, 0x28, 0x31, 0x0a, + 0x0e, 0x0a, 0x04, 0x04, 0x0c, 0x04, 0x01, 0x12, 0x06, 0xb6, 0x04, 0x02, 0xbf, 0x04, 0x03, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x04, 0x01, 0x01, 0x12, 0x04, 0xb6, 0x04, 0x07, 0x0d, 0x0a, 0x27, + 0x0a, 0x06, 0x04, 0x0c, 0x04, 0x01, 0x02, 0x00, 0x12, 0x04, 0xb8, 0x04, 0x04, 0x12, 0x1a, 0x17, + 0x20, 0x55, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x01, 0x02, + 0x00, 0x01, 0x12, 0x04, 0xb8, 0x04, 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x01, + 0x02, 0x00, 0x02, 0x12, 0x04, 0xb8, 0x04, 0x10, 0x11, 0x0a, 0x29, 0x0a, 0x06, 0x04, 0x0c, 0x04, + 0x01, 0x02, 0x01, 0x12, 0x04, 0xbb, 0x04, 0x04, 0x12, 0x1a, 0x19, 0x20, 0x55, 0x73, 0x65, 0x20, + 0x4a, 0x61, 0x76, 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x73, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, + 0x04, 0xbb, 0x04, 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x01, 0x02, 0x01, 0x02, + 0x12, 0x04, 0xbb, 0x04, 0x10, 0x11, 0x0a, 0x29, 0x0a, 0x06, 0x04, 0x0c, 0x04, 0x01, 0x02, 0x02, + 0x12, 0x04, 0xbe, 0x04, 0x04, 0x12, 0x1a, 0x19, 0x20, 0x55, 0x73, 0x65, 0x20, 0x4a, 0x61, 0x76, + 0x61, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x2e, + 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x04, 0xbe, 0x04, + 0x04, 0x0d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0c, 0x04, 0x01, 0x02, 0x02, 0x02, 0x12, 0x04, 0xbe, + 0x04, 0x10, 0x11, 0x0a, 0xd6, 0x0e, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x03, 0x12, 0x04, 0xe3, 0x04, + 0x02, 0x2b, 0x1a, 0xc7, 0x0e, 0x20, 0x53, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x64, 0x20, 0x6c, 0x61, 0x7a, 0x69, 0x6c, 0x79, 0x3f, 0x20, 0x20, 0x4c, 0x61, 0x7a, 0x79, 0x20, + 0x61, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x74, 0x6f, 0x20, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x0a, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x20, 0x20, 0x49, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x6e, 0x73, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x75, + 0x74, 0x65, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, 0x73, 0x20, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x64, 0x2c, + 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x27, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x77, + 0x69, 0x6c, 0x6c, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x62, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x64, 0x20, 0x62, 0x75, 0x74, 0x20, 0x69, 0x6e, 0x73, 0x74, 0x65, 0x61, 0x64, 0x20, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x0a, + 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x6e, 0x65, + 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, + 0x63, 0x74, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x64, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x2e, 0x0a, 0x0a, 0x20, 0x54, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x61, 0x20, 0x68, 0x69, + 0x6e, 0x74, 0x2e, 0x20, 0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x66, 0x72, 0x65, 0x65, 0x20, 0x74, 0x6f, + 0x20, 0x63, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, + 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x0a, 0x20, 0x65, 0x61, 0x67, 0x65, 0x72, 0x20, 0x6f, 0x72, + 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x72, 0x65, + 0x67, 0x61, 0x72, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x20, 0x48, 0x6f, 0x77, 0x65, 0x76, 0x65, 0x72, 0x2c, 0x0a, + 0x20, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x73, 0x75, 0x67, 0x67, 0x65, 0x73, + 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x20, 0x62, 0x65, 0x6c, 0x69, + 0x65, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x0a, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, + 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x77, + 0x6f, 0x72, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, + 0x6e, 0x61, 0x6c, 0x20, 0x62, 0x6f, 0x6f, 0x6b, 0x6b, 0x65, 0x65, 0x70, 0x69, 0x6e, 0x67, 0x0a, + 0x20, 0x6f, 0x76, 0x65, 0x72, 0x68, 0x65, 0x61, 0x64, 0x20, 0x74, 0x79, 0x70, 0x69, 0x63, 0x61, + 0x6c, 0x6c, 0x79, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6d, + 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x74, 0x2e, 0x0a, 0x0a, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x75, + 0x62, 0x6c, 0x69, 0x63, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x20, 0x6f, + 0x66, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x63, 0x6f, 0x64, 0x65, 0x3b, 0x0a, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x20, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, 0x65, 0x73, 0x20, 0x72, 0x65, 0x6d, + 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x2e, 0x20, 0x20, 0x46, + 0x75, 0x72, 0x74, 0x68, 0x65, 0x72, 0x6d, 0x6f, 0x72, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x72, 0x65, + 0x61, 0x64, 0x2d, 0x73, 0x61, 0x66, 0x65, 0x74, 0x79, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x0a, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x61, 0x66, 0x66, 0x65, 0x63, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x3b, 0x20, 0x63, 0x6f, 0x6e, 0x73, + 0x74, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x20, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, + 0x20, 0x73, 0x61, 0x66, 0x65, 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x63, 0x61, 0x6c, 0x6c, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x74, 0x68, 0x72, + 0x65, 0x61, 0x64, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, + 0x79, 0x2c, 0x20, 0x77, 0x68, 0x69, 0x6c, 0x65, 0x20, 0x6e, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, + 0x73, 0x74, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x69, + 0x6e, 0x75, 0x65, 0x0a, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x20, + 0x65, 0x78, 0x63, 0x6c, 0x75, 0x73, 0x69, 0x76, 0x65, 0x20, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, + 0x2e, 0x0a, 0x0a, 0x0a, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, + 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6d, + 0x61, 0x79, 0x20, 0x63, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x74, 0x6f, + 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x69, 0x6e, 0x0a, 0x20, 0x61, + 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x73, 0x75, 0x62, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x61, 0x74, 0x20, 0x69, 0x73, 0x2c, 0x20, 0x63, 0x61, 0x6c, + 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x49, 0x73, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x69, 0x7a, + 0x65, 0x64, 0x28, 0x29, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x75, 0x74, 0x65, + 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x0a, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x72, + 0x65, 0x74, 0x75, 0x72, 0x6e, 0x20, 0x74, 0x72, 0x75, 0x65, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, + 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x6e, 0x65, 0x72, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, + 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, + 0x2e, 0x0a, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x65, 0x63, 0x65, 0x73, + 0x73, 0x61, 0x72, 0x79, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x6f, 0x74, 0x68, + 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x6e, 0x6e, 0x65, 0x72, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x77, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x68, + 0x61, 0x76, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, 0x0a, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, + 0x64, 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x70, 0x65, + 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x2c, + 0x20, 0x64, 0x65, 0x66, 0x65, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, + 0x75, 0x72, 0x70, 0x6f, 0x73, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x0a, 0x20, + 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x20, 0x20, 0x41, 0x6e, 0x20, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x68, 0x69, 0x63, + 0x68, 0x20, 0x63, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x74, 0x6f, + 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x0a, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, + 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, + 0x20, 0x69, 0x74, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x61, 0x74, 0x20, 0x69, 0x73, 0x2c, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x75, 0x6c, 0x61, + 0x72, 0x20, 0x73, 0x75, 0x62, 0x2d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x74, + 0x68, 0x65, 0x0a, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x2a, + 0x61, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x2a, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x69, 0x74, + 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x2a, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x2a, 0x0a, 0x20, 0x63, + 0x68, 0x65, 0x63, 0x6b, 0x20, 0x69, 0x74, 0x73, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, + 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2c, 0x20, 0x72, 0x65, 0x67, 0x61, 0x72, 0x64, + 0x6c, 0x65, 0x73, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x77, 0x68, 0x65, 0x74, 0x68, 0x65, 0x72, 0x20, + 0x6f, 0x72, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x68, 0x61, 0x73, 0x0a, 0x20, 0x62, 0x65, 0x65, 0x6e, 0x20, 0x70, 0x61, 0x72, + 0x73, 0x65, 0x64, 0x2e, 0x0a, 0x0a, 0x20, 0x41, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x32, 0x30, 0x32, + 0x31, 0x2c, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x20, + 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x63, 0x68, 0x65, 0x63, + 0x6b, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x20, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x70, 0x61, + 0x72, 0x73, 0x69, 0x6e, 0x67, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x61, 0x79, + 0x20, 0x6c, 0x65, 0x61, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x72, 0x61, 0x73, 0x68, 0x65, 0x73, + 0x20, 0x69, 0x66, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x61, 0x6e, 0x20, + 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x20, 0x69, 0x73, 0x0a, 0x20, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x20, + 0x70, 0x61, 0x72, 0x73, 0x65, 0x64, 0x20, 0x75, 0x70, 0x6f, 0x6e, 0x20, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x2e, 0x0a, 0x0a, 0x20, 0x4e, 0x4f, 0x44, 0x4f, 0x28, 0x62, 0x2f, 0x32, 0x31, 0x31, + 0x39, 0x30, 0x36, 0x31, 0x31, 0x33, 0x29, 0x3a, 0x20, 0x20, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, + 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x20, 0x6c, + 0x61, 0x7a, 0x79, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0c, 0x02, 0x03, 0x04, 0x12, 0x04, 0xe3, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x03, 0x05, 0x12, 0x04, 0xe3, 0x04, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, + 0x02, 0x03, 0x01, 0x12, 0x04, 0xe3, 0x04, 0x10, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x03, 0x03, 0x12, 0x04, 0xe3, 0x04, 0x17, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x03, + 0x08, 0x12, 0x04, 0xe3, 0x04, 0x19, 0x2a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x03, 0x07, + 0x12, 0x04, 0xe3, 0x04, 0x24, 0x29, 0x0a, 0xaf, 0x01, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x04, 0x12, + 0x04, 0xe8, 0x04, 0x02, 0x37, 0x1a, 0xa0, 0x01, 0x20, 0x75, 0x6e, 0x76, 0x65, 0x72, 0x69, 0x66, + 0x69, 0x65, 0x64, 0x5f, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, + 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x63, 0x74, 0x6e, 0x65, 0x73, 0x73, 0x20, 0x63, 0x68, 0x65, + 0x63, 0x6b, 0x73, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x0a, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, + 0x64, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6c, 0x61, 0x7a, 0x79, 0x20, 0x77, 0x69, 0x74, + 0x68, 0x20, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, + 0x73, 0x20, 0x70, 0x72, 0x6f, 0x68, 0x69, 0x62, 0x69, 0x74, 0x69, 0x76, 0x65, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x70, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, 0x63, 0x65, 0x0a, 0x20, 0x72, + 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, + 0x04, 0x12, 0x04, 0xe8, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, 0x05, + 0x12, 0x04, 0xe8, 0x04, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, 0x01, 0x12, + 0x04, 0xe8, 0x04, 0x10, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, 0x03, 0x12, 0x04, + 0xe8, 0x04, 0x22, 0x24, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, 0x08, 0x12, 0x04, 0xe8, + 0x04, 0x25, 0x36, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x04, 0x07, 0x12, 0x04, 0xe8, 0x04, + 0x30, 0x35, 0x0a, 0xe8, 0x01, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x05, 0x12, 0x04, 0xee, 0x04, 0x02, + 0x31, 0x1a, 0xd9, 0x01, 0x20, 0x49, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3f, 0x0a, 0x20, + 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, + 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x6d, 0x69, 0x74, 0x20, + 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x6e, 0x6f, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x6f, 0x72, 0x73, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79, 0x20, + 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x0a, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, + 0x74, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0c, 0x02, 0x05, 0x04, 0x12, 0x04, 0xee, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0c, 0x02, 0x05, 0x05, 0x12, 0x04, 0xee, 0x04, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x05, 0x01, 0x12, 0x04, 0xee, 0x04, 0x10, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, + 0x02, 0x05, 0x03, 0x12, 0x04, 0xee, 0x04, 0x1d, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x05, 0x08, 0x12, 0x04, 0xee, 0x04, 0x1f, 0x30, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x05, + 0x07, 0x12, 0x04, 0xee, 0x04, 0x2a, 0x2f, 0x0a, 0x3f, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x06, 0x12, + 0x04, 0xf1, 0x04, 0x02, 0x2c, 0x1a, 0x31, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x6d, 0x69, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x2e, 0x20, 0x44, 0x6f, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x75, 0x73, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, + 0x04, 0x12, 0x04, 0xf1, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, 0x05, + 0x12, 0x04, 0xf1, 0x04, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, 0x01, 0x12, + 0x04, 0xf1, 0x04, 0x10, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, 0x03, 0x12, 0x04, + 0xf1, 0x04, 0x17, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, 0x08, 0x12, 0x04, 0xf1, + 0x04, 0x1a, 0x2b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x06, 0x07, 0x12, 0x04, 0xf1, 0x04, + 0x25, 0x2a, 0x0a, 0x4f, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x07, 0x12, 0x04, 0xf5, 0x04, 0x02, 0x3a, + 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, + 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, + 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, + 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, + 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x07, 0x04, 0x12, 0x04, 0xf5, 0x04, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x07, 0x06, 0x12, 0x04, 0xf5, 0x04, 0x0b, + 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x07, 0x01, 0x12, 0x04, 0xf5, 0x04, 0x1f, 0x33, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x07, 0x03, 0x12, 0x04, 0xf5, 0x04, 0x36, 0x39, 0x0a, + 0x5a, 0x0a, 0x03, 0x04, 0x0c, 0x05, 0x12, 0x04, 0xf8, 0x04, 0x02, 0x19, 0x1a, 0x4d, 0x20, 0x43, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, + 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, + 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, + 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, + 0x0c, 0x05, 0x00, 0x12, 0x04, 0xf8, 0x04, 0x0d, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x05, + 0x00, 0x01, 0x12, 0x04, 0xf8, 0x04, 0x0d, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x05, 0x00, + 0x02, 0x12, 0x04, 0xf8, 0x04, 0x15, 0x18, 0x0a, 0x1c, 0x0a, 0x03, 0x04, 0x0c, 0x09, 0x12, 0x04, + 0xfa, 0x04, 0x02, 0x0d, 0x22, 0x0f, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x20, 0x6a, + 0x74, 0x79, 0x70, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0c, 0x09, 0x00, 0x12, 0x04, 0xfa, + 0x04, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x09, 0x00, 0x01, 0x12, 0x04, 0xfa, 0x04, + 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x09, 0x00, 0x02, 0x12, 0x04, 0xfa, 0x04, 0x0b, + 0x0c, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0d, 0x12, 0x06, 0xfd, 0x04, 0x00, 0x83, 0x05, 0x01, 0x0a, + 0x0b, 0x0a, 0x03, 0x04, 0x0d, 0x01, 0x12, 0x04, 0xfd, 0x04, 0x08, 0x14, 0x0a, 0x4f, 0x0a, 0x04, + 0x04, 0x0d, 0x02, 0x00, 0x12, 0x04, 0xff, 0x04, 0x02, 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, + 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, + 0x74, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, + 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0d, 0x02, 0x00, 0x04, 0x12, 0x04, 0xff, 0x04, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0d, 0x02, 0x00, 0x06, 0x12, 0x04, 0xff, 0x04, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0d, 0x02, 0x00, 0x01, 0x12, 0x04, 0xff, 0x04, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0d, + 0x02, 0x00, 0x03, 0x12, 0x04, 0xff, 0x04, 0x36, 0x39, 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x0d, 0x05, + 0x12, 0x04, 0x82, 0x05, 0x02, 0x19, 0x1a, 0x4d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, + 0x6f, 0x6d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, + 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0d, 0x05, 0x00, 0x12, 0x04, 0x82, + 0x05, 0x0d, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0d, 0x05, 0x00, 0x01, 0x12, 0x04, 0x82, 0x05, + 0x0d, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0d, 0x05, 0x00, 0x02, 0x12, 0x04, 0x82, 0x05, 0x15, + 0x18, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0e, 0x12, 0x06, 0x85, 0x05, 0x00, 0x98, 0x05, 0x01, 0x0a, + 0x0b, 0x0a, 0x03, 0x04, 0x0e, 0x01, 0x12, 0x04, 0x85, 0x05, 0x08, 0x13, 0x0a, 0x60, 0x0a, 0x04, + 0x04, 0x0e, 0x02, 0x00, 0x12, 0x04, 0x89, 0x05, 0x02, 0x20, 0x1a, 0x52, 0x20, 0x53, 0x65, 0x74, + 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, + 0x74, 0x72, 0x75, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x20, 0x6d, 0x61, + 0x70, 0x70, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, + 0x74, 0x61, 0x67, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x73, 0x61, 0x6d, 0x65, 0x0a, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x00, 0x04, 0x12, 0x04, 0x89, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0e, 0x02, 0x00, 0x05, 0x12, 0x04, 0x89, 0x05, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0e, 0x02, 0x00, 0x01, 0x12, 0x04, 0x89, 0x05, 0x10, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0e, 0x02, 0x00, 0x03, 0x12, 0x04, 0x89, 0x05, 0x1e, 0x1f, 0x0a, 0xe5, 0x01, 0x0a, 0x04, 0x04, + 0x0e, 0x02, 0x01, 0x12, 0x04, 0x8f, 0x05, 0x02, 0x31, 0x1a, 0xd6, 0x01, 0x20, 0x49, 0x73, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x3f, 0x0a, 0x20, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, + 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x70, + 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x65, 0x6d, 0x69, 0x74, 0x20, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, + 0x64, 0x20, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x2c, 0x20, 0x6f, 0x72, 0x20, + 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, + 0x65, 0x74, 0x65, 0x6c, 0x79, 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, + 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x0a, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, + 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, + 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x73, + 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x04, 0x12, 0x04, 0x8f, 0x05, 0x02, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x05, 0x12, 0x04, 0x8f, 0x05, 0x0b, 0x0f, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x01, 0x12, 0x04, 0x8f, 0x05, 0x10, 0x1a, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x03, 0x12, 0x04, 0x8f, 0x05, 0x1d, 0x1e, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x01, 0x08, 0x12, 0x04, 0x8f, 0x05, 0x1f, 0x30, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0e, 0x02, 0x01, 0x07, 0x12, 0x04, 0x8f, 0x05, 0x2a, 0x2f, 0x0a, 0x1f, 0x0a, 0x03, + 0x04, 0x0e, 0x09, 0x12, 0x04, 0x91, 0x05, 0x02, 0x0d, 0x22, 0x12, 0x20, 0x6a, 0x61, 0x76, 0x61, + 0x6e, 0x61, 0x6e, 0x6f, 0x5f, 0x61, 0x73, 0x5f, 0x6c, 0x69, 0x74, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x0e, 0x09, 0x00, 0x12, 0x04, 0x91, 0x05, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0e, 0x09, 0x00, 0x01, 0x12, 0x04, 0x91, 0x05, 0x0b, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, + 0x09, 0x00, 0x02, 0x12, 0x04, 0x91, 0x05, 0x0b, 0x0c, 0x0a, 0x4f, 0x0a, 0x04, 0x04, 0x0e, 0x02, + 0x02, 0x12, 0x04, 0x94, 0x05, 0x02, 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, + 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x72, + 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x20, 0x53, + 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, + 0x02, 0x02, 0x04, 0x12, 0x04, 0x94, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, + 0x02, 0x06, 0x12, 0x04, 0x94, 0x05, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x02, + 0x01, 0x12, 0x04, 0x94, 0x05, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x02, 0x02, 0x03, + 0x12, 0x04, 0x94, 0x05, 0x36, 0x39, 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x0e, 0x05, 0x12, 0x04, 0x97, + 0x05, 0x02, 0x19, 0x1a, 0x4d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x63, 0x61, + 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, + 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0e, 0x05, 0x00, 0x12, 0x04, 0x97, 0x05, 0x0d, 0x18, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x05, 0x00, 0x01, 0x12, 0x04, 0x97, 0x05, 0x0d, 0x11, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0e, 0x05, 0x00, 0x02, 0x12, 0x04, 0x97, 0x05, 0x15, 0x18, 0x0a, 0x0c, + 0x0a, 0x02, 0x04, 0x0f, 0x12, 0x06, 0x9a, 0x05, 0x00, 0xa6, 0x05, 0x01, 0x0a, 0x0b, 0x0a, 0x03, + 0x04, 0x0f, 0x01, 0x12, 0x04, 0x9a, 0x05, 0x08, 0x18, 0x0a, 0xf7, 0x01, 0x0a, 0x04, 0x04, 0x0f, + 0x02, 0x00, 0x12, 0x04, 0x9f, 0x05, 0x02, 0x31, 0x1a, 0xe8, 0x01, 0x20, 0x49, 0x73, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x64, + 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3f, 0x0a, 0x20, 0x44, 0x65, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x20, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2c, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x6d, 0x69, 0x74, 0x20, 0x44, 0x65, 0x70, 0x72, + 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x75, 0x6d, + 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79, + 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x0a, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, + 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x04, 0x12, 0x04, 0x9f, 0x05, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x05, 0x12, 0x04, 0x9f, 0x05, 0x0b, + 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x01, 0x12, 0x04, 0x9f, 0x05, 0x10, 0x1a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x03, 0x12, 0x04, 0x9f, 0x05, 0x1d, 0x1e, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x08, 0x12, 0x04, 0x9f, 0x05, 0x1f, 0x30, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x00, 0x07, 0x12, 0x04, 0x9f, 0x05, 0x2a, 0x2f, 0x0a, 0x4f, 0x0a, + 0x04, 0x04, 0x0f, 0x02, 0x01, 0x12, 0x04, 0xa2, 0x05, 0x02, 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, + 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, + 0x27, 0x74, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, + 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0f, 0x02, 0x01, 0x04, 0x12, 0x04, 0xa2, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0f, 0x02, 0x01, 0x06, 0x12, 0x04, 0xa2, 0x05, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0f, 0x02, 0x01, 0x01, 0x12, 0x04, 0xa2, 0x05, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0f, 0x02, 0x01, 0x03, 0x12, 0x04, 0xa2, 0x05, 0x36, 0x39, 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x0f, + 0x05, 0x12, 0x04, 0xa5, 0x05, 0x02, 0x19, 0x1a, 0x4d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, + 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0f, 0x05, 0x00, 0x12, 0x04, + 0xa5, 0x05, 0x0d, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x05, 0x00, 0x01, 0x12, 0x04, 0xa5, + 0x05, 0x0d, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0f, 0x05, 0x00, 0x02, 0x12, 0x04, 0xa5, 0x05, + 0x15, 0x18, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x10, 0x12, 0x06, 0xa8, 0x05, 0x00, 0xba, 0x05, 0x01, + 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x10, 0x01, 0x12, 0x04, 0xa8, 0x05, 0x08, 0x16, 0x0a, 0xd9, 0x03, + 0x0a, 0x04, 0x04, 0x10, 0x02, 0x00, 0x12, 0x04, 0xb3, 0x05, 0x02, 0x32, 0x1a, 0xdf, 0x01, 0x20, + 0x49, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, + 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3f, 0x0a, 0x20, 0x44, 0x65, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x61, + 0x72, 0x67, 0x65, 0x74, 0x20, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x2c, 0x20, 0x74, + 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x6d, 0x69, 0x74, 0x20, 0x44, 0x65, 0x70, + 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, 0x77, 0x69, 0x6c, 0x6c, + 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x6c, 0x79, 0x20, 0x69, + 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x76, + 0x65, 0x72, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x0a, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, + 0x69, 0x6e, 0x67, 0x20, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, 0x2e, 0x0a, 0x32, 0xe8, + 0x01, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x3a, 0x20, 0x20, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x31, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, + 0x20, 0x33, 0x32, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x52, 0x50, 0x43, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x72, + 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x20, 0x20, 0x57, 0x65, 0x20, 0x61, 0x70, 0x6f, + 0x6c, 0x6f, 0x67, 0x69, 0x7a, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x68, 0x6f, 0x61, 0x72, 0x64, + 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, + 0x73, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x6c, 0x76, 0x65, 0x73, 0x2c, 0x20, + 0x62, 0x75, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x65, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x61, + 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, + 0x6d, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x77, 0x65, + 0x20, 0x64, 0x65, 0x63, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x6c, 0x65, + 0x61, 0x73, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x0a, 0x20, 0x20, 0x20, + 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, + 0x00, 0x04, 0x12, 0x04, 0xb3, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, + 0x05, 0x12, 0x04, 0xb3, 0x05, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x01, + 0x12, 0x04, 0xb3, 0x05, 0x10, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x03, 0x12, + 0x04, 0xb3, 0x05, 0x1d, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x08, 0x12, 0x04, + 0xb3, 0x05, 0x20, 0x31, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x00, 0x07, 0x12, 0x04, 0xb3, + 0x05, 0x2b, 0x30, 0x0a, 0x4f, 0x0a, 0x04, 0x04, 0x10, 0x02, 0x01, 0x12, 0x04, 0xb6, 0x05, 0x02, + 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x74, + 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, + 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, + 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x01, 0x04, 0x12, 0x04, 0xb6, + 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x01, 0x06, 0x12, 0x04, 0xb6, 0x05, + 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x01, 0x01, 0x12, 0x04, 0xb6, 0x05, 0x1f, + 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x02, 0x01, 0x03, 0x12, 0x04, 0xb6, 0x05, 0x36, 0x39, + 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x10, 0x05, 0x12, 0x04, 0xb9, 0x05, 0x02, 0x19, 0x1a, 0x4d, 0x20, + 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, + 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, + 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x04, + 0x04, 0x10, 0x05, 0x00, 0x12, 0x04, 0xb9, 0x05, 0x0d, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, + 0x05, 0x00, 0x01, 0x12, 0x04, 0xb9, 0x05, 0x0d, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x10, 0x05, + 0x00, 0x02, 0x12, 0x04, 0xb9, 0x05, 0x15, 0x18, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x11, 0x12, 0x06, + 0xbc, 0x05, 0x00, 0xd9, 0x05, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x11, 0x01, 0x12, 0x04, 0xbc, + 0x05, 0x08, 0x15, 0x0a, 0xd6, 0x03, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x00, 0x12, 0x04, 0xc7, 0x05, + 0x02, 0x32, 0x1a, 0xdc, 0x01, 0x20, 0x49, 0x73, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x20, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x3f, + 0x0a, 0x20, 0x44, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x20, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, + 0x72, 0x6d, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x65, 0x6d, 0x69, + 0x74, 0x20, 0x44, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x6e, + 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x74, 0x20, + 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, + 0x6c, 0x79, 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, 0x3b, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x2c, 0x0a, 0x20, + 0x74, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6c, + 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x64, 0x65, 0x70, 0x72, + 0x65, 0x63, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x2e, + 0x0a, 0x32, 0xe8, 0x01, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x3a, 0x20, 0x20, 0x46, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x31, 0x20, 0x74, 0x68, 0x72, 0x6f, + 0x75, 0x67, 0x68, 0x20, 0x33, 0x32, 0x20, 0x61, 0x72, 0x65, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, + 0x76, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x27, 0x73, + 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x20, 0x52, 0x50, 0x43, 0x0a, 0x20, 0x20, + 0x20, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x77, 0x6f, 0x72, 0x6b, 0x2e, 0x20, 0x20, 0x57, 0x65, 0x20, + 0x61, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x69, 0x7a, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x68, 0x6f, + 0x61, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x6e, 0x75, 0x6d, + 0x62, 0x65, 0x72, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x6f, 0x75, 0x72, 0x73, 0x65, 0x6c, 0x76, 0x65, + 0x73, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x77, 0x65, 0x20, 0x77, 0x65, 0x72, + 0x65, 0x20, 0x61, 0x6c, 0x72, 0x65, 0x61, 0x64, 0x79, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, + 0x74, 0x68, 0x65, 0x6d, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, + 0x20, 0x77, 0x65, 0x20, 0x64, 0x65, 0x63, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x72, + 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x0a, + 0x20, 0x20, 0x20, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x11, 0x02, 0x00, 0x04, 0x12, 0x04, 0xc7, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x11, 0x02, 0x00, 0x05, 0x12, 0x04, 0xc7, 0x05, 0x0b, 0x0f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, + 0x02, 0x00, 0x01, 0x12, 0x04, 0xc7, 0x05, 0x10, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, + 0x00, 0x03, 0x12, 0x04, 0xc7, 0x05, 0x1d, 0x1f, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, + 0x08, 0x12, 0x04, 0xc7, 0x05, 0x20, 0x31, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x00, 0x07, + 0x12, 0x04, 0xc7, 0x05, 0x2b, 0x30, 0x0a, 0xf0, 0x01, 0x0a, 0x04, 0x04, 0x11, 0x04, 0x00, 0x12, + 0x06, 0xcc, 0x05, 0x02, 0xd0, 0x05, 0x03, 0x1a, 0xdf, 0x01, 0x20, 0x49, 0x73, 0x20, 0x74, 0x68, + 0x69, 0x73, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x20, 0x73, 0x69, 0x64, 0x65, 0x2d, 0x65, + 0x66, 0x66, 0x65, 0x63, 0x74, 0x2d, 0x66, 0x72, 0x65, 0x65, 0x20, 0x28, 0x6f, 0x72, 0x20, 0x73, + 0x61, 0x66, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x48, 0x54, 0x54, 0x50, 0x20, 0x70, 0x61, 0x72, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x29, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, + 0x74, 0x65, 0x6e, 0x74, 0x2c, 0x0a, 0x20, 0x6f, 0x72, 0x20, 0x6e, 0x65, 0x69, 0x74, 0x68, 0x65, + 0x72, 0x3f, 0x20, 0x48, 0x54, 0x54, 0x50, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x52, 0x50, + 0x43, 0x20, 0x69, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x6d, 0x61, 0x79, 0x20, 0x63, 0x68, 0x6f, 0x6f, 0x73, 0x65, 0x20, 0x47, 0x45, 0x54, 0x20, + 0x76, 0x65, 0x72, 0x62, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x73, 0x61, 0x66, 0x65, 0x0a, 0x20, 0x6d, + 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x50, 0x55, 0x54, 0x20, + 0x76, 0x65, 0x72, 0x62, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, + 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x20, 0x69, 0x6e, 0x73, 0x74, + 0x65, 0x61, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x20, 0x50, 0x4f, 0x53, 0x54, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x04, + 0x00, 0x01, 0x12, 0x04, 0xcc, 0x05, 0x07, 0x17, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x11, 0x04, 0x00, + 0x02, 0x00, 0x12, 0x04, 0xcd, 0x05, 0x04, 0x1c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x11, 0x04, 0x00, + 0x02, 0x00, 0x01, 0x12, 0x04, 0xcd, 0x05, 0x04, 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x11, 0x04, + 0x00, 0x02, 0x00, 0x02, 0x12, 0x04, 0xcd, 0x05, 0x1a, 0x1b, 0x0a, 0x24, 0x0a, 0x06, 0x04, 0x11, + 0x04, 0x00, 0x02, 0x01, 0x12, 0x04, 0xce, 0x05, 0x04, 0x18, 0x22, 0x14, 0x20, 0x69, 0x6d, 0x70, + 0x6c, 0x69, 0x65, 0x73, 0x20, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x0a, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x11, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x04, 0xce, 0x05, 0x04, + 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x11, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x04, 0xce, 0x05, + 0x16, 0x17, 0x0a, 0x37, 0x0a, 0x06, 0x04, 0x11, 0x04, 0x00, 0x02, 0x02, 0x12, 0x04, 0xcf, 0x05, + 0x04, 0x13, 0x22, 0x27, 0x20, 0x69, 0x64, 0x65, 0x6d, 0x70, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x2c, + 0x20, 0x62, 0x75, 0x74, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x73, 0x69, + 0x64, 0x65, 0x20, 0x65, 0x66, 0x66, 0x65, 0x63, 0x74, 0x73, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x11, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x04, 0xcf, 0x05, 0x04, 0x0e, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x11, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x04, 0xcf, 0x05, 0x11, 0x12, 0x0a, 0x0e, 0x0a, + 0x04, 0x04, 0x11, 0x02, 0x01, 0x12, 0x06, 0xd1, 0x05, 0x02, 0xd2, 0x05, 0x26, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x11, 0x02, 0x01, 0x04, 0x12, 0x04, 0xd1, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x11, 0x02, 0x01, 0x06, 0x12, 0x04, 0xd1, 0x05, 0x0b, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x11, 0x02, 0x01, 0x01, 0x12, 0x04, 0xd1, 0x05, 0x1c, 0x2d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, + 0x02, 0x01, 0x03, 0x12, 0x04, 0xd1, 0x05, 0x30, 0x32, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, + 0x01, 0x08, 0x12, 0x04, 0xd2, 0x05, 0x06, 0x25, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x01, + 0x07, 0x12, 0x04, 0xd2, 0x05, 0x11, 0x24, 0x0a, 0x4f, 0x0a, 0x04, 0x04, 0x11, 0x02, 0x02, 0x12, + 0x04, 0xd5, 0x05, 0x02, 0x3a, 0x1a, 0x41, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, + 0x65, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x72, 0x65, 0x63, + 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x20, 0x68, 0x65, 0x72, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, + 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x02, + 0x04, 0x12, 0x04, 0xd5, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x02, 0x06, + 0x12, 0x04, 0xd5, 0x05, 0x0b, 0x1e, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x02, 0x01, 0x12, + 0x04, 0xd5, 0x05, 0x1f, 0x33, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x11, 0x02, 0x02, 0x03, 0x12, 0x04, + 0xd5, 0x05, 0x36, 0x39, 0x0a, 0x5a, 0x0a, 0x03, 0x04, 0x11, 0x05, 0x12, 0x04, 0xd8, 0x05, 0x02, + 0x19, 0x1a, 0x4d, 0x20, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x63, 0x61, 0x6e, 0x20, + 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x2e, 0x20, 0x53, 0x65, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x2e, 0x0a, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x11, 0x05, 0x00, 0x12, 0x04, 0xd8, 0x05, 0x0d, 0x18, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x11, 0x05, 0x00, 0x01, 0x12, 0x04, 0xd8, 0x05, 0x0d, 0x11, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x11, 0x05, 0x00, 0x02, 0x12, 0x04, 0xd8, 0x05, 0x15, 0x18, 0x0a, 0x8b, 0x03, 0x0a, + 0x02, 0x04, 0x12, 0x12, 0x06, 0xe2, 0x05, 0x00, 0xf6, 0x05, 0x01, 0x1a, 0xfc, 0x02, 0x20, 0x41, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, + 0x6e, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x70, 0x61, 0x72, 0x73, 0x65, 0x72, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, + 0x6f, 0x74, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x67, 0x6e, 0x69, 0x7a, 0x65, 0x2e, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x0a, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x73, + 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x73, 0x20, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x72, 0x3a, 0x3a, 0x50, 0x61, 0x72, 0x73, + 0x65, 0x72, 0x20, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x2e, 0x0a, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x6f, 0x6f, 0x6c, 0x20, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x76, + 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x62, 0x75, + 0x69, 0x6c, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, + 0x72, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, + 0x66, 0x6f, 0x72, 0x65, 0x2c, 0x0a, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x6f, 0x72, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x73, 0x20, 0x28, 0x65, 0x2e, 0x67, + 0x2e, 0x20, 0x72, 0x65, 0x74, 0x75, 0x72, 0x6e, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x3a, 0x3a, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x28, 0x29, 0x2c, 0x0a, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, + 0x64, 0x20, 0x62, 0x79, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x3a, + 0x3a, 0x43, 0x6f, 0x70, 0x79, 0x54, 0x6f, 0x28, 0x29, 0x29, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, + 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x55, 0x6e, 0x69, 0x6e, 0x74, + 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x0a, + 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x2e, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x12, + 0x01, 0x12, 0x04, 0xe2, 0x05, 0x08, 0x1b, 0x0a, 0xcb, 0x02, 0x0a, 0x04, 0x04, 0x12, 0x03, 0x00, + 0x12, 0x06, 0xe8, 0x05, 0x02, 0xeb, 0x05, 0x03, 0x1a, 0xba, 0x02, 0x20, 0x54, 0x68, 0x65, 0x20, + 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6e, + 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x20, 0x20, 0x45, 0x61, 0x63, 0x68, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x72, + 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x20, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x0a, 0x20, 0x61, 0x20, 0x64, 0x6f, 0x74, 0x2d, 0x73, 0x65, + 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x2e, 0x20, 0x20, 0x69, + 0x73, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x74, + 0x72, 0x75, 0x65, 0x20, 0x69, 0x66, 0x66, 0x20, 0x61, 0x20, 0x73, 0x65, 0x67, 0x6d, 0x65, 0x6e, + 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x6e, 0x0a, + 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x64, 0x65, 0x6e, 0x6f, + 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x68, + 0x65, 0x73, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, + 0x73, 0x70, 0x65, 0x63, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, + 0x66, 0x69, 0x6c, 0x65, 0x73, 0x29, 0x2e, 0x0a, 0x20, 0x45, 0x2e, 0x67, 0x2e, 0x2c, 0x7b, 0x20, + 0x5b, 0x22, 0x66, 0x6f, 0x6f, 0x22, 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x5d, 0x2c, 0x20, + 0x5b, 0x22, 0x62, 0x61, 0x72, 0x2e, 0x62, 0x61, 0x7a, 0x22, 0x2c, 0x20, 0x74, 0x72, 0x75, 0x65, + 0x5d, 0x2c, 0x20, 0x5b, 0x22, 0x6d, 0x6f, 0x6f, 0x22, 0x2c, 0x20, 0x66, 0x61, 0x6c, 0x73, 0x65, + 0x5d, 0x20, 0x7d, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x73, 0x0a, 0x20, + 0x22, 0x66, 0x6f, 0x6f, 0x2e, 0x28, 0x62, 0x61, 0x72, 0x2e, 0x62, 0x61, 0x7a, 0x29, 0x2e, 0x6d, + 0x6f, 0x6f, 0x22, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x03, 0x00, 0x01, 0x12, 0x04, + 0xe8, 0x05, 0x0a, 0x12, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x12, 0x03, 0x00, 0x02, 0x00, 0x12, 0x04, + 0xe9, 0x05, 0x04, 0x22, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, + 0x04, 0xe9, 0x05, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, 0x03, 0x00, 0x02, 0x00, 0x05, + 0x12, 0x04, 0xe9, 0x05, 0x0d, 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, 0x03, 0x00, 0x02, 0x00, + 0x01, 0x12, 0x04, 0xe9, 0x05, 0x14, 0x1d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, 0x03, 0x00, 0x02, + 0x00, 0x03, 0x12, 0x04, 0xe9, 0x05, 0x20, 0x21, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x12, 0x03, 0x00, + 0x02, 0x01, 0x12, 0x04, 0xea, 0x05, 0x04, 0x23, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, 0x03, 0x00, + 0x02, 0x01, 0x04, 0x12, 0x04, 0xea, 0x05, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, 0x03, + 0x00, 0x02, 0x01, 0x05, 0x12, 0x04, 0xea, 0x05, 0x0d, 0x11, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x12, + 0x03, 0x00, 0x02, 0x01, 0x01, 0x12, 0x04, 0xea, 0x05, 0x12, 0x1e, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x12, 0x03, 0x00, 0x02, 0x01, 0x03, 0x12, 0x04, 0xea, 0x05, 0x21, 0x22, 0x0a, 0x0c, 0x0a, 0x04, + 0x04, 0x12, 0x02, 0x00, 0x12, 0x04, 0xec, 0x05, 0x02, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, + 0x02, 0x00, 0x04, 0x12, 0x04, 0xec, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, + 0x00, 0x06, 0x12, 0x04, 0xec, 0x05, 0x0b, 0x13, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x00, + 0x01, 0x12, 0x04, 0xec, 0x05, 0x14, 0x18, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x00, 0x03, + 0x12, 0x04, 0xec, 0x05, 0x1b, 0x1c, 0x0a, 0x9c, 0x01, 0x0a, 0x04, 0x04, 0x12, 0x02, 0x01, 0x12, + 0x04, 0xf0, 0x05, 0x02, 0x27, 0x1a, 0x8d, 0x01, 0x20, 0x54, 0x68, 0x65, 0x20, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6e, 0x74, 0x65, + 0x72, 0x70, 0x72, 0x65, 0x74, 0x65, 0x64, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, + 0x69, 0x6e, 0x20, 0x77, 0x68, 0x61, 0x74, 0x65, 0x76, 0x65, 0x72, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x69, 0x7a, 0x65, 0x72, 0x0a, 0x20, + 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x69, 0x74, 0x20, 0x61, 0x73, + 0x20, 0x64, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x70, 0x61, 0x72, 0x73, 0x69, 0x6e, 0x67, 0x2e, + 0x20, 0x45, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x73, 0x65, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, + 0x73, 0x65, 0x74, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x01, 0x04, 0x12, 0x04, + 0xf0, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x01, 0x05, 0x12, 0x04, 0xf0, + 0x05, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x01, 0x01, 0x12, 0x04, 0xf0, 0x05, + 0x12, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x01, 0x03, 0x12, 0x04, 0xf0, 0x05, 0x25, + 0x26, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x12, 0x02, 0x02, 0x12, 0x04, 0xf1, 0x05, 0x02, 0x29, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x02, 0x04, 0x12, 0x04, 0xf1, 0x05, 0x02, 0x0a, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x12, 0x02, 0x02, 0x05, 0x12, 0x04, 0xf1, 0x05, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x12, 0x02, 0x02, 0x01, 0x12, 0x04, 0xf1, 0x05, 0x12, 0x24, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x12, 0x02, 0x02, 0x03, 0x12, 0x04, 0xf1, 0x05, 0x27, 0x28, 0x0a, 0x0c, 0x0a, 0x04, 0x04, + 0x12, 0x02, 0x03, 0x12, 0x04, 0xf2, 0x05, 0x02, 0x28, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, + 0x03, 0x04, 0x12, 0x04, 0xf2, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x03, + 0x05, 0x12, 0x04, 0xf2, 0x05, 0x0b, 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x03, 0x01, + 0x12, 0x04, 0xf2, 0x05, 0x11, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x03, 0x03, 0x12, + 0x04, 0xf2, 0x05, 0x26, 0x27, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x12, 0x02, 0x04, 0x12, 0x04, 0xf3, + 0x05, 0x02, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x04, 0x04, 0x12, 0x04, 0xf3, 0x05, + 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x04, 0x05, 0x12, 0x04, 0xf3, 0x05, 0x0b, + 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x04, 0x01, 0x12, 0x04, 0xf3, 0x05, 0x12, 0x1e, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x04, 0x03, 0x12, 0x04, 0xf3, 0x05, 0x21, 0x22, 0x0a, + 0x0c, 0x0a, 0x04, 0x04, 0x12, 0x02, 0x05, 0x12, 0x04, 0xf4, 0x05, 0x02, 0x22, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x12, 0x02, 0x05, 0x04, 0x12, 0x04, 0xf4, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x12, 0x02, 0x05, 0x05, 0x12, 0x04, 0xf4, 0x05, 0x0b, 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x12, 0x02, 0x05, 0x01, 0x12, 0x04, 0xf4, 0x05, 0x11, 0x1d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, + 0x02, 0x05, 0x03, 0x12, 0x04, 0xf4, 0x05, 0x20, 0x21, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x12, 0x02, + 0x06, 0x12, 0x04, 0xf5, 0x05, 0x02, 0x26, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x06, 0x04, + 0x12, 0x04, 0xf5, 0x05, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x06, 0x05, 0x12, + 0x04, 0xf5, 0x05, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x06, 0x01, 0x12, 0x04, + 0xf5, 0x05, 0x12, 0x21, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x12, 0x02, 0x06, 0x03, 0x12, 0x04, 0xf5, + 0x05, 0x24, 0x25, 0x0a, 0xda, 0x01, 0x0a, 0x02, 0x04, 0x13, 0x12, 0x06, 0xfd, 0x05, 0x00, 0xfe, + 0x06, 0x01, 0x1a, 0x6a, 0x20, 0x45, 0x6e, 0x63, 0x61, 0x70, 0x73, 0x75, 0x6c, 0x61, 0x74, 0x65, + 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x62, + 0x6f, 0x75, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, + 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x66, 0x72, 0x6f, + 0x6d, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x61, 0x0a, 0x20, 0x46, 0x69, 0x6c, 0x65, 0x44, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x77, + 0x61, 0x73, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x2e, 0x0a, 0x32, 0x60, + 0x20, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x0a, 0x20, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x0a, + 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x13, 0x01, 0x12, 0x04, 0xfd, 0x05, 0x08, 0x16, 0x0a, 0x82, 0x11, + 0x0a, 0x04, 0x04, 0x13, 0x02, 0x00, 0x12, 0x04, 0xa9, 0x06, 0x02, 0x21, 0x1a, 0xf3, 0x10, 0x20, + 0x41, 0x20, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x61, 0x20, 0x70, 0x69, 0x65, 0x63, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x20, + 0x61, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x20, 0x77, 0x68, + 0x69, 0x63, 0x68, 0x0a, 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, 0x63, 0x75, 0x6c, 0x61, 0x72, + 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x20, 0x54, 0x68, + 0x69, 0x73, 0x20, 0x69, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, + 0x73, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x0a, 0x20, 0x74, 0x6f, 0x20, 0x62, + 0x65, 0x20, 0x75, 0x73, 0x65, 0x66, 0x75, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x49, 0x44, 0x45, 0x73, + 0x2c, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x72, 0x73, 0x2c, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x67, + 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, + 0x69, 0x6d, 0x69, 0x6c, 0x61, 0x72, 0x0a, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x2e, 0x0a, 0x0a, + 0x20, 0x46, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, 0x20, 0x73, 0x61, + 0x79, 0x20, 0x77, 0x65, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x20, 0x6c, 0x69, 0x6b, 0x65, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x20, 0x46, 0x6f, 0x6f, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x6f, + 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x4c, 0x65, 0x74, 0x27, + 0x73, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x20, 0x61, 0x74, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x3d, 0x20, 0x31, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x5e, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x5e, 0x5e, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x5e, 0x5e, 0x20, 0x20, 0x5e, 0x20, 0x20, 0x5e, 0x5e, 0x5e, 0x0a, 0x20, 0x20, + 0x20, 0x61, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x62, 0x63, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x64, 0x65, 0x20, 0x20, 0x66, 0x20, 0x20, 0x67, 0x68, 0x69, 0x0a, 0x20, 0x57, 0x65, 0x20, 0x68, + 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, + 0x67, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x3a, 0x0a, 0x20, 0x20, 0x20, + 0x73, 0x70, 0x61, 0x6e, 0x20, 0x20, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x74, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x5b, 0x61, 0x2c, 0x69, 0x29, 0x20, 0x20, 0x5b, + 0x20, 0x34, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x30, 0x20, 0x5d, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x77, 0x68, 0x6f, 0x6c, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, 0x20, + 0x20, 0x5b, 0x61, 0x2c, 0x62, 0x29, 0x20, 0x20, 0x5b, 0x20, 0x34, 0x2c, 0x20, 0x30, 0x2c, 0x20, + 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x34, 0x20, 0x5d, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6c, + 0x61, 0x62, 0x65, 0x6c, 0x20, 0x28, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x29, 0x2e, + 0x0a, 0x20, 0x20, 0x20, 0x5b, 0x63, 0x2c, 0x64, 0x29, 0x20, 0x20, 0x5b, 0x20, 0x34, 0x2c, 0x20, + 0x30, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x35, 0x20, 0x5d, 0x20, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x28, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x29, 0x2e, + 0x0a, 0x20, 0x20, 0x20, 0x5b, 0x65, 0x2c, 0x66, 0x29, 0x20, 0x20, 0x5b, 0x20, 0x34, 0x2c, 0x20, + 0x30, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x31, 0x20, 0x5d, 0x20, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x28, 0x66, 0x6f, 0x6f, 0x29, 0x2e, 0x0a, 0x20, 0x20, + 0x20, 0x5b, 0x67, 0x2c, 0x68, 0x29, 0x20, 0x20, 0x5b, 0x20, 0x34, 0x2c, 0x20, 0x30, 0x2c, 0x20, + 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x33, 0x20, 0x5d, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x28, 0x31, 0x29, 0x2e, 0x0a, 0x0a, 0x20, 0x4e, 0x6f, 0x74, + 0x65, 0x73, 0x3a, 0x0a, 0x20, 0x2d, 0x20, 0x41, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x61, + 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, + 0x69, 0x74, 0x73, 0x65, 0x6c, 0x66, 0x20, 0x28, 0x69, 0x2e, 0x65, 0x2e, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x79, 0x0a, 0x20, 0x20, 0x20, 0x70, 0x61, 0x72, 0x74, 0x69, + 0x63, 0x75, 0x6c, 0x61, 0x72, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x77, 0x69, 0x74, 0x68, + 0x69, 0x6e, 0x20, 0x69, 0x74, 0x29, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, + 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x65, 0x76, 0x65, 0x72, 0x20, 0x61, + 0x20, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, + 0x20, 0x61, 0x72, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x6c, + 0x79, 0x20, 0x65, 0x6e, 0x63, 0x6c, 0x6f, 0x73, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x73, 0x65, 0x67, 0x6d, + 0x65, 0x6e, 0x74, 0x2e, 0x20, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x2c, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x6e, 0x74, 0x69, 0x72, 0x65, 0x0a, 0x20, 0x20, 0x20, + 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x28, 0x70, 0x6f, + 0x73, 0x73, 0x69, 0x62, 0x6c, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x69, 0x6e, + 0x67, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x29, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x0a, 0x20, 0x20, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x61, + 0x6e, 0x20, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x20, 0x77, 0x68, 0x6f, 0x73, 0x65, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x72, 0x65, 0x66, 0x65, + 0x72, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, + 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x0a, + 0x20, 0x20, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, + 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x0a, 0x20, 0x2d, 0x20, 0x4d, 0x75, + 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x6d, 0x61, 0x79, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, + 0x6d, 0x65, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x20, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x68, + 0x61, 0x70, 0x70, 0x65, 0x6e, 0x73, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x69, + 0x6e, 0x67, 0x6c, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x6c, 0x6f, 0x67, 0x69, 0x63, 0x61, 0x6c, 0x20, + 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x73, + 0x70, 0x72, 0x65, 0x61, 0x64, 0x20, 0x6f, 0x75, 0x74, 0x20, 0x61, 0x63, 0x72, 0x6f, 0x73, 0x73, + 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x70, 0x6c, 0x61, 0x63, 0x65, 0x73, + 0x2e, 0x20, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x6f, 0x73, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x6f, + 0x62, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x69, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x22, 0x20, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x61, 0x67, 0x61, 0x69, 0x6e, 0x20, 0x2d, 0x2d, 0x20, 0x74, 0x68, + 0x65, 0x72, 0x65, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, 0x6d, 0x75, 0x6c, 0x74, 0x69, + 0x70, 0x6c, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x20, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, + 0x20, 0x73, 0x63, 0x6f, 0x70, 0x65, 0x2c, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6f, 0x66, 0x20, + 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x0a, 0x20, + 0x2d, 0x20, 0x41, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x73, 0x20, 0x73, + 0x70, 0x61, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x61, 0x6c, 0x77, 0x61, 0x79, + 0x73, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x74, + 0x73, 0x20, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x27, 0x73, 0x20, 0x73, 0x70, 0x61, 0x6e, 0x2e, + 0x20, 0x20, 0x46, 0x6f, 0x72, 0x0a, 0x20, 0x20, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x65, 0x22, + 0x20, 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x70, 0x70, + 0x65, 0x61, 0x72, 0x73, 0x20, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x62, + 0x65, 0x67, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x22, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x22, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x69, 0x73, 0x20, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, + 0x61, 0x6c, 0x6c, 0x20, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x77, + 0x69, 0x74, 0x68, 0x69, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x2e, 0x0a, 0x20, 0x2d, 0x20, 0x4a, 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x63, 0x61, + 0x75, 0x73, 0x65, 0x20, 0x61, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x73, + 0x20, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x73, 0x75, 0x62, 0x73, 0x65, + 0x74, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, + 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x27, 0x73, 0x20, 0x73, 0x70, 0x61, 0x6e, 0x0a, + 0x20, 0x20, 0x20, 0x64, 0x6f, 0x65, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6d, 0x65, 0x61, 0x6e, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x64, 0x65, + 0x73, 0x63, 0x65, 0x6e, 0x64, 0x61, 0x6e, 0x74, 0x2e, 0x20, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x65, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, 0x20, 0x61, 0x20, 0x22, 0x67, 0x72, 0x6f, 0x75, 0x70, + 0x22, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x62, 0x6f, 0x74, + 0x68, 0x20, 0x61, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, + 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x20, 0x20, 0x54, + 0x68, 0x75, 0x73, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x63, 0x6f, 0x72, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, + 0x65, 0x69, 0x72, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x77, + 0x69, 0x6c, 0x6c, 0x20, 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x61, 0x70, 0x2e, 0x0a, 0x20, 0x2d, 0x20, + 0x43, 0x6f, 0x64, 0x65, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x74, 0x72, 0x69, 0x65, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x72, 0x65, 0x74, 0x20, 0x6c, 0x6f, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x70, + 0x72, 0x6f, 0x62, 0x61, 0x62, 0x6c, 0x79, 0x20, 0x62, 0x65, 0x20, 0x64, 0x65, 0x73, 0x69, 0x67, + 0x6e, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, + 0x20, 0x74, 0x68, 0x6f, 0x73, 0x65, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x74, 0x20, 0x64, + 0x6f, 0x65, 0x73, 0x6e, 0x27, 0x74, 0x20, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x74, 0x61, 0x6e, + 0x64, 0x2c, 0x20, 0x61, 0x73, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x20, 0x6f, 0x66, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x63, 0x6f, + 0x75, 0x6c, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, + 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x74, 0x75, 0x72, 0x65, + 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x04, 0x12, 0x04, 0xa9, 0x06, 0x02, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x06, 0x12, 0x04, 0xa9, 0x06, 0x0b, 0x13, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x01, 0x12, 0x04, 0xa9, 0x06, 0x14, 0x1c, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x13, 0x02, 0x00, 0x03, 0x12, 0x04, 0xa9, 0x06, 0x1f, 0x20, 0x0a, 0x0e, + 0x0a, 0x04, 0x04, 0x13, 0x03, 0x00, 0x12, 0x06, 0xaa, 0x06, 0x02, 0xfd, 0x06, 0x03, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x13, 0x03, 0x00, 0x01, 0x12, 0x04, 0xaa, 0x06, 0x0a, 0x12, 0x0a, 0x89, 0x07, + 0x0a, 0x06, 0x04, 0x13, 0x03, 0x00, 0x02, 0x00, 0x12, 0x04, 0xc2, 0x06, 0x04, 0x2c, 0x1a, 0xf8, + 0x06, 0x20, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x77, 0x68, 0x69, + 0x63, 0x68, 0x20, 0x70, 0x61, 0x72, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x46, + 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, + 0x74, 0x6f, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x20, 0x61, + 0x74, 0x20, 0x74, 0x68, 0x69, 0x73, 0x0a, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2e, 0x0a, 0x0a, 0x20, 0x45, 0x61, 0x63, 0x68, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, + 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x62, + 0x65, 0x72, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x2e, 0x20, + 0x20, 0x54, 0x68, 0x65, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, 0x61, 0x20, 0x70, 0x61, 0x74, + 0x68, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x6f, 0x6f, 0x74, + 0x20, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, + 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x6c, 0x61, 0x63, + 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, + 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x63, 0x63, 0x75, 0x72, 0x73, 0x2e, 0x0a, 0x20, + 0x46, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, + 0x73, 0x20, 0x70, 0x61, 0x74, 0x68, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x5b, 0x20, 0x34, 0x2c, 0x20, + 0x33, 0x2c, 0x20, 0x32, 0x2c, 0x20, 0x37, 0x2c, 0x20, 0x31, 0x20, 0x5d, 0x0a, 0x20, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x73, 0x20, 0x74, 0x6f, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x2e, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x28, 0x33, 0x29, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x34, 0x2c, 0x20, 0x33, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x2e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x28, 0x37, 0x29, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x32, 0x2c, 0x20, 0x37, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x28, 0x29, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x31, 0x0a, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, + 0x73, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x66, + 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x34, 0x3a, 0x0a, 0x20, + 0x20, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x20, 0x3d, 0x20, 0x34, 0x3b, 0x0a, 0x20, 0x61, 0x6e, + 0x64, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x68, 0x61, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x32, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x72, + 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x44, 0x65, 0x73, + 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x65, + 0x6c, 0x64, 0x20, 0x3d, 0x20, 0x32, 0x3b, 0x0a, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x46, 0x69, 0x65, + 0x6c, 0x64, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x68, 0x61, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x20, 0x31, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x54, 0x68, 0x75, 0x73, 0x2c, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x67, + 0x69, 0x76, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x61, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x6e, 0x61, 0x6d, + 0x65, 0x2e, 0x20, 0x20, 0x49, 0x66, 0x20, 0x77, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, + 0x64, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x20, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x5b, 0x20, 0x34, 0x2c, 0x20, 0x33, 0x2c, 0x20, + 0x32, 0x2c, 0x20, 0x37, 0x20, 0x5d, 0x0a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x61, 0x74, + 0x68, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x77, 0x68, 0x6f, 0x6c, 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x64, 0x65, 0x63, 0x6c, + 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x28, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x0a, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x73, 0x65, 0x6d, + 0x69, 0x63, 0x6f, 0x6c, 0x6f, 0x6e, 0x29, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, + 0x00, 0x02, 0x00, 0x04, 0x12, 0x04, 0xc2, 0x06, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, + 0x03, 0x00, 0x02, 0x00, 0x05, 0x12, 0x04, 0xc2, 0x06, 0x0d, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x13, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0xc2, 0x06, 0x13, 0x17, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x13, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x04, 0xc2, 0x06, 0x1a, 0x1b, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x00, 0x08, 0x12, 0x04, 0xc2, 0x06, 0x1c, 0x2b, 0x0a, 0x10, + 0x0a, 0x08, 0x04, 0x13, 0x03, 0x00, 0x02, 0x00, 0x08, 0x02, 0x12, 0x04, 0xc2, 0x06, 0x1d, 0x2a, + 0x0a, 0xd2, 0x02, 0x0a, 0x06, 0x04, 0x13, 0x03, 0x00, 0x02, 0x01, 0x12, 0x04, 0xc9, 0x06, 0x04, + 0x2c, 0x1a, 0xc1, 0x02, 0x20, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x20, 0x68, 0x61, 0x73, 0x20, + 0x65, 0x78, 0x61, 0x63, 0x74, 0x6c, 0x79, 0x20, 0x74, 0x68, 0x72, 0x65, 0x65, 0x20, 0x6f, 0x72, + 0x20, 0x66, 0x6f, 0x75, 0x72, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x3a, 0x20, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x2c, 0x20, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x20, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, 0x2c, 0x0a, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x6c, + 0x69, 0x6e, 0x65, 0x20, 0x28, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x2c, 0x20, 0x6f, + 0x74, 0x68, 0x65, 0x72, 0x77, 0x69, 0x73, 0x65, 0x20, 0x61, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x64, + 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x73, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x20, 0x6c, + 0x69, 0x6e, 0x65, 0x29, 0x2c, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x63, 0x6f, 0x6c, 0x75, 0x6d, 0x6e, + 0x2e, 0x0a, 0x20, 0x54, 0x68, 0x65, 0x73, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, 0x70, 0x61, 0x63, + 0x6b, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x73, 0x69, 0x6e, 0x67, 0x6c, + 0x65, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x66, 0x66, 0x69, + 0x63, 0x69, 0x65, 0x6e, 0x63, 0x79, 0x2e, 0x20, 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x0a, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 0x6f, 0x6c, + 0x75, 0x6d, 0x6e, 0x20, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x7a, 0x65, 0x72, 0x6f, 0x2d, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x2d, 0x2d, 0x20, 0x74, 0x79, + 0x70, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x77, 0x69, 0x6c, 0x6c, + 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x64, 0x64, 0x0a, 0x20, 0x31, 0x20, + 0x74, 0x6f, 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x64, + 0x69, 0x73, 0x70, 0x6c, 0x61, 0x79, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x75, + 0x73, 0x65, 0x72, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x01, 0x04, + 0x12, 0x04, 0xc9, 0x06, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x01, + 0x05, 0x12, 0x04, 0xc9, 0x06, 0x0d, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, + 0x01, 0x01, 0x12, 0x04, 0xc9, 0x06, 0x13, 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, + 0x02, 0x01, 0x03, 0x12, 0x04, 0xc9, 0x06, 0x1a, 0x1b, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, + 0x00, 0x02, 0x01, 0x08, 0x12, 0x04, 0xc9, 0x06, 0x1c, 0x2b, 0x0a, 0x10, 0x0a, 0x08, 0x04, 0x13, + 0x03, 0x00, 0x02, 0x01, 0x08, 0x02, 0x12, 0x04, 0xc9, 0x06, 0x1d, 0x2a, 0x0a, 0xa5, 0x0c, 0x0a, + 0x06, 0x04, 0x13, 0x03, 0x00, 0x02, 0x02, 0x12, 0x04, 0xfa, 0x06, 0x04, 0x29, 0x1a, 0x94, 0x0c, + 0x20, 0x49, 0x66, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, + 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, + 0x74, 0x73, 0x20, 0x61, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x20, 0x64, 0x65, + 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x74, 0x68, 0x65, 0x73, 0x65, + 0x20, 0x61, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x79, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x62, 0x65, 0x66, + 0x6f, 0x72, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x66, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x68, + 0x69, 0x63, 0x68, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, + 0x0a, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, + 0x20, 0x41, 0x20, 0x73, 0x65, 0x72, 0x69, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x6c, 0x69, 0x6e, + 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, + 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x76, 0x65, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, + 0x6e, 0x6f, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x0a, 0x20, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, + 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, + 0x6f, 0x73, 0x65, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x2c, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, + 0x62, 0x65, 0x20, 0x74, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, + 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x0a, + 0x0a, 0x20, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x64, 0x65, 0x74, 0x61, 0x63, 0x68, + 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x77, 0x69, 0x6c, 0x6c, + 0x20, 0x6b, 0x65, 0x65, 0x70, 0x20, 0x70, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x73, + 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x61, 0x70, 0x70, 0x65, 0x61, 0x72, 0x0a, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, + 0x20, 0x28, 0x62, 0x75, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x29, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x75, 0x72, 0x72, + 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x2e, 0x20, 0x45, 0x61, 0x63, + 0x68, 0x20, 0x70, 0x61, 0x72, 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x2c, 0x0a, 0x20, 0x73, 0x65, + 0x70, 0x61, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x79, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, + 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x2c, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, + 0x6f, 0x6e, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x65, 0x6c, 0x65, 0x6d, + 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, + 0x74, 0x65, 0x64, 0x0a, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x0a, 0x0a, 0x20, 0x4f, 0x6e, + 0x6c, 0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x63, + 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x64, 0x3b, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x61, 0x72, 0x6b, + 0x65, 0x72, 0x73, 0x20, 0x28, 0x65, 0x2e, 0x67, 0x2e, 0x20, 0x2f, 0x2f, 0x29, 0x20, 0x61, 0x72, + 0x65, 0x0a, 0x20, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x20, 0x6f, 0x75, 0x74, 0x2e, + 0x20, 0x20, 0x46, 0x6f, 0x72, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x63, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x74, 0x73, 0x2c, 0x20, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x77, 0x68, + 0x69, 0x74, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x6e, 0x20, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x0a, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, + 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x70, 0x70, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, + 0x20, 0x65, 0x61, 0x63, 0x68, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, + 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x2e, + 0x0a, 0x20, 0x4e, 0x65, 0x77, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x69, + 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, + 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x73, 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, + 0x69, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x3d, 0x20, 0x31, 0x3b, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x66, 0x6f, 0x6f, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x61, 0x72, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x69, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x62, 0x61, 0x72, + 0x20, 0x3d, 0x20, 0x32, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x62, 0x61, 0x7a, 0x20, 0x3d, 0x20, + 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x43, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, + 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x61, 0x7a, + 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x41, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, + 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, + 0x20, 0x62, 0x61, 0x7a, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x43, 0x6f, 0x6d, + 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, + 0x20, 0x6d, 0x6f, 0x6f, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x41, 0x6e, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x61, + 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x6f, 0x6f, 0x2e, 0x0a, + 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x64, 0x6f, 0x75, 0x62, + 0x6c, 0x65, 0x20, 0x6d, 0x6f, 0x6f, 0x20, 0x3d, 0x20, 0x34, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, + 0x2f, 0x2f, 0x20, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6d, 0x6d, + 0x65, 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x72, 0x67, 0x65, 0x2e, 0x20, 0x54, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x6c, 0x65, 0x61, 0x64, 0x69, + 0x6e, 0x67, 0x20, 0x6f, 0x72, 0x20, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x74, 0x6f, + 0x20, 0x6d, 0x6f, 0x6f, 0x20, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x72, 0x67, 0x65, 0x20, 0x62, 0x65, + 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x20, 0x61, 0x72, 0x65, 0x20, + 0x62, 0x6c, 0x61, 0x6e, 0x6b, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x73, 0x65, 0x70, 0x61, + 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x74, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x0a, 0x20, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x62, 0x6f, 0x74, 0x68, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x2f, + 0x2f, 0x20, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, + 0x6e, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x63, 0x6f, 0x72, 0x67, 0x65, 0x20, 0x70, 0x61, 0x72, + 0x61, 0x67, 0x72, 0x61, 0x70, 0x68, 0x20, 0x32, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x6f, + 0x72, 0x67, 0x65, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x74, 0x74, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x74, 0x6f, 0x20, 0x63, + 0x6f, 0x72, 0x67, 0x65, 0x2e, 0x20, 0x20, 0x4c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x61, + 0x73, 0x74, 0x65, 0x72, 0x69, 0x73, 0x6b, 0x73, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x77, + 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x2e, 0x20, + 0x2a, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2a, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x63, + 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, + 0x74, 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x67, 0x72, 0x61, 0x75, 0x6c, 0x74, 0x2e, + 0x20, 0x2a, 0x2f, 0x0a, 0x20, 0x20, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, + 0x69, 0x6e, 0x74, 0x33, 0x32, 0x20, 0x67, 0x72, 0x61, 0x75, 0x6c, 0x74, 0x20, 0x3d, 0x20, 0x36, + 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, + 0x20, 0x64, 0x65, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x02, 0x04, 0x12, + 0x04, 0xfa, 0x06, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x02, 0x05, + 0x12, 0x04, 0xfa, 0x06, 0x0d, 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x02, + 0x01, 0x12, 0x04, 0xfa, 0x06, 0x14, 0x24, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, + 0x02, 0x03, 0x12, 0x04, 0xfa, 0x06, 0x27, 0x28, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x13, 0x03, 0x00, + 0x02, 0x03, 0x12, 0x04, 0xfb, 0x06, 0x04, 0x2a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, + 0x02, 0x03, 0x04, 0x12, 0x04, 0xfb, 0x06, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, + 0x00, 0x02, 0x03, 0x05, 0x12, 0x04, 0xfb, 0x06, 0x0d, 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x13, + 0x03, 0x00, 0x02, 0x03, 0x01, 0x12, 0x04, 0xfb, 0x06, 0x14, 0x25, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x13, 0x03, 0x00, 0x02, 0x03, 0x03, 0x12, 0x04, 0xfb, 0x06, 0x28, 0x29, 0x0a, 0x0e, 0x0a, 0x06, + 0x04, 0x13, 0x03, 0x00, 0x02, 0x04, 0x12, 0x04, 0xfc, 0x06, 0x04, 0x32, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x13, 0x03, 0x00, 0x02, 0x04, 0x04, 0x12, 0x04, 0xfc, 0x06, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x04, 0x05, 0x12, 0x04, 0xfc, 0x06, 0x0d, 0x13, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x04, 0x01, 0x12, 0x04, 0xfc, 0x06, 0x14, 0x2d, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x13, 0x03, 0x00, 0x02, 0x04, 0x03, 0x12, 0x04, 0xfc, 0x06, 0x30, 0x31, + 0x0a, 0xee, 0x01, 0x0a, 0x02, 0x04, 0x14, 0x12, 0x06, 0x83, 0x07, 0x00, 0x98, 0x07, 0x01, 0x1a, + 0xdf, 0x01, 0x20, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x20, 0x62, 0x65, + 0x74, 0x77, 0x65, 0x65, 0x6e, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x63, 0x6f, 0x64, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x73, 0x20, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x0a, 0x20, 0x66, 0x69, + 0x6c, 0x65, 0x2e, 0x20, 0x41, 0x20, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x43, + 0x6f, 0x64, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, + 0x69, 0x73, 0x20, 0x61, 0x73, 0x73, 0x6f, 0x63, 0x69, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, + 0x72, 0x61, 0x74, 0x65, 0x64, 0x0a, 0x20, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x66, 0x69, + 0x6c, 0x65, 0x2c, 0x20, 0x62, 0x75, 0x74, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x20, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x20, 0x74, + 0x6f, 0x20, 0x64, 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x74, 0x20, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, + 0x0a, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x14, 0x01, 0x12, 0x04, 0x83, 0x07, 0x08, 0x19, 0x0a, 0x78, + 0x0a, 0x04, 0x04, 0x14, 0x02, 0x00, 0x12, 0x04, 0x86, 0x07, 0x02, 0x25, 0x1a, 0x6a, 0x20, 0x41, + 0x6e, 0x20, 0x41, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, + 0x6e, 0x65, 0x63, 0x74, 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x73, 0x70, 0x61, 0x6e, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, 0x6f, 0x20, 0x61, 0x6e, 0x20, + 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x0a, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x74, 0x73, 0x20, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00, + 0x04, 0x12, 0x04, 0x86, 0x07, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00, 0x06, + 0x12, 0x04, 0x86, 0x07, 0x0b, 0x15, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00, 0x01, 0x12, + 0x04, 0x86, 0x07, 0x16, 0x20, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x02, 0x00, 0x03, 0x12, 0x04, + 0x86, 0x07, 0x23, 0x24, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x14, 0x03, 0x00, 0x12, 0x06, 0x87, 0x07, + 0x02, 0x97, 0x07, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x14, 0x03, 0x00, 0x01, 0x12, 0x04, 0x87, + 0x07, 0x0a, 0x14, 0x0a, 0x8f, 0x01, 0x0a, 0x06, 0x04, 0x14, 0x03, 0x00, 0x02, 0x00, 0x12, 0x04, + 0x8a, 0x07, 0x04, 0x2c, 0x1a, 0x7f, 0x20, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x20, 0x66, 0x69, 0x6c, 0x65, + 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x0a, 0x20, 0x69, 0x73, + 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x74, 0x65, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, + 0x61, 0x6d, 0x65, 0x20, 0x61, 0x73, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x43, 0x6f, 0x64, + 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x70, + 0x61, 0x74, 0x68, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x00, 0x04, + 0x12, 0x04, 0x8a, 0x07, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x00, + 0x05, 0x12, 0x04, 0x8a, 0x07, 0x0d, 0x12, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, + 0x00, 0x01, 0x12, 0x04, 0x8a, 0x07, 0x13, 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, + 0x02, 0x00, 0x03, 0x12, 0x04, 0x8a, 0x07, 0x1a, 0x1b, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, + 0x00, 0x02, 0x00, 0x08, 0x12, 0x04, 0x8a, 0x07, 0x1c, 0x2b, 0x0a, 0x10, 0x0a, 0x08, 0x04, 0x14, + 0x03, 0x00, 0x02, 0x00, 0x08, 0x02, 0x12, 0x04, 0x8a, 0x07, 0x1d, 0x2a, 0x0a, 0x4f, 0x0a, 0x06, + 0x04, 0x14, 0x03, 0x00, 0x02, 0x01, 0x12, 0x04, 0x8d, 0x07, 0x04, 0x24, 0x1a, 0x3f, 0x20, 0x49, + 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, + 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x20, 0x70, 0x61, 0x74, 0x68, 0x20, 0x74, 0x6f, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61, 0x6c, 0x20, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x20, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x01, 0x04, 0x12, 0x04, 0x8d, 0x07, 0x04, 0x0c, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x01, 0x05, 0x12, 0x04, 0x8d, 0x07, 0x0d, 0x13, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x01, 0x01, 0x12, 0x04, 0x8d, 0x07, 0x14, 0x1f, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x01, 0x03, 0x12, 0x04, 0x8d, 0x07, 0x22, + 0x23, 0x0a, 0x77, 0x0a, 0x06, 0x04, 0x14, 0x03, 0x00, 0x02, 0x02, 0x12, 0x04, 0x91, 0x07, 0x04, + 0x1d, 0x1a, 0x67, 0x20, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x73, 0x74, 0x61, 0x72, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x66, 0x73, + 0x65, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x64, + 0x65, 0x0a, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, + 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x20, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x14, + 0x03, 0x00, 0x02, 0x02, 0x04, 0x12, 0x04, 0x91, 0x07, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x14, 0x03, 0x00, 0x02, 0x02, 0x05, 0x12, 0x04, 0x91, 0x07, 0x0d, 0x12, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x14, 0x03, 0x00, 0x02, 0x02, 0x01, 0x12, 0x04, 0x91, 0x07, 0x13, 0x18, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x02, 0x03, 0x12, 0x04, 0x91, 0x07, 0x1b, 0x1c, 0x0a, 0xdb, + 0x01, 0x0a, 0x06, 0x04, 0x14, 0x03, 0x00, 0x02, 0x03, 0x12, 0x04, 0x96, 0x07, 0x04, 0x1b, 0x1a, + 0xca, 0x01, 0x20, 0x49, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, + 0x69, 0x6e, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x74, + 0x68, 0x61, 0x74, 0x0a, 0x20, 0x72, 0x65, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x65, 0x6e, 0x64, 0x20, 0x6f, + 0x66, 0x66, 0x73, 0x65, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, + 0x6f, 0x6e, 0x65, 0x20, 0x70, 0x61, 0x73, 0x74, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, + 0x73, 0x74, 0x20, 0x72, 0x65, 0x6c, 0x65, 0x76, 0x61, 0x6e, 0x74, 0x20, 0x62, 0x79, 0x74, 0x65, + 0x20, 0x28, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x65, 0x78, 0x74, 0x20, 0x3d, 0x20, 0x65, 0x6e, + 0x64, 0x20, 0x2d, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x29, 0x2e, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x14, 0x03, 0x00, 0x02, 0x03, 0x04, 0x12, 0x04, 0x96, 0x07, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x03, 0x05, 0x12, 0x04, 0x96, 0x07, 0x0d, 0x12, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x03, 0x01, 0x12, 0x04, 0x96, 0x07, 0x13, 0x16, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x14, 0x03, 0x00, 0x02, 0x03, 0x03, 0x12, 0x04, 0x96, 0x07, 0x19, 0x1a, + 0x0a, 0xd9, 0x2c, 0x0a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x22, + 0x36, 0x0a, 0x03, 0x41, 0x6e, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x79, 0x70, 0x65, 0x5f, 0x75, + 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x79, 0x70, 0x65, 0x55, 0x72, + 0x6c, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x42, 0x76, 0x0a, 0x13, 0x63, 0x6f, 0x6d, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x42, 0x08, + 0x41, 0x6e, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2c, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2e, 0x67, 0x6f, 0x6c, 0x61, 0x6e, 0x67, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x6b, 0x6e, 0x6f, + 0x77, 0x6e, 0x2f, 0x61, 0x6e, 0x79, 0x70, 0x62, 0xa2, 0x02, 0x03, 0x47, 0x50, 0x42, 0xaa, 0x02, + 0x1e, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, + 0x2e, 0x57, 0x65, 0x6c, 0x6c, 0x4b, 0x6e, 0x6f, 0x77, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x73, 0x4a, + 0xf2, 0x2a, 0x0a, 0x07, 0x12, 0x05, 0x1e, 0x00, 0x9d, 0x01, 0x01, 0x0a, 0xcc, 0x0c, 0x0a, 0x01, + 0x0c, 0x12, 0x03, 0x1e, 0x00, 0x12, 0x32, 0xc1, 0x0c, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x20, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x20, 0x2d, 0x20, 0x47, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, + 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x0a, 0x20, 0x43, + 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x32, 0x30, 0x30, 0x38, 0x20, 0x47, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x20, 0x41, 0x6c, 0x6c, 0x20, 0x72, + 0x69, 0x67, 0x68, 0x74, 0x73, 0x20, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x2e, 0x0a, + 0x20, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x65, 0x72, 0x73, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x2d, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x73, 0x2f, + 0x0a, 0x0a, 0x20, 0x52, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, + 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, 0x73, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x66, + 0x6f, 0x72, 0x6d, 0x73, 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6f, 0x72, 0x20, 0x77, 0x69, + 0x74, 0x68, 0x6f, 0x75, 0x74, 0x0a, 0x20, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x61, 0x72, 0x65, 0x20, 0x70, 0x65, 0x72, 0x6d, 0x69, 0x74, 0x74, + 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x63, + 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x72, 0x65, 0x0a, 0x20, 0x6d, + 0x65, 0x74, 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x52, 0x65, 0x64, 0x69, + 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, + 0x72, 0x65, 0x74, 0x61, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, + 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, 0x67, 0x68, 0x74, 0x0a, 0x20, 0x6e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, + 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, + 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2a, + 0x20, 0x52, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x69, 0x6e, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x20, + 0x6d, 0x75, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, 0x20, 0x74, + 0x68, 0x65, 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x0a, 0x20, 0x63, 0x6f, 0x70, 0x79, 0x72, 0x69, + 0x67, 0x68, 0x74, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x63, 0x65, 0x2c, 0x20, 0x74, 0x68, 0x69, 0x73, + 0x20, 0x6c, 0x69, 0x73, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x6c, 0x6c, + 0x6f, 0x77, 0x69, 0x6e, 0x67, 0x20, 0x64, 0x69, 0x73, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x72, + 0x0a, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, + 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x2f, 0x6f, 0x72, 0x20, 0x6f, 0x74, + 0x68, 0x65, 0x72, 0x20, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x20, 0x70, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x0a, + 0x20, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x2a, 0x20, 0x4e, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x20, 0x6e, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x69, 0x74, 0x73, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x69, 0x62, 0x75, 0x74, 0x6f, 0x72, 0x73, 0x20, 0x6d, 0x61, 0x79, 0x20, 0x62, 0x65, 0x20, + 0x75, 0x73, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x73, 0x65, 0x20, + 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x6d, 0x6f, 0x74, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, + 0x63, 0x74, 0x73, 0x20, 0x64, 0x65, 0x72, 0x69, 0x76, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f, 0x6d, + 0x0a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, + 0x20, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x20, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x20, 0x70, + 0x65, 0x72, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x20, 0x54, 0x48, 0x49, + 0x53, 0x20, 0x53, 0x4f, 0x46, 0x54, 0x57, 0x41, 0x52, 0x45, 0x20, 0x49, 0x53, 0x20, 0x50, 0x52, + 0x4f, 0x56, 0x49, 0x44, 0x45, 0x44, 0x20, 0x42, 0x59, 0x20, 0x54, 0x48, 0x45, 0x20, 0x43, 0x4f, + 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, 0x20, 0x48, 0x4f, 0x4c, 0x44, 0x45, 0x52, 0x53, 0x20, + 0x41, 0x4e, 0x44, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x4f, 0x52, 0x53, + 0x0a, 0x20, 0x22, 0x41, 0x53, 0x20, 0x49, 0x53, 0x22, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x41, 0x4e, + 0x59, 0x20, 0x45, 0x58, 0x50, 0x52, 0x45, 0x53, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x49, 0x4d, 0x50, + 0x4c, 0x49, 0x45, 0x44, 0x20, 0x57, 0x41, 0x52, 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x2c, + 0x20, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x2c, 0x20, 0x42, 0x55, 0x54, 0x20, + 0x4e, 0x4f, 0x54, 0x0a, 0x20, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x45, 0x44, 0x20, 0x54, 0x4f, 0x2c, + 0x20, 0x54, 0x48, 0x45, 0x20, 0x49, 0x4d, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x20, 0x57, 0x41, 0x52, + 0x52, 0x41, 0x4e, 0x54, 0x49, 0x45, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x4d, 0x45, 0x52, 0x43, 0x48, + 0x41, 0x4e, 0x54, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x41, 0x4e, 0x44, 0x20, 0x46, + 0x49, 0x54, 0x4e, 0x45, 0x53, 0x53, 0x20, 0x46, 0x4f, 0x52, 0x0a, 0x20, 0x41, 0x20, 0x50, 0x41, + 0x52, 0x54, 0x49, 0x43, 0x55, 0x4c, 0x41, 0x52, 0x20, 0x50, 0x55, 0x52, 0x50, 0x4f, 0x53, 0x45, + 0x20, 0x41, 0x52, 0x45, 0x20, 0x44, 0x49, 0x53, 0x43, 0x4c, 0x41, 0x49, 0x4d, 0x45, 0x44, 0x2e, + 0x20, 0x49, 0x4e, 0x20, 0x4e, 0x4f, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x54, 0x20, 0x53, 0x48, 0x41, + 0x4c, 0x4c, 0x20, 0x54, 0x48, 0x45, 0x20, 0x43, 0x4f, 0x50, 0x59, 0x52, 0x49, 0x47, 0x48, 0x54, + 0x0a, 0x20, 0x4f, 0x57, 0x4e, 0x45, 0x52, 0x20, 0x4f, 0x52, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, + 0x49, 0x42, 0x55, 0x54, 0x4f, 0x52, 0x53, 0x20, 0x42, 0x45, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x4c, + 0x45, 0x20, 0x46, 0x4f, 0x52, 0x20, 0x41, 0x4e, 0x59, 0x20, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, + 0x2c, 0x20, 0x49, 0x4e, 0x44, 0x49, 0x52, 0x45, 0x43, 0x54, 0x2c, 0x20, 0x49, 0x4e, 0x43, 0x49, + 0x44, 0x45, 0x4e, 0x54, 0x41, 0x4c, 0x2c, 0x0a, 0x20, 0x53, 0x50, 0x45, 0x43, 0x49, 0x41, 0x4c, + 0x2c, 0x20, 0x45, 0x58, 0x45, 0x4d, 0x50, 0x4c, 0x41, 0x52, 0x59, 0x2c, 0x20, 0x4f, 0x52, 0x20, + 0x43, 0x4f, 0x4e, 0x53, 0x45, 0x51, 0x55, 0x45, 0x4e, 0x54, 0x49, 0x41, 0x4c, 0x20, 0x44, 0x41, + 0x4d, 0x41, 0x47, 0x45, 0x53, 0x20, 0x28, 0x49, 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, + 0x2c, 0x20, 0x42, 0x55, 0x54, 0x20, 0x4e, 0x4f, 0x54, 0x0a, 0x20, 0x4c, 0x49, 0x4d, 0x49, 0x54, + 0x45, 0x44, 0x20, 0x54, 0x4f, 0x2c, 0x20, 0x50, 0x52, 0x4f, 0x43, 0x55, 0x52, 0x45, 0x4d, 0x45, + 0x4e, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x53, 0x55, 0x42, 0x53, 0x54, 0x49, 0x54, 0x55, 0x54, 0x45, + 0x20, 0x47, 0x4f, 0x4f, 0x44, 0x53, 0x20, 0x4f, 0x52, 0x20, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, + 0x45, 0x53, 0x3b, 0x20, 0x4c, 0x4f, 0x53, 0x53, 0x20, 0x4f, 0x46, 0x20, 0x55, 0x53, 0x45, 0x2c, + 0x0a, 0x20, 0x44, 0x41, 0x54, 0x41, 0x2c, 0x20, 0x4f, 0x52, 0x20, 0x50, 0x52, 0x4f, 0x46, 0x49, + 0x54, 0x53, 0x3b, 0x20, 0x4f, 0x52, 0x20, 0x42, 0x55, 0x53, 0x49, 0x4e, 0x45, 0x53, 0x53, 0x20, + 0x49, 0x4e, 0x54, 0x45, 0x52, 0x52, 0x55, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x29, 0x20, 0x48, 0x4f, + 0x57, 0x45, 0x56, 0x45, 0x52, 0x20, 0x43, 0x41, 0x55, 0x53, 0x45, 0x44, 0x20, 0x41, 0x4e, 0x44, + 0x20, 0x4f, 0x4e, 0x20, 0x41, 0x4e, 0x59, 0x0a, 0x20, 0x54, 0x48, 0x45, 0x4f, 0x52, 0x59, 0x20, + 0x4f, 0x46, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x2c, 0x20, 0x57, 0x48, + 0x45, 0x54, 0x48, 0x45, 0x52, 0x20, 0x49, 0x4e, 0x20, 0x43, 0x4f, 0x4e, 0x54, 0x52, 0x41, 0x43, + 0x54, 0x2c, 0x20, 0x53, 0x54, 0x52, 0x49, 0x43, 0x54, 0x20, 0x4c, 0x49, 0x41, 0x42, 0x49, 0x4c, + 0x49, 0x54, 0x59, 0x2c, 0x20, 0x4f, 0x52, 0x20, 0x54, 0x4f, 0x52, 0x54, 0x0a, 0x20, 0x28, 0x49, + 0x4e, 0x43, 0x4c, 0x55, 0x44, 0x49, 0x4e, 0x47, 0x20, 0x4e, 0x45, 0x47, 0x4c, 0x49, 0x47, 0x45, + 0x4e, 0x43, 0x45, 0x20, 0x4f, 0x52, 0x20, 0x4f, 0x54, 0x48, 0x45, 0x52, 0x57, 0x49, 0x53, 0x45, + 0x29, 0x20, 0x41, 0x52, 0x49, 0x53, 0x49, 0x4e, 0x47, 0x20, 0x49, 0x4e, 0x20, 0x41, 0x4e, 0x59, + 0x20, 0x57, 0x41, 0x59, 0x20, 0x4f, 0x55, 0x54, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x45, 0x20, + 0x55, 0x53, 0x45, 0x0a, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x49, 0x53, 0x20, 0x53, 0x4f, 0x46, + 0x54, 0x57, 0x41, 0x52, 0x45, 0x2c, 0x20, 0x45, 0x56, 0x45, 0x4e, 0x20, 0x49, 0x46, 0x20, 0x41, + 0x44, 0x56, 0x49, 0x53, 0x45, 0x44, 0x20, 0x4f, 0x46, 0x20, 0x54, 0x48, 0x45, 0x20, 0x50, 0x4f, + 0x53, 0x53, 0x49, 0x42, 0x49, 0x4c, 0x49, 0x54, 0x59, 0x20, 0x4f, 0x46, 0x20, 0x53, 0x55, 0x43, + 0x48, 0x20, 0x44, 0x41, 0x4d, 0x41, 0x47, 0x45, 0x2e, 0x0a, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, + 0x03, 0x20, 0x00, 0x18, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x22, 0x00, 0x3b, 0x0a, 0x09, + 0x0a, 0x02, 0x08, 0x25, 0x12, 0x03, 0x22, 0x00, 0x3b, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, + 0x23, 0x00, 0x43, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x23, 0x00, 0x43, 0x0a, 0x08, + 0x0a, 0x01, 0x08, 0x12, 0x03, 0x24, 0x00, 0x2c, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x01, 0x12, 0x03, + 0x24, 0x00, 0x2c, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x25, 0x00, 0x29, 0x0a, 0x09, 0x0a, + 0x02, 0x08, 0x08, 0x12, 0x03, 0x25, 0x00, 0x29, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x26, + 0x00, 0x22, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0a, 0x12, 0x03, 0x26, 0x00, 0x22, 0x0a, 0x08, 0x0a, + 0x01, 0x08, 0x12, 0x03, 0x27, 0x00, 0x21, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x24, 0x12, 0x03, 0x27, + 0x00, 0x21, 0x0a, 0xf6, 0x10, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x05, 0x7c, 0x00, 0x9d, 0x01, 0x01, + 0x1a, 0xe8, 0x10, 0x20, 0x60, 0x41, 0x6e, 0x79, 0x60, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x73, 0x20, 0x61, 0x6e, 0x20, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, 0x72, 0x79, 0x20, + 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x63, 0x6f, 0x6c, 0x20, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x61, 0x6c, 0x6f, 0x6e, 0x67, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, 0x0a, + 0x20, 0x55, 0x52, 0x4c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x6d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x0a, 0x0a, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, + 0x64, 0x65, 0x73, 0x20, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x70, + 0x61, 0x63, 0x6b, 0x2f, 0x75, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x6f, 0x72, + 0x6d, 0x0a, 0x20, 0x6f, 0x66, 0x20, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x66, 0x75, + 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, + 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x20, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x41, + 0x6e, 0x79, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, + 0x6c, 0x65, 0x20, 0x31, 0x3a, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, + 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, + 0x69, 0x6e, 0x20, 0x43, 0x2b, 0x2b, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x46, 0x6f, + 0x6f, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x3d, 0x20, 0x2e, 0x2e, 0x2e, 0x3b, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x61, 0x6e, 0x79, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x61, 0x6e, 0x79, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x46, 0x72, 0x6f, 0x6d, 0x28, 0x66, 0x6f, 0x6f, + 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x69, 0x66, 0x20, 0x28, 0x61, 0x6e, 0x79, 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x54, + 0x6f, 0x28, 0x26, 0x66, 0x6f, 0x6f, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x45, + 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x32, 0x3a, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x75, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x4a, 0x61, 0x76, 0x61, 0x2e, 0x0a, 0x0a, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x46, 0x6f, 0x6f, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x3d, 0x20, 0x2e, 0x2e, 0x2e, + 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x41, 0x6e, 0x79, 0x20, 0x61, 0x6e, 0x79, 0x20, 0x3d, + 0x20, 0x41, 0x6e, 0x79, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x28, 0x66, 0x6f, 0x6f, 0x29, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, + 0x20, 0x28, 0x61, 0x6e, 0x79, 0x2e, 0x69, 0x73, 0x28, 0x46, 0x6f, 0x6f, 0x2e, 0x63, 0x6c, 0x61, + 0x73, 0x73, 0x29, 0x29, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, + 0x6f, 0x20, 0x3d, 0x20, 0x61, 0x6e, 0x79, 0x2e, 0x75, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x28, 0x46, + 0x6f, 0x6f, 0x2e, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x29, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x7d, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x33, 0x3a, 0x20, 0x50, + 0x61, 0x63, 0x6b, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x20, 0x61, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x50, 0x79, 0x74, 0x68, + 0x6f, 0x6e, 0x2e, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x3d, 0x20, + 0x46, 0x6f, 0x6f, 0x28, 0x2e, 0x2e, 0x2e, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6e, + 0x79, 0x20, 0x3d, 0x20, 0x41, 0x6e, 0x79, 0x28, 0x29, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, + 0x6e, 0x79, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x28, 0x66, 0x6f, 0x6f, 0x29, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x61, 0x6e, + 0x79, 0x2e, 0x49, 0x73, 0x28, 0x46, 0x6f, 0x6f, 0x2e, 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x50, + 0x54, 0x4f, 0x52, 0x29, 0x3a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6e, 0x79, + 0x2e, 0x55, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x28, 0x66, 0x6f, 0x6f, 0x29, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x0a, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x20, 0x34, 0x3a, 0x20, 0x50, 0x61, 0x63, 0x6b, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x75, 0x6e, + 0x70, 0x61, 0x63, 0x6b, 0x20, 0x61, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x69, + 0x6e, 0x20, 0x47, 0x6f, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6f, 0x20, + 0x3a, 0x3d, 0x20, 0x26, 0x70, 0x62, 0x2e, 0x46, 0x6f, 0x6f, 0x7b, 0x2e, 0x2e, 0x2e, 0x7d, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x61, 0x6e, 0x79, 0x2c, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, + 0x3d, 0x20, 0x61, 0x6e, 0x79, 0x70, 0x62, 0x2e, 0x4e, 0x65, 0x77, 0x28, 0x66, 0x6f, 0x6f, 0x29, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x66, 0x20, 0x65, 0x72, 0x72, 0x20, 0x21, 0x3d, + 0x20, 0x6e, 0x69, 0x6c, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, + 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x66, 0x6f, 0x6f, 0x20, 0x3a, + 0x3d, 0x20, 0x26, 0x70, 0x62, 0x2e, 0x46, 0x6f, 0x6f, 0x7b, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x69, 0x66, 0x20, 0x65, 0x72, 0x72, 0x20, 0x3a, 0x3d, 0x20, 0x61, 0x6e, 0x79, 0x2e, + 0x55, 0x6e, 0x6d, 0x61, 0x72, 0x73, 0x68, 0x61, 0x6c, 0x54, 0x6f, 0x28, 0x66, 0x6f, 0x6f, 0x29, + 0x3b, 0x20, 0x65, 0x72, 0x72, 0x20, 0x21, 0x3d, 0x20, 0x6e, 0x69, 0x6c, 0x20, 0x7b, 0x0a, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x20, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x73, 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x20, 0x62, + 0x79, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x20, 0x6c, 0x69, 0x62, 0x72, 0x61, + 0x72, 0x79, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x79, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, + 0x6c, 0x74, 0x20, 0x75, 0x73, 0x65, 0x0a, 0x20, 0x27, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x66, 0x75, 0x6c, + 0x6c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x6e, 0x61, 0x6d, 0x65, 0x27, 0x20, 0x61, 0x73, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x20, 0x61, 0x6e, 0x64, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x75, 0x6e, 0x70, 0x61, 0x63, 0x6b, 0x0a, 0x20, 0x6d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x73, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x20, 0x71, 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, + 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x61, 0x66, 0x74, 0x65, + 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x20, 0x27, 0x2f, 0x27, 0x0a, 0x20, + 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x2c, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x22, 0x66, 0x6f, + 0x6f, 0x2e, 0x62, 0x61, 0x72, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x78, 0x2f, 0x79, 0x2e, 0x7a, 0x22, + 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x79, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, + 0x0a, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x22, 0x79, 0x2e, 0x7a, 0x22, 0x2e, 0x0a, 0x0a, 0x0a, + 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x0a, 0x0a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x4a, 0x53, 0x4f, 0x4e, + 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, + 0x6f, 0x66, 0x20, 0x61, 0x6e, 0x20, 0x60, 0x41, 0x6e, 0x79, 0x60, 0x20, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x20, 0x75, 0x73, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x65, 0x67, 0x75, 0x6c, + 0x61, 0x72, 0x0a, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x73, 0x65, 0x72, 0x69, + 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x2c, 0x20, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, + 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x61, + 0x6e, 0x0a, 0x20, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x20, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x60, 0x40, 0x74, 0x79, 0x70, 0x65, 0x60, 0x20, 0x77, 0x68, 0x69, 0x63, + 0x68, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x2e, 0x20, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x20, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x3b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x50, 0x65, 0x72, + 0x73, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x72, + 0x69, 0x6e, 0x67, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x3d, + 0x20, 0x31, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, + 0x67, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x3d, 0x20, 0x32, 0x3b, + 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x40, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, + 0x22, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x66, + 0x69, 0x6c, 0x65, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x22, 0x66, 0x69, 0x72, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, + 0x20, 0x3c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x22, 0x6c, 0x61, 0x73, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x22, 0x3a, 0x20, 0x3c, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, + 0x49, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x20, + 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x69, 0x73, 0x20, + 0x77, 0x65, 0x6c, 0x6c, 0x2d, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x68, + 0x61, 0x73, 0x20, 0x61, 0x20, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x4a, 0x53, 0x4f, 0x4e, + 0x0a, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x2c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x62, 0x65, 0x20, 0x65, 0x6d, + 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x20, 0x61, 0x64, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x20, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x0a, 0x20, 0x60, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x60, 0x20, 0x77, + 0x68, 0x69, 0x63, 0x68, 0x20, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, + 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x20, 0x4a, 0x53, 0x4f, 0x4e, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x64, + 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x60, 0x40, + 0x74, 0x79, 0x70, 0x65, 0x60, 0x0a, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x2e, 0x20, 0x45, 0x78, + 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x28, 0x66, 0x6f, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x20, 0x5b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5d, 0x5b, 0x5d, 0x29, + 0x3a, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x40, 0x74, 0x79, 0x70, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, + 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x22, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a, 0x20, 0x22, 0x31, 0x2e, 0x32, 0x31, 0x32, + 0x73, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x00, 0x01, 0x12, 0x03, 0x7c, 0x08, 0x0b, 0x0a, 0xd7, 0x0a, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, + 0x12, 0x04, 0x99, 0x01, 0x02, 0x16, 0x1a, 0xc8, 0x0a, 0x20, 0x41, 0x20, 0x55, 0x52, 0x4c, 0x2f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x74, 0x68, + 0x61, 0x74, 0x20, 0x75, 0x6e, 0x69, 0x71, 0x75, 0x65, 0x6c, 0x79, 0x20, 0x69, 0x64, 0x65, 0x6e, + 0x74, 0x69, 0x66, 0x69, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, + 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, + 0x64, 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20, 0x62, 0x75, 0x66, 0x66, + 0x65, 0x72, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x20, 0x54, 0x68, 0x69, 0x73, + 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x20, 0x61, 0x74, 0x20, 0x6c, 0x65, 0x61, 0x73, 0x74, 0x0a, 0x20, 0x6f, + 0x6e, 0x65, 0x20, 0x22, 0x2f, 0x22, 0x20, 0x63, 0x68, 0x61, 0x72, 0x61, 0x63, 0x74, 0x65, 0x72, + 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x20, 0x73, 0x65, 0x67, 0x6d, 0x65, + 0x6e, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x27, 0x73, 0x20, + 0x70, 0x61, 0x74, 0x68, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, + 0x65, 0x6e, 0x74, 0x0a, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x6c, 0x6c, 0x79, 0x20, 0x71, + 0x75, 0x61, 0x6c, 0x69, 0x66, 0x69, 0x65, 0x64, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x6f, 0x66, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x28, 0x61, 0x73, 0x20, 0x69, 0x6e, + 0x0a, 0x20, 0x60, 0x70, 0x61, 0x74, 0x68, 0x2f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x60, 0x29, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x63, 0x61, 0x6e, 0x6f, + 0x6e, 0x69, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x0a, 0x20, 0x28, 0x65, 0x2e, 0x67, + 0x2e, 0x2c, 0x20, 0x6c, 0x65, 0x61, 0x64, 0x69, 0x6e, 0x67, 0x20, 0x22, 0x2e, 0x22, 0x20, 0x69, + 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x29, 0x2e, + 0x0a, 0x0a, 0x20, 0x49, 0x6e, 0x20, 0x70, 0x72, 0x61, 0x63, 0x74, 0x69, 0x63, 0x65, 0x2c, 0x20, + 0x74, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x75, 0x73, 0x75, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x70, 0x72, + 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x74, 0x68, + 0x65, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x68, 0x65, 0x79, 0x0a, 0x20, 0x65, 0x78, + 0x70, 0x65, 0x63, 0x74, 0x20, 0x69, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x69, + 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x20, 0x6f, 0x66, + 0x20, 0x41, 0x6e, 0x79, 0x2e, 0x20, 0x48, 0x6f, 0x77, 0x65, 0x76, 0x65, 0x72, 0x2c, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x55, 0x52, 0x4c, 0x73, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20, 0x75, 0x73, + 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x20, 0x60, 0x68, + 0x74, 0x74, 0x70, 0x60, 0x2c, 0x20, 0x60, 0x68, 0x74, 0x74, 0x70, 0x73, 0x60, 0x2c, 0x20, 0x6f, + 0x72, 0x20, 0x6e, 0x6f, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x2c, 0x20, 0x6f, 0x6e, 0x65, + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x20, + 0x73, 0x65, 0x74, 0x20, 0x75, 0x70, 0x20, 0x61, 0x20, 0x74, 0x79, 0x70, 0x65, 0x0a, 0x20, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6d, 0x61, 0x70, 0x73, 0x20, + 0x74, 0x79, 0x70, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x20, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x20, 0x61, 0x73, 0x20, 0x66, 0x6f, 0x6c, 0x6c, 0x6f, 0x77, 0x73, 0x3a, 0x0a, 0x0a, 0x20, 0x2a, + 0x20, 0x49, 0x66, 0x20, 0x6e, 0x6f, 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x20, 0x69, 0x73, + 0x20, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x60, 0x68, 0x74, 0x74, 0x70, + 0x73, 0x60, 0x20, 0x69, 0x73, 0x20, 0x61, 0x73, 0x73, 0x75, 0x6d, 0x65, 0x64, 0x2e, 0x0a, 0x20, + 0x2a, 0x20, 0x41, 0x6e, 0x20, 0x48, 0x54, 0x54, 0x50, 0x20, 0x47, 0x45, 0x54, 0x20, 0x6f, 0x6e, + 0x20, 0x74, 0x68, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x20, 0x6d, 0x75, 0x73, 0x74, 0x20, 0x79, 0x69, + 0x65, 0x6c, 0x64, 0x20, 0x61, 0x20, 0x5b, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x79, 0x70, 0x65, 0x5d, 0x5b, 0x5d, 0x0a, 0x20, + 0x20, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, + 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x65, 0x20, 0x61, 0x6e, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x2e, 0x0a, 0x20, + 0x2a, 0x20, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, + 0x72, 0x65, 0x20, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x63, 0x61, + 0x63, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x20, 0x72, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x73, 0x20, 0x62, 0x61, 0x73, 0x65, 0x64, 0x20, 0x6f, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x0a, + 0x20, 0x20, 0x20, 0x55, 0x52, 0x4c, 0x2c, 0x20, 0x6f, 0x72, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, + 0x74, 0x68, 0x65, 0x6d, 0x20, 0x70, 0x72, 0x65, 0x63, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x64, + 0x20, 0x69, 0x6e, 0x74, 0x6f, 0x20, 0x61, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x74, + 0x6f, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x61, 0x6e, 0x79, 0x0a, 0x20, 0x20, 0x20, 0x6c, + 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x66, 0x6f, 0x72, 0x65, + 0x2c, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, + 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x73, 0x20, 0x74, 0x6f, 0x20, + 0x62, 0x65, 0x20, 0x70, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x0a, 0x20, 0x20, 0x20, + 0x6f, 0x6e, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x20, 0x28, 0x55, 0x73, 0x65, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x65, 0x64, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x20, 0x74, + 0x6f, 0x20, 0x6d, 0x61, 0x6e, 0x61, 0x67, 0x65, 0x0a, 0x20, 0x20, 0x20, 0x62, 0x72, 0x65, 0x61, + 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x2e, 0x29, 0x0a, 0x0a, + 0x20, 0x4e, 0x6f, 0x74, 0x65, 0x3a, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x75, 0x6e, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, 0x74, + 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x61, 0x76, 0x61, 0x69, 0x6c, + 0x61, 0x62, 0x6c, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6f, 0x66, 0x66, 0x69, + 0x63, 0x69, 0x61, 0x6c, 0x0a, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x20, 0x72, + 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x69, + 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, + 0x79, 0x70, 0x65, 0x20, 0x55, 0x52, 0x4c, 0x73, 0x20, 0x62, 0x65, 0x67, 0x69, 0x6e, 0x6e, 0x69, + 0x6e, 0x67, 0x20, 0x77, 0x69, 0x74, 0x68, 0x0a, 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x67, 0x6f, + 0x6f, 0x67, 0x6c, 0x65, 0x61, 0x70, 0x69, 0x73, 0x2e, 0x63, 0x6f, 0x6d, 0x2e, 0x0a, 0x0a, 0x20, + 0x53, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x73, 0x20, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x20, 0x74, 0x68, + 0x61, 0x6e, 0x20, 0x60, 0x68, 0x74, 0x74, 0x70, 0x60, 0x2c, 0x20, 0x60, 0x68, 0x74, 0x74, 0x70, + 0x73, 0x60, 0x20, 0x28, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x6d, 0x70, 0x74, 0x79, + 0x20, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x65, 0x29, 0x20, 0x6d, 0x69, 0x67, 0x68, 0x74, 0x20, 0x62, + 0x65, 0x0a, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x69, 0x6d, 0x70, + 0x6c, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x20, 0x73, 0x65, 0x6d, 0x61, 0x6e, 0x74, 0x69, 0x63, 0x73, 0x2e, 0x0a, + 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x04, 0x99, 0x01, 0x02, 0x08, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0x99, 0x01, 0x09, 0x11, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x04, 0x99, 0x01, 0x14, 0x15, 0x0a, 0x57, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x04, 0x9c, 0x01, 0x02, 0x12, 0x1a, 0x49, 0x20, 0x4d, + 0x75, 0x73, 0x74, 0x20, 0x62, 0x65, 0x20, 0x61, 0x20, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x20, 0x73, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, + 0x6f, 0x6c, 0x20, 0x62, 0x75, 0x66, 0x66, 0x65, 0x72, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x61, 0x62, 0x6f, 0x76, 0x65, 0x20, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, + 0x20, 0x74, 0x79, 0x70, 0x65, 0x2e, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, + 0x12, 0x04, 0x9c, 0x01, 0x02, 0x07, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, + 0x04, 0x9c, 0x01, 0x08, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x04, + 0x9c, 0x01, 0x10, 0x11, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +]; +// @@protoc_insertion_point(module) \ No newline at end of file diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/mod.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/mod.rs new file mode 100644 index 0000000..20abd31 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/mod.rs @@ -0,0 +1,4 @@ +#[allow(unused_imports)] +#[allow(dead_code)] +#[path = "./my.types.v1.rs"] +pub mod my_types_v1; diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/my.types.v1.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/my.types.v1.rs new file mode 100644 index 0000000..69885a0 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/my.types.v1.rs @@ -0,0 +1,53 @@ +// @generated +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Tests { + #[prost(message, repeated, tag="1")] + pub tests: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Test { + #[prost(string, tag="1")] + pub field1: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Number { + #[prost(int64, tag="1")] + pub field1: i64, +} +/// Encoded file descriptor set for the `my.types.v1` package +pub const FILE_DESCRIPTOR_SET: &[u8] = &[ + 0x0a, 0x86, 0x04, 0x0a, 0x0e, 0x6d, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x79, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x12, 0x0b, 0x6d, 0x79, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x1a, 0x1e, 0x6d, 0x79, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x2d, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x22, 0x30, 0x0a, 0x05, 0x54, 0x65, 0x73, 0x74, 0x73, 0x12, 0x27, 0x0a, 0x05, 0x74, 0x65, 0x73, + 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6d, 0x79, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x05, 0x74, 0x65, 0x73, + 0x74, 0x73, 0x22, 0x1e, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, + 0x64, 0x31, 0x22, 0x20, 0x0a, 0x06, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, + 0x66, 0x69, 0x65, 0x6c, 0x64, 0x31, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x66, 0x69, + 0x65, 0x6c, 0x64, 0x31, 0x42, 0x0f, 0xea, 0xdc, 0x1b, 0x0b, 0x6d, 0x79, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x5f, 0x76, 0x31, 0x4a, 0xb9, 0x02, 0x0a, 0x06, 0x12, 0x04, 0x00, 0x00, 0x10, 0x01, + 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, + 0x12, 0x03, 0x01, 0x00, 0x28, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x02, 0x00, 0x14, 0x0a, + 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x04, 0x00, 0x25, 0x0a, 0x0b, 0x0a, 0x04, 0x08, 0xcd, 0xbb, + 0x03, 0x12, 0x03, 0x04, 0x00, 0x25, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x06, 0x00, + 0x08, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x06, 0x08, 0x0d, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x07, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x07, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x00, 0x06, 0x12, 0x03, 0x07, 0x0b, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x07, 0x10, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x07, 0x18, 0x19, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x0a, 0x00, 0x0c, 0x01, 0x0a, + 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x0a, 0x08, 0x0c, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x01, 0x02, 0x00, 0x12, 0x03, 0x0b, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, + 0x05, 0x12, 0x03, 0x0b, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x0b, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0b, + 0x12, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x0e, 0x00, 0x10, 0x01, 0x0a, 0x0a, + 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x0e, 0x08, 0x0e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, + 0x02, 0x00, 0x12, 0x03, 0x0f, 0x02, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, + 0x12, 0x03, 0x0f, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, + 0x0f, 0x08, 0x0e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0f, 0x11, + 0x12, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +]; +// @@protoc_insertion_point(module) \ No newline at end of file diff --git a/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/sf.substreams.v1.rs b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/sf.substreams.v1.rs new file mode 100644 index 0000000..b360ef0 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/expected_test_outputs/pb/sf.substreams.v1.rs @@ -0,0 +1,1669 @@ +// @generated +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Clock { + #[prost(string, tag="1")] + pub id: ::prost::alloc::string::String, + #[prost(uint64, tag="2")] + pub number: u64, + #[prost(message, optional, tag="3")] + pub timestamp: ::core::option::Option<::prost_types::Timestamp>, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Modules { + #[prost(message, repeated, tag="1")] + pub modules: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="2")] + pub binaries: ::prost::alloc::vec::Vec, +} +/// Binary represents some code compiled to its binary form. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Binary { + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + #[prost(bytes="vec", tag="2")] + pub content: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Module { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(uint32, tag="4")] + pub binary_index: u32, + #[prost(string, tag="5")] + pub binary_entrypoint: ::prost::alloc::string::String, + #[prost(message, repeated, tag="6")] + pub inputs: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="7")] + pub output: ::core::option::Option, + #[prost(uint64, tag="8")] + pub initial_block: u64, + #[prost(oneof="module::Kind", tags="2, 3")] + pub kind: ::core::option::Option, +} +/// Nested message and enum types in `Module`. +pub mod module { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct KindMap { + #[prost(string, tag="1")] + pub output_type: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct KindStore { + /// The `update_policy` determines the functions available to mutate the store + /// (like `set()`, `set_if_not_exists()` or `sum()`, etc..) in + /// order to ensure that parallel operations are possible and deterministic + /// + /// Say a store cumulates keys from block 0 to 1M, and a second store + /// cumulates keys from block 1M to 2M. When we want to use this + /// store as a dependency for a downstream module, we will merge the + /// two stores according to this policy. + #[prost(enumeration="kind_store::UpdatePolicy", tag="1")] + pub update_policy: i32, + #[prost(string, tag="2")] + pub value_type: ::prost::alloc::string::String, + } + /// Nested message and enum types in `KindStore`. + pub mod kind_store { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum UpdatePolicy { + Unset = 0, + /// Provides a store where you can `set()` keys, and the latest key wins + Set = 1, + /// Provides a store where you can `set_if_not_exists()` keys, and the first key wins + SetIfNotExists = 2, + /// Provides a store where you can `add_*()` keys, where two stores merge by summing its values. + Add = 3, + /// Provides a store where you can `min_*()` keys, where two stores merge by leaving the minimum value. + Min = 4, + /// Provides a store where you can `max_*()` keys, where two stores merge by leaving the maximum value. + Max = 5, + /// Provides a store where you can `append()` keys, where two stores merge by concatenating the bytes in order. + Append = 6, + } + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Input { + #[prost(oneof="input::Input", tags="1, 2, 3, 4")] + pub input: ::core::option::Option, + } + /// Nested message and enum types in `Input`. + pub mod input { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Source { + /// ex: "sf.ethereum.type.v1.Block" + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Map { + /// ex: "block_to_pairs" + #[prost(string, tag="1")] + pub module_name: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Store { + #[prost(string, tag="1")] + pub module_name: ::prost::alloc::string::String, + #[prost(enumeration="store::Mode", tag="2")] + pub mode: i32, + } + /// Nested message and enum types in `Store`. + pub mod store { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Mode { + Unset = 0, + Get = 1, + Deltas = 2, + } + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Params { + #[prost(string, tag="1")] + pub value: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Input { + #[prost(message, tag="1")] + Source(Source), + #[prost(message, tag="2")] + Map(Map), + #[prost(message, tag="3")] + Store(Store), + #[prost(message, tag="4")] + Params(Params), + } + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Output { + #[prost(string, tag="1")] + pub r#type: ::prost::alloc::string::String, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Kind { + #[prost(message, tag="2")] + KindMap(KindMap), + #[prost(message, tag="3")] + KindStore(KindStore), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Package { + /// Needs to be one so this file can be used _directly_ as a + /// buf `Image` andor a ProtoSet for grpcurl and other tools + #[prost(message, repeated, tag="1")] + pub proto_files: ::prost::alloc::vec::Vec<::prost_types::FileDescriptorProto>, + #[prost(uint64, tag="5")] + pub version: u64, + #[prost(message, optional, tag="6")] + pub modules: ::core::option::Option, + #[prost(message, repeated, tag="7")] + pub module_meta: ::prost::alloc::vec::Vec, + #[prost(message, repeated, tag="8")] + pub package_meta: ::prost::alloc::vec::Vec, + /// Source network for Substreams to fetch its data from. + #[prost(string, tag="9")] + pub network: ::prost::alloc::string::String, + #[prost(message, optional, tag="10")] + pub sink_config: ::core::option::Option<::prost_types::Any>, + #[prost(string, tag="11")] + pub sink_module: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PackageMetadata { + #[prost(string, tag="1")] + pub version: ::prost::alloc::string::String, + #[prost(string, tag="2")] + pub url: ::prost::alloc::string::String, + #[prost(string, tag="3")] + pub name: ::prost::alloc::string::String, + #[prost(string, tag="4")] + pub doc: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ModuleMetadata { + /// Corresponds to the index in `Package.metadata.package_meta` + #[prost(uint64, tag="1")] + pub package_index: u64, + #[prost(string, tag="2")] + pub doc: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Request { + #[prost(int64, tag="1")] + pub start_block_num: i64, + #[prost(string, tag="2")] + pub start_cursor: ::prost::alloc::string::String, + #[prost(uint64, tag="3")] + pub stop_block_num: u64, + #[prost(enumeration="ForkStep", repeated, tag="4")] + pub fork_steps: ::prost::alloc::vec::Vec, + #[prost(string, tag="5")] + pub irreversibility_condition: ::prost::alloc::string::String, + /// Substreams has two mode when executing your module(s) either development mode or production + /// mode. Development and production modes impact the execution of Substreams, important aspects + /// of execution include: + /// * The time required to reach the first byte. + /// * The speed that large ranges get executed. + /// * The module logs and outputs sent back to the client. + /// + /// By default, the engine runs in developer mode, with richer and deeper output. Differences + /// between production and development modes include: + /// * Forward parallel execution is enabled in production mode and disabled in development mode + /// * The time required to reach the first byte in development mode is faster than in production mode. + /// + /// Specific attributes of development mode include: + /// * The client will receive all of the executed module's logs. + /// * It's possible to request specific store snapshots in the execution tree (via `debug_initial_store_snapshot_for_modules`). + /// * Multiple module's output is possible. + /// + /// With production mode`, however, you trade off functionality for high speed enabling forward + /// parallel execution of module ahead of time. + #[prost(bool, tag="9")] + pub production_mode: bool, + #[prost(message, optional, tag="6")] + pub modules: ::core::option::Option, + #[prost(string, repeated, tag="7")] + pub output_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// Available only in developer mode + #[prost(string, repeated, tag="8")] + pub debug_initial_store_snapshot_for_modules: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + #[prost(string, tag="10")] + pub output_module: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Response { + #[prost(oneof="response::Message", tags="5, 1, 2, 3, 4")] + pub message: ::core::option::Option, +} +/// Nested message and enum types in `Response`. +pub mod response { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Message { + /// Always sent first + #[prost(message, tag="5")] + Session(super::SessionInit), + /// Progress of data preparation, before sending in the stream of `data` events. + #[prost(message, tag="1")] + Progress(super::ModulesProgress), + /// Available only in developer mode, and only if `debug_initial_store_snapshot_for_modules` is set. + #[prost(message, tag="2")] + DebugSnapshotData(super::InitialSnapshotData), + /// Available only in developer mode, and only if `debug_initial_store_snapshot_for_modules` is set. + #[prost(message, tag="3")] + DebugSnapshotComplete(super::InitialSnapshotComplete), + #[prost(message, tag="4")] + Data(super::BlockScopedData), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct SessionInit { + #[prost(string, tag="1")] + pub trace_id: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InitialSnapshotComplete { + #[prost(string, tag="1")] + pub cursor: ::prost::alloc::string::String, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct InitialSnapshotData { + #[prost(string, tag="1")] + pub module_name: ::prost::alloc::string::String, + #[prost(message, optional, tag="2")] + pub deltas: ::core::option::Option, + #[prost(uint64, tag="4")] + pub sent_keys: u64, + #[prost(uint64, tag="3")] + pub total_keys: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockScopedData { + #[prost(message, repeated, tag="1")] + pub outputs: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="3")] + pub clock: ::core::option::Option, + #[prost(enumeration="ForkStep", tag="6")] + pub step: i32, + #[prost(string, tag="10")] + pub cursor: ::prost::alloc::string::String, + /// Potentially non-deterministic. Reserved for future use. + #[prost(uint64, tag="11")] + pub final_block_height: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ModuleOutput { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(string, repeated, tag="4")] + pub debug_logs: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// LogsTruncated is a flag that tells you if you received all the logs or if they + /// were truncated because you logged too much (fixed limit currently is set to 128 KiB). + #[prost(bool, tag="5")] + pub debug_logs_truncated: bool, + #[prost(bool, tag="6")] + pub cached: bool, + #[prost(oneof="module_output::Data", tags="2, 3")] + pub data: ::core::option::Option, +} +/// Nested message and enum types in `ModuleOutput`. +pub mod module_output { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Data { + #[prost(message, tag="2")] + MapOutput(::prost_types::Any), + /// StoreDeltas are produced for store modules in development mode. + /// It is not possible to retrieve store models in production, with parallelization + /// enabled. If you need the deltas directly, write a pass through mapper module + /// that will get them down to you. + #[prost(message, tag="3")] + DebugStoreDeltas(super::StoreDeltas), + } +} +// think about: +// message ModuleOutput { ... +// ModuleOutputDebug debug_info = 6; +// ...} +//message ModuleOutputDebug { +// StoreDeltas store_deltas = 3; +// repeated string logs = 4; +// // LogsTruncated is a flag that tells you if you received all the logs or if they +// // were truncated because you logged too much (fixed limit currently is set to 128 KiB). +// bool logs_truncated = 5; +//} + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ModulesProgress { + #[prost(message, repeated, tag="1")] + pub modules: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ModuleProgress { + #[prost(string, tag="1")] + pub name: ::prost::alloc::string::String, + #[prost(oneof="module_progress::Type", tags="2, 3, 4, 5")] + pub r#type: ::core::option::Option, +} +/// Nested message and enum types in `ModuleProgress`. +pub mod module_progress { + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct ProcessedRange { + #[prost(message, repeated, tag="1")] + pub processed_ranges: ::prost::alloc::vec::Vec, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct InitialState { + #[prost(uint64, tag="2")] + pub available_up_to_block: u64, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct ProcessedBytes { + #[prost(uint64, tag="1")] + pub total_bytes_read: u64, + #[prost(uint64, tag="2")] + pub total_bytes_written: u64, + #[prost(uint64, tag="3")] + pub bytes_read_delta: u64, + #[prost(uint64, tag="4")] + pub bytes_written_delta: u64, + #[prost(uint64, tag="5")] + pub nano_seconds_delta: u64, + } + #[derive(Clone, PartialEq, ::prost::Message)] + pub struct Failed { + #[prost(string, tag="1")] + pub reason: ::prost::alloc::string::String, + #[prost(string, repeated, tag="2")] + pub logs: ::prost::alloc::vec::Vec<::prost::alloc::string::String>, + /// FailureLogsTruncated is a flag that tells you if you received all the logs or if they + /// were truncated because you logged too much (fixed limit currently is set to 128 KiB). + #[prost(bool, tag="3")] + pub logs_truncated: bool, + } + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Type { + #[prost(message, tag="2")] + ProcessedRanges(ProcessedRange), + #[prost(message, tag="3")] + InitialState(InitialState), + #[prost(message, tag="4")] + ProcessedBytes(ProcessedBytes), + #[prost(message, tag="5")] + Failed(Failed), + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct BlockRange { + #[prost(uint64, tag="2")] + pub start_block: u64, + #[prost(uint64, tag="3")] + pub end_block: u64, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StoreDeltas { + #[prost(message, repeated, tag="1")] + pub deltas: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct StoreDelta { + #[prost(enumeration="store_delta::Operation", tag="1")] + pub operation: i32, + #[prost(uint64, tag="2")] + pub ordinal: u64, + #[prost(string, tag="3")] + pub key: ::prost::alloc::string::String, + #[prost(bytes="vec", tag="4")] + pub old_value: ::prost::alloc::vec::Vec, + #[prost(bytes="vec", tag="5")] + pub new_value: ::prost::alloc::vec::Vec, +} +/// Nested message and enum types in `StoreDelta`. +pub mod store_delta { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] + #[repr(i32)] + pub enum Operation { + Unset = 0, + Create = 1, + Update = 2, + Delete = 3, + } +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Output { + #[prost(uint64, tag="1")] + pub block_num: u64, + #[prost(string, tag="2")] + pub block_id: ::prost::alloc::string::String, + #[prost(message, optional, tag="4")] + pub timestamp: ::core::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag="10")] + pub value: ::core::option::Option<::prost_types::Any>, +} +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] +#[repr(i32)] +pub enum ForkStep { + StepUnknown = 0, + /// Block is new head block of the chain, that is linear with the previous block + StepNew = 1, + /// Block is now forked and should be undone, it's not the head block of the chain anymore + StepUndo = 2, + /// Block is now irreversible and can be committed to (finality is chain specific, see chain documentation for more details) + StepIrreversible = 4, +} +/// Encoded file descriptor set for the `sf.substreams.v1` package +pub const FILE_DESCRIPTOR_SET: &[u8] = &[ + 0x0a, 0x88, 0x04, 0x0a, 0x1c, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x12, 0x10, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, + 0x2e, 0x76, 0x31, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x69, 0x0a, 0x05, 0x43, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x0e, 0x0a, + 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, + 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, + 0x6d, 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x42, + 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x75, 0x62, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x70, 0x62, 0x73, 0x75, 0x62, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x4a, 0xf9, 0x01, 0x0a, 0x06, 0x12, 0x04, 0x00, 0x00, + 0x0b, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x08, 0x0a, 0x01, + 0x02, 0x12, 0x03, 0x02, 0x00, 0x19, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x03, 0x00, 0x5b, + 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x03, 0x00, 0x5b, 0x0a, 0x09, 0x0a, 0x02, 0x03, + 0x00, 0x12, 0x03, 0x05, 0x00, 0x29, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x07, 0x00, + 0x0b, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, 0x03, 0x07, 0x08, 0x0d, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x08, 0x02, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x08, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x00, 0x01, 0x12, 0x03, 0x08, 0x09, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, + 0x12, 0x03, 0x08, 0x0e, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x09, + 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x09, 0x02, 0x08, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x09, 0x09, 0x0f, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x09, 0x12, 0x13, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x0a, 0x02, 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x02, 0x06, 0x12, 0x03, 0x0a, 0x02, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, + 0x12, 0x03, 0x0a, 0x1c, 0x25, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, + 0x0a, 0x28, 0x29, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xee, 0x29, 0x0a, 0x1e, + 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, + 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, + 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, + 0x22, 0x73, 0x0a, 0x07, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x73, + 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, + 0x34, 0x0a, 0x08, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x18, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x52, 0x08, 0x62, 0x69, 0x6e, + 0x61, 0x72, 0x69, 0x65, 0x73, 0x22, 0x36, 0x0a, 0x06, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x12, + 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, + 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x22, 0xa3, 0x0a, + 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x08, + 0x6b, 0x69, 0x6e, 0x64, 0x5f, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, + 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, + 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x70, + 0x48, 0x00, 0x52, 0x07, 0x6b, 0x69, 0x6e, 0x64, 0x4d, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x0a, 0x6b, + 0x69, 0x6e, 0x64, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x48, 0x00, 0x52, 0x09, 0x6b, 0x69, 0x6e, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, + 0x12, 0x21, 0x0a, 0x0c, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0b, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x49, 0x6e, + 0x64, 0x65, 0x78, 0x12, 0x2b, 0x0a, 0x11, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x5f, 0x65, 0x6e, + 0x74, 0x72, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x10, + 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x70, 0x6f, 0x69, 0x6e, 0x74, + 0x12, 0x36, 0x0a, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1e, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, + 0x52, 0x06, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x37, 0x0a, 0x06, 0x6f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, + 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x1a, 0x2a, 0x0a, 0x07, 0x4b, 0x69, 0x6e, 0x64, 0x4d, 0x61, + 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x1a, 0xc5, 0x02, 0x0a, 0x09, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, + 0x12, 0x54, 0x0a, 0x0d, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x2e, 0x4b, 0x69, 0x6e, 0x64, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x0c, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, 0xc2, 0x01, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x17, 0x0a, 0x13, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, + 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, + 0x15, 0x0a, 0x11, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, + 0x5f, 0x53, 0x45, 0x54, 0x10, 0x01, 0x12, 0x23, 0x0a, 0x1f, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, + 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x53, 0x45, 0x54, 0x5f, 0x49, 0x46, 0x5f, 0x4e, + 0x4f, 0x54, 0x5f, 0x45, 0x58, 0x49, 0x53, 0x54, 0x53, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x55, + 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x41, 0x44, 0x44, + 0x10, 0x03, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x4f, 0x4c, + 0x49, 0x43, 0x59, 0x5f, 0x4d, 0x49, 0x4e, 0x10, 0x04, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x50, 0x44, + 0x41, 0x54, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x5f, 0x4d, 0x41, 0x58, 0x10, 0x05, + 0x12, 0x18, 0x0a, 0x14, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, + 0x59, 0x5f, 0x41, 0x50, 0x50, 0x45, 0x4e, 0x44, 0x10, 0x06, 0x1a, 0x80, 0x04, 0x0a, 0x05, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x12, 0x3f, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x49, + 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x00, 0x52, 0x06, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x36, 0x0a, 0x03, 0x6d, 0x61, 0x70, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x49, 0x6e, 0x70, + 0x75, 0x74, 0x2e, 0x4d, 0x61, 0x70, 0x48, 0x00, 0x52, 0x03, 0x6d, 0x61, 0x70, 0x12, 0x3c, 0x0a, + 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, + 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x48, 0x00, 0x52, 0x05, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x3f, 0x0a, 0x06, 0x70, + 0x61, 0x72, 0x61, 0x6d, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x66, + 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x50, 0x61, 0x72, 0x61, + 0x6d, 0x73, 0x48, 0x00, 0x52, 0x06, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x1a, 0x1c, 0x0a, 0x06, + 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x1a, 0x26, 0x0a, 0x03, 0x4d, 0x61, + 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4e, 0x61, + 0x6d, 0x65, 0x1a, 0x8f, 0x01, 0x0a, 0x05, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x3d, 0x0a, + 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x29, 0x2e, 0x73, 0x66, + 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x2e, 0x53, 0x74, 0x6f, 0x72, + 0x65, 0x2e, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x22, 0x26, 0x0a, 0x04, + 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x4e, 0x53, 0x45, 0x54, 0x10, 0x00, 0x12, + 0x07, 0x0a, 0x03, 0x47, 0x45, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, 0x4c, 0x54, + 0x41, 0x53, 0x10, 0x02, 0x1a, 0x1e, 0x0a, 0x06, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x73, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x42, 0x07, 0x0a, 0x05, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x1a, 0x1c, 0x0a, + 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x42, 0x06, 0x0a, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x73, 0x74, 0x2f, + 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x66, + 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x70, + 0x62, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x4a, 0x96, 0x1d, 0x0a, 0x06, + 0x12, 0x04, 0x00, 0x00, 0x61, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, + 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x02, 0x00, 0x19, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, + 0x03, 0x04, 0x00, 0x5b, 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x04, 0x00, 0x5b, 0x0a, + 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x06, 0x00, 0x09, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x00, 0x01, 0x12, 0x03, 0x06, 0x08, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, + 0x03, 0x07, 0x02, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x04, 0x12, 0x03, 0x07, + 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x07, 0x0b, 0x11, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x07, 0x12, 0x19, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x07, 0x1c, 0x1d, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x08, 0x02, 0x1f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x01, 0x04, 0x12, 0x03, 0x08, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x06, + 0x12, 0x03, 0x08, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, + 0x08, 0x12, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x08, 0x1d, + 0x1e, 0x0a, 0x46, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x0c, 0x00, 0x0f, 0x01, 0x1a, 0x3a, 0x20, + 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, + 0x73, 0x20, 0x73, 0x6f, 0x6d, 0x65, 0x20, 0x63, 0x6f, 0x64, 0x65, 0x20, 0x63, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x69, 0x74, 0x73, 0x20, 0x62, 0x69, 0x6e, 0x61, + 0x72, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x6d, 0x2e, 0x0a, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, + 0x12, 0x03, 0x0c, 0x08, 0x0e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x00, 0x12, 0x03, 0x0d, + 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x0d, 0x02, 0x08, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0d, 0x09, 0x0d, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0d, 0x10, 0x11, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x0e, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x01, 0x05, 0x12, 0x03, 0x0e, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, + 0x12, 0x03, 0x0e, 0x08, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, + 0x0e, 0x12, 0x13, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x11, 0x00, 0x61, 0x01, 0x0a, + 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x11, 0x08, 0x0e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x02, 0x02, 0x00, 0x12, 0x03, 0x12, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, + 0x05, 0x12, 0x03, 0x12, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x12, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x12, + 0x10, 0x11, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x02, 0x08, 0x00, 0x12, 0x04, 0x13, 0x02, 0x16, 0x03, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x08, 0x00, 0x01, 0x12, 0x03, 0x13, 0x08, 0x0c, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x14, 0x04, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x14, 0x04, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x01, 0x01, 0x12, 0x03, 0x14, 0x0c, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x03, + 0x12, 0x03, 0x14, 0x17, 0x18, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x02, 0x12, 0x03, 0x15, + 0x04, 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x06, 0x12, 0x03, 0x15, 0x04, 0x0d, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x01, 0x12, 0x03, 0x15, 0x0e, 0x18, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x02, 0x02, 0x03, 0x12, 0x03, 0x15, 0x1b, 0x1c, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x02, 0x02, 0x03, 0x12, 0x03, 0x18, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x03, 0x05, 0x12, 0x03, 0x18, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x01, + 0x12, 0x03, 0x18, 0x09, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x03, 0x03, 0x12, 0x03, + 0x18, 0x18, 0x19, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x04, 0x12, 0x03, 0x19, 0x02, 0x1f, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x04, 0x05, 0x12, 0x03, 0x19, 0x02, 0x08, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x02, 0x04, 0x01, 0x12, 0x03, 0x19, 0x09, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x04, 0x03, 0x12, 0x03, 0x19, 0x1d, 0x1e, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, + 0x02, 0x05, 0x12, 0x03, 0x1b, 0x02, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x04, + 0x12, 0x03, 0x1b, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x06, 0x12, 0x03, + 0x1b, 0x0b, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x01, 0x12, 0x03, 0x1b, 0x11, + 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x05, 0x03, 0x12, 0x03, 0x1b, 0x1a, 0x1b, 0x0a, + 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x06, 0x12, 0x03, 0x1c, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x06, 0x06, 0x12, 0x03, 0x1c, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, + 0x02, 0x06, 0x01, 0x12, 0x03, 0x1c, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x06, + 0x03, 0x12, 0x03, 0x1c, 0x12, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x07, 0x12, 0x03, + 0x1e, 0x02, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x07, 0x05, 0x12, 0x03, 0x1e, 0x02, + 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x07, 0x01, 0x12, 0x03, 0x1e, 0x09, 0x16, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x07, 0x03, 0x12, 0x03, 0x1e, 0x19, 0x1a, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x02, 0x03, 0x00, 0x12, 0x04, 0x20, 0x02, 0x22, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x02, 0x03, 0x00, 0x01, 0x12, 0x03, 0x20, 0x0a, 0x11, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, + 0x00, 0x02, 0x00, 0x12, 0x03, 0x21, 0x04, 0x1b, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, + 0x02, 0x00, 0x05, 0x12, 0x03, 0x21, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, + 0x02, 0x00, 0x01, 0x12, 0x03, 0x21, 0x0b, 0x16, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x00, + 0x02, 0x00, 0x03, 0x12, 0x03, 0x21, 0x19, 0x1a, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x02, 0x03, 0x01, + 0x12, 0x04, 0x24, 0x02, 0x3f, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x01, 0x01, 0x12, + 0x03, 0x24, 0x0a, 0x13, 0x0a, 0xcb, 0x03, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, + 0x03, 0x2d, 0x04, 0x23, 0x1a, 0xbb, 0x03, 0x20, 0x54, 0x68, 0x65, 0x20, 0x60, 0x75, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x60, 0x20, 0x64, 0x65, 0x74, 0x65, + 0x72, 0x6d, 0x69, 0x6e, 0x65, 0x73, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x74, + 0x6f, 0x20, 0x6d, 0x75, 0x74, 0x61, 0x74, 0x65, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x0a, 0x20, 0x28, 0x6c, 0x69, 0x6b, 0x65, 0x20, 0x60, 0x73, 0x65, 0x74, 0x28, 0x29, + 0x60, 0x2c, 0x20, 0x60, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x65, + 0x78, 0x69, 0x73, 0x74, 0x73, 0x28, 0x29, 0x60, 0x20, 0x6f, 0x72, 0x20, 0x60, 0x73, 0x75, 0x6d, + 0x28, 0x29, 0x60, 0x2c, 0x20, 0x65, 0x74, 0x63, 0x2e, 0x2e, 0x29, 0x20, 0x69, 0x6e, 0x0a, 0x20, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x20, 0x74, 0x6f, 0x20, 0x65, 0x6e, 0x73, 0x75, 0x72, 0x65, 0x20, + 0x74, 0x68, 0x61, 0x74, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, 0x6f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x70, 0x6f, 0x73, + 0x73, 0x69, 0x62, 0x6c, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, + 0x69, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x63, 0x0a, 0x0a, 0x20, 0x53, 0x61, 0x79, 0x20, 0x61, 0x20, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x63, 0x75, 0x6d, 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, + 0x6b, 0x65, 0x79, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, + 0x30, 0x20, 0x74, 0x6f, 0x20, 0x31, 0x4d, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x61, 0x20, 0x73, + 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x0a, 0x20, 0x63, 0x75, 0x6d, + 0x75, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x20, 0x66, 0x72, 0x6f, 0x6d, + 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x31, 0x4d, 0x20, 0x74, 0x6f, 0x20, 0x32, 0x4d, 0x2e, + 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x77, 0x65, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, + 0x20, 0x75, 0x73, 0x65, 0x20, 0x74, 0x68, 0x69, 0x73, 0x0a, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x20, 0x61, 0x73, 0x20, 0x61, 0x20, 0x64, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x6e, 0x63, 0x79, + 0x20, 0x66, 0x6f, 0x72, 0x20, 0x61, 0x20, 0x64, 0x6f, 0x77, 0x6e, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2c, 0x20, 0x77, 0x65, 0x20, 0x77, 0x69, 0x6c, + 0x6c, 0x20, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x20, 0x74, 0x68, 0x65, 0x0a, 0x20, 0x74, 0x77, 0x6f, + 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x61, 0x63, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, + 0x67, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, + 0x2e, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x2d, + 0x04, 0x10, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x2d, + 0x11, 0x1e, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x2d, + 0x21, 0x22, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x12, 0x03, 0x2e, 0x04, + 0x1a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x05, 0x12, 0x03, 0x2e, 0x04, + 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x2e, 0x0b, + 0x15, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x2e, 0x18, + 0x19, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x12, 0x04, 0x30, 0x04, 0x3e, + 0x05, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x01, 0x12, 0x03, 0x30, 0x09, + 0x15, 0x0a, 0x0f, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x31, + 0x06, 0x1e, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x31, 0x06, 0x19, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x00, + 0x02, 0x12, 0x03, 0x31, 0x1c, 0x1d, 0x0a, 0x57, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, + 0x02, 0x01, 0x12, 0x03, 0x33, 0x06, 0x1c, 0x1a, 0x46, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, + 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x60, 0x73, 0x65, 0x74, 0x28, 0x29, 0x60, + 0x20, 0x6b, 0x65, 0x79, 0x73, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x20, 0x6b, 0x65, 0x79, 0x20, 0x77, 0x69, 0x6e, 0x73, 0x0a, 0x0a, + 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, 0x33, 0x06, + 0x17, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x01, 0x02, 0x12, 0x03, + 0x33, 0x1a, 0x1b, 0x0a, 0x64, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x02, 0x12, + 0x03, 0x35, 0x06, 0x2a, 0x1a, 0x53, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, + 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x79, 0x6f, + 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x60, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x66, 0x5f, 0x6e, 0x6f, + 0x74, 0x5f, 0x65, 0x78, 0x69, 0x73, 0x74, 0x73, 0x28, 0x29, 0x60, 0x20, 0x6b, 0x65, 0x79, 0x73, + 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x20, + 0x6b, 0x65, 0x79, 0x20, 0x77, 0x69, 0x6e, 0x73, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, + 0x01, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x35, 0x06, 0x25, 0x0a, 0x10, 0x0a, 0x09, 0x04, + 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x35, 0x28, 0x29, 0x0a, 0x6f, 0x0a, + 0x08, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x37, 0x06, 0x1c, 0x1a, 0x5e, + 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, + 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, + 0x60, 0x61, 0x64, 0x64, 0x5f, 0x2a, 0x28, 0x29, 0x60, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x2c, 0x20, + 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, + 0x20, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x20, 0x62, 0x79, 0x20, 0x73, 0x75, 0x6d, 0x6d, 0x69, 0x6e, + 0x67, 0x20, 0x69, 0x74, 0x73, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x2e, 0x0a, 0x0a, 0x10, + 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x37, 0x06, 0x17, + 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x37, + 0x1a, 0x1b, 0x0a, 0x76, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, + 0x39, 0x06, 0x1c, 0x1a, 0x65, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, + 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, + 0x20, 0x63, 0x61, 0x6e, 0x20, 0x60, 0x6d, 0x69, 0x6e, 0x5f, 0x2a, 0x28, 0x29, 0x60, 0x20, 0x6b, + 0x65, 0x79, 0x73, 0x2c, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x20, 0x62, 0x79, 0x20, 0x6c, + 0x65, 0x61, 0x76, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x6d, + 0x75, 0x6d, 0x20, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, + 0x03, 0x01, 0x04, 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x39, 0x06, 0x17, 0x0a, 0x10, 0x0a, 0x09, + 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x04, 0x02, 0x12, 0x03, 0x39, 0x1a, 0x1b, 0x0a, 0x76, + 0x0a, 0x08, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x05, 0x12, 0x03, 0x3b, 0x06, 0x1c, 0x1a, + 0x65, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x6f, + 0x72, 0x65, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, + 0x20, 0x60, 0x6d, 0x61, 0x78, 0x5f, 0x2a, 0x28, 0x29, 0x60, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x2c, + 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x73, 0x20, 0x6d, 0x65, 0x72, 0x67, 0x65, 0x20, 0x62, 0x79, 0x20, 0x6c, 0x65, 0x61, 0x76, 0x69, + 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d, 0x61, 0x78, 0x69, 0x6d, 0x75, 0x6d, 0x20, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x2e, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, + 0x02, 0x05, 0x01, 0x12, 0x03, 0x3b, 0x06, 0x17, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x01, + 0x04, 0x00, 0x02, 0x05, 0x02, 0x12, 0x03, 0x3b, 0x1a, 0x1b, 0x0a, 0x7e, 0x0a, 0x08, 0x04, 0x02, + 0x03, 0x01, 0x04, 0x00, 0x02, 0x06, 0x12, 0x03, 0x3d, 0x06, 0x1f, 0x1a, 0x6d, 0x20, 0x50, 0x72, + 0x6f, 0x76, 0x69, 0x64, 0x65, 0x73, 0x20, 0x61, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x77, + 0x68, 0x65, 0x72, 0x65, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x60, 0x61, 0x70, + 0x70, 0x65, 0x6e, 0x64, 0x28, 0x29, 0x60, 0x20, 0x6b, 0x65, 0x79, 0x73, 0x2c, 0x20, 0x77, 0x68, + 0x65, 0x72, 0x65, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x73, 0x20, 0x6d, + 0x65, 0x72, 0x67, 0x65, 0x20, 0x62, 0x79, 0x20, 0x63, 0x6f, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x6e, + 0x61, 0x74, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x20, 0x62, 0x79, 0x74, 0x65, 0x73, 0x20, + 0x69, 0x6e, 0x20, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x2e, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, + 0x03, 0x01, 0x04, 0x00, 0x02, 0x06, 0x01, 0x12, 0x03, 0x3d, 0x06, 0x1a, 0x0a, 0x10, 0x0a, 0x09, + 0x04, 0x02, 0x03, 0x01, 0x04, 0x00, 0x02, 0x06, 0x02, 0x12, 0x03, 0x3d, 0x1d, 0x1e, 0x0a, 0x0c, + 0x0a, 0x04, 0x04, 0x02, 0x03, 0x02, 0x12, 0x04, 0x41, 0x02, 0x5c, 0x03, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x03, 0x02, 0x01, 0x12, 0x03, 0x41, 0x0a, 0x0f, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x02, + 0x03, 0x02, 0x08, 0x00, 0x12, 0x04, 0x42, 0x04, 0x47, 0x05, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, + 0x03, 0x02, 0x08, 0x00, 0x01, 0x12, 0x03, 0x42, 0x0a, 0x0f, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, + 0x03, 0x02, 0x02, 0x00, 0x12, 0x03, 0x43, 0x06, 0x18, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x02, 0x02, 0x00, 0x06, 0x12, 0x03, 0x43, 0x06, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x43, 0x0d, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, + 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x43, 0x16, 0x17, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, + 0x02, 0x02, 0x01, 0x12, 0x03, 0x44, 0x06, 0x12, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, + 0x02, 0x01, 0x06, 0x12, 0x03, 0x44, 0x06, 0x09, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, + 0x02, 0x01, 0x01, 0x12, 0x03, 0x44, 0x0a, 0x0d, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, + 0x02, 0x01, 0x03, 0x12, 0x03, 0x44, 0x10, 0x11, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x02, + 0x02, 0x02, 0x12, 0x03, 0x45, 0x06, 0x16, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x02, + 0x02, 0x06, 0x12, 0x03, 0x45, 0x06, 0x0b, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x02, + 0x02, 0x01, 0x12, 0x03, 0x45, 0x0c, 0x11, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x02, + 0x02, 0x03, 0x12, 0x03, 0x45, 0x14, 0x15, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x02, 0x02, + 0x03, 0x12, 0x03, 0x46, 0x06, 0x18, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x02, 0x03, + 0x06, 0x12, 0x03, 0x46, 0x06, 0x0c, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x02, 0x03, + 0x01, 0x12, 0x03, 0x46, 0x0d, 0x13, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x02, 0x03, + 0x03, 0x12, 0x03, 0x46, 0x16, 0x17, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x02, 0x03, 0x00, + 0x12, 0x04, 0x49, 0x04, 0x4b, 0x05, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x03, 0x00, + 0x01, 0x12, 0x03, 0x49, 0x0c, 0x12, 0x0a, 0x32, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x02, 0x03, 0x00, + 0x02, 0x00, 0x12, 0x03, 0x4a, 0x06, 0x16, 0x22, 0x21, 0x20, 0x65, 0x78, 0x3a, 0x20, 0x22, 0x73, + 0x66, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, + 0x03, 0x02, 0x03, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, 0x4a, 0x06, 0x0c, 0x0a, 0x10, 0x0a, 0x09, + 0x04, 0x02, 0x03, 0x02, 0x03, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x4a, 0x0d, 0x11, 0x0a, 0x10, + 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x4a, 0x14, 0x15, + 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x02, 0x03, 0x01, 0x12, 0x04, 0x4c, 0x04, 0x4e, 0x05, + 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x03, 0x01, 0x01, 0x12, 0x03, 0x4c, 0x0c, 0x0f, + 0x0a, 0x27, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x02, 0x03, 0x01, 0x02, 0x00, 0x12, 0x03, 0x4d, 0x06, + 0x1d, 0x22, 0x16, 0x20, 0x65, 0x78, 0x3a, 0x20, 0x22, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x74, + 0x6f, 0x5f, 0x70, 0x61, 0x69, 0x72, 0x73, 0x22, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, + 0x02, 0x03, 0x01, 0x02, 0x00, 0x05, 0x12, 0x03, 0x4d, 0x06, 0x0c, 0x0a, 0x10, 0x0a, 0x09, 0x04, + 0x02, 0x03, 0x02, 0x03, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x4d, 0x0d, 0x18, 0x0a, 0x10, 0x0a, + 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x4d, 0x1b, 0x1c, 0x0a, + 0x0e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x12, 0x04, 0x4f, 0x04, 0x58, 0x05, 0x0a, + 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x01, 0x12, 0x03, 0x4f, 0x0c, 0x11, 0x0a, + 0x0f, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x00, 0x12, 0x03, 0x50, 0x06, 0x1d, + 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x00, 0x05, 0x12, 0x03, 0x50, + 0x06, 0x0c, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x50, 0x0d, 0x18, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x00, + 0x03, 0x12, 0x03, 0x50, 0x1b, 0x1c, 0x0a, 0x0f, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, + 0x02, 0x01, 0x12, 0x03, 0x51, 0x06, 0x14, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, + 0x02, 0x02, 0x01, 0x06, 0x12, 0x03, 0x51, 0x06, 0x0a, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, + 0x02, 0x03, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x51, 0x0b, 0x0f, 0x0a, 0x10, 0x0a, 0x09, 0x04, + 0x02, 0x03, 0x02, 0x03, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x51, 0x12, 0x13, 0x0a, 0x10, 0x0a, + 0x08, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x12, 0x04, 0x53, 0x06, 0x57, 0x07, 0x0a, + 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x01, 0x12, 0x03, 0x53, 0x0b, + 0x0f, 0x0a, 0x11, 0x0a, 0x0a, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x02, 0x00, 0x12, + 0x03, 0x54, 0x08, 0x12, 0x0a, 0x12, 0x0a, 0x0b, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, + 0x02, 0x00, 0x01, 0x12, 0x03, 0x54, 0x08, 0x0d, 0x0a, 0x12, 0x0a, 0x0b, 0x04, 0x02, 0x03, 0x02, + 0x03, 0x02, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x54, 0x10, 0x11, 0x0a, 0x11, 0x0a, 0x0a, + 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x55, 0x08, 0x10, 0x0a, + 0x12, 0x0a, 0x0b, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, 0x03, + 0x55, 0x08, 0x0b, 0x0a, 0x12, 0x0a, 0x0b, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x02, + 0x01, 0x02, 0x12, 0x03, 0x55, 0x0e, 0x0f, 0x0a, 0x11, 0x0a, 0x0a, 0x04, 0x02, 0x03, 0x02, 0x03, + 0x02, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x56, 0x08, 0x13, 0x0a, 0x12, 0x0a, 0x0b, 0x04, 0x02, + 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x56, 0x08, 0x0e, 0x0a, 0x12, + 0x0a, 0x0b, 0x04, 0x02, 0x03, 0x02, 0x03, 0x02, 0x04, 0x00, 0x02, 0x02, 0x02, 0x12, 0x03, 0x56, + 0x11, 0x12, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x02, 0x03, 0x03, 0x12, 0x04, 0x59, 0x04, + 0x5b, 0x05, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x02, 0x03, 0x03, 0x01, 0x12, 0x03, 0x59, + 0x0c, 0x12, 0x0a, 0x0f, 0x0a, 0x08, 0x04, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x00, 0x12, 0x03, + 0x5a, 0x06, 0x17, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, 0x00, 0x05, + 0x12, 0x03, 0x5a, 0x06, 0x0c, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, 0x03, 0x02, + 0x00, 0x01, 0x12, 0x03, 0x5a, 0x0d, 0x12, 0x0a, 0x10, 0x0a, 0x09, 0x04, 0x02, 0x03, 0x02, 0x03, + 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, 0x5a, 0x15, 0x16, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x02, 0x03, + 0x03, 0x12, 0x04, 0x5e, 0x02, 0x60, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x03, 0x03, 0x01, + 0x12, 0x03, 0x5e, 0x0a, 0x10, 0x0a, 0x0d, 0x0a, 0x06, 0x04, 0x02, 0x03, 0x03, 0x02, 0x00, 0x12, + 0x03, 0x5f, 0x04, 0x14, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x03, 0x02, 0x00, 0x05, 0x12, + 0x03, 0x5f, 0x04, 0x0a, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x03, 0x02, 0x00, 0x01, 0x12, + 0x03, 0x5f, 0x0b, 0x0f, 0x0a, 0x0e, 0x0a, 0x07, 0x04, 0x02, 0x03, 0x03, 0x02, 0x00, 0x03, 0x12, + 0x03, 0x5f, 0x12, 0x13, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 0x0a, 0xd3, 0x10, 0x0a, + 0x1e, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, + 0x31, 0x2f, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x10, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, + 0x31, 0x1a, 0x20, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, + 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, + 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xa0, + 0x03, 0x0a, 0x07, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x12, 0x45, 0x0a, 0x0b, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x24, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6f, 0x72, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x46, 0x69, 0x6c, 0x65, + 0x73, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x33, 0x0a, 0x07, 0x6d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, + 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, + 0x12, 0x41, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, + 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4d, + 0x65, 0x74, 0x61, 0x12, 0x44, 0x0a, 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x5f, 0x6d, + 0x65, 0x74, 0x61, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x73, 0x66, 0x2e, 0x73, + 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x61, 0x63, + 0x6b, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x0b, 0x70, 0x61, + 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x6e, 0x65, 0x74, + 0x77, 0x6f, 0x72, 0x6b, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x69, 0x6e, 0x6b, 0x5f, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x0a, + 0x73, 0x69, 0x6e, 0x6b, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x69, + 0x6e, 0x6b, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x73, 0x69, 0x6e, 0x6b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4a, 0x04, 0x08, 0x02, 0x10, + 0x05, 0x22, 0x63, 0x0a, 0x0f, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x4d, 0x65, 0x74, 0x61, + 0x64, 0x61, 0x74, 0x61, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x10, + 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, + 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x64, 0x6f, 0x63, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x64, 0x6f, 0x63, 0x22, 0x47, 0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x23, 0x0a, 0x0d, 0x70, 0x61, 0x63, 0x6b, + 0x61, 0x67, 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x0c, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x10, 0x0a, + 0x03, 0x64, 0x6f, 0x63, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x64, 0x6f, 0x63, 0x42, + 0x46, 0x5a, 0x44, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x69, 0x6e, 0x67, 0x66, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x75, 0x62, 0x73, + 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x70, 0x62, 0x73, 0x75, 0x62, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x4a, 0xa0, 0x0a, 0x0a, 0x06, 0x12, 0x04, 0x00, 0x00, + 0x27, 0x01, 0x0a, 0x08, 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x08, 0x0a, 0x01, + 0x02, 0x12, 0x03, 0x02, 0x00, 0x19, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x04, 0x00, 0x5b, + 0x0a, 0x09, 0x0a, 0x02, 0x08, 0x0b, 0x12, 0x03, 0x04, 0x00, 0x5b, 0x0a, 0x09, 0x0a, 0x02, 0x03, + 0x00, 0x12, 0x03, 0x06, 0x00, 0x2a, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x01, 0x12, 0x03, 0x07, 0x00, + 0x23, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x02, 0x12, 0x03, 0x08, 0x00, 0x28, 0x0a, 0x0a, 0x0a, 0x02, + 0x04, 0x00, 0x12, 0x04, 0x0a, 0x00, 0x1a, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x00, 0x01, 0x12, + 0x03, 0x0a, 0x08, 0x0f, 0x0a, 0x81, 0x01, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, 0x12, 0x03, 0x0d, + 0x02, 0x3f, 0x1a, 0x74, 0x20, 0x4e, 0x65, 0x65, 0x64, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x62, 0x65, + 0x20, 0x6f, 0x6e, 0x65, 0x20, 0x73, 0x6f, 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x66, 0x69, 0x6c, + 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x75, 0x73, 0x65, 0x64, 0x20, 0x5f, 0x64, + 0x69, 0x72, 0x65, 0x63, 0x74, 0x6c, 0x79, 0x5f, 0x20, 0x61, 0x73, 0x20, 0x61, 0x0a, 0x20, 0x62, + 0x75, 0x66, 0x20, 0x60, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x60, 0x20, 0x61, 0x6e, 0x64, 0x6f, 0x72, + 0x20, 0x61, 0x20, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x53, 0x65, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, + 0x67, 0x72, 0x70, 0x63, 0x75, 0x72, 0x6c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x74, 0x68, 0x65, + 0x72, 0x20, 0x74, 0x6f, 0x6f, 0x6c, 0x73, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, + 0x04, 0x12, 0x03, 0x0d, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x06, 0x12, + 0x03, 0x0d, 0x0b, 0x2e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x0d, + 0x2f, 0x3a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0d, 0x3d, 0x3e, + 0x0a, 0x40, 0x0a, 0x03, 0x04, 0x00, 0x09, 0x12, 0x03, 0x0e, 0x02, 0x12, 0x22, 0x34, 0x20, 0x52, + 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x66, 0x75, 0x74, 0x75, + 0x72, 0x65, 0x3a, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x61, 0x73, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x73, 0x65, 0x74, 0x73, 0x20, 0x61, 0x64, 0x64, 0x73, 0x20, 0x66, 0x69, 0x65, 0x6c, 0x64, + 0x73, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x09, 0x00, 0x12, 0x03, 0x0e, 0x0b, 0x11, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x09, 0x00, 0x01, 0x12, 0x03, 0x0e, 0x0b, 0x0c, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x09, 0x00, 0x02, 0x12, 0x03, 0x0e, 0x10, 0x11, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x00, 0x02, 0x01, 0x12, 0x03, 0x10, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, + 0x05, 0x12, 0x03, 0x10, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, + 0x03, 0x10, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, 0x03, 0x12, 0x03, 0x10, + 0x13, 0x14, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, 0x11, 0x02, 0x27, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x06, 0x12, 0x03, 0x11, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x11, 0x1b, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x11, 0x25, 0x26, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, + 0x03, 0x12, 0x03, 0x12, 0x02, 0x2a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x04, 0x12, + 0x03, 0x12, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x06, 0x12, 0x03, 0x12, + 0x0b, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x12, 0x1a, 0x25, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x12, 0x28, 0x29, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x13, 0x02, 0x2c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x04, 0x04, 0x12, 0x03, 0x13, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x04, 0x06, 0x12, 0x03, 0x13, 0x0b, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x04, 0x01, + 0x12, 0x03, 0x13, 0x1b, 0x27, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x04, 0x03, 0x12, 0x03, + 0x13, 0x2a, 0x2b, 0x0a, 0x44, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x05, 0x12, 0x03, 0x16, 0x02, 0x15, + 0x1a, 0x37, 0x20, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x53, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, + 0x20, 0x74, 0x6f, 0x20, 0x66, 0x65, 0x74, 0x63, 0x68, 0x20, 0x69, 0x74, 0x73, 0x20, 0x64, 0x61, + 0x74, 0x61, 0x20, 0x66, 0x72, 0x6f, 0x6d, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x05, 0x05, 0x12, 0x03, 0x16, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x01, + 0x12, 0x03, 0x16, 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x03, 0x12, 0x03, + 0x16, 0x13, 0x14, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x06, 0x12, 0x03, 0x18, 0x02, 0x27, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x06, 0x12, 0x03, 0x18, 0x02, 0x15, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x01, 0x12, 0x03, 0x18, 0x16, 0x21, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x06, 0x03, 0x12, 0x03, 0x18, 0x24, 0x26, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, + 0x02, 0x07, 0x12, 0x03, 0x19, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x05, + 0x12, 0x03, 0x19, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x01, 0x12, 0x03, + 0x19, 0x09, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x03, 0x12, 0x03, 0x19, 0x17, + 0x19, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x1c, 0x00, 0x21, 0x01, 0x0a, 0x0a, 0x0a, + 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x1c, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, + 0x00, 0x12, 0x03, 0x1d, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x05, 0x12, + 0x03, 0x1d, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x1d, + 0x09, 0x10, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x00, 0x03, 0x12, 0x03, 0x1d, 0x13, 0x14, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, 0x03, 0x1e, 0x02, 0x11, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x01, 0x05, 0x12, 0x03, 0x1e, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x1e, 0x09, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x01, 0x03, 0x12, 0x03, 0x1e, 0x0f, 0x10, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, + 0x03, 0x1f, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x05, 0x12, 0x03, 0x1f, + 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x1f, 0x09, 0x0d, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x1f, 0x10, 0x11, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x01, 0x02, 0x03, 0x12, 0x03, 0x20, 0x02, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x03, 0x05, 0x12, 0x03, 0x20, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x03, 0x01, 0x12, 0x03, 0x20, 0x09, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, + 0x12, 0x03, 0x20, 0x0f, 0x10, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x23, 0x00, 0x27, + 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x02, 0x01, 0x12, 0x03, 0x23, 0x08, 0x16, 0x0a, 0x4a, 0x0a, + 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, 0x03, 0x25, 0x02, 0x1b, 0x1a, 0x3d, 0x20, 0x43, 0x6f, 0x72, + 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, + 0x69, 0x6e, 0x64, 0x65, 0x78, 0x20, 0x69, 0x6e, 0x20, 0x60, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, + 0x65, 0x2e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x60, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, + 0x00, 0x05, 0x12, 0x03, 0x25, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, + 0x12, 0x03, 0x25, 0x09, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, + 0x25, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x26, 0x02, 0x11, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x05, 0x12, 0x03, 0x26, 0x02, 0x08, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x02, 0x02, 0x01, 0x01, 0x12, 0x03, 0x26, 0x09, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x02, 0x02, 0x01, 0x03, 0x12, 0x03, 0x26, 0x0f, 0x10, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, 0x0a, 0xeb, 0x58, 0x0a, 0x21, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x1a, 0x19, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x6e, 0x79, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf3, 0x03, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x26, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, + 0x6e, 0x75, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x73, 0x74, 0x61, 0x72, 0x74, + 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x74, 0x61, 0x72, + 0x74, 0x5f, 0x63, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x43, 0x75, 0x72, 0x73, 0x6f, 0x72, 0x12, 0x24, 0x0a, 0x0e, 0x73, + 0x74, 0x6f, 0x70, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x0c, 0x73, 0x74, 0x6f, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, + 0x6d, 0x12, 0x39, 0x0a, 0x0a, 0x66, 0x6f, 0x72, 0x6b, 0x5f, 0x73, 0x74, 0x65, 0x70, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x74, 0x65, + 0x70, 0x52, 0x09, 0x66, 0x6f, 0x72, 0x6b, 0x53, 0x74, 0x65, 0x70, 0x73, 0x12, 0x3b, 0x0a, 0x19, + 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x5f, + 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x18, 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x69, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, + 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x09, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x6f, + 0x64, 0x65, 0x12, 0x33, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x52, 0x07, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x55, + 0x0a, 0x28, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, + 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x66, + 0x6f, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x23, 0x64, 0x65, 0x62, 0x75, 0x67, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x46, 0x6f, 0x72, 0x4d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, + 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x22, 0x88, 0x03, 0x0a, 0x08, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, + 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x69, 0x74, 0x48, 0x00, 0x52, 0x07, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x3f, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x50, + 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x48, 0x00, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x57, 0x0a, 0x13, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x73, 0x6e, 0x61, + 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x25, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x44, 0x61, 0x74, 0x61, 0x48, 0x00, 0x52, 0x11, 0x64, 0x65, 0x62, 0x75, 0x67, + 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x63, 0x0a, 0x17, + 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x63, + 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x29, 0x2e, + 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, + 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x48, 0x00, 0x52, 0x15, 0x64, 0x65, 0x62, 0x75, + 0x67, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, + 0x65, 0x12, 0x37, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, + 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x64, 0x44, 0x61, + 0x74, 0x61, 0x48, 0x00, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x42, 0x09, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x28, 0x0a, 0x0b, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x49, 0x6e, 0x69, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x74, 0x72, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x74, 0x72, 0x61, 0x63, 0x65, 0x49, 0x64, 0x22, + 0x31, 0x0a, 0x17, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6e, 0x61, 0x70, 0x73, 0x68, + 0x6f, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x75, + 0x72, 0x73, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, 0x73, + 0x6f, 0x72, 0x22, 0xa9, 0x01, 0x0a, 0x13, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x6e, + 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x44, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x64, + 0x65, 0x6c, 0x74, 0x61, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x66, + 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, + 0x74, 0x6f, 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x74, + 0x61, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x73, 0x65, 0x6e, 0x74, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x73, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x79, 0x73, 0x12, + 0x1d, 0x0a, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x6b, 0x65, 0x79, 0x73, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4b, 0x65, 0x79, 0x73, 0x22, 0xf0, + 0x01, 0x0a, 0x0f, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x63, 0x6f, 0x70, 0x65, 0x64, 0x44, 0x61, + 0x74, 0x61, 0x12, 0x38, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4f, 0x75, 0x74, + 0x70, 0x75, 0x74, 0x52, 0x07, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x73, 0x12, 0x2d, 0x0a, 0x05, + 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x73, 0x66, + 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x43, + 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x05, 0x63, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x2e, 0x0a, 0x04, 0x73, + 0x74, 0x65, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1a, 0x2e, 0x73, 0x66, 0x2e, 0x73, + 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x46, 0x6f, 0x72, + 0x6b, 0x53, 0x74, 0x65, 0x70, 0x52, 0x04, 0x73, 0x74, 0x65, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x63, + 0x75, 0x72, 0x73, 0x6f, 0x72, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x75, 0x72, + 0x73, 0x6f, 0x72, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x5f, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x10, 0x66, 0x69, 0x6e, 0x61, 0x6c, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x65, 0x69, 0x67, 0x68, + 0x74, 0x22, 0x99, 0x02, 0x0a, 0x0c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, + 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x0a, 0x6d, 0x61, 0x70, 0x5f, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, + 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, + 0x48, 0x00, 0x52, 0x09, 0x6d, 0x61, 0x70, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x12, 0x4d, 0x0a, + 0x12, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x64, 0x65, 0x6c, + 0x74, 0x61, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x66, 0x2e, 0x73, + 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x48, 0x00, 0x52, 0x10, 0x64, 0x65, 0x62, 0x75, + 0x67, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x12, 0x1d, 0x0a, 0x0a, + 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, + 0x52, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, 0x4c, 0x6f, 0x67, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x64, + 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, + 0x74, 0x65, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x64, 0x65, 0x62, 0x75, 0x67, + 0x4c, 0x6f, 0x67, 0x73, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x12, 0x16, 0x0a, + 0x06, 0x63, 0x61, 0x63, 0x68, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x63, + 0x61, 0x63, 0x68, 0x65, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x4d, 0x0a, + 0x0f, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, + 0x12, 0x3a, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, + 0x65, 0x73, 0x73, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0xef, 0x06, 0x0a, + 0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, + 0x61, 0x6d, 0x65, 0x12, 0x5c, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, + 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, + 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, + 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x48, 0x00, + 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x12, 0x54, 0x0a, 0x0d, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, + 0x74, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, + 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x49, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0c, 0x69, 0x6e, 0x69, 0x74, 0x69, + 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x5a, 0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, + 0x73, 0x73, 0x65, 0x64, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2f, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, + 0x73, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, + 0x73, 0x48, 0x00, 0x52, 0x0e, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x42, 0x79, + 0x74, 0x65, 0x73, 0x12, 0x41, 0x0a, 0x06, 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, + 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, + 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x48, 0x00, 0x52, 0x06, + 0x66, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x1a, 0x59, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, + 0x73, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x47, 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x63, + 0x65, 0x73, 0x73, 0x65, 0x64, 0x5f, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x52, 0x0f, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x64, 0x52, 0x61, 0x6e, 0x67, 0x65, + 0x73, 0x1a, 0x41, 0x0a, 0x0c, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x12, 0x31, 0x0a, 0x15, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x75, + 0x70, 0x5f, 0x74, 0x6f, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x12, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x70, 0x54, 0x6f, 0x42, + 0x6c, 0x6f, 0x63, 0x6b, 0x1a, 0xf2, 0x01, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, + 0x65, 0x64, 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, + 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x52, 0x65, 0x61, + 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, + 0x5f, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, + 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x42, 0x79, 0x74, 0x65, 0x73, 0x57, 0x72, 0x69, 0x74, 0x74, 0x65, + 0x6e, 0x12, 0x28, 0x0a, 0x10, 0x62, 0x79, 0x74, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x61, 0x64, 0x5f, + 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x62, 0x79, 0x74, + 0x65, 0x73, 0x52, 0x65, 0x61, 0x64, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2e, 0x0a, 0x13, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x5f, 0x77, 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x5f, 0x64, 0x65, 0x6c, + 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x62, 0x79, 0x74, 0x65, 0x73, 0x57, + 0x72, 0x69, 0x74, 0x74, 0x65, 0x6e, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x2c, 0x0a, 0x12, 0x6e, + 0x61, 0x6e, 0x6f, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x73, 0x5f, 0x64, 0x65, 0x6c, 0x74, + 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x6e, 0x61, 0x6e, 0x6f, 0x53, 0x65, 0x63, + 0x6f, 0x6e, 0x64, 0x73, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x1a, 0x5b, 0x0a, 0x06, 0x46, 0x61, 0x69, + 0x6c, 0x65, 0x64, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6c, + 0x6f, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x6c, 0x6f, 0x67, 0x73, 0x12, + 0x25, 0x0a, 0x0e, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x74, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, + 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x6c, 0x6f, 0x67, 0x73, 0x54, 0x72, 0x75, + 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x42, 0x06, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x22, 0x4a, + 0x0a, 0x0a, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x52, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, + 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x1b, 0x0a, + 0x09, 0x65, 0x6e, 0x64, 0x5f, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x08, 0x65, 0x6e, 0x64, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x22, 0x43, 0x0a, 0x0b, 0x53, 0x74, + 0x6f, 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x12, 0x34, 0x0a, 0x06, 0x64, 0x65, 0x6c, + 0x74, 0x61, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x66, 0x2e, 0x73, + 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, + 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x52, 0x06, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x22, + 0xf4, 0x01, 0x0a, 0x0a, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x44, + 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x26, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x2e, + 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x6c, 0x12, 0x10, + 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, + 0x12, 0x1b, 0x0a, 0x09, 0x6f, 0x6c, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x6f, 0x6c, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, + 0x09, 0x6e, 0x65, 0x77, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x6e, 0x65, 0x77, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3a, 0x0a, 0x09, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x55, 0x4e, 0x53, 0x45, 0x54, + 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x10, 0x01, 0x12, 0x0a, + 0x0a, 0x06, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, + 0x4c, 0x45, 0x54, 0x45, 0x10, 0x03, 0x22, 0xa6, 0x01, 0x0a, 0x06, 0x4f, 0x75, 0x74, 0x70, 0x75, + 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x6e, 0x75, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x4e, 0x75, 0x6d, 0x12, 0x19, + 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x49, 0x64, 0x12, 0x38, 0x0a, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, + 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x12, 0x2a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x0a, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x41, 0x6e, 0x79, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, + 0x5c, 0x0a, 0x08, 0x46, 0x6f, 0x72, 0x6b, 0x53, 0x74, 0x65, 0x70, 0x12, 0x10, 0x0a, 0x0c, 0x53, + 0x54, 0x45, 0x50, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0c, 0x0a, + 0x08, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x4e, 0x45, 0x57, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x53, + 0x54, 0x45, 0x50, 0x5f, 0x55, 0x4e, 0x44, 0x4f, 0x10, 0x02, 0x12, 0x15, 0x0a, 0x11, 0x53, 0x54, + 0x45, 0x50, 0x5f, 0x49, 0x52, 0x52, 0x45, 0x56, 0x45, 0x52, 0x53, 0x49, 0x42, 0x4c, 0x45, 0x10, + 0x04, 0x22, 0x04, 0x08, 0x03, 0x10, 0x03, 0x22, 0x04, 0x08, 0x05, 0x10, 0x05, 0x32, 0x4b, 0x0a, + 0x06, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x41, 0x0a, 0x06, 0x42, 0x6c, 0x6f, 0x63, 0x6b, + 0x73, 0x12, 0x19, 0x2e, 0x73, 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x73, + 0x66, 0x2e, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x73, 0x2e, 0x76, 0x31, 0x2e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x30, 0x01, 0x42, 0x46, 0x5a, 0x44, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x69, + 0x6e, 0x67, 0x66, 0x61, 0x73, 0x74, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x2f, 0x70, 0x62, 0x2f, 0x73, 0x66, 0x2f, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x70, 0x62, 0x73, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x73, 0x4a, 0xb3, 0x3c, 0x0a, 0x07, 0x12, 0x05, 0x00, 0x00, 0xc8, 0x01, 0x01, 0x0a, 0x08, + 0x0a, 0x01, 0x0c, 0x12, 0x03, 0x00, 0x00, 0x12, 0x0a, 0x08, 0x0a, 0x01, 0x02, 0x12, 0x03, 0x02, + 0x00, 0x19, 0x0a, 0x08, 0x0a, 0x01, 0x08, 0x12, 0x03, 0x04, 0x00, 0x5b, 0x0a, 0x09, 0x0a, 0x02, + 0x08, 0x0b, 0x12, 0x03, 0x04, 0x00, 0x5b, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x00, 0x12, 0x03, 0x06, + 0x00, 0x23, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x01, 0x12, 0x03, 0x07, 0x00, 0x29, 0x0a, 0x09, 0x0a, + 0x02, 0x03, 0x02, 0x12, 0x03, 0x08, 0x00, 0x28, 0x0a, 0x09, 0x0a, 0x02, 0x03, 0x03, 0x12, 0x03, + 0x09, 0x00, 0x26, 0x0a, 0x0a, 0x0a, 0x02, 0x06, 0x00, 0x12, 0x04, 0x0b, 0x00, 0x0d, 0x01, 0x0a, + 0x0a, 0x0a, 0x03, 0x06, 0x00, 0x01, 0x12, 0x03, 0x0b, 0x08, 0x0e, 0x0a, 0x0b, 0x0a, 0x04, 0x06, + 0x00, 0x02, 0x00, 0x12, 0x03, 0x0c, 0x02, 0x30, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, + 0x01, 0x12, 0x03, 0x0c, 0x06, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x02, 0x12, + 0x03, 0x0c, 0x0d, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x06, 0x12, 0x03, 0x0c, + 0x1f, 0x25, 0x0a, 0x0c, 0x0a, 0x05, 0x06, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x0c, 0x26, 0x2e, + 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x00, 0x12, 0x04, 0x0f, 0x00, 0x32, 0x01, 0x0a, 0x0a, 0x0a, 0x03, + 0x04, 0x00, 0x01, 0x12, 0x03, 0x0f, 0x08, 0x0f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x00, + 0x12, 0x03, 0x10, 0x02, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x05, 0x12, 0x03, + 0x10, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x10, 0x08, + 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x00, 0x03, 0x12, 0x03, 0x10, 0x1a, 0x1b, 0x0a, + 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x01, 0x12, 0x03, 0x11, 0x02, 0x1a, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x00, 0x02, 0x01, 0x05, 0x12, 0x03, 0x11, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x01, 0x01, 0x12, 0x03, 0x11, 0x09, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x01, + 0x03, 0x12, 0x03, 0x11, 0x18, 0x19, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x02, 0x12, 0x03, + 0x12, 0x02, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x05, 0x12, 0x03, 0x12, 0x02, + 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x12, 0x09, 0x17, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x02, 0x03, 0x12, 0x03, 0x12, 0x1a, 0x1b, 0x0a, 0x0b, 0x0a, + 0x04, 0x04, 0x00, 0x02, 0x03, 0x12, 0x03, 0x13, 0x02, 0x23, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, + 0x02, 0x03, 0x04, 0x12, 0x03, 0x13, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, + 0x06, 0x12, 0x03, 0x13, 0x0b, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x01, 0x12, + 0x03, 0x13, 0x14, 0x1e, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x03, 0x03, 0x12, 0x03, 0x13, + 0x21, 0x22, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x04, 0x12, 0x03, 0x14, 0x02, 0x27, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x04, 0x05, 0x12, 0x03, 0x14, 0x02, 0x08, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x04, 0x01, 0x12, 0x03, 0x14, 0x09, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x04, 0x03, 0x12, 0x03, 0x14, 0x25, 0x26, 0x0a, 0xe5, 0x08, 0x0a, 0x04, 0x04, 0x00, + 0x02, 0x05, 0x12, 0x03, 0x29, 0x02, 0x1b, 0x1a, 0xd7, 0x08, 0x20, 0x53, 0x75, 0x62, 0x73, 0x74, + 0x72, 0x65, 0x61, 0x6d, 0x73, 0x20, 0x68, 0x61, 0x73, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x6d, 0x6f, + 0x64, 0x65, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6e, + 0x67, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x28, 0x73, 0x29, + 0x20, 0x65, 0x69, 0x74, 0x68, 0x65, 0x72, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, + 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x6f, 0x72, 0x20, 0x70, 0x72, 0x6f, 0x64, + 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x20, 0x44, 0x65, + 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x70, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x73, 0x20, 0x69, + 0x6d, 0x70, 0x61, 0x63, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, + 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x53, 0x75, 0x62, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, + 0x73, 0x2c, 0x20, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x61, 0x6e, 0x74, 0x20, 0x61, 0x73, 0x70, + 0x65, 0x63, 0x74, 0x73, 0x0a, 0x20, 0x6f, 0x66, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, + 0x6f, 0x6e, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x3a, 0x0a, 0x20, 0x2a, 0x20, 0x54, + 0x68, 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, + 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, + 0x72, 0x73, 0x74, 0x20, 0x62, 0x79, 0x74, 0x65, 0x2e, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, 0x65, + 0x20, 0x73, 0x70, 0x65, 0x65, 0x64, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x6c, 0x61, 0x72, 0x67, + 0x65, 0x20, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x20, 0x67, 0x65, 0x74, 0x20, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x64, 0x2e, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x6d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x75, + 0x74, 0x70, 0x75, 0x74, 0x73, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x62, 0x61, 0x63, 0x6b, 0x20, + 0x74, 0x6f, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x0a, 0x0a, + 0x20, 0x42, 0x79, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x2c, 0x20, 0x74, 0x68, 0x65, + 0x20, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x20, 0x72, 0x75, 0x6e, 0x73, 0x20, 0x69, 0x6e, 0x20, + 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2c, 0x20, + 0x77, 0x69, 0x74, 0x68, 0x20, 0x72, 0x69, 0x63, 0x68, 0x65, 0x72, 0x20, 0x61, 0x6e, 0x64, 0x20, + 0x64, 0x65, 0x65, 0x70, 0x65, 0x72, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x2e, 0x20, 0x44, + 0x69, 0x66, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x0a, 0x20, 0x62, 0x65, 0x74, 0x77, + 0x65, 0x65, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, + 0x6f, 0x64, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x3a, 0x0a, 0x20, 0x2a, + 0x20, 0x46, 0x6f, 0x72, 0x77, 0x61, 0x72, 0x64, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, + 0x6c, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x69, 0x73, 0x20, 0x65, + 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, + 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, + 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, + 0x65, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x20, + 0x74, 0x6f, 0x20, 0x72, 0x65, 0x61, 0x63, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x69, 0x72, + 0x73, 0x74, 0x20, 0x62, 0x79, 0x74, 0x65, 0x20, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, + 0x6f, 0x70, 0x6d, 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x73, 0x20, 0x66, + 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x74, 0x68, 0x61, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, + 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0a, 0x0a, + 0x20, 0x53, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x63, 0x20, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, + 0x75, 0x74, 0x65, 0x73, 0x20, 0x6f, 0x66, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, + 0x65, 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x20, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, + 0x3a, 0x0a, 0x20, 0x2a, 0x20, 0x54, 0x68, 0x65, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, + 0x77, 0x69, 0x6c, 0x6c, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x20, 0x61, 0x6c, 0x6c, + 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x64, + 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x27, 0x73, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x2e, 0x0a, + 0x20, 0x2a, 0x20, 0x49, 0x74, 0x27, 0x73, 0x20, 0x70, 0x6f, 0x73, 0x73, 0x69, 0x62, 0x6c, 0x65, + 0x20, 0x74, 0x6f, 0x20, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x20, 0x73, 0x70, 0x65, 0x63, + 0x69, 0x66, 0x69, 0x63, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x73, 0x6e, 0x61, 0x70, 0x73, + 0x68, 0x6f, 0x74, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x65, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x74, 0x72, 0x65, 0x65, 0x20, 0x28, 0x76, 0x69, 0x61, 0x20, + 0x60, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, + 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x66, 0x6f, + 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x60, 0x29, 0x2e, 0x0a, 0x20, 0x2a, 0x20, + 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x27, + 0x73, 0x20, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x69, 0x73, 0x20, 0x70, 0x6f, 0x73, 0x73, + 0x69, 0x62, 0x6c, 0x65, 0x2e, 0x0a, 0x0a, 0x20, 0x57, 0x69, 0x74, 0x68, 0x20, 0x70, 0x72, 0x6f, + 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x60, 0x2c, 0x20, 0x68, + 0x6f, 0x77, 0x65, 0x76, 0x65, 0x72, 0x2c, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x74, 0x72, 0x61, 0x64, + 0x65, 0x20, 0x6f, 0x66, 0x66, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, + 0x69, 0x74, 0x79, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x68, 0x69, 0x67, 0x68, 0x20, 0x73, 0x70, 0x65, + 0x65, 0x64, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x66, 0x6f, 0x72, 0x77, + 0x61, 0x72, 0x64, 0x0a, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, 0x65, 0x6c, 0x20, 0x65, 0x78, + 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x20, 0x61, 0x68, 0x65, 0x61, 0x64, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x69, 0x6d, 0x65, 0x2e, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x05, 0x12, 0x03, 0x29, 0x02, 0x06, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x05, 0x01, 0x12, 0x03, 0x29, 0x07, 0x16, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x05, 0x03, 0x12, 0x03, 0x29, 0x19, 0x1a, 0x0a, 0x0b, 0x0a, 0x04, 0x04, + 0x00, 0x02, 0x06, 0x12, 0x03, 0x2b, 0x02, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, + 0x06, 0x12, 0x03, 0x2b, 0x02, 0x09, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x01, 0x12, + 0x03, 0x2b, 0x0a, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x06, 0x03, 0x12, 0x03, 0x2b, + 0x14, 0x15, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x07, 0x12, 0x03, 0x2c, 0x02, 0x25, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x07, 0x04, 0x12, 0x03, 0x2c, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x07, 0x05, 0x12, 0x03, 0x2c, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x07, 0x01, 0x12, 0x03, 0x2c, 0x12, 0x20, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x07, 0x03, 0x12, 0x03, 0x2c, 0x23, 0x24, 0x0a, 0x2f, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x08, 0x12, + 0x03, 0x2f, 0x02, 0x3f, 0x1a, 0x22, 0x20, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, + 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, + 0x65, 0x72, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x08, + 0x04, 0x12, 0x03, 0x2f, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x08, 0x05, 0x12, + 0x03, 0x2f, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x08, 0x01, 0x12, 0x03, 0x2f, + 0x12, 0x3a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, 0x08, 0x03, 0x12, 0x03, 0x2f, 0x3d, 0x3e, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x00, 0x02, 0x09, 0x12, 0x03, 0x31, 0x02, 0x1c, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x00, 0x02, 0x09, 0x05, 0x12, 0x03, 0x31, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x00, 0x02, 0x09, 0x01, 0x12, 0x03, 0x31, 0x09, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x00, 0x02, + 0x09, 0x03, 0x12, 0x03, 0x31, 0x19, 0x1b, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x01, 0x12, 0x04, 0x34, + 0x00, 0x40, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x01, 0x01, 0x12, 0x03, 0x34, 0x08, 0x10, 0x0a, + 0x0c, 0x0a, 0x04, 0x04, 0x01, 0x08, 0x00, 0x12, 0x04, 0x35, 0x02, 0x3f, 0x03, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x08, 0x00, 0x01, 0x12, 0x03, 0x35, 0x08, 0x0f, 0x0a, 0x20, 0x0a, 0x04, 0x04, + 0x01, 0x02, 0x00, 0x12, 0x03, 0x36, 0x04, 0x1c, 0x22, 0x13, 0x20, 0x41, 0x6c, 0x77, 0x61, 0x79, + 0x73, 0x20, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x66, 0x69, 0x72, 0x73, 0x74, 0x0a, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x00, 0x06, 0x12, 0x03, 0x36, 0x04, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x00, 0x01, 0x12, 0x03, 0x36, 0x10, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, + 0x00, 0x03, 0x12, 0x03, 0x36, 0x1a, 0x1b, 0x0a, 0x5b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x01, 0x12, + 0x03, 0x37, 0x04, 0x21, 0x22, 0x4e, 0x20, 0x50, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x20, + 0x6f, 0x66, 0x20, 0x64, 0x61, 0x74, 0x61, 0x20, 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x20, 0x73, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x74, 0x72, 0x65, 0x61, + 0x6d, 0x20, 0x6f, 0x66, 0x20, 0x60, 0x64, 0x61, 0x74, 0x61, 0x60, 0x20, 0x65, 0x76, 0x65, 0x6e, + 0x74, 0x73, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x06, 0x12, 0x03, 0x37, + 0x04, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x01, 0x12, 0x03, 0x37, 0x14, 0x1c, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x01, 0x03, 0x12, 0x03, 0x37, 0x1f, 0x20, 0x0a, 0x6f, + 0x0a, 0x04, 0x04, 0x01, 0x02, 0x02, 0x12, 0x03, 0x3a, 0x04, 0x30, 0x1a, 0x62, 0x20, 0x41, 0x76, + 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x6e, 0x20, + 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x65, 0x72, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2c, 0x20, + 0x61, 0x6e, 0x64, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x66, 0x20, 0x60, 0x64, 0x65, 0x62, + 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, + 0x5f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x6d, 0x6f, + 0x64, 0x75, 0x6c, 0x65, 0x73, 0x60, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, 0x74, 0x2e, 0x0a, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x02, 0x06, 0x12, 0x03, 0x3a, 0x04, 0x17, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x02, 0x01, 0x12, 0x03, 0x3a, 0x18, 0x2b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x01, 0x02, 0x02, 0x03, 0x12, 0x03, 0x3a, 0x2e, 0x2f, 0x0a, 0x6f, 0x0a, 0x04, 0x04, 0x01, 0x02, + 0x03, 0x12, 0x03, 0x3c, 0x04, 0x38, 0x1a, 0x62, 0x20, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x20, 0x6f, 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, + 0x6f, 0x70, 0x65, 0x72, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2c, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x6f, + 0x6e, 0x6c, 0x79, 0x20, 0x69, 0x66, 0x20, 0x60, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x73, 0x6e, 0x61, 0x70, + 0x73, 0x68, 0x6f, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, + 0x60, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, 0x74, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, + 0x02, 0x03, 0x06, 0x12, 0x03, 0x3c, 0x04, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, + 0x01, 0x12, 0x03, 0x3c, 0x1c, 0x33, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x03, 0x03, 0x12, + 0x03, 0x3c, 0x36, 0x37, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x01, 0x02, 0x04, 0x12, 0x03, 0x3e, 0x04, + 0x1d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x06, 0x12, 0x03, 0x3e, 0x04, 0x13, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x01, 0x02, 0x04, 0x01, 0x12, 0x03, 0x3e, 0x14, 0x18, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x01, 0x02, 0x04, 0x03, 0x12, 0x03, 0x3e, 0x1b, 0x1c, 0x0a, 0x0a, 0x0a, 0x02, 0x05, + 0x00, 0x12, 0x04, 0x42, 0x00, 0x4e, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x05, 0x00, 0x01, 0x12, 0x03, + 0x42, 0x05, 0x0d, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x00, 0x12, 0x03, 0x43, 0x02, 0x13, + 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x01, 0x12, 0x03, 0x43, 0x02, 0x0e, 0x0a, 0x0c, + 0x0a, 0x05, 0x05, 0x00, 0x02, 0x00, 0x02, 0x12, 0x03, 0x43, 0x11, 0x12, 0x0a, 0x5b, 0x0a, 0x04, + 0x05, 0x00, 0x02, 0x01, 0x12, 0x03, 0x45, 0x02, 0x0f, 0x1a, 0x4e, 0x20, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x65, 0x77, 0x20, 0x68, 0x65, 0x61, 0x64, 0x20, 0x62, 0x6c, + 0x6f, 0x63, 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x2c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x69, 0x73, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x61, 0x72, + 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x74, 0x68, 0x65, 0x20, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, + 0x75, 0x73, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, + 0x01, 0x01, 0x12, 0x03, 0x45, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x01, 0x02, + 0x12, 0x03, 0x45, 0x0d, 0x0e, 0x0a, 0x65, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x02, 0x12, 0x03, 0x47, + 0x02, 0x10, 0x1a, 0x58, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x69, 0x73, 0x20, 0x6e, 0x6f, + 0x77, 0x20, 0x66, 0x6f, 0x72, 0x6b, 0x65, 0x64, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x73, 0x68, 0x6f, + 0x75, 0x6c, 0x64, 0x20, 0x62, 0x65, 0x20, 0x75, 0x6e, 0x64, 0x6f, 0x6e, 0x65, 0x2c, 0x20, 0x69, + 0x74, 0x27, 0x73, 0x20, 0x6e, 0x6f, 0x74, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x65, 0x61, 0x64, + 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x20, 0x61, 0x6e, 0x79, 0x6d, 0x6f, 0x72, 0x65, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, + 0x05, 0x00, 0x02, 0x02, 0x01, 0x12, 0x03, 0x47, 0x02, 0x0b, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, + 0x02, 0x02, 0x02, 0x12, 0x03, 0x47, 0x0e, 0x0f, 0x0a, 0x24, 0x0a, 0x03, 0x05, 0x00, 0x04, 0x12, + 0x03, 0x49, 0x02, 0x0d, 0x1a, 0x18, 0x20, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x2c, 0x20, + 0x77, 0x61, 0x73, 0x20, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x52, 0x45, 0x44, 0x4f, 0x0a, 0x0a, 0x0b, + 0x0a, 0x04, 0x05, 0x00, 0x04, 0x00, 0x12, 0x03, 0x49, 0x0b, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x05, + 0x00, 0x04, 0x00, 0x01, 0x12, 0x03, 0x49, 0x0b, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x04, + 0x00, 0x02, 0x12, 0x03, 0x49, 0x0b, 0x0c, 0x0a, 0x87, 0x01, 0x0a, 0x04, 0x05, 0x00, 0x02, 0x03, + 0x12, 0x03, 0x4b, 0x02, 0x18, 0x1a, 0x7a, 0x20, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x69, 0x73, + 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x69, 0x72, 0x72, 0x65, 0x76, 0x65, 0x72, 0x73, 0x69, 0x62, 0x6c, + 0x65, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x63, 0x6f, 0x6d, + 0x6d, 0x69, 0x74, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x28, 0x66, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x74, 0x79, 0x20, 0x69, 0x73, 0x20, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x20, 0x73, 0x70, 0x65, + 0x63, 0x69, 0x66, 0x69, 0x63, 0x2c, 0x20, 0x73, 0x65, 0x65, 0x20, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x20, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x66, + 0x6f, 0x72, 0x20, 0x6d, 0x6f, 0x72, 0x65, 0x20, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x29, + 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x01, 0x12, 0x03, 0x4b, 0x02, 0x13, 0x0a, + 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x02, 0x03, 0x02, 0x12, 0x03, 0x4b, 0x16, 0x17, 0x0a, 0x27, 0x0a, + 0x03, 0x05, 0x00, 0x04, 0x12, 0x03, 0x4d, 0x02, 0x0d, 0x1a, 0x1b, 0x20, 0x52, 0x65, 0x6d, 0x6f, + 0x76, 0x65, 0x64, 0x2c, 0x20, 0x77, 0x61, 0x73, 0x20, 0x53, 0x54, 0x45, 0x50, 0x5f, 0x53, 0x54, + 0x41, 0x4c, 0x4c, 0x45, 0x44, 0x0a, 0x0a, 0x0b, 0x0a, 0x04, 0x05, 0x00, 0x04, 0x01, 0x12, 0x03, + 0x4d, 0x0b, 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x04, 0x01, 0x01, 0x12, 0x03, 0x4d, 0x0b, + 0x0c, 0x0a, 0x0c, 0x0a, 0x05, 0x05, 0x00, 0x04, 0x01, 0x02, 0x12, 0x03, 0x4d, 0x0b, 0x0c, 0x0a, + 0x0a, 0x0a, 0x02, 0x04, 0x02, 0x12, 0x04, 0x50, 0x00, 0x52, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, + 0x02, 0x01, 0x12, 0x03, 0x50, 0x08, 0x13, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x02, 0x02, 0x00, 0x12, + 0x03, 0x51, 0x02, 0x16, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x05, 0x12, 0x03, 0x51, + 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x01, 0x12, 0x03, 0x51, 0x09, 0x11, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x02, 0x02, 0x00, 0x03, 0x12, 0x03, 0x51, 0x14, 0x15, 0x0a, 0x0a, + 0x0a, 0x02, 0x04, 0x03, 0x12, 0x04, 0x54, 0x00, 0x56, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x03, + 0x01, 0x12, 0x03, 0x54, 0x08, 0x1f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x03, 0x02, 0x00, 0x12, 0x03, + 0x55, 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x05, 0x12, 0x03, 0x55, 0x02, + 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x01, 0x12, 0x03, 0x55, 0x09, 0x0f, 0x0a, + 0x0c, 0x0a, 0x05, 0x04, 0x03, 0x02, 0x00, 0x03, 0x12, 0x03, 0x55, 0x12, 0x13, 0x0a, 0x0a, 0x0a, + 0x02, 0x04, 0x04, 0x12, 0x04, 0x58, 0x00, 0x5d, 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x04, 0x01, + 0x12, 0x03, 0x58, 0x08, 0x1b, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x00, 0x12, 0x03, 0x59, + 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x05, 0x12, 0x03, 0x59, 0x02, 0x08, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x01, 0x12, 0x03, 0x59, 0x09, 0x14, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x04, 0x02, 0x00, 0x03, 0x12, 0x03, 0x59, 0x17, 0x18, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x04, 0x02, 0x01, 0x12, 0x03, 0x5a, 0x02, 0x19, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, + 0x01, 0x06, 0x12, 0x03, 0x5a, 0x02, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x01, + 0x12, 0x03, 0x5a, 0x0e, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x01, 0x03, 0x12, 0x03, + 0x5a, 0x17, 0x18, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, 0x02, 0x02, 0x12, 0x03, 0x5b, 0x02, 0x17, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x05, 0x12, 0x03, 0x5b, 0x02, 0x08, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x04, 0x02, 0x02, 0x01, 0x12, 0x03, 0x5b, 0x09, 0x12, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x04, 0x02, 0x02, 0x03, 0x12, 0x03, 0x5b, 0x15, 0x16, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x04, + 0x02, 0x03, 0x12, 0x03, 0x5c, 0x02, 0x18, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x05, + 0x12, 0x03, 0x5c, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x01, 0x12, 0x03, + 0x5c, 0x09, 0x13, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x04, 0x02, 0x03, 0x03, 0x12, 0x03, 0x5c, 0x16, + 0x17, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x05, 0x12, 0x04, 0x5f, 0x00, 0x66, 0x01, 0x0a, 0x0a, 0x0a, + 0x03, 0x04, 0x05, 0x01, 0x12, 0x03, 0x5f, 0x08, 0x17, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, + 0x00, 0x12, 0x03, 0x60, 0x02, 0x24, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x04, 0x12, + 0x03, 0x60, 0x02, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x06, 0x12, 0x03, 0x60, + 0x0b, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x01, 0x12, 0x03, 0x60, 0x18, 0x1f, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x00, 0x03, 0x12, 0x03, 0x60, 0x22, 0x23, 0x0a, 0x0b, + 0x0a, 0x04, 0x04, 0x05, 0x02, 0x01, 0x12, 0x03, 0x61, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x05, 0x02, 0x01, 0x06, 0x12, 0x03, 0x61, 0x02, 0x07, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, + 0x01, 0x01, 0x12, 0x03, 0x61, 0x08, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x01, 0x03, + 0x12, 0x03, 0x61, 0x10, 0x11, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x02, 0x12, 0x03, 0x62, + 0x02, 0x14, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x06, 0x12, 0x03, 0x62, 0x02, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x01, 0x12, 0x03, 0x62, 0x0b, 0x0f, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x05, 0x02, 0x02, 0x03, 0x12, 0x03, 0x62, 0x12, 0x13, 0x0a, 0x0b, 0x0a, 0x04, + 0x04, 0x05, 0x02, 0x03, 0x12, 0x03, 0x63, 0x02, 0x15, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, + 0x03, 0x05, 0x12, 0x03, 0x63, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x01, + 0x12, 0x03, 0x63, 0x09, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x03, 0x03, 0x12, 0x03, + 0x63, 0x12, 0x14, 0x0a, 0x46, 0x0a, 0x04, 0x04, 0x05, 0x02, 0x04, 0x12, 0x03, 0x65, 0x02, 0x21, + 0x1a, 0x39, 0x20, 0x50, 0x6f, 0x74, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x6e, + 0x6f, 0x6e, 0x2d, 0x64, 0x65, 0x74, 0x65, 0x72, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x63, + 0x2e, 0x20, 0x52, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x66, + 0x75, 0x74, 0x75, 0x72, 0x65, 0x20, 0x75, 0x73, 0x65, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x05, 0x02, 0x04, 0x05, 0x12, 0x03, 0x65, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, + 0x04, 0x01, 0x12, 0x03, 0x65, 0x09, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x05, 0x02, 0x04, 0x03, + 0x12, 0x03, 0x65, 0x1e, 0x20, 0x0a, 0x0a, 0x0a, 0x02, 0x04, 0x06, 0x12, 0x04, 0x68, 0x00, 0x7a, + 0x01, 0x0a, 0x0a, 0x0a, 0x03, 0x04, 0x06, 0x01, 0x12, 0x03, 0x68, 0x08, 0x14, 0x0a, 0x0b, 0x0a, + 0x04, 0x04, 0x06, 0x02, 0x00, 0x12, 0x03, 0x69, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, + 0x02, 0x00, 0x05, 0x12, 0x03, 0x69, 0x02, 0x08, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, + 0x01, 0x12, 0x03, 0x69, 0x09, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x00, 0x03, 0x12, + 0x03, 0x69, 0x10, 0x11, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x06, 0x08, 0x00, 0x12, 0x04, 0x6b, 0x02, + 0x73, 0x03, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x08, 0x00, 0x01, 0x12, 0x03, 0x6b, 0x08, 0x0c, + 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x01, 0x12, 0x03, 0x6c, 0x04, 0x27, 0x0a, 0x0c, 0x0a, + 0x05, 0x04, 0x06, 0x02, 0x01, 0x06, 0x12, 0x03, 0x6c, 0x04, 0x17, 0x0a, 0x0c, 0x0a, 0x05, 0x04, + 0x06, 0x02, 0x01, 0x01, 0x12, 0x03, 0x6c, 0x18, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, + 0x01, 0x03, 0x12, 0x03, 0x6c, 0x25, 0x26, 0x0a, 0x8f, 0x02, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x02, + 0x12, 0x03, 0x72, 0x04, 0x27, 0x1a, 0x81, 0x02, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x65, + 0x6c, 0x74, 0x61, 0x73, 0x20, 0x61, 0x72, 0x65, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x65, + 0x64, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x64, 0x65, 0x76, 0x65, 0x6c, 0x6f, 0x70, 0x6d, 0x65, + 0x6e, 0x74, 0x20, 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0a, 0x20, 0x49, 0x74, 0x20, 0x69, 0x73, 0x20, + 0x6e, 0x6f, 0x74, 0x20, 0x70, 0x6f, 0x73, 0x73, 0x69, 0x62, 0x6c, 0x65, 0x20, 0x74, 0x6f, 0x20, + 0x72, 0x65, 0x74, 0x72, 0x69, 0x65, 0x76, 0x65, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x6d, + 0x6f, 0x64, 0x65, 0x6c, 0x73, 0x20, 0x69, 0x6e, 0x20, 0x70, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x2c, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x70, 0x61, 0x72, 0x61, 0x6c, 0x6c, + 0x65, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, + 0x65, 0x64, 0x2e, 0x20, 0x49, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6e, 0x65, 0x65, 0x64, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x20, 0x64, 0x69, 0x72, 0x65, 0x63, + 0x74, 0x6c, 0x79, 0x2c, 0x20, 0x77, 0x72, 0x69, 0x74, 0x65, 0x20, 0x61, 0x20, 0x70, 0x61, 0x73, + 0x73, 0x20, 0x74, 0x68, 0x72, 0x6f, 0x75, 0x67, 0x68, 0x20, 0x6d, 0x61, 0x70, 0x70, 0x65, 0x72, + 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x0a, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x77, 0x69, + 0x6c, 0x6c, 0x20, 0x67, 0x65, 0x74, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x64, 0x6f, 0x77, 0x6e, + 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x2e, 0x0a, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, + 0x02, 0x06, 0x12, 0x03, 0x72, 0x04, 0x0f, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x01, + 0x12, 0x03, 0x72, 0x10, 0x22, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x02, 0x03, 0x12, 0x03, + 0x72, 0x25, 0x26, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x06, 0x02, 0x03, 0x12, 0x03, 0x74, 0x02, 0x21, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x03, 0x04, 0x12, 0x03, 0x74, 0x02, 0x0a, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x06, 0x02, 0x03, 0x05, 0x12, 0x03, 0x74, 0x0b, 0x11, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x06, 0x02, 0x03, 0x01, 0x12, 0x03, 0x74, 0x12, 0x1c, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, + 0x02, 0x03, 0x03, 0x12, 0x03, 0x74, 0x1f, 0x20, 0x0a, 0xb5, 0x01, 0x0a, 0x04, 0x04, 0x06, 0x02, + 0x04, 0x12, 0x03, 0x77, 0x02, 0x20, 0x1a, 0xa7, 0x01, 0x20, 0x4c, 0x6f, 0x67, 0x73, 0x54, 0x72, + 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x61, + 0x67, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x73, 0x20, 0x79, 0x6f, 0x75, + 0x20, 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, + 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x20, 0x6f, 0x72, + 0x20, 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x79, 0x0a, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x74, + 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, + 0x20, 0x79, 0x6f, 0x75, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x6f, 0x20, + 0x6d, 0x75, 0x63, 0x68, 0x20, 0x28, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x6d, 0x69, + 0x74, 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x69, 0x73, 0x20, 0x73, + 0x65, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x31, 0x32, 0x38, 0x20, 0x4b, 0x69, 0x42, 0x29, 0x2e, 0x0a, + 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x04, 0x05, 0x12, 0x03, 0x77, 0x02, 0x06, 0x0a, 0x0c, + 0x0a, 0x05, 0x04, 0x06, 0x02, 0x04, 0x01, 0x12, 0x03, 0x77, 0x07, 0x1b, 0x0a, 0x0c, 0x0a, 0x05, + 0x04, 0x06, 0x02, 0x04, 0x03, 0x12, 0x03, 0x77, 0x1e, 0x1f, 0x0a, 0x0b, 0x0a, 0x04, 0x04, 0x06, + 0x02, 0x05, 0x12, 0x03, 0x79, 0x02, 0x12, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x05, 0x05, + 0x12, 0x03, 0x79, 0x02, 0x06, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x05, 0x01, 0x12, 0x03, + 0x79, 0x07, 0x0d, 0x0a, 0x0c, 0x0a, 0x05, 0x04, 0x06, 0x02, 0x05, 0x03, 0x12, 0x03, 0x79, 0x10, + 0x11, 0x0a, 0x88, 0x03, 0x0a, 0x02, 0x04, 0x07, 0x12, 0x06, 0x88, 0x01, 0x00, 0x8a, 0x01, 0x01, + 0x32, 0xf9, 0x02, 0x20, 0x74, 0x68, 0x69, 0x6e, 0x6b, 0x20, 0x61, 0x62, 0x6f, 0x75, 0x74, 0x3a, + 0x0a, 0x20, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, + 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x20, 0x7b, 0x20, 0x2e, 0x2e, 0x2e, 0x0a, 0x20, 0x20, 0x20, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x20, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x20, 0x3d, 0x20, 0x36, + 0x3b, 0x0a, 0x20, 0x2e, 0x2e, 0x2e, 0x7d, 0x0a, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x20, + 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x44, 0x65, 0x62, 0x75, + 0x67, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x44, 0x65, 0x6c, 0x74, 0x61, + 0x73, 0x20, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x73, 0x20, 0x3d, + 0x20, 0x33, 0x3b, 0x0a, 0x20, 0x20, 0x72, 0x65, 0x70, 0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x73, + 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x20, 0x3d, 0x20, 0x34, 0x3b, 0x0a, + 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x4c, 0x6f, 0x67, 0x73, 0x54, 0x72, 0x75, 0x6e, 0x63, 0x61, 0x74, + 0x65, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x20, 0x74, 0x68, 0x61, + 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x69, 0x66, 0x20, 0x79, + 0x6f, 0x75, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, 0x61, 0x6c, 0x6c, 0x20, + 0x74, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x20, 0x6f, 0x72, 0x20, 0x69, 0x66, 0x20, 0x74, + 0x68, 0x65, 0x79, 0x0a, 0x20, 0x20, 0x2f, 0x2f, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x74, 0x72, + 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, + 0x79, 0x6f, 0x75, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x6d, + 0x75, 0x63, 0x68, 0x20, 0x28, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x31, 0x32, 0x38, 0x20, 0x4b, 0x69, 0x42, 0x29, 0x2e, 0x0a, 0x20, + 0x20, 0x62, 0x6f, 0x6f, 0x6c, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x5f, 0x74, 0x72, 0x75, 0x6e, 0x63, + 0x61, 0x74, 0x65, 0x64, 0x20, 0x3d, 0x20, 0x35, 0x3b, 0x0a, 0x7d, 0x0a, 0x0a, 0x0b, 0x0a, 0x03, + 0x04, 0x07, 0x01, 0x12, 0x04, 0x88, 0x01, 0x08, 0x17, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x07, 0x02, + 0x00, 0x12, 0x04, 0x89, 0x01, 0x02, 0x26, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x04, + 0x12, 0x04, 0x89, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x06, 0x12, + 0x04, 0x89, 0x01, 0x0b, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x01, 0x12, 0x04, + 0x89, 0x01, 0x1a, 0x21, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x07, 0x02, 0x00, 0x03, 0x12, 0x04, 0x89, + 0x01, 0x24, 0x25, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x08, 0x12, 0x06, 0x8c, 0x01, 0x00, 0xaa, 0x01, + 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x08, 0x01, 0x12, 0x04, 0x8c, 0x01, 0x08, 0x16, 0x0a, 0x0c, + 0x0a, 0x04, 0x04, 0x08, 0x02, 0x00, 0x12, 0x04, 0x8d, 0x01, 0x02, 0x12, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x08, 0x02, 0x00, 0x05, 0x12, 0x04, 0x8d, 0x01, 0x02, 0x08, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x08, 0x02, 0x00, 0x01, 0x12, 0x04, 0x8d, 0x01, 0x09, 0x0d, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, + 0x02, 0x00, 0x03, 0x12, 0x04, 0x8d, 0x01, 0x10, 0x11, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x08, 0x08, + 0x00, 0x12, 0x06, 0x8f, 0x01, 0x02, 0x94, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x08, + 0x00, 0x01, 0x12, 0x04, 0x8f, 0x01, 0x08, 0x0c, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x01, + 0x12, 0x04, 0x90, 0x01, 0x04, 0x28, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x06, 0x12, + 0x04, 0x90, 0x01, 0x04, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x01, 0x12, 0x04, + 0x90, 0x01, 0x13, 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x01, 0x03, 0x12, 0x04, 0x90, + 0x01, 0x26, 0x27, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x08, 0x02, 0x02, 0x12, 0x04, 0x91, 0x01, 0x04, + 0x23, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x06, 0x12, 0x04, 0x91, 0x01, 0x04, 0x10, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x01, 0x12, 0x04, 0x91, 0x01, 0x11, 0x1e, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x02, 0x03, 0x12, 0x04, 0x91, 0x01, 0x21, 0x22, 0x0a, 0x0c, + 0x0a, 0x04, 0x04, 0x08, 0x02, 0x03, 0x12, 0x04, 0x92, 0x01, 0x04, 0x27, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x08, 0x02, 0x03, 0x06, 0x12, 0x04, 0x92, 0x01, 0x04, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x08, 0x02, 0x03, 0x01, 0x12, 0x04, 0x92, 0x01, 0x13, 0x22, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, + 0x02, 0x03, 0x03, 0x12, 0x04, 0x92, 0x01, 0x25, 0x26, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x08, 0x02, + 0x04, 0x12, 0x04, 0x93, 0x01, 0x04, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x06, + 0x12, 0x04, 0x93, 0x01, 0x04, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x01, 0x12, + 0x04, 0x93, 0x01, 0x0b, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x02, 0x04, 0x03, 0x12, 0x04, + 0x93, 0x01, 0x14, 0x15, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x08, 0x03, 0x00, 0x12, 0x06, 0x96, 0x01, + 0x02, 0x98, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x03, 0x00, 0x01, 0x12, 0x04, 0x96, + 0x01, 0x0a, 0x18, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, 0x00, 0x02, 0x00, 0x12, 0x04, 0x97, + 0x01, 0x04, 0x2d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x00, 0x02, 0x00, 0x04, 0x12, 0x04, + 0x97, 0x01, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x00, 0x02, 0x00, 0x06, 0x12, + 0x04, 0x97, 0x01, 0x0d, 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x00, 0x02, 0x00, 0x01, + 0x12, 0x04, 0x97, 0x01, 0x18, 0x28, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x00, 0x02, 0x00, + 0x03, 0x12, 0x04, 0x97, 0x01, 0x2b, 0x2c, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x08, 0x03, 0x01, 0x12, + 0x06, 0x99, 0x01, 0x02, 0x9b, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x03, 0x01, 0x01, + 0x12, 0x04, 0x99, 0x01, 0x0a, 0x16, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, 0x01, 0x02, 0x00, + 0x12, 0x04, 0x9a, 0x01, 0x04, 0x25, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x01, 0x02, 0x00, + 0x05, 0x12, 0x04, 0x9a, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x01, 0x02, + 0x00, 0x01, 0x12, 0x04, 0x9a, 0x01, 0x0b, 0x20, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x01, + 0x02, 0x00, 0x03, 0x12, 0x04, 0x9a, 0x01, 0x23, 0x24, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x08, 0x03, + 0x02, 0x12, 0x06, 0x9c, 0x01, 0x02, 0xa2, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x03, + 0x02, 0x01, 0x12, 0x04, 0x9c, 0x01, 0x0a, 0x18, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, 0x02, + 0x02, 0x00, 0x12, 0x04, 0x9d, 0x01, 0x04, 0x20, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, + 0x02, 0x00, 0x05, 0x12, 0x04, 0x9d, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, + 0x02, 0x02, 0x00, 0x01, 0x12, 0x04, 0x9d, 0x01, 0x0b, 0x1b, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, + 0x03, 0x02, 0x02, 0x00, 0x03, 0x12, 0x04, 0x9d, 0x01, 0x1e, 0x1f, 0x0a, 0x0e, 0x0a, 0x06, 0x04, + 0x08, 0x03, 0x02, 0x02, 0x01, 0x12, 0x04, 0x9e, 0x01, 0x04, 0x23, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x08, 0x03, 0x02, 0x02, 0x01, 0x05, 0x12, 0x04, 0x9e, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x08, 0x03, 0x02, 0x02, 0x01, 0x01, 0x12, 0x04, 0x9e, 0x01, 0x0b, 0x1e, 0x0a, 0x0f, 0x0a, + 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x01, 0x03, 0x12, 0x04, 0x9e, 0x01, 0x21, 0x22, 0x0a, 0x0e, + 0x0a, 0x06, 0x04, 0x08, 0x03, 0x02, 0x02, 0x02, 0x12, 0x04, 0x9f, 0x01, 0x04, 0x20, 0x0a, 0x0f, + 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x02, 0x05, 0x12, 0x04, 0x9f, 0x01, 0x04, 0x0a, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x02, 0x01, 0x12, 0x04, 0x9f, 0x01, 0x0b, 0x1b, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x02, 0x03, 0x12, 0x04, 0x9f, 0x01, 0x1e, + 0x1f, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, 0x02, 0x02, 0x03, 0x12, 0x04, 0xa0, 0x01, 0x04, + 0x23, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x03, 0x05, 0x12, 0x04, 0xa0, 0x01, + 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x03, 0x01, 0x12, 0x04, 0xa0, + 0x01, 0x0b, 0x1e, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x03, 0x03, 0x12, 0x04, + 0xa0, 0x01, 0x21, 0x22, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, 0x02, 0x02, 0x04, 0x12, 0x04, + 0xa1, 0x01, 0x04, 0x22, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x04, 0x05, 0x12, + 0x04, 0xa1, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x04, 0x01, + 0x12, 0x04, 0xa1, 0x01, 0x0b, 0x1d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x02, 0x02, 0x04, + 0x03, 0x12, 0x04, 0xa1, 0x01, 0x20, 0x21, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x08, 0x03, 0x03, 0x12, + 0x06, 0xa3, 0x01, 0x02, 0xa9, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x08, 0x03, 0x03, 0x01, + 0x12, 0x04, 0xa3, 0x01, 0x0a, 0x10, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, 0x03, 0x02, 0x00, + 0x12, 0x04, 0xa4, 0x01, 0x04, 0x16, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x03, 0x02, 0x00, + 0x05, 0x12, 0x04, 0xa4, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x03, 0x02, + 0x00, 0x01, 0x12, 0x04, 0xa4, 0x01, 0x0b, 0x11, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x03, + 0x02, 0x00, 0x03, 0x12, 0x04, 0xa4, 0x01, 0x14, 0x15, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x08, 0x03, + 0x03, 0x02, 0x01, 0x12, 0x04, 0xa5, 0x01, 0x04, 0x1d, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, + 0x03, 0x02, 0x01, 0x04, 0x12, 0x04, 0xa5, 0x01, 0x04, 0x0c, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, + 0x03, 0x03, 0x02, 0x01, 0x05, 0x12, 0x04, 0xa5, 0x01, 0x0d, 0x13, 0x0a, 0x0f, 0x0a, 0x07, 0x04, + 0x08, 0x03, 0x03, 0x02, 0x01, 0x01, 0x12, 0x04, 0xa5, 0x01, 0x14, 0x18, 0x0a, 0x0f, 0x0a, 0x07, + 0x04, 0x08, 0x03, 0x03, 0x02, 0x01, 0x03, 0x12, 0x04, 0xa5, 0x01, 0x1b, 0x1c, 0x0a, 0xbf, 0x01, + 0x0a, 0x06, 0x04, 0x08, 0x03, 0x03, 0x02, 0x02, 0x12, 0x04, 0xa8, 0x01, 0x04, 0x1c, 0x1a, 0xae, + 0x01, 0x20, 0x46, 0x61, 0x69, 0x6c, 0x75, 0x72, 0x65, 0x4c, 0x6f, 0x67, 0x73, 0x54, 0x72, 0x75, + 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x69, 0x73, 0x20, 0x61, 0x20, 0x66, 0x6c, 0x61, 0x67, + 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x73, 0x20, 0x79, 0x6f, 0x75, 0x20, + 0x69, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x20, + 0x61, 0x6c, 0x6c, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6c, 0x6f, 0x67, 0x73, 0x20, 0x6f, 0x72, 0x20, + 0x69, 0x66, 0x20, 0x74, 0x68, 0x65, 0x79, 0x0a, 0x20, 0x77, 0x65, 0x72, 0x65, 0x20, 0x74, 0x72, + 0x75, 0x6e, 0x63, 0x61, 0x74, 0x65, 0x64, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x20, + 0x79, 0x6f, 0x75, 0x20, 0x6c, 0x6f, 0x67, 0x67, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x6f, 0x20, 0x6d, + 0x75, 0x63, 0x68, 0x20, 0x28, 0x66, 0x69, 0x78, 0x65, 0x64, 0x20, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x20, 0x63, 0x75, 0x72, 0x72, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x20, 0x69, 0x73, 0x20, 0x73, 0x65, + 0x74, 0x20, 0x74, 0x6f, 0x20, 0x31, 0x32, 0x38, 0x20, 0x4b, 0x69, 0x42, 0x29, 0x2e, 0x0a, 0x0a, + 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x03, 0x02, 0x02, 0x05, 0x12, 0x04, 0xa8, 0x01, 0x04, 0x08, + 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x03, 0x02, 0x02, 0x01, 0x12, 0x04, 0xa8, 0x01, 0x09, + 0x17, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x08, 0x03, 0x03, 0x02, 0x02, 0x03, 0x12, 0x04, 0xa8, 0x01, + 0x1a, 0x1b, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x09, 0x12, 0x06, 0xac, 0x01, 0x00, 0xaf, 0x01, 0x01, + 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x09, 0x01, 0x12, 0x04, 0xac, 0x01, 0x08, 0x12, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x09, 0x02, 0x00, 0x12, 0x04, 0xad, 0x01, 0x02, 0x19, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x09, 0x02, 0x00, 0x05, 0x12, 0x04, 0xad, 0x01, 0x02, 0x08, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, + 0x02, 0x00, 0x01, 0x12, 0x04, 0xad, 0x01, 0x09, 0x14, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, + 0x00, 0x03, 0x12, 0x04, 0xad, 0x01, 0x17, 0x18, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x09, 0x02, 0x01, + 0x12, 0x04, 0xae, 0x01, 0x02, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, 0x05, 0x12, + 0x04, 0xae, 0x01, 0x02, 0x08, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, 0x01, 0x12, 0x04, + 0xae, 0x01, 0x09, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x09, 0x02, 0x01, 0x03, 0x12, 0x04, 0xae, + 0x01, 0x15, 0x16, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0a, 0x12, 0x06, 0xb1, 0x01, 0x00, 0xb3, 0x01, + 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x0a, 0x01, 0x12, 0x04, 0xb1, 0x01, 0x08, 0x13, 0x0a, 0x0c, + 0x0a, 0x04, 0x04, 0x0a, 0x02, 0x00, 0x12, 0x04, 0xb2, 0x01, 0x02, 0x21, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0a, 0x02, 0x00, 0x04, 0x12, 0x04, 0xb2, 0x01, 0x02, 0x0a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0a, 0x02, 0x00, 0x06, 0x12, 0x04, 0xb2, 0x01, 0x0b, 0x15, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, + 0x02, 0x00, 0x01, 0x12, 0x04, 0xb2, 0x01, 0x16, 0x1c, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0a, 0x02, + 0x00, 0x03, 0x12, 0x04, 0xb2, 0x01, 0x1f, 0x20, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0b, 0x12, 0x06, + 0xb5, 0x01, 0x00, 0xc1, 0x01, 0x01, 0x0a, 0x0b, 0x0a, 0x03, 0x04, 0x0b, 0x01, 0x12, 0x04, 0xb5, + 0x01, 0x08, 0x12, 0x0a, 0x0e, 0x0a, 0x04, 0x04, 0x0b, 0x04, 0x00, 0x12, 0x06, 0xb6, 0x01, 0x02, + 0xbb, 0x01, 0x03, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x04, 0x00, 0x01, 0x12, 0x04, 0xb6, 0x01, + 0x07, 0x10, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x00, 0x12, 0x04, 0xb7, 0x01, + 0x04, 0x0e, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x00, 0x01, 0x12, 0x04, 0xb7, + 0x01, 0x04, 0x09, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x00, 0x02, 0x12, 0x04, + 0xb7, 0x01, 0x0c, 0x0d, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x01, 0x12, 0x04, + 0xb8, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x01, 0x01, 0x12, + 0x04, 0xb8, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x01, 0x02, + 0x12, 0x04, 0xb8, 0x01, 0x0d, 0x0e, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x02, + 0x12, 0x04, 0xb9, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, 0x02, 0x02, + 0x01, 0x12, 0x04, 0xb9, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, 0x02, + 0x02, 0x02, 0x12, 0x04, 0xb9, 0x01, 0x0d, 0x0e, 0x0a, 0x0e, 0x0a, 0x06, 0x04, 0x0b, 0x04, 0x00, + 0x02, 0x03, 0x12, 0x04, 0xba, 0x01, 0x04, 0x0f, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, 0x00, + 0x02, 0x03, 0x01, 0x12, 0x04, 0xba, 0x01, 0x04, 0x0a, 0x0a, 0x0f, 0x0a, 0x07, 0x04, 0x0b, 0x04, + 0x00, 0x02, 0x03, 0x02, 0x12, 0x04, 0xba, 0x01, 0x0d, 0x0e, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, + 0x02, 0x00, 0x12, 0x04, 0xbc, 0x01, 0x02, 0x1a, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, + 0x06, 0x12, 0x04, 0xbc, 0x01, 0x02, 0x0b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x01, + 0x12, 0x04, 0xbc, 0x01, 0x0c, 0x15, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x00, 0x03, 0x12, + 0x04, 0xbc, 0x01, 0x18, 0x19, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x01, 0x12, 0x04, 0xbd, + 0x01, 0x02, 0x15, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x05, 0x12, 0x04, 0xbd, 0x01, + 0x02, 0x08, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x01, 0x12, 0x04, 0xbd, 0x01, 0x09, + 0x10, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x01, 0x03, 0x12, 0x04, 0xbd, 0x01, 0x13, 0x14, + 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x02, 0x12, 0x04, 0xbe, 0x01, 0x02, 0x11, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x02, 0x05, 0x12, 0x04, 0xbe, 0x01, 0x02, 0x08, 0x0a, 0x0d, 0x0a, + 0x05, 0x04, 0x0b, 0x02, 0x02, 0x01, 0x12, 0x04, 0xbe, 0x01, 0x09, 0x0c, 0x0a, 0x0d, 0x0a, 0x05, + 0x04, 0x0b, 0x02, 0x02, 0x03, 0x12, 0x04, 0xbe, 0x01, 0x0f, 0x10, 0x0a, 0x0c, 0x0a, 0x04, 0x04, + 0x0b, 0x02, 0x03, 0x12, 0x04, 0xbf, 0x01, 0x02, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, + 0x03, 0x05, 0x12, 0x04, 0xbf, 0x01, 0x02, 0x07, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x03, + 0x01, 0x12, 0x04, 0xbf, 0x01, 0x08, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x03, 0x03, + 0x12, 0x04, 0xbf, 0x01, 0x14, 0x15, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0b, 0x02, 0x04, 0x12, 0x04, + 0xc0, 0x01, 0x02, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, 0x05, 0x12, 0x04, 0xc0, + 0x01, 0x02, 0x07, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, 0x01, 0x12, 0x04, 0xc0, 0x01, + 0x08, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0b, 0x02, 0x04, 0x03, 0x12, 0x04, 0xc0, 0x01, 0x14, + 0x15, 0x0a, 0x0c, 0x0a, 0x02, 0x04, 0x0c, 0x12, 0x06, 0xc3, 0x01, 0x00, 0xc8, 0x01, 0x01, 0x0a, + 0x0b, 0x0a, 0x03, 0x04, 0x0c, 0x01, 0x12, 0x04, 0xc3, 0x01, 0x08, 0x0e, 0x0a, 0x0c, 0x0a, 0x04, + 0x04, 0x0c, 0x02, 0x00, 0x12, 0x04, 0xc4, 0x01, 0x02, 0x17, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, + 0x02, 0x00, 0x05, 0x12, 0x04, 0xc4, 0x01, 0x02, 0x08, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x00, 0x01, 0x12, 0x04, 0xc4, 0x01, 0x09, 0x12, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x00, + 0x03, 0x12, 0x04, 0xc4, 0x01, 0x15, 0x16, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x01, 0x12, + 0x04, 0xc5, 0x01, 0x02, 0x16, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x01, 0x05, 0x12, 0x04, + 0xc5, 0x01, 0x02, 0x08, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x01, 0x01, 0x12, 0x04, 0xc5, + 0x01, 0x09, 0x11, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x01, 0x03, 0x12, 0x04, 0xc5, 0x01, + 0x14, 0x15, 0x0a, 0x0c, 0x0a, 0x04, 0x04, 0x0c, 0x02, 0x02, 0x12, 0x04, 0xc6, 0x01, 0x02, 0x2a, + 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x06, 0x12, 0x04, 0xc6, 0x01, 0x02, 0x1b, 0x0a, + 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x01, 0x12, 0x04, 0xc6, 0x01, 0x1c, 0x25, 0x0a, 0x0d, + 0x0a, 0x05, 0x04, 0x0c, 0x02, 0x02, 0x03, 0x12, 0x04, 0xc6, 0x01, 0x28, 0x29, 0x0a, 0x0c, 0x0a, + 0x04, 0x04, 0x0c, 0x02, 0x03, 0x12, 0x04, 0xc7, 0x01, 0x02, 0x21, 0x0a, 0x0d, 0x0a, 0x05, 0x04, + 0x0c, 0x02, 0x03, 0x06, 0x12, 0x04, 0xc7, 0x01, 0x02, 0x15, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, + 0x02, 0x03, 0x01, 0x12, 0x04, 0xc7, 0x01, 0x16, 0x1b, 0x0a, 0x0d, 0x0a, 0x05, 0x04, 0x0c, 0x02, + 0x03, 0x03, 0x12, 0x04, 0xc7, 0x01, 0x1e, 0x20, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +]; +// @@protoc_insertion_point(module) \ No newline at end of file diff --git a/firehose/substreams/codegen/test_substreams/proto/my/v1/my.proto b/firehose/substreams/codegen/test_substreams/proto/my/v1/my.proto new file mode 100644 index 0000000..a424150 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/proto/my/v1/my.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +import "my/v1/substreams-options.proto"; +package my.types.v1; + +option (rust_module) = "my_types_v1"; + +message Tests { + repeated Test tests = 1; +} + +message Test { + string field1 = 1; +} + +message Number { + int64 field1 = 1; +} \ No newline at end of file diff --git a/firehose/substreams/codegen/test_substreams/proto/my/v1/substreams-options.proto b/firehose/substreams/codegen/test_substreams/proto/my/v1/substreams-options.proto new file mode 100644 index 0000000..f8ed9c3 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/proto/my/v1/substreams-options.proto @@ -0,0 +1,7 @@ +syntax = "proto3"; + +import "google/protobuf/descriptor.proto"; + +extend google.protobuf.FileOptions { + string rust_module = 56781; +} diff --git a/firehose/substreams/codegen/test_substreams/substreams.yaml b/firehose/substreams/codegen/test_substreams/substreams.yaml new file mode 100644 index 0000000..1b26e66 --- /dev/null +++ b/firehose/substreams/codegen/test_substreams/substreams.yaml @@ -0,0 +1,83 @@ +specVersion: v0.1.0 +package: + name: uniswap_v3 + version: v0.1.0 + url: https://github.com/streamingfast/substreams + doc: some doc +### section added to have the manifest validation work, don't need to build anything + +binaries: + default: + type: wasm/rust-v1 + file: ./bla/bla + protoPackageMapping: + sf.ethereum.type.v2: "substreams_ethereum::pb::eth::v2" + sf.substreams.v1: "substreams::pb::substreams" + my.types.v1: "pb::my_types_v1" + +protobuf: + files: + - my/v1/my.proto + importPaths: + - ./proto + +modules: + - name: map_block + kind: map + initialBlock: 1 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:my.types.v1.Tests + + - name: map_block_i64 + kind: map + initialBlock: 1 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:my.types.v1.Number + + - name: store_test + kind: store + initialBlock: 1 + updatePolicy: set + valueType: proto:my.types.v1.Test + inputs: + - source: sf.ethereum.type.v2.Block + - map: map_block + + - name: store_append_string + kind: store + initialBlock: 1 + updatePolicy: append + valueType: string + inputs: + - source: sf.ethereum.type.v2.Block + + - name: store_bigint + kind: store + initialBlock: 1 + updatePolicy: set + valueType: bigint + inputs: + - source: sf.ethereum.type.v2.Block + + - name: store_test2 + kind: store + initialBlock: 1 + updatePolicy: set + valueType: proto:my.types.v1.Test + inputs: + - source: sf.ethereum.type.v2.Block + - map: map_block + - store: store_test + - store: store_test + mode: deltas + - map: map_block_i64 + - store: store_bigint + - store: store_bigint + mode: deltas + - store: store_append_string + - store: store_append_string + mode: deltas diff --git a/firehose/substreams/devel/substreams b/firehose/substreams/devel/substreams new file mode 100755 index 0000000..7566ddf --- /dev/null +++ b/firehose/substreams/devel/substreams @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && cd .. && pwd )" + +active_pid= + +main() { + set -e + + version="unknown" + if [[ -f .version ]]; then + version=`cat .version` + fi + + binary_name="substreams" + binary_package="./cmd/${binary_name}" + + pushd "$ROOT" &> /dev/null + go install -ldflags "-X main.Version=$version" "${binary_package}" + popd &> /dev/null + + if [[ $KILL_AFTER != "" ]]; then + "${GOPATH:-$HOME/go}/bin/${binary_name}" "$@" & + active_pid=$! + + sleep $KILL_AFTER + kill -s TERM $active_pid &> /dev/null || true + else + exec "${GOPATH:-$HOME/go}/bin/${binary_name}" "$@" + fi +} + +main "$@" diff --git a/firehose/substreams/docs/.gitbook/assets/chains-endpoints.png b/firehose/substreams/docs/.gitbook/assets/chains-endpoints.png new file mode 100644 index 0000000..cca6213 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/chains-endpoints.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-block-structure.png b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-block-structure.png new file mode 100644 index 0000000..d2d358e Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-block-structure.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-blockheader-structure.png b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-blockheader-structure.png new file mode 100644 index 0000000..2cd3d04 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-blockheader-structure.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-logs-structure.png b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-logs-structure.png new file mode 100644 index 0000000..f26e814 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-logs-structure.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-transaction-structure.png b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-transaction-structure.png new file mode 100644 index 0000000..95a1b8e Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/cheatsheet/cheatsheet-transaction-structure.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/consume/consume-services-only.png b/firehose/substreams/docs/.gitbook/assets/consume/consume-services-only.png new file mode 100644 index 0000000..a82dc99 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/consume/consume-services-only.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/consume/consume-services.png b/firehose/substreams/docs/.gitbook/assets/consume/consume-services.png new file mode 100644 index 0000000..651bd46 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/consume/consume-services.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/consume/featured-packages.png b/firehose/substreams/docs/.gitbook/assets/consume/featured-packages.png new file mode 100644 index 0000000..0abd206 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/consume/featured-packages.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/consume/service-sql.png b/firehose/substreams/docs/.gitbook/assets/consume/service-sql.png new file mode 100644 index 0000000..1f03f8b Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/consume/service-sql.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/consume/service-stream.png b/firehose/substreams/docs/.gitbook/assets/consume/service-stream.png new file mode 100644 index 0000000..0e01762 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/consume/service-stream.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/consume/service-subgraph.png b/firehose/substreams/docs/.gitbook/assets/consume/service-subgraph.png new file mode 100644 index 0000000..eb3ced9 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/consume/service-subgraph.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/develop/provider-gif.gif b/firehose/substreams/docs/.gitbook/assets/develop/provider-gif.gif new file mode 100644 index 0000000..10b8980 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/develop/provider-gif.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/develop/rust-package-gif.gif b/firehose/substreams/docs/.gitbook/assets/develop/rust-package-gif.gif new file mode 100644 index 0000000..4648aec Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/develop/rust-package-gif.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/develop/sending-gif.gif b/firehose/substreams/docs/.gitbook/assets/develop/sending-gif.gif new file mode 100644 index 0000000..28cd351 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/develop/sending-gif.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/develop/transformations-gif.gif b/firehose/substreams/docs/.gitbook/assets/develop/transformations-gif.gif new file mode 100644 index 0000000..e1e357d Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/develop/transformations-gif.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/erc721.json b/firehose/substreams/docs/.gitbook/assets/erc721.json new file mode 100644 index 0000000..dc8a5c2 --- /dev/null +++ b/firehose/substreams/docs/.gitbook/assets/erc721.json @@ -0,0 +1,388 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "_approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "tokenOfOwnerByIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "tokenURI", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/firehose/substreams/docs/.gitbook/assets/eth-scan-calls.png b/firehose/substreams/docs/.gitbook/assets/eth-scan-calls.png new file mode 100644 index 0000000..1e0c217 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/eth-scan-calls.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/gui.png b/firehose/substreams/docs/.gitbook/assets/gui/gui.png new file mode 100644 index 0000000..0d2568c Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/gui.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/jump-to-block.gif b/firehose/substreams/docs/.gitbook/assets/gui/jump-to-block.gif new file mode 100644 index 0000000..cb2545c Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/jump-to-block.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/launching.gif b/firehose/substreams/docs/.gitbook/assets/gui/launching.gif new file mode 100644 index 0000000..9cd9bfc Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/launching.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/navigating-blocks.gif b/firehose/substreams/docs/.gitbook/assets/gui/navigating-blocks.gif new file mode 100644 index 0000000..0f1a5c9 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/navigating-blocks.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/navigating-modules.gif b/firehose/substreams/docs/.gitbook/assets/gui/navigating-modules.gif new file mode 100644 index 0000000..23d2141 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/navigating-modules.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/output.png b/firehose/substreams/docs/.gitbook/assets/gui/output.png new file mode 100644 index 0000000..9851b65 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/output.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/restart.gif b/firehose/substreams/docs/.gitbook/assets/gui/restart.gif new file mode 100644 index 0000000..90301bd Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/restart.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/run.png b/firehose/substreams/docs/.gitbook/assets/gui/run.png new file mode 100644 index 0000000..6fd4a72 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/run.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/search.gif b/firehose/substreams/docs/.gitbook/assets/gui/search.gif new file mode 100644 index 0000000..94c9356 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/search.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/gui/tabs.gif b/firehose/substreams/docs/.gitbook/assets/gui/tabs.gif new file mode 100644 index 0000000..b2c3990 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/gui/tabs.gif differ diff --git a/firehose/substreams/docs/.gitbook/assets/intro/consume-flow.png b/firehose/substreams/docs/.gitbook/assets/intro/consume-flow.png new file mode 100644 index 0000000..485bafc Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/intro/consume-flow.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/intro/develop-flow.png b/firehose/substreams/docs/.gitbook/assets/intro/develop-flow.png new file mode 100644 index 0000000..1665d3a Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/intro/develop-flow.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/intro/solana-logo.png b/firehose/substreams/docs/.gitbook/assets/intro/solana-logo.png new file mode 100644 index 0000000..cdb381e Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/intro/solana-logo.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/intro/supported-chains.png b/firehose/substreams/docs/.gitbook/assets/intro/supported-chains.png new file mode 100644 index 0000000..fbb2f5f Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/intro/supported-chains.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/packages/arch.png b/firehose/substreams/docs/.gitbook/assets/packages/arch.png new file mode 100644 index 0000000..755d414 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/packages/arch.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/packages/erc20-balance-changes.png b/firehose/substreams/docs/.gitbook/assets/packages/erc20-balance-changes.png new file mode 100644 index 0000000..f24d63c Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/packages/erc20-balance-changes.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/pgweb.png b/firehose/substreams/docs/.gitbook/assets/pgweb.png new file mode 100644 index 0000000..f7a588a Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/pgweb.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/postgraphile.png b/firehose/substreams/docs/.gitbook/assets/postgraphile.png new file mode 100644 index 0000000..6143bfa Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/postgraphile.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/sql/explore-pgweb.png b/firehose/substreams/docs/.gitbook/assets/sql/explore-pgweb.png new file mode 100644 index 0000000..368bea4 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/sql/explore-pgweb.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-in-action.png b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-in-action.png new file mode 100644 index 0000000..e4cd53a Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-in-action.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-map-module-handler (1).png b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-map-module-handler (1).png new file mode 100644 index 0000000..ff285eb Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-map-module-handler (1).png differ diff --git a/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-map-module-handler.png b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-map-module-handler.png new file mode 100644 index 0000000..ff285eb Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-map-module-handler.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-module-handler-and-protobuf.png b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-module-handler-and-protobuf.png new file mode 100644 index 0000000..327fd33 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/substreams-breakdown-module-handler-and-protobuf.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/substreams.excalidraw.svg b/firehose/substreams/docs/.gitbook/assets/substreams.excalidraw.svg new file mode 100644 index 0000000..b3a8514 --- /dev/null +++ b/firehose/substreams/docs/.gitbook/assets/substreams.excalidraw.svg @@ -0,0 +1,16 @@ + + + eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nOVcXFtX2tpcdTAwMTZ+769guF/OOaNkr/ulb1xutljFXHUwMDFi1qKne7hjiICGXHUwMDA0k6DiXHUwMDFl/e9nXHUwMDA2XHUwMDE0Qi5cYlxuXHUwMDE2etIxKqyVy0zW/Ob85iX886FQ2Fxi+11741Nhw36wTKfd8M37jY/R+J3tXHUwMDA3bc+FKTL4XHUwMDFleD3fXHUwMDFh7NlcbsNu8OnPP8dHXHUwMDE4ltdcdTAwMTlcdTAwMWVlO3bHdsNcdTAwMDD2+y98L1x1MDAxNP5cdTAwMTn8XHUwMDFmu45vW6HpNlx1MDAxZHtwwGBqfCnGeHJ033NcdTAwMDeXVVozKlx1MDAwNGejXHUwMDFk2kFcdTAwMTkuXHUwMDE32lxymL0yncBcdTAwMWXPRENcdTAwMWKHhy1WXHQ7zqO5efBQazRRy7/YXHUwMDFlX/Wq7Ti1sO9cZu/JtFo9PyZTXHUwMDEw+t6N/b3dXGJbMI9cdTAwMTPjo+NcdTAwMDJcdTAwMGaewPgo3+s1W65cdTAwMWRcdTAwMDRcdTAwMTPHeF3Taof9aFxmodHo8CF8KoxHXHUwMDFloieAsUEwU4zK8aOIXHUwMDBlpVxiXHUwMDE5mCiNXHUwMDA04Vx0aUqe4/mRNH+gwTaW59K0bpoglNtcdTAwMTjtXHUwMDEz+qZcdTAwMWJ0TVx1MDAxZlZpvN/9830qYlx1MDAxMIVcdNNP2/hpt+x2s1x1MDAxNUb7YGpoROiEhIE9WFx1MDAwNoyVoIRhMT4uunx3pzHWiMFosdO7vbi1S93G9V5576rhXHUwMDA2W8X+xtP8X8mn3TL97tNT3Vxioi+xO4xubjupdXHNi2nEiTo66tarO2bfo1x1MDAxZC5vXHUwMDBmPlx1MDAxZlx1MDAxZFujc02oqen73v3GaOZnSrRet2FcdTAwMGV1XHUwMDBmXHUwMDBiQSnTXGYhrclo3mm7NzDp9lx1MDAxY2c85lk3Y3VcdTAwMWSM/vyYXHKT0H5cYrNcdTAwMTBCXHUwMDE4zUNcYkZEK0xcdTAwMDVWM0Pk7rjWcU/ZbktcdTAwMWSI3Yuv/nUlXHUwMDE0uytcdTAwMGZcdTAwMTGCXHKhOcUkgVx1MDAxMICO0mxyYuFcYuEqXHJcdI5SQECAXCJcIplAv1xmXGKLVNWxRnhuWGs/RotC0MToZ7PTdvpcdTAwMTOLOtBhkPRz27dbXmBcdTAwMTdt17x07MZcdTAwMGZ3Kzq91TLbbmHfa9jx5VxibFx1MDAxMCc6f0zRo1x1MDAxM2067WZcdTAwMDSAXHLHvppERthcdTAwMDY/NJpcdTAwMGW92EOxQDC4iO3vNJI36PntZts1nZOZhZyK16luTcRcdTAwMWV2XHUwMDAytJxcdTAwMTCklJpcdTAwMDOzpZ7bOd1+eKh828LVvWq9etStnuZg1vK9ICi2zNBq/XrcXG6tjVx1MDAwNGRcdFxmMco5XHUwMDEzSr5cdLN/XFxccrY0XsGXXHUwMDFh8smfXHUwMDBlTiOyPVx1MDAxYUaYi2e/J5NwZoJQRFx1MDAxNHvJre037VP3yFx1MDAxMdbjcVxyXHUwMDE166XObu2+snS3Vlx1MDAwYvUmXHUwMDBij6t2v/NYso43v+yyvcPZ3NrHaed9g7ucel77tt7aXHUwMDE1UoVcdTAwMDet6jE+uLi4JnV3YW6YSSHGi7wkN0y1yEM0sCfGXHUwMDE5kVx1MDAxMs1cZmnWv7/+KmXpKzJ3POtzeV/U/fNVd8NcdTAwMTJpQ1I0XHTdZz9MXHUwMDEwxlx1MDAxMr9cdTAwMTHU01x1MDAxZLFGaSRnOGKKpdaCcvHLoLs6jrjWu1x1MDAwNPlts1x1MDAxM1x1MDAxNGq2f9e27Fx1MDAxZu6/wrbt44+FXmD7cFxyq+02/71cbv54Zllf75aBK6NcXFx1MDAxNFx1MDAwYlxiYlx1MDAxNOZzOObm3XXZ2exs31x1MDAxZpry/lR+a5TDL2rlUSy5ocFY0VTAyZEwxJJBzKhBn1x1MDAxOPvgTDRccmlGjKFww2shkkI4x4hKhF50zlxuXHUwMDFkXnXv+2W7Uy1dnFx1MDAxY3/nrtksx9zOXHUwMDA2qt/df32wXHUwMDFj1zlq6M7Zfr/iVzqr7L1n8IZcdTAwMGJcckqno1x04rI8NEFQilx1MDAxMIfwdGYwdY6kWfouXHUwMDE53z7U6sTa269cdTAwMWZVb1dcdTAwMWVMmlx1MDAxYkhziVKxKdhcdTAwMTFcdTAwMDPzZNJkXHUwMDE10YRUXHUwMDE0WmNFXkDT9tX29cWNxHebXG5tlS5Oz0pcdTAwMTcxP7BUNK2KvrNcXH3HXGbMXHUwMDEx1kqJmVx1MDAxNb54XHUwMDEyWvVOe0u4vaNSSXwtle/r16uu8FxuXHUwMDEzQ3HKo9gpofBcdTAwMTSgsFx1MDAwZfquXHRcdTAwMTKM0ZhpytZ3aqtT1Pi+U9t3vnfFTmWv2ate/mb6nlx1MDAxN+1gkZ90hGhLc83miHbKXHUwMDE10u5eyMeTO0tsX7VcdTAwMWJcdTAwMGb1g/OD9UhgXGIjkYBcdTAwMWaoumbG23lSflx1MDAwNkNkpCzSgVx1MDAwZcGMK/iPZFx1MDAxYu6VUcCFRTBgXHUwMDE1TMexnVx1MDAxZm7X9yy4XHUwMDEzz1x1MDAwZlxug1xigXwstN3Q9iGaWLVQZkahp6J0yFx1MDAwMjNgSmKJq2SaXHUwMDExS1x1MDAwNVx1MDAxOJ3DXHUwMDFmTaelK4xSXHUwMDAwgUGVhqBAXHUwMDEwilUs4ddcdTAwMWYsPTJksnqwULhcdTAwMWFcdTAwMThcdTAwMGJcdTAwMDToXHUwMDEwlEiBScwqjNArmEGR4jqjgsa0VJxBSDM/ilx1MDAwN1x1MDAwMr43ioPQ9MOtttuAWFx1MDAxYybH8c1zKXhnhqT1XHUwMDAw91YvkrKIgCdLJZFcdTAwMDRrxlxiPCNFY7s1zW4ktTJcYoXZyOY+zY0yjlx1MDAxYrbbeFme6bF6TFx1MDAxZWRo0CGFhdKUgE4xkZZcdTAwMDZcdTAwMWKcXHUwMDBl3EJKXHUwMDE2x1xmwpLX6bRDeNyHXHUwMDFlXHUwMDAwPPlYXHUwMDA3z28zQnTLNlOWXHUwMDA07iU+l4R+NzrjJE9cdTAwMTl/KoxxMfgy+vzXx8y9i/maXHUwMDFibTGdXHUwMDFkn+lD/O/c9kry3LJcYlx1MDAwMVxyIJzPYa+mJ6tX2F5cdCRcZsSRVKl6pkBcdTAwMDYwU6RcdTAwMThcdTAwMTUyMlgsIdNiXGaWYlx1MDAwNknmeUaWXG5cdTAwMTlcYtgzXHUwMDA1o4Q0XHUwMDA2Jq1iPPmZQSvKXHUwMDEwoDQnYlxcW5M1vX1kwkQgrjVcdTAwMTOwiIQg4MNEx5DzbCSI8VTmep3BmseAglxmYK2QXHUwMDA0bk64UuOK9UhcdTAwMWFlxHC8nuYqQ22jrZihsXPaq1x1MDAwMUnMolexXFxwwlxcaURcdTAwMTSs+lx1MDAxY81JO8oztztnO+XeyYOUrcN6y2uRdTBX4Fx1MDAwM5FcdTAwMDG6nlH3kdpQXHUwMDEwSL815s+1VHBdNlx1MDAxMX2NzFx1MDAxNFXY0JKIiTTEs31cIlx1MDAxYeynjnnu97FPXHUwMDEwLSq9XHUwMDAw+5REWM7MgrE7MbdYnpFcXMZoiy/gnHDNS1ponVx1MDAxY1x1MDAxY1doOaXAdcTYfbxcdTAwMDTXvcedy6t+57NcdTAwMWKU207IW3dcdTAwMTfFs29rXHUwMDAxVyyilr6MXGZcdTAwMWT4Klx1MDAwMy0tXGbCWVmLXGZwUoFcdTAwMDEn8f6X3zBr8S1cdTAwMDBcdTAwMDWE8L5cdTAwMTC27MJ924mblHFWXCL2XGbeIyuRXHUwMDE26lUwwzS/hqpcdTAwMTinwD/07G5R8ePt2t3WRbl1eYpkZbOn9OXJOuBcZixKuj33XHUwMDE5ZqDly8xcdTAwMGZm5L3TSCNcdTAwMTJjKTX6f0lcdTAwMGa23StcdTAwMWZcXKLfs8KJUsmvXHUwMDAzXFy+cFOBN7VcYlx1MDAwNVx1MDAwNDdcdTAwMTd9WjKISOI1j5fQd35Qv/tssu2zasXBd1tcdTAwMGZcdTAwMDe29d1ZXHUwMDA39IFcdTAwMTHihkxGscNcdTAwMGVDukwvx7RcdTAwMTE5VvS8ZTg9XHSSXHKJzXBcdTAwMTecRCZcdTAwMDamqFx1MDAxOZHoXHUwMDE1XHUwMDA09S2NXHUwMDA3x2V+/yg37erN5mZvt19s7F1cdTAwMWaNOc2EXHUwMDBlvqZcco/EXHUwMDEzXHUwMDE2yypM5delKOWMYTm76zm99b6SfX52+1jcRPvq4Dg4XHUwMDEytfVQ/ojJZSWQXHUwMDA2mclcdTAwMDT1W6j6x4pN00hcdTAwMWVBmmrwXHUwMDA1S1DwaSpIXHUwMDA1IP/dfE/ZXGbNyLlcdTAwMTTqq+BsYtK83rtgmlx1MDAxZkMxRVx1MDAxOFVcXM7e0vPtgt5Y51x1MDAxNjlcdTAwMTTqzDk6PHaq5cpaZGgxjrJ3iqTbeohGxttfOJnmXtL40lx1MDAxMClcdTAwMTM+ifZxM4OSWCqZ3Vxut4qe5OO08/5cdTAwMDaN4iw/Olwimlx0iuN5qZdcdTAwMTBcdTAwMTSw3UdZ6pZ3rrvet9uLb1x1MDAwZmfnWq5cdTAwMDeCkq4p1qyzUMTgjNezMt5TpFxiVp+p1+RcdTAwMDTfXHUwMDAylPpcdTAwMGVybj9v6sqh9dXdrrbPXHUwMDBmzsjCXkBcdTAwMDRvJ+g8lGtRsdZcdTAwMGY3gGtcdTAwMTVC74fbmO5cdTAwMDTFr4q4skWciujcsiXPJ51MacSFILOHXFzTTecqQ5pIaiQrXHUwMDAwlFx1MDAxYXxcdTAwMDEvfuTnOriR7JuIlSqz8/86eilZydf0Rb0l/T9v/Fx1MDAxM5NjpvLkdDZVmCxcYjLCNFx1MDAxMfCPXG6iXHUwMDA0Ypn1SSykfn2BcnruICFcdTAwMGZVjFx1MDAxM8XBXiHOXHUwMDEw0emmXG4xXCI5KXHWqUqZVthoKz7r6vjwXHUwMDBm8b/zd1LkslxmXG5LL7VWs1uk6aRrlS1cdTAwMTJGxEhcdTAwMTUlKTKWV5HEklx1MDAxYlxcXHUwMDBlXHUwMDFikdJcdTAwMTaJUFx1MDAwM+HMkiT4XGIlKXpNQPwmmzQn412aTVx1MDAwMv3XXG5cdCqFVlx1MDAxY6xcdTAwMTORsZ2GXHUwMDA2QFx1MDAxYlx1MDAxMlxyX6R4nT2auWFcdTAwMDJcdTAwMTlYR+1bXHUwMDAy0+hcclx1MDAxNYUy+s2oQSlf+5aJYpa6RttIUee0R7lBXHUwMDBmzs1KS1x1MDAxNT1GNUenROhcdTAwMDSH1N7/gord01x1MDAxZbs+Mc9K53pcdTAwMWTskeZR1mCSrTy/9K7Q8l55x2zcXHUwMDA2PC0rh1xisKLcN1x1MDAxZpaVleNAi2JcdTAwMTWr9yi9XHUwMDE2hkFAZC+ygpJfUHeNSTRcdTAwMTVgea1ILL+5QXBFXHRjc/Q2VC6dqlu7bp1tncuAVPzuXvW8t1x1MDAwZVx1MDAwMIs02JBUkVTmXHUwMDFi84jOJp3vXCIxZiS7jWK/JoGAu+pJeFx1MDAwZiHHXHUwMDAxcOD23jtcdTAwMTRR4NhelVx1MDAwN//NO5FSizhcdTAwMThcdTAwMWMv35zecPpPwLDcXHUwMDFmjCB4kFxuZLMjtn9q1cj16cN+5absXHUwMDA2+to9On3I+2Wz1UIs5thcdTAwMDCqxVJtXHUwMDEyXGY4VmJ4kXjlWU2D41x1MDAxNXl2iVx1MDAxMih59Fsx75Y/z2RSsZU+91RcdTAwMGJVxHZcdTAwMWbRh0ukiu6daZ/Mmj5fbraRkPhcdTAwMWKXS3vzMD9/XHUwMDBll1dYi1j/0Yv9XHKZT3M9cCPycKNccrw83LCssDbdW4SxZoiLd+7im1tcdTAwMDXfRiWD7s18XHUwMDA00rKjXHUwMDE3+qZQyE670Yi7i0lcdTAwMTb5kplPXHUwMDEyy4F8U1GW/+ZgrCslldPGXGJcIlx1MDAxOTRHmWq63VlpmClcdTAwMDKRvmAyilHgrtUk1pAyXHUwMDEwwohiRSPlXHUwMDEzcimYo1x1MDAwNlx1MDAxMUBqseZcdTAwMTKuxFx1MDAxOM7CoDBcYpOIY6WE1CRO90ddRUAqgCGT980xRZik8jWYnDHHNFx1MDAxZFx1MDAxNIXnlFxyN6JcdTAwMDXUXHUwMDE0XHUwMDFjXGKVknJcdTAwMWTbI5aFXCJA/zSChaWESKXl005z5pqm12VHMkX1XHUwMDEyqqWOXHUwMDE0i1wirFx1MDAwNM9cdTAwMTRcbnOpJYRxSoHV15ilZFqrnFO+Nlx1MDAwZqbTijwn685lXHUwMDBlKr8zXHUwMDEyU00gXHUwMDEym6N3JSAtfLS7f9+8ZDdcdTAwMTdfXHUwMDFlb67qx+7ntTBpSMvMLFx1MDAxNKfYWFx1MDAxZW8gXCKjXHUwMDBlT2LSjjLhTFNcYon5+zJcdTAwMDeAlqDv15Z8XHUwMDAyulx1MDAxYVx1MDAxNLyrQlSEXHUwMDBlPv1w/1No+ma3VXS9hl3w3MJJyy58iUaiqUMvXGKbvlx1MDAxZESfq57b9Mpbg4/92tFe9GHXvLoxXHUwMDA3O/bDXHUwMDE2XHUwMDFj3PU9OFsnXHUwMDFh+TtcdTAwMTj3XHUwMDE4+z3370Jpbyebsuj3/Vx0hF/7XHUwMDA0hlx1MDAwNuTDk5HaMLvdWlxiXG4xsuhcdTAwMWJ3bft+K1+9PzyZn1xi53akW//8/PDzf6BcdTAwMWQ0mSJ9 + + + + Firehose-enabledBlockchain NodeSubstreams Service(tier1, user facing)Substreams parallelprocessors (tier2, internal)Users in the wildSubstreams infrastructureDatabase XSubstreamssink todatabase XUser streamingspkgTypes of sinks:* graph-node on The Graph* Postgres* MongoDB* MySQL* Kafka* Python program* `substreams run` CLI \ No newline at end of file diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/dex-trades-gui.png b/firehose/substreams/docs/.gitbook/assets/tutorials/dex-trades-gui.png new file mode 100644 index 0000000..c018d4d Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/dex-trades-gui.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/eth-explorer-manifest.png b/firehose/substreams/docs/.gitbook/assets/tutorials/eth-explorer-manifest.png new file mode 100644 index 0000000..95c65ab Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/eth-explorer-manifest.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/eth-explorer-structure.png b/firehose/substreams/docs/.gitbook/assets/tutorials/eth-explorer-structure.png new file mode 100644 index 0000000..49a53bc Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/eth-explorer-structure.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/nft-trades-dapps.png b/firehose/substreams/docs/.gitbook/assets/tutorials/nft-trades-dapps.png new file mode 100644 index 0000000..f24758f Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/nft-trades-dapps.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/nft-trades-gui.png b/firehose/substreams/docs/.gitbook/assets/tutorials/nft-trades-gui.png new file mode 100644 index 0000000..6797a43 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/nft-trades-gui.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/solana-filter-instructions-output.png b/firehose/substreams/docs/.gitbook/assets/tutorials/solana-filter-instructions-output.png new file mode 100644 index 0000000..781299d Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/solana-filter-instructions-output.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/solana-filter-transactions-output.png b/firehose/substreams/docs/.gitbook/assets/tutorials/solana-filter-transactions-output.png new file mode 100644 index 0000000..b6ce655 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/solana-filter-transactions-output.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/solana-learn.png b/firehose/substreams/docs/.gitbook/assets/tutorials/solana-learn.png new file mode 100644 index 0000000..01722a4 Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/solana-learn.png differ diff --git a/firehose/substreams/docs/.gitbook/assets/tutorials/topledger-website.png b/firehose/substreams/docs/.gitbook/assets/tutorials/topledger-website.png new file mode 100644 index 0000000..dce934b Binary files /dev/null and b/firehose/substreams/docs/.gitbook/assets/tutorials/topledger-website.png differ diff --git a/firehose/substreams/docs/README.md b/firehose/substreams/docs/README.md new file mode 100644 index 0000000..3db6991 --- /dev/null +++ b/firehose/substreams/docs/README.md @@ -0,0 +1,74 @@ +Substreams is a powerful indexing technology, which allows you to: +- Extract data from several blockchains (Solana, Ethereum, Polygon, BNB...). +- Apply custom transformations to the data. +- Send the data to a place of your choice (for example, a Postgres database or a file). + +
+ +**You can use Substreams packages to define which specific data you want to extract from the blockchain**. For example, consider that you want to retrieve data from the Uniswap v3 smart contract. You can simply use the [Uniswap v3 Substreams Package](https://substreams.dev/streamingfast/uniswap-v3/v0.2.7) and send that data wherever you want! + +## Consume Substreams + +There are many ready-to-use Substreams packages, so you can simply consume them. Use the **([Substreams.dev Registry](https://substreams.dev)) to explore packages**. + +Once you find a package that fits your needs, you only have choose **how you want to consume the data**. Send the data to a SQL database, configure a webhook or stream directly from your application! + +
+ +## Develop Substreams + +If you can't find a Substreams package that retrieves exactly the data you need, **you can develop your own Substreams**. + +You can write your own Rust function to extract data from the blockchain: + +{% tabs %} +{% tab title="Solana" %} +The following function extracts Solana instructions from a specific program ID. +```rust +fn map_filter_instructions(params: String, blk: Block) -> Result> { + let filters = parse_filters_from_params(params)?; + + let mut instructions : Vec = Vec::new(); + + blk.transactions.iter().for_each(|tx| { + let msg = tx.transaction.clone().unwrap().message.unwrap(); + let acct_keys = msg.account_keys.as_slice(); + let insts : Vec = msg.instructions.iter() + .filter(|inst| apply_filter(inst, &filters, acct_keys.to_vec())) + .map(|inst| { + Instruction { + program_id: bs58::encode(acct_keys[inst.program_id_index as usize].to_vec()).into_string(), + accounts: inst.accounts.iter().map(|acct| bs58::encode(acct_keys[*acct as usize].to_vec()).into_string()).collect(), + data: bs58::encode(inst.data.clone()).into_string(), + } + }).collect(); + instructions.extend(insts); + }); + + Ok(Instructions { instructions }) +} +``` +{% endtab %} + +{% tab title="EVM" %} +The following function extracts USDT transaction from EVM blockchains. +```rust +fn get_usdt_transaction(block: eth::Block) -> Result, substreams:error:Error> { + let my_transactions = block.transactions(). + .filter(|transaction| transaction.to == USDT_CONTRACT_ADDRESS) + .map(|transaction| MyTransaction(transaction.hash, transaction.from, transaction.to)) + .collect(); + Ok(my_transactions) +} +``` +{% endtab %} +{% endtabs %} + + +## How Does It Work? + +The following video covers how Substreams works in less than 2 minutes: + +{% embed url="https://www.youtube.com/watch?v=gVqGCqKVM08" %} +Get an overview of Substreams +{% endembed %} \ No newline at end of file diff --git a/firehose/substreams/docs/README_old.md b/firehose/substreams/docs/README_old.md new file mode 100644 index 0000000..563b310 --- /dev/null +++ b/firehose/substreams/docs/README_old.md @@ -0,0 +1,68 @@ +--- +description: StreamingFast Substreams documentation +--- + +# Substreams + +## Welcome to Substreams documentation + +Substreams is a powerful blockchain indexing technology, developed for [The Graph Network](https://thegraph.com). + +> Substreams enables developers to write Rust modules, composing data streams alongside the community, and provides extremely high-performance indexing by virtue of parallelization, in a streaming-first fashion. +> +> Substreams have all the benefits of StreamingFast Firehose, like low-cost caching and archiving of blockchain data, high throughput processing, and cursor-based reorgs handling. + +### Where to start + +Learn about Substreams in a short, dense 25-minute intro and understand its impact on the blockchain ecosystem. + +{% embed url="https://www.youtube.com/watch?v=K-nhC2FCB5k" %} +A walkthrough of Firehose features, Substreams modules, including a sample Rust module and StreamingFast's vision. +{% endembed %} + +Learn about the benefits of Substreams, and how it compares to otheressential facts about Substreams through [reading the Benefits and comparison](concepts-and-fundamentals/benefits.md). + +The primary ways to use Substreams include: + +* [Installing the `substreams` CLI](getting-started/installing-the-cli.md) +* [Going through the Quickstart](getting-started/quickstart.md) + +After installing Substreams and reviewing the Quickstart: + +* You can [learn more about ](developers-guide/modules/)modules, and then [study the Developer's guide](developers-guide/overview.md). + +Find pre-built Substreams by using the following resources: + +* The [Substreams Template](https://github.com/streamingfast/substreams-template) helps expedite the process of getting you up and running. +* A [list of maintained Substreams examples](reference-and-specs/examples.md) + +### Network model diagram + + + +**You can view Substreams from two perspectives** as illustrated in the high-level visual diagram. It can be viewed through the perspective of the **Substreams engine** itself and also the perspective of the **end-user developer and consumer**. + +### Community + +Substreams is an open source community effort, so feel free to suggest new topics, report issues, and provide feedback. Contribute through GitHub [pull requests](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests). + +* [StreamingFast Discord](https://discord.gg/mYPcRAzeVN) +* [The Graph Discord](https://discord.gg/vtvv7FP) +* [StreamingFast on Twitter](https://twitter.com/streamingfastio) +* [StreamingFast on YouTube](https://www.youtube.com/c/streamingfast) + +### Contributing + +For additional information, [refer to the general StreamingFast contribution guide](https://github.com/streamingfast/streamingfast/blob/master/CONTRIBUTING.md). + +### License + +Substreams uses the [Apache 2.0](../LICENSE/) license. + +### Disclaimer + +The content in the Substreams documentation was created through StreamingFast's full effort. It is up to the reader to validate the accuracy of all content presented. Substreams is in active development and, at times, the associated documentation becomes outdated. [Contact StreamingFast](https://discord.gg/mYPcRAzeVN) to report problems or service interruptions. + +{% hint style="info" %} +**Note**: The Substreams documentation uses the [Google developer documentation style guide](https://developers.google.com/style) for its style and formatting. +{% endhint %} diff --git a/firehose/substreams/docs/SUMMARY.md b/firehose/substreams/docs/SUMMARY.md new file mode 100644 index 0000000..35b7bc7 --- /dev/null +++ b/firehose/substreams/docs/SUMMARY.md @@ -0,0 +1,81 @@ +# Table of contents + +* [What is Substreams?](README.md) +* [Substreams for Solana Developers](new/common/intro-solana.md) + +## Documentation + +* [Consume Substreams](new/consume/consume.md) + * [Install the CLI](new/common/installing-the-cli.md) + * [Authentication](new/common/authentication.md) + * [Packages](new/common/packages.md) + * [Reliability Guarantees](new/common/reliability-guarantees.md) + * [Deployable Services](new/common/deployable-services.md) + * [Substreams:SQL](new/consume/sql/sql.md) + * [Deployable Services](documentation/consume/sql/deployable-services/README.md) + * [Remote Service](new/consume/sql/deployable-services/remote-service.md) + * [Local Service](new/consume/sql/deployable-services/local-service.md) + * [SQL Sink](new/consume/sql/sql-sink.md) + * [Substreams:Stream](new/consume/stream/stream.md) + * [JavaScript](new/consume/stream/javascript.md) + * [Go](new/consume/stream/go.md) + * [Substreams:Subgraph](new/consume/subgraph/subgraph.md) + * [Other Ways of Consuming](new/consume/other-sinks/README.md) + * [Files](new/consume/other-sinks/files.md) + * [Key-Value Store](new/consume/other-sinks/kv.md) + * [MongoDB](new/consume/other-sinks/mongodb.md) + * [Prometheus](new/consume/other-sinks/prometheus.md) + * [PubSub](new/consume/other-sinks/pubsub.md) +* [Develop Substreams](new/develop/develop.md) + * [Install the CLI](new/common/installing-the-cli.md) + * [Authentication](new/common/authentication.md) + * [Quickstart](new/develop/init-project.md) + * [Manifest & Modules](new/common/manifest-modules.md) + * [Module types](new/develop/modules/types.md) + * [Inputs](new/develop/modules/inputs.md) + * [Output](new/develop/modules/outputs.md) + * [Module handlers](new/develop/modules/setting-up-handlers.md) + * [Module handler creation](new/develop/modules/writing-module-handlers.md) + * [Keys in stores](new/develop/modules/keys-in-stores.md) + * [Dynamic data sources](new/develop/modules/dynamic-data-sources.md) + * [Aggregation Windows](new/develop/modules/aggregation-windows.md) + * [Protobuf Schemas](new/develop/creating-protobuf-schemas.md) + * [Packages](new/common/packages.md) + * [Run a Substreams](new/common/running-substreams.md) + * [Reliability Guarantees](new/common/reliability-guarantees.md) + * [Deployable Services](new/common/deployable-services.md) + * [Rust Dependencies](new/develop/rust-crates.md) + * [Parameterized Modules](new/develop/parameterized-modules.md) + * [Chain-Specific Extensions](documentation/develop/chain-specific-extensions/README.md) + * [EVM Chain-Specific Extensions](documentation/develop/chain-specific-extensions/evm-chain-specific-extensions/README.md) + * [Making eth\_calls](new/develop/chain-specific/evm/eth-calls.md) + * [Test Locally](documentation/develop/test-locally.md) + * [Architecture](new/develop/architecture.md) + +## Tutorials + +* [EVM](new/tutorials/evm/evm.md) + * [Exploring Ethereum](new/tutorials/evm/exploring-ethereum/exploring-ethereum.md) + * [Mapping Blocks](new/tutorials/evm/exploring-ethereum/map\_block\_meta\_module.md) + * [Filter Transactions](new/tutorials/evm/exploring-ethereum/map\_filter\_transactions\_module.md) + * [Retrieve Events of a Smart Contract](new/tutorials/evm/exploring-ethereum/map\_contract\_events\_module.md) +* [Solana](new/tutorials/solana/solana.md) + * [Explore Solana](new/tutorials/solana/explore-solana/explore-solana.md) + * [Filter Instructions](new/tutorials/solana/explore-solana/filter-instructions.md) + * [Filter Transactions](new/tutorials/solana/explore-solana/filter-transactions.md) + * [SPL Token Tracker](new/tutorials/solana/token-tracker/token-tracker.md) + * [NFT Trades](new/tutorials/solana/top-ledger/nft-trades.md) + * [DEX Trades](new/tutorials/solana/top-ledger/dex-trades.md) +* [Rust](new/tutorials/rust/rust.md) + * [Option struct](new/tutorials/rust/option.md) + * [Result struct](new/tutorials/rust/result.md) + +## Reference & Specs + +* [Chains and endpoints](new/references/chains-and-endpoints.md) +* [Substreams CLI reference](new/references/command-line-interface.md) +* [Manifests Reference](new/references/manifests.md) +* [GUI Reference](new/references/gui.md) +* [Glossary](new/references/glossary.md) +* [Change log](new/references/change-log.md) +* [FAQ](new/references/faq.md) diff --git a/firehose/substreams/docs/assets/init-flow.gif b/firehose/substreams/docs/assets/init-flow.gif new file mode 100644 index 0000000..98e07ef Binary files /dev/null and b/firehose/substreams/docs/assets/init-flow.gif differ diff --git a/firehose/substreams/docs/assets/range_planning.excalidraw b/firehose/substreams/docs/assets/range_planning.excalidraw new file mode 100644 index 0000000..252462a --- /dev/null +++ b/firehose/substreams/docs/assets/range_planning.excalidraw @@ -0,0 +1,6479 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 125, + "versionNonce": 621599656, + "isDeleted": false, + "id": "F4SC7sCB_hOw0bsR7DhTg", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 531.0022597321517, + "y": -1380.4088831706106, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 318.77984619140625, + "height": 46, + "seed": 799949334, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Development Mode", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Development Mode", + "lineHeight": 1.2777777777777777, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 738, + "versionNonce": 1338277336, + "isDeleted": false, + "id": "JTCFELIAkXgnG-AkDZM04", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 917.1921943026646, + "y": -1606.7846936341373, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 26.656143239909987, + "height": 28.715861125374484, + "seed": 2001502678, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 3508, + "versionNonce": 1529670568, + "isDeleted": false, + "id": "17U-Y4HJemK7FVFfbQ9_R", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 918.1405226686215, + "y": -1559.4261315322246, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 22.185660355525677, + "height": 23.325444465260333, + "seed": 1420325526, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 198, + "versionNonce": 595464920, + "isDeleted": false, + "id": "JzdlWI9jee3_5SIS1EdVc", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 959.855373724383, + "y": -1603.51513888784, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 242.8798065185547, + "height": 26, + "seed": 719698634, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Data produced on Tier2", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data produced on Tier2", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 352, + "versionNonce": 1766008488, + "isDeleted": false, + "id": "CBysbAFM3EuKDznTduTes", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 961.8463574773247, + "y": -1561.0373691934683, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 325.6197509765625, + "height": 26, + "seed": 665255114, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Data produced on Tier1 (linearly)", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data produced on Tier1 (linearly)", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1798, + "versionNonce": 1643177944, + "isDeleted": false, + "id": "pLXzqYXI78q1Z5hPnpY8s", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 918.0032616665336, + "y": -1440.6379335499764, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 23.660679893223346, + "height": 24.914086686389577, + "seed": 1566180874, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1723, + "versionNonce": 533995944, + "isDeleted": false, + "id": "j0I8LqhVrlx8GV4x76lS4", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 919.7125009138437, + "y": -1476.9453364014616, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 21.9896961046103, + "height": 20.726960366493206, + "seed": 699705034, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 478, + "versionNonce": 484833240, + "isDeleted": false, + "id": "cg9B-D_WguqMnMvCMjDr_", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 960.766575643211, + "y": -1480.8008867315145, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 640.9395141601562, + "height": 26, + "seed": 1387434902, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Data that must produced on Tier2 so that the linear can work", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data that must produced on Tier2 so that the linear can work", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 539, + "versionNonce": 108532136, + "isDeleted": false, + "id": "-UT-NE4vZcWcVNJixvJN5", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 961.3305251633379, + "y": -1439.1984939641807, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 446.8196105957031, + "height": 26, + "seed": 107623114, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Data that should not be produced on Tier2.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data that should not be produced on Tier2.", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 3697, + "versionNonce": 797626840, + "isDeleted": false, + "id": "2-gB9C0dNDxXodg3C8mGa", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 918.3576477933451, + "y": -1514.3992048011623, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 22.185660355525677, + "height": 9.21742130206306, + "seed": 1673469168, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 589, + "versionNonce": 1242119384, + "isDeleted": false, + "id": "RLOzKkgpq4l0Le1hyHTtF", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 962.0634826020483, + "y": -1520.2428494113653, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 296.8997802734375, + "height": 26, + "seed": 285305360, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "Data read from files on Tier1", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Data read from files on Tier1", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 844, + "versionNonce": 654497496, + "isDeleted": false, + "id": "erITLg0AXjmzXxkqZfYwD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1333.3387660793967, + "y": -1114.5556068506664, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 138.3745055802591, + "seed": 812242902, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 138.3745055802591 + ] + ] + }, + { + "type": "text", + "version": 605, + "versionNonce": 601332904, + "isDeleted": false, + "id": "F_7bJQaOCI3svAECRpHJQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1308.8372298189333, + "y": -1148.5820946540107, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 1168160906, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 674, + "versionNonce": 1551622616, + "isDeleted": false, + "id": "GKl6A6_7KhkDlBzSDRkv5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1674.1341048189333, + "y": -1151.1719384040107, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.67997741699219, + "height": 26, + "seed": 1816947990, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030113, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "738", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "738", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 706, + "versionNonce": 588105640, + "isDeleted": false, + "id": "8jwhtpCcnfgq-8ICeQIfF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1838.4288844417713, + "y": -1150.908069638217, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 37.79997253417969, + "height": 26, + "seed": 516757322, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "742", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "742", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1169, + "versionNonce": 2116878552, + "isDeleted": false, + "id": "jIVz8H6mUPLhPKC80_X06", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1599.6581345305653, + "y": -1120.085312575375, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 145.68199155199102, + "seed": 1370118742, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 145.68199155199102 + ] + ] + }, + { + "type": "text", + "version": 667, + "versionNonce": 861005528, + "isDeleted": false, + "id": "DrVgPO2P4VLDeBNvcN8CR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1581.7682574523085, + "y": -1158.82056035823, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 38.27998352050781, + "height": 26, + "seed": 947887626, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "700", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "700", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 949, + "versionNonce": 1602692568, + "isDeleted": false, + "id": "pSKBd_mRxkhDUwQElCUya", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1335.8784602424205, + "y": -1104.4382228625032, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 255.1991805206118, + "height": 21.52013744116413, + "seed": 1196990358, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1862, + "versionNonce": 474530728, + "isDeleted": false, + "id": "RN_PYSdLpvJ9av0E0bSb6", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1606.634669311734, + "y": -1104.2647449812448, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 90.79556080200939, + "height": 20.90096363704293, + "seed": 1874021578, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 840, + "versionNonce": 1012181720, + "isDeleted": false, + "id": "YWhC2aiUOFlBLuCaLCoxX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1700.590507667108, + "y": -1115.322643533781, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.2737367544323206e-13, + "height": 141.31670868326114, + "seed": 1638539850, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.2737367544323206e-13, + 141.31670868326114 + ] + ] + }, + { + "type": "line", + "version": 897, + "versionNonce": 1623330472, + "isDeleted": false, + "id": "5iNSUV8Wh1Ct8ZPeCbr1s", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1858.5484611297932, + "y": -1111.1682128704097, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 136.73351925443137, + "seed": 1912153238, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 136.73351925443137 + ] + ] + }, + { + "type": "line", + "version": 1256, + "versionNonce": 254382040, + "isDeleted": false, + "id": "Odz_wEthBEQqGrQWMVg7T", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1963.2601858237451, + "y": -1113.1737356062001, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 138.55603520008697, + "seed": 554276810, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 138.55603520008697 + ] + ] + }, + { + "type": "text", + "version": 649, + "versionNonce": 1346802344, + "isDeleted": false, + "id": "VhOCL1DD6fOSh3vH-zaXq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1946.0060348031725, + "y": -1158.7772084189219, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 42.819976806640625, + "height": 26, + "seed": 2144608726, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "800", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "800", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1108, + "versionNonce": 1612305624, + "isDeleted": false, + "id": "5Tue8kmwvjqiN5X-l7qIO", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1608.9355757623905, + "y": -1059.6376155045732, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 246.21202900620133, + "height": 21.159362900083917, + "seed": 1682863446, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1001, + "versionNonce": 649888936, + "isDeleted": false, + "id": "cnqklVAKCeot3Y-sqsOov", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1331.714609892812, + "y": -768.658699319812, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 153.73300177542194, + "seed": 981846346, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759156, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 153.73300177542194 + ] + ] + }, + { + "type": "text", + "version": 845, + "versionNonce": 723471320, + "isDeleted": false, + "id": "KOuhyWLVqyfajouOnrpwL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1307.187266142812, + "y": -802.662605569812, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 1888340054, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 914, + "versionNonce": 305003944, + "isDeleted": false, + "id": "ZtXSyVMUpxq9ER4k8zxaH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1672.4841411428115, + "y": -805.252449319812, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.67997741699219, + "height": 26, + "seed": 2119307274, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "738", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "738", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1003, + "versionNonce": 1844973784, + "isDeleted": false, + "id": "o2DgGpYfGCx_IwNV7eQkJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1950.3070283469242, + "y": -803.8447944016883, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 42.3399658203125, + "height": 26, + "seed": 1402883478, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "842", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "842", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1419, + "versionNonce": 1024555688, + "isDeleted": false, + "id": "MPn6deoIdG-3fYAoZMCct", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1598.0081708544442, + "y": -773.9880454664739, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 160.21790508264905, + "seed": 1986494154, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 160.21790508264905 + ] + ] + }, + { + "type": "text", + "version": 932, + "versionNonce": 435161256, + "isDeleted": false, + "id": "5CvfxAp7vwkAbve61nmpA", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1579.118293776187, + "y": -812.9010712740313, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 38.27998352050781, + "height": 26, + "seed": 619457238, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "700", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "700", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1209, + "versionNonce": 1968956840, + "isDeleted": false, + "id": "MDcSNxCmTSK-2C_JBvI80", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1332.960703642812, + "y": -752.4405555109764, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 255.1991805206118, + "height": 21.52013744116413, + "seed": 258020746, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1050, + "versionNonce": 184330456, + "isDeleted": false, + "id": "AmmvaUg6IwGkTnpRqh_X0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1698.9405439909865, + "y": -769.2358339557444, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 156.18203443616142, + "seed": 1861735062, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 156.18203443616142 + ] + ] + }, + { + "type": "line", + "version": 1168, + "versionNonce": 790687912, + "isDeleted": false, + "id": "GZdgPt21ezov0DO5GpHd_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1965.346369674597, + "y": -761.4523965284772, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 149.94631157689105, + "seed": 1177354378, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 149.94631157689105 + ] + ] + }, + { + "type": "line", + "version": 1474, + "versionNonce": 69324248, + "isDeleted": false, + "id": "C70X0Wy1dl0jXlBNDp19p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1846.5355629567716, + "y": -767.4355694909445, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 154.75824108249594, + "seed": 1065919766, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 154.75824108249594 + ] + ] + }, + { + "type": "text", + "version": 966, + "versionNonce": 1131427288, + "isDeleted": false, + "id": "SD_TJTnaxELCORUmnNdhM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1821.9206521117962, + "y": -802.1480069362319, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 42.819976806640625, + "height": 26, + "seed": 1452302154, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "800", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "800", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1346, + "versionNonce": 1197101784, + "isDeleted": false, + "id": "7NVxNNMEnseVpaxRGeTVN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1608.7876172956442, + "y": -705.4079592758359, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 229.35810721393037, + "height": 20.83584723629713, + "seed": 1587954506, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1364, + "versionNonce": 810215080, + "isDeleted": false, + "id": "5QWsCkdAkHkJP_5m6brKL", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1610.1252301363008, + "y": -751.078852089389, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 225.8460958253232, + "height": 19.227601099973075, + "seed": 1483002442, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1453, + "versionNonce": 2067412952, + "isDeleted": false, + "id": "aDBk4_FBPa67jjfZxEj1X", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1858.5510419215664, + "y": -705.8372396758608, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 96.76334597443964, + "height": 19.684878047825354, + "seed": 1375490890, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 995, + "versionNonce": 377999784, + "isDeleted": false, + "id": "R3y9U0JiSyKG9lOGDePJu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1330.5187386312018, + "y": -385.44814920592466, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 147.95565563546097, + "seed": 571270678, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 147.95565563546097 + ] + ] + }, + { + "type": "text", + "version": 820, + "versionNonce": 322483112, + "isDeleted": false, + "id": "_-crHAHTDCDHVLQEYCrGs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1305.9913948812018, + "y": -419.27427743122234, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 2116857418, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1427, + "versionNonce": 1656758744, + "isDeleted": false, + "id": "Kv9KbmvCE7tzWKpmYLl9D", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1761.867557093649, + "y": -383.54865523322627, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 146.35420129241055, + "seed": 1611585686, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 146.35420129241055 + ] + ] + }, + { + "type": "line", + "version": 1210, + "versionNonce": 1110280104, + "isDeleted": false, + "id": "vJ5xXaL2n1HtaG_QLiwZ4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1890.0795318472215, + "y": -380.6588574053943, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 142.85263790898523, + "seed": 256147466, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 142.85263790898523 + ] + ] + }, + { + "type": "line", + "version": 1363, + "versionNonce": 1728980696, + "isDeleted": false, + "id": "lJr7uTnM-TJ3N1bZUq3fM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 2006.2447095114137, + "y": -382.2180847468956, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 145.556964880187, + "seed": 1652996822, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 145.556964880187 + ] + ] + }, + { + "type": "rectangle", + "version": 1312, + "versionNonce": 1012776360, + "isDeleted": false, + "id": "0CN7UtLunckSRmkkldDBd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1643.9319035187716, + "y": -358.99925576703504, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 1311460310, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1365, + "versionNonce": 262426840, + "isDeleted": false, + "id": "yic7AdNFjmfqradve95rU", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1526.9175726562983, + "y": -360.0989271755375, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 1331463498, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1480, + "versionNonce": 1747437736, + "isDeleted": false, + "id": "3D_9pevuqUwzqXa_q6Zr-", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1339.1215750775445, + "y": -361.14826151252385, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 33423894, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 618, + "versionNonce": 227404200, + "isDeleted": false, + "id": "V3T0SaSRwQevA_1aE3_YV", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1470.0182435221454, + "y": -366.3230924138706, + "strokeColor": "#2b8a3e", + "backgroundColor": "#fab005", + "width": 38.35997009277344, + "height": 26, + "seed": 58481878, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": ".......", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": ".......", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1571, + "versionNonce": 1148744616, + "isDeleted": false, + "id": "CftbNRWVsyd1nWAnyXSjy", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1339.1525517369391, + "y": -315.55301017538466, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 627929238, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 702, + "versionNonce": 1239213272, + "isDeleted": false, + "id": "_SP6dn9s1O1ELw_2sSl5j", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1464.6053807490289, + "y": -322.0633470015454, + "strokeColor": "#2b8a3e", + "backgroundColor": "#fab005", + "width": 38.35997009277344, + "height": 26, + "seed": 349261398, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": ".......", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": ".......", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1444, + "versionNonce": 1199292072, + "isDeleted": false, + "id": "lsaJMt3hXrRX_Ba083p8c", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1524.203242876861, + "y": -316.05250880812, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 73072406, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1383, + "versionNonce": 1310908376, + "isDeleted": false, + "id": "3azMia_YzfnWBXzzejatp", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1647.5523005854957, + "y": -317.15218021662224, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 1564466250, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2416, + "versionNonce": 1105045928, + "isDeleted": false, + "id": "B_c07P4VulsBpl4l7lVyI", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1772.540238730612, + "y": -363.8229412807073, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 105.63694545094289, + "height": 23.192591175751463, + "seed": 189921622, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4332, + "versionNonce": 325657816, + "isDeleted": false, + "id": "KJgDwLGJclRKt01aZT4da", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1899.146828038005, + "y": -316.95116023758186, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 94.98293652137356, + "height": 18.864101312897287, + "seed": 399137046, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4327, + "versionNonce": 248132776, + "isDeleted": false, + "id": "Pz3kBxXidhWeLCLWuneES", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1898.002966422932, + "y": -362.8639223335136, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 96.99601121285589, + "height": 19.22488612773436, + "seed": 273297866, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1615, + "versionNonce": 1100925400, + "isDeleted": false, + "id": "z9Ozq2_NlpSjqZ_2O_Oqd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1772.6940630587078, + "y": -316.4273727190069, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.54656271150283, + "height": 20.253320037977463, + "seed": 1436867350, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1984, + "versionNonce": 1139629992, + "isDeleted": false, + "id": "bmesEjSOX7Fi89zZA4Ozi", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1852.8891980585965, + "y": -755.6377347905182, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 103.32858513977432, + "height": 20.8003492956035, + "seed": 500960714, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 1105, + "versionNonce": 1138216664, + "isDeleted": false, + "id": "TsekSY978c3n8vH5-zP8g", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1331.9618022392613, + "y": -22.161371913550397, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 141.3804849083291, + "seed": 328178646, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 141.3804849083291 + ] + ] + }, + { + "type": "text", + "version": 932, + "versionNonce": 2054321320, + "isDeleted": false, + "id": "B0jjbUcgygaOycCqfDKuu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1307.4344584892613, + "y": -56.1652781635504, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 1672033418, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030114, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1189, + "versionNonce": 1725160104, + "isDeleted": false, + "id": "gJFdc3nlrKDRylgzXc7X3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1742.5141554743686, + "y": -57.692682082238434, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.699981689453125, + "height": 26, + "seed": 1847177494, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931103832, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "900", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "900", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1549, + "versionNonce": 34051496, + "isDeleted": false, + "id": "Dr9pDTWZHvQX4DS8OfMAZ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1763.1537577387353, + "y": -20.46868939128308, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 140.50348979351952, + "seed": 1583747914, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 140.50348979351952 + ] + ] + }, + { + "type": "line", + "version": 1334, + "versionNonce": 874772696, + "isDeleted": false, + "id": "RNpYwJVoiuskOZ5Jp3QMQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1909.1857403301374, + "y": -16.367744158357027, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 135.7907790050001, + "seed": 574441674, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 135.7907790050001 + ] + ] + }, + { + "type": "text", + "version": 1300, + "versionNonce": 1999234728, + "isDeleted": false, + "id": "fCPply_nxR3vws3AQGNlm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1887.9240085712327, + "y": -56.082445292070815, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.219970703125, + "height": 26, + "seed": 1055202890, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931098515, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "942", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "942", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1436, + "versionNonce": 736042456, + "isDeleted": false, + "id": "YgFEHJh9RGBlR5TIkxNot", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1645.3749671268308, + "y": -4.89025649936309, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 1174324490, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1489, + "versionNonce": 1535921064, + "isDeleted": false, + "id": "px-VKH9OS3mnn6RIh9plJ", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1528.360636264358, + "y": -5.989927907865535, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 839084182, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1604, + "versionNonce": 720216792, + "isDeleted": false, + "id": "kE-KmCfP0GBpzojdFE7pW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1340.564638685604, + "y": -7.039262244851898, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 1890948042, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 742, + "versionNonce": 1079742168, + "isDeleted": false, + "id": "cWtoGinjzRPmk84XapTcx", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1471.461307130205, + "y": -12.214093146198707, + "strokeColor": "#2b8a3e", + "backgroundColor": "#fab005", + "width": 38.35997009277344, + "height": 26, + "seed": 1307960790, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": ".......", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": ".......", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1695, + "versionNonce": 803440600, + "isDeleted": false, + "id": "GoewQ0aPkG7VIidxBrYJ2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1340.5956153449986, + "y": 38.55598909228729, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 556042890, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 826, + "versionNonce": 806054568, + "isDeleted": false, + "id": "WLzSPsQkf91yaqbgaknqP", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1466.0484443570883, + "y": 32.04565226612647, + "strokeColor": "#2b8a3e", + "backgroundColor": "#fab005", + "width": 38.35997009277344, + "height": 26, + "seed": 1495710486, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": ".......", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": ".......", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1568, + "versionNonce": 1188351192, + "isDeleted": false, + "id": "pTiORwoY6SHeVdGSOqnWq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1525.6463064849206, + "y": 38.056490459551924, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 1040623946, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1507, + "versionNonce": 1464667304, + "isDeleted": false, + "id": "RUdFk48Icpx5zxthewciP", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1648.9953641935551, + "y": 36.956819051049706, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 107.89354885280693, + "height": 20.188141087203462, + "seed": 168486998, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2477, + "versionNonce": 1498771928, + "isDeleted": false, + "id": "TauSKn3ME_bUBAJjHk8__", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1775.9963770301538, + "y": -8.673417691983559, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 121.91409224207166, + "height": 22.418733891753078, + "seed": 515305482, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4291, + "versionNonce": 643119016, + "isDeleted": false, + "id": "4iaGr2INQW4wuCvfnLoFW", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1922.0931488281406, + "y": 36.95029752360222, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 243.2569780257546, + "height": 18.367649656015896, + "seed": 7826838, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4405, + "versionNonce": 2121889496, + "isDeleted": false, + "id": "-Trxlp8umqfDa-PbfabVk", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1921.7048993259514, + "y": -7.3422989186634595, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 239.95726192982917, + "height": 14.710951624694133, + "seed": 1999658698, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1716, + "versionNonce": 1495270056, + "isDeleted": false, + "id": "u3VkSlL1J7KAnCyNgTJL_", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1776.6220960495748, + "y": 39.23979864752664, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 120.77533925553014, + "height": 19.09776287741227, + "seed": 430000854, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 465, + "versionNonce": 951317464, + "isDeleted": false, + "id": "2-UrLNme3UhbWC_V_hW7m", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1510.4538203966886, + "y": -1387.3792707899934, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 288.89990234375, + "height": 46, + "seed": 1218927062, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 36, + "fontFamily": 1, + "text": "Production Mode", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Production Mode", + "lineHeight": 1.2777777777777777, + "baseline": 32 + }, + { + "type": "rectangle", + "version": 4269, + "versionNonce": 509177256, + "isDeleted": false, + "id": "SSHveYHFmeRPYS1yttSdN", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1707.696410583589, + "y": -1012.3663700614154, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 142.10385724270193, + "height": 13.054370779026547, + "seed": 152313360, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4434, + "versionNonce": 544973016, + "isDeleted": false, + "id": "mj1rCvO6m0iFZB6h-Pv4J", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1703.936380501173, + "y": -653.3438136271669, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 250.73563559932072, + "height": 11.26806170200649, + "seed": 103549168, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4599, + "versionNonce": 1865250984, + "isDeleted": false, + "id": "CRV42756Wz3VHayTGQkfr", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1340.8878083404613, + "y": -267.3406974620326, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 537.6276937654554, + "height": 10.236689109412168, + "seed": 2043187440, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 4640, + "versionNonce": 2130822616, + "isDeleted": false, + "id": "iVGHMEpIBCS6V4tFvWL-c", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1343.8871909978025, + "y": 86.53827698525771, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 552.0368432715918, + "height": 8.80661898574354, + "seed": 2127237136, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2071, + "versionNonce": 1402459560, + "isDeleted": false, + "id": "3eX9SXvIai31MhEHnrEYs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1554.905814084999, + "y": -1196.6558964682847, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 148.19989013671875, + "height": 31, + "seed": 575624404, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "start_block ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "start_block ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 1972, + "versionNonce": 850901208, + "isDeleted": false, + "id": "3VUUVbSfbK7H9s5vfiaf1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1850.9113341160994, + "y": -1190.9205798006153, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 133.1998748779297, + "height": 57, + "seed": 1162273260, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ stop_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ stop_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 816, + "versionNonce": 1079959720, + "isDeleted": false, + "id": "J2pRbkI0zV_YL_BM2VsJG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1142.6221351843578, + "y": -1104.0276667096311, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 1209427668, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1284, + "versionNonce": 570088920, + "isDeleted": false, + "id": "WVh6s0ziXC4lKQYsFjAIS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1168.4175798765987, + "y": -1058.7974899145515, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 746878956, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 871, + "versionNonce": 813783464, + "isDeleted": false, + "id": "AetzWS16ZR0n7bNYgFbYI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1235.6169230863331, + "y": 117.48316287121884, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 792.4560026415402, + "height": 0, + "seed": 332583252, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 792.4560026415402, + 0 + ] + ] + }, + { + "type": "text", + "version": 943, + "versionNonce": 906791848, + "isDeleted": false, + "id": "JZ6ORuVYdvEdB1GwogQk9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1140.189117282618, + "y": -750.8984423679124, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 336124628, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1411, + "versionNonce": 1737581272, + "isDeleted": false, + "id": "fIOHDLop70bkE0URDMTYq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1165.9845619748585, + "y": -705.668265572833, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 832957548, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 2163, + "versionNonce": 516740776, + "isDeleted": false, + "id": "4spkJrayG7XF3RV0MyiQc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1552.8299942083277, + "y": -851.2331944716362, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 148.19989013671875, + "height": 31, + "seed": 2134026860, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "start_block ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "start_block ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2085, + "versionNonce": 571426776, + "isDeleted": false, + "id": "UBJD6Nl3Lh6dQjfncFVNq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1964.7990739996776, + "y": -840.6246684209499, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 133.1998748779297, + "height": 57, + "seed": 2025431660, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ stop_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ stop_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 1005, + "versionNonce": 1061929384, + "isDeleted": false, + "id": "A8ulNHV_eXTENxmC3_fEz", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1148.2048146905208, + "y": -361.3188162944665, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 942789868, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1473, + "versionNonce": 2106100952, + "isDeleted": false, + "id": "GRHfTb57gl5j8J1oPLLCE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1174.0002593827612, + "y": -316.0886394993871, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 2083733460, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030115, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 2052, + "versionNonce": 1723157672, + "isDeleted": false, + "id": "-Nu887tLtAhXWHYNU2ewk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1323.4334772933948, + "y": -459.02936716281795, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 145.4998779296875, + "height": 57, + "seed": 916922604, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ start_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ start_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 2395, + "versionNonce": 59847128, + "isDeleted": false, + "id": "bvczwaiobWkmVImJMOLGp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1656.913561051384, + "y": -463.33723086743544, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 235.31983947753906, + "height": 31, + "seed": 732261076, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "linear_handoff (LIB) ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "linear_handoff (LIB) ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2222, + "versionNonce": 515209128, + "isDeleted": false, + "id": "xPtw4JZNaikxZ-DesMIhC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1999.1834354832818, + "y": -460.1064928418957, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 133.1998748779297, + "height": 57, + "seed": 1777059820, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ stop_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ stop_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 1063, + "versionNonce": 1799173848, + "isDeleted": false, + "id": "PnmGG0VsSrzNsdyUAaEgx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1150.7878248141353, + "y": -8.73793442084127, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 1346955244, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1531, + "versionNonce": 1534608040, + "isDeleted": false, + "id": "4UekDH7QfO76RIj_uGXjJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1176.5832695063757, + "y": 36.49224237423812, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 1661656276, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 2141, + "versionNonce": 1763744728, + "isDeleted": false, + "id": "Dl7I0UfZQUUrAaxMhWH2z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1321.623679886276, + "y": -94.9347438069974, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 145.4998779296875, + "height": 57, + "seed": 194323412, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ start_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ start_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 2307, + "versionNonce": 732540328, + "isDeleted": false, + "id": "5F3qaQRsCmpKyPDH2B4a3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 2029.2226928925434, + "y": -88.81708825106398, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 180.45980834960938, + "height": 51, + "seed": 1249084012, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "stop_block=infinity\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "stop_block=infinity\n", + "lineHeight": 1.275, + "baseline": 43 + }, + { + "type": "text", + "version": 2427, + "versionNonce": 2135908568, + "isDeleted": false, + "id": "Ry6MySflvhLS7W6sno8dS", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1679.8037458656968, + "y": -102.73019405504101, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 235.31983947753906, + "height": 31, + "seed": 1417498452, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "linear_handoff (LIB) ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "linear_handoff (LIB) ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2331, + "versionNonce": 1752545448, + "isDeleted": false, + "id": "ux_xobXZIXbP-Y1w5s00e", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1798.8184731798278, + "y": -869.3979255838744, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 172.83987426757812, + "height": 31, + "seed": 491255508, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "linear_handoff ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "linear_handoff ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2404, + "versionNonce": 1297974744, + "isDeleted": false, + "id": "IuOd1VCNEvK4pKMdFQU4R", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1686.9960956420566, + "y": -1231.981435729728, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 172.83987426757812, + "height": 31, + "seed": 1538704468, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "linear_handoff ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "linear_handoff ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "line", + "version": 754, + "versionNonce": 691281112, + "isDeleted": false, + "id": "xhPyLBIZPlNYboHg9cbkU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 379.0932352868089, + "y": -1105.9289367211527, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 84.27325692916997, + "seed": 308465046, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 84.27325692916997 + ] + ] + }, + { + "type": "text", + "version": 411, + "versionNonce": 1703437224, + "isDeleted": false, + "id": "hQxL0rEsdYR-INiJe3rJ8", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 354.5658915368089, + "y": -1139.5772869217478, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 507883094, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 480, + "versionNonce": 1616841432, + "isDeleted": false, + "id": "XqWtD8O7kdiROivz11Bw6", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 719.862766536809, + "y": -1142.1671306717478, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.67997741699219, + "height": 26, + "seed": 1518864202, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "738", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "738", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 513, + "versionNonce": 554544808, + "isDeleted": false, + "id": "seJMYJBr_v51yktKdcjAH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 884.1575461596467, + "y": -1142.9032619059542, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 37.79997253417969, + "height": 26, + "seed": 1839073494, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "742", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "742", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1168, + "versionNonce": 759482072, + "isDeleted": false, + "id": "Jw3kN0tQVhwpZ7mL8VrIf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 645.386796248441, + "y": -1111.2582828678146, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 1.1368683772161603e-13, + "height": 88.16992134734824, + "seed": 1565675094, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759157, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.1368683772161603e-13, + 88.16992134734824 + ] + ] + }, + { + "type": "text", + "version": 499, + "versionNonce": 48754648, + "isDeleted": false, + "id": "3ngkOZroVJ1W-r7ydbGWc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 626.4969191701841, + "y": -1148.8157526259668, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 38.27998352050781, + "height": 26, + "seed": 1272519638, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "700", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "700", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 747, + "versionNonce": 599141336, + "isDeleted": false, + "id": "qE09RjZsDAjjdrEmKTrae", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 382.73611918843466, + "y": -1093.9372838053916, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 255.1991805206118, + "height": 21.52013744116413, + "seed": 654447446, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1657, + "versionNonce": 989142440, + "isDeleted": false, + "id": "7VhEAzjNBwsnqXQmxHBsF", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 650.3406940602079, + "y": -1093.7376620969712, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 90.79556080200939, + "height": 20.90096363704293, + "seed": 160539670, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 744, + "versionNonce": 1941019048, + "isDeleted": false, + "id": "Kdmiyv9GOTOs-NTLk5Rdu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 198.28117323648416, + "y": -1095.2120203115837, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 1591214422, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030116, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 815, + "versionNonce": 1557342376, + "isDeleted": false, + "id": "4Bh_I5YX9SnRYOSEwH53R", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 746.3191693849838, + "y": -1106.5060713570851, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.2737367544323206e-13, + "height": 84.72490119472423, + "seed": 2007354954, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.2737367544323206e-13, + 84.72490119472423 + ] + ] + }, + { + "type": "text", + "version": 1265, + "versionNonce": 218903768, + "isDeleted": false, + "id": "FxZQxApKLHYOfBPlBZ8iJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 221.1564601049231, + "y": -1049.5602147088543, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 896563466, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 870, + "versionNonce": 1287115688, + "isDeleted": false, + "id": "-RFbIbl9-dAeFHXkBIG3h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 904.2771228476688, + "y": -1102.5189611875514, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.2737367544323206e-13, + "height": 76.42405954344258, + "seed": 451814474, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -2.2737367544323206e-13, + 76.42405954344258 + ] + ] + }, + { + "type": "line", + "version": 1361, + "versionNonce": 1511955160, + "isDeleted": false, + "id": "WUf6qcxf3z1bfzmA39nWw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1008.9888475416208, + "y": -1104.524483923342, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 1.1368683772161603e-13, + "height": 82.68056857579188, + "seed": 1481918026, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.1368683772161603e-13, + 82.68056857579188 + ] + ] + }, + { + "type": "text", + "version": 459, + "versionNonce": 1084236968, + "isDeleted": false, + "id": "GCCk03O1EIqPAW0qeZVc1", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 991.9072457803181, + "y": -1146.7724006866586, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 42.819976806640625, + "height": 26, + "seed": 1414546954, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "800", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "800", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 3493, + "versionNonce": 1043693528, + "isDeleted": false, + "id": "kx3tW7tD4cwpdqrmyP-WR", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 753.4784763354312, + "y": -1095.1784707523823, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 145.37824582219972, + "height": 20.83289670447704, + "seed": 263787606, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 3558, + "versionNonce": 163908008, + "isDeleted": false, + "id": "59yxtNaZEttlUNhiSduw6", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 752.5099996150718, + "y": -1049.4190900374738, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 145.37824582219972, + "height": 20.83289670447704, + "seed": 2087376010, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 781, + "versionNonce": 673694936, + "isDeleted": false, + "id": "2qLm_ERfjeE02N4LH9Ed0", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 379.41318457595463, + "y": -776.2131989299204, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 105.52794125838477, + "seed": 1089809430, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 105.52794125838477 + ] + ] + }, + { + "type": "text", + "version": 640, + "versionNonce": 878696920, + "isDeleted": false, + "id": "ZgpiB06VDPQJp9pe8iSHf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 354.88584082595463, + "y": -810.0602422169477, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 1431554122, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 709, + "versionNonce": 629493672, + "isDeleted": false, + "id": "hjBB1-u9BclIBvdxLoBZY", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 720.1827158259543, + "y": -812.6500859669477, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.67997741699219, + "height": 26, + "seed": 820197718, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "738", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "738", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 798, + "versionNonce": 1542905560, + "isDeleted": false, + "id": "Fu2pRzeetckYWUT6JBPnP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 998.005603030067, + "y": -811.2424310488241, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 42.3399658203125, + "height": 26, + "seed": 755511050, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "842", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "842", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1210, + "versionNonce": 1903062744, + "isDeleted": false, + "id": "A58X1pHSzOkQ_ZL3ElxGU", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 645.7067455375866, + "y": -781.3856821136096, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 111.07689555320837, + "seed": 706584214, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 111.07689555320837 + ] + ] + }, + { + "type": "text", + "version": 727, + "versionNonce": 713890472, + "isDeleted": false, + "id": "un-YTO2pmA-9TM_UfN5rn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 626.8168684593297, + "y": -820.2987079211671, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 38.27998352050781, + "height": 26, + "seed": 1191296458, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "700", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "700", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 968, + "versionNonce": 74109912, + "isDeleted": false, + "id": "5LC_R58J6zx91mbNd1DGV", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 380.65927832595463, + "y": -758.8381921581122, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 255.1991805206118, + "height": 21.52013744116413, + "seed": 1901681622, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1879, + "versionNonce": 1799288232, + "isDeleted": false, + "id": "qr6DbajJPk_gq0qvF4Woe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 651.415487395268, + "y": -758.6647142768538, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 90.79556080200939, + "height": 20.90096363704293, + "seed": 1885041802, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 888, + "versionNonce": 613587160, + "isDeleted": false, + "id": "QQws3UQqxlKWRrJiI1_fa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 746.6391186741293, + "y": -776.6334706028802, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 107.0828550301801, + "seed": 1220237834, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 107.0828550301801 + ] + ] + }, + { + "type": "line", + "version": 1013, + "versionNonce": 371327144, + "isDeleted": false, + "id": "sowWKKFvBIYZOblFc7R2_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1013.04494435774, + "y": -768.850033175613, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 99.15301217080514, + "seed": 1822015702, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 99.15301217080514 + ] + ] + }, + { + "type": "line", + "version": 1330, + "versionNonce": 1434041816, + "isDeleted": false, + "id": "-ozwU9dfhWRQmaDad-rlN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 906.7189464882965, + "y": -773.8097767786013, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 104.38465157627934, + "seed": 327147402, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 104.38465157627934 + ] + ] + }, + { + "type": "text", + "version": 742, + "versionNonce": 1684358104, + "isDeleted": false, + "id": "GiRQ0eq6P6F2ks7YEunIx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 889.1981459091053, + "y": -811.184996995056, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 42.819976806640625, + "height": 26, + "seed": 1284978198, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "800", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "800", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 3832, + "versionNonce": 137642712, + "isDeleted": false, + "id": "W2qR426gMDpDFfXSsh1RN", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 759.5883530961089, + "y": -758.3685878516177, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 244.23859031393056, + "height": 18.750619350235134, + "seed": 1412390474, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 3931, + "versionNonce": 1984427688, + "isDeleted": false, + "id": "vNKhCaU_0wvRTz_WW77D8", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 759.6769513215766, + "y": -715.7058946526922, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 244.91255145564062, + "height": 21.233710655293574, + "seed": 1337197398, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928759158, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1763, + "versionNonce": 1919124904, + "isDeleted": false, + "id": "yobX60hutRoNiteyV3W25", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 897.0080259953943, + "y": -1179.914509254043, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 133.1998748779297, + "height": 57, + "seed": 1516604944, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ stop_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ stop_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 2011, + "versionNonce": 2076117208, + "isDeleted": false, + "id": "nUBtcxAe8qeqwzgU4D3Nr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 602.4946699513255, + "y": -1183.347365972956, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 148.19989013671875, + "height": 31, + "seed": 384041964, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "start_block ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "start_block ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 1367, + "versionNonce": 1781829800, + "isDeleted": false, + "id": "XTgHGdrK8NVr02W_Qsg7J", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 207.19543742570448, + "y": -711.4339356140663, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 870465620, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 2202, + "versionNonce": 1838689752, + "isDeleted": false, + "id": "AFpDBszHVcRwuZrgeFXQ5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 596.7136335319831, + "y": -854.8619632375456, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 148.19989013671875, + "height": 31, + "seed": 1609625044, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "start_block ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "start_block ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2411, + "versionNonce": 2041330600, + "isDeleted": false, + "id": "IPY8vZ09tBTFXcVrnErIM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 736.1006677902797, + "y": -885.9286758321806, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 170.13986206054688, + "height": 31, + "seed": 1879239404, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ linear_handoff", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ linear_handoff", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2491, + "versionNonce": 1929687768, + "isDeleted": false, + "id": "e3nvbUe9gOZnhrYH4JW94", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 742.4327027289423, + "y": -1215.6232514081007, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 170.13986206054688, + "height": 31, + "seed": 677135980, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ linear_handoff", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ linear_handoff", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2220, + "versionNonce": 855017128, + "isDeleted": false, + "id": "7fFmyK5C1NdckFYg8klCw", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1011.0931741876796, + "y": -849.6076929896589, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 133.1998748779297, + "height": 57, + "seed": 374595668, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ stop_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ stop_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 761, + "versionNonce": 43554776, + "isDeleted": false, + "id": "hz7HCnzA1UeiPvOvonG5I", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 201.48485944628828, + "y": -753.8967888682946, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 354861886, + "groupIds": [ + "LKtL87uIWZ2gLC4jXcyyj" + ], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030117, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1191, + "versionNonce": 1671629736, + "isDeleted": false, + "id": "pd029vbJN0qz_tkjBcZc2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1343.9502703360897, + "y": 280.8006181386722, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 153.73300177542194, + "seed": 1459888296, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 153.73300177542194 + ] + ] + }, + { + "type": "text", + "version": 1035, + "versionNonce": 46100904, + "isDeleted": false, + "id": "1t-opL0FpXP53MK2NwqoI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1319.4229265860897, + "y": 246.79671188867218, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 32.45997619628906, + "height": 26, + "seed": 1175047080, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "621", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "621", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1104, + "versionNonce": 543677656, + "isDeleted": false, + "id": "tYbqnle8ji7ZLUg_0P1SR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1684.7198015860893, + "y": 244.20686813867218, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.67997741699219, + "height": 26, + "seed": 1025644200, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "738", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "738", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1193, + "versionNonce": 1613954216, + "isDeleted": false, + "id": "MSn6wyF8DU6XnB2oSQVCv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1962.542688790202, + "y": 245.6145230567958, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 42.3399658203125, + "height": 26, + "seed": 1626931624, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "842", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "842", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1609, + "versionNonce": 1497514408, + "isDeleted": false, + "id": "KL2vasMisWCPamEXokiAh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1610.2438312977217, + "y": 275.47127199201026, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 160.21790508264905, + "seed": 1622838440, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 160.21790508264905 + ] + ] + }, + { + "type": "text", + "version": 1122, + "versionNonce": 772178392, + "isDeleted": false, + "id": "qf4dga2RFbGjR-c-AblUn", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1591.3539542194646, + "y": 236.5582461844528, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 38.27998352050781, + "height": 26, + "seed": 638760872, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "700", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "700", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "line", + "version": 1240, + "versionNonce": 940749272, + "isDeleted": false, + "id": "dEtmLybngADlgiRoNPpfV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1711.1762044342643, + "y": 280.2234835027397, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 156.18203443616142, + "seed": 1823927720, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 156.18203443616142 + ] + ] + }, + { + "type": "line", + "version": 1358, + "versionNonce": 218888104, + "isDeleted": false, + "id": "W2nDCahIFirALZqKnsjZI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "x": 1977.5820301178746, + "y": 288.00692093000697, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0, + "height": 149.94631157689105, + "seed": 366672040, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 149.94631157689105 + ] + ] + }, + { + "type": "line", + "version": 1664, + "versionNonce": 328120024, + "isDeleted": false, + "id": "WkwwLzj4d0vmz2EodVAhH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1858.7712234000494, + "y": 282.0237479675396, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 0, + "height": 154.75824108249594, + "seed": 876979112, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 154.75824108249594 + ] + ] + }, + { + "type": "text", + "version": 1156, + "versionNonce": 2096965544, + "isDeleted": false, + "id": "2MpBECojjVS3Q0pm9LFzK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1834.156312555074, + "y": 247.3113105222522, + "strokeColor": "#a61e4d", + "backgroundColor": "transparent", + "width": 42.819976806640625, + "height": 26, + "seed": 639823528, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "800", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "800", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "rectangle", + "version": 1536, + "versionNonce": 188881880, + "isDeleted": false, + "id": "BX0fmx2FqDqkS_UpJjUNq", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1621.0232777389217, + "y": 344.0513581826483, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 229.35810721393037, + "height": 20.83584723629713, + "seed": 606001576, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1554, + "versionNonce": 1449143720, + "isDeleted": false, + "id": "vG8PGXHHahchzMmftmLDM", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1622.3608905795788, + "y": 298.38046536909513, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 225.8460958253232, + "height": 19.227601099973075, + "seed": 1902470312, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 1643, + "versionNonce": 1631641816, + "isDeleted": false, + "id": "r5PMRuFTYlC20UTMeiWX2", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1870.786702364844, + "y": 343.62207778262336, + "strokeColor": "#2b8a3e", + "backgroundColor": "#12b886", + "width": 96.76334597443964, + "height": 19.684878047825354, + "seed": 195202984, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 2174, + "versionNonce": 2019309736, + "isDeleted": false, + "id": "SBF2uAJeUJU3YjbpIWcIq", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1865.1248585018745, + "y": 293.82158266796597, + "strokeColor": "#e67700", + "backgroundColor": "#fab005", + "width": 103.32858513977432, + "height": 20.8003492956035, + "seed": 1526880936, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1686928768076, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 5012, + "versionNonce": 2002556632, + "isDeleted": false, + "id": "QECmAwaLjvNFkwNsNCTOp", + "fillStyle": "cross-hatch", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1622.1624814091, + "y": 386.4492319626507, + "strokeColor": "#364fc7", + "backgroundColor": "#4c6ef5", + "width": 344.5144480143558, + "height": 11.130322563080815, + "seed": 1647363496, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928957867, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1133, + "versionNonce": 982025944, + "isDeleted": false, + "id": "RjrZbnBWNQh8THYjswbP2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1152.4247777258959, + "y": 298.56087509057176, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160.7398681640625, + "height": 26, + "seed": 1851379880, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " store [pools]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " store [pools]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1601, + "versionNonce": 1967780520, + "isDeleted": false, + "id": "R88lx55EyOVFNSSmV811q", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1178.2202224181362, + "y": 343.7910518856511, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 137.67991638183594, + "height": 26, + "seed": 1193900968, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": " map [trxs]", + "textAlign": "left", + "verticalAlign": "middle", + "containerId": null, + "originalText": " map [trxs]", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 2276, + "versionNonce": 1384246232, + "isDeleted": false, + "id": "KfutglCaserGSiudfCRe2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1977.2734383605232, + "y": 208.83464903753423, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 133.1998748779297, + "height": 57, + "seed": 32289192, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ stop_block\n", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ stop_block\n", + "lineHeight": 1.425, + "baseline": 48 + }, + { + "type": "text", + "version": 2521, + "versionNonce": 2143641000, + "isDeleted": false, + "id": "YNpzfD_rhAKbxrvvpJU3D", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1811.0541336231056, + "y": 180.06139187460968, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 172.83987426757812, + "height": 31, + "seed": 129514664, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931030118, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "linear_handoff ⤵", + "textAlign": "right", + "verticalAlign": "top", + "containerId": null, + "originalText": "linear_handoff ⤵", + "lineHeight": 1.55, + "baseline": 21 + }, + { + "type": "text", + "version": 2571, + "versionNonce": 1734599336, + "isDeleted": false, + "id": "n-N5nvQlaWo2pum_BhS_n", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1606.3597193082057, + "y": 193.1655487527902, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 118.39990234375, + "height": 28.5, + "seed": 999529128, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928840438, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ graph init", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ graph init", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "type": "text", + "version": 2516, + "versionNonce": 2107401128, + "isDeleted": false, + "id": "oHXtZMKN9GrOLxx9YtUdf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1323.5161471239737, + "y": -137.1694698504606, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 118.39990234375, + "height": 28.5, + "seed": 2071512232, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928788934, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ graph init", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ graph init", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "type": "text", + "version": 2539, + "versionNonce": 393689048, + "isDeleted": false, + "id": "8_O5YY4Bl7zj4eBjBNTWQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1321.8293061064967, + "y": -502.4739884985746, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 118.39990234375, + "height": 28.5, + "seed": 379694760, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928792093, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ graph init", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ graph init", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "type": "text", + "version": 2508, + "versionNonce": 467737048, + "isDeleted": false, + "id": "QP9CN1QFXr6feRWAdlGwI", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1324.860845859604, + "y": -846.2394569848435, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 118.39990234375, + "height": 28.5, + "seed": 1618115800, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928794293, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ graph init", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ graph init", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "type": "text", + "version": 2501, + "versionNonce": 980804008, + "isDeleted": false, + "id": "X7qxJif6bEE43GduZUJl3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 375.27279138416657, + "y": -855.8035272820498, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 118.39990234375, + "height": 28.5, + "seed": 1095769816, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928797008, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ graph init", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ graph init", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "type": "text", + "version": 2583, + "versionNonce": 284714968, + "isDeleted": false, + "id": "d9t1e4FDvUspkzf8zQusi", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 374.93860589957217, + "y": -1185.0796679719697, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 118.39990234375, + "height": 28.5, + "seed": 932932776, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928800860, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ graph init", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ graph init", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "type": "text", + "version": 2571, + "versionNonce": 1877170904, + "isDeleted": false, + "id": "Uc_g57YVwZ07PVbC5lPjH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1336.3106771055948, + "y": 199.4110107170252, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 145.4998779296875, + "height": 28.5, + "seed": 154818264, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686928852072, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "⤹ start_block", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "⤹ start_block", + "lineHeight": 1.425, + "baseline": 19 + }, + { + "id": "hKauQWNHUGqgu-Rz87oSS", + "type": "ellipse", + "x": 225.30445638885772, + "y": -1223.6866721186777, + "width": 80, + "height": 74, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 2052324312, + "version": 159, + "versionNonce": 840530648, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "hPdnH31A7pxd8wKMo9W4w" + } + ], + "updated": 1686930940351, + "link": null, + "locked": false + }, + { + "id": "hPdnH31A7pxd8wKMo9W4w", + "type": "text", + "x": 249.1139351413958, + "y": -1203.64962302258, + "width": 32.8125, + "height": 33.6, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "roundness": null, + "seed": 1323657384, + "version": 266, + "versionNonce": 237024424, + "isDeleted": false, + "boundElements": null, + "updated": 1686930940361, + "link": null, + "locked": false, + "text": "g1", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 27, + "containerId": "hKauQWNHUGqgu-Rz87oSS", + "originalText": "g1", + "lineHeight": 1.2, + "isFrameName": false + }, + { + "type": "ellipse", + "version": 194, + "versionNonce": 1644903896, + "isDeleted": false, + "id": "cllmOyZ7imBNQVoS07lIE", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 216.8849846830435, + "y": -880.0175779764669, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 80, + "height": 74, + "seed": 83447512, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "sdwQT-Fjy6c0MfolLZjwj" + } + ], + "updated": 1686930943161, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 304, + "versionNonce": 1510564264, + "isDeleted": false, + "id": "sdwQT-Fjy6c0MfolLZjwj", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 240.6944634355816, + "y": -859.9805288803691, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 32.8125, + "height": 33.6, + "seed": 597275608, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686930944788, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "g2", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "cllmOyZ7imBNQVoS07lIE", + "originalText": "g2", + "lineHeight": 1.2, + "baseline": 27 + }, + { + "type": "ellipse", + "version": 265, + "versionNonce": 77461928, + "isDeleted": false, + "id": "Mq4g-vhcwGNoAnfwluVdm", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1189.0464729631176, + "y": -1229.074316635483, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 80, + "height": 74, + "seed": 141660632, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "6LoRGRSEYZjntKqAbFLO6" + } + ], + "updated": 1686930947777, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 377, + "versionNonce": 1217953496, + "isDeleted": false, + "id": "6LoRGRSEYZjntKqAbFLO6", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1212.8559517156557, + "y": -1209.0372675393853, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 32.8125, + "height": 33.6, + "seed": 890345176, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686930948962, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "g3", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Mq4g-vhcwGNoAnfwluVdm", + "originalText": "g3", + "lineHeight": 1.2, + "baseline": 27 + }, + { + "type": "ellipse", + "version": 324, + "versionNonce": 1655492264, + "isDeleted": false, + "id": "LauW3FSTyhFn4zR_Bzqhr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1196.2155472873965, + "y": -879.007064725431, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 80, + "height": 74, + "seed": 2048910760, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "imsx6Di2x9Gszw-Ou0wNc" + } + ], + "updated": 1686930952027, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 439, + "versionNonce": 613071320, + "isDeleted": false, + "id": "imsx6Di2x9Gszw-Ou0wNc", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1220.0250260399346, + "y": -858.9700156293331, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 32.8125, + "height": 33.6, + "seed": 2070212776, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686930953216, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "g4", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "LauW3FSTyhFn4zR_Bzqhr", + "originalText": "g4", + "lineHeight": 1.2, + "baseline": 27 + }, + { + "type": "ellipse", + "version": 368, + "versionNonce": 708435624, + "isDeleted": false, + "id": "x6QQ253VJlu8zEb5R0CzY", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1188.4815403582083, + "y": -482.9256543056089, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 80, + "height": 74, + "seed": 329005736, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "SSL9qU8KA_pkSg9iniZq0" + } + ], + "updated": 1686930955495, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 486, + "versionNonce": 1440393688, + "isDeleted": false, + "id": "SSL9qU8KA_pkSg9iniZq0", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1212.2910191107464, + "y": -462.8886052095111, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 32.8125, + "height": 33.6, + "seed": 747546024, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686930956601, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "g5", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "x6QQ253VJlu8zEb5R0CzY", + "originalText": "g5", + "lineHeight": 1.2, + "baseline": 27 + }, + { + "type": "ellipse", + "version": 400, + "versionNonce": 407153320, + "isDeleted": false, + "id": "Ey_Lk9tEwkohPADBqN13b", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1192.595204537622, + "y": -131.1715613780798, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 80, + "height": 74, + "seed": 1679903400, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "7Aaf8r8XW5ccww1Nbm6vQ" + } + ], + "updated": 1686930958649, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 521, + "versionNonce": 1967496664, + "isDeleted": false, + "id": "7Aaf8r8XW5ccww1Nbm6vQ", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1216.4046832901602, + "y": -111.13451228198204, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 32.8125, + "height": 33.6, + "seed": 1240577448, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686930959682, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "g6", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Ey_Lk9tEwkohPADBqN13b", + "originalText": "g6", + "lineHeight": 1.2, + "baseline": 27 + }, + { + "type": "ellipse", + "version": 443, + "versionNonce": 2117463976, + "isDeleted": false, + "id": "ZU-jb4-By8ru40smcW1Dr", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1189.579578379019, + "y": 186.47174172904852, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 80, + "height": 74, + "seed": 6690472, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "type": "text", + "id": "3XttogphHOr4vUgvEQzaO" + } + ], + "updated": 1686930962938, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 567, + "versionNonce": 152070360, + "isDeleted": false, + "id": "3XttogphHOr4vUgvEQzaO", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1213.3890571315571, + "y": 206.50879082514624, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "width": 32.8125, + "height": 33.6, + "seed": 465371560, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686930964288, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 3, + "text": "g7", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "ZU-jb4-By8ru40smcW1Dr", + "originalText": "g7", + "lineHeight": 1.2, + "baseline": 27 + }, + { + "type": "text", + "version": 1225, + "versionNonce": 2145855448, + "isDeleted": false, + "id": "uJC7XJrbJqcpOVBqh1vnT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1737.909745935992, + "y": -415.584979672596, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.699981689453125, + "height": 26, + "seed": 500002264, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931111326, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "900", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "900", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1319, + "versionNonce": 441034152, + "isDeleted": false, + "id": "6CX1RSJET36ygDruNQ-Fh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1863.3976969768453, + "y": -411.1450868058396, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.219970703125, + "height": 26, + "seed": 1705055960, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931117014, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "942", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "942", + "lineHeight": 1.3, + "baseline": 18 + }, + { + "type": "text", + "version": 1349, + "versionNonce": 1342616792, + "isDeleted": false, + "id": "NW2P2tRnJvw8PUwIxlnj9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1983.2270635957514, + "y": -415.0518742566951, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 39.65997314453125, + "height": 26, + "seed": 1863362216, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1686931123725, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "998", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "998", + "lineHeight": 1.3, + "baseline": 18 + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/firehose/substreams/docs/assets/range_planning.png b/firehose/substreams/docs/assets/range_planning.png new file mode 100644 index 0000000..1ca0dab Binary files /dev/null and b/firehose/substreams/docs/assets/range_planning.png differ diff --git a/firehose/substreams/docs/assets/sink-deploy-flow.gif b/firehose/substreams/docs/assets/sink-deploy-flow.gif new file mode 100644 index 0000000..7731cc6 Binary files /dev/null and b/firehose/substreams/docs/assets/sink-deploy-flow.gif differ diff --git a/firehose/substreams/docs/assets/substreams-banner.png b/firehose/substreams/docs/assets/substreams-banner.png new file mode 100644 index 0000000..214bdbd Binary files /dev/null and b/firehose/substreams/docs/assets/substreams-banner.png differ diff --git a/firehose/substreams/docs/assets/substreams_processing.png b/firehose/substreams/docs/assets/substreams_processing.png new file mode 100644 index 0000000..c21ba96 Binary files /dev/null and b/firehose/substreams/docs/assets/substreams_processing.png differ diff --git a/firehose/substreams/docs/book.json b/firehose/substreams/docs/book.json new file mode 100644 index 0000000..4f618d7 --- /dev/null +++ b/firehose/substreams/docs/book.json @@ -0,0 +1,12 @@ +{ + plugins: ['github-embed'] +} +{ + pluginsConfig: { + 'github-embed': { + showLink: boolean [default=true] + reindent: boolean [default=true] + cacheDir: string [default=undefined] + } + } +} diff --git a/firehose/substreams/docs/cheatsheet/evm/blocks.md b/firehose/substreams/docs/cheatsheet/evm/blocks.md new file mode 100644 index 0000000..a238c82 --- /dev/null +++ b/firehose/substreams/docs/cheatsheet/evm/blocks.md @@ -0,0 +1,50 @@ + +The [Block](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L9) model contains very useful information, such as transactions or logs, but you can also find relevant information about the block itself. The [BlockHeader](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L58) model contains fields such as `nonce`, `parent_hash` or `gas_used`. + +

EVM-compatible Protobuf Structure - Block Header

+ +# Retrieve Block Basic Data + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::Block; + +struct BlockData { + hash: String, + number: u64, + size: u64 +} + +fn block_data(blk: Block) -> BlockData { + return BlockData { + hash: Hex::encode(blk.hash), + number: blk.number, + size: blk.size + } +} +``` + +# Retrieve Block Header Data + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::Block; + +struct BlockHeaderData { + parent_hash: String, + gas_limit: u64, + gas_used: u64, + nonce: u64, +} + +fn block_data(blk: Block) -> BlockHeaderData { + let header = blk.header.unwrap(); + + return BlockHeaderData { + parent_hash: Hex::encode(header.parent_hash), + gas_limit: header.gas_limit, + gas_used: header.gas_used, + nonce: header.nonce, + }; +} +``` \ No newline at end of file diff --git a/firehose/substreams/docs/cheatsheet/evm/logs.md b/firehose/substreams/docs/cheatsheet/evm/logs.md new file mode 100644 index 0000000..e7455cd --- /dev/null +++ b/firehose/substreams/docs/cheatsheet/evm/logs.md @@ -0,0 +1,65 @@ +The logs of a transaction are contained in the [TransactionReceipt](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L296) model. + +There is also a helper method in the [Block](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L9) object (`block.logs()`), which retrieves all the logs for the block. + +

EVM-compatible Protobuf Structure - Logs

+ +# Retrieving the Logs of a Transaction + +Given a [TransactionTrace](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L157): +1. Get TransactionReceipt (`receipt` property) +2. Get logs from TransactionReceipt (`receipt.logs` property) + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::TransactionTrace; + +struct Log { + address: String, + topics: Vec, + tx_hash: String +} + +fn transaction_logs(transaction: &TransactionTrace) -> Vec { + return transaction.receipt.unwrap().logs + .iter() + .map(|log| Log { + address: Hex::encode(log.address), + topics: log.topics.into_iter().map(Hex::encode).collect(), + tx_hash: Hex::encode(&transaction.hash), + }) + .collect(); +} +``` + +# Retrieving the Logs of a Smart Contract + +Given a [Block](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L9) and a smart contract address (`String`): +1. Use the `logs()` method to get all the logs for the corresponding block. +2. Filter every log by its address (`address` property). + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::Block; + +struct ContractLog { + address: String, + topics: Vec, + tx_hash: String +} + +fn get_contract_logs(contract_address: String, blk: Block) -> Vec { + let events: Vec = blk + .logs() + .filter(|log| log.address().to_vec() == Hex::decode(&contract_address).unwrap()) + .map(|log| ContractLog { + address: Hex::encode(log.address()), + topics: log.topics().into_iter().map(Hex::encode).collect(), + tx_hash: Hex::encode(&log.receipt.transaction.hash), + }) + .collect(); + + return events; +} +``` + diff --git a/firehose/substreams/docs/cheatsheet/evm/overview.md b/firehose/substreams/docs/cheatsheet/evm/overview.md new file mode 100644 index 0000000..2604ea0 --- /dev/null +++ b/firehose/substreams/docs/cheatsheet/evm/overview.md @@ -0,0 +1,5 @@ +All EVM-compatible chains (Ethereum, BNB, Polygon and Arbitrum) share the same Protobuf model. A blockchain block is represented by the [Block](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L9) object, which you can use to retrieve transactions, logs, or calls, among other information. + +

EVM-compatible Protobuf Structure

+ +The following sections include ready-to-use examples with the most common transformations you can perform with Substreams. \ No newline at end of file diff --git a/firehose/substreams/docs/cheatsheet/evm/transactions.md b/firehose/substreams/docs/cheatsheet/evm/transactions.md new file mode 100644 index 0000000..ca273be --- /dev/null +++ b/firehose/substreams/docs/cheatsheet/evm/transactions.md @@ -0,0 +1,140 @@ + +In EVM-compatible chains, a Trasanction represents a change in the blockchain, such as ETH transfers or smart contract executions. In Substreams, transactions are abstracted by the [TransactionTrace](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L157) Protobuf model. Some of the most relevant fields and methods of the model are: +- `hash` (property): hash of the transaction. +- `from` (property): `from` field of the transaction. +- `to` (property): `to` field of the transaction. +- `input`: (property): `input` field of the transaction. +- `receipt`: (property): [TransactionReceipt](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L296) struct representing the receipt of the transaction. +- `status()` (method): [TransactionTraceStatus](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto#L289) enum struct representing the status of the transaction, with the following possible values: `TransactionTraceStatus::Succeeded`, `TransactionTraceStatus::Failed`, `TransactionTraceStatus::Reverted`, `TransactionTraceStatus::Unknown`. + + +

EVM-compatible Protobuf Structure - TransactionTrace

+ +# Iterating over ALL Transactions + +The `block.transaction_traces` property contains **all** transactions, regardless of their status. + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::Block; + +struct TransactionMeta { + hash: String, + from: String, + to: String +} + +fn all_transactions(blk: Block) -> Vec { + return blk.transaction_traces + .iter() + .map(|tx| TransactionMeta { + hash: Hex::encode(tx.hash), + from: Hex::encode(tx.from), + to: Hex::encode(tx.to) + }) + .collect(); +} +``` + +# Iterating over SUCCESSFUL Transactions + +The `transactions()` method contains **successful** transactions. + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::Block; + +struct TransactionMeta { + hash: String, + from: String, + to: String +} + +fn successful_transactions(blk: Block) -> Vec { + return blk.transactions() + .map(|tx| TransactionMeta { + hash: Hex::encode(tx.hash), + from: Hex::encode(tx.from), + to: Hex::encode(tx.to) + }) + .collect(); +} +``` + +You can also iterate over **all** transactions and **filter by their status** (`TransactionTraceStatus::Succeeded`), . + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::{Block, TransactionTraceStatus}; + +struct TransactionMeta { + hash: String, + from: String, + to: String +} + +fn successful_transactions(blk: Block) -> Vec { + return blk.transaction_traces.iter() + .filter(|tx| tx.status() == TransactionTraceStatus::Succeeded) + .map(|tx| TransactionMeta { + hash: Hex::encode(tx.hash), + from: Hex::encode(tx.from), + to: Hex::encode(tx.to) + }) + .collect(); +} +``` + +# Iterating over FAILED Transactions + +Iterate over **all** transactions and **filter by their status** (`TransactionTraceStatus::Failed`), . + + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::{Block, TransactionTraceStatus}; + +struct TransactionMeta { + hash: String, + from: String, + to: String +} + +fn failed_transactions(blk: Block) -> Vec { + return blk.transaction_traces.iter() + .filter(|tx| tx.status() == TransactionTraceStatus::Failed) + .map(|tx| TransactionMeta { + hash: Hex::encode(tx.hash), + from: Hex::encode(tx.from), + to: Hex::encode(tx.to) + }) + .collect(); +} +``` + + +# Iterating over REVERTED Transactions + +Iterate over **all** transactions and **filter by their status** (`TransactionTraceStatus::Reverted`), . + +```rust +use substreams::Hex; +use substreams_ethereum::pb::eth::v2::{Block, TransactionTraceStatus}; + +struct TransactionMeta { + hash: String, + from: String, + to: String +} + +fn failed_transactions(blk: Block) -> Vec { + return blk.transaction_traces.iter() + .filter(|tx| tx.status() == TransactionTraceStatus::Reverted) + .map(|tx| TransactionMeta { + hash: Hex::encode(tx.hash), + from: Hex::encode(tx.from), + to: Hex::encode(tx.to) + }) + .collect(); +} +``` diff --git a/firehose/substreams/docs/concepts-and-fundamentals/benefits.md b/firehose/substreams/docs/concepts-and-fundamentals/benefits.md new file mode 100644 index 0000000..a98dcf3 --- /dev/null +++ b/firehose/substreams/docs/concepts-and-fundamentals/benefits.md @@ -0,0 +1,70 @@ +--- +description: StreamingFast Substreams benefits and comparisons +--- + +# Benefits and comparisons + +## Important Substreams facts include: + +- It provides a streaming-first system based on gRPC, protobuf, and the StreamingFast Firehose. +- It supports a highly cacheable and parallelizable remote code execution framework. +- It enables the community to build higher-order modules that are composable down to individual modules. +- Deterministic blockchain data is fed to Substreams, **making it deterministic**. +- It is **not** a relational database. +- It is **not** a REST service. +- It is **not** concerned directly about how data is queried. +- It is **not** a general-purpose non-deterministic event stream processor. + +### Substreams offers several benefits including: + +- The ability to store and process blockchain data using advanced parallelization techniques, making the processed data available for various types of data stores or real-time systems. +- A streaming-first approach that inherits low latency extraction from [StreamingFast Firehose](https://firehose.streamingfast.io/). +- The ability to save time and money by horizontally scaling and increasing efficiency by reducing processing time and wait time. +- The ability for communities to [combine Substreams modules](../developers-guide/modules/) to form compounding levels of data richness and refinement. +- The use of [protobufs for data modeling and integration](../developers-guide/creating-protobuf-schemas.md) in a variety of programming languages. +- The use of the Rust programming language and a wide array of third-party libraries compilable with WASM to manipulate blockchain data on-the-fly. +- Inspiration from conventional large-scale data systems fused into the novelties of blockchain technology. + +### **Other features** + +#### Composition through community + +Substreams allows you to write Rust modules that compose data streams alongside the community. The end result of these community-developed solutions is more meaningful blockchain data. + +#### Parallelization + +Substreams' powerful parallelization techniques enable efficient processing of enormous blockchain histories, providing extremely high-performance indexing in a streaming-first fashion. + +#### Horizontally scalable + +Substreams is horizontally scalable, offering the opportunity to reduce processing time by adding more computing power or machines. + +#### Substreams and Firehose + +Substreams offers all the benefits of [Firehose](https://firehose.streamingfast.io/), including low-cost caching and archiving of blockchain data, high throughput processing, and cursor-based reorg handling. It is platform-independent of underlying blockchain protocols and works solely on data extracted from nodes using Firehose. For example, different protocols have different chain-specific extensions, such as Ethereum's `eth_calls`. + +### Comparison to other engines + +Substreams is a streaming engine similar to [Fluvio](https://www.fluvio.io/), [Kafka](https://kafka.apache.org/), [Apache Spark](https://spark.apache.org/), and [RabbitMQ](https://www.rabbitmq.com/), where a blockchain node serving as a deterministic data source acts as the producer. Its logs-based architecture through [Firehose](https://firehose.streamingfast.io/) allows users to send custom code for streaming and ad hoc querying of the available data. + +#### Substreams & Subgraphs + +A lot of questions arise around Substreams and Subgraphs as they are both part of The Graph ecosystem. Substreams has been created by the StreamingFast team, the first core developers teams outside of Edge & Node, the founding team of The Graph. It was created in response to different use cases especially around analytics and big data that couldn't be served by Subgraph due to its current programming model. Here some of the key points for which Substreams were created: + +- Offer a streaming-first approach to consuming/transforming blockchain's data +- Offer a highly parallelizable yet simple model to consume/transform blockchain's data +- Offer a composable system where you can depend on building blocks offered by the community +- Offer rich block model + +While they share similar ideas around blockchain's transformation/processing and they are both part of The Graph ecosystem, both can be viewed as independent technology that are unrelated to each other. One cannot take a Subgraph's code and run it on the Substreams engine, they are incompatible. Here some of key differences: + +- You write your Substreams in Rust while Subgraphs are written in AssemblyScript +- Substreams are "stateless" request through gRPC while Subgraphs are persistent deployment +- Substreams offers you the chain's specific full block while in Subgraph, you define "triggers" that will invoke your code +- Substreams are consumed through a gRPC connection where you control the actual output message while Subgraphs are consumed through GraphQL +- Substreams have no long-term storage nor database (it has transient storage) while Subgraph stores data persistently in Postgres +- Substreams can be consumed in real-time with a fork aware model while Subgraph can only be consumed through GraphQL and polling for "real-time" + +Substreams offer quite a different model when compared to Subgraph, just Rust alone is a big shift for someone used to write Subgraphs in AssemblyScript. Substreams is working a lot also with Protobuf models also. + +One of the benefits of Substreams is that the persistent storage solution is not part of the technology directly, so you are free to use the database of your choice. This enables a lot of analytics use cases that were not possible (or harder to implement) today using Subgraphs like persistent your transformed data to BigQuery or Clickhouse, Kafka, etc. Also, the live streaming feature of Substreams enables further use cases and super quick reactivity that will benefits a lot of user. diff --git a/firehose/substreams/docs/concepts-and-fundamentals/fundamentals.md b/firehose/substreams/docs/concepts-and-fundamentals/fundamentals.md new file mode 100644 index 0000000..27415fb --- /dev/null +++ b/firehose/substreams/docs/concepts-and-fundamentals/fundamentals.md @@ -0,0 +1,90 @@ +--- +description: StreamingFast Substreams fundamental knowledge +--- + +# Fundamentals + +## Fundamentals overview + +Substreams development involves using several different pieces of technology, including the [Substreams engine](fundamentals.md#the-substreams-engine), [`substreams` command line interface (CLI)](../reference-and-specs/command-line-interface.md), [modules](../developers-guide/modules/), [protobufs](../developers-guide/creating-protobuf-schemas.md), and various configuration files of different types. The documentation explains how these pieces fit together. + +

Substreams in Action

+ +### The process to use Substreams includes: + +- Choose the blockchain to capture and process data. +- Identify interesting smart contract addresses (like DEXs or interesting wallet addresses). +- Identify the data and defining and creating protobufs. +- Find already-built Substreams modules and consume their streams, or: +- Write Rust Substreams module handler functions. +- Update the Substreams manifest to reference the protobufs and module handlers. +- Use the [`substreams` CLI](../reference-and-specs/command-line-interface.md) to send commands and view results. + +### **The Substreams engine** + +The Substreams engine serves as the CPU or brain of the Substreams system, handling requests, communication, and orchestrating the transformation of blockchain data. + +{% hint style="info" %} +**Note**: The Substreams engine is responsible for running developer-defined data transformations to process blockchain data. +{% endhint %} + +Developers use the [`substreams` CLI](../reference-and-specs/command-line-interface.md) to send commands, flags, and a reference to the manifest configuration file to the Substreams engine. They create data transformation strategies in Substreams "_module handlers_" using the Rust programming language, which acts on protobuf-based data models referenced from within the Substreams manifest. + +### **Substreams module communication** + +The Substreams engine runs the code defined by developers in Rust-based module handlers. + +{% hint style="info" %} +**Note**: Substreams modules have **unidirectional data flow,** meaning data is passed from one module to another in a single direction. +{% endhint %} + +The data flow is [defined in the Substreams manifest](../reference-and-specs/manifests.md) through the "inputs" and "outputs" fields of the configuration file, which reference the protobuf definitions for blockchain data. The data flow is also defined by using the "inputs" field to send data directly from one module to another. + +### **Substreams DAG** + +Substreams modules are composed through a [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) (DAG). + +{% hint style="info" %} +**Note**: In DAGs, data flows from one module to another in a one-directional manner, with no cycle, similar to Git's model of commits and branches. +{% endhint %} + +The Substreams manifest references the modules and the handlers defined within them, forming the intention of how they are used by the Substreams engine. + +Directed acyclic graphs contain nodes, which in this case are modules communicating in only one direction, passing from one node or module to another. + +The Substreams engine creates the "_compute graph_" or "_dependency graph_" at run time through commands sent to the [`substreams` CLI](../reference-and-specs/command-line-interface.md) using the code in modules referenced by the manifest. + +### **Protobufs for Substreams** + +

Substreams module handlers linked to protobuf

+ +[Protocol buffers or protobufs](https://developers.google.com/protocol-buffers) are the data models operated on by the[ Rust-based module handler functions](../developers-guide/modules/writing-module-handlers.md). They define and outline the data models in the protobufs. + +- View the [`erc721.proto`](https://github.com/streamingfast/substreams-template/blob/develop/proto/erc721.proto) protobuf file in the [Substreams Template repository](https://github.com/streamingfast/substreams-template). +- View the Rust module handlers in the [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs) file in the [Substreams Template repository](https://github.com/streamingfast/substreams-template). + +{% hint style="info" %} +**Note**: Protobufs include the names of the data objects and the fields contained and accessible within them. +{% endhint %} + +Many protobuf definitions have already been created, such as [the erc721 token model](https://github.com/streamingfast/substreams-template/blob/develop/proto/erc721.proto), for use by developers creating Substreams data transformation strategies. + +Custom smart contracts, like [UniSwap](https://github.com/streamingfast/substreams-uniswap-v3/blob/e4b0fb016210870a385484f29bb5116931ea9a50/proto/uniswap/v1/uniswap.proto), also have protobuf definitions that are referenced in the Substreams manifest and made available to module handler functions. Protobufs provide an API to the data for smart contract addresses. + +In object-oriented programming terminology, protobufs are the objects or object models. In front-end web development, they are similar to REST or other data APIs. + +{% hint style="success" %} +**Tip**: Firehose and Substreams **treat the data as the API**. +{% endhint %} + +### **Substreams Rust modules** + +

Writing Rust Modules for Substreams

+ +The first step in Substreams development is to design an overall strategy for managing and transforming data. The Substreams engine processes modules by using the relationships defined in the manifest. + +{% hint style="info" %} +**Note**: Substreams modules work together by passing data from one module to another until they finally return an output transformed according to the rules in the manifest, modules, and module handler functions. +{% endhint %} + +Modules define two types of module handlers: `map` and `store`. These two types work together to sort, sift, temporarily store, and transform blockchain data from `Block` objects and smart contracts for use in data sinks such as databases or subgraphs. diff --git a/firehose/substreams/docs/concepts-and-fundamentals/modules.md b/firehose/substreams/docs/concepts-and-fundamentals/modules.md new file mode 100644 index 0000000..df1391e --- /dev/null +++ b/firehose/substreams/docs/concepts-and-fundamentals/modules.md @@ -0,0 +1,82 @@ +--- +description: StreamingFast Substreams modules basics +--- + +# Modules basics + +## Modules basics overview + +Modules are an important part of Substreams, offering hooks into the execution of the Substreams compute engine. You can create Substreams data manipulation and transformation strategies within modules. + +Modules are small pieces of Rust code running in a [WebAssembly (WASM)](https://webassembly.org/) virtual machine. They coexist within the stream of blocks sent by the Substreams compute engine, which arrives from a blockchain node. + +Modules have one or more inputs, which can be in the form of a `map` or `store`, or a `Block` or `Clock` object received from the blockchain's data source. + +{% embed url="https://mermaid.ink/svg/pako:eNp1kM0KwjAQhF8l7NkWvEbwIPUJ9NYUWZKtLTZJ2WwEEd_dCAr-4GFhd_h2GOYKNjoCDUfGeVD7ZmWCUqmvSQZiyr6Wy0z1eVlvpmhPbYqZLen_RKeqaq2EMaSe-OBxfhi-320Z_aF8_diYgxC3SSKT_tE7WIAn9ji6kvv6sDdQsngyoMvqqMc8iQETbgXNs0OhrRuLG-gep0QLwCxxdwkWtHCmF9SMWGrwT-p2B02rZZY" %} +Substreams modules data interaction diagram +{% endembed %} + +The diagram shows how the `transfer_map` module extracts the transfers in a `Block` and tracks the total number of transfers. + +{% hint style="info" %} +**Note:** You can use multiple inputs in blockchains because they are clocked, which allows for synchronization between multiple execution streams and improved performance compared to conventional streaming engines. +{% endhint %} + +As seen in the `counters` `store` example diagram, modules can also take in multiple inputs. In this case, two modules feed into a `store`, effectively tracking multiple `counters`. + +{% embed url="https://mermaid.ink/svg/pako:eNqdkE1qAzEMha9itE4GsnWgi5KcINmNh6LamozJeGxsuSGE3L1KW1PIptCdnnjv088NbHQEGk4Z06SOu61ZlHqfoz33JdZsSasydsQTZaqh42ui7mPTvT4cg1qvX1TA9HbxPLmMF5zLv_KOUiyev8JPvF60fm5-J22sC1MufeGYZVDTQ8M07C-jdf4AwAoC5YDeyWtuD5wBOSGQAS2loxHrzAbMchdrTQ6Z9s4LBfQo-9EKsHI8XBcLmnOlZtp5lE-HH9f9EylZic0" %} +Multiple module inputs diagram +{% endembed %} + +Every time a new `Block` is processed, all of the modules are executed as a directed acyclic graph (DAG). + +{% hint style="info" %} +**Note:** The protocol's Block protobuf model always serves as the top-level data source and executes deterministically. +{% endhint %} + +### Single output + +Modules have a single typed output, which is typed to inform consumers of the types of data to expect and how to interpret the bytes being sent. + +{% hint style="success" %} +**Tip**: In subsequent modules, input from one module's data output is used to form a chain of data flow from module to module. +{% endhint %} + +### `map` versus `store` modules + +To develop most non-trivial Substreams, you will need to use multiple `map` and `store` modules. The specific number, responsibilities, and communication methods for these modules will depend on the developer's specific goals for the Substreams development effort. + +The two module types are commonly used together to construct the directed acyclic graph (DAG) outlined in the Substreams manifest. The two module types are very different in their use and how they work. Understanding these differences is vital for harnessing the full power of Substreams. + +### `map` modules + +`map` modules are used for data extraction, filtering, and transformation. They should be used when direct extraction is needed avoiding the need to reuse them later in the DAG. + +To optimize performance, you should use a single `map` module instead of multiple `map` modules to extract single events or functions. It is more efficient to perform the maximum amount of extraction in a single top-level `map` module and then pass the data to other Substreams modules for consumption. This is the recommended, simplest approach for both backend and consumer development experiences. + +Functional `map` modules have several important use cases and facts to consider, including: + +* Extracting model data from an event or function's inputs. +* Reading data from a block and transforming it into a custom protobuf structure. +* Filtering out events or functions for any given number of contracts. + +### `store` modules + +`store` modules are used for the aggregation of values and to persist state that temporarily exists across a block. + +{% hint style="warning" %} +**Important:** Stores should not be used for temporary, free-form data persistence. +{% endhint %} + +Unbounded `store` modules are discouraged. `store` modules shouldn't be used as an infinite bucket to dump data into. + +Notable facts and use cases for working `store` modules include: + +* `store` modules should only be used when reading data from another downstream Substreams module. +* `store` modules cannot be output as a stream, except in development mode. +* `store` modules are used to implement the Dynamic Data Sources pattern from Subgraphs, keeping track of contracts created to filter the next block with that information. +* Downstream of the Substreams output, do not use `store` modules to query anything from them. Instead, use a sink to shape the data for proper querying. + +### Additional information + +Learn more about [modules in the Developer's guide](../developers-guide/modules/). diff --git a/firehose/substreams/docs/developers-guide/cookbook.md b/firehose/substreams/docs/developers-guide/cookbook.md new file mode 100644 index 0000000..4ef8f54 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook.md @@ -0,0 +1,11 @@ +--- +description: Cookbook for Substreams developers +--- + +# Cookbook for Substreams developers +This chapter contains various common patterns that developers can find useful with a special emphasis on subgraph development. + +Most of the patterns are based on Uniswap V3 Substreams. + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) \ No newline at end of file diff --git a/firehose/substreams/docs/developers-guide/cookbook/advanced-params.md b/firehose/substreams/docs/developers-guide/cookbook/advanced-params.md new file mode 100644 index 0000000..f936fc5 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook/advanced-params.md @@ -0,0 +1,86 @@ +--- +description: Advanced params usage +--- + +# Advanced parameters + +Sometimes you may need to use multiple parameters for a module. To pass multiple parameters, you can encode them as a URL-encoded query string, i.e. `param1=value1¶m2=value2`. + +Suppose you want to track transfers to/from a certain address exceeding a certain amount of ETH. Your module manifest could look like this: + +```yaml +modules: + - name: map_whale_transfers + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:Transfers +params: + map_params: address=aaa..aaa&amount=100 +``` + +Our module gets a params string with two parameters: `address` and `amount`. + +In your module handler, you can decode your parameters using one of the URL decoding crates such as `serde_qs`, `serde_urlencoded` or your own helper functions. Here's an example using `serde_qs`: + +```rust +#[derive(Debug, Deserialize)] +struct Params { + address: String, + amount: u64, +} + +#[substreams::handlers::map] +pub fn map_whale_transfers(params: String, block: Block) -> Result { + let query: Params = serde_qs::from_str(params.as_str()).unwrap(); + log::info!("Tracking transfers for address: {} of more than {} ETH", query.address, query.amount); + + // filter transfers by address and amount +} +``` + +Sometimes parameters can be optional, i.e. you want to track all transfers rather than a specific address. Decoding will look like this in that case: + +```rust +#[derive(Debug, Deserialize)] +struct QueryParams { + address: Option, + amount: u64, +} + +#[substreams::handlers::map] +pub fn map_whale_transfers(params: String, block: Block) -> Result { + let query: QueryParams = serde_qs::from_str(params.as_str()).unwrap(); + + if query.address.is_none() { + log::info!("Tracking all of more than {} ETH", query.amount); + } + else { + log::info!("Tracking transfers for address: {} of more than {} ETH", query.address, query.amount); + } +} +``` + +You can even pass a vector of addresses to track multiple specific whales in our example: + +```rust +#[derive(Debug, Deserialize)] +struct QueryParams { + address: Vec, + amount: u64, +} + +#[substreams::handlers::map] +pub fn map_whale_transfers(params: String, block: Block) -> Result { + let query: QueryParams = serde_qs::from_str(params.as_str()).unwrap(); + log::info!("Tracking transfers for addresses: {:?} of more than {} ETH", query.address, query.amount); +} +``` + +Depending on the crate you use to decode params string, you can pass them to Substreams CLI like this for example: + +```bash +substreams gui map_whale_transfers -p map_whale_transfers="address[]=aaa..aaa&address[]=bbb..bbb&amount=100" +``` diff --git a/firehose/substreams/docs/developers-guide/cookbook/aggregation-windows.md b/firehose/substreams/docs/developers-guide/cookbook/aggregation-windows.md new file mode 100644 index 0000000..e86f36a --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook/aggregation-windows.md @@ -0,0 +1,62 @@ +--- +description: Building and freeing up aggregation windows +--- + +# Building and freeing up aggregation windows + +Store module key-value storage can hold at most 1 GiB. It is usually enough if used correctly, but it is still a good idea (and sometimes even necessary) to free up unused keys. It is especially true for cases where you work with aggregation windows. + +Consider this store module that aggregates hourly trade counter for each token: +```rust +#[substreams::handlers::store] +pub fn store_total_tx_counts(clock: Clock, events: Events, output: StoreAddBigInt) { + let timestamp_seconds = clock.timestamp.unwrap().seconds; + let hour_id = timestamp_seconds / 3600; + let prev_hour_id = hour_id - 1; + + output.delete_prefix(0, &format!("TokenHourData:{prev_hour_id}:")); + + for event in events.pool_events { + output.add_many( + event.log_ordinal, + &vec![ + format!("TokenHourData:{}:{}", hour_id, event.token0), + format!("TokenHourData:{}:{}", hour_id, event.token1), + ], + &BigInt::from(1 as i32), + ); + } +} +``` + +Let's break it down. + +First, we use `Clock` input source to get the current and previous hour id for the block. + +```rust +let hour_id = timestamp_seconds / 3600; +let prev_hour_id = hour_id - 1; +``` + +Then we build hourly keys for our counters and use `add_many` method to increment them. These counters will be consumed downstream by other modules. + +```rust +output.add_many( + event.log_ordinal, + &vec![ + format!("TokenHourData:{}:{}", hour_id, event.token0), + format!("TokenHourData:{}:{}", hour_id, event.token1), + ], + &BigInt::from(1 as i32), +); +``` + +Here's the trick. Since we don't need these counters outside of the hourly window, we can safely delete these key-value pairs for the previous hourly window and free up the memory. + +This is done using `delete_prefix` method: +```rust +output.delete_prefix(0, &format!("TokenHourData:{prev_hour_id}:")); +``` + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) \ No newline at end of file diff --git a/firehose/substreams/docs/developers-guide/cookbook/dynamic-data-sources.md b/firehose/substreams/docs/developers-guide/cookbook/dynamic-data-sources.md new file mode 100644 index 0000000..bd3a7fe --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook/dynamic-data-sources.md @@ -0,0 +1,108 @@ +--- +description: Dynamic data sources and Substreams +--- + +# Dynamic data sources and Substreams + +Using Factory contract is a quite common pattern used by dApps when the main smart contract deploys and manages multiple identical associated contracts, i.e. one smart contract for each Uniswap or Curve swap pool. + +When developing traditional subgraphs, you could use [data source templates](https://thegraph.com/docs/en/developing/creating-a-subgraph/#data-source-templates) approach to keep track of such dynamically deployed smart contracts. + +Here's how you can achieve that with Substreams. + +We'll be using Uniswap V3 example where the Factory creates and deploys its smart contract for each pool. + +You start with a simple map module that emits all pool creation events: +```yaml +- name: map_pools_created + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:uniswap.types.v1.Pools +``` + +```rust +#[substreams::handlers::map] +pub fn map_pools_created(block: Block) -> Result { + Ok(Pools { + pools: block + .events::(&[&UNISWAP_V3_FACTORY]) + .filter_map(|(event, log)| { + // skipped: extracting pool information from the transaction + Some(Pool { + address, + token0, + token1, + ..Default::default() + }) + }) + .collect(), + }) +} +``` + +We can now take that map module output and direct these pool creation events into a Substreams key-value store using a store module: +```yaml + - name: store_pools_created + kind: store + updatePolicy: set + valueType: proto:uniswap.types.v1.Pool + inputs: + - map: map_pools_created +``` +```rust +#[substreams::handlers::store] +pub fn store_pools_created(pools: Pools, store: StoreSetProto) { + for pool in pools.pools { + let pool_address = &pool.address; + store.set(pool.log_ordinal, format!("pool:{pool_address}"), &pool); + } +} +``` + +Above we are using `pool:{pool_address}` as a key to store the pool information. Eventually, our store will contain all Uniswap pools. +Now, in the downstream modules, we can easily retrieve our pool from the store whenever we need it. + +```yaml +- name: map_events + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_pools_created + output: + type: proto:uniswap.types.v1.Events +``` + +```rust +#[substreams::handlers::map] +pub fn map_events(block: Block, pools_store: StoreGetProto) -> Result { + let mut events = Events::default(); + + for trx in block.transactions() { + for (log, call_view) in trx.logs_with_calls() { + let pool_address = &Hex(&log.address).to_string(); + + let pool = match pools_store.get_last(format!("pool:{pool_address}")) { + Some(pool) => pool, + None => { continue; } + }; + + // use the pool information from the store + } + } + + Ok(events) +} +``` + +Here we use `pools_store.get_last()` method to get the pool from the store by its smart contract address. Once we have it, we can use that information to analyze the swap transaction and emit the events. + +Alternatively, we could make RPC calls to get the pool details from an RPC node but that would be extremely inefficient considering that we would need to make RPC calls for millions of such events. Using a store will be much faster. + +For a real-life application of this pattern see [Uniswap V3 Substreams](https://github.com/streamingfast/substreams-uniswap-v3) + + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) +* [Substreams Sink Entity Changes](https://github.com/streamingfast/substreams-sink-entity-changes) \ No newline at end of file diff --git a/firehose/substreams/docs/developers-guide/cookbook/entity-changes.md b/firehose/substreams/docs/developers-guide/cookbook/entity-changes.md new file mode 100644 index 0000000..b1979f5 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook/entity-changes.md @@ -0,0 +1,100 @@ +--- +description: Creating and Updating Subgraph entities +--- + +# Creating and Updating Subgraph entities +A common convention for Substreams-powered subgraph development is to implement a `graph_out` module emitting `sf.substreams.sink.entity.v1.EntityChanges` events. When these events are consumed by the indexer, entities in the subgraph are updated accordingly. + +[substreams-entity-change](https://crates.io/crates/substreams-entity-change) crate offers helper methods to assist with that. + +Here's a sample declaration of `graph_out` module in `substreams.yaml`: + +```yaml + - name: graph_out + kind: map + inputs: + - source: sf.substreams.v1.Clock + - store: store_eth_prices + mode: deltas + output: + type: proto:sf.substreams.sink.entity.v1.EntityChanges +``` + +This module is for a simple subgraph that tracks ETH/USD rate derived from Uniswap pools. + +`Clock` input source emits events every block and helps you keep track of the block number and timestamp. + +`store_eth_prices` module stores derived ETH prices and in `delta` mode it + +Our subgraph has a single entity: +```graphql +# stores for USD calculations +type Bundle @entity { + id: ID! + # price of ETH in usd + ethPriceUSD: BigDecimal! +} +``` + +Corresponding Rust code for the `graph_out` module can look like this: + +```rust +use substreams_entity_change::pb::entity::EntityChanges; +use substreams_entity_change::tables::Tables; + +#[substreams::handlers::map] +pub fn graph_out(clock: Clock, derived_eth_prices_deltas: Deltas) -> Result { + let mut tables = Tables::new(); + + if clock.number == 12369621 { + tables + .create_row("Bundle", "1") + .set("ethPriceUSD", BigDecimal::zero()); + } + + for delta in derived_eth_prices_deltas.into_iter(){ + tables.update_row("Bundle", "1").set("ethPriceUSD", delta.new_value); + } + + Ok(tables.to_entity_changes()) +} + +``` +Let's break it down. + +`substreams-entity-change` crate offers `Tables` struct to work with the entities. +First, we instantiate `Tables` object: +```rust +let mut tables = Tables::new(); +``` + +Then we check if this is the first block of our substream and if so, we create the entity using `create_row` method. +```rust +if clock.number == 12369621 { + tables + .create_row("Bundle", "1") + .set("ethPriceUSD", BigDecimal::zero()); +} +``` +`create_row` takes two arguments: entity name and entity id. In our case, we use "Bundle" entity name - that's the entity we have defined in the subgraph `schema.graphql` schema. We use "1" as `id`. That's the only price that we will have. + +Note: `12369621` magic block number is used here for simplicity. Typically you would define it as a module parameter. + +Next, we iterate through all ETH price deltas within that block and update it in our table. +```rust +for delta in derived_eth_prices_deltas.into_iter(){ + tables + .update_row("Bundle", "1") + .set("ethPriceUSD", delta.new_value); +} +``` +Here, we use `update_row` method to create an `UPDATE` entity operation, and we use `set` method to set the corresponding entity field. + +One last step, we convert our `Tables` helper object into the `EntityChanges` object that our subgraph can consume: +```rust +Ok(tables.to_entity_changes()) +``` + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) +* [Substreams Entity Changes](https://github.com/streamingfast/substreams-sink-entity-changes) \ No newline at end of file diff --git a/firehose/substreams/docs/developers-guide/cookbook/factory-params.md b/firehose/substreams/docs/developers-guide/cookbook/factory-params.md new file mode 100644 index 0000000..05d6535 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook/factory-params.md @@ -0,0 +1,76 @@ +--- +description: Parameterization of a Factory contract +--- + +# Parameterization of a Factory contract + +It's quite common for a smart contract to be deployed on different networks or even by different dApps within the same network. Uniswap Factory smart contract is a good example of that. + +When running Substreams for a dApp, you need to know the smart contract deployment address and for obvious reasons, this address will be different for each deployment. + +Instead of hard-coding the address in the Substreams binary, you can customize it without having to rebuild or even repackage the Substreams package. The consumer can then just provide the address as a parameter. + +First, you need to add the `params` field as an input. Note that it's always a string and it's always the first input for the module: + +```yaml +modules: + - name: map_pools_created + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:uniswap.types.v1.Pools +params: + map_params: 1f98431c8ad98523631ae4a59f267346ea31f984 +``` + +You can specify the default value directly in the manifest. In this case, we use `0x1f98431c8ad98523631ae4a59f267346ea31f984` - the deployment address for UniswapV3 contract on Ethereum Mainnet. + +Handling the parameter in the module is easy. The module handler receives it as a first input parameter and you can use it to filter transactions instead of the hard-coded value: + +```rust +#[substreams::handlers::map] +pub fn map_pools_created(params: String, block: Block) -> Result { + let factory_address = Hex::decode(params).unwrap(); + Ok(Pools { + pools: block + .events::(&[&factory_address]) + .filter_map(|(event, log)| { + // skipped: extracting pool information from the transaction + Some(Pool { + address, + token0, + token1, + ..Default::default() + }) + }) + .collect(), + }) +} +``` + +To pass the parameter to the module using `substreams` CLI you can use `-p` key: + +```bash +substreams gui -e $SUBSTREAMS_ENDPOINT map_pools_created -t +1000 -p map_pools_created="1f98431c8ad98523631ae4a59f267346ea31f984"` +``` + +### Documenting parameters +It's always a good idea to document what the params represent and how they are structured, so the consumers of your modules know how to properly parameterize them. You can use `doc` field for the module definition in the manifest. + +```yaml +modules: + - name: map_pools_created + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + - params: string + output: + type: proto:uniswap.types.v1.Pools + doc: | + Params contains Uniswap factory smart contract address without `0x` prefix, i.e. 1f98431c8ad98523631ae4a59f267346ea31f984 for Ethereum Mainnet +``` + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) \ No newline at end of file diff --git a/firehose/substreams/docs/developers-guide/cookbook/keys-in-stores.md b/firehose/substreams/docs/developers-guide/cookbook/keys-in-stores.md new file mode 100644 index 0000000..a75b986 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/cookbook/keys-in-stores.md @@ -0,0 +1,71 @@ +--- +description: Using keys in stores +--- + +# Keys in stores + +We use store modules to aggregate the data in the underlying key-value storage. It is important to have a system for organizing your keys to be able to efficiently retrieve, filter and free them when needed. + +In most cases, you will encode data into your keys into segmented parts, adding a prefix as namespace for example `user` and `
` joined together using a separator. Segments in a key are conventionally joined with `:` as a separator. + +Here are some examples, +- `Pool:{pool_address}:volumeUSD` - `{pool_address}` pool total traded USD volume +- `Token:{token_addr}:volume` - total `{token_addr}` token volume traded +- `UniswapDayData:{day_id}:volumeUSD` - `{day_id}` daily USD trade volume +- `PoolDayData:{day_id}:{pool_address}:{token_addr}:volumeToken1` - total `{day_id}` daily volume of `{token_addr}` token that went through a `{pool_address}` pool in token1 equivalent + +In the example of a counter store below, we increment transaction counters for different metrics that we could use in the downstream modules: +```rust +#[substreams::handlers::store] +pub fn store_total_tx_counts(clock: Clock, events: Events, output: StoreAddBigInt) { + let timestamp_seconds = clock.timestamp.unwrap().seconds; + let day_id = timestamp_seconds / 86400; + let hour_id = timestamp_seconds / 3600; + let prev_day_id = day_id - 1; + let prev_hour_id = hour_id - 1; + + for event in events.pool_events { + let pool_address = &event.pool_address; + let token0_addr = &event.token0; + let token1_addr = &event.token1; + + output.add_many( + event.log_ordinal, + &vec![ + format!("pool:{pool_address}"), + format!("token:{token0_addr}"), + format!("token:{token1_addr}"), + format!("UniswapDayData:{day_id}"), + format!("PoolDayData:{day_id}:{pool_address}"), + format!("PoolHourData:{hour_id}:{pool_address}"), + format!("TokenDayData:{day_id}:{token0_addr}"), + format!("TokenDayData:{day_id}:{token1_addr}"), + format!("TokenHourData:{hour_id}:{token0_addr}"), + format!("TokenHourData:{hour_id}:{token1_addr}"), + ], + &BigInt::from(1 as i32), + ); + } +} +``` + +In the downstream modules consuming this store, you can query the store by key in `get` mode. Or, an even more powerful approach would be to filter needed store deltas by segments. `key` module of the `substreams` crates offers several helper functions. Using these functions you can extract the first/last/nth segment from a key: + +```rust +for delta in deltas.into_iter() { + let kind = key::first_segment(delta.get_key()); + let address = key::segment_at(delta.get_key(), 1); + // Do something for this kind and address +} +``` + +`key` module also provides corresponding `try_` methods that don't panic: +- `first_segment` & `try_first_segment` +- `last_segment` & `try_last_segment` +- `segment_at` & `try_segment_at` + +For a full example see [Uniswap V3 Substreams](https://github.com/streamingfast/substreams-uniswap-v3/blob/ca90fe3908a76905b43e05f0522e1e9338d88972/src/lib.rs#L1139-L1163) + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) +* [Key module documentation](https://docs.rs/substreams/latest/substreams/key/index.html) diff --git a/firehose/substreams/docs/developers-guide/creating-protobuf-schemas.md b/firehose/substreams/docs/developers-guide/creating-protobuf-schemas.md new file mode 100644 index 0000000..1413d7f --- /dev/null +++ b/firehose/substreams/docs/developers-guide/creating-protobuf-schemas.md @@ -0,0 +1,147 @@ +--- +description: StreamingFast Substreams protobuf schemas +--- + +# Protobuf schemas + +## Protobuf overview + +Substreams uses Google Protocol Buffers extensively. Protocol Buffers, also referred to as protobufs, are used as the API for data models specific to the different blockchains. Manifests contain references to the protobufs for your Substreams module. + +{% hint style="success" %} +**Tip**: Protobufs define the input and output for modules. +{% endhint %} + +Learn more about the details of Google Protocol Buffers in the official documentation provided by Google. + +**Google Protocol Buffer Documentation** + +[Learn more about Google Protocol Buffers](https://protobuf.dev/) in the official documentation provided by Google. + +**Google Protocol Buffer Tutorial** + +[Explore examples and additional learning material](https://protobuf.dev/programming-guides/proto3/) for Google Protocol Buffers provided by Google. + +### Protobuf definition for Substreams + +Define a protobuf model as [`proto:eth.erc721.v1.Transfers`](https://github.com/streamingfast/substreams-template/blob/develop/proto/erc721.proto) representing a list of ERC721 transfers. + +{% hint style="info" %} +**Note**: The `Transfers` protobuf in the Substreams Template example is located in the proto directory. +{% endhint %} + +{% code title="eth/erc721/v1/erc721.proto" lineNumbers="true" %} + +```protobuf +syntax = "proto3"; + +package eth.erc721.v1; + +message Transfers { + repeated Transfer transfers = 1; +} + +message Transfer { + bytes from = 1; + bytes to = 2; + uint64 token_id = 3; + bytes trx_hash = 4; + uint64 ordinal = 5; +} +``` + +{% endcode %} + +[View the `erc721.proto`](https://github.com/streamingfast/substreams-template/blob/develop/proto/erc721.proto) file in the official Substreams Template example repository. + +#### Identifying data types + +The ERC721 smart contract used in the Substreams Template example contains a `Transfer` event. You can use the event data through a custom protobuf. + +The protobuf file serves as the interface between the module handlers and the data being provided by Substreams. + +{% hint style="success" %} +**Tip**: Protobufs are platform-independent and are defined and used for various blockchains. + +- The ERC721 smart contracts used in the Substreams Template example are generic contracts used across many different Ethereum applications. +- The size and scope of the Substreams module dictates the number of and complexity of protobufs. + {% endhint %} + +The Substreams Template example extracts `Transfer` events from the [Bored Ape Yacht Club smart contract](https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d) which is located on the Ethereum blockchain. + +Several specific data types exist in the Ethereum smart contract ecosystem, some extending the ERC20 and ERC721 base modules. Complex protobufs are created and refined based on the various data types used across the different blockchains. + +{% hint style="success" %} +**Tip**_:_ The use of fully qualified protobuf file paths reduces the risk of naming conflicts when other community members build their [Substreams packages](../reference-and-specs/packages.md#dependencies). +{% endhint %} + +### Generating protobufs + +The [`substreams` CLI](../reference-and-specs/command-line-interface.md) is used to generate the associated Rust code for the protobuf. + +Notice the `protogen` command and Substreams manifest passed into the [`substreams` CLI](../reference-and-specs/command-line-interface.md). + +{% code overflow="wrap" %} + +```bash +substreams protogen ./substreams.yaml --exclude-paths="sf/ethereum,sf/substreams,google" +``` + +{% endcode %} + +The pairing code is generated and saved into the [`src/pb/eth.erc721.v1.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/pb/eth.erc721.v1.rs)Rust file. + +The [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/pb/mod.rs) file located in the `src/pb` directory of the Substreams Template example is responsible for exporting the freshly generated Rust code. + +{% code title="src/pb/mod.rs" overflow="wrap" lineNumbers="true" %} + +```rust +#[path = "eth.erc721.v1.rs"] +#[allow(dead_code)] +pub mod erc721; +``` + +{% endcode %} + +View the [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/pb/mod.rs) file in the repository. + +### Protobuf and Rust optional fields + +Protocol buffers define fields' type by using standard primitive data types, such as integers, booleans, and floats or a complex data type such as `message`, `enum`, `oneof` or `map`. View the [full list](https://developers.google.com/protocol-buffers/docs/proto#scalar) of types in the [Google Protocol Buffers documentation](https://developers.google.com/protocol-buffers/docs/overview). + +Any primitive data types in a message generate the corresponding Rust type,[`String`](https://doc.rust-lang.org/std/string/struct.String.html) for `string`, `u64` for `uint64,` and assign the default value of the corresponding Rust type if the field is not present in a message, an empty string for [`String`](https://doc.rust-lang.org/std/string/struct.String.html), 0 for integer types, `false` for `bool`. + +Rust generates the corresponding `message` type wrapped by an [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) enum type for fields referencing other complex `messages`. The [`None`](https://doc.rust-lang.org/std/option/) variant is used if the field is not present in the message. + +The [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) [`enum`](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html) is used to represent the presence through [`Some(x)`](https://doc.rust-lang.org/std/option/) or absence through [`None`](https://doc.rust-lang.org/std/option/) of a value in Rust. [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) allows developers to distinguish between a field containing a value versus a field without an assigned a value. + +{% hint style="info" %} +**Note**: The standard approach to represent nullable data in Rust is to wrap optional values in [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html). +{% endhint %} + +The Rust [`match`](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) keyword is used to compare the value of an [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) to a [`Some`](https://doc.rust-lang.org/std/option/) or [`None`](https://doc.rust-lang.org/std/option/) variant. Handle a type wrapped [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) in Rust by using: + +```rust +match person.Location { + Some(location) => { /* Value is present, do something */ } + None => { /* Value is absent, do something */ } +} +``` + +If you are only interested in finding the presence of a value, use the [`if let`](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html) statement to handle the [`Some(x)`](https://doc.rust-lang.org/std/option/) arm of the [`match`](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) code. + +```rust +if let Some(location) = person.location { + // Value is present, do something +} +``` + +If a value is present, use the [`.unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html) call on the [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) to obtain the wrapped data. You'll need to account for these types of scenarios if you control the creation of the messages yourself or if the field is documented as always being present. + +{% hint style="info" %} +**Note**: You need to be **absolutely sure** **the field is always defined**, otherwise Substreams panics and never completes, getting stuck on a block indefinitely. +{% endhint %} + +_**PROST!**_ is a tool for generating Rust code from protobuf definitions. [Learn more about `prost`](https://github.com/tokio-rs/prost) in the project's official GitHub repository. + +[Learn more about `Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) in the official Rust documentation. diff --git a/firehose/substreams/docs/developers-guide/creating-your-manifest.md b/firehose/substreams/docs/developers-guide/creating-your-manifest.md new file mode 100644 index 0000000..589e68d --- /dev/null +++ b/firehose/substreams/docs/developers-guide/creating-your-manifest.md @@ -0,0 +1,116 @@ +--- +description: StreamingFast Substreams manifest +--- + +# Manifest + +## Manifest Overview + +The manifest contains the details for the various aspects and components of a Substreams module. + +Every Substreams module contains one manifest. The manifest is a YAML-based file and provides vital insights into the blockchain being targeted, the design of the data flow, the names and types of modules, and locations and names for protobuf definitions. + +{% hint style="success" %} +**Tip**: Additional [detailed information for manifests](../reference-and-specs/manifests.md) is available in the Substreams reference section. +{% endhint %} + +## Example manifest + +The manifest from the [Substreams Template example](https://github.com/streamingfast/substreams-template) is used in the Substreams documentation. + +{% hint style="info" %} +**Note**: Learn more about Substreams manifests and different blockchains through the [list of maintained Substreams examples](../reference-and-specs/examples.md) provided by StreamingFast + +* The [example manifest](https://github.com/streamingfast/substreams-template/blob/develop/substreams.yaml) in the Substreams documentation is specific to the Ethereum blockchain. +{% endhint %} + +{% code title="substreams.yaml" overflow="wrap" lineNumbers="true" %} +```yaml +specVersion: v0.1.0 +package: + name: "substreams_template" + version: v0.1.0 + +protobuf: + files: + - erc721.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: map_transfers + kind: map + initialBlock: 12287507 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:eth.erc721.v1.Transfers + + - name: store_transfers + kind: store + initialBlock: 12287507 + updatePolicy: add + valueType: int64 + inputs: + - map: map_transfers +``` +{% endcode %} + +View the [`substreams.yaml`](https://github.com/streamingfast/substreams-template/blob/develop/substreams.yaml) file in the repository. + +## Manifest walkthrough + +{% hint style="success" %} +**Tip**: When writing and checking your `substreams.yaml` file, it may help to check your manifest against our [JSON schema](https://json-schema.org/) to ensure there are no problems. JSON schemas can be used in [Jetbrains](https://www.jetbrains.com/help/idea/json.html#ws\_json\_schema\_add\_custom) and [VSCode](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml). Our manifest schema can be seen [here](../../schemas/manifest-schema.json). +{% endhint %} + +### `protobuf.files` + +The `protobuf.files` contains a list of protobuf files for the current Substreams module. + +{% hint style="info" %} +**Note**: The Substreams Template references the Ethereum-specific `erc721.proto` protobuf while the Solana SPL Token Transfers example references the Solana-specific `solana_spl.proto`. +{% endhint %} + +### `protobuf.importPaths` + +The `protobuf.importPaths` contains the paths to the protobufs for the current Substreams module. + +## Module definitions + +The manifest defines a list of [modules](modules/) used in the Substreams module. + +The modules are Rust functions containing the business logic for the module. + +{% hint style="info" %} +**Note**: The manifest in the Substreams Template example lists two modules: `map_transfers` and `store_transfers.` The official naming convention for Substreams modules prefixes the module name by using `map_` or `store_` depending on the type. +{% endhint %} + +### **`map_transfers`** + +The `map_transfers` module extracts all ERC721 transfers related to a specific smart contract address. The module receives Ethereum blocks as [`sf.ethereum.type.v2.Block`](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto). + +The output for the `map_transfers` module is a list of ERC721 transfers. The business logic for `map_transfers` module is written as a Rust function. + +{% hint style="info" %} +**Note**: The `initialBlock` is set to `12287507` in the Substreams Template example because the first transfers of tokens originated from the contracts at that block. +{% endhint %} + +### **`store_transfers`** + +The `store_transfers` store module receives transfers in the blocks extracted by the mapper. The store is a `count` of ERC721 tokens for a holder. + +The inputs of the module are protobuf models defined as: `proto:eth.erc721.v1.Transfers`. + +The `eth.erc721.v1.Transfers` protobuf module represents a list of ERC721 transfers in a block. + +{% hint style="info" %} +**Note**: The `eth.erc721.v1.Transfers` protobuf module is also used as the output for the `map` module. +{% endhint %} + +The store's `valueType` is `int64` and the merge strategy is `add.` diff --git a/firehose/substreams/docs/developers-guide/gui.md b/firehose/substreams/docs/developers-guide/gui.md new file mode 100644 index 0000000..7aa26e0 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/gui.md @@ -0,0 +1,90 @@ +# Using the Substreams GUI + +When running Substreams through the CLI, you can use two different commands: `substreams run` and `substreams gui`. The `substreams run` command prints the output of the execution linearly for every block: + +
+ +However, this is not a useful approach when dealing with complex Substreams (i.e. with several modules and many blocks). The `substreams gui` command allows you to easily see the progress of a Substreams, move across blocks or search within the output. + +
+ +The Substreams GUI is a command-line utility, so you use the keys in your keyboard to interact with it. + +## Cheatsheet + +| Function | Keys | +|---|---| +| Switch screen (`Request`, `Progress`, `Output`) | `tab` | +| Restart | `r` | +| Quit | `q` | +| Navigate Blocks - Forward | `p` | +| Navigate Blocks - Backwards | `o` | +| Navigate Blocks - Go To | `=` + *block number* + `enter` | +| Navigate Modules - Forward | `i` | +| Navigate Modules - Backwards | `u` | +| Search | `/` + *text* + `enter` | +| Commnads information | `?` | + +## Launching the GUI + +In order to showcase the different options of the GUI, the [Ethereum Block Meta Substreams](https://github.com/streamingfast/substreams-eth-block-meta/) will be used as an example. +By running the following command, you are executing the `kv_out` module, which retrieves outputs the data in a key-value format. + +```bash +substreams gui -e mainnet.eth.streamingfast.io:443 https://github.com/streamingfast/substreams-eth-block-meta/releases/download/v0.5.1/substreams-eth-block-meta-v0.5.1.spkg kv_out --start-block 17712038 --stop-block +100 +``` + +In your command-line terminal, you should see something like: + +
+ +The `Progress` screen provides information about the Substreams execution, such as its status or the payload received. Once all the blocks have been consumed, the status is `Stream ended`. +There are two other main screens in the Substreams GUI: `Request` and `Output`. You can move to a different screen by using the `tab` key: + +
+ +You can restart the stream by pressing the `s` key. + +
+ +To quit the GUI, press the `q` key. + +## The Output Screen + +If you are in the `Progress` screen, press `tab` in your keyboard to move to the `Output` screen. In this screen, you can see the Protobuf output for every block. The image below shows the output for the block number `17712038` (the starting block). + +
+ +### Navigating Through Blocks + +You can see the output for other blocks by using the `o` and `p` keys. +The `o` key takes you to the following block, and the `p` takes you to the previous block. + +
+ + +If you want to jump to a specific block, you can press the `=` key and specify the block number. Then, just press `enter`. + +
+ + +### Navigating Through Modules + +You can see the output of a different module by using the `u` and `i` keys. In the following example, you go from the `kv_out` module (the module specified in the CLI command), to the `store_block_meta_end` module. + +
+ + +### Searching in the Output + +To search for a speciifc text in the output: + +1. Press the `/` key. +2. Introduce the text. +3. Press `enter`. + +In the following example, your search for the `SET` text. + +
+ + diff --git a/firehose/substreams/docs/developers-guide/installation-requirements.md b/firehose/substreams/docs/developers-guide/installation-requirements.md new file mode 100644 index 0000000..70bd9b1 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/installation-requirements.md @@ -0,0 +1,66 @@ +--- +description: StreamingFast Substreams dependency installation +--- + +# Dependency installation + +## Dependencies overview + +Substreams requires a number of different applications and tools. Instructions and links are provided to assist in the installation of the required dependencies for Substreams. + +{% hint style="success" %} +**Tip**: Instructions are also provided for cloud-based Gitpod setups. +{% endhint %} + +## Local installation + +### `substreams` CLI installation + +The [`substreams` CLI](../reference-and-specs/command-line-interface.md) is required and is the primary Substreams user interface. + +{% hint style="success" %} +**Tip**: Full setup instructions are available on the [installing the `substreams` CLI](../getting-started/installing-the-cli.md) page. +{% endhint %} + +### Rust installation + +Developing Substreams modules requires a working [Rust](https://www.rust-lang.org/) compilation environment. + +There are [several ways to install Rust](https://www.rust-lang.org/tools/install)**.** Install Rust through `curl` by using: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env # to configure your current shell +``` + +#### `wasm32-unknown-unknown` target + +Ensure you have the `wasm32-unknown-unknown` target installed on your Rust installation, if unsure, you can install it with: + +```bash +rustup target add wasm32-unknown-unknown +``` + +### Buf installation + +Buf simplifies the generation of typed structures in any language. Buf uses a remote builder executed on the Buf server, so an internet connection is required to generate Rust bindings from Protobuf definitions. + +Visit the [Buf website](https://buf.build/) for additional information and [installation instructions](https://docs.buf.build/installation). + +{% hint style="info" %} +**Note**_:_ [Substreams packages](../reference-and-specs/packages.md) and [Buf images](https://docs.buf.build/reference/images) are compatible. +{% endhint %} + +## Gitpod cloud-based environment + +Follow the steps to use [Gitpod](https://www.gitpod.io/) for Substreams: + +1. Copy the [substreams-template repository](https://github.com/streamingfast/substreams-template/generate). +2. Obtain a StreamingFast authentication key from: [https://app.streamingfast.io/](https://app.streamingfast.io/). +3. Create a [Gitpod](https://gitpod.io/) account. +4. Configure a `STREAMINGFAST_KEY` variable in the [Gitpod account settings](https://gitpod.io/variables). +5. Open the repository as a [Gitpod workspace](https://gitpod.io/workspaces). +6. The Substreams Template includes a `Makefile` simplifying the installation process. + 1. Running `make build` rebuilds the Substreams module. Run the command **after making changes to the code**. + 2. `make stream` runs the stream for a few blocks.\ + Edit `Makefile` to change the invocation as updates are made to the Substreams module. diff --git a/firehose/substreams/docs/developers-guide/modules/README.md b/firehose/substreams/docs/developers-guide/modules/README.md new file mode 100644 index 0000000..66c6bb8 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/modules/README.md @@ -0,0 +1,80 @@ +--- +description: StreamingFast Substreams modules overview +--- + +# Modules + +## Overview + +Modules are an important part of Substreams, offering hooks into the execution of the Substreams compute engine. You can create Substreams data manipulation and transformation strategies within modules. + +Modules are small pieces of Rust code running in a [WebAssembly (WASM)](https://webassembly.org/) virtual machine. They coexist within the stream of blocks sent by the Substreams compute engine, which arrives from a blockchain node. + +Modules have one or more inputs, which can be in the form of a `map` or `store`, or a `Block` or `Clock` object received from the blockchain's data source. + +{% embed url="https://mermaid.ink/svg/pako:eNp1kM0KwjAQhF8l7NkWvEbwIPUJ9NYUWZKtLTZJ2WwEEd_dCAr-4GFhd_h2GOYKNjoCDUfGeVD7ZmWCUqmvSQZiyr6Wy0z1eVlvpmhPbYqZLen_RKeqaq2EMaSe-OBxfhi-320Z_aF8_diYgxC3SSKT_tE7WIAn9ji6kvv6sDdQsngyoMvqqMc8iQETbgXNs0OhrRuLG-gep0QLwCxxdwkWtHCmF9SMWGrwT-p2B02rZZY" %} +Substreams modules data interaction diagram +{% endembed %} + +The diagram shows how the `transfer_map` module extracts the transfers in a `Block` and tracks the total number of transfers. + +{% hint style="info" %} +**Note:** You can use multiple inputs in blockchains because they are clocked, which allows for synchronization between multiple execution streams and improved performance compared to conventional streaming engines. +{% endhint %} + +As seen in the `counters` `store` example diagram, modules can also take in multiple inputs. In this case, two modules feed into a `store`, effectively tracking multiple `counters`. + +{% embed url="https://mermaid.ink/svg/pako:eNqdkE1qAzEMha9itE4GsnWgi5KcINmNh6LamozJeGxsuSGE3L1KW1PIptCdnnjv088NbHQEGk4Z06SOu61ZlHqfoz33JdZsSasydsQTZaqh42ui7mPTvT4cg1qvX1TA9HbxPLmMF5zLv_KOUiyev8JPvF60fm5-J22sC1MufeGYZVDTQ8M07C-jdf4AwAoC5YDeyWtuD5wBOSGQAS2loxHrzAbMchdrTQ6Z9s4LBfQo-9EKsHI8XBcLmnOlZtp5lE-HH9f9EylZic0" %} +Multiple module inputs diagram +{% endembed %} + +Every time a new `Block` is processed, all of the modules are executed as a directed acyclic graph (DAG). + +{% hint style="info" %} +**Note:** The protocol's Block protobuf model always serves as the top-level data source and executes deterministically. +{% endhint %} + +### Single output + +Modules have a single typed output, which is typed to inform consumers of the types of data to expect and how to interpret the bytes being sent. + +{% hint style="success" %} +**Tip**: In subsequent modules, input from one module's data output is used to form a chain of data flow from module to module. +{% endhint %} + +### `map` versus `store` modules + +To develop most non-trivial Substreams, you will need to use multiple `map` and `store` modules. The specific number, responsibilities, and communication methods for these modules will depend on the developer's specific goals for the Substreams development effort. + +The two module types are commonly used together to construct the directed acyclic graph (DAG) outlined in the Substreams manifest. The two module types are very different in their use and how they work. Understanding these differences is vital for harnessing the full power of Substreams. + +### `map` modules + +`map` modules are used for data extraction, filtering, and transformation. They should be used when direct extraction is needed avoiding the need to reuse them later in the DAG. + +To optimize performance, you should use a single `map` module instead of multiple `map` modules to extract single events or functions. It is more efficient to perform the maximum amount of extraction in a single top-level `map` module and then pass the data to other Substreams modules for consumption. This is the recommended, simplest approach for both backend and consumer development experiences. + +Functional `map` modules have several important use cases and facts to consider, including: + +* Extracting model data from an event or function's inputs. +* Reading data from a block and transforming it into a custom protobuf structure. +* Filtering out events or functions for any given number of contracts. + +### `store` modules + +`store` modules are used for the aggregation of values and to persist state that temporarily exists across a block. + +{% hint style="warning" %} +**Important:** Stores should not be used for temporary, free-form data persistence. +{% endhint %} + +Unbounded `store` modules are discouraged. `store` modules shouldn't be used as an infinite bucket to dump data into. + +Notable facts and use cases for working `store` modules include: + +* `store` modules should only be used when reading data from another downstream Substreams module. +* `store` modules cannot be output as a stream, except in development mode. +* `store` modules are used to implement the Dynamic Data Sources pattern from Subgraphs, keeping track of contracts created to filter the next block with that information. +* Downstream of the Substreams output, do not use `store` modules to query anything from them. Instead, use a sink to shape the data for proper querying. + + diff --git a/firehose/substreams/docs/developers-guide/modules/inputs.md b/firehose/substreams/docs/developers-guide/modules/inputs.md new file mode 100644 index 0000000..3849f47 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/modules/inputs.md @@ -0,0 +1,149 @@ +--- +description: StreamingFast Substreams module inputs +--- + +# Inputs + +## `inputs` overview + +Modules receive `inputs` of three types: + +* `source` +* `map` +* `store` +* `params` + +## Input type `source` + +An `inputs` of type `source` represents a chain-specific, Firehose-provisioned protobuf object. Learn more about the supported protocols and their corresponding message types in the [Chains and inputs documentation](../../reference-and-specs/chains-and-endpoints.md). + +{% hint style="info" %} +**Note**: The different blockchains reference different `Block` objects. For example, Solana references its `Block` object as `sf.solana.type.v1.Block`. Ethereum-based Substreams modules specify `sf.ethereum.type.v2.Block.` +{% endhint %} + +The `source` `inputs` type \_\_ is defined in the Substreams manifest. It is important to specify the correct `Block` object for the chain. + +```yaml +modules: +- name: my_mod + inputs: + - source: sf.ethereum.type.v2.Block +``` + +#### `Clock` object + +The `sf.substreams.v1.Clock` object is another source type available on any of the supported chains. + +The `sf.substreams.v1.Clock` represents: + +* `Block` `number` +* `Block` `ID` +* `Block` `timestamp` + +## Input type `params` + +An `inputs` of type `params` represents a parameterizable module input. Those parameters can be specified either: + +* in the `params` section of the manifest, +* on the command-line (using `substreams run -p` for instance), +* by tweaking the protobuf objects directly when consuming from your favorite language + +See the [Manifest's `params` manifest section of the Reference & specs](../../reference-and-specs/manifests.md#params) for more details. + +## Input type `map` + +An input of type `map` represents the output of another `map` module. It defines a parent-child relationship between modules. + +The object's type is defined in the [`output.type`](../../reference-and-specs/manifests.md#modules-.output) attribute of the `map` module. + +{% hint style="warning" %} +**Important**: The graph built by input dependencies is a Directed Acyclic Graph, which means there can be no circular dependencies. +{% endhint %} + +Define the `map` input type in the manifest and choose a name for the `map` reflecting the logic contained within it. + +{% code title="manifest excerpt" %} +```yaml + inputs: + - map: my_map +``` +{% endcode %} + +Learn more about `maps` in the [Modules](./) section. + +## Input type `store` + +A `store inputs` type represents the state of another `store` used by the Substreams module being created. + +The developer defines the `store` `inputs` type in the Substreams manifest and gives the `store` a descriptive name that reflects the logic contained within it, similar to a `map`. + +Store modules are set to `get` mode by default: + +{% code title="manifest excerpt" %} +```yaml + inputs: + - store: my_store # defaults to mode: get +``` +{% endcode %} + +Alternatively, set `stores` to `deltas` mode by using: + +{% code title="manifest excerpt" %} +```yaml + inputs: + - store: my_delta_store + mode: deltas +``` +{% endcode %} + +### Store access `mode` + +Substreams uses two types of `mode` for modules: + +* `get` +* `delta` + +### Store constraints + +* A `store` can only receive `inputs` as read-only. +* A `store` cannot depend on itself. + +### `get` mode + +`get` mode provides a key-value store readily queryable and guaranteed to be in sync with the block being processed. + +{% hint style="success" %} +**Tip**: `get` `mode` is the default mode for modules. +{% endhint %} + +### `delta` mode + +`delta` `mode` modules are [protobuf objects](../../../pb/sf/substreams/v1/substreams.proto#L124) containing all the changes occurring in the `store` module available in the same block. + +`delta` mode enables you to loop through keys and decode values mutated in the module. + +#### `store` `deltas` + +The protobuf model for `StoreDeltas` is defined by using: + +{% code overflow="wrap" %} +```protobuf +message StoreDeltas { + repeated StoreDelta deltas = 1; +} + +message StoreDelta { + enum Operation { + UNSET = 0; + CREATE = 1; + UPDATE = 2; + DELETE = 3; + } + Operation operation = 1; + uint64 ordinal = 2; + string key = 3; + bytes old_value = 4; + bytes new_value = 5; +} +``` +{% endcode %} diff --git a/firehose/substreams/docs/developers-guide/modules/outputs.md b/firehose/substreams/docs/developers-guide/modules/outputs.md new file mode 100644 index 0000000..8bf5541 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/modules/outputs.md @@ -0,0 +1,20 @@ +--- +description: StreamingFast Substreams module outputs +--- + +# Output + +## Output overview + +Substreams `map` modules support a single `output`. The `output` must be a protobuf populated by data acquired inside the `map` module. If the module intends to provide a basic `output` type of a single value, such as a `String` or `bool`, a protobuf is still required. The single value needs to be wrapped in a protobuf for use as the `output` value from a `map` module. + +{% hint style="info" %} +**Note:** `store` modules **cannot** define an `output`. +{% endhint %} + +An `output` object has a `type` attribute defining the `type` of the `output` for the `map` module. The `output` definition is located in the Substreams manifest, within the module definition. + +```yaml +output: + type: proto:eth.erc721.v1.Transfers +``` diff --git a/firehose/substreams/docs/developers-guide/modules/setting-up-handlers.md b/firehose/substreams/docs/developers-guide/modules/setting-up-handlers.md new file mode 100644 index 0000000..01c62a3 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/modules/setting-up-handlers.md @@ -0,0 +1,179 @@ +--- +description: StreamingFast Substreams module handlers +--- + +# Module handlers + +## Module handlers overview + +To begin creating the custom module handlers initialize a new Rust project by using the `cargo` `init` command. + +```bash +# Creates a empty Rust project suitable for WASM compilation +cargo init --lib +``` + +Update the generated [`Cargo.toml`](https://github.com/streamingfast/substreams-template/blob/develop/Cargo.toml) file by using: + +{% code title="Cargo.toml" overflow="wrap" lineNumbers="true" %} +```rust +[package] +name = "substreams-template" +version = "0.1.0" +description = "Substream template demo project" +edition = "2021" +repository = "https://github.com/streamingfast/substreams-template" + +[lib] +name = "substreams" +crate-type = ["cdylib"] + +[dependencies] +ethabi = "17" +hex-literal = "0.3.4" +prost = "0.11" +# Use latest from https://crates.io/crates/substreams +substreams = "0.5" +# Use latest from https://crates.io/crates/substreams-ethereum +substreams-ethereum = "0.9" + +# Required so ethabi > ethereum-types build correctly under wasm32-unknown-unknown +[target.wasm32-unknown-unknown.dependencies] +getrandom = { version = "0.2", features = ["custom"] } + +[build-dependencies] +anyhow = "1" +substreams-ethereum = "0.8" + +[profile.release] +lto = true +opt-level = 's' +strip = "debuginfo" +``` +{% endcode %} + +View the [`Cargo.toml`](https://github.com/streamingfast/substreams-template/blob/develop/Cargo.toml) file in the repository. + +You compile the Rust code into [WebAssembly (WASM)](https://webassembly.org/), a binary instruction format that runs in a virtual machine. The compilation process generates a .so file. + +### **`Cargo.toml` configuration file breakdown** + +Build the Rust dynamic system library after the `package` by using: + +{% code title="Cargo.toml excerpt" %} +```toml +... + +[lib] +crate-type = ["cdylib"] +``` +{% endcode %} + +The next definition in the [`Cargo.toml`](https://github.com/streamingfast/substreams-template/blob/develop/Cargo.toml) configuration file is for `dependencies`. + +{% hint style="info" %} +**Note**: Module handlers compile down to a WASM module. Explicitly specify the target`asm32-unknown-unknown` by using `[target.wasm32-unknown-unknown.dependencies]`. +{% endhint %} + +#### `ethabi` + +The [`ethabi` crate ](https://crates.io/crates/ethabi)is used to decode events from the application binary interface (ABI) and is required for `substreams-ethereum` ABI capabilities. + +#### `hex-literal` + +The [`hex-literal` crate ](https://crates.io/crates/hex-literal)is used to define bytes from hexadecimal string literals at compile time. + +#### `substreams` + +The [`substreams` crate](https://docs.rs/substreams/latest/substreams/) offers all the basic building blocks for the module handlers. + +#### `substreams-ethereum` + +The [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum-core) offers all the Ethereum constructs including blocks, transactions, eth, and useful ABI decoding capabilities. + +Because code is being built by WASM output it's necessary to configure Rust to match the correct architecture. Create and add a [`rust-toolchain.toml`](https://github.com/streamingfast/substreams-template/blob/develop/rust-toolchain.toml) configuration file at the root of your Substreams directory. + +### Rust toolchain + +{% code title="rust-toolchain.toml" overflow="wrap" lineNumbers="true" %} +```toml +[toolchain] +channel = "1.65" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] +``` +{% endcode %} + +View the [`rust-toolchain.toml`](https://github.com/streamingfast/substreams-template/blob/develop/rust-toolchain.toml) file in the repository. + +Build the code by using: + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +### **Rust build target** + +When running `cargo build` the target is set to `wasm32-unknown-unknown`, which is important because it specifies the goal is to generate compiled WASM code. + +To avoid having to specify the target `wasm32-unknown-unknown` for every `cargo` command, create a `config.toml` configuration file in the `.cargo` directory at the root of the Substreams project. The `config.toml` configuration file allows the target to be set automatically for all `cargo` commands. + +The content for the `config.toml` configuration file is: + +{% code title=".cargo/config.toml" %} +```toml +[build] +target = "wasm32-unknown-unknown" +``` +{% endcode %} + +The `config.toml` configuration file updates the default `cargo build` command to `cargo build --target wasm32-unknown-unknown` eliminating the need to specify the target manually every time you build. + +### ABI generation + +The [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum) offers an [`Abigen`](https://docs.rs/substreams-ethereum-abigen/latest/substreams_ethereum_abigen/build/struct.Abigen.html) API to generate Rust types from a smart contract's ABI. + +Place the contract's [ABI JSON file](../../.gitbook/assets/erc721.json) in the Substreams project in the `abi` directory. + +### **Rust build script** + +Before building a package, Cargo compiles a build script into an executable if it has not already been built. The build script runs as part of the build process responsible for performing a variety of tasks. + +To cause Cargo to compile and run a script before building a package, place a file called `build.rs` in the root of the package. + +Create a [`build.rs`](https://github.com/streamingfast/substreams-template/blob/develop/build.rs) build script file in the root of the Substreams project by using: + +{% code title="build.rs" overflow="wrap" lineNumbers="true" %} +```rust +use anyhow::{Ok, Result}; +use substreams_ethereum::Abigen; + +fn main() -> Result<(), anyhow::Error> { + Abigen::new("ERC721", "abi/erc721.json")? + .generate()? + .write_to_file("src/abi/erc721.rs")?; + + Ok(()) +} +``` +{% endcode %} + +View the [`build.rs`](https://github.com/streamingfast/substreams-template/blob/develop/build.rs) file in the repository. + +Run the build script to generate the ABI directory and files. + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +Create a [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/abi/mod.rs) export file in the ABI directory, which is created by the Rust build process. The [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/abi/mod.rs) export file is responsible for exporting the generated Rust code. + +{% code title="src/abi/mod.rs" lineNumbers="true" %} +```rust +pub mod erc721; +``` +{% endcode %} + +View the [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/abi/mod.rs) file in the repository. + +You're now ready to [write the module handlers](writing-module-handlers.md). diff --git a/firehose/substreams/docs/developers-guide/modules/types.md b/firehose/substreams/docs/developers-guide/modules/types.md new file mode 100644 index 0000000..623ed0f --- /dev/null +++ b/firehose/substreams/docs/developers-guide/modules/types.md @@ -0,0 +1,115 @@ +--- +description: StreamingFast Substreams module types +--- + +# Module types + +## Module types overview + +Substreams uses two types of modules, `map` and `store`. + +* `map` modules are functions receiving bytes as input and output. These bytes are encoded protobuf messages. +* `store` modules are stateful, saving and tracking data through the use of key-value stores. + +### `store` modules + +`store` modules write to key-value stores. + +{% hint style="info" %} +**Note**: To ensure successful and proper parallelization can occur, `store` modules are not permitted to read any of their own data or values. +{% endhint %} + +Stores declaring their own data types expose methods capable of mutating keys within the `store`. + +### Core principle usage of stores + +* Do not save keys in stores **unless they are going to be read by a downstream module**. Substreams stores are a way to aggregate data, but they are **not meant to be a storage layer**. +* Do not save all transfers of a chain in a `store` module, rather, output them in a `map` and have a downstream system store them for querying. + +There are limitations impose on store usage. Specifically, each key/value entry must be smaller than 10MiB while a store cannot exceed 1GiB total. Keys being string, each character in the key account for 1 byte of storage space. + +### Important store properties + +The two important store properties are `valueType,`and `updatePolicy`. + +#### `valueType` property + +The `valueType` property instructs the Substreams runtime of the data to be saved in the `stores`. + +| Value | Description | +| ------------------------------ | -------------------------------------------------------------------------------- | +| `bytes` | A basic list of bytes | +| `string` | A UTF-8 string | +| `proto:fully.qualified.Object` | Decode bytes by using the protobuf definition `fully.qualified.Object` | +| `int64` | A string-serialized integer by using int64 arithmetic operations | +| `float64` | A string-serialized floating point value, used for float64 arithmetic operations | +| `bigint` | A string-serialized integer, supporting precision of any depth | +| `bigfloat` | A string-serialized floating point value, supporting precision up to 100 digits | + +#### `updatePolicy` property + +The `updatePolicy` property determines what methods are available in the runtime. + +The `updatePolicy` also defines the merging strategy for identical keys found in two contiguous stores produced through parallel processing. + +| Method | Supported Value Types | Merge strategy\* | +| ------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `set` | `bytes`, `string`, `proto:...` | The last key wins | +| `set_if_not_exists` | `bytes`, `string`, `proto:...` | The first key wins | +| `add` | `int64`, `bigint`, `bigfloat`, `float64` | Values are summed up | +| `min` | `int64`, `bigint`, `bigfloat`, `float64` | The lowest value is kept | +| `max` | `int64`, `bigint`, `bigfloat`, `float64` | The highest value is kept | +| `append` | `string`, `bytes` | Both keys are concatenated in order. Appended values are limited to 8Kb. Aggregation pattern examples are available in the [`lib.rs`](https://github.com/streamingfast/substreams-uniswap-v3/blob/develop/src/lib.rs#L760) file | + +{% hint style="success" %} +**Tip**: All update policies provide the `delete_prefix` method. +{% endhint %} + +The merge strategy is **applied during parallel processing**. + +* A module has built two partial stores containing keys for segment A, blocks 0-1000, and segment B, blocks 1000-2000, and is prepared to merge them into a complete store. +* The complete store is represented acting as if the processing was done in a linear fashion, starting at block 0 and proceeding up to block 2000. + +{% hint style="warning" %} +**Important**_**:** _ To preserve the parallelization capabilities of the system, **Substreams is not permitted to read what it has written or read from a `store` actively being written**. + +A downstream module is created to read from a store by using one of its inputs to point to the output of the `store` module. +{% endhint %} + +### Ordinals + +Ordinals allow a key-value store to have multiple versions of a key within a single block. The `store` APIs contain different methods of `ordinal` or `ord`. + +For example, the price for a token can change after transaction B and transaction D, and a downstream module might want to know the value of a key before transaction B **and between B and D**_._ + +{% hint style="warning" %} +**Important**: Ordinals **must be set every time a key is set** and **you can only set keys in increasing ordinal order**, or by using an ordinal equal to the previous. +{% endhint %} + +In situations where a single key for a block is required and ordering in the store is not important, the ordinal uses a value of zero. + +### `store` modes + +You can consume data in one of two modes when declaring a `store` as an input to a module. + +#### `get mode` + +The `get mode` function provides the module with a key-value store that is guaranteed to be synchronized up to the block being processed. It's possible to query stores by using the `get_at`, `get_last` and `get_first` methods. + +{% hint style="success" %} +**Tip:** Lookups are local, in-memory, and **extremely high-speed**. +{% endhint %} + +The definition of `store` method behavior is: + +* The `get_last` method is the fastest because it queries the store directly. +* The `get_first` method first goes through the current block's deltas in reverse order, before querying the store, in case the key being queried was mutated in the block. +* The `get_at` method unwinds deltas up to a specific ordinal, ensuring values for keys set midway through a block are still reachable. + +#### `deltas mode` + +`deltas` mode provides the module with **all the changes** occurring in the source `store` module. Updates, creates, and deletes of the keys mutated during the block processing become available. + +{% hint style="info" %} +**Note:** When a `store` is set as an input to the module, it is read-only and you cannot modify, update or mutate them. +{% endhint %} diff --git a/firehose/substreams/docs/developers-guide/modules/writing-module-handlers.md b/firehose/substreams/docs/developers-guide/modules/writing-module-handlers.md new file mode 100644 index 0000000..a1895c9 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/modules/writing-module-handlers.md @@ -0,0 +1,287 @@ +--- +description: StreamingFast Substreams module handler creation +--- + +# Module handler creation + +## Module handler creation overview + +After generating the ABI and protobuf Rust code, you need to write the handler code. Save the code into the `src` directory and use the filename [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs). + +{% code title="src/lib.rs" overflow="wrap" lineNumbers="true" %} +```rust +mod abi; +mod pb; +use hex_literal::hex; +use pb::erc721; +use substreams::prelude::*; +use substreams::{log, store::StoreAddInt64, Hex}; +use substreams_ethereum::{pb::eth::v2 as eth, NULL_ADDRESS}; + +// Bored Ape Club Contract +const TRACKED_CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); + +substreams_ethereum::init!(); + +/// Extracts transfers events from the contract +#[substreams::handlers::map] +fn map_transfers(blk: eth::Block) -> Result { + Ok(erc721::Transfers { + transfers: blk + .events::(&[&TRACKED_CONTRACT]) + .map(|(transfer, log)| { + substreams::log::info!("NFT Transfer seen"); + + erc721::Transfer { + trx_hash: log.receipt.transaction.hash.clone(), + from: transfer.from, + to: transfer.to, + token_id: transfer.token_id.low_u64(), + ordinal: log.block_index() as u64, + } + }) + .collect(), + }) +} + +/// Store the total balance of NFT tokens for the specific TRACKED_CONTRACT by holder +#[substreams::handlers::store] +fn store_transfers(transfers: erc721::Transfers, s: StoreAddInt64) { + log::info!("NFT holders state builder"); + for transfer in transfers.transfers { + if transfer.from != NULL_ADDRESS { + log::info!("Found a transfer out {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.from), -1); + } + + if transfer.to != NULL_ADDRESS { + log::info!("Found a transfer in {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.to), 1); + } + } +} + +fn generate_key(holder: &Vec) -> String { + return format!("total:{}:{}", Hex(holder), Hex(TRACKED_CONTRACT)); +} +``` +{% endcode %} + +View the [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs) file in the repository. + +### **Module handler breakdown** + +The logical sections of the [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs) file are outlined and described in greater detail. + +Import the necessary modules. + +{% code title="lib.rs excerpt" overflow="wrap" %} +```rust +mod abi; +mod pb; +use hex_literal::hex; +use pb::erc721; +use substreams::{log, store, Hex}; +use substreams_ethereum::{pb::eth::v2 as eth, NULL_ADDRESS, Event}; +``` +{% endcode %} + +Store the tracked contract in the example in a `constant`. + +{% code title="lib.rs excerpt" %} +```rust +const TRACKED_CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); +``` +{% endcode %} + +Define the `map` module in the Substreams manifest. + +{% code title="manifest excerpt" %} +```yaml +- name: map_transfers + kind: map + initialBlock: 12287507 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:eth.erc721.v1.Transfers +``` +{% endcode %} + +Notice the: `name: map_transfers`, the module in the manifest name matches the handler function name. Also notice, there is one [`inputs`](inputs.md) and one [`output`](outputs.md) definition. + +The [`inputs`](inputs.md) uses the standard Ethereum Block, `sf.ethereum.type.v2.Block,` provided by the [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum-core). + +The output uses the `type` `proto:eth.erc721.v1.Transfers` which is a custom protobuf definition provided by the generated Rust code. + +The function signature produced resembles: + +{% code title="lib.rs excerpt" %} +```rust +#[substreams::handlers::map] +fn map_transfers(blk: eth::Block) -> Result { + ... +} +``` +{% endcode %} + +### **Rust macros** + +Did you notice the `#[substreams::handlers::map]` on top of the function? It is a [Rust macro](https://doc.rust-lang.org/book/ch19-06-macros.html) provided by the [`substreams` crate](https://docs.rs/substreams/latest/substreams/). + +The macro decorates the handler function as a `map.` Define `store` modules by using the syntax `#[substreams::handlers::store]`. + +### Module handler function + +The `map` extracts ERC721 transfers from a _`Block`_ object. The code finds all the `Transfer` `events` emitted by the tracked smart contract. As the events are encountered they are decoded into `Transfer` objects. + +{% code title="lib.rs excerpt" overflow="wrap" %} +```rust +/// Extracts transfers events from the contract +#[substreams::handlers::map] +fn map_transfers(blk: eth::Block) -> Result { + Ok(erc721::Transfers { + transfers: blk + .events::(&[&TRACKED_CONTRACT]) + .map(|(transfer, log)| { + substreams::log::info!("NFT Transfer seen"); + + erc721::Transfer { + trx_hash: log.receipt.transaction.hash.clone(), + from: transfer.from, + to: transfer.to, + token_id: transfer.token_id.low_u64(), + ordinal: log.block_index() as u64, + } + }) + .collect(), + }) +} +``` +{% endcode %} + +Define the `store` module in the Substreams manifest. + +{% code title="manifest excerpt" %} +```yaml +- name: store_transfers + kind: store + initialBlock: 12287507 + updatePolicy: add + valueType: int64 + inputs: + - map: map_transfers +``` +{% endcode %} + +{% hint style="info" %} +**Note:** `name: store_transfers` corresponds to the handler function name. +{% endhint %} + +The `inputs` corresponds to the `output` of the `map_transfers` `map` module typed as `proto:eth.erc721.v1.Transfers`. The custom protobuf definition is provided by the generated Rust code. + +{% code title="lib.rs excerpt" %} +```rust +#[substreams::handlers::store] +fn store_transfers(transfers: erc721::Transfers, s: store::StoreAddInt64) { + ... +} +``` +{% endcode %} + +{% hint style="info" %} +**Note**: the `store` always receives itself as its own last input. +{% endhint %} + +In the example the `store` module uses an `updatePolicy` set to `add` and a `valueType` set to `int64` yielding a writable `store` typed as `StoreAddInt64`. + +{% hint style="info" %} +**Note**: **Store types** + +* The writable `store` is always the last parameter of a `store` module function. +* The `type` of the writable `store` is determined by the `updatePolicy` and `valueType` of the `store` module. +{% endhint %} + +The goal of the `store` in the example is to track a holder's current NFT `count` for the smart contract provided. The tracking is achieved through the analysis of `Transfers`. + +**`Transfers` in detail** + +* If the "`from`" address of the `transfer` is the `null` address (`0x0000000000000000000000000000000000000000`) and the "`to`" address is not the `null` address, the "`to`" address is minting a token, which results in the `count` being incremented. +* If the "`from`" address of the `transfer` is not the `null` address and the "`to`" address is the `null` address, the "`from`" address has burned a token, which results in the `count` being decremented. +* If both the "`from`" and the "`to`" address is not the `null` address, the `count` is decremented from the "`from`" address and incremented for the "`to`" address. + +### `store` concepts + +There are three important things to consider when writing to a `store`: + +* `ordinal` +* `key` +* `value` + +#### `ordinal` + +`ordinal` represents the order in which the `store` operations are applied. + +The `store` handler is called once per `block.` + +The `add` operation may be called multiple times during execution, for various reasons such as discovering a relevant event or encountering a call responsible for triggering a method call. + +{% hint style="info" %} +**Note**: Blockchain execution models are linear. Operations to add must be added linearly and deterministically. +{% endhint %} + +If an `ordinal` is specified, the order of execution is guaranteed. In the example, when the `store` handler is executed by a given set of `inputs`, such as a list of `Transfers`, it emits the same number of `add` calls and `ordinal` values for the execution. + +#### `key` + +Stores are [key-value stores](https://en.wikipedia.org/wiki/Key%E2%80%93value\_database). Care needs to be taken when crafting a `key` to ensure it is unique **and flexible**. + +If the `generate_key` function in the example returns the `TRACKED_CONTRACT` address as the `key`, it is not unique among different token holders. + +The `generate_key` function returns a unique `key` for holders if it contains only the holder's address. + +{% hint style="warning" %} +**Important**: Issues are expected when attempting to track multiple contracts. +{% endhint %} + +#### `value` + +The value being stored. The `type` is dependent on the `store` `type` being used. + +{% code title="lib.rs excerpt" overflow="wrap" %} +```rust +#[substreams::handlers::store] +fn store_transfers(transfers: erc721::Transfers, s: StoreAddInt64) { + log::info!("NFT holders state builder"); + for transfer in transfers.transfers { + if transfer.from != NULL_ADDRESS { + log::info!("Found a transfer out {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.from), -1); + } + + if transfer.to != NULL_ADDRESS { + log::info!("Found a transfer in {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.to), 1); + } + } +} + +fn generate_key(holder: &Vec) -> String { + return format!("total:{}:{}", Hex(holder), Hex(TRACKED_CONTRACT)); +} +``` +{% endcode %} + +### Summary + +Both handler functions have been written. + +One handler function for extracting relevant _`transfers`_, and a second to store the token count per recipient. + +Build Substreams to continue the setup process. + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +The next step is to run Substreams with all of the changes made by using the generated code. diff --git a/firehose/substreams/docs/developers-guide/overview.md b/firehose/substreams/docs/developers-guide/overview.md new file mode 100644 index 0000000..e28e2fd --- /dev/null +++ b/firehose/substreams/docs/developers-guide/overview.md @@ -0,0 +1,33 @@ +--- +description: StreamingFast Substreams Developer's guide overview +--- + +# Overview + +## Developer's guide overview + +The Substreams Developer's guide explains the process of building a Substreams module responsible for tracking the ERC721 holder count for an Ethereum smart contract. + +{% hint style="success" %} +**Tip**_:_ The Substreams Developer's guide is focused on an Ethereum Substreams module. The development tasks and workflow are independent of the blockchain selected for your project. +{% endhint %} + +Example Substreams modules for Solana, Aptos, and other chains are available for reference and study. [Find a full list on the examples page](https://substreams.streamingfast.io/reference-and-specs/examples) of the Substreams documentation. + +Read through the [fundamentals](../concepts-and-fundamentals/fundamentals.md) to get a good grasp of what Substreams is first. + +### Substreams examples + +StreamingFast and the Substreams community have provided an assortment of different examples that you can use to explore and learn more. + +#### Substreams template for Developer's guide + +The accompanying template for the Developer's guide is[ ](https://github.com/streamingfast/substreams-template)conveniently located in the [official Substreams Template Github repository](https://github.com/streamingfast/substreams-template). + +#### Substreams for Solana + +A basic [Substreams module for the Solana blockchain](https://github.com/streamingfast/substreams-solana-quickstart) demonstrates how to reference the chain-specific SPKG, endpoints, and data models. + +### StreamingFast Discord for Substreams + +Talk to the StreamingFast core developers on Discord to [find answers to questions and get assistance](https://discord.gg/mYPcRAzeVN) for authentication and other Substreams issues. diff --git a/firehose/substreams/docs/developers-guide/parallel-execution.md b/firehose/substreams/docs/developers-guide/parallel-execution.md new file mode 100644 index 0000000..3c46300 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/parallel-execution.md @@ -0,0 +1,37 @@ +--- +description: StreamingFast Substreams parallel execution +--- + +# Parallel execution + +Parallel execution is the process of a Substreams module's code executing multiple segments of blockchain data simultaneously in a forward or backward direction. Substreams modules can be executed in parallel, rapidly producing data for consumption in end-user applications. Parallel execution enables Substreams' highly efficient blockchain data processing capabilities. + +Parallel execution occurs when a requested module's start block is further back in the blockchain's history than the requested start block. For example, if a module starts at block 12,000,000 and a user requests data at block 15,000,000, parallel execution is used. This applies to both the development and production modes of Substreams operation. + +Parallel execution addresses the problem of the slow single linear execution of a module. Instead of running a module in a linear fashion, one block after the other without leveraging full computing power, N number of workers are executed over a different segment of the chain. It means data can be pushed back to the user N times faster than cases using a single worker. + +The server will define an execution schedule and take the module's dependencies into consideration. The server's execution schedule is a list of pairs of (`module, range`), where range contains `N` blocks. This is a configurable value set to 25K blocks, on the server. + +The single map_transfer module will fulfill a request from 0 - 75,000. The server's execution plan returns the results of `[(map_transfer, 0 -> 24,999), (map_transfer, 25,000 -> 49,999), (map_transfer, 50,000 -> 74,999)]`. + +The three pairs will be simultaneously executed by the server handling caching of the output of the store. For stores, an additional step will combine the store keys across multiple segments producing a unified and linear view of the store's state. + +Assuming a chain has 16,000,000 blocks, which translates to 640 segments of 25K blocks. The server currently has a limited amount of concurrency. In theory, 640 concurrent workers could be spawned. In practice, the number of concurrent workers depends on the capabilities of the service provider. For the production endpoint, StreamingFast sets the concurrency to 15 to ensure fair usage of resources for the free service. + +## Production versus development mode for parallel execution + +The amount of parallel execution for the two modes is illustrated in the diagram. Production mode results in more parallel processing than development mode for the requested range. In contrast, development mode consists of more linear processing. Another important note is, forward parallel execution only occurs in production mode. + +

Substreams production versus development mode for parallel execution diagram

+ +## Backward and forward parallel execution steps + +The two steps involved during parallel execution are **backward execution and forward execution**. + +Backward parallel execution consists of executing in parallel block ranges, from the module's initial block, up to the start block of the request. If the start block of the request matches the module's initial block no backward execution is performed. + +Forward parallel execution consists of executing in parallel block ranges from the start block of the request up to the last known final block, also called an irreversible block, or the stop block of the request depending on which is smaller. Forward parallel execution significantly improves the performance of Substreams. + +Backward parallel execution will occur in both development and production modes. + +Forward parallel execution only occurs in production mode. diff --git a/firehose/substreams/docs/developers-guide/running-substreams.md b/firehose/substreams/docs/developers-guide/running-substreams.md new file mode 100644 index 0000000..0fe7859 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/running-substreams.md @@ -0,0 +1,112 @@ +--- +description: Running StreamingFast Substreams for the first time +--- + +# Running Substreams + +## Overview + +After a successful build, start Substreams by using the [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) command: + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 \ + substreams.yaml \ + map_transfers \ + --start-block 12292922 \ + --stop-block +1 +``` + +### Explanation + +#### Substreams `run` + +First, start the [`substreams` CLI](../reference-and-specs/command-line-interface.md) passing it a [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) command. + +#### Firehose URI + +The server address is required by Substreams to connect to for data retrieval. The data provider for Substreams is located at the address, which is a running Firehose instance.\ +`-e mainnet.eth.streamingfast.io:443` + +#### Substreams YAML configuration file + +Inform Substreams where to find the `substreams.yaml` configuration file. + +{% hint style="info" %} +**Note**: The `substreams.yaml` configuration file argument in the command is optional if you are within the root folder of your Substreams and your manifest file is named `substreams.yaml. +{% endhint %} + +#### Module + +The `map_transfers` module is defined in the manifest and it is the module run by Substreams. + +#### Block mapping + +Start mapping at the specific block `12292922` by using passing the flag and block number `--start-block 12292922`. + +Cease block processing by using `--stop-block +1.` The `+1` option requests a single block. In the example, the next block is `12292923`. + +### Successful Substreams results + +Messages are printed to the terminal for successfully installed and configured Substreams setups. + +```bash + substreams run -e mainnet.eth.streamingfast.io:443 \ + substreams.yaml \ + map_transfers \ + --start-block 12292922 \ + --stop-block +1 +``` + +The `substreams` [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) command outputs: + +```bash +2022-05-30T10:52:27.256-0400 INFO (substreams) connecting... +2022-05-30T10:52:27.389-0400 INFO (substreams) connected + +----------- IRREVERSIBLE BLOCK #12,292,922 (12292922) --------------- +map_transfers: log: NFT Contract bc4ca0eda7647a8ab7c2061c2e118a18a936f13d invoked +[...] +map_transfers: message "eth.erc721.v1.Transfers": { + "transfers": [ + { + "from": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "to": "q6cWGn+2nIjhbtn0Vc5it5HuTQM=", + "trxHash": "z7GX9i7Fx/DnGhHsDEoOOUo6pB21OG6FUm+GyEs/J5Y=", + "ordinal": "85" + }, + ... + ] +} +``` + +## Development and production mode + +Substreams has two mode when executing your module(s) either development mode or production mode. Development +and production modes impact the execution of Substreams, important aspects of execution include: + +* The time required to reach the first byte. +* The speed that large ranges get executed. +* The module logs and outputs sent back to the client. + +### Differences + +Differences between production and development modes include: + +* Forward parallel execution is enabled in production mode and disabled in development mode +* The time required to reach the first byte in development mode is faster than in production mode. + +Specific attributes of development mode include: + +* The client will receive all of the executed module's logs. +* It's possible to request specific store snapshots in the execution tree. +* Multiple module's output is possible. + +### Examples + +In most cases, you will run production mode, using a Substreams sink. Development mode is enabled by default in the CLI unless the `-p` flag is specified. + +Examples: (given the dependencies: `[block] --> [map_pools] --> [store_pools] --> [map_transfers])` + +* Running the `substreams run substreams.yaml map_transfers` command executes in development mode and only prints the `map_transfers` module's outputs and logs. +* Running the `substreams run substreams.yaml map_transfers --debug-modules-output=map_pools,map_transfers,store_pools` command executes in development mode and only prints the outputs of the `map_pools`, `map_transfers`, and `store_pools` modules. +* Running the `substreams run substreams.yaml map_transfers -s 1000 -t +5 --debug-modules-initial-snapshot=store_pools` command executes in development mode and prints all the entries in the `store_pools` module at block 999, then continues with outputs and logs from the `map_transfers` module in blocks 1000 through 1004. diff --git a/firehose/substreams/docs/developers-guide/sink-deployable-units.md b/firehose/substreams/docs/developers-guide/sink-deployable-units.md new file mode 100644 index 0000000..35cd806 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-deployable-units.md @@ -0,0 +1,90 @@ +--- +description: Working with Deployable Units (alpha feature) +--- + +# Defining a deployable unit + +## Overview + +Deployable units allow you to define everything that is needed to run a substreams with a sink, inside the package itself. + +{% hint style="info" %} +**Note**: Currently, only SQL sink supports this feature. +{% endhint %} + +## Requirements + +* A substreams that can output to a sink (follow [`substreams-sink-sql`](https://substreams.streamingfast.io/developers-guide/sink-targets/substreams-sink-sql.md)) +* Substreams CLI version v1.1.15 or above (https://github.com/streamingfast/substreams/releases) +* To run the development environmentk, you will need : + * [Docker](https://docs.docker.com/engine/install/) to be installed + * An [authentication token](https://substreams.streamingfast.io/reference-and-specs/authentication) + +## Adding sink information to your substreams manifest + +* Add the following to your substreams.yaml manifest: + +```yaml +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.3/substreams-sink-sql-protodefs-v1.0.3.spkg + +network: mainnet + +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + postgraphile_frontend: + enabled: true +``` + +Explanation: + * The `imports.sql` field embeds the protobuf definition for `sf.substreams.sink.sql.v1.Service` + * The `network` field is a string that represents the chain. While there is no official canonical identifer for each network, this is a good reference (see CLI Name column) https://thegraph.com/docs/en/developing/supported-networks/ + * The `sink.module` is simply the name of the module that outputs to the sink + * The `sink.type` will be the fully-qualified domain name of the protobuf message supported by the sink + * The `sink.config` should map with the protobuf message type specified under `sink.type`, see (sf.substreams.sink.sql.v1 protobuf definition)[https://github.com/streamingfast/substreams-sink-sql/blob/develop/proto/sf/substreams/sink/sql/v1/services.proto] + +* Run `substreams info substreams.yaml` to validate that your changes are correctly decoded, you should see something like this: +``` +Sink config: +---- +type: sf.substreams.sink.sql.v1.Service +configs: +- schema: (371 bytes) MD5SUM: 280ada56ad9f83f58891872bf93e4794 [LOADED_FILE] +- postgraphile_frontend: + - enabled: true +(...) +``` + +# Running the stack on a local dev environment + +1. Set your authentication token in your environment: `export SUBSTREAMS_API_TOKEN="your-token"` +1. Run the `serve` command in a shell (this is the development server that will create docker containers to run the sink and database) `substreams alpha sink-serve` (it will store data under './sink-data' by default, override to your preference with `--data-dir`) +1. From another shell, deploy your Substreams deployable unit: `substreams alpha sink-deploy ./substreams.yaml` and see the output services details: +``` +Deploying... (creating services, please wait) +Deployed substreams sink "1ef89c74": + Status: RUNNING () +Services: + - 1ef89c74-pgweb: PGWeb service "1ef89c74-pgweb" available at URL: 'http://localhost:8081' + - 1ef89c74-postgraphile: Postgraphile service "1ef89c74-postgraphile" available at URL: 'http://localhost:3000/graphiql' (API at 'http://localhost:3000/graphql') + - 1ef89c74-postgres: PostgreSQL service "1ef89c74-postgres" available at DSN: 'postgres://dev-node:insecure-change-me-in-prod@localhost:5432/substreams?sslmode=disable' + - 1ef89c74-sink: Sink service (no exposed port). Use 'substreams alpha sink-info 1ef89c74-sink' to see last processed block or 'docker logs 1ef89c74-sink' to see the logs. +``` + +1. You can explore the different services directly from your browser: + * Postgraphile: http://localhost:3000/graphiql + * PGWeb: http://localhost:8081/ +1. After a few seconds, the command `substreams alpha sink-info` should give you information about the progress of the sink (ex: `Last processed block: 11000`) +1. You can pause the sink process and keep the database and tools available by running `substreams alpha sink-pause` +1. Resume the sink process by running `substreams alpha sink-resume` +1. To deploy a new version of your substreams to your development environment, simply use `substreams alpha sink-update ./substreams.yaml` with the new code. The sql-sink will continue feeding from where it left off, unless you use `--reset`, forcing the sink to start from the beginning. +1. After deploying a new version, you can check the "output module hash" from the `sink-info` command and see confirm that it matches the module hash from your `substreams info` command. (ex: `Output module: db_out (dec326aecb9e27fbfb67d1748a91f7f84746ec27)`). The version number from your substreams.yaml is also displayed as part of the sink-info output. +1. When you're done, use `substreams alpha sink-stop` or simply hit "ctrl-c" on the terminal running `sink-serve` and let it shut down the docker containers completely. You can always use `docker ps` to list all running containers on your machine. +* Get the full list of sink management commands by running `substremas alpha help` + +{% hint style="info" %} +**Note**: The "sink" will keep running until it is stopped, either by hitting "ctrl-c" on the `substreams alpha sink-serve` terminal, or by using the commands `sink-pause`, `sink-stop` or `sink-remove`. +{% endhint %} diff --git a/firehose/substreams/docs/developers-guide/sink-targets/README.md b/firehose/substreams/docs/developers-guide/sink-targets/README.md new file mode 100644 index 0000000..dabb483 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/README.md @@ -0,0 +1,70 @@ +--- +description: StreamingFast Substreams data sinks +--- + +# Sink targets + +## **Substreams sinks overview** + +A sink is a final destination for data acquired through Substreams. Examples include databases, a Slack channel, or flat file storage. Sinks have a wide range of types and you can route data anywhere you're able to imagine. + +StreamingFast provides a few examples, libraries, and tools to assist you when routing blockchain data to sinks. + +## **Basics** + +Databases and flat files are standard storage types however you can pipe Substreams data into other locations required by a new or existing application or architecture. + +An important design aspect of Substreams is the decision to rely on protobufs for data packaging and transmission. + +Protobufs provide a data-centric, technology stack, non-language specific, and platform-independent approach to using data that is passed from one application to another. + +{% hint style="success" %} +**Tip**: The platform-independent, data-centric capabilities of protobufs give you the opportunity to package and route data captured by Substreams to other sources, including sinks. +{% endhint %} + +At a low level, Substreams consumes data through a gRPC streaming service. Consumers receive streams of data scoped to a single block as requests are sent. + +## **General requirements** + +The first step of having Substreams output consumed by a sink involves the creation of a `map` module; whose output type is a protobuf, which is accepted by the sink. The protobuf is populated from Substreams protobuf types representing a transformation of types into a format suitable for loading into sinks. + +For example, database-like Substreams sinks such as PostgreSQL or MongoDB accept a module's `output` of `type` [sf.substreams.database.v1.DatabaseChanges](https://github.com/streamingfast/substreams-sink-database-changes/blob/develop/proto/sf/substreams/sink/database/v1/database.proto#L7). + +{% hint style="success" %} +**Tip**: Databases are only one type of sink. The sink determines the `output` `type` to be respected. +{% endhint %} + +The sink reads the specific protobuf-based data being sent out of Substreams and performs the processing for it. Every sink performs differently regarding the data received, most perform some kind of storage. + +The configuration of the storage layer and its requirements are your responsibility. StreamingFast provides documentation for the infrastructure required by various Substreams sinks. Read through the documentation to understand the behavior and requirements for the other sink types. + +An understanding of basic [Substreams fundamentals](../../concepts-and-fundamentals/fundamentals.md) is expected before continuing. [Learn more about modules](https://substreams.streamingfast.io/concept-and-fundamentals/modules) in the Substreams documentation. + +## **Existing and community sinks** + +StreamingFast values external contributions for Substreams sinks. If your team has created a sink, [contact the StreamingFast team through Discord](https://discord.gg/mYPcRAzeVN) so it gets included in the Substreams documentation. + +The [`substreams-eth-block-meta`](https://github.com/streamingfast/substreams-eth-block-meta) example demonstrates sinks in action. Check out the source code in the project’s official GitHub repository. + +StreamingFast provides several tools to assist database persistence for Substreams. + +* **SQL:** A command line tool to [sync a Substreams with an SQL database](https://github.com/streamingfast/substreams-sink-sql) (currently supported drivers are PostgresSQL and Clickhouse) +* **MongoDB:** A command line tool to [sync a Substreams with a MongoDB database](https://github.com/streamingfast/substreams-sink-mongodb) +* **File-based storage:** A command line tool to [sync a Substreams to file-based storage](https://github.com/streamingfast/substreams-sink-files) +* **Key-value-based storage:** A command line tool to [sync a Substreams to a key-value store](https://github.com/streamingfast/substreams-sink-kv) -- see [tutorial](substreams-sink-kv.md) + +## **Build a sink** + +StreamingFast provides tools allowing you to route blockchain data to a few different types of data storage sinks, or means of importation. StreamingFast sink tools aren’t the only options. Existing applications, databases, and other tools are fed by blockchain data captured by Substreams. + +{% hint style="success" %} +**Tip**: To get inspiration for writing your own sink study the examples provided by StreamingFast. One example is a database, such as Oracle, lacking Substreams sink tools. Study the [SQL Sink](https://github.com/streamingfast/substreams-sink-sqk) tool and its codebase to understand how to construct your own data-sinking solution. +{% endhint %} + +Protobufs are designed to use for transferring data out of Substreams into the data sink. Protobufs aren’t tied to any particular technology stack or language, enabling you to capture, further process, use and store data provided by Substreams in different capacities. + +{% hint style="info" %} +**Note**: Through careful design of the Substreams manifest, modules, and protobufs you can craft your output data in a variety of ways. One option, as seen in the [SQL example](https://github.com/streamingfast/substreams-sink-sql) is through a single `output` protobuf. The flexibility of Substreams design however allows for other strategies, including multiple protobufs and modules. +{% endhint %} + +You need to examine and account for the format and any requirements of the end environment you want your data routed into. The specifics of how data is ingested by the sink determine the design of the `output` from Substreams. diff --git a/firehose/substreams/docs/developers-guide/sink-targets/custom-sink-js.md b/firehose/substreams/docs/developers-guide/sink-targets/custom-sink-js.md new file mode 100644 index 0000000..db1fc9e --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/custom-sink-js.md @@ -0,0 +1,149 @@ +## Custom Sink Using JavaScript + +If none of the previous sinks work for you, don't worry! You can create your own custom sink by using the [Substreams JavaScript library](https://github.com/substreams-js/substreams-js). +This library enables you to run a Substreams, just like you would through the CLI, but programatically. + +The library works both on the client-side and the server-side. + +### Installing the Library + +In your JavaScript project, use your preferred JavaScript package manager to install the required dependencies: + +1. The Substreams Core library: + +```bash +npm install @substreams/core +``` + +2. The Substreams Manifest library: + +```bash +npm install @substreams/manifest +``` + +3. The Protobuf library, which will be used to decode the Substreams response: + +```bash +npm install @bufbuild/connect-web +``` + +### Using the Library + +In order to use the library, you will need: + +- A Substreams endpoint. +- An authentication token (visit https://app.streamingfast.io to get one). +- A Substreams package (`spkg`). + +Consider that you want to consume the `map_block_meta` module of the [Ethereum Explorer package](https://github.com/streamingfast/substreams-explorers/tree/main/ethereum-explorer), which is hosted on Google Cloud (`https://storage.googleapis.com/substreams-registry/spkg/ethereum-explorer-v0.1.1.spkg`). + +1. First, let's define a few helper variables: + +```javascript +const TOKEN = "YOUR_TOKEN" // Your authentication token +const SPKG = "https://storage.googleapis.com/substreams-registry/spkg/ethereum-explorer-v0.1.1.spkg" // URL of the SPKG +const MODULE = "map_block_meta" // Name of the Substreams Module to run +``` + +2. Use the `fetchSubstream` method from the library to download the Substreams. Then, the `createRegistry` function creates the Protobuf definitions from the package: + +```javascript +const fetchPackage = async () => { + return await fetchSubstream(SPKG) +} + +const main = async () => { + // Fetch Substreams + const pkg = await fetchPackage() + // Create Protobuf registry + const registry = createRegistry(pkg); +} +``` + +3. Use the `createConnectTransport` to define the networking details of the connection (Substreams endpoint and authentication token): + +```javascript +const main = async () => { + const pkg = await fetchPackage() + const registry = createRegistry(pkg); + + const transport = createConnectTransport({ + // Substreams endpoint + baseUrl: "https://api.streamingfast.io", + // Authentication token + interceptors: [createAuthInterceptor(TOKEN)], + useBinaryFormat: true, + jsonOptions: { + // Protobuf Registry + typeRegistry: registry, + }, + }); +} +``` + +4. The `createRequest` function encapsulates the information of the execution (package, module, start block and stop block): + +```javascript +const main = async () => { + const pkg = await fetchPackage() + const registry = createRegistry(pkg); + + const transport = createConnectTransport({ + baseUrl: "https://api.streamingfast.io", + interceptors: [createAuthInterceptor(TOKEN)], + useBinaryFormat: true, + jsonOptions: { + typeRegistry: registry, + }, + }); + + // Execution details + const request = createRequest({ + substreamPackage: pkg, + outputModule: MODULE, + productionMode: true, + startBlockNum: 100000, + stopBlockNum: '+10', + }); +} +``` + +5. Finally, you can use the `streamBlocks` function to iterate over the stream of blocks returned by the Substreams endpoint: + +```javascript +const main = async () => { + const pkg = await fetchPackage() + const registry = createRegistry(pkg); + + const transport = createConnectTransport({ + baseUrl: "https://api.streamingfast.io", + interceptors: [createAuthInterceptor(TOKEN)], + useBinaryFormat: true, + jsonOptions: { + typeRegistry: registry, + }, + }); + + const request = createRequest({ + substreamPackage: pkg, + outputModule: MODULE, + productionMode: true, + startBlockNum: 100000, + stopBlockNum: '+10', + }); + + // Iterate over blocks + for await (const response of streamBlocks(transport, request)) { + const output = unpackMapOutput(response.response, registry); + + if (output !== undefined && !isEmptyMessage(output)) { + const outputAsJson = output.toJson({typeRegistry: registry}); + console.log(outputAsJson) + } + } +} +``` + +Now, you can send the data anywhere and create your own custom sink! If you have created a sink and you think it can be reused by other developers, [let us know on Discord](https://discord.gg/jZwqxJAvRs)! + +The previous code is availalble [on GitHub](https://gist.github.com/enoldev/b9f32e045f47675bd5c20f92246aed84). diff --git a/firehose/substreams/docs/developers-guide/sink-targets/substreams-powered-subgraph.md b/firehose/substreams/docs/developers-guide/sink-targets/substreams-powered-subgraph.md new file mode 100644 index 0000000..e7127b5 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/substreams-powered-subgraph.md @@ -0,0 +1,7 @@ +# Substreams-powered subgraph + +Substreams-powered subgraph are the prime candidate for Substreams output. + +See The Graph's documentation to roll out yours: + +[https://thegraph.com/docs/en/cookbook/substreams-powered-subgraphs/](https://thegraph.com/docs/en/cookbook/substreams-powered-subgraphs/) diff --git a/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-files.md b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-files.md new file mode 100644 index 0000000..b7b96c3 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-files.md @@ -0,0 +1,215 @@ +--- +description: StreamingFast Substreams sink files +--- + +# Files + +### Purpose + +This documentation exists to assist you in understanding and beginning to use the StreamingFast [`substreams-sink-file`](https://github.com/streamingfast/substreams-sink-files)`s` tool. The Substreams module paired with this tutorial is a basic example of the elements required for sinking blockchain data into files-based storage solutions. + +### Overview + +The `substreams-sink-files` tool provides the ability to pipe data extracted from a blockchain to various types of files-based persistence solutions. + +For example, you could extract all of the ERC20, ERC721, and ERC1155 transfers from the Ethereum blockchain and persist the data to a files-based store. + +Substreams modules are created and prepared specifically for the sink tool. After the sink tool begins running, automated tasks can be set up to have [BigQuery](https://cloud.google.com/bigquery), [Clickhouse](https://clickhouse.com), custom scripts, or other files-based storage solutions, ingest the data. This can only be accomplished indirectly. It's possible to automate further ingestion from files to data stores. + +You could use `substreams-sink-files` to sink data in `JSONL` format to a [Google Cloud Storage (GCS)](https://cloud.google.com/storage) bucket and configure a BigQuery Transfer job to run every 15 minutes. The scheduled job ingests the new files found in the GCS bucket where the data, extracted by the Substreams, was written. + +### Accompanying code example + +The accompanying Substreams module associated with this documentation is responsible for extracting a handful of data fields from the Block object injected into the Rust-based map module. The sink tool processes the extracted blockchain data line-by-line and outputs the data to the files-based persistence mechanism you've chosen. + +The accompanying code example extracts four data points from the Block object and packs them into the `substreams.sink.files.v1` Protobuf's data model. The data is passed to the Protobuf as a single line of plain text. + +Binary formats such as [Avro](https://avro.apache.org/) or [Parquet](https://parquet.apache.org/) is possible, however, support is not available. Contributions are welcome to help with support of binary data formats. [Contact the StreamingFast team on Discord](https://discord.gg/mYPcRAzeVN) to learn more and discuss specifics. + +## Installation + +### Install `substreams-sink-files` + +Install `substreams-sink-files` by using the pre-built binary release [available in the official GitHub repository](https://github.com/streamingfast/substreams-sink-files/releases). + +Extract `substreams-sink-files` into a folder and ensure this folder is referenced globally via your `PATH` environment variable. + +### Accompanying code example + +The accompanying code example for this tutorial is available in the `substreams-sink-files` repository. The Substreams project for the tutorial is located in the [docs/tutorial](https://github.com/streamingfast/substreams-sink-files/tree/master/docs/tutorial) directory. + +Run the included `make protogen` command to create the required Protobuf files. + +```bash +make protogen +``` + +It's a good idea to run the example code using your installation of the `substreams` CLI to make sure everything is set up and working properly. + +Verify the setup for the example project by using the `make build` and `substreams run` commands. + +Build the Substreams module by using the included `make` command. + +```bash +make +``` + +Run the project by using the `substreams run` command. + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml jsonl_out --start-block 1000000 --stop-block +1 +``` + +The `substreams run` command will result in output resembling the following: + +```bash +----------- NEW BLOCK #1,000,000 (1000000) --------------- +{ + "@module": "jsonl_out", + "@block": 1000000, + "@type": "sf.substreams.sink.files.v1", + "@data": { + "lines": [ + "{\"hash\":\"8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e\",\"number\":1000000,\"parent_hash\":\"b4fbadf8ea452b139718e2700dc1135cfc81145031c84b7ab27cd710394f7b38\",\"timestamp\":\"2016-02-13T22:54:13Z\"}" + ] + } +} +``` + +## Substreams modifications + +### Module handler changes for sink + +The example code in the [`lib.rs`](https://github.com/streamingfast/substreams-sink-files/blob/master/docs/tutorial/src/lib.rs) Rust source code file contains the `jsonl_out` module handler responsible for extracting the blockchain data. The module handler is responsible for passing the data to the `sf.substreams.sink.files.v1` Protobuf for the sink tool and its processes. + +```rust +#[substreams::handlers::map] +fn jsonl_out(block: eth::Block) -> Result { + + let header = block.header.as_ref().unwrap(); + + Ok(pb::sinkfiles::Lines { + lines: vec![json!({ + "number": block.number, + "hash": Hex(&block.hash).to_string(), + "parent_hash": Hex(&header.parent_hash).to_string(), + "timestamp": header.timestamp.as_ref().unwrap().to_string() + }) + .to_string()], + }) +} +``` + +This module handler uses `JSONL` for the output type, any other plain-text line-based format can be supported, `CSV` for example. The [`json!`](https://docs.rs/serde_json/latest/serde_json/macro.json.html) macro is used to write the block data to the Rust `Vec` type by using the Rust [`vec!`](https://doc.rust-lang.org/std/macro.vec.html) macro. + +The example code is intentionally very basic. StreamingFast [provides a more robust and full example](https://github.com/streamingfast/substreams-eth-token-transfers/blob/develop/src/lib.rs#L24) demonstrating how to extract data related to transfers from Ethereum. A crucial aspect of working with Substreams and sinks is a significant amount of data can be extracted from a Block object. The data is extracted and packed into a row. The row is represented by the JSONL or CSV based Protobuf you're responsible for designing for your sink. + +The output type for sink is a list of lines. The line content can be any type anything that is formatted as plain text, and line based. For example, a basic string like the transaction's hash, would result in files containing all the hashes for the transactions, one per line. + +### Core steps for Substreams sink modules + +- Import sink `.spkg` files, re-generate Protobufs and create and add a mod.rs file. +- Create a map module outputting sf.substreams.sink.files.v1 format. This module extracts the entity to be written, one per block from the block or another module's dependencies. Each line will be in JSON format. You can use the json! macro from the [`serde_json`](https://docs.rs/serde_json/latest/serde_json) crate to assist creating your structure, one per line. +- Add the correct module definition to the Substreams manifest `substreams.yaml`. + +```yaml +imports: + sink_files: https://github.com/streamingfast/substreams-sink-files/releases/download/v0.2.0/substreams-sink-files-v0.2.0.spkg + +binaries: + default: + type: wasm/rust-v1 + file: target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: jsonl_out + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:sf.substreams.sink.files.v1.Lines +``` + +## Understanding the sink tool + +### Run and configure the `substreams-sink-files` tool + +The command to start and run the `substreams-sink-files` tool for the accompanying Substreams project will resemble: + +{% code overflow="wrap" %} + +```bash +substreams-sink-files run --encoder=lines --state-store=./output/state.yaml mainnet.eth.streamingfast.io:443 substreams.yaml jsonl_out ./output/files +``` + +{% endcode %} + +## Verify output from tool + +Running the sink tool results in logging information printed to the terminal and directories and files being written to the local system or a cloud provider bucket if configured. + +The sink tool will produce output in the terminal resembling the following for a properly configured and working environment and project. + +```bash +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) starting prometheus metrics server {"listen_addr": "localhost:9102"} +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) sink to files {"file_output_path": "./localdata/out", "file_working_dir": "./localdata/working", "endpoint": "mainnet.eth.streamingfast.io:443", "encoder": "lines", "manifest_path": "substreams.yaml", "output_module_name": "jsonl_out", "block_range": "", "state_store": "./localdata/working/state.yaml", "blocks_per_file": 10000, "buffer_max_size": 67108864} +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) reading substreams manifest {"manifest_path": "substreams.yaml"} +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) starting pprof server {"listen_addr": "localhost:6060"} +2023-01-09T07:45:04.041-0800 INFO (pipeline) computed start block {"module_name": "jsonl_out", "start_block": 0} +2023-01-09T07:45:04.042-0800 INFO (substreams-sink-files) ready, waiting for signal to quit +2023-01-09T07:45:04.045-0800 INFO (substreams-sink-files) setting up sink {"block_range": {"start_block": 0, "end_block": "None"}, "cursor": {"Cursor":"","Block":{}}} +2023-01-09T07:45:04.048-0800 INFO (substreams-sink-files) starting new file boundary {"boundary": "[0, 10000)"} +2023-01-09T07:45:04.049-0800 INFO (substreams-sink-files) boundary started {"boundary": "[0, 10000)"} +2023-01-09T07:45:04.049-0800 INFO (substreams-sink-files) starting stats service {"runs_each": "2s"} +2023-01-09T07:45:06.052-0800 INFO (substreams-sink-files) substreams sink stats {"progress_msg_rate": "0.000 msg/s (0 total)", "block_rate": "650.000 blocks/s (1300 total)", "last_block": "#1299 (a0f0f283e0d297dd4bcf4bbff916b1df139d08336ad970e77f26b45f9a521802)"} +``` + +One bundle of data is created for every 10K blocks during the sink process. + +To view the files the `substreams-sink-files` tool generates navigate into the directory you used for the output path. The directory referenced in the example points to the `localdata/out` directory. List the files in the output directory using the standard `ls` command to reveal the files created by the `substreams-sink-files` tool. + +```bash +... +0000000000-0000010000.jsonl 0000090000-0000100000.jsonl 0000180000-0000190000.jsonl +0000010000-0000020000.jsonl 0000100000-0000110000.jsonl 0000190000-0000200000.jsonl +0000020000-0000030000.jsonl 0000110000-0000120000.jsonl 0000200000-0000210000.jsonl +0000030000-0000040000.jsonl 0000120000-0000130000.jsonl 0000210000-0000220000.jsonl +0000040000-0000050000.jsonl 0000130000-0000140000.jsonl 0000220000-0000230000.jsonl +0000050000-0000060000.jsonl 0000140000-0000150000.jsonl 0000230000-0000240000.jsonl +0000060000-0000070000.jsonl 0000150000-0000160000.jsonl 0000240000-0000250000.jsonl +0000070000-0000080000.jsonl 0000160000-0000170000.jsonl 0000250000-0000260000.jsonl +0000080000-0000090000.jsonl 0000170000-0000180000.jsonl +... +``` + +The block range spanned by the example is from block 0000000000 to block 0000260000. The blocks contain all the lines received for the full 10K of processed blocks by default. The block range is controlled by using the `--file-block-count` flag. + +### Cursors + +When you use Substreams, it sends back a block to a consumer using an opaque cursor. This cursor points to the exact location within the blockchain where the block is. In case your connection terminates or the process restarts, upon re-connection, Substreams sends back the cursor of the last written bundle in the request so that the stream of data can be resumed exactly where it left off and data integrity is maintained. + +You will find that the cursor is saved in a file on disk. The location of this file is specified by the flag `--state-store` which points to a local folder. You must ensure that this file is properly saved to a persistent location. If the file is lost, the `substreams-sink-files` tool will restart from the beginning of the chain, redoing all the previous processing. + +Therefore, It is crucial that this file is properly persisted and follows your deployment of `substreams-sink-files` to avoid any data loss. + +### High Performance + +If you are looking for the fastest performance possible, we suggest that your destination source is able to handle heavy traffic. Also, to speed up things, you can allocate a lot of RAM to the process and increase the flag `--buffer-max-size` to a point where you are able to hold a full batch of N blocks in memory (checking the size of the final file is a good indicator of the size to keep stuff in memory). + +A lot of I/O operations is avoid if the buffer can hold everything in memory greatly speeding up the process of writing blocks bundle to its final destination. + +### Cloud-based storage + +You can use the `substreams-sink-files` tool to route data to files on your local file system and cloud-based storage solutions. To use a cloud-based solution such as Google Cloud Storage bucket, S3 compatible bucket, or Azure bucket, you need to make sure it is set up properly. Then, instead of referencing a local file in the `substreams-sink-files run` command, use the path to the bucket. The paths resemble `gs:///`, `s3:///`, and `az:///` respectively. Be sure to update the values according to your account and provider. + +### Limitations + +When you use the `substreams-sink-files` tool, you will find that it syncs up to the most recent "final" block of the chain. This means it is not real-time. Additionally, the tool writes bundles to disk when it has seen 10,000 blocks. As a result, the latency of the last available bundle can be delayed by around 10,000 blocks. How many blocks per batch can be controlled by changing the flag `--file-block-count` + +## Conclusion and review + +The ability to route data extracted from the blockchain by using Substreams is powerful and useful. Files aren't the only type of sink the data extracted by Substreams can be piped into. Review the core Substreams sinks documentation for [additional information on other types of sinks](./) and sinking strategies. + +To use `substreams-sink-files` you need to clone the official repository, install the tooling, generate the required files from the substreams CLI for the example Substreams module and run the sink tool. + +You have to ensure the sinking strategy has been defined, the appropriate file types have been targeted, and accounted for, and the module handler code in your Substreams module has been properly updated. You need to start the `substreams-sink-files` tool and use the `run` command being sure to provide all of the required values for the various flags. diff --git a/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-kv.md b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-kv.md new file mode 100644 index 0000000..c34bc6d --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-kv.md @@ -0,0 +1,291 @@ +--- +description: StreamingFast Substreams Key/Value store sink +--- + +# Key/value store + +## Purpose + +This documentation will assist you in using [`substreams-sink-kv`](https://github.com/streamingfast/substreams-sink-kv) to write data from your existing substreams into a key-value store and serve it back through Connect-Web/GRPC. + +## Overview + +`substreams-sink-kv` works by reading the output of specially-designed substreams module (usually called `kv_out`) that produces data in a protobuf-encoded structure called `sf.substreams.sink.kv.v1.KVOperations`. + +The data is written to a key-value store. Currently supported KV store are Badger, BigTable and TiKV. + +A Connect-Web interface makes the data available directly from the `substreams-sink-kv` process. Alternatively, you can consume the data directly from your key-value store. + +## Requirements + +* An existing substreams (including `substreams.yaml` and Rust code) that you want to instrument for `substreams-sink-kv`. +* A key-value store where you want to send your data (a badger local file can be used for development) +* Knowledge about Substreams development (start [here](https://substreams.streamingfast.io/getting-started/quickstart)) +* Rust installation and compiler + +## Installation + +* Install [substreams-sink-kv CLI](https://github.com/streamingfast/substreams-sink-kv/releases) +* Install [substreams CLI](https://substreams.streamingfast.io/getting-started/installing-the-cli) +* Install [grpcurl](https://github.com/fullstorydev/grpcurl/releases) to easily read the data back from the KV store + +## Instrumenting your Substreams + +### Assumptions + +The following instructions will assume that you are instrumenting [substreams-eth-block-meta](https://github.com/streamingfast/substreams-eth-block-meta), which contains: + +* A store `store_block_meta_end` defined like [this](https://github.com/streamingfast/substreams-eth-block-meta/blob/v0.4.0/substreams.yaml#L29-L34): + +```yaml +# substreams.yaml +... + - name: store_block_meta_end + kind: store + updatePolicy: set + valueType: proto:eth.block_meta.v1.BlockMeta + inputs: + - source: sf.ethereum.type.v2.Block +``` + +* a `eth.block_meta.v1.BlockMeta` protobuf structure like [this](https://github.com/streamingfast/substreams-eth-block-meta/blob/v0.4.0/proto/block\_meta.proto#L7-L12): + +``` +message BlockMeta { + uint64 number = 1; + bytes hash = 2; + bytes parent_hash = 3; + google.protobuf.Timestamp timestamp = 4; +} +``` + +> **Note** The [substreams-eth-block-meta](https://github.com/streamingfast/substreams-eth-block-meta) is already instrumented for sink-kv, the proposed changes here are a simplified version of what has been implemented. Please adjust the proposed code to your own substreams. + +### Import the Cargo module + +1. Add the `substreams-sink-kv` crate to your `Cargo.toml`: + +```toml +# Cargo.toml + +[dependencies] +substreams-sink-kv = "0.1.1" +# ... + +``` + +1. Add `map` module implementation function named `kv_out` to your `src/lib.rs`: + +```yaml +# substreams.yaml +... + - name: kv_out + kind: map + inputs: + - store: store_block_meta_end + mode: deltas + output: + type: proto:sf.substreams.sink.kv.v1.KVOperations +``` + +1. Add a `kv_out` public function to your `src/lib.rs`: + +``` +// src/lib.rs + +#[path = "kv_out.rs"] +mod kv; +use substreams_sink_kv::pb::kv::KvOperations; + +#[substreams::handlers::map] +pub fn kv_out( + deltas: store::Deltas>, +) -> Result { + + // Create an empty 'KvOperations' structure + let mut kv_ops: KvOperations = Default::default(); + + // Call a function that will push key-value operations from the deltas + kv::process_deltas(&mut kv_ops, deltas); + + // Here, we could add more operations to the kv_ops + // ... + + Ok(kv_ops) +} +``` + +1. Add the `kv::process_deltas` transformation function referenced in the last snippet: + +``` +// src/kv_out.rs + +use substreams::proto; +use substreams::store::{self, DeltaProto}; +use substreams_sink_kv::pb::kv::KvOperations; + +use crate::pb::block_meta::BlockMeta; + +pub fn process_deltas(ops: &mut KvOperations, deltas: store::Deltas>) { + use substreams::pb::substreams::store_delta::Operation; + + for delta in deltas.deltas { + match delta.operation { + // KV Operations do not distinguish between Create and Update. + Operation::Create | Operation::Update => { + let val = proto::encode(&delta.new_value).unwrap(); + ops.push_new(delta.key, val, delta.ordinal); + } + Operation::Delete => ops.push_delete(&delta.key, delta.ordinal), + x => panic!("unsupported opeation {:?}", x), + } + } +} +``` + +## Test your substreams + +1. Compile your changes in your rust code: + +``` +cargo build --release --target=wasm32-unknown-unknown +``` + +1. Run with `substreams` command directly: + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml kv_out --start-block 1000000 --stop-block +1 +``` + +> **Note** To connect to a public StreamingFast substreams endpoint, you will need an authentication token, follow this [guide](https://substreams.streamingfast.io/reference-and-specs/authentication) to obtain one. + +1. Run with `substreams-sink-kv`: + +```bash +substreams-sink-kv \ + run \ + "badger3://$(pwd)/badger_data.db" \ + mainnet.eth.streamingfast.io:443 \ + manifest.yaml \ + kv_out +``` + +You should see output similar to this one: + +```bash +2023-01-12T10:08:31.803-0500 INFO (sink-kv) starting prometheus metrics server {"listen_addr": "localhost:9102"} +2023-01-12T10:08:31.803-0500 INFO (sink-kv) sink to kv {"dsn": "badger3:///Users/stepd/repos/substreams-sink-kv/badger_data.db", "endpoint": "mainnet.eth.streamingfast.io:443", "manifest_path": "https://github.com/streamingfast/substreams-eth-block-meta/releases/download/v0.4.0/substreams-eth-block-meta-v0.4.0.spkg", "output_module_name": "kv_out", "block_range": ""} +2023-01-12T10:08:31.803-0500 INFO (sink-kv) starting pprof server {"listen_addr": "localhost:6060"} +2023-01-12T10:08:31.826-0500 INFO (sink-kv) reading substreams manifest {"manifest_path": "https://github.com/streamingfast/substreams-eth-block-meta/releases/download/v0.4.0/substreams-eth-block-meta-v0.4.0.spkg"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) validating output store {"output_store": "kv_out"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) resolved block range {"start_block": 0, "stop_block": 0} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) starting to listen on {"addr": "localhost:8000"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) starting stats service {"runs_each": "2s"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) no block data buffer provided. since undo steps are possible, using default buffer size {"size": 12} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) starting stats service {"runs_each": "2s"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) ready, waiting for signal to quit +2023-01-12T10:08:32.186-0500 INFO (sink-kv) launching server {"listen_addr": "localhost:8000"} +2023-01-12T10:08:32.187-0500 INFO (sink-kv) serving plaintext {"listen_addr": "localhost:8000"} +2023-01-12T10:08:32.278-0500 INFO (sink-kv) session init {"trace_id": "a3c59bd7992c433402b70f9541565d2d"} +2023-01-12T10:08:34.186-0500 INFO (sink-kv) substreams sink stats {"db_flush_rate": "10.500 flush/s (21 total)", "data_msg_rate": "0.000 msg/s (0 total)", "progress_msg_rate": "0.000 msg/s (0 total)", "block_rate": "0.000 blocks/s (0 total)", "flushed_entries": 0, "last_block": "None"} +2023-01-12T10:08:34.186-0500 INFO (sink-kv) substreams sink stats {"progress_msg_rate": "16551.500 msg/s (33103 total)", "block_rate": "10941.500 blocks/s (21883 total)", "last_block": "#291883 (66d03f819dde948b297c8d582889246d7ba11a5b947335497f8716a7b608f78e)"} +``` + +> **Note** This writes the data to a local folder "./badger\_data.db/" in Badger format. You can `rm -rf ./badger_data.db` between your tests to cleanup all existing data. + +1. Look at the stored data + +You can scan the whole dataset using the 'Scan' command: + +```bash +grpcurl --plaintext -d '{"begin": "", "limit":100}' localhost:8000 sf.substreams.sink.kv.v1.Kv/Scan +``` + +You can look at data by key prefix: + +```bash +grpcurl --plaintext -d '{"prefix": "day:first:201511", "limit":31}' localhost:8000 sf.substreams.sink.kv.v1.Kv/GetByPrefix +``` + +## Consume the key-value data from a web-page using Connect-Web + +The [Connect-Web](https://connect.build/docs/web/getting-started) library allows you to quickly bootstrap a web-based client for your key-value store. + +### Requirements + +* [NodeJS](https://nodejs.dev/download) +* [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) +* [buf CLI](https://docs.buf.build/installation) + +### Start from our example for `substreams-eth-block-meta` + +You can checkout and run our connect-web-example like this: + +```bash +git clone git@github.com:streamingfast/substreams-sink-kv +cd substreams-sink-kv/connect-web-example +npm install +npm run dev +``` + +Then, enter a key in the text box. The app currently only decodes `eth.block_meta.v1.BlockMeta`, so you will likely receive the corresponding value encoded in hex string. + +To decode the value of your own data structures, add your `.proto` files under `proto/` and generate Rust bindings like this: + +```bash +npm run buf:generate +``` + +You should see this output: + +``` +> connect-web-example@0.0.0 buf:generate +> buf generate ../proto/substreams/sink/kv/v1 && buf generate ./proto +``` + +Then, modify the code from `src/App.tsx` to decode your custom type, from this: + +```rust + import { BlockMeta } from "../gen/block_meta_pb"; + + ... + + const blkmeta = BlockMeta.fromBinary(response.value); + output = JSON.stringify(blkmeta, (key, value) => { + if (key === "hash") { + return "0x" + bufferToHex(blkmeta.hash); + } + if (key === "parentHash") { + return "0x" + bufferToHex(blkmeta.parentHash); + } + return value; + }, 2); +``` + +to this: + +```rust + import { MyData } from "../gen/my_data_pb"; + + ... + + const decoded = MyData.fromBinary(response.value); + output = JSON.stringify(decoded, null, 2); +``` + +### Bootstrap your own application + +If you want to start with an empty application, you can follow [these instructions](https://github.com/streamingfast/substreams-sink-kv/tree/main/connect-web-example/README.md) + +## Sending to a production key-value store + +Until now, we've used the **badger** database as a store, for simplicity. However, `substreams-sink-kv` also supports **TiKV** and **bigtable**. + +* `tikv://pd0,pd1,pd2:2379?prefix=namespace_prefix` +* `bigkv://project.instance/namespace-prefix?createTables=true` + +See [kvdb](https://github.com/streamingfast/kvdb) for more details. + +## Conclusion and review + +The ability to route data extracted from the blockchain by using Substreams is powerful and useful. Key-value stores aren't the only type of sink the data extracted by Substreams can be piped into. Review the core Substreams sinks documentation for [additional information on other types of sinks](https://substreams.streamingfast.io/developers-guide/substreams-sinks) and sinking strategies. diff --git a/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-mongodb.md b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-mongodb.md new file mode 100644 index 0000000..42632c4 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-mongodb.md @@ -0,0 +1,5 @@ +# MongoDB + +See the GitHub repository for an early version of the MongoDB Substreams Sink: + +* [https://github.com/streamingfast/substreams-sink-mongodb](https://github.com/streamingfast/substreams-sink-mongodb) diff --git a/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-prometheus.md b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-prometheus.md new file mode 100644 index 0000000..0d9757a --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-prometheus.md @@ -0,0 +1,162 @@ +--- +description: Pinax Substreams Prometheus sink +--- + +# [`Substreams`](https://substreams.streamingfast.io/) [Prometheus](https://prometheus.io/) sink module + +[github](https://github.com/pinax-network/substreams-sink-prometheus) +[crates.io](https://crates.io/crates/substreams-sink-prometheus) +[npm](https://www.npmjs.com/package/substreams-sink-prometheus) +[docs.rs](https://docs.rs/substreams-sink-prometheus) +[GitHub Workflow Status](https://github.com/pinax-network/substreams-sink-prometheus/actions?query=branch%3Amain) + +> `substreams-sink-prometheus` is a tool that allows developers to pipe data extracted metrics from a blockchain into a Prometheus time series database. + +## 📖 Documentation + +### https://docs.rs/substreams-sink-prometheus + +### Further resources + +- [Substreams documentation](https://substreams.streamingfast.io) +- [Prometheus documentation](https://prometheus.io) + +## CLI +[**Use pre-built binaries**](https://github.com/pinax-network/substreams-sink-prometheus/releases) +- [x] MacOS +- [x] Linux +- [x] Windows + +**Install** globally via npm +``` +$ npm install -g substreams-sink-prometheus +``` + +**Run** +``` +$ substreams-sink-prometheus run [options] +``` + +> Open the browser at [http://localhost:9102/metrics](http://localhost:9102/metrics) + +## 🛠 Feature Roadmap + +### [Gauge Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Gauge) +- [x] Set +- [x] Inc +- [x] Dec +- [x] Add +- [x] Sub +- [x] SetToCurrentTime +- [x] Remove +- [x] Reset + +### [Counter Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Counter) +- [x] Inc +- [x] Add +- [x] Remove +- [x] Reset + +### [Histogram Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Histogram) +- [ ] Observe +- [ ] buckets +- [ ] zero + +### [Summary Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Summary) +> Summaries calculate percentiles of observed values. +- [ ] Observe +- [ ] percentiles +- [ ] maxAgeSeconds +- [ ] ageBuckets +- [ ] startTimer + +### [Registry](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Registry) +- [ ] Clear +- [ ] SetDefaultLabels +- [ ] RemoveSingleMetric + +## Install + +```bash +$ cargo add substreams-sink-prometheus +``` + +## Quickstart + +**Cargo.toml** + +```toml +[dependencies] +substreams = "0.5" +substreams-sink-prometheus = "0.1" +``` + +**src/lib.rs** + +```rust +use std::collections::HashMap; +use substreams::prelude::*; +use substreams::errors::Error; +use substreams_sink_prometheus::{PrometheusOperations, Counter, Gauge}; + +#[substreams::handlers::map] +fn prom_out( + ... some stores ... +) -> Result { + + // Initialize Prometheus Operations container + let mut prom_ops: PrometheusOperations = Default::default(); + + // Counter Metric + // ============== + // Initialize Gauge with a name & labels + let mut counter = Counter::from("counter_name"); + + // Increments the Counter by 1. + prom_ops.push(counter.inc()); + + // Adds an arbitrary value to a Counter. (Returns an error if the value is < 0.) + prom_ops.push(counter.add(123.456)); + + // Labels + // ====== + // Create a HashMap of labels + // Labels represents a collection of label name -> value mappings. + let labels1 = HashMap::from([("label1".to_string(), "value1".to_string())]); + let mut labels2 = HashMap::new(); + labels2.insert("label2".to_string(), "value2".to_string()); + + // Gauge Metric + // ============ + // Initialize Gauge + let mut gauge = Gauge::from("gauge_name").with(labels1); + + // Sets the Gauge to an arbitrary value. + prom_ops.push(gauge.set(88.8)); + + // Increments the Gauge by 1. + prom_ops.push(gauge.inc()); + + // Decrements the Gauge by 1. + prom_ops.push(gauge.dec()); + + // Adds an arbitrary value to a Gauge. (The value can be negative, resulting in a rease of the Gauge.) + prom_ops.push(gauge.add(50.0)); + prom_ops.push(gauge.add(-10.0)); + + // Subtracts arbitrary value from the Gauge. (The value can be negative, resulting in an rease of the Gauge.) + prom_ops.push(gauge.sub(25.0)); + prom_ops.push(gauge.sub(-5.0)); + + // Set Gauge to the current Unix time in seconds. + prom_ops.push(gauge.set_to_current_time()); + + // Remove metrics for the given label values + prom_ops.push(gauge.remove(labels2)); + + // Reset gauge values + prom_ops.push(gauge.reset()); + + Ok(prom_ops) +} +``` diff --git a/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-sql.md b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-sql.md new file mode 100644 index 0000000..bf06985 --- /dev/null +++ b/firehose/substreams/docs/developers-guide/sink-targets/substreams-sink-sql.md @@ -0,0 +1,231 @@ +--- +description: StreamingFast Substreams SQL sink +--- + +# `substreams-sink-sql` introduction + +### Purpose + +Learn how to use the StreamingFast [`substreams-sink-sql`](https://github.com/streamingfast/substreams-sink-sql) tool with this documentation. A basic Substreams module example is provided to help you get started. We are going to showcase a Substreams module to extract data from the Ethereum blockchain and route it into a Protobuf for persistence in a SQL database. + +The `substreams-sink-sql` today supports two database drivers namely _PostgresSQL_ and _Clickhouse_. The tutorial below will focus on Postgres but we will describe how to connect to the other supported drivers. + +## Installation + +### 1. Install `substreams-sink-sql` + +Install `substreams-sink-sql` by using the pre-built binary release [available in the official GitHub repository](https://github.com/streamingfast/substreams-sink-sql/releases). + +Extract `substreams-sink-sql` into a folder and ensure this folder is referenced globally via your `PATH` environment variable. + +### 2. Set up accompanying code example + +Access the accompanying code example for this tutorial in the official `substreams-sink-sql` repository. You will find the Substreams project for the tutorial in the [docs/tutorial](https://github.com/streamingfast/substreams-sink-sql/tree/develop/docs/tutorial) directory. + +To create the required Protobuf files, run the included `make protogen` command. + +```bash +make protogen +``` + +To ensure proper setup and functionality, use your installation of the [`substreams` CLI](https://substreams.streamingfast.io/reference-and-specs/command-line-interface) to run the example code. + +Use the `make build` and `make stream_db` commands to verify the setup for the example project. Use the included `make` command to build the Substreams module. + +```bash +make build +make stream_db +``` + +### Module handler for sink + +The Rust source code file [`lib.rs`](https://github.com/streamingfast/substreams-sink-sql/blob/develop/docs/tutorial/src/lib.rs) contains an example code, the `db_out` module handler, which prepares and returns the module's [`DatabaseChanges`](https://docs.rs/substreams-database-change/latest/substreams_database_change/pb/database/struct.DatabaseChanges.html) output. The `substreams-sink-sql` tool captures the data sent out of the Substreams module and routes it into the appropriate columns and tables in the SQL database. + +```rust +#[substreams::handlers::map] +fn db_out(block_meta_start: store::Deltas>) -> Result { + let mut database_changes: DatabaseChanges = Default::default(); + transform_block_meta_to_database_changes(&mut database_changes, block_meta_start); + Ok(database_changes) +} +``` + +To gain a full understanding of the procedures and steps required for a database sink Substreams module, review the code in [`lib.rs`](https://github.com/streamingfast/substreams-sink-sql/blob/develop/docs/tutorial/src/lib.rs). The complete code includes the addition of a Substreams store module and other helper functions related to the database. + +**DatabaseChanges** + +The [`DatabaseChanges`](https://github.com/streamingfast/substreams-sink-database-changes/blob/develop/proto/sf/substreams/sink/database/v1/database.proto#L7) Protobuf definition can be viewed at the following link for a peek into the crates implementation. + +When developing your Substreams, the Rust crate [substreams-database-change](https://docs.rs/substreams-database-change/latest/substreams_database_change) can be used to create the required `DatabaseChanges` output type. + +**Note**: An output type of `proto:sf.substreams.sink.database.v1.DatabaseChanges` is required by the map module in the Substreams manifest when working with this sink. + +## 3. Install PostgreSQL + +To proceed with this tutorial, you must have a working PostgreSQL installation. Obtain the software by [downloading it from the vendor](https://www.postgresql.org/download/) and [install it by following the instructions](https://www.postgresql.org/docs/current/tutorial-install.html) for your operating system and platform. + +If you encounter any issues, [refer to the Troubleshooting Installation page](https://wiki.postgresql.org/wiki/Troubleshooting_Installation) on the official PostgreSQL Wiki for assistance. + +## 4. Create example database + +To store the blockchain data output by the Substreams module, you must create a new database in your PostgreSQL installation. The tutorial provides a schema and the PostgreSQL sink tool that handle the detailed aspects of the database design. + +Use the `psql` command in your terminal to launch PostgreSQL. + +Upon successful launch, you will see a prompt similar to the following, ready to accept commands for PostgreSQL. + +```bash +psql (15.1) +Type "help" for help. + +default-database-name=# +``` + +Use the following `SQL` command to create the example database: + +```bash +CREATE DATABASE "substreams_example"; +``` + +## 5. Create configuration file + +Once the database has been created, you must now define the Substreams Sink Config in a Substreams manifest creating a deployable unit. + +Let's create a folder `sink` and in it create a file called `substreams.dev.yaml` with the following content: + +```yaml +specVersion: v0.1.0 +package: + name: "" + version: + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.1/substreams-sink-sql-protodefs-v1.0.1.spkg + main: ../substreams.yaml + +network: 'mainnet' + +sink: + module: main:db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "../schema.sql" +``` + +The `package.name` and `package.version` are meant to be replaced to fit your project. + +The `imports.main` defines your Substreams manifest that you want to sink. The `sink.module` defines which import key (`main` here) and which module's name (`db_out` here). + +The `network` field defines which network this deployment should be part of, in our case `mainnet` + +The `sink.type` defines the type of the config that we are expecting, in our case it's [sf.substreams.sink.sql.v1.Service](https://buf.build/streamingfast/substreams-sink-sql/docs/main:sf.substreams.sink.sql.v1#sf.substreams.sink.sql.v1.Service) (click on the link to see the message definition). + +The `sink.config` is the instantiation of this `sink.type` with the config fully filled. Some config are special because they load from a file or from a folder. For example in our case the `sink.config.schema` is defined with a Protobuf option `load_from_file` which means the content of the `../schema.sql` will actually be inlined in the Substreams manifest. + +> The final final can be found at [`sink/substreams.dev.yaml`](https://github.com/streamingfast/substreams-sink-sql/blob/develop/docs/tutorial/sink/substreams.dev.yaml) + +## 6. Run setup command + +Use the following command to run the `substreams-sink-sql` tool and set up the database for the tutorial. + +```bash +substreams-sink-sql setup "psql://dev-node:insecure-change-me-in-prod@127.0.0.1:5432/substreams_example?sslmode=disable" ./sink/substreams.dev.yaml +``` + +The `"psql://..."` is the DSN (Database Source Name) containing the connection details to your database packed as an URL. The `scheme` (`psql` here) part of the DSN's url defines which driver to use, `psql` is what we are going to use here, see [Drivers](#drivers) section below to see what other DSN you can use here. + +The DSN's URL defines the database IP address, username, and password, which depend on your PostgreSQL installation. Adjust `dev-node` to your own username `insecure-change-me-in-prod` to your password and `127.0.0.1:5432` to where your database can be reached. + +### Drivers + +For DSN configuration for the currently supported drivers, see the list below: + +- [Clickhouse DSN](https://github.com/streamingfast/substreams-sink-sql#clickhouse) +- [PostgresSQL DSN](https://github.com/streamingfast/substreams-sink-sql#postgresql) + +## 7. Sink data to database + +The `substreams-sink-sql` tool sinks data from the Substreams module to the SQL database. Use the tool's `run` command, followed by the endpoint to reach and your Substreams config file to use: + +```bash +substreams-sink-sql run "psql://dev-node:insecure-change-me-in-prod@127.0.0.1:5432/substreams_example?sslmode=disable" ./sink/substreams.dev.yaml +``` + +The endpoint needs to match the blockchain targeted in the Substreams module. The example Substreams module uses the Ethereum blockchain. + +Successful output from the `substreams-sink-sql` tool will resemble the following: + +```log +2023-01-18T12:32:19.107-0800 INFO (sink-sql) starting prometheus metrics server {"listen_addr": "localhost:9102"} +2023-01-18T12:32:19.107-0800 INFO (sink-sql) sink from psql {"dsn": "psql://dev-node:insecure-change-me-in-prod@127.0.0.1:5432/substreams_example?sslmode=disable", "endpoint": "mainnet.eth.streamingfast.io:443", "manifest_path": "substreams.yaml", "output_module_name": "db_out", "block_range": ""} +2023-01-18T12:32:19.107-0800 INFO (sink-sql) starting pprof server {"listen_addr": "localhost:6060"} +2023-01-18T12:32:19.127-0800 INFO (sink-sql) reading substreams manifest {"manifest_path": "sink/substreams.dev.yaml"} +2023-01-18T12:32:20.283-0800 INFO (pipeline) computed start block {"module_name": "store_block_meta_start", "start_block": 0} +2023-01-18T12:32:20.283-0800 INFO (pipeline) computed start block {"module_name": "db_out", "start_block": 0} +2023-01-18T12:32:20.283-0800 INFO (sink-sql) validating output store {"output_store": "db_out"} +2023-01-18T12:32:20.285-0800 INFO (sink-sql) resolved block range {"start_block": 0, "stop_block": 0} +2023-01-18T12:32:20.287-0800 INFO (sink-sql) ready, waiting for signal to quit +2023-01-18T12:32:20.287-0800 INFO (sink-sql) starting stats service {"runs_each": "2s"} +2023-01-18T12:32:20.288-0800 INFO (sink-sql) no block data buffer provided. since undo steps are possible, using default buffer size {"size": 12} +2023-01-18T12:32:20.288-0800 INFO (sink-sql) starting stats service {"runs_each": "2s"} +2023-01-18T12:32:20.730-0800 INFO (sink-sql) session init {"trace_id": "4605d4adbab0831c7505265a0366744c"} +2023-01-18T12:32:21.041-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 2} +2023-01-18T12:32:21.206-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 2} +2023-01-18T12:32:21.319-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 0} +2023-01-18T12:32:21.418-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 0} +``` + +{% hint style="info" %} +**Note**: If you have an error looking like `load psql table: retrieving table and schema: pq: SSL is not enabled on the server`, it's because SSL is not enabled to reach you database, add `?sslmode=disable` at the end of the `sink.config.dsn` value to connect without SSL. +{% endhint %} + +You can view the database structure by using the following command, after launching PostgreSQL through the `psql` command. + +```bash +=# \c substreams_example +``` + +The table information is displayed in the terminal resembling the following: + +```bash + List of relations + Schema | Name | Type | Owner +--------+------------+-------+---------- + public | block_data | table | postgres + public | cursors | table | postgres +(2 rows) +``` + +You can view the data extracted by Substreams and routed into the database table by using the following command: + +```bash +substreams_example=# SELECT * FROM "block_data"; +``` + +Output similar to the following is displayed in the terminal: + +```bash + id | version | at | number | hash | parent_hash | timestamp +--------------------+---------+---------------------+--------+------------------------------------------------------------------+------------------------------------------------------------------+---------------------- + day:first:19700101 | | 1970-01-01 00:00:00 | 0 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 0000000000000000000000000000000000000000000000000000000000000000 | 1970-01-01T00:00:00Z + month:first:197001 | | 1970-01-01 00:00:00 | 0 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 0000000000000000000000000000000000000000000000000000000000000000 | 1970-01-01T00:00:00Z + day:first:20150730 | | 2015-07-30 00:00:00 | 1 | 88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 2015-07-30T15:26:28Z + month:first:201507 | | 2015-07-01 00:00:00 | 1 | 88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 2015-07-30T15:26:28Z + day:first:20150731 | | 2015-07-31 00:00:00 | 6912 | ab79f822909750f88dfb9dd0350c1ebe98d5495e9c969cdeb6e0ac993b80175b | 8ffd8c04cb89ef45e0e1163639d51d9ed4fa03dd169db90123a1e047361b46fe | 2015-07-31T00:00:01Z + day:first:20150801 | | 2015-08-01 00:00:00 | 13775 | 2dcecad4cf2079d18169ca05bc21e7ba0add7132b9382984760f43f2761bd822 | abaabb8f8b7f7fa07668fb38fd5a08da9814cd8ad18a793e54eef6fa9b794ab4 | 2015-08-01T00:00:03Z + month:first:201508 | | 2015-08-01 00:00:00 | 13775 | 2dcecad4cf2079d18169ca05bc21e7ba0add7132b9382984760f43f2761bd822 | abaabb8f8b7f7fa07668fb38fd5a08da9814cd8ad18a793e54eef6fa9b794ab4 | 2015-08-01T00:00:03Z +``` + +### Cursors + +When you use Substreams, it sends back a block to a consumer using an opaque cursor. This cursor points to the exact location within the blockchain where the block is. In case your connection terminates or the process restarts, upon re-connection, Substreams sends back the cursor of the last written bundle in the request so that the stream of data can be resumed exactly where it left off and data integrity is maintained. + +You will find that the cursor is saved in the `cursors` table of the `substreams_example` database. + +### Batching + +Insertion for historical blocks is performed in batched to increase ingestion speed. The `--flush-interval` flag can be used to change the default value of 1000 blocks. Also, the flag `--live-block-time-delta ` can be used to change the delta at which we start considering blocks to be live, the logic is `isLive = (now() - block.timestamp) < valueOfFlag(live-block-time-delta)`. + +## Conclusion and review + +Routing data extracted from the blockchain using Substreams is a powerful and useful feature. With Substreams, you can route data to various types of sinks, including files and databases such as PostgreSQL. For more information on other types of sinks and sinking strategies, consult the core Substreams sinks documentation at https://substreams.streamingfast.io/developers-guide/substreams-sinks. diff --git a/firehose/substreams/docs/documentation/consume/other-ways-of-consuming/README.md b/firehose/substreams/docs/documentation/consume/other-ways-of-consuming/README.md new file mode 100644 index 0000000..db0baae --- /dev/null +++ b/firehose/substreams/docs/documentation/consume/other-ways-of-consuming/README.md @@ -0,0 +1,2 @@ +# Other Ways of Consuming + diff --git a/firehose/substreams/docs/documentation/consume/sql/deployable-services/README.md b/firehose/substreams/docs/documentation/consume/sql/deployable-services/README.md new file mode 100644 index 0000000..5ae161b --- /dev/null +++ b/firehose/substreams/docs/documentation/consume/sql/deployable-services/README.md @@ -0,0 +1,2 @@ +# Deployable Services + diff --git a/firehose/substreams/docs/documentation/develop/chain-specific-extensions/README.md b/firehose/substreams/docs/documentation/develop/chain-specific-extensions/README.md new file mode 100644 index 0000000..d55cfbc --- /dev/null +++ b/firehose/substreams/docs/documentation/develop/chain-specific-extensions/README.md @@ -0,0 +1,2 @@ +# Chain-Specific Extensions + diff --git a/firehose/substreams/docs/documentation/develop/chain-specific-extensions/evm-chain-specific-extensions/README.md b/firehose/substreams/docs/documentation/develop/chain-specific-extensions/evm-chain-specific-extensions/README.md new file mode 100644 index 0000000..d32b501 --- /dev/null +++ b/firehose/substreams/docs/documentation/develop/chain-specific-extensions/evm-chain-specific-extensions/README.md @@ -0,0 +1,2 @@ +# EVM Chain-Specific Extensions + diff --git a/firehose/substreams/docs/documentation/develop/test-locally.md b/firehose/substreams/docs/documentation/develop/test-locally.md new file mode 100644 index 0000000..f64d2d8 --- /dev/null +++ b/firehose/substreams/docs/documentation/develop/test-locally.md @@ -0,0 +1,72 @@ +# Test Locally + +You can run a Substreams server locally to run e2e testing. + +It requires a local copy of full merged-blocks files for the range you want to test over. You can easily download those files from a Firehose endpoint. + +Then run the Substreams software locally, and run tests against it + +## Install binaries + +{% tabs %} +{% tab title="Firehose Core" %} +Install the `firehose-core` single binary for most chains (those without chain-specific extensions) with brew: + +```bash +brew install streamingfast/tap/firehose-core +``` + +or get a release from: [https://github.com/streamingfast/firehose-core/releases](https://github.com/streamingfast/firehose-core/releases) +{% endtab %} + +{% tab title="Firehose Ethereum" %} +Install the `firehose-ethereum` single binary with brew: + +```bash +brew install streamingfast/tap/firehose-ethereum +``` + +or get a release from: [https://github.com/streamingfast/firehose-ethereum/releases](https://github.com/streamingfast/firehose-core/releases) +{% endtab %} +{% endtabs %} + +## Download merged blocks locally + +Run against an endpoint for the chain you're interested in. For example: + +```bash +fireeth tools download-from-firehose mainnet.eth.streamingfast.io:443 1000 2000 \ + ./firehose-data/storage/merged-blocks +``` + +which will download merged blocks to your local disk. You might need to be authenticated. + +## Run the Substreams engine locally + +Run: + +```bash +fireeth start substreams-tier1,substreams-tier2 --config-file= \ + --common-live-blocks-addr= --common-first-streamable-block=1000 \ + --substreams-state-bundle-size=10 +``` + +**Notes**: + +1. the `--common-first-streamable-block` must be the lowest block available on disk, otherwise the server will fail to start. +2. if you need to do `eth_calls` with the Ethereum, you can add: `--substreams-rpc-endpoints https://example.com/json-rpc/somekeysometimes` +3. the `--substreams-state-bundle-size=10` flag will write smaller stores snapshot, suitable for dev + +This will run a fully workable stack + +## Stream against your local instance + +Test with: + +```bash +substreams run -e localhost:10016 --plaintext \ + https://spkg.io/streamingfast/ethereum-explorer-v0.1.2.spkg \ + map_block_meta -s 1000 -t +10 +``` + +and enjoy. diff --git a/firehose/substreams/docs/getting-started/installing-the-cli.md b/firehose/substreams/docs/getting-started/installing-the-cli.md new file mode 100644 index 0000000..eb2611d --- /dev/null +++ b/firehose/substreams/docs/getting-started/installing-the-cli.md @@ -0,0 +1,57 @@ +--- +description: StreamingFast Substreams CLI installation documentation +--- + +# Installing the Substreams CLI + +## Install the `substreams` CLI + +Used for connecting to endpoints, streaming data in real time, and packaging custom modules. + +{% hint style="success" %} +**Tip**_:_ [Check the official Github repository](https://github.com/streamingfast/substreams/releases) to get the **latest** [**`substreams` CLI**](../reference-and-specs/command-line-interface.md) **release available**. +{% endhint %} + +### Homebrew installation + +``` +brew install streamingfast/tap/substreams +``` + +### Pre-compiled binary installation + +```bash +# Use correct binary for your platform +LINK=$(curl -s https://api.github.com/repos/streamingfast/substreams/releases/latest | awk '/download.url.*linux/ {print $2}' | sed 's/"//g') +curl -L $LINK | tar zxf - +``` + +### Installation from source + +```bash +git clone https://github.com/streamingfast/substreams +cd substreams +go install -v ./cmd/substreams +``` + +{% hint style="warning" %} +**Important**: Add $HOME/go/bin to the system path if it's not already present. +{% endhint %} + +## Validation of installation + +Run the [`substreams` CLI](../reference-and-specs/command-line-interface.md) passing the `--version` flag to check the success of the installation. + +```bash +substreams --version +``` + +A successful installation will print the version that you have installed. + +```bash +substreams version dev +``` + +{% hint style="info" %} +**Note**: You can [also use Gitpod](../developers-guide/installation-requirements.md) instead of a local installation. +{% endhint %} diff --git a/firehose/substreams/docs/getting-started/quickstart.md b/firehose/substreams/docs/getting-started/quickstart.md new file mode 100644 index 0000000..2c70a46 --- /dev/null +++ b/firehose/substreams/docs/getting-started/quickstart.md @@ -0,0 +1,205 @@ +--- +description: Get off the ground by using Substreams by StreamingFast +--- + +# Quickstart + +## Run your first Substreams + +You will first need to get a StreamingFast API **key** from [https://app.streamingfast.io](https://app.streamingfast.io). Set it in your SUBSTREAMS_API_KEY environment variable. + +{% code overflow="wrap" %} +```bash +export SUBSTREAMS_API_KEY=server_123123 # Use your own API key +``` +{% endcode %} + +After you have authenticated, you're ready to [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) your first Substreams. + +{% hint style="success" %} +**Tip**: The [`substreams` CLI](../reference-and-specs/command-line-interface.md) [**must** **be installed** ](installing-the-cli.md)**to continue**. +{% endhint %} + +{% code overflow="wrap" %} +```bash +$ substreams gui -e mainnet.eth.streamingfast.io:443 https://github.com/streamingfast/substreams-ethereum-quickstart/releases/download/1.0.0/substreams-ethereum-quickstart-v1.0.0.spkg map_block --start-block 12292922 --stop-block +1 +``` +{% endcode %} + +The [`gui`](../reference-and-specs/command-line-interface/#run) command starts a consumer by using the `--endpoint` serving [a given blockchain](../reference-and-specs/chains-and-endpoints.md), for the [spkg package](../reference-and-specs/packages.md). Processing starts at the given block, then stops after processing one block. The output of the `map_block` [module](../developers-guide/modules/setting-up-handlers.md) is streamed to the GUI. + +The equivalent `run` command can be used to stream data to the `stdout`, without a graphical user interface: + +{% code overflow="wrap" %} +```bash +$ substreams run -e mainnet.eth.streamingfast.io:443 https://github.com/streamingfast/substreams-ethereum-quickstart/releases/download/1.0.0/substreams-ethereum-quickstart-v1.0.0.spkg map_block --start-block 12292922 --stop-block +1 +``` +{% endcode %} + +{% hint style="info" %} +**Note**: While Substreams technology is chain-agnostic, you must write your Substreams for a specific chain. In this quickstart, we are using Ethereum as our specific chain, general concepts given in the quick start applied to every Substreams supported chain. +{% endhint %} + +## Build a Substreams module + +In this section we are going to: + +* Create your first Substreams module +* Use the [`substreams` CLI](../reference-and-specs/command-line-interface.md) to run the module + +{% hint style="info" %} +**Note**: Before continuing, ensure that your system [meets the basic requirements](../developers-guide/installation-requirements.md) for Substreams development. +{% endhint %} + +### Create Substreams manifest + +To create a "Substreams module", you must first create the manifest file. This example manifest includes the minimal required fields to demonstrate the core values that you must provide. + +To use the example manifest, copy and paste it into a new file named `substreams.yaml`. Save this file in the root directory of your Substreams module. You can find the example manifest in the [official GitHub repository for `substreams-ethereum-quickstart`](https://github.com/streamingfast/substreams-ethereum-quickstart). + +```yaml +specVersion: v0.1.0 +package: + name: 'substreams_ethereum_quickstart' + version: v1.0.0 + +protobuf: + files: + - block_meta.proto + importPaths: + - ./proto + +binaries: + default: + type: wasm/rust-v1 + file: ./target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: map_block + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:acme.block_meta.v1.BlockMeta +``` + +### Create Rust manifest file + +To complete your new Substreams module, you must also create a Rust manifest file. + +To use the example Rust manifest file, copy and paste its content into a new file named [`Cargo.toml`](https://github.com/streamingfast/substreams-ethereum-quickstart/blob/main/Cargo.toml). Save this file in the root directory of your Substreams module. It's important to provide a unique and useful value for the "name" field and to make sure that `crate-type = ["cdylib"]` is defined so the WASM is generated. + +Include any dependencies on Substreams crates for helpers and `prost` for protobuf encoding and decoding. Finally, use the values provided in the example for the `profile.release` section to build an optimized `.WASM` file for your module. + +```toml +[package] +name = "substreams-ethereum-quickstart" +version = "1.0.0" +edition = "2021" + +[lib] +name = "substreams" +crate-type = ["cdylib"] + +[dependencies] +substreams = "0.5" +substreams-ethereum = "0.9" +prost = "0.11" + +[profile.release] +lto = true +opt-level = 'z' +strip = "debuginfo" +``` + +{% hint style="success" %} +**Tip**: The `[profile.release]` section can dramatically reduce the size of your wasm code and its loading time in substreams engine. `lto = true` is always beneficial to performance. `opt-level = 'z'` optimizes for size, which results in a reduced "Time To First Byte", in some cases by several seconds, which is usually beneficial, but it may not always be the best choice for performance. [See more info](https://docs.rust-embedded.org/book/unsorted/speed-vs-size.html). We do, however, recommend it. The `strip = "debuginfo"` will remove useful information from stack traces, but the faster load time still make it a good choice when iterating in development. +{% endhint %} + +### Create protobufs + +Substreams modules are required to output protobuf encoded messages. The example protobuf definition from the [`substreams-ethereum-quickstart`](https://github.com/streamingfast/substreams-ethereum-quickstart) defines a simple `BlockMeta` message that contains that block's hash, number, parent hash and timestamp all in human readable form. + +Copy and paste the content for the example protobuf definition into a new file named [`block_meta.proto`](https://github.com/streamingfast/substreams-ethereum-quickstart/blob/main/proto/block\_meta.proto) and save it to a `proto` directory in the root directory of your Substreams module. + +``` +syntax = "proto3"; + +package acme.block_meta.v1; + +message BlockMeta { + string hash = 1; + uint64 number = 2; + string parent_hash = 3; + string timestamp = 4; +} +``` + +Use the `substreams protogen` command to generate the Rust code to communicate with the protobuf: + +```bash +substreams protogen substreams.yaml --exclude-paths="sf/substreams,google" +``` + +{% hint style="info" %} +**Note**: The flag `--exclude-paths="sf/substreams,google"` avoids generating files which are already provided implicitly. +{% endhint %} + +### Create Substreams module handlers + +Your Substreams module must contain a Rust library that houses the module handlers, the code that is invoked to perform your customized logic. These handlers are responsible for handling blockchain data injected into the module at runtime, see [Substreams Modules](../developers-guide/modules/types.md) for further details about module and module handlers. + +To include this example module handler in your module, copy it into a new Rust source code file named [`lib.rs`](https://github.com/streamingfast/substreams-ethereum-quickstart/blob/main/src/lib.rs) within the `src` directory. + +{% code overflow="wrap" %} +```rust +mod pb; + +use pb::acme::block_meta::v1::BlockMeta; +use substreams::Hex; +use substreams_ethereum::pb::eth; + +#[substreams::handlers::map] +fn map_block(block: eth::v2::Block) -> Result { + let header = block.header.as_ref().unwrap(); + + Ok(BlockMeta { + number: block.number, + hash: Hex(&block.hash).to_string(), + parent_hash: Hex(&header.parent_hash).to_string(), + timestamp: header.timestamp.as_ref().unwrap().to_string(), + }) +} +``` +{% endcode %} + +Compile your Substreams module. + +```bash +cargo build --release --target wasm32-unknown-unknown +``` + +{% hint style="info" %} +**Note**: If you have a lots of weird compilation errors like `cannot find function, tuple struct or tuple variant` Ok `in this scope`, `cannot find macro 'assert' in this scope`, `cannot find tuple struct or tuple variant 'Some' in this scope`, etc. you probably don't have the target `wasm32-unknown-unknown` installed in your Rust environment, install it with `rustup target add wasm32-unknown-unknown`, see [Rust installation](../developers-guide/installation-requirements.md#wasm32-unknown-unknown-target) extra details. +{% endhint %} + +### Execute + +To execute, or run, the example use the [`substreams run`](../reference-and-specs/command-line-interface.md#run) command: + +{% code overflow="wrap" %} +```bash +substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml map_block --start-block 10000001 --stop-block +1 +``` +{% endcode %} + +To ensure that the [`run`](../reference-and-specs/command-line-interface.md#run) command is executed correctly, you need to pass the proper endpoint, manifest name, and module handler name. The `--start-block` and `--stop-block` flags are optional, but they can help limit the results that are returned to the client. + +You have successfully created your first Substreams module that extracts data from the Ethereum blockchain. + +## Next steps + +* [Modules](../developers-guide/modules/) +* [Substreams fundamentals](../concepts-and-fundamentals/fundamentals.md) +* [Protobuf schemas](../developers-guide/creating-protobuf-schemas.md) +* [Substreams Template](https://github.com/streamingfast/substreams-template) diff --git a/firehose/substreams/docs/glossary/glossary.md b/firehose/substreams/docs/glossary/glossary.md new file mode 100644 index 0000000..40397db --- /dev/null +++ b/firehose/substreams/docs/glossary/glossary.md @@ -0,0 +1,92 @@ +# Glossary + +## Substreams +[Substreams](https://substreams.streamingfast.io/) is a powerful indexing technology, which allows you to: +1. Extract data from several blockchains (Ethereum, Polygon, BNB, Solana...). +2. Apply custom transformations to the data. +3. Send the data to a place of your choice (for example, a Postgres database or a file). + +## Firehose +[Firehose](https://firehose.streamingfast.io/) is the extraction layer of Substreams (i.e. step number one of the previous glossary entry). Although Firehose is a different project, it is tightly related to Substreams. + +## CLI +`CLI`, which stands for command-line interface, is a text-based interface that allows you to input commands to interact with a computer. +The [Substreams CLI](https://substreams.streamingfast.io/getting-started/installing-the-cli) allows you to deploy and manage your Substreams. + +## Module +[Modules](https://substreams.streamingfast.io/developers-guide/modules) are small pieces of Rust code running in a WebAssembly (WASM) virtual machine. Modules have one or more inputs and an output. +For example, a module could receive an Ethereum block as input and emit a list of transfers for that block as output. + +There are two types of modules: `map` and `store`. + +## map Module +`map` modules receive an input and emit an output (i.e. they perform a transformation). + +## store Module +`store` modules write to key-value stores and are stateful. They are useful in combination with `map` modules to keep track of past data. + +## Directed Acyclic Graph (DAG) +[DAGs](https://en.wikipedia.org/wiki/Directed_acyclic_graph) are data structures used in many computational models. In Substreams, DAGs are used to define module data flows. + +A DAG is a one-direction, acyclic graph. They are used in a variety of software, such as Git or IPFS. + +## Composability +Modules make Substreams really _composable_. Being composable means that Substreams can be independent, but they can also work together to create powerful streams. + +For example, consider that you have two _map modules_: one emitting `Transfer` objects and another one emitting `AccountInformation` objects. +You could create another module that receives the previous two modules as input and merges the information from both. + +That is why Substreams is so powerful! + +## Protocol Buffers (Protobuf) +[Protocol Buffers](https://protobuf.dev/) is a serializing format used to define module inputs and outputs in Substreams. +For example, a manifest might define a module called `map_tranfers` with an input object, `Transfer` (representing an Ethereum transaction), and an output object `MyTransfer` (representing a reduced version of an Ethereum transaction). + +## Manifest +The [Substreams manifest](https://substreams.streamingfast.io/developers-guide/creating-your-manifest) (called `substreams.yaml`) is a YAML file where you define all the configurations needed. For example, the modules of your Substreams (along with their inputs and outputs), or the Protobuf definitions used. + +## WebAssembly (WASM) +[WebAssembly (WASM)](https://webassembly.org/) is a binary-code format used to run a Substreams. The Rust code used to define your Substreams transformations is packed into a WASM module, which you can use as an independent executable. + +## Block +The `Block` Protobuf object contains all the blockchain information for a specific block number. EVM-compatible chains share the same [Block](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto) object, but non-EVM-compatible chains must use [their corresponding Block Protobuf definition](https://substreams.streamingfast.io/reference-and-specs/chains-and-endpoints). + +
+ +## SPKG (.spkg) +[SPKG files](https://substreams.streamingfast.io/reference-and-specs/packages) contain Substreams definitions. You can create an `.spkg` file from a Substreams manifest using the `substreams pack` command. Then, you can use this file to share or run the Substreams independently. +The `.spkg` file contains everything needed to run a Substreams: Rust code, Protobuf definitions and the manifest. + +## GUI +The CLI includes two commands to run a Substreams: `run` and `gui`. The `substreams run` command prints the output of the execution linearly for every block, while the `substreams gui` allows you to easily jump to the output of a specific block. + +
+ +## Subgraph +[Subgraphs](https://thegraph.com/docs/en/developing/creating-a-subgraph/) are another indexing mechanism developed by The Graph. +In Subgraphs, data is indexed and available through a GraphQL endpoint. + +One of the main differences between Subgraphs and Substreams is that Subgraphs rely on _polling_, while Substreams relies on _streaming_. + +## Triggers +In Subgraphs, you define _triggers_ to index your data. These triggers are events that happen in the blockchain (for example, `AccountCreated`). Subgraphs listen for those events, and index the data accordingly. + +## Sink +Substreams allows you to extract blockchain data and apply transformations to it. After that, you should choose **a place to send your transform data, which is called _sink_**. +A sink can be a [SQL database](https://substreams.streamingfast.io/developers-guide/sink-targets/substreams-sink-sql), [a file](https://substreams.streamingfast.io/developers-guide/sink-targets/substreams-sink-files) or a [custom solution of your choice](https://substreams.streamingfast.io/developers-guide/sink-targets/custom-sink-js). + +## Deployable Unit +A deployable unit is a Substreams manifest or package (spkg) that contains all the information about how to run it from sink service. In the manifest, it corresponds to the `network` and `sink` fields. +See [Working with deployable units](https://substreams.streamingfast.io/developers-guide/sink-deployable-units) + + +## Substreams-powered Subgraph +When a Subgraph acts as a sink for your Substreams, you call it a [Substreams-powered Subgraph](https://thegraph.com/docs/en/cookbook/substreams-powered-subgraphs/). + +The Subgraph Sink is one of the _official sinks_ supported by Substreams, and can help you index your Subgraph way faster! + +## Parallel execution +[Parallel execution](https://substreams.streamingfast.io/developers-guide/parallel-execution) is the process of a Substreams module's code executing multiple segments of blockchain data simultaneously in a forward or backward direction. + +## Workers +Workers are the fundamental unit of parallelizing in Substreams. Workers are computer processes that run in parallel to speed up the Substreams computations. diff --git a/firehose/substreams/docs/new/common/authentication.md b/firehose/substreams/docs/new/common/authentication.md new file mode 100644 index 0000000..4bafe7f --- /dev/null +++ b/firehose/substreams/docs/new/common/authentication.md @@ -0,0 +1,72 @@ +--- +description: StreamingFast Substreams authentication reference +--- + +# Authentication + +Running a Substreams involves sending your package (`.spkg`) to a a Substreams provider for execution. Usually, Substreams provider will require you to authenticate to avoid abuses on the service. + +### Authentication with StreamingFast + +Authentication can be done either directly from an API key or from a derived JWT. + +#### Get your API key + +First, obtain an API key by visiting our Portal: + +* [https://app.streamingfast.io](https://app.streamingfast.io) + +The StreamingFast team is also available on [Discord](https://discord.gg/jZwqxJAvRs) to help you. + +#### Authenticate with your API key + +The substreams server expects the `X-Api-Key` header to be set with your API key. Here's how you do it in the terminal: + +Set the token as an `ENV` variable through the terminal by using: + +```bash +export SUBSTREAMS_API_KEY="server_0123456789abcdef0123456789abcdef" +``` + +The `substreams` [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) and [`gui`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#gui) commands check the `SUBSTREAMS_API_KEY` environment variable for the key by default. You can change that with the `--substreams-api-key-envvar` flag. + +#### Authentication using a JWT (optional) + +Streamingfast also provides a way to generate a token (JWT) from your API key and use it as authentication. The advantage of that method is that you can manage the JWT expiration, more suitable for using in web apps or tighter security standards. + +#### Request your authentication token + +Use your API Key to obtain an authentication token using `curl`: + +```bash +# lifetime is the token duration in seconds +curl -s https://auth.streamingfast.io/v1/auth/issue --data-binary '{"api_key": "your-api-key", "lifetime": 3600}' +``` + +#### Use it in your requests + +The substreams server expects the standard `Authorization: bearer your_api_key` header format for JWT-based authentication. Here's how you do it in the terminal: + +```bash +export SUBSTREAMS_API_TOKEN="your_token" +``` + +The `substreams` [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) and [`gui`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#gui) commands check the `SUBSTREAMS_API_TOKEN` environment variable for the token by default. You can change that with the `--substreams-api-token-envvar` flag. + +#### All-in-one bash function + +Place this function in your terminal profile (`.bashrc` or `.zshrc`), for a quick all-in-one token fetcher: + +```bash +export STREAMINGFAST_KEY=server_YOUR_KEY_HERE +function sftoken { + export SUBSTREAMS_API_TOKEN=$(curl https://auth.streamingfast.io/v1/auth/issue -s --data-binary '{"api_key":"'$STREAMINGFAST_KEY'"}' | jq -r .token) + echo "Token set on in SUBSTREAMS_API_TOKEN" +} +``` + +Then obtain a new key and set it in your environment by running: + +```bash +$ sftoken +``` diff --git a/firehose/substreams/docs/new/common/deployable-services.md b/firehose/substreams/docs/new/common/deployable-services.md new file mode 100644 index 0000000..cd3fd7c --- /dev/null +++ b/firehose/substreams/docs/new/common/deployable-services.md @@ -0,0 +1,40 @@ +The Substreams Deployable Services define a common interface to easily deploy your Substreams to one of the supported sinks, such as SQL or subgraphs. Essentially, it facilitates sending data to a variety of sinks by simply using the Substreams CLI. + +## Hoes Does It Work? + +1. Choose what sink you want to use (SQL or subgraphs). +2. Add the `sink` configuration to your manifest. +3. Use the `substreams alpha service` command to deploy, stop or remove your services. + +### Choose a Sink + +Depending on your needs, you must choose how you want to consume the data: using a SQL database or a subgraph. Substreams using the SQL sink must have a `db_out` module and those using the subgraph sink must have a `graph_out` module. + +### Add the Sink Configuration + +The `sink` configuration in a Substreams manifest defines what sink should be used. To get more information about the Substreams manifest, refer to the [Manifest & Modules page](manifest-modules.md) + +Every sink has different configuration fields available, so check out the Manifest Reference for more information. In the following example, a SQL sink is defined: + +``` +sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + engine: clickhouse + postgraphile_frontend: + enabled: false + pgweb_frontend: + enabled: false + dbt_config: + enabled: true + files: "./path/to/folder" + run_interval_seconds: 300 +``` + +### Deploy the Service + +Use the `substreams alpha service ` command to manage your services. Once your Substreams has the corresponding manifest configuration, you can deploy it by using the `substreams alpha service deploy` command. + +You will get a service ID, which is a unique identifier for your service. This will allow you to manage your service and apply actions to it, such a stopping or removing it. \ No newline at end of file diff --git a/firehose/substreams/docs/new/common/installing-the-cli.md b/firehose/substreams/docs/new/common/installing-the-cli.md new file mode 100644 index 0000000..4628d68 --- /dev/null +++ b/firehose/substreams/docs/new/common/installing-the-cli.md @@ -0,0 +1,101 @@ +--- +description: StreamingFast Substreams CLI installation documentation +--- + +# Installing the Substreams CLI + +## Dependency installation + +Substreams requires a number of different applications and tools. Instructions and links are provided to assist in the installation of the required dependencies for Substreams. + +{% hint style="success" %} +**Tip**: Instructions are also provided for cloud-based Gitpod setups. +{% endhint %} + +### Rust installation + +Developing Substreams modules requires a working [Rust](https://www.rust-lang.org/) compilation environment. + +There are [several ways to install Rust](https://www.rust-lang.org/tools/install)**.** Install Rust through `curl` by using: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env # to configure your current shell +``` + +#### `wasm32-unknown-unknown` target + +Ensure you have the `wasm32-unknown-unknown` target installed on your Rust installation, if unsure, you can install it with: + +```bash +rustup target add wasm32-unknown-unknown +``` + +### Buf installation + +Buf simplifies the generation of typed structures in any language. Buf uses a remote builder executed on the Buf server, so an internet connection is required to generate Rust bindings from Protobuf definitions. + +Visit the [Buf website](https://buf.build/) for additional information and [installation instructions](https://docs.buf.build/installation). + +{% hint style="info" %} +**Note**_:_ [Substreams packages](../reference-and-specs/packages.md) and [Buf images](https://docs.buf.build/reference/images) are compatible. +{% endhint %} + +## Install the `substreams` CLI + +Used for connecting to endpoints, streaming data in real time, and packaging custom modules. + +### Homebrew installation + +``` +brew install streamingfast/tap/substreams +``` + +### Pre-compiled binary installation + +There are several CLI binaries available for different operating systems. Choose the correct platform in the [CLI releases page](https://github.com/streamingfast/substreams/releases). + +If you are on MacOS, you can use the following command: + +```bash +LINK=$(curl -s https://api.github.com/repos/streamingfast/substreams/releases/latest | awk "/download.url.*$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m)/ {print \$2}" | sed 's/"//g') +curl -L $LINK | tar zxf - +``` + +If you are on Linux, you can use the following command: + +```bash +# Use correct binary for your platform +LINK=$(curl -s https://api.github.com/repos/streamingfast/substreams/releases/latest | awk "/download.url.*linux_$(uname -m)/ {print \$2}" | sed 's/"//g') +curl -L $LINK | tar zxf - +``` + +### Installation from source + +```bash +git clone https://github.com/streamingfast/substreams +cd substreams +go install -v ./cmd/substreams +``` + +{% hint style="warning" %} +**Important**: Add $HOME/go/bin to the system path if it's not already present. +{% endhint %} + +## Validation of installation + +Run the [`substreams` CLI](../reference-and-specs/command-line-interface.md) passing the `--version` flag to check the success of the installation. + +```bash +substreams --version +``` + +A successful installation will print the version that you have installed. + +```bash +substreams version dev +``` + +{% hint style="info" %} +**Note**: You can [also use Gitpod](../developers-guide/installation-requirements.md) instead of a local installation. +{% endhint %} \ No newline at end of file diff --git a/firehose/substreams/docs/new/common/intro-evm.md b/firehose/substreams/docs/new/common/intro-evm.md new file mode 100644 index 0000000..6f0fc21 --- /dev/null +++ b/firehose/substreams/docs/new/common/intro-evm.md @@ -0,0 +1,13 @@ +Substreams is available for several EVM-compatible chains, such as Ethereum, Polygon or Avalanche. + + +## Getting Started with Substreams + +If you intention is to consume an already developed Substreams package... + + +## The EVM Data Model + +## Eth Calls + +## Tutorials \ No newline at end of file diff --git a/firehose/substreams/docs/new/common/intro-solana.md b/firehose/substreams/docs/new/common/intro-solana.md new file mode 100644 index 0000000..c5ea39d --- /dev/null +++ b/firehose/substreams/docs/new/common/intro-solana.md @@ -0,0 +1,63 @@ +Substreams allows you to easily extract data from the the Solana blockchain. With Substreams, you can retrieve transactions, instructions or accounts, taking advantage of its powerful streaming technology. It's super fast! + +
+ +## Getting Started + +First, you must consider whether you want to develop your own Substreams or consume a ready-to-use Substreams. It is possible that someone has already built a Substreams package to extract the data you want; you can explore Substreams packages in the [Substreams Registry](https://substreams.dev). + +**If you have found a Substreams package that fits your needs**, then explore the [Consume Substreams](../consume/consume.md) section. At the most basic level you should cover: + +- [Install the Substreams CLI](./installing-the-cli.md) +- [Authentication](./authentication.md) +- [Packages](./packages.md) +- Choose how you want to consume the data: + - [Send the data to a SQL database.](./../consume/sql/sql.md) + - [Stream the data from your application.](../consume/stream/stream.md) + + +**If you can't find a Substreams package that fits your needs**, then you can go ahead and develop your own Substreams. The [Develop Substreams](../develop/develop.md) section of the documentation covers everything you need to know about building a Substreams from scratch. At the most basic level, you should cover: + +- [Install the Substreams CLI](./installing-the-cli.md) +- [Authentication](./authentication.md) + +- [Manifest & Modules](./../common/manifest-modules.md) +- [Protobuf defitions](./../develop/creating-protobuf-schemas.md) +- [Packages](./packages.md) +- [Run a Substreams](./running-substreams.md) +- [Choose how you want to consume the data](./../consume/consume.md) + +## Tutorials + +If you want to deep dive into the code, you can follow one or several of the Solana Tutorials available in the documentation. +- The [Explore Solana Tutorial](../tutorials/solana/explore-solana/explore-solana.md) will teach you the most basic operations you can perform in a Solana Substreams. +- The [Solana Token Tracker Tutorial](../tutorials/solana/token-tracker/token-tracker.md) will teach how to track an SPL token of your choice. +- The [NFT Trades Tutorial](../tutorials/solana/top-ledger/nft-trades.md) will help you in extracting data from different NFT exchanges. +- The [DEX Trades Tutorial](../tutorials/solana/top-ledger/dex-trades.md) will help you in extracting data from different decentralized exchanges. + +## The Solana Data Model + +A Substreams module is, essentially, a Rust function that extracts data from the blockchain. In order to specify which data you want to retrieve, Substreams gives you access to the abstraction of a full [Solana block](https://github.com/streamingfast/firehose-solana/blob/develop/proto/sf/solana/type/v1/type.proto#L9). + +In the following example, a Solana block (`solana::Block`) is passed as a parameter. Then, a custom object defined by the user, `BlockMeta`, is emitted as output, containing some relevant fields (`slot`, `hash`, `parent_hash`): + +```rust +fn map_block_meta(block: solana::Block) -> Result { + Ok(BlockMeta { + slot: blk.slot, + hash: blk.blockhash, + parent_hash: blk.previous_blockhash, + }) +} +``` + +The [Block](https://github.com/streamingfast/firehose-solana/blob/develop/proto/sf/solana/type/v1/type.proto#L9) object holds other important data, such as: +- [block.transactions_owned()](https://github.com/streamingfast/substreams-solana/blob/1f66cc3081f61ad1189dc814cb82096ae5ac4b3b/core/src/block_view.rs#L15) +- [block.rewards](https://github.com/streamingfast/firehose-solana/blob/develop/proto/sf/solana/type/v1/type.proto#L64) +- [block.parent_slot](https://github.com/streamingfast/firehose-solana/blob/develop/proto/sf/solana/type/v1/type.proto#L12) + +## The Account Address Tables + +In Solana, the _account_ concept plays a very important role. However, the original Solana data model imposes a restriction on the number of accounts that a single transaction can have. The [Account Address Tables](https://docs.solana.com/developing/lookup-tables) is a way to overcome this restriction, allowing developers to increase the number of accounts per transaction. + +The [resolved_accounts()](https://github.com/streamingfast/substreams-solana/blob/1f66cc3081f61ad1189dc814cb82096ae5ac4b3b/core/src/lib.rs#L9) method of the [ConfirmedTransaction](https://github.com/streamingfast/substreams-solana/blob/1f66cc3081f61ad1189dc814cb82096ae5ac4b3b/core/src/lib.rs#L8) object includes a ready-to-use array of accounts, including accounts from the Lookup Table. \ No newline at end of file diff --git a/firehose/substreams/docs/new/common/manifest-modules.md b/firehose/substreams/docs/new/common/manifest-modules.md new file mode 100644 index 0000000..6ba39a4 --- /dev/null +++ b/firehose/substreams/docs/new/common/manifest-modules.md @@ -0,0 +1,129 @@ +--- +description: Learn the basics about modules and manifests +--- + +## Modules and Manifests + +In Substreams, manifests and modules are concepts tighly related because they are fundamental to understand how Substreams works. + +In simple terms, a Substreams module is a Rust function that receives an input and returns an output. For example, the following Rust function receives an Ethereum block and returns a custom object containing fields such as block number, hash or parent hash. + +```rust +fn get_my_block(blk: Block) -> Result { + let header = blk.header.as_ref().unwrap(); + + Ok(MyBlock { + number: blk.number, + hash: Hex::encode(&blk.hash), + parent_hash: Hex::encode(&header.parent_hash), + }) +} +``` + +And also in simple terms, a Substreams manifest (`substreams.yaml`) is a configuration file (a YAML file) for your Substreams, which defines the different modules (functions) for your Substreams, among other configurations. For example, the following manifest receives a raw Ethereum block as input (`sf.ethereum.type.v2.Block`) and outputs a custom object (`eth.example.MyBlock`). + +```yaml +modules: + - name: map_block + kind: map + initialBlock: 12287507 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:eth.example.MyBlock +``` + +Among other things, the manifest allows you to define: +- How many modules your Substreams uses, along with their corresponding inputs and outputs. +- The schema(s) (i.e. the data model) your Substreams uses. +- How you will consume the data emitted by your Substreams (SQL, Webhooks...). + +## Module Chaining + +Modules were built with composability in mind, so it is possible to chain them. Given two modules, `module1` and `module2`, you can set the output of `module1` to be the input of `module2`, creating a chain of interconnected Substreams modules. Let's take a look at the following example: + +```yaml +modules: + - name: map_events + kind: map + initialBlock: 4634748 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:contract.v1.Events + + - name: db_out + kind: map + initialBlock: 4634748 + inputs: + - map: map_events + output: + type: proto:sf.substreams.sink.database.v1.DatabaseChanges +``` + +There are two modules defined: `map_events` and `graph_out`. +- The `map_events` module receives a `sf.ethereum.type.v2.Block` object (a raw Ethereum block) as a parameter and outputs a custom `contract.v1.Events` object. +- The `db_out` module receives `map_events`'s output as an input, and outputs another custom object, `sf.substreams.sink.database.v1.DatabaseChanges`. + +Technically, modules have one or more inputs, which can be in the form of a `map` or `store`, or a `Block` or `Clock` object received from the blockchain's data source. Every time a new `Block` is processed, all of the modules are executed as a directed acyclic graph (DAG). + +## Module Kinds + +There are two types of modules: `map` and `store`. `map` modules are used for stateless transformations and `store` modules are used for stateful transformations. + +Substreams executes the Rust function associated with module for every block on the blockchain, but there will be times when you will have to save data between blocks. `store` modules allow you to save in-memory data. + +### `map` modules + +`map` modules are used for data extraction, filtering, and transformation. They should be used when direct extraction is needed avoiding the need to reuse them later in the DAG. + +To optimize performance, you should use a single `map` module instead of multiple `map` modules to extract single events or functions. It is more efficient to perform the maximum amount of extraction in a single top-level `map` module and then pass the data to other Substreams modules for consumption. This is the recommended, simplest approach for both backend and consumer development experiences. + +Functional `map` modules have several important use cases and facts to consider, including: + +* Extracting model data from an event or function's inputs. +* Reading data from a block and transforming it into a custom protobuf structure. +* Filtering out events or functions for any given number of contracts. + +### `store` modules + +`store` modules are used for the aggregation of values and to persist state that temporarily exists across a block. + +{% hint style="warning" %} +**Important:** Stores should not be used for temporary, free-form data persistence. +{% endhint %} + +Unbounded `store` modules are discouraged. `store` modules shouldn't be used as an infinite bucket to dump data into. + +Notable facts and use cases for working `store` modules include: + +* `store` modules should only be used when reading data from another downstream Substreams module. +* `store` modules cannot be output as a stream, except in development mode. +* `store` modules are used to implement the Dynamic Data Sources pattern from Subgraphs, keeping track of contracts created to filter the next block with that information. +* Downstream of the Substreams output, do not use `store` modules to query anything from them. Instead, use a sink to shape the data for proper querying. + +## Defining Modules + +Modules are defined as a YAML list under the `modules` section of the manifest. In the following example, a `map_events` module is defined: + +```yaml +modules: + - name: map_events + kind: map + initialBlock: 4634748 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:contract.v1.Events +``` + +Then, you create the corresponding Rust function under the `src/lib.rs` file. + +```rust +#[substreams::handlers::map] +fn map_events(blk: eth::Block) -> Result { + +...output omitted... + +} +``` diff --git a/firehose/substreams/docs/new/common/packages.md b/firehose/substreams/docs/new/common/packages.md new file mode 100644 index 0000000..8e5eb97 --- /dev/null +++ b/firehose/substreams/docs/new/common/packages.md @@ -0,0 +1,48 @@ +--- +description: Learn about basics of Substreams packages +--- + +There are a lot of developers building Substreams and creating very useful transformations that can be reused by other people. +Once a Substreams is developed, you can pack it into a Substreams package and share it with other people! + +Essentially, a Substreams package is a ready-to-consume binary file, which contains all the necessary dependencies (manifest, modules, protobufs...). The standard file extension for a Substreams package is `.spkg`. + +
+ +## The Substreams Registry + +In order to facilitate how developers share Substreams packages, the Substreams Registry (https://substreams.dev) was created. In the Registry, developers can discover and push Substreams. + +For example, the [ERC20 Balance Changes](https://github.com/streamingfast/substreams-erc20-balance-changes) package is stored at the registry (https://substreams.dev/streamingfast/erc20-balance-changes/v1.1.0). + +
+ +## Using a Package + +You can easily run a Substreams package by inputting the `.spkg` file in the CLI: + +```bash +substreams gui \ + https://spkg.io/streamingfast/erc20-balance-changes-v1.1.0.spkg \ + map_balance_changes \ + -e mainnet.eth.streamingfast.io:443 \ + --start-block 1397553 +``` + +## Creating a Package + +You can create a Substreams package by executing the `substreams pack` command in the CLI. Given a Substreams project, you can create a new package from a manifest (`substreams.yaml`): + +```bash +substreams pack ./substreams.yaml +``` + +### Package Dependencies + +Developers can use modules and protobuf definitions from other Substreams packages when `imports` is defined in the manifest. + +{% hint style="warning" %} +**Important**: To avoid potential naming collisions select unique `.proto` filenames and namespaces specifying fully qualified paths. +{% endhint %} + +Local protobuf filenames take precedence over the imported package's proto files. \ No newline at end of file diff --git a/firehose/substreams/docs/new/common/reliability-guarantees.md b/firehose/substreams/docs/new/common/reliability-guarantees.md new file mode 100644 index 0000000..3cbb6cb --- /dev/null +++ b/firehose/substreams/docs/new/common/reliability-guarantees.md @@ -0,0 +1,63 @@ + +## Reliability Guarantees + +When you consume a Substreams package through the CLI (or through any of the different sinks available), you are establishing a gRPC connection with the Substreams provider (i.e. StreamingFast, Pinax...), which streams the data of every block back to your sink. + +### The Response Format +The response returned by the provider is a [Protobuf object](https://github.com/streamingfast/substreams/blob/831093480ab6bf6970e41f74ea9bc0b04410a028/proto/sf/substreams/rpc/v2/service.proto#L53), which contains the blockchain data plus other relevant information: + +```protobuf +message Response { + oneof message { + SessionInit session = 1; + ModulesProgress progress = 2; + BlockScopedData block_scoped_data = 3; + BlockUndoSignal block_undo_signal = 4; + Error fatal_error = 5; + + InitialSnapshotData debug_snapshot_data = 10; + InitialSnapshotComplete debug_snapshot_complete = 11; + } +} +``` + +### Data & Cursor + +One of the most important fields of the response is the `BlockScopedData` object, which contains the actual data of the blockchain, along with other useful fields. Specifically, The `output` field holds the binary data emitted by the Substreams. + +```protobuf +message BlockScopedData { + MapModuleOutput output = 1; + sf.substreams.v1.Clock clock = 2; + string cursor = 3; + + uint64 final_block_height = 4; + + repeated MapModuleOutput debug_map_outputs = 10; + repeated StoreModuleOutput debug_store_outputs = 11; +} +``` + +In a connection, errors might occur; any of the two parties involved may get disconnected because of a network issue. +In theses cases, it is essential to have a mechanism that allows you to consume the data exactly where you left it before the disconnection. This mechanism is usually called a **cursor**. Essentially, a cursor points to the latest piece of data consumed by the user. + +In Substreams, the `cursor` field of the response indicates the latest block consumed by the user. The user **must** persist the cursor, so that in the case of a disconnection, the Substreams provider can start streaming data from the latest consumed block. + +For example, the SQL sink establishes a gRPC connection with the Substreams provider, and for every block consumed, it persists the number of the block in a table. If a disconnection occurs, the SQL sink establishes a new connection and starts consuming from the latest persisted block. That's why it is very important to persist the cursor! + +### Forks + +Forks are really common in blockchain. Essentially, a fork occurs when the path of the blockchain diverges (i.e. there are two or more different paths available because different the nodes involved do not agree on the correct path). + +The `BlockUndoSignal` object of the response is used to keep track of forks. In Substreams, you are reading real-time data, so if a fork occurs, you may read blocks from the incorrect path. When the blockchain resolves the fork and eventually chooses a path, you will have to _unread_ all the incorrect blocks (i.e. discard all the blocks belonging to the incorrect path of the fork). The `BlockUndoSignal` contains the latest valid block of the blockchain and a cursor: + +```protobuf +message BlockUndoSignal { + sf.substreams.v1.BlockRef last_valid_block = 1; + string last_valid_cursor = 2; +} +``` + +{% hint style="info" %} +If you commit cursors in the BlockUndoSignals, you don’t need to mind about disconnections amid forks. It will bring you back exactly where you left off, even if it was mid-ways through a fork. +{% endhint %} diff --git a/firehose/substreams/docs/new/common/running-substreams.md b/firehose/substreams/docs/new/common/running-substreams.md new file mode 100644 index 0000000..7580b79 --- /dev/null +++ b/firehose/substreams/docs/new/common/running-substreams.md @@ -0,0 +1,129 @@ + +Running a Substreams means executing a Substreams package and consuming the data emitted. The data produced by a Substreams can be consumed in a variety of ways, such as using the CLI, a SQL database or a simple files. + +## Running a Local Project + +A typical Substreams local project contains Protobufs (`1`), modules (`2`) and a manifest (`substreams.yaml`) file (`3`): + +

Ethereum Explorer Project Structure

+ +If you are currently working on a local project, you can run your Substreams by specifying the location of the manifest: + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 \ + substreams.yaml \ + map_transfers \ + --start-block 12292922 \ + --stop-block +1 +``` + +The `--start-block` flag specifies the starting block of the Substreams (i.e. at which block the Substreams will start indexing data) + +{% hint style="info" %} +**Note**: In Solana, `--start-block` specifies the **slot**, not the block numnber. +{% endhint %} + +### Substreams `run` + +First, start the [`substreams` CLI](../reference-and-specs/command-line-interface.md) passing it a [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) command. + +### Firehose URI + +The server address is required by Substreams to connect to for data retrieval. The data provider for Substreams is located at the address, which is a running Firehose instance.\ +`-e mainnet.eth.streamingfast.io:443` + +### Substreams YAML configuration file + +Inform Substreams where to find the `substreams.yaml` configuration file. + +{% hint style="info" %} +**Note**: The `substreams.yaml` configuration file argument in the command is optional if you are within the root folder of your Substreams and your manifest file is named `substreams.yaml. +{% endhint %} + +### Module + +The `map_transfers` module is defined in the manifest and it is the module run by Substreams. + +### Block mapping + +Start mapping at the specific block `12292922` by using passing the flag and block number `--start-block 12292922`. + +Cease block processing by using `--stop-block +1.` The `+1` option requests a single block. In the example, the next block is `12292923`. + +### Successful Substreams results + +Messages are printed to the terminal for successfully installed and configured Substreams setups. + +```bash + substreams run -e mainnet.eth.streamingfast.io:443 \ + substreams.yaml \ + map_transfers \ + --start-block 12292922 \ + --stop-block +1 +``` + +The `substreams` [`run`](https://substreams.streamingfast.io/reference-and-specs/command-line-interface#run) command outputs: + +```bash +2022-05-30T10:52:27.256-0400 INFO (substreams) connecting... +2022-05-30T10:52:27.389-0400 INFO (substreams) connected + +----------- IRREVERSIBLE BLOCK #12,292,922 (12292922) --------------- +map_transfers: log: NFT Contract bc4ca0eda7647a8ab7c2061c2e118a18a936f13d invoked +[...] +map_transfers: message "eth.erc721.v1.Transfers": { + "transfers": [ + { + "from": "AAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "to": "q6cWGn+2nIjhbtn0Vc5it5HuTQM=", + "trxHash": "z7GX9i7Fx/DnGhHsDEoOOUo6pB21OG6FUm+GyEs/J5Y=", + "ordinal": "85" + }, + ... + ] +} +``` + +## Running a Substreams Package (.spkg) + +If you have an `spkg` file, you can run it by providing a path to the file: + +```bash +substreams gui \ + https://spkg.io/streamingfast/erc20-balance-changes-v1.1.0.spkg \ + map_balance_changes \ + -e mainnet.eth.streamingfast.io:443 \ + --start-block 1397553 +``` + +## Development and production mode + +Substreams has two mode when executing your module(s) either development mode or production mode. Development +and production modes impact the execution of Substreams, important aspects of execution include: + +* The time required to reach the first byte. +* The speed that large ranges get executed. +* The module logs and outputs sent back to the client. + +### Differences + +Differences between production and development modes include: + +* Forward parallel execution is enabled in production mode and disabled in development mode +* The time required to reach the first byte in development mode is faster than in production mode. + +Specific attributes of development mode include: + +* The client will receive all of the executed module's logs. +* It's possible to request specific store snapshots in the execution tree. +* Multiple module's output is possible. + +### Examples + +In most cases, you will run production mode, using a Substreams sink. Development mode is enabled by default in the CLI unless the `-p` flag is specified. + +Examples: (given the dependencies: `[block] --> [map_pools] --> [store_pools] --> [map_transfers])` + +* Running the `substreams run substreams.yaml map_transfers` command executes in development mode and only prints the `map_transfers` module's outputs and logs. +* Running the `substreams run substreams.yaml map_transfers --debug-modules-output=map_pools,map_transfers,store_pools` command executes in development mode and only prints the outputs of the `map_pools`, `map_transfers`, and `store_pools` modules. +* Running the `substreams run substreams.yaml map_transfers -s 1000 -t +5 --debug-modules-initial-snapshot=store_pools` command executes in development mode and prints all the entries in the `store_pools` module at block 999, then continues with outputs and logs from the `map_transfers` module in blocks 1000 through 1004. diff --git a/firehose/substreams/docs/new/consume/consume.md b/firehose/substreams/docs/new/consume/consume.md new file mode 100644 index 0000000..38497d2 --- /dev/null +++ b/firehose/substreams/docs/new/consume/consume.md @@ -0,0 +1,25 @@ +Substreams is a technology that allows you to extract blockchain data in a fast and reliable way! + +You can retrieve blockchain data by using packages. A Substreams package is a file that contains which data you want to retrieve from the blockchain. For example, the Uniswap v3 package allows you to extract all the information related to the Uniswap v3 smart contract. Use the [Substreams Registry](https://substreams.dev) to explore new packages! + +If you can't find a package that fits your needs, you can always develop your own Substreams to extract custom data from the blockchain! + +The _Consume_ sections focus on consuming data from Substreams packages, instead of developing your own Substreams. +If you want to learn how to develop your own Substreams package, navigate to the Develop Substreams sections. + +## How to Consume a Package + +### Choose a Substreams Package +Among the wide range of packages available, choose whichever retrieves the data that you need! + +
+ +### Choose a Consuming Service +What do you want to do with the data extracted from the blockchain? You have several options: send it to a PosgreSQL database, directly stream the data from your application (JS, Python, Rust...) or configure a webhook. + +
+ +### Access the Data! +Depending on the consuming service selected, the way you access the data will vary. + +For example, with PosgreSQL, you create SQL queries to perform aggregations over the data; if you are streaming data with JavaScript, you can directly embed the data into your application! \ No newline at end of file diff --git a/firehose/substreams/docs/new/consume/other-sinks/README.md b/firehose/substreams/docs/new/consume/other-sinks/README.md new file mode 100644 index 0000000..c026315 --- /dev/null +++ b/firehose/substreams/docs/new/consume/other-sinks/README.md @@ -0,0 +1,27 @@ +A Substreams package defines the data you want to extract from the blockchain. Then, you can consume that data by using one of the many sinks available. Sinks are integrations that allow you to send the extracted data to different destinations, such as a SQL database, a file or a subgraph. + +Some of the sinks are officially supported by one or several Substreams providers (i.e. active support is provided), but other sinks are community-driven and support can't be guaranteed. + +## Official + +| Name | Support | Maintainer | Source Code | +|-----------|---------|------------------|-------------| +| SQL | O | StreamingFast |[substreams-sink-sql](https://github.com/streamingfast/substreams-sink-sql)| +| Go SDK | O | StreamingFast |[substreams-sink](https://github.com/streamingfast/substreams-sink)| +| Rust SDK | O | StreamingFast |[substreams-sink-rust](https://github.com/streamingfast/substreams-sink-rust)| +| JS SDK | O | StreamingFast |[substreams-js](https://github.com/substreams-js/substreams-js)| +| KV Store | O | StreamingFast |[substreams-sink-kv](https://github.com/streamingfast/substreams-sink-kv)| +| Prometheus| O | Pinax |[substreams-sink-prometheus](https://github.com/pinax-network/substreams-sink-prometheus)| +| Webhook | O | Pinax |[substreams-sink-webhook](https://github.com/pinax-network/substreams-sink-webhook)| +| CSV | O | Pinax |[substreams-sink-csv](https://github.com/pinax-network/substreams-sink-csv)| +| PubSub | O | StreamingFast |[substreams-sink-pubsub](https://github.com/streamingfast/substreams-sink-pubsub)| + +## Community + +| Name | Support | Maintainer | Source Code | +|-----------|---------|------------------|-------------| +| MongoDB | C | Community |[substreams-sink-mongodb](https://github.com/streamingfast/substreams-sink-mongodb)| +| Files | C | Community |[substreams-sink-files](https://github.com/streamingfast/substreams-sink-files)| + +* O = Official Support (by one of the main Substreams providers) +* C = Community Support diff --git a/firehose/substreams/docs/new/consume/other-sinks/files.md b/firehose/substreams/docs/new/consume/other-sinks/files.md new file mode 100644 index 0000000..874749f --- /dev/null +++ b/firehose/substreams/docs/new/consume/other-sinks/files.md @@ -0,0 +1,211 @@ +# Files + +### Purpose + +This documentation exists to assist you in understanding and beginning to use the StreamingFast [`substreams-sink-file`](https://github.com/streamingfast/substreams-sink-files)`s` tool. The Substreams module paired with this tutorial is a basic example of the elements required for sinking blockchain data into files-based storage solutions. + +### Overview + +The `substreams-sink-files` tool provides the ability to pipe data extracted from a blockchain to various types of files-based persistence solutions. + +For example, you could extract all of the ERC20, ERC721, and ERC1155 transfers from the Ethereum blockchain and persist the data to a files-based store. + +Substreams modules are created and prepared specifically for the sink tool. After the sink tool begins running, automated tasks can be set up to have [BigQuery](https://cloud.google.com/bigquery), [Clickhouse](https://clickhouse.com), custom scripts, or other files-based storage solutions, ingest the data. This can only be accomplished indirectly. It's possible to automate further ingestion from files to data stores. + +You could use `substreams-sink-files` to sink data in `JSONL` format to a [Google Cloud Storage (GCS)](https://cloud.google.com/storage) bucket and configure a BigQuery Transfer job to run every 15 minutes. The scheduled job ingests the new files found in the GCS bucket where the data, extracted by the Substreams, was written. + +### Accompanying code example + +The accompanying Substreams module associated with this documentation is responsible for extracting a handful of data fields from the Block object injected into the Rust-based map module. The sink tool processes the extracted blockchain data line-by-line and outputs the data to the files-based persistence mechanism you've chosen. + +The accompanying code example extracts four data points from the Block object and packs them into the `substreams.sink.files.v1` Protobuf's data model. The data is passed to the Protobuf as a single line of plain text. + +Binary formats such as [Avro](https://avro.apache.org/) or [Parquet](https://parquet.apache.org/) is possible, however, support is not available. Contributions are welcome to help with support of binary data formats. [Contact the StreamingFast team on Discord](https://discord.gg/mYPcRAzeVN) to learn more and discuss specifics. + +## Installation + +### Install `substreams-sink-files` + +Install `substreams-sink-files` by using the pre-built binary release [available in the official GitHub repository](https://github.com/streamingfast/substreams-sink-files/releases). + +Extract `substreams-sink-files` into a folder and ensure this folder is referenced globally via your `PATH` environment variable. + +### Accompanying code example + +The accompanying code example for this tutorial is available in the `substreams-sink-files` repository. The Substreams project for the tutorial is located in the [docs/tutorial](https://github.com/streamingfast/substreams-sink-files/tree/master/docs/tutorial) directory. + +Run the included `make protogen` command to create the required Protobuf files. + +```bash +make protogen +``` + +It's a good idea to run the example code using your installation of the `substreams` CLI to make sure everything is set up and working properly. + +Verify the setup for the example project by using the `make build` and `substreams run` commands. + +Build the Substreams module by using the included `make` command. + +```bash +make +``` + +Run the project by using the `substreams run` command. + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml jsonl_out --start-block 1000000 --stop-block +1 +``` + +The `substreams run` command will result in output resembling the following: + +```bash +----------- NEW BLOCK #1,000,000 (1000000) --------------- +{ + "@module": "jsonl_out", + "@block": 1000000, + "@type": "sf.substreams.sink.files.v1", + "@data": { + "lines": [ + "{\"hash\":\"8e38b4dbf6b11fcc3b9dee84fb7986e29ca0a02cecd8977c161ff7333329681e\",\"number\":1000000,\"parent_hash\":\"b4fbadf8ea452b139718e2700dc1135cfc81145031c84b7ab27cd710394f7b38\",\"timestamp\":\"2016-02-13T22:54:13Z\"}" + ] + } +} +``` + +## Substreams modifications + +### Module handler changes for sink + +The example code in the [`lib.rs`](https://github.com/streamingfast/substreams-sink-files/blob/master/docs/tutorial/src/lib.rs) Rust source code file contains the `jsonl_out` module handler responsible for extracting the blockchain data. The module handler is responsible for passing the data to the `sf.substreams.sink.files.v1` Protobuf for the sink tool and its processes. + +```rust +#[substreams::handlers::map] +fn jsonl_out(block: eth::Block) -> Result { + + let header = block.header.as_ref().unwrap(); + + Ok(pb::sinkfiles::Lines { + lines: vec![json!({ + "number": block.number, + "hash": Hex(&block.hash).to_string(), + "parent_hash": Hex(&header.parent_hash).to_string(), + "timestamp": header.timestamp.as_ref().unwrap().to_string() + }) + .to_string()], + }) +} +``` + +This module handler uses `JSONL` for the output type, any other plain-text line-based format can be supported, `CSV` for example. The [`json!`](https://docs.rs/serde_json/latest/serde_json/macro.json.html) macro is used to write the block data to the Rust `Vec` type by using the Rust [`vec!`](https://doc.rust-lang.org/std/macro.vec.html) macro. + +The example code is intentionally very basic. StreamingFast [provides a more robust and full example](https://github.com/streamingfast/substreams-eth-token-transfers/blob/develop/src/lib.rs#L24) demonstrating how to extract data related to transfers from Ethereum. A crucial aspect of working with Substreams and sinks is a significant amount of data can be extracted from a Block object. The data is extracted and packed into a row. The row is represented by the JSONL or CSV based Protobuf you're responsible for designing for your sink. + +The output type for sink is a list of lines. The line content can be any type anything that is formatted as plain text, and line based. For example, a basic string like the transaction's hash, would result in files containing all the hashes for the transactions, one per line. + +### Core steps for Substreams sink modules + +- Import sink `.spkg` files, re-generate Protobufs and create and add a mod.rs file. +- Create a map module outputting sf.substreams.sink.files.v1 format. This module extracts the entity to be written, one per block from the block or another module's dependencies. Each line will be in JSON format. You can use the json! macro from the [`serde_json`](https://docs.rs/serde_json/latest/serde_json) crate to assist creating your structure, one per line. +- Add the correct module definition to the Substreams manifest `substreams.yaml`. + +```yaml +imports: + sink_files: https://github.com/streamingfast/substreams-sink-files/releases/download/v0.2.0/substreams-sink-files-v0.2.0.spkg + +binaries: + default: + type: wasm/rust-v1 + file: target/wasm32-unknown-unknown/release/substreams.wasm + +modules: + - name: jsonl_out + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:sf.substreams.sink.files.v1.Lines +``` + +## Understanding the sink tool + +### Run and configure the `substreams-sink-files` tool + +The command to start and run the `substreams-sink-files` tool for the accompanying Substreams project will resemble: + +{% code overflow="wrap" %} + +```bash +substreams-sink-files run --encoder=lines --state-store=./output/state.yaml mainnet.eth.streamingfast.io:443 substreams.yaml jsonl_out ./output/files +``` + +{% endcode %} + +## Verify output from tool + +Running the sink tool results in logging information printed to the terminal and directories and files being written to the local system or a cloud provider bucket if configured. + +The sink tool will produce output in the terminal resembling the following for a properly configured and working environment and project. + +```bash +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) starting prometheus metrics server {"listen_addr": "localhost:9102"} +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) sink to files {"file_output_path": "./localdata/out", "file_working_dir": "./localdata/working", "endpoint": "mainnet.eth.streamingfast.io:443", "encoder": "lines", "manifest_path": "substreams.yaml", "output_module_name": "jsonl_out", "block_range": "", "state_store": "./localdata/working/state.yaml", "blocks_per_file": 10000, "buffer_max_size": 67108864} +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) reading substreams manifest {"manifest_path": "substreams.yaml"} +2023-01-09T07:45:02.563-0800 INFO (substreams-sink-files) starting pprof server {"listen_addr": "localhost:6060"} +2023-01-09T07:45:04.041-0800 INFO (pipeline) computed start block {"module_name": "jsonl_out", "start_block": 0} +2023-01-09T07:45:04.042-0800 INFO (substreams-sink-files) ready, waiting for signal to quit +2023-01-09T07:45:04.045-0800 INFO (substreams-sink-files) setting up sink {"block_range": {"start_block": 0, "end_block": "None"}, "cursor": {"Cursor":"","Block":{}}} +2023-01-09T07:45:04.048-0800 INFO (substreams-sink-files) starting new file boundary {"boundary": "[0, 10000)"} +2023-01-09T07:45:04.049-0800 INFO (substreams-sink-files) boundary started {"boundary": "[0, 10000)"} +2023-01-09T07:45:04.049-0800 INFO (substreams-sink-files) starting stats service {"runs_each": "2s"} +2023-01-09T07:45:06.052-0800 INFO (substreams-sink-files) substreams sink stats {"progress_msg_rate": "0.000 msg/s (0 total)", "block_rate": "650.000 blocks/s (1300 total)", "last_block": "#1299 (a0f0f283e0d297dd4bcf4bbff916b1df139d08336ad970e77f26b45f9a521802)"} +``` + +One bundle of data is created for every 10K blocks during the sink process. + +To view the files the `substreams-sink-files` tool generates navigate into the directory you used for the output path. The directory referenced in the example points to the `localdata/out` directory. List the files in the output directory using the standard `ls` command to reveal the files created by the `substreams-sink-files` tool. + +```bash +... +0000000000-0000010000.jsonl 0000090000-0000100000.jsonl 0000180000-0000190000.jsonl +0000010000-0000020000.jsonl 0000100000-0000110000.jsonl 0000190000-0000200000.jsonl +0000020000-0000030000.jsonl 0000110000-0000120000.jsonl 0000200000-0000210000.jsonl +0000030000-0000040000.jsonl 0000120000-0000130000.jsonl 0000210000-0000220000.jsonl +0000040000-0000050000.jsonl 0000130000-0000140000.jsonl 0000220000-0000230000.jsonl +0000050000-0000060000.jsonl 0000140000-0000150000.jsonl 0000230000-0000240000.jsonl +0000060000-0000070000.jsonl 0000150000-0000160000.jsonl 0000240000-0000250000.jsonl +0000070000-0000080000.jsonl 0000160000-0000170000.jsonl 0000250000-0000260000.jsonl +0000080000-0000090000.jsonl 0000170000-0000180000.jsonl +... +``` + +The block range spanned by the example is from block 0000000000 to block 0000260000. The blocks contain all the lines received for the full 10K of processed blocks by default. The block range is controlled by using the `--file-block-count` flag. + +### Cursors + +When you use Substreams, it sends back a block to a consumer using an opaque cursor. This cursor points to the exact location within the blockchain where the block is. In case your connection terminates or the process restarts, upon re-connection, Substreams sends back the cursor of the last written bundle in the request so that the stream of data can be resumed exactly where it left off and data integrity is maintained. + +You will find that the cursor is saved in a file on disk. The location of this file is specified by the flag `--state-store` which points to a local folder. You must ensure that this file is properly saved to a persistent location. If the file is lost, the `substreams-sink-files` tool will restart from the beginning of the chain, redoing all the previous processing. + +Therefore, It is crucial that this file is properly persisted and follows your deployment of `substreams-sink-files` to avoid any data loss. + +### High Performance + +If you are looking for the fastest performance possible, we suggest that your destination source is able to handle heavy traffic. Also, to speed up things, you can allocate a lot of RAM to the process and increase the flag `--buffer-max-size` to a point where you are able to hold a full batch of N blocks in memory (checking the size of the final file is a good indicator of the size to keep stuff in memory). + +A lot of I/O operations is avoid if the buffer can hold everything in memory greatly speeding up the process of writing blocks bundle to its final destination. + +### Cloud-based storage + +You can use the `substreams-sink-files` tool to route data to files on your local file system and cloud-based storage solutions. To use a cloud-based solution such as Google Cloud Storage bucket, S3 compatible bucket, or Azure bucket, you need to make sure it is set up properly. Then, instead of referencing a local file in the `substreams-sink-files run` command, use the path to the bucket. The paths resemble `gs:///`, `s3:///`, and `az:///` respectively. Be sure to update the values according to your account and provider. + +### Limitations + +When you use the `substreams-sink-files` tool, you will find that it syncs up to the most recent "final" block of the chain. This means it is not real-time. Additionally, the tool writes bundles to disk when it has seen 10,000 blocks. As a result, the latency of the last available bundle can be delayed by around 10,000 blocks. How many blocks per batch can be controlled by changing the flag `--file-block-count` + +## Conclusion and review + +The ability to route data extracted from the blockchain by using Substreams is powerful and useful. Files aren't the only type of sink the data extracted by Substreams can be piped into. Review the core Substreams sinks documentation for [additional information on other types of sinks](./) and sinking strategies. + +To use `substreams-sink-files` you need to clone the official repository, install the tooling, generate the required files from the substreams CLI for the example Substreams module and run the sink tool. + +You have to ensure the sinking strategy has been defined, the appropriate file types have been targeted, and accounted for, and the module handler code in your Substreams module has been properly updated. You need to start the `substreams-sink-files` tool and use the `run` command being sure to provide all of the required values for the various flags. diff --git a/firehose/substreams/docs/new/consume/other-sinks/kv.md b/firehose/substreams/docs/new/consume/other-sinks/kv.md new file mode 100644 index 0000000..9939731 --- /dev/null +++ b/firehose/substreams/docs/new/consume/other-sinks/kv.md @@ -0,0 +1,287 @@ +# Key/value store + +## Purpose + +This documentation will assist you in using [`substreams-sink-kv`](https://github.com/streamingfast/substreams-sink-kv) to write data from your existing substreams into a key-value store and serve it back through Connect-Web/GRPC. + +## Overview + +`substreams-sink-kv` works by reading the output of specially-designed substreams module (usually called `kv_out`) that produces data in a protobuf-encoded structure called `sf.substreams.sink.kv.v1.KVOperations`. + +The data is written to a key-value store. Currently supported KV store are Badger, BigTable and TiKV. + +A Connect-Web interface makes the data available directly from the `substreams-sink-kv` process. Alternatively, you can consume the data directly from your key-value store. + +## Requirements + +* An existing substreams (including `substreams.yaml` and Rust code) that you want to instrument for `substreams-sink-kv`. +* A key-value store where you want to send your data (a badger local file can be used for development) +* Knowledge about Substreams development (start [here](https://substreams.streamingfast.io/getting-started/quickstart)) +* Rust installation and compiler + +## Installation + +* Install [substreams-sink-kv CLI](https://github.com/streamingfast/substreams-sink-kv/releases) +* Install [substreams CLI](https://substreams.streamingfast.io/getting-started/installing-the-cli) +* Install [grpcurl](https://github.com/fullstorydev/grpcurl/releases) to easily read the data back from the KV store + +## Instrumenting your Substreams + +### Assumptions + +The following instructions will assume that you are instrumenting [substreams-eth-block-meta](https://github.com/streamingfast/substreams-eth-block-meta), which contains: + +* A store `store_block_meta_end` defined like [this](https://github.com/streamingfast/substreams-eth-block-meta/blob/v0.4.0/substreams.yaml#L29-L34): + +```yaml +# substreams.yaml +... + - name: store_block_meta_end + kind: store + updatePolicy: set + valueType: proto:eth.block_meta.v1.BlockMeta + inputs: + - source: sf.ethereum.type.v2.Block +``` + +* a `eth.block_meta.v1.BlockMeta` protobuf structure like [this](https://github.com/streamingfast/substreams-eth-block-meta/blob/v0.4.0/proto/block\_meta.proto#L7-L12): + +``` +message BlockMeta { + uint64 number = 1; + bytes hash = 2; + bytes parent_hash = 3; + google.protobuf.Timestamp timestamp = 4; +} +``` + +> **Note** The [substreams-eth-block-meta](https://github.com/streamingfast/substreams-eth-block-meta) is already instrumented for sink-kv, the proposed changes here are a simplified version of what has been implemented. Please adjust the proposed code to your own substreams. + +### Import the Cargo module + +1. Add the `substreams-sink-kv` crate to your `Cargo.toml`: + +```toml +# Cargo.toml + +[dependencies] +substreams-sink-kv = "0.1.1" +# ... + +``` + +1. Add `map` module implementation function named `kv_out` to your `src/lib.rs`: + +```yaml +# substreams.yaml +... + - name: kv_out + kind: map + inputs: + - store: store_block_meta_end + mode: deltas + output: + type: proto:sf.substreams.sink.kv.v1.KVOperations +``` + +1. Add a `kv_out` public function to your `src/lib.rs`: + +``` +// src/lib.rs + +#[path = "kv_out.rs"] +mod kv; +use substreams_sink_kv::pb::kv::KvOperations; + +#[substreams::handlers::map] +pub fn kv_out( + deltas: store::Deltas>, +) -> Result { + + // Create an empty 'KvOperations' structure + let mut kv_ops: KvOperations = Default::default(); + + // Call a function that will push key-value operations from the deltas + kv::process_deltas(&mut kv_ops, deltas); + + // Here, we could add more operations to the kv_ops + // ... + + Ok(kv_ops) +} +``` + +1. Add the `kv::process_deltas` transformation function referenced in the last snippet: + +``` +// src/kv_out.rs + +use substreams::proto; +use substreams::store::{self, DeltaProto}; +use substreams_sink_kv::pb::kv::KvOperations; + +use crate::pb::block_meta::BlockMeta; + +pub fn process_deltas(ops: &mut KvOperations, deltas: store::Deltas>) { + use substreams::pb::substreams::store_delta::Operation; + + for delta in deltas.deltas { + match delta.operation { + // KV Operations do not distinguish between Create and Update. + Operation::Create | Operation::Update => { + let val = proto::encode(&delta.new_value).unwrap(); + ops.push_new(delta.key, val, delta.ordinal); + } + Operation::Delete => ops.push_delete(&delta.key, delta.ordinal), + x => panic!("unsupported opeation {:?}", x), + } + } +} +``` + +## Test your substreams + +1. Compile your changes in your rust code: + +``` +cargo build --release --target=wasm32-unknown-unknown +``` + +1. Run with `substreams` command directly: + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml kv_out --start-block 1000000 --stop-block +1 +``` + +> **Note** To connect to a public StreamingFast substreams endpoint, you will need an authentication token, follow this [guide](https://substreams.streamingfast.io/reference-and-specs/authentication) to obtain one. + +1. Run with `substreams-sink-kv`: + +```bash +substreams-sink-kv \ + run \ + "badger3://$(pwd)/badger_data.db" \ + mainnet.eth.streamingfast.io:443 \ + manifest.yaml \ + kv_out +``` + +You should see output similar to this one: + +```bash +2023-01-12T10:08:31.803-0500 INFO (sink-kv) starting prometheus metrics server {"listen_addr": "localhost:9102"} +2023-01-12T10:08:31.803-0500 INFO (sink-kv) sink to kv {"dsn": "badger3:///Users/stepd/repos/substreams-sink-kv/badger_data.db", "endpoint": "mainnet.eth.streamingfast.io:443", "manifest_path": "https://github.com/streamingfast/substreams-eth-block-meta/releases/download/v0.4.0/substreams-eth-block-meta-v0.4.0.spkg", "output_module_name": "kv_out", "block_range": ""} +2023-01-12T10:08:31.803-0500 INFO (sink-kv) starting pprof server {"listen_addr": "localhost:6060"} +2023-01-12T10:08:31.826-0500 INFO (sink-kv) reading substreams manifest {"manifest_path": "https://github.com/streamingfast/substreams-eth-block-meta/releases/download/v0.4.0/substreams-eth-block-meta-v0.4.0.spkg"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) validating output store {"output_store": "kv_out"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) resolved block range {"start_block": 0, "stop_block": 0} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) starting to listen on {"addr": "localhost:8000"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) starting stats service {"runs_each": "2s"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) no block data buffer provided. since undo steps are possible, using default buffer size {"size": 12} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) starting stats service {"runs_each": "2s"} +2023-01-12T10:08:32.186-0500 INFO (sink-kv) ready, waiting for signal to quit +2023-01-12T10:08:32.186-0500 INFO (sink-kv) launching server {"listen_addr": "localhost:8000"} +2023-01-12T10:08:32.187-0500 INFO (sink-kv) serving plaintext {"listen_addr": "localhost:8000"} +2023-01-12T10:08:32.278-0500 INFO (sink-kv) session init {"trace_id": "a3c59bd7992c433402b70f9541565d2d"} +2023-01-12T10:08:34.186-0500 INFO (sink-kv) substreams sink stats {"db_flush_rate": "10.500 flush/s (21 total)", "data_msg_rate": "0.000 msg/s (0 total)", "progress_msg_rate": "0.000 msg/s (0 total)", "block_rate": "0.000 blocks/s (0 total)", "flushed_entries": 0, "last_block": "None"} +2023-01-12T10:08:34.186-0500 INFO (sink-kv) substreams sink stats {"progress_msg_rate": "16551.500 msg/s (33103 total)", "block_rate": "10941.500 blocks/s (21883 total)", "last_block": "#291883 (66d03f819dde948b297c8d582889246d7ba11a5b947335497f8716a7b608f78e)"} +``` + +> **Note** This writes the data to a local folder "./badger\_data.db/" in Badger format. You can `rm -rf ./badger_data.db` between your tests to cleanup all existing data. + +1. Look at the stored data + +You can scan the whole dataset using the 'Scan' command: + +```bash +grpcurl --plaintext -d '{"begin": "", "limit":100}' localhost:8000 sf.substreams.sink.kv.v1.Kv/Scan +``` + +You can look at data by key prefix: + +```bash +grpcurl --plaintext -d '{"prefix": "day:first:201511", "limit":31}' localhost:8000 sf.substreams.sink.kv.v1.Kv/GetByPrefix +``` + +## Consume the key-value data from a web-page using Connect-Web + +The [Connect-Web](https://connect.build/docs/web/getting-started) library allows you to quickly bootstrap a web-based client for your key-value store. + +### Requirements + +* [NodeJS](https://nodejs.dev/download) +* [npm](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) +* [buf CLI](https://docs.buf.build/installation) + +### Start from our example for `substreams-eth-block-meta` + +You can checkout and run our connect-web-example like this: + +```bash +git clone git@github.com:streamingfast/substreams-sink-kv +cd substreams-sink-kv/connect-web-example +npm install +npm run dev +``` + +Then, enter a key in the text box. The app currently only decodes `eth.block_meta.v1.BlockMeta`, so you will likely receive the corresponding value encoded in hex string. + +To decode the value of your own data structures, add your `.proto` files under `proto/` and generate Rust bindings like this: + +```bash +npm run buf:generate +``` + +You should see this output: + +``` +> connect-web-example@0.0.0 buf:generate +> buf generate ../proto/substreams/sink/kv/v1 && buf generate ./proto +``` + +Then, modify the code from `src/App.tsx` to decode your custom type, from this: + +```rust + import { BlockMeta } from "../gen/block_meta_pb"; + + ... + + const blkmeta = BlockMeta.fromBinary(response.value); + output = JSON.stringify(blkmeta, (key, value) => { + if (key === "hash") { + return "0x" + bufferToHex(blkmeta.hash); + } + if (key === "parentHash") { + return "0x" + bufferToHex(blkmeta.parentHash); + } + return value; + }, 2); +``` + +to this: + +```rust + import { MyData } from "../gen/my_data_pb"; + + ... + + const decoded = MyData.fromBinary(response.value); + output = JSON.stringify(decoded, null, 2); +``` + +### Bootstrap your own application + +If you want to start with an empty application, you can follow [these instructions](https://github.com/streamingfast/substreams-sink-kv/tree/main/connect-web-example/README.md) + +## Sending to a production key-value store + +Until now, we've used the **badger** database as a store, for simplicity. However, `substreams-sink-kv` also supports **TiKV** and **bigtable**. + +* `tikv://pd0,pd1,pd2:2379?prefix=namespace_prefix` +* `bigkv://project.instance/namespace-prefix?createTables=true` + +See [kvdb](https://github.com/streamingfast/kvdb) for more details. + +## Conclusion and review + +The ability to route data extracted from the blockchain by using Substreams is powerful and useful. Key-value stores aren't the only type of sink the data extracted by Substreams can be piped into. Review the core Substreams sinks documentation for [additional information on other types of sinks](https://substreams.streamingfast.io/developers-guide/substreams-sinks) and sinking strategies. diff --git a/firehose/substreams/docs/new/consume/other-sinks/mongodb.md b/firehose/substreams/docs/new/consume/other-sinks/mongodb.md new file mode 100644 index 0000000..48d97fe --- /dev/null +++ b/firehose/substreams/docs/new/consume/other-sinks/mongodb.md @@ -0,0 +1,4 @@ + +It is possible to send Substreams data to MongoDB. See the GitHub repository for an early version of the MongoDB Substreams Sink: + +* [https://github.com/streamingfast/substreams-sink-mongodb](https://github.com/streamingfast/substreams-sink-mongodb) diff --git a/firehose/substreams/docs/new/consume/other-sinks/prometheus.md b/firehose/substreams/docs/new/consume/other-sinks/prometheus.md new file mode 100644 index 0000000..dcb76ff --- /dev/null +++ b/firehose/substreams/docs/new/consume/other-sinks/prometheus.md @@ -0,0 +1,158 @@ +# [`Substreams`](https://substreams.streamingfast.io/) [Prometheus](https://prometheus.io/) sink module + +[github](https://github.com/pinax-network/substreams-sink-prometheus) +[crates.io](https://crates.io/crates/substreams-sink-prometheus) +[npm](https://www.npmjs.com/package/substreams-sink-prometheus) +[docs.rs](https://docs.rs/substreams-sink-prometheus) +[GitHub Workflow Status](https://github.com/pinax-network/substreams-sink-prometheus/actions?query=branch%3Amain) + +> `substreams-sink-prometheus` is a tool that allows developers to pipe data extracted metrics from a blockchain into a Prometheus time series database. + +## 📖 Documentation + +### https://docs.rs/substreams-sink-prometheus + +### Further resources + +- [Substreams documentation](https://substreams.streamingfast.io) +- [Prometheus documentation](https://prometheus.io) + +## CLI +[**Use pre-built binaries**](https://github.com/pinax-network/substreams-sink-prometheus/releases) +- [x] MacOS +- [x] Linux +- [x] Windows + +**Install** globally via npm +``` +$ npm install -g substreams-sink-prometheus +``` + +**Run** +``` +$ substreams-sink-prometheus run [options] +``` + +> Open the browser at [http://localhost:9102/metrics](http://localhost:9102/metrics) + +## 🛠 Feature Roadmap + +### [Gauge Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Gauge) +- [x] Set +- [x] Inc +- [x] Dec +- [x] Add +- [x] Sub +- [x] SetToCurrentTime +- [x] Remove +- [x] Reset + +### [Counter Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Counter) +- [x] Inc +- [x] Add +- [x] Remove +- [x] Reset + +### [Histogram Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Histogram) +- [ ] Observe +- [ ] buckets +- [ ] zero + +### [Summary Metric](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Summary) +> Summaries calculate percentiles of observed values. +- [ ] Observe +- [ ] percentiles +- [ ] maxAgeSeconds +- [ ] ageBuckets +- [ ] startTimer + +### [Registry](https://pkg.go.dev/github.com/prometheus/client_golang/prometheus#Registry) +- [ ] Clear +- [ ] SetDefaultLabels +- [ ] RemoveSingleMetric + +## Install + +```bash +$ cargo add substreams-sink-prometheus +``` + +## Quickstart + +**Cargo.toml** + +```toml +[dependencies] +substreams = "0.5" +substreams-sink-prometheus = "0.1" +``` + +**src/lib.rs** + +```rust +use std::collections::HashMap; +use substreams::prelude::*; +use substreams::errors::Error; +use substreams_sink_prometheus::{PrometheusOperations, Counter, Gauge}; + +#[substreams::handlers::map] +fn prom_out( + ... some stores ... +) -> Result { + + // Initialize Prometheus Operations container + let mut prom_ops: PrometheusOperations = Default::default(); + + // Counter Metric + // ============== + // Initialize Gauge with a name & labels + let mut counter = Counter::from("counter_name"); + + // Increments the Counter by 1. + prom_ops.push(counter.inc()); + + // Adds an arbitrary value to a Counter. (Returns an error if the value is < 0.) + prom_ops.push(counter.add(123.456)); + + // Labels + // ====== + // Create a HashMap of labels + // Labels represents a collection of label name -> value mappings. + let labels1 = HashMap::from([("label1".to_string(), "value1".to_string())]); + let mut labels2 = HashMap::new(); + labels2.insert("label2".to_string(), "value2".to_string()); + + // Gauge Metric + // ============ + // Initialize Gauge + let mut gauge = Gauge::from("gauge_name").with(labels1); + + // Sets the Gauge to an arbitrary value. + prom_ops.push(gauge.set(88.8)); + + // Increments the Gauge by 1. + prom_ops.push(gauge.inc()); + + // Decrements the Gauge by 1. + prom_ops.push(gauge.dec()); + + // Adds an arbitrary value to a Gauge. (The value can be negative, resulting in a rease of the Gauge.) + prom_ops.push(gauge.add(50.0)); + prom_ops.push(gauge.add(-10.0)); + + // Subtracts arbitrary value from the Gauge. (The value can be negative, resulting in an rease of the Gauge.) + prom_ops.push(gauge.sub(25.0)); + prom_ops.push(gauge.sub(-5.0)); + + // Set Gauge to the current Unix time in seconds. + prom_ops.push(gauge.set_to_current_time()); + + // Remove metrics for the given label values + prom_ops.push(gauge.remove(labels2)); + + // Reset gauge values + prom_ops.push(gauge.reset()); + + Ok(prom_ops) +} +``` diff --git a/firehose/substreams/docs/new/consume/other-sinks/pubsub.md b/firehose/substreams/docs/new/consume/other-sinks/pubsub.md new file mode 100644 index 0000000..f4b1bfa --- /dev/null +++ b/firehose/substreams/docs/new/consume/other-sinks/pubsub.md @@ -0,0 +1,28 @@ +## PubSub + +The PubSub integration allows you to send blockchain data to a [Google PubSub](https://cloud.google.com/pubsub?hl=en) topic by emitting a specific Protobuf object in your Substreams: [sf.substreams.sink.pubsub.v1.Publish](https://github.com/streamingfast/substreams-sink-pubsub/blob/develop/proto/sf/substreams/sink/pubsub/v1/pubsub.proto). + +### Getting Started + +If you are new Substreams, refer to the [Develop Substreams](../../develop/develop.md) section to learn about the main pieces of building a Substreams from scratch. + +- Clone the [https://github.com/streamingfast/substreams-sink-pubsub](https://github.com/streamingfast/substreams-sink-pubsub) GitHub repository. +- Install the PubSub CLI. This CLI will help in deploying your Substreams to the PubSub Service. + +```bash +go install ./cmd/substreams-sink-pubsub +``` + +- Create a topic in the Google PubSub Service, where the data of your Substreams will be sent. +- Deploy your Substreams by using the PubSub CLI: + +```bash +substreams-sink-pubsub sink -e --project +``` + - `endpoint`: the Substreams provider endpoint that will be used to extract the data (you can find the endpoints available in the [Chains & Endpoints](../../references/chains-and-endpoints.md)) section. + - `project_id`: ID of the Google project. + - `substreams_manifest`: path to the Substreams manifest. + - `substreams_module_name`: name of the Substreams output module. The module must emit `sf.substreams.sink.pubsub.v1.Publish` data. + - `topic_name`: name of the Google topic where the data will be sent. + +You can find some Substreams examples in the `examples` directory of the repository. \ No newline at end of file diff --git a/firehose/substreams/docs/new/consume/sql/deployable-services/local-service.md b/firehose/substreams/docs/new/consume/sql/deployable-services/local-service.md new file mode 100644 index 0000000..142c50f --- /dev/null +++ b/firehose/substreams/docs/new/consume/sql/deployable-services/local-service.md @@ -0,0 +1,324 @@ +In you want to manage your own infrastructure, you can use still the deployable services, but locally. This essetially means using the `substreams alpha service` command pointing to a local Docker installation. The following tutorial teaches you how to use the Substreams:SQL deployable service locally. + +## Tutorial + +In this tutorial, you will: + +1. Generate a simple Substreams that extract all events from the Cryptopunks smart contract on Ethereum. +2. Feed your data into a local PostgreSQL database in a Docker-based development environment. +3. Develop and apply SQL transformations with [dbt models](https://docs.getdbt.com/docs/build/models). +4. Go from a local Substreams:SQL environment to a shared remote development environment. +5. Create a production `.spkg` package and test it locally +6. Deploy your `.spkg` to a production environment that fills a PostgreSQL database from your Substreams and applies dbt transformations automatically. + +### Generate a Substreams project from the Cryptopunks ABI + +* The cryptopunks address on mainnet is `0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb` + +* Use the `substreams init` command to fetch the ABI from etherscan and scaffold a Substreams project: + +```bash +substreams init +``` + +Fill the requested information (name: `cryptopunks`, protocol: `ethereum`, chain: `mainnet`, contract: `b47e3cd837ddf8e4c57f05d70ab865de6e193bbb`) +``` +Project name (lowercase, numbers, undescores): cryptopunks +Protocol: Ethereum +Ethereum chain: Mainnet +✔ Contract address to track: b47e3cd837ddf8e4c57f05d70ab865de6e193bbb +✔ Would you like to track another contract? (Leave empty if not): █ +Would you like to track another contract? (Leave empty if not): +Retrieving Ethereum Mainnet contract information (ABI & creation block) +Fetched contract ABI for b47e3cd837ddf8e4c57f05d70ab865de6e193bbb +Fetched initial block 3914495 for b47e3cd837ddf8e4c57f05d70ab865de6e193bbb (lowest 3914495) +Generating ABI Event models for + Generating ABI Events for Assign (to,punkIndex) + Generating ABI Events for PunkBidEntered (punkIndex,value,fromAddress) + Generating ABI Events for PunkBidWithdrawn (punkIndex,value,fromAddress) + Generating ABI Events for PunkBought (punkIndex,value,fromAddress,toAddress) + Generating ABI Events for PunkNoLongerForSale (punkIndex) + Generating ABI Events for PunkOffered (punkIndex,minValue,toAddress) + Generating ABI Events for PunkTransfer (from,to,punkIndex) + Generating ABI Events for Transfer (from,to,value) +Writing project files +Generating Protobuf Rust code +Project "cryptopunks" initialized at "/Users/stepd/repos" +Run 'make build' to build the wasm code. +The following substreams.yaml files have been created with different sink targets: + * substreams.yaml: no sink target + * substreams.sql.yaml: PostgreSQL sink + * substreams.clickhouse.yaml: Clickhouse sink + * substreams.subgraph.yaml: Sink into Substreams-based subgraph +``` + +* See the list of events that have been identified above. By default, each event type is mapped to its own table. This happens in the `map_events()` function inside `src/lib.rs` and the SQL schema is available in `schema.sql`. + +* Build the rust code: + +```bash +make build +``` + +You should see: + +```bash +cargo build --target wasm32-unknown-unknown --release + Compiling proc-macro2 v1.0.69 + Compiling unicode-ident v1.0.12 + Compiling syn v1.0.109 + ... + Compiling cryptopunks v0.0.1 (/Users/stepd/repos/cryptopunks) + Finished release [optimized] target(s) in 30.71s +``` + +### Start feeding a local database using 'substreams alpha service' and Docker + +From another window: + +``` +SUBSTREAMS_API_TOKEN=(...) substreams alpha service serve +``` + +Back to your substreams project: + +* Deploy your substreams locally to start putting data in your database: + +```bash +substreams alpha service deploy substreams.sql.yaml +``` + +{% hint style="success" %} +**Tip:** A new PostgreSQL container will be created at port `5432`. The deployment will fail if there is another Docker container using that port. +{% endhint %} + +{% hint style="info" %} +**Tip:** You can also use `substreams service deploy substreams.clickhouse.yaml` to use the Clickhouse engine instead of PostgreSQL. There is no postgraphile or pgweb in that case, you will need a tool like DataGrip to see the data. +{% endhint %} + +You should see: + +```bash +Deploying... (creating services, please wait) +Deployed substreams sink "7590fdbf": + Status: STOPPED +Running your deployment inside local docker containersServices: + - 7590fdbf-pgweb: PGWeb service "7590fdbf-pgweb" available at URL: 'http://localhost:8081' + - 7590fdbf-postgraphile: Postgraphile service "7590fdbf-postgraphile" available at URL: 'http://localhost:3000/graphiql' (API at 'http://localhost:3000/graphql') + - 7590fdbf-postgres: PostgreSQL service "7590fdbf-postgres" available at DSN: 'postgres://dev-node:insecure-change-me-in-prod@localhost:5432/substreams?sslmode=disable' + - 7590fdbf-sink: Sink service (no exposed port). Use 'substreams alpha sink-info 7590fdbf' to see last processed block or 'docker logs 7590fdbf-sink' to see the logs. + - 7590fdbf-sinkinfo: Sink info service "7590fdbf-sinkinfo" available at URL: 'http://localhost:8282/sinkinfo' +``` + +* Look at some SQL data via pgweb at [http://localhost:8081](http://localhost:8081) + +{% hint style="success" %} +**Tip:** You can run `substreams alpha service pause` if you want to pause the sink from consuming Substreams data while you continue your development. `substreams alpha service resume` will continue the progress. +{% endhint %} + +### Create a dbt project for transformations + +* Initialize the dbt project: + +```bash +dbt init cryptopunks +``` + +{% hint style="success" %} +**Tip:** Choose 'postgres' as the database. +{% endhint %} + +* Move that project under 'dbt' folder: + +```bash +mv cryptopunks dbt +``` +* Edit the dev credentials under $HOME/.dbt/profiles.yml file: + +```bash +... +cryptopunks: + outputs: + dev: + type: postgres + host: localhost + user: dev-node + password: insecure-change-me-in-prod + port: 5432 + dbname: substreams + schema: public +... + target: dev + +``` + +{% hint style="success" %} +**Tip:** You can see the database credentials again by running `substreams alpha service info` +{% endhint %} + +* Test the dbt connection + +```bash +dbt debug +``` + +You should see: + +```bash +16:04:10 All checks passed! +``` + +* Remove the example models: + +```bash +rm -rf models/example +``` + +* Create a datasource and a basic materialized view: + +```bash +mkdir models/default + +cat < models/default/source.yml +version: 2 +sources: + - name: cryptopunks + loaded_at_field: evt_block_time + tables: + - name: punk_bought + - name: punk_bid_entered + - name: punk_offered + - name: punk_no_longer_for_sale + - name: punk_transfer + - name: punk_bid_withdrawn + - name: transfer + - name: assign +EOF + +cat < models/default/punks_bought_per_hour.sql +{{ config(materialized='table') }} +select date_trunc('hour', evt_block_time) AS "hour", count(*) from punk_bought GROUP BY hour order by hour +EOF +``` + +* Create your views: + +```bash +dbt run +``` + +* Check that the `punks_bought_per_hour` has been created. + +### Deploy your Substreams to the "hosted dev" environment + +Deployments of type "dev" gives you full read/write access to the database and are a good way of working together on a dataset while developing your dbt models. + +```bash +substreams alpha service deploy substreams.sql.yaml -e https://deploy.streamingfast.io +``` + +{% hint style="info" %} +**Tip:** Here again, you can use the `substreams.clickhouse.yaml` manifest to use a Clickhouse engine. +{% endhint %} + +You should see this output: + +```bash +Deployed substreams sink "54546874": + Status: RUNNING +Deployment *54546874* is *RUNNING* + - Database type is *postgres* + - Owner is *0doqed628575ba7d2bd03* + +Indexed blocks: [12287507 - 12326000] + - Sink status is available at 'https://srv.streamingfast.io/54546874/sinkinfo' + +Environment is *Development*, which means: + - Read-only direct access to the database is available at 'postgresql://db.srv.streamingfast.io:17622/substreams?sslmode=disable&user=dev-node-ro&password=iv2rqsKsUVH5' + - Read/write direct access to the database is available at 'postgresql://db.srv.streamingfast.io:17622/substreams?sslmode=disable&user=dev-node&password=oTWPmz2Sqssb' + - Read/write access to the database via PGWeb at 'https://srv.streamingfast.io/54546874/pgweb' + +Postgraphile is available at these URL: + - GraphiQL (browser): 'https://srv.streamingfast.io/54546874/graphiql' + - GraphQL (apps): 'https://srv.streamingfast.io/54546874/graphql' + +See some tutorials at https://substreams.streamingfast.io +``` + +{% hint style="success" %} +**Tip:** You are getting SQL credentials in this output. They can be fetched again by running `substreams alpha service info 46f5e9f6 -e https://deploy.streamingfast.io`. +{% endhint %} + +{% hint style="success" %} +**Tip:** The SQL endpoint will be assigned a specific port mapped to your deployment, in this example, port 17622. +{% endhint %} + +* Create another target in your `$HOME/.dbt/profiles.yml` replacing `PORT_NUMBER` and `RW_PASSWORD` with those assigned to you on the previous step: + +```bash +cryptopunks: + outputs: +... + remote: + type: postgres + host: db.srv.streamingfast.io + user: dev-node + password: {RW_PASSWORD} + port: {PORT_NUMBER} + dbname: substreams + schema: public +``` + +* You can iterate using `dbt run --target=remote` +* Other devs can work on that data and create dbt models. + +### Pack your production package and test it locally + +When your dbt models are ready, you can pack everything (Substreams, [dbt project](https://docs.getdbt.com/docs/build/projects), etc.) inside an `.spkg` file and deploy it as production: + +* Add a `dbt_config` section to the `substreams.sql.yaml` (or `substreams.clickhouse.yaml`) file: + +```yaml + dbt_config: + enabled: true + files: "./dbt" + run_interval_seconds: 180 +``` + +* Start the `substreams alpha service serve` command in another window (if it is not still running from before...) + +```bash +SUBSTREAMS_API_TOKEN=(...) substreams alpha service serve +``` + +* Stop the previous deployment completely (only a single deployment can run at the same time on local Docker environment) + +```bash +substreams alpha service stop +``` + +* Test your deployment locally, in production mode: + +```bash +substreams alpha service deploy substreams.sql.yaml --prod # or substreams.clickhouse.yaml +``` + +{% hint style="success" %} +**Tip:** If using Clickhouse, you will need to set `sink.config.rest_frontend.enabled` to `true`: it is currently the only way to consume data in a 'production' deployment. +{% endhint %} + +* See that the database starts correctly and that the tables defined in `dbt` are being created correctly + +* When you are happy with the results, verify or bump the `version` field in `substreams.sql.yaml`, you can generate the `cryptopunks-v0.1.0.spkg` file. + +```bash +substreams pack substreams.sql.yaml # or substreams.clickhouse.yaml +``` + +### Deploy your production package to the "hosted prod" environment + +```bash +substreams alpha service deploy cryptopunks-v0.1.0.spkg -e https://deploy.streamingfast.io --prod +``` + +The production environment does not allow direct SQL access at the moment, so your apps will need to access the data to either the `postgraphile` frontend (or the `rest` frontend when using Clickhouse) \ No newline at end of file diff --git a/firehose/substreams/docs/new/consume/sql/deployable-services/remote-service.md b/firehose/substreams/docs/new/consume/sql/deployable-services/remote-service.md new file mode 100644 index 0000000..4dde647 --- /dev/null +++ b/firehose/substreams/docs/new/consume/sql/deployable-services/remote-service.md @@ -0,0 +1,86 @@ +Using the StreamingFast SQL remote (hosted) service is the easiest way to get started with Substreams:SQL. The following tutorial teaches you how to deploy a Substreams package from the [Substreams Registry](https://substreams.dev) to StreamingFast remote service. + +{% hint style="success" %} +This tutorial shows you how to deploy Substreams package to the **StreamingFast remote service**. +You can also set up a Substreams deployable service environment in your computer, which will allow you to test your deployable services easily. + +The Substreams:SQL Tutorial +{% endhint %} + +## Tutorial + +In this short tutorial, you will: + +- Deploy the Substreams package to the StreamingFast SQL remote service. +- Explore the SQL database in your browser. + +Before you get started, make sure you have: +- The Substreams CLI installed. + +The package used in this tutorial is the USDT Ethereum package, which retrieves events from the USDT smart contract on the Ethereum blockchain. + +### Deploying a Substreams Package to the Remote Service + +1. You can find the USDT Ethereum package in the Substreams Registry. + +3. Deploy the package using the `substreams alpha service deploy` command. + +```bash +substreams alpha service deploy https://spkg.io/enoldev/substreams-ethereum-usdt-v0.1.0.spkg -e https://deploy.streamingfast.io +``` + +* The `substreams alpha service deploy` command is used to deploy a package to the remote service. +* In this example, you deploy the USDT Ethereum package (`https://spkg.io/enoldev/substreams-ethereum-usdt-v0.1.0.spkg`). +* The `-e` flag specifies the location of the remote service. In this example, you are using the StreamingFast Remote Service (`https://deploy.streamingfast.io`). + +The deployment of the package might take 1-2 minutes. + +4. After the deployment is completed, some useful data will be displayed: + +```bash +Deploying... (creating services, please wait) +Deployed substreams sink "60589e45": + Status: RUNNING +Deployment *60589e45* is *RUNNING* + - Database type is *postgres* + - Owner is *0qeru2bd28b954a35c12e* + +Indexed blocks: [4634748 - 4785000] + - Sink status is available at 'https://srv.streamingfast.io/60589e45/sinkinfo' + +Environment is *Development*, which means: + - Read-only direct access to the database is available at 'postgresql://db.srv.streamingfast.io:17441/substreams?sslmode=disable&user=dev-node-ro&password=JWgg68gP33lZ' + - Read/write direct access to the database is available at 'postgresql://db.srv.streamingfast.io:17441/substreams?sslmode=disable&user=dev-node&password=iESYNNa5EihR' + - Read/write access to the database via PGWeb at 'https://srv.streamingfast.io/60589e45/pgweb' + +Postgraphile is available at these URL: + - GraphiQL (browser): 'https://srv.streamingfast.io/60589e45/graphiql' + - GraphQL (apps): 'https://srv.streamingfast.io/60589e45/graphql' + +Documentation: https://substreams.streamingfast.io +Services: + - pgweb: pod running + - postgraphile: pod running + - postgres: pod running + - sink: pod running + - sinkinfo: pod running +``` + +1. **Service ID:** the identifier of the deployed service, which you can use to manage the service. +2. **URL of the service status:** use this URL to verify the status of the service. +3. **URL of the PostgreSQL client:** use this client to run SQL queries, update the SQL schema, and manage the SQL database in general. +4. **URL of the GraphQL clent:** use this client to run GraphQL queries. + +### Inspecting the PostgreSQL client + +Once the package is deployed, your Substreams starts indexing data. You can access this data through in the form of a PostgreSQL database or a GraphQL API. To access the PostgreSQL client (PGWeb), copy the URL provided. + +
+ +### Stopping a Service + +If you have not included a stop block in the package manifest, your Substreams will keep indexing the head of the chain, thus consuming bytes from your StreamingFast plan. To stop a service, get the service ID and run the following command: + +```bash +substreams alpha service stop -e https://deploy.streamingfast.io +``` \ No newline at end of file diff --git a/firehose/substreams/docs/new/consume/sql/sql-sink.md b/firehose/substreams/docs/new/consume/sql/sql-sink.md new file mode 100644 index 0000000..76d6214 --- /dev/null +++ b/firehose/substreams/docs/new/consume/sql/sql-sink.md @@ -0,0 +1,232 @@ +--- +description: StreamingFast Substreams SQL sink +--- + +# `substreams-sink-sql` introduction + +### Purpose + +Learn how to use the StreamingFast [`substreams-sink-sql`](https://github.com/streamingfast/substreams-sink-sql) tool with this documentation. A basic Substreams module example is provided to help you get started. We are going to showcase a Substreams module to extract data from the Ethereum blockchain and route it into a Protobuf for persistence in a SQL database. + +The `substreams-sink-sql` today supports two database drivers namely _PostgresSQL_ and _Clickhouse_. The tutorial below will focus on Postgres but we will describe how to connect to the other supported drivers. + +## Installation + +### 1. Install `substreams-sink-sql` + +Install `substreams-sink-sql` by using the pre-built binary release [available in the official GitHub repository](https://github.com/streamingfast/substreams-sink-sql/releases). + +Extract `substreams-sink-sql` into a folder and ensure this folder is referenced globally via your `PATH` environment variable. + +### 2. Set up accompanying code example + +Access the accompanying code example for this tutorial in the official `substreams-sink-sql` repository. You will find the Substreams project for the tutorial in the [docs/tutorial](https://github.com/streamingfast/substreams-sink-sql/tree/develop/docs/tutorial) directory. + +To create the required Protobuf files, run the included `make protogen` command. + +```bash +make protogen +``` + +To ensure proper setup and functionality, use your installation of the [`substreams` CLI](https://substreams.streamingfast.io/reference-and-specs/command-line-interface) to run the example code. + +Use the `make build` and `make stream_db` commands to verify the setup for the example project. Use the included `make` command to build the Substreams module. + +```bash +make build +make stream_db +``` + +### Module handler for sink + +The Rust source code file [`lib.rs`](https://github.com/streamingfast/substreams-sink-sql/blob/develop/docs/tutorial/src/lib.rs) contains an example code, the `db_out` module handler, which prepares and returns the module's [`DatabaseChanges`](https://docs.rs/substreams-database-change/latest/substreams_database_change/pb/database/struct.DatabaseChanges.html) output. The `substreams-sink-sql` tool captures the data sent out of the Substreams module and routes it into the appropriate columns and tables in the SQL database. + +```rust +#[substreams::handlers::map] +fn db_out(block_meta_start: store::Deltas>) -> Result { + let mut database_changes: DatabaseChanges = Default::default(); + transform_block_meta_to_database_changes(&mut database_changes, block_meta_start); + Ok(database_changes) +} +``` + +To gain a full understanding of the procedures and steps required for a database sink Substreams module, review the code in [`lib.rs`](https://github.com/streamingfast/substreams-sink-sql/blob/develop/docs/tutorial/src/lib.rs). The complete code includes the addition of a Substreams store module and other helper functions related to the database. + +**DatabaseChanges** + +The [`DatabaseChanges`](https://github.com/streamingfast/substreams-sink-database-changes/blob/develop/proto/sf/substreams/sink/database/v1/database.proto#L7) Protobuf definition can be viewed at the following link for a peek into the crates implementation. + +When developing your Substreams, the Rust crate [substreams-database-change](https://docs.rs/substreams-database-change/latest/substreams_database_change) can be used to create the required `DatabaseChanges` output type. + +**Note**: An output type of `proto:sf.substreams.sink.database.v1.DatabaseChanges` is required by the map module in the Substreams manifest when working with this sink. + +## 3. Install PostgreSQL + +To proceed with this tutorial, you must have a working PostgreSQL installation. Obtain the software by [downloading it from the vendor](https://www.postgresql.org/download/) and [install it by following the instructions](https://www.postgresql.org/docs/current/tutorial-install.html) for your operating system and platform. + +If you encounter any issues, [refer to the Troubleshooting Installation page](https://wiki.postgresql.org/wiki/Troubleshooting_Installation) on the official PostgreSQL Wiki for assistance. + +## 4. Create example database + +To store the blockchain data output by the Substreams module, you must create a new database in your PostgreSQL installation. The tutorial provides a schema and the PostgreSQL sink tool that handle the detailed aspects of the database design. + +Use the `psql` command in your terminal to launch PostgreSQL. + +Upon successful launch, you will see a prompt similar to the following, ready to accept commands for PostgreSQL. + +```bash +psql (15.1) +Type "help" for help. + +default-database-name=# +``` + +Use the following `SQL` command to create the example database: + +```bash +CREATE DATABASE "substreams_example"; +``` + +## 5. Create configuration file + +Once the database has been created, you must now define the Substreams Sink Config in a Substreams manifest creating a deployable unit. + +Let's create a folder `sink` and in it create a file called `substreams.dev.yaml` with the following content: + +```yaml +specVersion: v0.1.0 +package: + name: "" + version: + +imports: + sql: https://github.com/streamingfast/substreams-sink-sql/releases/download/protodefs-v1.0.1/substreams-sink-sql-protodefs-v1.0.1.spkg + main: ../substreams.yaml + +network: 'mainnet' + +sink: + module: main:db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "../schema.sql" + wire_protocol_access: true +``` + +The `package.name` and `package.version` are meant to be replaced to fit your project. + +The `imports.main` defines your Substreams manifest that you want to sink. The `sink.module` defines which import key (`main` here) and which module's name (`db_out` here). + +The `network` field defines which network this deployment should be part of, in our case `mainnet` + +The `sink.type` defines the type of the config that we are expecting, in our case it's [sf.substreams.sink.sql.v1.Service](https://buf.build/streamingfast/substreams-sink-sql/docs/main:sf.substreams.sink.sql.v1#sf.substreams.sink.sql.v1.Service) (click on the link to see the message definition). + +The `sink.config` is the instantiation of this `sink.type` with the config fully filled. Some config are special because they load from a file or from a folder. For example in our case the `sink.config.schema` is defined with a Protobuf option `load_from_file` which means the content of the `../schema.sql` will actually be inlined in the Substreams manifest. + +> The final final can be found at [`sink/substreams.dev.yaml`](https://github.com/streamingfast/substreams-sink-sql/blob/develop/docs/tutorial/sink/substreams.dev.yaml) + +## 6. Run setup command + +Use the following command to run the `substreams-sink-sql` tool and set up the database for the tutorial. + +```bash +substreams-sink-sql setup "psql://dev-node:insecure-change-me-in-prod@127.0.0.1:5432/substreams_example?sslmode=disable" ./sink/substreams.dev.yaml +``` + +The `"psql://..."` is the DSN (Database Source Name) containing the connection details to your database packed as an URL. The `scheme` (`psql` here) part of the DSN's url defines which driver to use, `psql` is what we are going to use here, see [Drivers](#drivers) section below to see what other DSN you can use here. + +The DSN's URL defines the database IP address, username, and password, which depend on your PostgreSQL installation. Adjust `dev-node` to your own username `insecure-change-me-in-prod` to your password and `127.0.0.1:5432` to where your database can be reached. + +### Drivers + +For DSN configuration for the currently supported drivers, see the list below: + +- [Clickhouse DSN](https://github.com/streamingfast/substreams-sink-sql#clickhouse) +- [PostgresSQL DSN](https://github.com/streamingfast/substreams-sink-sql#postgresql) + +## 7. Sink data to database + +The `substreams-sink-sql` tool sinks data from the Substreams module to the SQL database. Use the tool's `run` command, followed by the endpoint to reach and your Substreams config file to use: + +```bash +substreams-sink-sql run "psql://dev-node:insecure-change-me-in-prod@127.0.0.1:5432/substreams_example?sslmode=disable" ./sink/substreams.dev.yaml +``` + +The endpoint needs to match the blockchain targeted in the Substreams module. The example Substreams module uses the Ethereum blockchain. + +Successful output from the `substreams-sink-sql` tool will resemble the following: + +```log +2023-01-18T12:32:19.107-0800 INFO (sink-sql) starting prometheus metrics server {"listen_addr": "localhost:9102"} +2023-01-18T12:32:19.107-0800 INFO (sink-sql) sink from psql {"dsn": "psql://dev-node:insecure-change-me-in-prod@127.0.0.1:5432/substreams_example?sslmode=disable", "endpoint": "mainnet.eth.streamingfast.io:443", "manifest_path": "substreams.yaml", "output_module_name": "db_out", "block_range": ""} +2023-01-18T12:32:19.107-0800 INFO (sink-sql) starting pprof server {"listen_addr": "localhost:6060"} +2023-01-18T12:32:19.127-0800 INFO (sink-sql) reading substreams manifest {"manifest_path": "sink/substreams.dev.yaml"} +2023-01-18T12:32:20.283-0800 INFO (pipeline) computed start block {"module_name": "store_block_meta_start", "start_block": 0} +2023-01-18T12:32:20.283-0800 INFO (pipeline) computed start block {"module_name": "db_out", "start_block": 0} +2023-01-18T12:32:20.283-0800 INFO (sink-sql) validating output store {"output_store": "db_out"} +2023-01-18T12:32:20.285-0800 INFO (sink-sql) resolved block range {"start_block": 0, "stop_block": 0} +2023-01-18T12:32:20.287-0800 INFO (sink-sql) ready, waiting for signal to quit +2023-01-18T12:32:20.287-0800 INFO (sink-sql) starting stats service {"runs_each": "2s"} +2023-01-18T12:32:20.288-0800 INFO (sink-sql) no block data buffer provided. since undo steps are possible, using default buffer size {"size": 12} +2023-01-18T12:32:20.288-0800 INFO (sink-sql) starting stats service {"runs_each": "2s"} +2023-01-18T12:32:20.730-0800 INFO (sink-sql) session init {"trace_id": "4605d4adbab0831c7505265a0366744c"} +2023-01-18T12:32:21.041-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 2} +2023-01-18T12:32:21.206-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 2} +2023-01-18T12:32:21.319-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 0} +2023-01-18T12:32:21.418-0800 INFO (sink-sql) flushing table rows {"table_name": "block_data", "row_count": 0} +``` + +{% hint style="info" %} +**Note**: If you have an error looking like `load psql table: retrieving table and schema: pq: SSL is not enabled on the server`, it's because SSL is not enabled to reach you database, add `?sslmode=disable` at the end of the `sink.config.dsn` value to connect without SSL. +{% endhint %} + +You can view the database structure by using the following command, after launching PostgreSQL through the `psql` command. + +```bash +=# \c substreams_example +``` + +The table information is displayed in the terminal resembling the following: + +```bash + List of relations + Schema | Name | Type | Owner +--------+------------+-------+---------- + public | block_data | table | postgres + public | cursors | table | postgres +(2 rows) +``` + +You can view the data extracted by Substreams and routed into the database table by using the following command: + +```bash +substreams_example=# SELECT * FROM "block_data"; +``` + +Output similar to the following is displayed in the terminal: + +```bash + id | version | at | number | hash | parent_hash | timestamp +--------------------+---------+---------------------+--------+------------------------------------------------------------------+------------------------------------------------------------------+---------------------- + day:first:19700101 | | 1970-01-01 00:00:00 | 0 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 0000000000000000000000000000000000000000000000000000000000000000 | 1970-01-01T00:00:00Z + month:first:197001 | | 1970-01-01 00:00:00 | 0 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 0000000000000000000000000000000000000000000000000000000000000000 | 1970-01-01T00:00:00Z + day:first:20150730 | | 2015-07-30 00:00:00 | 1 | 88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 2015-07-30T15:26:28Z + month:first:201507 | | 2015-07-01 00:00:00 | 1 | 88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6 | d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 | 2015-07-30T15:26:28Z + day:first:20150731 | | 2015-07-31 00:00:00 | 6912 | ab79f822909750f88dfb9dd0350c1ebe98d5495e9c969cdeb6e0ac993b80175b | 8ffd8c04cb89ef45e0e1163639d51d9ed4fa03dd169db90123a1e047361b46fe | 2015-07-31T00:00:01Z + day:first:20150801 | | 2015-08-01 00:00:00 | 13775 | 2dcecad4cf2079d18169ca05bc21e7ba0add7132b9382984760f43f2761bd822 | abaabb8f8b7f7fa07668fb38fd5a08da9814cd8ad18a793e54eef6fa9b794ab4 | 2015-08-01T00:00:03Z + month:first:201508 | | 2015-08-01 00:00:00 | 13775 | 2dcecad4cf2079d18169ca05bc21e7ba0add7132b9382984760f43f2761bd822 | abaabb8f8b7f7fa07668fb38fd5a08da9814cd8ad18a793e54eef6fa9b794ab4 | 2015-08-01T00:00:03Z +``` + +### Cursors + +When you use Substreams, it sends back a block to a consumer using an opaque cursor. This cursor points to the exact location within the blockchain where the block is. In case your connection terminates or the process restarts, upon re-connection, Substreams sends back the cursor of the last written bundle in the request so that the stream of data can be resumed exactly where it left off and data integrity is maintained. + +You will find that the cursor is saved in the `cursors` table of the `substreams_example` database. + +### Batching + +Insertion for historical blocks is performed in batched to increase ingestion speed. The `--flush-interval` flag can be used to change the default value of 1000 blocks. Also, the flag `--live-block-time-delta ` can be used to change the delta at which we start considering blocks to be live, the logic is `isLive = (now() - block.timestamp) < valueOfFlag(live-block-time-delta)`. + +## Conclusion and review + +Routing data extracted from the blockchain using Substreams is a powerful and useful feature. With Substreams, you can route data to various types of sinks, including files and databases such as PostgreSQL. For more information on other types of sinks and sinking strategies, consult the core Substreams sinks documentation at https://substreams.streamingfast.io/developers-guide/substreams-sinks. diff --git a/firehose/substreams/docs/new/consume/sql/sql.md b/firehose/substreams/docs/new/consume/sql/sql.md new file mode 100644 index 0000000..1256406 --- /dev/null +++ b/firehose/substreams/docs/new/consume/sql/sql.md @@ -0,0 +1,32 @@ +The **Substreams:SQL service** allows you to consume the data extracted from the blockchain through a SQL database. + +
+ +## Different Ways of Setting Up the SQL Consumption + +Substreams offers two different ways of consuming data as SQL: +- Using the Deployable Services (beta) +- Using the SQL sink, which currently supports PostgresSQL and Clickhouse (recommended) + +### - Substreams:SQL Deployable Service (beta) +Use the Substreams CLI to easily send the data of your Substreams to a database. It also has support for **dbt transformations**, so it's great for data analyts! + +You can deploy a new service by using the `substreams alpha service` command. + +#### Hosted Service +The StreamingFast Hosted Service deploys a remote database in the StreamingFast servers, so you don't have to take of the infrastructure. + +#### Local Service +If you want to manage your own infrastructure, you can set up a services environment locally using Docker. + +### - SQL sink (recommended) + +Previous to the implementation of the Deployable Services, the Postgres Sink was used to send data to a database. This is still available and there are a lot developers using it. + + + +## Module Requirements + +In order to the send the data to a SQL database, your Substreams must have a `db_out` module that emits [`DatabaseChanges`](https://docs.rs/substreams-database-change/latest/substreams_database_change/pb/database/struct.DatabaseChanges.html) objects. + +The `DatabaseChanges` object is something that the Postgres sink can understand, thus acting as a conversion layet between the data model of your Substreams and the table structure of the database. diff --git a/firehose/substreams/docs/new/consume/stream/go.md b/firehose/substreams/docs/new/consume/stream/go.md new file mode 100644 index 0000000..399e62d --- /dev/null +++ b/firehose/substreams/docs/new/consume/stream/go.md @@ -0,0 +1,108 @@ +The [Substreams Go Sink library](https://github.com/streamingfast/substreams-sink) allows to you to programmatically stream a Substreams using the Go programming language. The library handles reconnections and provides best practices for error handling. + +The [Substreams Sink Examples GitHub repository](https://github.com/streamingfast/substreams-sink-examples) contains an example that you can use as the starting point to build your custom sink logic. After cloning the repository, move to the `go` directory. + +## Run the Program + +This example is built in the form of a CLI by using the `cobra` library. You can run the program by running the following command structure: + +```bash +go run . sink +``` + +In the following command, `go run .` is used to execute the `main.go` file. The `mainnet.eth.streamingfast.io:443 https://spkg.io/streamingfast/substreams-eth-block-meta-v0.4.3.spkg db_out` part of the command are useful parameters passed to the Go program (separated by a whitespace). + +In the parameters, you pass the Substreams endpoint, the package, and the module to execute. + +```bash +go run . sink mainnet.eth.streamingfast.io:443 https://github.com/streamingfast/substreams-eth-block-meta/releases/download/v0.5.1/substreams-eth-block-meta-v0.5.1.spkg db_out +``` + +#### Inspect the Code + +The example contains code comments, which are very useful to understand and adjust the code to your logic needs. Let's inspect the most important parts of the code: + +```go +var expectedOutputModuleType = string(new(pbchanges.DatabaseChanges).ProtoReflect().Descriptor().FullName()) // 1. + +// ...code omitted... + +func main() { + logging.InstantiateLoggers() + + Run( + "sinker", + "Simple Go sinker sinking data to your terminal", + + Command(sinkRunE, + "sink []", + "Run the sinker code", + RangeArgs(2, 3), + Flags(func(flags *pflag.FlagSet) { + sink.AddFlagsToSet(flags) + }), + ), + + OnCommandErrorLogAndExit(zlog), + ) +} +``` +Create a new sink object from the parameters passed to the program: + +```go +func sinkRunE(cmd *cobra.Command, args []string) error { + endpoint := args[0] + manifestPath := args[1] + + // Find the output module in the manifest sink.moduleName configuration. If you have no + // such configuration, you can change the value below and set the module name explicitly. + outputModuleName := sink.InferOutputModuleFromPackage + if len(args) == 3 { + outputModuleName = args[2] + } + + sinker, err := sink.NewFromViper( + cmd, + // Should be the Protobuf full name of the map's module output, we use + // `substreams-database-changes` imported type. Adjust to your needs. + // + // If your Protobuf is defined in your Substreams manifest, you can use `substream protogen` + // while being in the same folder that contain `buf.gen.yaml` file in the example folder. + expectedOutputModuleType, + endpoint, + manifestPath, + outputModuleName, + // This is the block range, in our case defined as Substreams module's start block and up forever + ":", + zlog, + tracer, + ) + cli.NoError(err, "unable to create sinker: %s", err) + + sinker.OnTerminating(func(err error) { + cli.NoError(err, "unexpected sinker error") + + zlog.Info("sink is terminating") + }) + + // You **must** save the cursor somewhere, saving it to memory while + // make it last until the process is killed, in which on re-start, the + // sinker will resume from start block again. You can simply read from + // a file the string value of the cursor and use `sink.NewCursor(value)` + // to load it. + + // Blocking call, will return on sinker termination + sinker.Run(context.Background(), sink.NewBlankCursor(), sink.NewSinkerHandlers(handleBlockScopedData, handleBlockUndoSignal)) + return nil +} +``` + +It is necessary to handle two kind of Substreams response messages: +- `blockScopedData`: sent by the server whenever a new block is discovered in the blockchain. Contains all the block information that you can decode. +- `blockUndoSignal`: sent every time there is a fork in the blockchain. Because you have probably read incorrect blocks in the `blockScopedData` message, you must rewind back to the latest valid block. + +When you run the sinker, you pass two different functions to handle these messages: + +```go +sinker.Run(context.Background(), sink.NewBlankCursor(), sink.NewSinkerHandlers(handleBlockScopedData, handleBlockUndoSignal)) +``` \ No newline at end of file diff --git a/firehose/substreams/docs/new/consume/stream/javascript.md b/firehose/substreams/docs/new/consume/stream/javascript.md new file mode 100644 index 0000000..f371f3f --- /dev/null +++ b/firehose/substreams/docs/new/consume/stream/javascript.md @@ -0,0 +1,235 @@ +The [Substreams JavaScript library](https://github.com/substreams-js/substreams-js) enables you to run a Substreams, just like you would through the CLI, but using JavaScript. + +The library works both on the client-side and on the server-side, but with some small differences. Clone the [Substreams Sink Examples](https://github.com/streamingfast/substreams-sink-examples) repository contains examples several programming language. Then, move to the `javascript` folder. + +Depending on your needs, you can use the `node` directory (which contains an example using server-side NodeJS) or the `web` directory (which contains an example using client-side JavaScript). + +## Install the dependencies + +The `package.json` contains all the necessary dependencies to run the application. + +The NodeJS example uses `@connectrpc/connect-node`, while the Web example uses `@connectrpc/connect-web`. + +{% tabs %} +{% tab title="NodeJS" %} +```json +{ + "name": "substreams-js-node-example", + "version": "1.0.0", + "description": "", + "main": "index.js", + "dependencies": { + "@substreams/core": "^0.1.19", + "@substreams/manifest": "^0.0.9", + "@connectrpc/connect-node": "1.3.0", + "@connectrpc/connect": "1.3.0" + }, + "type": "module" + } +``` +{% endtab %} + +{% tab title="Web" %} +```json +{ + "name": "substreams-js-web-example", + "private": true, + "version": "0.0.0", + "type": "module", + "dependencies": { + "@substreams/core": "^0.1.19", + "@substreams/manifest": "^0.0.9", + "@connectrpc/connect-web": "1.4.0", + "@connectrpc/connect": "1.4.0" + }, + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "devDependencies": { + "vite": "^5.1.6" + } +} +``` +{% endtab %} +{% endtabs %} + +You can install the dependencies by running: + +```bash +npm install +``` + +## Run the Application + +{% tabs %} +{% tab title="NodeJS" %} +```bash +node index.js +``` + +You will start receiving data! +{% endtab %} + +{% tab title="Web" %} +The Web example uses [ViteJS](https://vitejs.dev/) to create a development server that runs the application: +```bash +npm run dev +``` + +Then, you can navigate to `https://localhost:5173`. You will start receiving data! +{% endtab %} +{% endtabs %} + +## Explore the Application + +When you consume a Substreams package, a long-live gRPC connection is established, therefore, disconnections will happen and should be taken as _normal_. The Substreams keeps track of latest block you consumed by sending a **cursor** to your application. You **must** persist the cursor, so that in the case of a disconnection, you can restart the application from the latest consumed block. + +{% tabs %} +{% tab title="NodeJS" %} +The `index.js` file contains the `main()` function, which runs an infite loop and takes care of managing the disconnections. + +```js +const TOKEN = process.env.SUBSTREAMS_API_TOKEN // Substreams token. By default it takes the SUBSTREAMS_API_TOKEN environment variable of your system +const ENDPOINT = "https://mainnet.eth.streamingfast.io" // Substreams endpont. In this case, Ethereum mainnet +const SPKG = "https://spkg.io/streamingfast/ethereum-explorer-v0.1.2.spkg" // Substreams package. In this case, taken from the substreams.dev registry +const MODULE = "map_block_meta" +const START_BLOCK = '100000' +const STOP_BLOCK = '+10000' + +/* + Entrypoint of the application. + Because of the long-running connection, Substreams will disconnect from time to time. + The application MUST handle disconnections and commit the provided cursor to avoid missing information. +*/ +const main = async () => { + const pkg = await fetchPackage() // Download spkg + const registry = createRegistry(pkg); + + // Create gRPC connection + const transport = createConnectTransport({ + baseUrl: ENDPOINT, + interceptors: [createAuthInterceptor(TOKEN)], + useBinaryFormat: true, + jsonOptions: { + typeRegistry: registry, + }, + }); + + // The infite loop handles disconnections. Every time an disconnection error is thrown, the loop will automatically reconnect + // and start consuming from the latest commited cursor. + while (true) { + try { + await stream(pkg, registry, transport); + } catch (e) { + if (!isErrorRetryable(e)) { + console.log(`A fatal error occurred: ${e}`) + throw e + } + console.log(`A retryable error occurred (${e}), retrying after backoff`) + console.log(e) + // Add backoff from a an easy to use library + } + } +} +``` +{% endtab %} + +{% tab title="Web" %} +The `main.js` file contains the `main()` function, which runs an infite loop and takes care of managing the disconnections. + +```js +const TOKEN = "" // Substreams token. Put here your Substreams API token. +const ENDPOINT = "https://mainnet.eth.streamingfast.io" // Substreams endpont. In this case, Ethereum mainnet +const SPKG = "https://spkg.io/streamingfast/ethereum-explorer-v0.1.2.spkg" // Substreams package. In this case, taken from the substreams.dev registry +const MODULE = "map_block_meta" +const START_BLOCK = '100000' +const STOP_BLOCK = '+10000' + + +/* + Entrypoint of the application. + Because of the long-running connection, Substreams will disconnect from time to time. + The application MUST handle disconnections and commit the provided cursor to avoid missing information. +*/ +const main = async () => { + const pkg = await fetchPackage(); // Download spkg + const registry = createRegistry(pkg); + + const transport = createConnectTransport({ + baseUrl: ENDPOINT, + interceptors: [createAuthInterceptor(TOKEN)], + useBinaryFormat: true, + jsonOptions: { + typeRegistry: registry, + }, + }); + + // The infite loop handles disconnections. Every time an disconnection error is thrown, the loop will automatically reconnect + // and start consuming from the latest commited cursor. + while (true) { + try { + await stream(pkg, registry, transport); + } catch (e) { + if (!isErrorRetryable(e)) { + console.log(`A fatal error occurred: ${e}`) + throw e + } + console.log(`A retryable error occurred (${e}), retrying after backoff`) + console.log(e) + } + } +} +``` +{% endtab %} +{% endtabs %} + +The `stream()` function establishes the actual streaming connection by calling the `streamBlocks` function. The response of the function is a `StatefulResponse` object, which contains a progress message (containing useful information about the Substreams execution. The `handleProgressMessage()` function handles this message) and a response message (containing the message sent from the server. The `handleResponseMessage()` function decodes this message). + +```js +const stream = async (pkg, registry, transport) => { + const request = createRequest({ + substreamPackage: pkg, + outputModule: MODULE, + productionMode: true, + startBlockNum: START_BLOCK, + stopBlockNum: STOP_BLOCK, + startCursor: getCursor() ?? undefined + }); + + // Stream the blocks + for await (const statefulResponse of streamBlocks(transport, request)) { + /* + Decode the response and handle the message. + There different types of response messages that you can receive. You can read more about the response message in the docs: + https://substreams.streamingfast.io/documentation/consume/reliability-guarantees#the-response-format + */ + await handleResponseMessage(statefulResponse.response, registry); + + /* + Handle the progress message. + Regardless of the response message, the progress message is always sent, and gives you useful information about the execution of the Substreams. + */ + handleProgressMessage(statefulResponse.progress, registry); + } +} +``` + +There are different kind of response messages that the server can send. The most common are ones `blockScopedData` and `blockUndoSignal`: +- `blockScopedData`: sent by the server whenever a new block is discovered in the blockchain. Contains all the block information that you can decode. +- `blockUndoSignal`: sent every time there is a fork in the blockchain. Because you have probably read incorrect blocks in the `blockScopedData` message, you must rewind back to the latest valid block. + +```js +export const handleResponseMessage = async (response, registry) => { + switch(response.message.case) { + case "blockScopedData": + handleBlockScopedDataMessage(response.message.value, registry); + break; + + case "blockUndoSignal": + handleBlockUndoSignalMessage(response.message.value); + break; + } +} +``` diff --git a/firehose/substreams/docs/new/consume/stream/stream.md b/firehose/substreams/docs/new/consume/stream/stream.md new file mode 100644 index 0000000..e2ad176 --- /dev/null +++ b/firehose/substreams/docs/new/consume/stream/stream.md @@ -0,0 +1,4 @@ + +The **Substreams:Stream service** allows you to stream blockchain data from your JavaScript, Python or Rust application. + +
\ No newline at end of file diff --git a/firehose/substreams/docs/new/consume/subgraph/subgraph.md b/firehose/substreams/docs/new/consume/subgraph/subgraph.md new file mode 100644 index 0000000..9b6a155 --- /dev/null +++ b/firehose/substreams/docs/new/consume/subgraph/subgraph.md @@ -0,0 +1,5 @@ +It is possible to the send data the of a Substreams to a subgraph, thus creating a Substreams-powered Subgraph. + +You can find information about Substreams-powered Subgraphs in [The Graph documentation](https://thegraph.com/docs/en/cookbook/substreams-powered-subgraphs/). + +
\ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/architecture.md b/firehose/substreams/docs/new/develop/architecture.md new file mode 100644 index 0000000..b970e9b --- /dev/null +++ b/firehose/substreams/docs/new/develop/architecture.md @@ -0,0 +1,37 @@ +--- +description: Learn about the Substreams architecture +--- + +# Parallel execution + +Parallel execution is the process of a Substreams module's code executing multiple segments of blockchain data simultaneously in a forward or backward direction. Substreams modules can be executed in parallel, rapidly producing data for consumption in end-user applications. Parallel execution enables Substreams' highly efficient blockchain data processing capabilities. + +Parallel execution occurs when a requested module's start block is further back in the blockchain's history than the requested start block. For example, if a module starts at block 12,000,000 and a user requests data at block 15,000,000, parallel execution is used. This applies to both the development and production modes of Substreams operation. + +Parallel execution addresses the problem of the slow single linear execution of a module. Instead of running a module in a linear fashion, one block after the other without leveraging full computing power, N number of workers are executed over a different segment of the chain. It means data can be pushed back to the user N times faster than cases using a single worker. + +The server will define an execution schedule and take the module's dependencies into consideration. The server's execution schedule is a list of pairs of (`module, range`), where range contains `N` blocks. This is a configurable value set to 25K blocks, on the server. + +The single map_transfer module will fulfill a request from 0 - 75,000. The server's execution plan returns the results of `[(map_transfer, 0 -> 24,999), (map_transfer, 25,000 -> 49,999), (map_transfer, 50,000 -> 74,999)]`. + +The three pairs will be simultaneously executed by the server handling caching of the output of the store. For stores, an additional step will combine the store keys across multiple segments producing a unified and linear view of the store's state. + +Assuming a chain has 16,000,000 blocks, which translates to 640 segments of 25K blocks. The server currently has a limited amount of concurrency. In theory, 640 concurrent workers could be spawned. In practice, the number of concurrent workers depends on the capabilities of the service provider. For the production endpoint, StreamingFast sets the concurrency to 15 to ensure fair usage of resources for the free service. + +## Production versus development mode for parallel execution + +The amount of parallel execution for the two modes is illustrated in the diagram. Production mode results in more parallel processing than development mode for the requested range. In contrast, development mode consists of more linear processing. Another important note is, forward parallel execution only occurs in production mode. + +

Substreams production versus development mode for parallel execution diagram

+ +## Backward and forward parallel execution steps + +The two steps involved during parallel execution are **backward execution and forward execution**. + +Backward parallel execution consists of executing in parallel block ranges, from the module's initial block, up to the start block of the request. If the start block of the request matches the module's initial block no backward execution is performed. + +Forward parallel execution consists of executing in parallel block ranges from the start block of the request up to the last known final block, also called an irreversible block, or the stop block of the request depending on which is smaller. Forward parallel execution significantly improves the performance of Substreams. + +Backward parallel execution will occur in both development and production modes. + +Forward parallel execution only occurs in production mode. diff --git a/firehose/substreams/docs/new/develop/chain-specific/evm/eth-calls.md b/firehose/substreams/docs/new/develop/chain-specific/evm/eth-calls.md new file mode 100644 index 0000000..33cd008 --- /dev/null +++ b/firehose/substreams/docs/new/develop/chain-specific/evm/eth-calls.md @@ -0,0 +1,388 @@ +--- +description: Learn how to perform Contract Calls (eth_calls) in EVM-compatible Substreams +--- + +# Contract Calls + +EVM-compatible smart contracts are queryable, which means that you can get real-time data from the contract's internal database. +In this tutorial, you will learn how to perform contract calls (`eth_calls`) through Substreams. + +Specifically, you will query the USDT smart contract (`0xdac17f958d2ee523a2206206994597c13d831ec7`) to get the number of decimals used by the token. +The USDT smart contract exposes a read function called `decimals`. + +## Pre-requisites + +- You have some knowledge about Substreams ([modules](https://substreams.streamingfast.io/concepts-and-fundamentals/modules) and [fundamentals](https://substreams.streamingfast.io/concepts-and-fundamentals/fundamentals)). +- You have the latest version of the [CLI](https://substreams.streamingfast.io/getting-started/installing-the-cli) installed. + +## Querying on EthScan + +You can query the `decimals` function by [visiting EthScan](https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#readContract). + + +

USDT contract on EthScan

+ +## Initializing the Substreams project + +1. First, let's use `substreams init` to scaffold a new Substreams project that uses the USDT smart contract: + +```bash +substreams init +``` + +Complete the information required by the previous command, such as name of the project or smart contract to track. +In the `Contract address to track` step, write `0xdac17f958d2ee523a2206206994597c13d831ec7`, the address of the USDT smart contract. + +```bash +Project name (lowercase, numbers, undescores): usdttracker +Protocol: Ethereum +Ethereum chain: Mainnet +Contract address to track (leave empty to use "Bored Ape Yacht Club"): 0xdac17f958d2ee523a2206206994597c13d831ec7 +Would you like to track another contract? (Leave empty if not): +Retrieving Ethereum Mainnet contract information (ABI & creation block) +Fetched contract ABI for dac17f958d2ee523a2206206994597c13d831ec7 +Fetched initial block 4634748 for dac17f958d2ee523a2206206994597c13d831ec7 (lowest 4634748) +Generating ABI Event models for + Generating ABI Events for AddedBlackList (_user) + Generating ABI Events for Approval (owner,spender,value) + Generating ABI Events for Deprecate (newAddress) + Generating ABI Events for DestroyedBlackFunds (_blackListedUser,_balance) + Generating ABI Events for Issue (amount) + Generating ABI Events for Params (feeBasisPoints,maxFee) + Generating ABI Events for Redeem (amount) + Generating ABI Events for RemovedBlackList (_user) + Generating ABI Events for Transfer (from,to,value) +Writing project files +Generating Protobuf Rust code +``` + +2. Move to the project folder and build the Substreams. + +```bash +make build +``` + +3. Then, verify that the Substreams runs correctly. By default, it will output all the events of the smart contract. + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 \ + substreams.yaml \ + map_events \ + --start-block 12292922 \ + --stop-block +1 +``` + +The previous command will output the following: + +```bash +Progress messages received: 0 (0/sec) +Backprocessing history up to requested target block 12292922: +(hit 'm' to switch mode) + +----------- BLOCK #12,292,922 (e2d521d11856591b77506a383033cf85e1d46f1669321859154ab38643244293) --------------- +{ + "@module": "map_events", + "@block": 12292922, + "@type": "contract.v1.Events", + "@data": { + "transfers": [ + { + "evtTxHash": "90e4fd16c989cdc7ecdfd0b6f458eb4be1c538901106bb794bb608f38ac9dd9f", + "evtIndex": 1, + "evtBlockTime": "2021-04-22T23:13:40Z", + "evtBlockNumber": "12292922", + "from": "odjZclYML4FEr4cdtQjwsLEKP78=", + "to": "XmM2sGcWQDHSwcLHo85fcWEdAcw=", + "value": "372200000" + } + ] + } +} + +all done +``` + +## Adding Calls to the Substreams + +The `substreams init` command generates Rust structures based on the ABI of the smart contract provided. All the calls are available under the `abi::contract::functions` namespace of the generated code. Let's take a look. + +1. Open the project in an editor of your choice (for example, VS Code) and navigate to the `lib.rs` file, which contains the main Substreams code. + +2. Create a new function, `get_decimals`, which returns a `BigInt` struct: + +```rust +fn get_decimals() -> substreams::scalar::BigInt { + +} +``` + +3. Import the `abi::contract::functions::Decimals` struct from the generated ABI code. + +```rust +fn get_decimals() -> substreams::scalar::BigInt { + let decimals = abi::contract::functions::Decimals {}; + +} +``` + +4. Next, use the `call` method to make the actual _eth_call_ by providing the smart contract address: + +```rust +fn get_decimals() -> substreams::scalar::BigInt { + let decimals = abi::contract::functions::Decimals {}; + let decimals_option = decimals.call(TRACKED_CONTRACT.to_vec()); + + decimals_option.unwrap() +} +``` + +In this case, the `call` method returns a `substreams::scalar::BigInt` struct containing the number of decimals used in the USDT token (`6`). + +5. You can include this function in the `map_events` module just for testing purposes: + +```rust +#[substreams::handlers::map] +fn map_events(blk: eth::Block) -> Result { + let evt_block_time = + (blk.timestamp().seconds as u64 * 1000) + (blk.timestamp().nanos as u64 / 1000000); + + // Using the decimals function + let decimals = get_decimals(); + substreams::log::info!("Number of decimals for the USDT token: {}", decimals.to_string()); + +...output omitted... +} +``` + +{% hint style="warning" %} +**Important:** Remember that this tutorial shows how to call the `decimals` function, but all the available calls are under the `abi::contract::functions` namespace, so you should be able to find them just by exploring the auto-generated ABI Rust files. +{% endhint %} + +6. To see it in action, just re-build and re-run the Substreams: + +```bash +make build +``` + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 \ + substreams.yaml \ + map_events \ + --start-block 12292922 \ + --stop-block +1 +``` + +The output should be similar to the following: + +```bash +Connected (trace ID 6fb1a55ed17001d850d8c6655226ef6f) +Progress messages received: 0 (0/sec) +Backprocessing history up to requested target block 12292922: +(hit 'm' to switch mode) + +----------- BLOCK #12,292,922 (e2d521d11856591b77506a383033cf85e1d46f1669321859154ab38643244293) --------------- +map_events: log: Number of decimals for the USDT token: 6 +{ + "@module": "map_events", + "@block": 12292922, + "@type": "contract.v1.Events", + "@data": { + "transfers": [ + { + "evtTxHash": "90e4fd16c989cdc7ecdfd0b6f458eb4be1c538901106bb794bb608f38ac9dd9f", + "evtIndex": 1, + "evtBlockTime": "1619133220000", + "evtBlockNumber": "12292922", + "from": "odjZclYML4FEr4cdtQjwsLEKP78=", + "to": "XmM2sGcWQDHSwcLHo85fcWEdAcw=", + "value": "372200000" + } + ] + } +} + +all done +``` + +## Batching Calls + +RPC calls add latency to your Substreams, so you should avoid them as much as possible. However, if you still have to use `eth_calls`, you should batch them. Batching RPC calls meaning making several calls within the same request. + +In the previous USDT example, consider that you want to make three RPC calls: `Decimals`, `Name` and `Symbol`. Instead of creating a request for every call, you can use the `substreams_ethereum::rpc::RpcBatch` struct to make a single request for all the calls. + +1. In the `lib.rs` file, create a new function, `get_calls()` and initialize a batch struct. + +```rust +fn get_calls() { + let batch = substreams_ethereum::rpc::RpcBatch::new(); + +} +``` + +2. Add the calls that you want to retrieve by using the ABI of the smart contract: `abi::contract::functions::Decimals`, `abi::contract::functions::Name` and `abi::contract::functions::Symbol`. + +```rust +fn get_calls() { + let batch = substreams_ethereum::rpc::RpcBatch::new(); + + let responses = batch + .add( + abi::contract::functions::Decimals {}, + TRACKED_CONTRACT.to_vec(), + ) + .add( + abi::contract::functions::Name {}, + TRACKED_CONTRACT.to_vec(), + ) + .add( + abi::contract::functions::Symbol {}, + TRACKED_CONTRACT.to_vec(), + ) + .execute() + .unwrap() + .responses; +} +``` + +The `execute()` method make the actual RPC call and returns an array of responses. In this case, the array will have 3 responses, one for each call made. + +The order used for the response is the same as the order of addition to the request. In this example, `responses[0]` contains `Decimals`, `responses[1]` contains `Name`, and `response[2]` contains `Symbol`. + +3. Decode the `Decimals` response using the ABI. + +```rust +fn get_calls() { + let batch = substreams_ethereum::rpc::RpcBatch::new(); + + let responses = batch + .add( + abi::contract::functions::Decimals {}, + TRACKED_CONTRACT.to_vec(), + ) + .add( + abi::contract::functions::Name {}, + TRACKED_CONTRACT.to_vec(), + ) + .add( + abi::contract::functions::Symbol {}, + TRACKED_CONTRACT.to_vec(), + ) + .execute() + .unwrap() + .responses; + + let decimals: u64; + match substreams_ethereum::rpc::RpcBatch::decode::<_, abi::contract::functions::Decimals>(&responses[0]) { + Some(decoded_decimals) => { + decimals = decoded_decimals.to_u64(); + substreams::log::debug!("decoded_decimals ok: {}", decimals); + } + None => { + substreams::log::debug!("failed to get decimals"); + } + }; +} +``` + +4. Then, do the same for `Name` and `Symbol`. + +```rust +fn get_calls() { + let token_address = &TRACKED_CONTRACT.to_vec(); + let batch = substreams_ethereum::rpc::RpcBatch::new(); + let responses = batch + .add( + abi::contract::functions::Decimals {}, + TRACKED_CONTRACT.to_vec(), + ) + .add( + abi::contract::functions::Name {}, + TRACKED_CONTRACT.to_vec(), + ) + .add( + abi::contract::functions::Symbol {}, + TRACKED_CONTRACT.to_vec(), + ) + .execute() + .unwrap() + .responses; + + let decimals: u64; + match substreams_ethereum::rpc::RpcBatch::decode::<_, abi::contract::functions::Decimals>(&responses[0]) { + Some(decoded_decimals) => { + decimals = decoded_decimals.to_u64(); + substreams::log::debug!("decoded_decimals ok: {}", decimals); + } + None => { + substreams::log::debug!("failed to get decimals"); + } + }; + + let name: String; + match substreams_ethereum::rpc::RpcBatch::decode::<_, abi::contract::functions::Name>(&responses[1]) { + Some(decoded_name) => { + name = decoded_name; + substreams::log::debug!("decoded_name ok: {}", name); + } + None => { + substreams::log::debug!("failed to get name"); + } + }; + + let symbol: String; + match substreams_ethereum::rpc::RpcBatch::decode::<_, abi::contract::functions::Symbol>(&responses[2]) { + Some(decoded_symbol) => { + symbol = decoded_symbol; + substreams::log::debug!("decoded_symbol ok: {}", symbol); + } + None => { + substreams::log::debug!("failed to get symbol"); + } + }; +} +``` + +5. Build and run the Substreams. + +```bash +make build +``` + +```bash +substreams run -e mainnet.eth.streamingfast.io:443 substreams.yaml map_events --start-block 12292922 --stop-block +1 +``` + +You should see an output similar to the following: + +```bash +Connected (trace ID 0f3e3f3868d4f8028b8fd4d6eab7d0b4) +Progress messages received: 0 (0/sec) +Backprocessing history up to requested target block 12292922: +(hit 'm' to switch mode) + + +----------- BLOCK #12,292,922 (e2d521d11856591b77506a383033cf85e1d46f1669321859154ab38643244293) --------------- +map_events: log: decoded_decimals ok: 6 +map_events: log: decoded_name ok: Tether USD +map_events: log: decoded_symbol ok: USDT +{ + "@module": "map_events", + "@block": 12292922, + "@type": "contract.v1.Events", + "@data": { + "transfers": [ + { + "evtTxHash": "90e4fd16c989cdc7ecdfd0b6f458eb4be1c538901106bb794bb608f38ac9dd9f", + "evtIndex": 1, + "evtBlockTime": "2021-04-22T23:13:40Z", + "evtBlockNumber": "12292922", + "from": "odjZclYML4FEr4cdtQjwsLEKP78=", + "to": "XmM2sGcWQDHSwcLHo85fcWEdAcw=", + "value": "372200000" + } + ] + } +} + +all done +``` \ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/chain-specific/evm/overview.md b/firehose/substreams/docs/new/develop/chain-specific/evm/overview.md new file mode 100644 index 0000000..c3d387c --- /dev/null +++ b/firehose/substreams/docs/new/develop/chain-specific/evm/overview.md @@ -0,0 +1 @@ +## EVM Chain Extensions \ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/chain-specific/overview.md b/firehose/substreams/docs/new/develop/chain-specific/overview.md new file mode 100644 index 0000000..90fab7e --- /dev/null +++ b/firehose/substreams/docs/new/develop/chain-specific/overview.md @@ -0,0 +1 @@ +## Chain-Specific Extensions \ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/creating-protobuf-schemas.md b/firehose/substreams/docs/new/develop/creating-protobuf-schemas.md new file mode 100644 index 0000000..1413d7f --- /dev/null +++ b/firehose/substreams/docs/new/develop/creating-protobuf-schemas.md @@ -0,0 +1,147 @@ +--- +description: StreamingFast Substreams protobuf schemas +--- + +# Protobuf schemas + +## Protobuf overview + +Substreams uses Google Protocol Buffers extensively. Protocol Buffers, also referred to as protobufs, are used as the API for data models specific to the different blockchains. Manifests contain references to the protobufs for your Substreams module. + +{% hint style="success" %} +**Tip**: Protobufs define the input and output for modules. +{% endhint %} + +Learn more about the details of Google Protocol Buffers in the official documentation provided by Google. + +**Google Protocol Buffer Documentation** + +[Learn more about Google Protocol Buffers](https://protobuf.dev/) in the official documentation provided by Google. + +**Google Protocol Buffer Tutorial** + +[Explore examples and additional learning material](https://protobuf.dev/programming-guides/proto3/) for Google Protocol Buffers provided by Google. + +### Protobuf definition for Substreams + +Define a protobuf model as [`proto:eth.erc721.v1.Transfers`](https://github.com/streamingfast/substreams-template/blob/develop/proto/erc721.proto) representing a list of ERC721 transfers. + +{% hint style="info" %} +**Note**: The `Transfers` protobuf in the Substreams Template example is located in the proto directory. +{% endhint %} + +{% code title="eth/erc721/v1/erc721.proto" lineNumbers="true" %} + +```protobuf +syntax = "proto3"; + +package eth.erc721.v1; + +message Transfers { + repeated Transfer transfers = 1; +} + +message Transfer { + bytes from = 1; + bytes to = 2; + uint64 token_id = 3; + bytes trx_hash = 4; + uint64 ordinal = 5; +} +``` + +{% endcode %} + +[View the `erc721.proto`](https://github.com/streamingfast/substreams-template/blob/develop/proto/erc721.proto) file in the official Substreams Template example repository. + +#### Identifying data types + +The ERC721 smart contract used in the Substreams Template example contains a `Transfer` event. You can use the event data through a custom protobuf. + +The protobuf file serves as the interface between the module handlers and the data being provided by Substreams. + +{% hint style="success" %} +**Tip**: Protobufs are platform-independent and are defined and used for various blockchains. + +- The ERC721 smart contracts used in the Substreams Template example are generic contracts used across many different Ethereum applications. +- The size and scope of the Substreams module dictates the number of and complexity of protobufs. + {% endhint %} + +The Substreams Template example extracts `Transfer` events from the [Bored Ape Yacht Club smart contract](https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d) which is located on the Ethereum blockchain. + +Several specific data types exist in the Ethereum smart contract ecosystem, some extending the ERC20 and ERC721 base modules. Complex protobufs are created and refined based on the various data types used across the different blockchains. + +{% hint style="success" %} +**Tip**_:_ The use of fully qualified protobuf file paths reduces the risk of naming conflicts when other community members build their [Substreams packages](../reference-and-specs/packages.md#dependencies). +{% endhint %} + +### Generating protobufs + +The [`substreams` CLI](../reference-and-specs/command-line-interface.md) is used to generate the associated Rust code for the protobuf. + +Notice the `protogen` command and Substreams manifest passed into the [`substreams` CLI](../reference-and-specs/command-line-interface.md). + +{% code overflow="wrap" %} + +```bash +substreams protogen ./substreams.yaml --exclude-paths="sf/ethereum,sf/substreams,google" +``` + +{% endcode %} + +The pairing code is generated and saved into the [`src/pb/eth.erc721.v1.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/pb/eth.erc721.v1.rs)Rust file. + +The [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/pb/mod.rs) file located in the `src/pb` directory of the Substreams Template example is responsible for exporting the freshly generated Rust code. + +{% code title="src/pb/mod.rs" overflow="wrap" lineNumbers="true" %} + +```rust +#[path = "eth.erc721.v1.rs"] +#[allow(dead_code)] +pub mod erc721; +``` + +{% endcode %} + +View the [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/pb/mod.rs) file in the repository. + +### Protobuf and Rust optional fields + +Protocol buffers define fields' type by using standard primitive data types, such as integers, booleans, and floats or a complex data type such as `message`, `enum`, `oneof` or `map`. View the [full list](https://developers.google.com/protocol-buffers/docs/proto#scalar) of types in the [Google Protocol Buffers documentation](https://developers.google.com/protocol-buffers/docs/overview). + +Any primitive data types in a message generate the corresponding Rust type,[`String`](https://doc.rust-lang.org/std/string/struct.String.html) for `string`, `u64` for `uint64,` and assign the default value of the corresponding Rust type if the field is not present in a message, an empty string for [`String`](https://doc.rust-lang.org/std/string/struct.String.html), 0 for integer types, `false` for `bool`. + +Rust generates the corresponding `message` type wrapped by an [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) enum type for fields referencing other complex `messages`. The [`None`](https://doc.rust-lang.org/std/option/) variant is used if the field is not present in the message. + +The [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) [`enum`](https://doc.rust-lang.org/book/ch06-01-defining-an-enum.html) is used to represent the presence through [`Some(x)`](https://doc.rust-lang.org/std/option/) or absence through [`None`](https://doc.rust-lang.org/std/option/) of a value in Rust. [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) allows developers to distinguish between a field containing a value versus a field without an assigned a value. + +{% hint style="info" %} +**Note**: The standard approach to represent nullable data in Rust is to wrap optional values in [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html). +{% endhint %} + +The Rust [`match`](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) keyword is used to compare the value of an [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) to a [`Some`](https://doc.rust-lang.org/std/option/) or [`None`](https://doc.rust-lang.org/std/option/) variant. Handle a type wrapped [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) in Rust by using: + +```rust +match person.Location { + Some(location) => { /* Value is present, do something */ } + None => { /* Value is absent, do something */ } +} +``` + +If you are only interested in finding the presence of a value, use the [`if let`](https://doc.rust-lang.org/rust-by-example/flow_control/if_let.html) statement to handle the [`Some(x)`](https://doc.rust-lang.org/std/option/) arm of the [`match`](https://doc.rust-lang.org/rust-by-example/flow_control/match.html) code. + +```rust +if let Some(location) = person.location { + // Value is present, do something +} +``` + +If a value is present, use the [`.unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html) call on the [`Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) to obtain the wrapped data. You'll need to account for these types of scenarios if you control the creation of the messages yourself or if the field is documented as always being present. + +{% hint style="info" %} +**Note**: You need to be **absolutely sure** **the field is always defined**, otherwise Substreams panics and never completes, getting stuck on a block indefinitely. +{% endhint %} + +_**PROST!**_ is a tool for generating Rust code from protobuf definitions. [Learn more about `prost`](https://github.com/tokio-rs/prost) in the project's official GitHub repository. + +[Learn more about `Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) in the official Rust documentation. diff --git a/firehose/substreams/docs/new/develop/develop.md b/firehose/substreams/docs/new/develop/develop.md new file mode 100644 index 0000000..779e24a --- /dev/null +++ b/firehose/substreams/docs/new/develop/develop.md @@ -0,0 +1,34 @@ + +Substreams is a technology that allows you to extract blockchain data in a fast and reliable way! + +## How Does It Work? + +1. **The Substreams Provider** + +
+ +First of all, there is a Substreams provider. The Substreams provider takes care of storing blockchain data and making it available in a fast and reliable way + +2. **Apply Your Transformations** + +
+ +In order for the Substreams provider to know which specific data you want to retrieve, you write a Rust program that defines the transformations that you want to apply to the blockchain data. + +3. **The Substreams Package** + +
+ +Then, you pack your Rust program into a Substreams package. + +4. **Execution of the Package** + +
+ +Lastly, you send the Substreams package to the Substreams provider for execution. The Substreams provider will start streaming data back to you ultra-fast! + +5. **Consume the Data** + +Use one of the Substreams services to consume the extracted data (SQL, Webhooks, Stream, Subgraphs...) + +
\ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/init-project.md b/firehose/substreams/docs/new/develop/init-project.md new file mode 100644 index 0000000..0797a1b --- /dev/null +++ b/firehose/substreams/docs/new/develop/init-project.md @@ -0,0 +1,78 @@ + +Getting started with Substreams is very easy! Depending on the blockchain that you want to use, the best way to get started might change: + +{% tabs %} +{% tab title="Solana" %} +If you want to extract data from Solana, you can take a look at the [Tutorials](../tutorials/solana/solana.md) section, which covers the development of several useful Substreams (SPL tokens, NFT trades, DEX trades...). The [Substreams for Solana Developers](../common/intro-solana.md) section is really useful if this is your first time using Substreams. + +The [Substreams Explorer](../tutorials/solana/explore-solana/explore-solana.md) teaches you to most basic extractions you can perform on Solana. More tooling will be developed for Solana soon. +{% endtab %} + +{% tab title="EVM" %} +If you have a specific smart contract that you want to extract data from, the `substreams init` initializes a Substreams project that extract the events from the smart contract. + +## Tracking a Smart Contract + +{% embed url="https://www.youtube.com/watch?v=vWYuOczDiAA" %} +Initialize a Substreams project +{% endembed %} + +## Tracking a Factory Smart Contract (Dynamic Datasource) + +The `substreams init` command also offers you the possibility to easily track a factory smart contract (in Substreams terminology, a _dynamic datasource_). The following video covers how easy it is to get started with factory contracts on Substreams. + +{% embed url="https://www.youtube.com/watch?v=Vn11ovfSpNU" %} +Initialize a Substreams project +{% endembed %} + +The following is the `substreams init` execution to create a Substreams that tracks new pools created from the UniswapV3 factory contract. + +```bash +✔ Project name (lowercase, numbers, undescores): uniswapv3_factory +Protocol: Ethereum +Ethereum chain: Mainnet +Contract address to track (leave empty to use "Bored Ape Yacht Club"): 0x1f98431c8ad98523631ae4a59f267346ea31f984 +Would you like to track another contract? (Leave empty if not): +Tracking 1 contract(s), let's define a short name for each contract +Choose a short name for 1f98431c8ad98523631ae4a59f267346ea31f984 (lowercase and numbers only): factory +✔ Events only +Retrieving Ethereum Mainnet contract information (ABI & creation block) +Fetched contract ABI for 1f98431c8ad98523631ae4a59f267346ea31f984 +Fetched initial block 12369621 for 1f98431c8ad98523631ae4a59f267346ea31f984 (lowest 12369621) +Generating ABI Event models for factory + Generating ABI Events for FeeAmountEnabled (fee,tickSpacing) + Generating ABI Events for OwnerChanged (oldOwner,newOwner) + Generating ABI Events for PoolCreated (token0,token1,fee,tickSpacing,pool) +Track a dynamic datasource: y +Select the event on the factory that triggers the creation of a dynamic datasource: +Event: PoolCreated +Select the field on the factory event that provides the address of the dynamic datasource: +Field: pool +Choose a short name for the created datasource, (lowercase and numbers only): pool +✔ Events only +Enter a reference contract address to fetch the ABI: 0xc2e9f25be6257c210d7adf0d4cd6e3e881ba25f8 +adding dynamic datasource pool PoolCreated pool + Generating ABI Events for Burn (owner,tickLower,tickUpper,amount,amount0,amount1) + Generating ABI Events for Collect (owner,recipient,tickLower,tickUpper,amount0,amount1) + Generating ABI Events for CollectProtocol (sender,recipient,amount0,amount1) + Generating ABI Events for Flash (sender,recipient,amount0,amount1,paid0,paid1) + Generating ABI Events for IncreaseObservationCardinalityNext (observationCardinalityNextOld,observationCardinalityNextNew) + Generating ABI Events for Initialize (sqrtPriceX96,tick) + Generating ABI Events for Mint (sender,owner,tickLower,tickUpper,amount,amount0,amount1) + Generating ABI Events for SetFeeProtocol (feeProtocol0Old,feeProtocol1Old,feeProtocol0New,feeProtocol1New) + Generating ABI Events for Swap (sender,recipient,amount0,amount1,sqrtPriceX96,liquidity,tick) +Writing project files +Generating Protobuf Rust code +Project "uniswapv3_factory" initialized at "/Users/enolalvarezdeprado/Documents/projects/substreams/dsds/test" + +Run 'make build' to build the wasm code. + +The following substreams.yaml files have been created with different sink targets: + * substreams.yaml: no sink target + * substreams.sql.yaml: PostgreSQL sink + * substreams.clickhouse.yaml: Clickhouse sink + * substreams.subgraph.yaml: Sink into Substreams-based subgraph +``` + +{% endtab %} +{% endtabs %} \ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/modules/README.md b/firehose/substreams/docs/new/develop/modules/README.md new file mode 100644 index 0000000..66c6bb8 --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/README.md @@ -0,0 +1,80 @@ +--- +description: StreamingFast Substreams modules overview +--- + +# Modules + +## Overview + +Modules are an important part of Substreams, offering hooks into the execution of the Substreams compute engine. You can create Substreams data manipulation and transformation strategies within modules. + +Modules are small pieces of Rust code running in a [WebAssembly (WASM)](https://webassembly.org/) virtual machine. They coexist within the stream of blocks sent by the Substreams compute engine, which arrives from a blockchain node. + +Modules have one or more inputs, which can be in the form of a `map` or `store`, or a `Block` or `Clock` object received from the blockchain's data source. + +{% embed url="https://mermaid.ink/svg/pako:eNp1kM0KwjAQhF8l7NkWvEbwIPUJ9NYUWZKtLTZJ2WwEEd_dCAr-4GFhd_h2GOYKNjoCDUfGeVD7ZmWCUqmvSQZiyr6Wy0z1eVlvpmhPbYqZLen_RKeqaq2EMaSe-OBxfhi-320Z_aF8_diYgxC3SSKT_tE7WIAn9ji6kvv6sDdQsngyoMvqqMc8iQETbgXNs0OhrRuLG-gep0QLwCxxdwkWtHCmF9SMWGrwT-p2B02rZZY" %} +Substreams modules data interaction diagram +{% endembed %} + +The diagram shows how the `transfer_map` module extracts the transfers in a `Block` and tracks the total number of transfers. + +{% hint style="info" %} +**Note:** You can use multiple inputs in blockchains because they are clocked, which allows for synchronization between multiple execution streams and improved performance compared to conventional streaming engines. +{% endhint %} + +As seen in the `counters` `store` example diagram, modules can also take in multiple inputs. In this case, two modules feed into a `store`, effectively tracking multiple `counters`. + +{% embed url="https://mermaid.ink/svg/pako:eNqdkE1qAzEMha9itE4GsnWgi5KcINmNh6LamozJeGxsuSGE3L1KW1PIptCdnnjv088NbHQEGk4Z06SOu61ZlHqfoz33JdZsSasydsQTZaqh42ui7mPTvT4cg1qvX1TA9HbxPLmMF5zLv_KOUiyev8JPvF60fm5-J22sC1MufeGYZVDTQ8M07C-jdf4AwAoC5YDeyWtuD5wBOSGQAS2loxHrzAbMchdrTQ6Z9s4LBfQo-9EKsHI8XBcLmnOlZtp5lE-HH9f9EylZic0" %} +Multiple module inputs diagram +{% endembed %} + +Every time a new `Block` is processed, all of the modules are executed as a directed acyclic graph (DAG). + +{% hint style="info" %} +**Note:** The protocol's Block protobuf model always serves as the top-level data source and executes deterministically. +{% endhint %} + +### Single output + +Modules have a single typed output, which is typed to inform consumers of the types of data to expect and how to interpret the bytes being sent. + +{% hint style="success" %} +**Tip**: In subsequent modules, input from one module's data output is used to form a chain of data flow from module to module. +{% endhint %} + +### `map` versus `store` modules + +To develop most non-trivial Substreams, you will need to use multiple `map` and `store` modules. The specific number, responsibilities, and communication methods for these modules will depend on the developer's specific goals for the Substreams development effort. + +The two module types are commonly used together to construct the directed acyclic graph (DAG) outlined in the Substreams manifest. The two module types are very different in their use and how they work. Understanding these differences is vital for harnessing the full power of Substreams. + +### `map` modules + +`map` modules are used for data extraction, filtering, and transformation. They should be used when direct extraction is needed avoiding the need to reuse them later in the DAG. + +To optimize performance, you should use a single `map` module instead of multiple `map` modules to extract single events or functions. It is more efficient to perform the maximum amount of extraction in a single top-level `map` module and then pass the data to other Substreams modules for consumption. This is the recommended, simplest approach for both backend and consumer development experiences. + +Functional `map` modules have several important use cases and facts to consider, including: + +* Extracting model data from an event or function's inputs. +* Reading data from a block and transforming it into a custom protobuf structure. +* Filtering out events or functions for any given number of contracts. + +### `store` modules + +`store` modules are used for the aggregation of values and to persist state that temporarily exists across a block. + +{% hint style="warning" %} +**Important:** Stores should not be used for temporary, free-form data persistence. +{% endhint %} + +Unbounded `store` modules are discouraged. `store` modules shouldn't be used as an infinite bucket to dump data into. + +Notable facts and use cases for working `store` modules include: + +* `store` modules should only be used when reading data from another downstream Substreams module. +* `store` modules cannot be output as a stream, except in development mode. +* `store` modules are used to implement the Dynamic Data Sources pattern from Subgraphs, keeping track of contracts created to filter the next block with that information. +* Downstream of the Substreams output, do not use `store` modules to query anything from them. Instead, use a sink to shape the data for proper querying. + + diff --git a/firehose/substreams/docs/new/develop/modules/aggregation-windows.md b/firehose/substreams/docs/new/develop/modules/aggregation-windows.md new file mode 100644 index 0000000..e86f36a --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/aggregation-windows.md @@ -0,0 +1,62 @@ +--- +description: Building and freeing up aggregation windows +--- + +# Building and freeing up aggregation windows + +Store module key-value storage can hold at most 1 GiB. It is usually enough if used correctly, but it is still a good idea (and sometimes even necessary) to free up unused keys. It is especially true for cases where you work with aggregation windows. + +Consider this store module that aggregates hourly trade counter for each token: +```rust +#[substreams::handlers::store] +pub fn store_total_tx_counts(clock: Clock, events: Events, output: StoreAddBigInt) { + let timestamp_seconds = clock.timestamp.unwrap().seconds; + let hour_id = timestamp_seconds / 3600; + let prev_hour_id = hour_id - 1; + + output.delete_prefix(0, &format!("TokenHourData:{prev_hour_id}:")); + + for event in events.pool_events { + output.add_many( + event.log_ordinal, + &vec![ + format!("TokenHourData:{}:{}", hour_id, event.token0), + format!("TokenHourData:{}:{}", hour_id, event.token1), + ], + &BigInt::from(1 as i32), + ); + } +} +``` + +Let's break it down. + +First, we use `Clock` input source to get the current and previous hour id for the block. + +```rust +let hour_id = timestamp_seconds / 3600; +let prev_hour_id = hour_id - 1; +``` + +Then we build hourly keys for our counters and use `add_many` method to increment them. These counters will be consumed downstream by other modules. + +```rust +output.add_many( + event.log_ordinal, + &vec![ + format!("TokenHourData:{}:{}", hour_id, event.token0), + format!("TokenHourData:{}:{}", hour_id, event.token1), + ], + &BigInt::from(1 as i32), +); +``` + +Here's the trick. Since we don't need these counters outside of the hourly window, we can safely delete these key-value pairs for the previous hourly window and free up the memory. + +This is done using `delete_prefix` method: +```rust +output.delete_prefix(0, &format!("TokenHourData:{prev_hour_id}:")); +``` + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) \ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/modules/dynamic-data-sources.md b/firehose/substreams/docs/new/develop/modules/dynamic-data-sources.md new file mode 100644 index 0000000..bd3a7fe --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/dynamic-data-sources.md @@ -0,0 +1,108 @@ +--- +description: Dynamic data sources and Substreams +--- + +# Dynamic data sources and Substreams + +Using Factory contract is a quite common pattern used by dApps when the main smart contract deploys and manages multiple identical associated contracts, i.e. one smart contract for each Uniswap or Curve swap pool. + +When developing traditional subgraphs, you could use [data source templates](https://thegraph.com/docs/en/developing/creating-a-subgraph/#data-source-templates) approach to keep track of such dynamically deployed smart contracts. + +Here's how you can achieve that with Substreams. + +We'll be using Uniswap V3 example where the Factory creates and deploys its smart contract for each pool. + +You start with a simple map module that emits all pool creation events: +```yaml +- name: map_pools_created + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:uniswap.types.v1.Pools +``` + +```rust +#[substreams::handlers::map] +pub fn map_pools_created(block: Block) -> Result { + Ok(Pools { + pools: block + .events::(&[&UNISWAP_V3_FACTORY]) + .filter_map(|(event, log)| { + // skipped: extracting pool information from the transaction + Some(Pool { + address, + token0, + token1, + ..Default::default() + }) + }) + .collect(), + }) +} +``` + +We can now take that map module output and direct these pool creation events into a Substreams key-value store using a store module: +```yaml + - name: store_pools_created + kind: store + updatePolicy: set + valueType: proto:uniswap.types.v1.Pool + inputs: + - map: map_pools_created +``` +```rust +#[substreams::handlers::store] +pub fn store_pools_created(pools: Pools, store: StoreSetProto) { + for pool in pools.pools { + let pool_address = &pool.address; + store.set(pool.log_ordinal, format!("pool:{pool_address}"), &pool); + } +} +``` + +Above we are using `pool:{pool_address}` as a key to store the pool information. Eventually, our store will contain all Uniswap pools. +Now, in the downstream modules, we can easily retrieve our pool from the store whenever we need it. + +```yaml +- name: map_events + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + - store: store_pools_created + output: + type: proto:uniswap.types.v1.Events +``` + +```rust +#[substreams::handlers::map] +pub fn map_events(block: Block, pools_store: StoreGetProto) -> Result { + let mut events = Events::default(); + + for trx in block.transactions() { + for (log, call_view) in trx.logs_with_calls() { + let pool_address = &Hex(&log.address).to_string(); + + let pool = match pools_store.get_last(format!("pool:{pool_address}")) { + Some(pool) => pool, + None => { continue; } + }; + + // use the pool information from the store + } + } + + Ok(events) +} +``` + +Here we use `pools_store.get_last()` method to get the pool from the store by its smart contract address. Once we have it, we can use that information to analyze the swap transaction and emit the events. + +Alternatively, we could make RPC calls to get the pool details from an RPC node but that would be extremely inefficient considering that we would need to make RPC calls for millions of such events. Using a store will be much faster. + +For a real-life application of this pattern see [Uniswap V3 Substreams](https://github.com/streamingfast/substreams-uniswap-v3) + + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) +* [Substreams Sink Entity Changes](https://github.com/streamingfast/substreams-sink-entity-changes) \ No newline at end of file diff --git a/firehose/substreams/docs/new/develop/modules/inputs.md b/firehose/substreams/docs/new/develop/modules/inputs.md new file mode 100644 index 0000000..3849f47 --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/inputs.md @@ -0,0 +1,149 @@ +--- +description: StreamingFast Substreams module inputs +--- + +# Inputs + +## `inputs` overview + +Modules receive `inputs` of three types: + +* `source` +* `map` +* `store` +* `params` + +## Input type `source` + +An `inputs` of type `source` represents a chain-specific, Firehose-provisioned protobuf object. Learn more about the supported protocols and their corresponding message types in the [Chains and inputs documentation](../../reference-and-specs/chains-and-endpoints.md). + +{% hint style="info" %} +**Note**: The different blockchains reference different `Block` objects. For example, Solana references its `Block` object as `sf.solana.type.v1.Block`. Ethereum-based Substreams modules specify `sf.ethereum.type.v2.Block.` +{% endhint %} + +The `source` `inputs` type \_\_ is defined in the Substreams manifest. It is important to specify the correct `Block` object for the chain. + +```yaml +modules: +- name: my_mod + inputs: + - source: sf.ethereum.type.v2.Block +``` + +#### `Clock` object + +The `sf.substreams.v1.Clock` object is another source type available on any of the supported chains. + +The `sf.substreams.v1.Clock` represents: + +* `Block` `number` +* `Block` `ID` +* `Block` `timestamp` + +## Input type `params` + +An `inputs` of type `params` represents a parameterizable module input. Those parameters can be specified either: + +* in the `params` section of the manifest, +* on the command-line (using `substreams run -p` for instance), +* by tweaking the protobuf objects directly when consuming from your favorite language + +See the [Manifest's `params` manifest section of the Reference & specs](../../reference-and-specs/manifests.md#params) for more details. + +## Input type `map` + +An input of type `map` represents the output of another `map` module. It defines a parent-child relationship between modules. + +The object's type is defined in the [`output.type`](../../reference-and-specs/manifests.md#modules-.output) attribute of the `map` module. + +{% hint style="warning" %} +**Important**: The graph built by input dependencies is a Directed Acyclic Graph, which means there can be no circular dependencies. +{% endhint %} + +Define the `map` input type in the manifest and choose a name for the `map` reflecting the logic contained within it. + +{% code title="manifest excerpt" %} +```yaml + inputs: + - map: my_map +``` +{% endcode %} + +Learn more about `maps` in the [Modules](./) section. + +## Input type `store` + +A `store inputs` type represents the state of another `store` used by the Substreams module being created. + +The developer defines the `store` `inputs` type in the Substreams manifest and gives the `store` a descriptive name that reflects the logic contained within it, similar to a `map`. + +Store modules are set to `get` mode by default: + +{% code title="manifest excerpt" %} +```yaml + inputs: + - store: my_store # defaults to mode: get +``` +{% endcode %} + +Alternatively, set `stores` to `deltas` mode by using: + +{% code title="manifest excerpt" %} +```yaml + inputs: + - store: my_delta_store + mode: deltas +``` +{% endcode %} + +### Store access `mode` + +Substreams uses two types of `mode` for modules: + +* `get` +* `delta` + +### Store constraints + +* A `store` can only receive `inputs` as read-only. +* A `store` cannot depend on itself. + +### `get` mode + +`get` mode provides a key-value store readily queryable and guaranteed to be in sync with the block being processed. + +{% hint style="success" %} +**Tip**: `get` `mode` is the default mode for modules. +{% endhint %} + +### `delta` mode + +`delta` `mode` modules are [protobuf objects](../../../pb/sf/substreams/v1/substreams.proto#L124) containing all the changes occurring in the `store` module available in the same block. + +`delta` mode enables you to loop through keys and decode values mutated in the module. + +#### `store` `deltas` + +The protobuf model for `StoreDeltas` is defined by using: + +{% code overflow="wrap" %} +```protobuf +message StoreDeltas { + repeated StoreDelta deltas = 1; +} + +message StoreDelta { + enum Operation { + UNSET = 0; + CREATE = 1; + UPDATE = 2; + DELETE = 3; + } + Operation operation = 1; + uint64 ordinal = 2; + string key = 3; + bytes old_value = 4; + bytes new_value = 5; +} +``` +{% endcode %} diff --git a/firehose/substreams/docs/new/develop/modules/keys-in-stores.md b/firehose/substreams/docs/new/develop/modules/keys-in-stores.md new file mode 100644 index 0000000..a75b986 --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/keys-in-stores.md @@ -0,0 +1,71 @@ +--- +description: Using keys in stores +--- + +# Keys in stores + +We use store modules to aggregate the data in the underlying key-value storage. It is important to have a system for organizing your keys to be able to efficiently retrieve, filter and free them when needed. + +In most cases, you will encode data into your keys into segmented parts, adding a prefix as namespace for example `user` and `
` joined together using a separator. Segments in a key are conventionally joined with `:` as a separator. + +Here are some examples, +- `Pool:{pool_address}:volumeUSD` - `{pool_address}` pool total traded USD volume +- `Token:{token_addr}:volume` - total `{token_addr}` token volume traded +- `UniswapDayData:{day_id}:volumeUSD` - `{day_id}` daily USD trade volume +- `PoolDayData:{day_id}:{pool_address}:{token_addr}:volumeToken1` - total `{day_id}` daily volume of `{token_addr}` token that went through a `{pool_address}` pool in token1 equivalent + +In the example of a counter store below, we increment transaction counters for different metrics that we could use in the downstream modules: +```rust +#[substreams::handlers::store] +pub fn store_total_tx_counts(clock: Clock, events: Events, output: StoreAddBigInt) { + let timestamp_seconds = clock.timestamp.unwrap().seconds; + let day_id = timestamp_seconds / 86400; + let hour_id = timestamp_seconds / 3600; + let prev_day_id = day_id - 1; + let prev_hour_id = hour_id - 1; + + for event in events.pool_events { + let pool_address = &event.pool_address; + let token0_addr = &event.token0; + let token1_addr = &event.token1; + + output.add_many( + event.log_ordinal, + &vec![ + format!("pool:{pool_address}"), + format!("token:{token0_addr}"), + format!("token:{token1_addr}"), + format!("UniswapDayData:{day_id}"), + format!("PoolDayData:{day_id}:{pool_address}"), + format!("PoolHourData:{hour_id}:{pool_address}"), + format!("TokenDayData:{day_id}:{token0_addr}"), + format!("TokenDayData:{day_id}:{token1_addr}"), + format!("TokenHourData:{hour_id}:{token0_addr}"), + format!("TokenHourData:{hour_id}:{token1_addr}"), + ], + &BigInt::from(1 as i32), + ); + } +} +``` + +In the downstream modules consuming this store, you can query the store by key in `get` mode. Or, an even more powerful approach would be to filter needed store deltas by segments. `key` module of the `substreams` crates offers several helper functions. Using these functions you can extract the first/last/nth segment from a key: + +```rust +for delta in deltas.into_iter() { + let kind = key::first_segment(delta.get_key()); + let address = key::segment_at(delta.get_key(), 1); + // Do something for this kind and address +} +``` + +`key` module also provides corresponding `try_` methods that don't panic: +- `first_segment` & `try_first_segment` +- `last_segment` & `try_last_segment` +- `segment_at` & `try_segment_at` + +For a full example see [Uniswap V3 Substreams](https://github.com/streamingfast/substreams-uniswap-v3/blob/ca90fe3908a76905b43e05f0522e1e9338d88972/src/lib.rs#L1139-L1163) + +## Links +* [Uniswap-v3 Subgraph and Substreams](https://github.com/streamingfast/substreams-uniswap-v3) +* [Key module documentation](https://docs.rs/substreams/latest/substreams/key/index.html) diff --git a/firehose/substreams/docs/new/develop/modules/outputs.md b/firehose/substreams/docs/new/develop/modules/outputs.md new file mode 100644 index 0000000..8bf5541 --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/outputs.md @@ -0,0 +1,20 @@ +--- +description: StreamingFast Substreams module outputs +--- + +# Output + +## Output overview + +Substreams `map` modules support a single `output`. The `output` must be a protobuf populated by data acquired inside the `map` module. If the module intends to provide a basic `output` type of a single value, such as a `String` or `bool`, a protobuf is still required. The single value needs to be wrapped in a protobuf for use as the `output` value from a `map` module. + +{% hint style="info" %} +**Note:** `store` modules **cannot** define an `output`. +{% endhint %} + +An `output` object has a `type` attribute defining the `type` of the `output` for the `map` module. The `output` definition is located in the Substreams manifest, within the module definition. + +```yaml +output: + type: proto:eth.erc721.v1.Transfers +``` diff --git a/firehose/substreams/docs/new/develop/modules/setting-up-handlers.md b/firehose/substreams/docs/new/develop/modules/setting-up-handlers.md new file mode 100644 index 0000000..01c62a3 --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/setting-up-handlers.md @@ -0,0 +1,179 @@ +--- +description: StreamingFast Substreams module handlers +--- + +# Module handlers + +## Module handlers overview + +To begin creating the custom module handlers initialize a new Rust project by using the `cargo` `init` command. + +```bash +# Creates a empty Rust project suitable for WASM compilation +cargo init --lib +``` + +Update the generated [`Cargo.toml`](https://github.com/streamingfast/substreams-template/blob/develop/Cargo.toml) file by using: + +{% code title="Cargo.toml" overflow="wrap" lineNumbers="true" %} +```rust +[package] +name = "substreams-template" +version = "0.1.0" +description = "Substream template demo project" +edition = "2021" +repository = "https://github.com/streamingfast/substreams-template" + +[lib] +name = "substreams" +crate-type = ["cdylib"] + +[dependencies] +ethabi = "17" +hex-literal = "0.3.4" +prost = "0.11" +# Use latest from https://crates.io/crates/substreams +substreams = "0.5" +# Use latest from https://crates.io/crates/substreams-ethereum +substreams-ethereum = "0.9" + +# Required so ethabi > ethereum-types build correctly under wasm32-unknown-unknown +[target.wasm32-unknown-unknown.dependencies] +getrandom = { version = "0.2", features = ["custom"] } + +[build-dependencies] +anyhow = "1" +substreams-ethereum = "0.8" + +[profile.release] +lto = true +opt-level = 's' +strip = "debuginfo" +``` +{% endcode %} + +View the [`Cargo.toml`](https://github.com/streamingfast/substreams-template/blob/develop/Cargo.toml) file in the repository. + +You compile the Rust code into [WebAssembly (WASM)](https://webassembly.org/), a binary instruction format that runs in a virtual machine. The compilation process generates a .so file. + +### **`Cargo.toml` configuration file breakdown** + +Build the Rust dynamic system library after the `package` by using: + +{% code title="Cargo.toml excerpt" %} +```toml +... + +[lib] +crate-type = ["cdylib"] +``` +{% endcode %} + +The next definition in the [`Cargo.toml`](https://github.com/streamingfast/substreams-template/blob/develop/Cargo.toml) configuration file is for `dependencies`. + +{% hint style="info" %} +**Note**: Module handlers compile down to a WASM module. Explicitly specify the target`asm32-unknown-unknown` by using `[target.wasm32-unknown-unknown.dependencies]`. +{% endhint %} + +#### `ethabi` + +The [`ethabi` crate ](https://crates.io/crates/ethabi)is used to decode events from the application binary interface (ABI) and is required for `substreams-ethereum` ABI capabilities. + +#### `hex-literal` + +The [`hex-literal` crate ](https://crates.io/crates/hex-literal)is used to define bytes from hexadecimal string literals at compile time. + +#### `substreams` + +The [`substreams` crate](https://docs.rs/substreams/latest/substreams/) offers all the basic building blocks for the module handlers. + +#### `substreams-ethereum` + +The [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum-core) offers all the Ethereum constructs including blocks, transactions, eth, and useful ABI decoding capabilities. + +Because code is being built by WASM output it's necessary to configure Rust to match the correct architecture. Create and add a [`rust-toolchain.toml`](https://github.com/streamingfast/substreams-template/blob/develop/rust-toolchain.toml) configuration file at the root of your Substreams directory. + +### Rust toolchain + +{% code title="rust-toolchain.toml" overflow="wrap" lineNumbers="true" %} +```toml +[toolchain] +channel = "1.65" +components = [ "rustfmt" ] +targets = [ "wasm32-unknown-unknown" ] +``` +{% endcode %} + +View the [`rust-toolchain.toml`](https://github.com/streamingfast/substreams-template/blob/develop/rust-toolchain.toml) file in the repository. + +Build the code by using: + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +### **Rust build target** + +When running `cargo build` the target is set to `wasm32-unknown-unknown`, which is important because it specifies the goal is to generate compiled WASM code. + +To avoid having to specify the target `wasm32-unknown-unknown` for every `cargo` command, create a `config.toml` configuration file in the `.cargo` directory at the root of the Substreams project. The `config.toml` configuration file allows the target to be set automatically for all `cargo` commands. + +The content for the `config.toml` configuration file is: + +{% code title=".cargo/config.toml" %} +```toml +[build] +target = "wasm32-unknown-unknown" +``` +{% endcode %} + +The `config.toml` configuration file updates the default `cargo build` command to `cargo build --target wasm32-unknown-unknown` eliminating the need to specify the target manually every time you build. + +### ABI generation + +The [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum) offers an [`Abigen`](https://docs.rs/substreams-ethereum-abigen/latest/substreams_ethereum_abigen/build/struct.Abigen.html) API to generate Rust types from a smart contract's ABI. + +Place the contract's [ABI JSON file](../../.gitbook/assets/erc721.json) in the Substreams project in the `abi` directory. + +### **Rust build script** + +Before building a package, Cargo compiles a build script into an executable if it has not already been built. The build script runs as part of the build process responsible for performing a variety of tasks. + +To cause Cargo to compile and run a script before building a package, place a file called `build.rs` in the root of the package. + +Create a [`build.rs`](https://github.com/streamingfast/substreams-template/blob/develop/build.rs) build script file in the root of the Substreams project by using: + +{% code title="build.rs" overflow="wrap" lineNumbers="true" %} +```rust +use anyhow::{Ok, Result}; +use substreams_ethereum::Abigen; + +fn main() -> Result<(), anyhow::Error> { + Abigen::new("ERC721", "abi/erc721.json")? + .generate()? + .write_to_file("src/abi/erc721.rs")?; + + Ok(()) +} +``` +{% endcode %} + +View the [`build.rs`](https://github.com/streamingfast/substreams-template/blob/develop/build.rs) file in the repository. + +Run the build script to generate the ABI directory and files. + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +Create a [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/abi/mod.rs) export file in the ABI directory, which is created by the Rust build process. The [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/abi/mod.rs) export file is responsible for exporting the generated Rust code. + +{% code title="src/abi/mod.rs" lineNumbers="true" %} +```rust +pub mod erc721; +``` +{% endcode %} + +View the [`mod.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/abi/mod.rs) file in the repository. + +You're now ready to [write the module handlers](writing-module-handlers.md). diff --git a/firehose/substreams/docs/new/develop/modules/types.md b/firehose/substreams/docs/new/develop/modules/types.md new file mode 100644 index 0000000..623ed0f --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/types.md @@ -0,0 +1,115 @@ +--- +description: StreamingFast Substreams module types +--- + +# Module types + +## Module types overview + +Substreams uses two types of modules, `map` and `store`. + +* `map` modules are functions receiving bytes as input and output. These bytes are encoded protobuf messages. +* `store` modules are stateful, saving and tracking data through the use of key-value stores. + +### `store` modules + +`store` modules write to key-value stores. + +{% hint style="info" %} +**Note**: To ensure successful and proper parallelization can occur, `store` modules are not permitted to read any of their own data or values. +{% endhint %} + +Stores declaring their own data types expose methods capable of mutating keys within the `store`. + +### Core principle usage of stores + +* Do not save keys in stores **unless they are going to be read by a downstream module**. Substreams stores are a way to aggregate data, but they are **not meant to be a storage layer**. +* Do not save all transfers of a chain in a `store` module, rather, output them in a `map` and have a downstream system store them for querying. + +There are limitations impose on store usage. Specifically, each key/value entry must be smaller than 10MiB while a store cannot exceed 1GiB total. Keys being string, each character in the key account for 1 byte of storage space. + +### Important store properties + +The two important store properties are `valueType,`and `updatePolicy`. + +#### `valueType` property + +The `valueType` property instructs the Substreams runtime of the data to be saved in the `stores`. + +| Value | Description | +| ------------------------------ | -------------------------------------------------------------------------------- | +| `bytes` | A basic list of bytes | +| `string` | A UTF-8 string | +| `proto:fully.qualified.Object` | Decode bytes by using the protobuf definition `fully.qualified.Object` | +| `int64` | A string-serialized integer by using int64 arithmetic operations | +| `float64` | A string-serialized floating point value, used for float64 arithmetic operations | +| `bigint` | A string-serialized integer, supporting precision of any depth | +| `bigfloat` | A string-serialized floating point value, supporting precision up to 100 digits | + +#### `updatePolicy` property + +The `updatePolicy` property determines what methods are available in the runtime. + +The `updatePolicy` also defines the merging strategy for identical keys found in two contiguous stores produced through parallel processing. + +| Method | Supported Value Types | Merge strategy\* | +| ------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `set` | `bytes`, `string`, `proto:...` | The last key wins | +| `set_if_not_exists` | `bytes`, `string`, `proto:...` | The first key wins | +| `add` | `int64`, `bigint`, `bigfloat`, `float64` | Values are summed up | +| `min` | `int64`, `bigint`, `bigfloat`, `float64` | The lowest value is kept | +| `max` | `int64`, `bigint`, `bigfloat`, `float64` | The highest value is kept | +| `append` | `string`, `bytes` | Both keys are concatenated in order. Appended values are limited to 8Kb. Aggregation pattern examples are available in the [`lib.rs`](https://github.com/streamingfast/substreams-uniswap-v3/blob/develop/src/lib.rs#L760) file | + +{% hint style="success" %} +**Tip**: All update policies provide the `delete_prefix` method. +{% endhint %} + +The merge strategy is **applied during parallel processing**. + +* A module has built two partial stores containing keys for segment A, blocks 0-1000, and segment B, blocks 1000-2000, and is prepared to merge them into a complete store. +* The complete store is represented acting as if the processing was done in a linear fashion, starting at block 0 and proceeding up to block 2000. + +{% hint style="warning" %} +**Important**_**:** _ To preserve the parallelization capabilities of the system, **Substreams is not permitted to read what it has written or read from a `store` actively being written**. + +A downstream module is created to read from a store by using one of its inputs to point to the output of the `store` module. +{% endhint %} + +### Ordinals + +Ordinals allow a key-value store to have multiple versions of a key within a single block. The `store` APIs contain different methods of `ordinal` or `ord`. + +For example, the price for a token can change after transaction B and transaction D, and a downstream module might want to know the value of a key before transaction B **and between B and D**_._ + +{% hint style="warning" %} +**Important**: Ordinals **must be set every time a key is set** and **you can only set keys in increasing ordinal order**, or by using an ordinal equal to the previous. +{% endhint %} + +In situations where a single key for a block is required and ordering in the store is not important, the ordinal uses a value of zero. + +### `store` modes + +You can consume data in one of two modes when declaring a `store` as an input to a module. + +#### `get mode` + +The `get mode` function provides the module with a key-value store that is guaranteed to be synchronized up to the block being processed. It's possible to query stores by using the `get_at`, `get_last` and `get_first` methods. + +{% hint style="success" %} +**Tip:** Lookups are local, in-memory, and **extremely high-speed**. +{% endhint %} + +The definition of `store` method behavior is: + +* The `get_last` method is the fastest because it queries the store directly. +* The `get_first` method first goes through the current block's deltas in reverse order, before querying the store, in case the key being queried was mutated in the block. +* The `get_at` method unwinds deltas up to a specific ordinal, ensuring values for keys set midway through a block are still reachable. + +#### `deltas mode` + +`deltas` mode provides the module with **all the changes** occurring in the source `store` module. Updates, creates, and deletes of the keys mutated during the block processing become available. + +{% hint style="info" %} +**Note:** When a `store` is set as an input to the module, it is read-only and you cannot modify, update or mutate them. +{% endhint %} diff --git a/firehose/substreams/docs/new/develop/modules/writing-module-handlers.md b/firehose/substreams/docs/new/develop/modules/writing-module-handlers.md new file mode 100644 index 0000000..a1895c9 --- /dev/null +++ b/firehose/substreams/docs/new/develop/modules/writing-module-handlers.md @@ -0,0 +1,287 @@ +--- +description: StreamingFast Substreams module handler creation +--- + +# Module handler creation + +## Module handler creation overview + +After generating the ABI and protobuf Rust code, you need to write the handler code. Save the code into the `src` directory and use the filename [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs). + +{% code title="src/lib.rs" overflow="wrap" lineNumbers="true" %} +```rust +mod abi; +mod pb; +use hex_literal::hex; +use pb::erc721; +use substreams::prelude::*; +use substreams::{log, store::StoreAddInt64, Hex}; +use substreams_ethereum::{pb::eth::v2 as eth, NULL_ADDRESS}; + +// Bored Ape Club Contract +const TRACKED_CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); + +substreams_ethereum::init!(); + +/// Extracts transfers events from the contract +#[substreams::handlers::map] +fn map_transfers(blk: eth::Block) -> Result { + Ok(erc721::Transfers { + transfers: blk + .events::(&[&TRACKED_CONTRACT]) + .map(|(transfer, log)| { + substreams::log::info!("NFT Transfer seen"); + + erc721::Transfer { + trx_hash: log.receipt.transaction.hash.clone(), + from: transfer.from, + to: transfer.to, + token_id: transfer.token_id.low_u64(), + ordinal: log.block_index() as u64, + } + }) + .collect(), + }) +} + +/// Store the total balance of NFT tokens for the specific TRACKED_CONTRACT by holder +#[substreams::handlers::store] +fn store_transfers(transfers: erc721::Transfers, s: StoreAddInt64) { + log::info!("NFT holders state builder"); + for transfer in transfers.transfers { + if transfer.from != NULL_ADDRESS { + log::info!("Found a transfer out {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.from), -1); + } + + if transfer.to != NULL_ADDRESS { + log::info!("Found a transfer in {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.to), 1); + } + } +} + +fn generate_key(holder: &Vec) -> String { + return format!("total:{}:{}", Hex(holder), Hex(TRACKED_CONTRACT)); +} +``` +{% endcode %} + +View the [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs) file in the repository. + +### **Module handler breakdown** + +The logical sections of the [`lib.rs`](https://github.com/streamingfast/substreams-template/blob/develop/src/lib.rs) file are outlined and described in greater detail. + +Import the necessary modules. + +{% code title="lib.rs excerpt" overflow="wrap" %} +```rust +mod abi; +mod pb; +use hex_literal::hex; +use pb::erc721; +use substreams::{log, store, Hex}; +use substreams_ethereum::{pb::eth::v2 as eth, NULL_ADDRESS, Event}; +``` +{% endcode %} + +Store the tracked contract in the example in a `constant`. + +{% code title="lib.rs excerpt" %} +```rust +const TRACKED_CONTRACT: [u8; 20] = hex!("bc4ca0eda7647a8ab7c2061c2e118a18a936f13d"); +``` +{% endcode %} + +Define the `map` module in the Substreams manifest. + +{% code title="manifest excerpt" %} +```yaml +- name: map_transfers + kind: map + initialBlock: 12287507 + inputs: + - source: sf.ethereum.type.v2.Block + output: + type: proto:eth.erc721.v1.Transfers +``` +{% endcode %} + +Notice the: `name: map_transfers`, the module in the manifest name matches the handler function name. Also notice, there is one [`inputs`](inputs.md) and one [`output`](outputs.md) definition. + +The [`inputs`](inputs.md) uses the standard Ethereum Block, `sf.ethereum.type.v2.Block,` provided by the [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum-core). + +The output uses the `type` `proto:eth.erc721.v1.Transfers` which is a custom protobuf definition provided by the generated Rust code. + +The function signature produced resembles: + +{% code title="lib.rs excerpt" %} +```rust +#[substreams::handlers::map] +fn map_transfers(blk: eth::Block) -> Result { + ... +} +``` +{% endcode %} + +### **Rust macros** + +Did you notice the `#[substreams::handlers::map]` on top of the function? It is a [Rust macro](https://doc.rust-lang.org/book/ch19-06-macros.html) provided by the [`substreams` crate](https://docs.rs/substreams/latest/substreams/). + +The macro decorates the handler function as a `map.` Define `store` modules by using the syntax `#[substreams::handlers::store]`. + +### Module handler function + +The `map` extracts ERC721 transfers from a _`Block`_ object. The code finds all the `Transfer` `events` emitted by the tracked smart contract. As the events are encountered they are decoded into `Transfer` objects. + +{% code title="lib.rs excerpt" overflow="wrap" %} +```rust +/// Extracts transfers events from the contract +#[substreams::handlers::map] +fn map_transfers(blk: eth::Block) -> Result { + Ok(erc721::Transfers { + transfers: blk + .events::(&[&TRACKED_CONTRACT]) + .map(|(transfer, log)| { + substreams::log::info!("NFT Transfer seen"); + + erc721::Transfer { + trx_hash: log.receipt.transaction.hash.clone(), + from: transfer.from, + to: transfer.to, + token_id: transfer.token_id.low_u64(), + ordinal: log.block_index() as u64, + } + }) + .collect(), + }) +} +``` +{% endcode %} + +Define the `store` module in the Substreams manifest. + +{% code title="manifest excerpt" %} +```yaml +- name: store_transfers + kind: store + initialBlock: 12287507 + updatePolicy: add + valueType: int64 + inputs: + - map: map_transfers +``` +{% endcode %} + +{% hint style="info" %} +**Note:** `name: store_transfers` corresponds to the handler function name. +{% endhint %} + +The `inputs` corresponds to the `output` of the `map_transfers` `map` module typed as `proto:eth.erc721.v1.Transfers`. The custom protobuf definition is provided by the generated Rust code. + +{% code title="lib.rs excerpt" %} +```rust +#[substreams::handlers::store] +fn store_transfers(transfers: erc721::Transfers, s: store::StoreAddInt64) { + ... +} +``` +{% endcode %} + +{% hint style="info" %} +**Note**: the `store` always receives itself as its own last input. +{% endhint %} + +In the example the `store` module uses an `updatePolicy` set to `add` and a `valueType` set to `int64` yielding a writable `store` typed as `StoreAddInt64`. + +{% hint style="info" %} +**Note**: **Store types** + +* The writable `store` is always the last parameter of a `store` module function. +* The `type` of the writable `store` is determined by the `updatePolicy` and `valueType` of the `store` module. +{% endhint %} + +The goal of the `store` in the example is to track a holder's current NFT `count` for the smart contract provided. The tracking is achieved through the analysis of `Transfers`. + +**`Transfers` in detail** + +* If the "`from`" address of the `transfer` is the `null` address (`0x0000000000000000000000000000000000000000`) and the "`to`" address is not the `null` address, the "`to`" address is minting a token, which results in the `count` being incremented. +* If the "`from`" address of the `transfer` is not the `null` address and the "`to`" address is the `null` address, the "`from`" address has burned a token, which results in the `count` being decremented. +* If both the "`from`" and the "`to`" address is not the `null` address, the `count` is decremented from the "`from`" address and incremented for the "`to`" address. + +### `store` concepts + +There are three important things to consider when writing to a `store`: + +* `ordinal` +* `key` +* `value` + +#### `ordinal` + +`ordinal` represents the order in which the `store` operations are applied. + +The `store` handler is called once per `block.` + +The `add` operation may be called multiple times during execution, for various reasons such as discovering a relevant event or encountering a call responsible for triggering a method call. + +{% hint style="info" %} +**Note**: Blockchain execution models are linear. Operations to add must be added linearly and deterministically. +{% endhint %} + +If an `ordinal` is specified, the order of execution is guaranteed. In the example, when the `store` handler is executed by a given set of `inputs`, such as a list of `Transfers`, it emits the same number of `add` calls and `ordinal` values for the execution. + +#### `key` + +Stores are [key-value stores](https://en.wikipedia.org/wiki/Key%E2%80%93value\_database). Care needs to be taken when crafting a `key` to ensure it is unique **and flexible**. + +If the `generate_key` function in the example returns the `TRACKED_CONTRACT` address as the `key`, it is not unique among different token holders. + +The `generate_key` function returns a unique `key` for holders if it contains only the holder's address. + +{% hint style="warning" %} +**Important**: Issues are expected when attempting to track multiple contracts. +{% endhint %} + +#### `value` + +The value being stored. The `type` is dependent on the `store` `type` being used. + +{% code title="lib.rs excerpt" overflow="wrap" %} +```rust +#[substreams::handlers::store] +fn store_transfers(transfers: erc721::Transfers, s: StoreAddInt64) { + log::info!("NFT holders state builder"); + for transfer in transfers.transfers { + if transfer.from != NULL_ADDRESS { + log::info!("Found a transfer out {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.from), -1); + } + + if transfer.to != NULL_ADDRESS { + log::info!("Found a transfer in {}", Hex(&transfer.trx_hash)); + s.add(transfer.ordinal, generate_key(&transfer.to), 1); + } + } +} + +fn generate_key(holder: &Vec) -> String { + return format!("total:{}:{}", Hex(holder), Hex(TRACKED_CONTRACT)); +} +``` +{% endcode %} + +### Summary + +Both handler functions have been written. + +One handler function for extracting relevant _`transfers`_, and a second to store the token count per recipient. + +Build Substreams to continue the setup process. + +```bash +cargo build --target wasm32-unknown-unknown --release +``` + +The next step is to run Substreams with all of the changes made by using the generated code. diff --git a/firehose/substreams/docs/new/develop/parameterized-modules.md b/firehose/substreams/docs/new/develop/parameterized-modules.md new file mode 100644 index 0000000..b373e32 --- /dev/null +++ b/firehose/substreams/docs/new/develop/parameterized-modules.md @@ -0,0 +1,156 @@ + + +Substreams allows you to pass parameters to your modules by specifying them in the manifest. + +## Parameterization of a Factory contract + +It's quite common for a smart contract to be deployed on different networks or even by different dApps within the same network. Uniswap Factory smart contract is a good example of that. + +When running Substreams for a dApp, you need to know the smart contract deployment address and for obvious reasons, this address will be different for each deployment. + +Instead of hard-coding the address in the Substreams binary, you can customize it without having to rebuild or even repackage the Substreams package. The consumer can then just provide the address as a parameter. + +First, you need to add the `params` field as an input. Note that it's always a string and it's always the first input for the module: + +```yaml +modules: + - name: map_pools_created + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:uniswap.types.v1.Pools +params: + map_params: 1f98431c8ad98523631ae4a59f267346ea31f984 +``` + +You can specify the default value directly in the manifest. In this case, we use `0x1f98431c8ad98523631ae4a59f267346ea31f984` - the deployment address for UniswapV3 contract on Ethereum Mainnet. + +Handling the parameter in the module is easy. The module handler receives it as a first input parameter and you can use it to filter transactions instead of the hard-coded value: + +```rust +#[substreams::handlers::map] +pub fn map_pools_created(params: String, block: Block) -> Result { + let factory_address = Hex::decode(params).unwrap(); + Ok(Pools { + pools: block + .events::(&[&factory_address]) + .filter_map(|(event, log)| { + // skipped: extracting pool information from the transaction + Some(Pool { + address, + token0, + token1, + ..Default::default() + }) + }) + .collect(), + }) +} +``` + +To pass the parameter to the module using `substreams` CLI you can use `-p` key: + +```bash +substreams gui -e $SUBSTREAMS_ENDPOINT map_pools_created -t +1000 -p map_pools_created="1f98431c8ad98523631ae4a59f267346ea31f984"` +``` + +### Documenting parameters +It's always a good idea to document what the params represent and how they are structured, so the consumers of your modules know how to properly parameterize them. You can use `doc` field for the module definition in the manifest. + +```yaml +modules: + - name: map_pools_created + kind: map + inputs: + - source: sf.ethereum.type.v2.Block + - params: string + output: + type: proto:uniswap.types.v1.Pools + doc: | + Params contains Uniswap factory smart contract address without `0x` prefix, i.e. 1f98431c8ad98523631ae4a59f267346ea31f984 for Ethereum Mainnet +``` + +## Advanced parameters + +Sometimes you may need to use multiple parameters for a module. To pass multiple parameters, you can encode them as a URL-encoded query string, i.e. `param1=value1¶m2=value2`. + +Suppose you want to track transfers to/from a certain address exceeding a certain amount of ETH. Your module manifest could look like this: + +```yaml +modules: + - name: map_whale_transfers + kind: map + inputs: + - params: string + - source: sf.ethereum.type.v2.Block + output: + type: proto:Transfers +params: + map_params: address=aaa..aaa&amount=100 +``` + +Our module gets a params string with two parameters: `address` and `amount`. + +In your module handler, you can decode your parameters using one of the URL decoding crates such as `serde_qs`, `serde_urlencoded` or your own helper functions. Here's an example using `serde_qs`: + +```rust +#[derive(Debug, Deserialize)] +struct Params { + address: String, + amount: u64, +} + +#[substreams::handlers::map] +pub fn map_whale_transfers(params: String, block: Block) -> Result { + let query: Params = serde_qs::from_str(params.as_str()).unwrap(); + log::info!("Tracking transfers for address: {} of more than {} ETH", query.address, query.amount); + + // filter transfers by address and amount +} +``` + +Sometimes parameters can be optional, i.e. you want to track all transfers rather than a specific address. Decoding will look like this in that case: + +```rust +#[derive(Debug, Deserialize)] +struct QueryParams { + address: Option, + amount: u64, +} + +#[substreams::handlers::map] +pub fn map_whale_transfers(params: String, block: Block) -> Result { + let query: QueryParams = serde_qs::from_str(params.as_str()).unwrap(); + + if query.address.is_none() { + log::info!("Tracking all of more than {} ETH", query.amount); + } + else { + log::info!("Tracking transfers for address: {} of more than {} ETH", query.address, query.amount); + } +} +``` + +You can even pass a vector of addresses to track multiple specific whales in our example: + +```rust +#[derive(Debug, Deserialize)] +struct QueryParams { + address: Vec, + amount: u64, +} + +#[substreams::handlers::map] +pub fn map_whale_transfers(params: String, block: Block) -> Result { + let query: QueryParams = serde_qs::from_str(params.as_str()).unwrap(); + log::info!("Tracking transfers for addresses: {:?} of more than {} ETH", query.address, query.amount); +} +``` + +Depending on the crate you use to decode params string, you can pass them to Substreams CLI like this for example: + +```bash +substreams gui map_whale_transfers -p map_whale_transfers="address[]=aaa..aaa&address[]=bbb..bbb&amount=100" +``` diff --git a/firehose/substreams/docs/new/develop/rust-crates.md b/firehose/substreams/docs/new/develop/rust-crates.md new file mode 100644 index 0000000..606ac78 --- /dev/null +++ b/firehose/substreams/docs/new/develop/rust-crates.md @@ -0,0 +1,33 @@ +--- +description: Substreams Rust APIs +--- + +# Rust crates + +## Rust crates overview + +The official [`substreams` crate](https://crates.io/crates/substreams) helps developers create module handlers. + +* Use the [`substreams-ethereum` crate](https://crates.io/crates/substreams-ethereum) for Ethereum and other Ethereum-compatible chains. +* Use the [`substreams-solana` crate](https://crates.io/crates/substreams-solana) for the Solana blockchain. +* Use the [`substreams-antelope` crate](https://github.com/pinax-network/substreams-antelope) for the Antelope blockchain (by [Pinax Network](https://pinax.network/)) + +{% hint style="info" %} +**Note**: If a crate is not available for Substreams, you can use the `spkg` release for the chain, which includes the `Block` protobuf model, and generate the Rust structs yourself. +{% endhint %} + +### Third-party libraries + +Any third-party library capable of compiling `wasm32` can be used for execution in Substreams services. + +Some libraries include kernel syscalls or other operations unavailable in the Substreams execution environment and cannot be compiled to WASM. The internal functionality of third-party libraries is an essential consideration for Substreams development. + +Helpful information people found through the use of third-party libraries and Substreams together include: + +* [`tiny_keccak`](https://docs.rs/tiny-keccak): an implementation of Keccak-derived functions specified in FIPS-202, SP800-185, and KangarooTwelve. + +### Git Versions + +[Specifying dependencies from Git repositories](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-dependencies-from-git-repositories) is possible in Rust, but it is **NOT recommended** by the Substreams team, as they are not fully tested and can bring bugs to your Substreams project. + +The Substreams team recommends using the templates provided in the [Examples section](examples.md) as a starting point to develop your Substreams application. \ No newline at end of file diff --git a/firehose/substreams/docs/new/references/chains-and-endpoints.md b/firehose/substreams/docs/new/references/chains-and-endpoints.md new file mode 100644 index 0000000..adbf654 --- /dev/null +++ b/firehose/substreams/docs/new/references/chains-and-endpoints.md @@ -0,0 +1,70 @@ +--- +description: StreamingFast Substreams chains and endpoints +--- + +# Chains and endpoints + +## Chains and endpoints overview + +The different blockchains have separate endpoints that Substreams uses. You will use the endpoint that matches the blockchain you've selected for your development initiative. + +### Supported blockchains and protobuf models + +There are different Substreams providers that you can use. StreamingFast and Pinax are the largest providers currently. + +Protobuf definitions and public endpoints are provided for the supported protocols and chains. + +{% hint style="success" %} +**Tip**: All of the endpoints listed in the documentation require [authentication](../common/authentication.md) before use. +{% endhint %} + +{% hint style="warning" %} +**Important**_:_ Endpoints serve protobuf models specific to the underlying blockchain protocol and must match the `source:` field for the module. + +**Streaming a `sf.near.type.v1.Block` from an Ethereum endpoint does not work!** +{% endhint %} + +

Protobuf for the different supported chains

+ +| Protocol | Proto model | Latest package | +| -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| Ethereum | [`sf.ethereum.type.v2.Block`](https://github.com/streamingfast/firehose-ethereum/blob/develop/proto/sf/ethereum/type/v2/type.proto) | [ethereum-v0.10.4.spkg](https://github.com/streamingfast/sf-ethereum/releases/download/v0.10.2/ethereum-v0.10.4.spkg) | +| NEAR | [`sf.near.type.v1.Block`](https://github.com/streamingfast/firehose-near/blob/develop/proto/sf/near/type/v1/type.proto) | | +| Solana | [`sf.solana.type.v1.Block`](https://github.com/streamingfast/firehose-solana/blob/develop/proto/sf/solana/type/v1/type.proto) | [solana-v0.1.0.spkg](https://github.com/streamingfast/sf-solana/releases/download/v0.1.0/solana-v0.1.0.spkg) | +| Cosmos | [`sf.cosmos.type.v1.Block`](https://github.com/figment-networks/proto-cosmos/blob/main/sf/cosmos/type/v1/type.proto) | | +| Arweave | [`sf.arweave.type.v1.Block`](https://github.com/streamingfast/firehose-arweave/blob/develop/proto/sf/arweave/type/v1/type.proto) | | +| Bitcoin | [`sf.bitcoin.type.v1.Block`](https://github.com/streamingfast/firehose-bitcoin/blob/develop/proto/sf/bitcoin/type/v1/type.proto) | | + +## Official Endpoints + +* **Ethereum Mainnet**: `mainnet.eth.streamingfast.io:443` +* **Ethereum Görli**: `goerli.eth.streamingfast.io:443` +* **Ethereum Sepolia**: `sepolia.eth.streamingfast.io:443` +* **Ethereum Holesky**: `holesky.eth.streamingfast.io:443` +* **Polygon** **Mainnet**: `polygon.streamingfast.io:443` +* **Mumbai Testnet**: `mumbai.streamingfast.io:443` +* **Arbitrum One:** `arb-one.streamingfast.io:443` +* **BNB**: `bnb.streamingfast.io:443` +* **Optimism:** `opt-mainnet.streamingfast.io:443` +* **Avalanche C-Chain Mainnet**: `avalanche-mainnet.streamingfast.io:443` +* **NEAR Mainnet**: `mainnet.near.streamingfast.io:443` +* **NEAR Testnet**: `testnet.near.streamingfast.io:443` +* **Solana mainnet-beta**: `mainnet.sol.streamingfast.io:443` +* **Arweave Mainnet**: `mainnet.arweave.streamingfast.io:443` +* **Bitcoin Mainnet**: `mainnet.btc.streamingfast.io:443` + +## Community Endpoints + +### Pinax Endpoints + +* **Ethereum Mainnet**: `eth.substreams.pinax.network:9000` +* **Ethereum Görli**: `goerli.substreams.pinax.network:9000` +* **Ethereum Sepolia**: `sepolia.substreams.pinax.network:9000` +* **Polygon Mainnet**: `polygon.substreams.pinax.network:9000` +* **Mumbai Testnet**: `mumbai.substreams.pinax.network:9000` +* **BNB**: `bsc.substreams.pinax.network:9000` +* **Chapel Testnet**: `bsc.substreams.pinax.network:9000` +* **NEAR Mainnet**: `near.substreams.pinax.network:9000` +* **NEAR Testnet**: `neartest.substreams.pinax.network:9000` + +You can support other blockchains for Substreams through Firehose instrumentation. Learn more in the [official Firehose documentation](https://firehose.streamingfast.io/). diff --git a/firehose/substreams/docs/new/references/change-log.md b/firehose/substreams/docs/new/references/change-log.md new file mode 100644 index 0000000..402261c --- /dev/null +++ b/firehose/substreams/docs/new/references/change-log.md @@ -0,0 +1,1059 @@ +# Change log +All notable changes to the Substreams project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +{% hint style="info" %} +Substreams builds upon Firehose.\ +Keep track of [Firehose releases and Data model updates](https://firehose.streamingfast.io/release-notes/change-logs) in the Firehose documentation. +{% endhint %} + +## v1.3.5 + +### Code generation + +* Added `substreams init` support for creating a substreams with data from fully-decoded Calls instead of only extracting events. + +## v1.3.4 + +### Code generation + +* Added `substreams init` support for creating a substreams with the "Dynamic DataSources" pattern (ex: a `Factory` contract creating `pool` contracts through the `PoolCreated` event) +* Changed `substreams init` to always add prefixes the tables and entities with the project name +* Fixed `substreams init` support for unnamed params and topics on log events + +## v1.3.3 + +* Fixed `substreams init` generated code when dealing with Ethereum ABI events containing array types. + + > [!NOTE] + > For now, the generated code only works with Postgres, an upcoming revision is going to lift that constraint. + +## v1.3.2 + +* Fixed `store.has_at` Wazero signature which was defined as `has_at(storeIdx: i32, ord: i32, key_ptr: i32, key_len: i32)` but should have been `has_at(storeIdx: i32, ord: i64, key_ptr: i32, key_len: i32)`. +* Fixed the local `substreams alpha service serve` ClickHouse deployment which was failing with a message regarding fork handling. +* Catch more cases of WASM deterministic errors as `InvalidArgument`. +* Added some output-stream info to logs. + +## v1.3.1 + +### Server + +* Fixed error-passing between tier2 and tier1 (tier1 will not retry sending requests that fail deterministicly to tier2) +* Tier1 will now schedule a single job on tier2, quickly ramping up to the requested number of workers after 4 seconds of delay, to catch early exceptions +* "store became too big" is now considered a deterministic error and returns code "InvalidArgument" + +## v1.3.0 + +### Highlights + +* Support new `networks` configuration block in `substreams.yaml` to override modules' *params* and *initial_block*. Network can be specified at run-time, avoiding the need for separate spkg files for each chain. +* [BREAKING CHANGE] Remove the support for the `deriveFrom` overrides. The `imports`, along with the new `networks` feature, should provide a better mechanism to cover the use cases that `deriveFrom` tried to address. + +> [!NOTE] +> These changes are all handled in the substreams CLI, applying the necessary changes to the package before sending the requests. The Substreams server endpoints do not need to be upgraded to support it. + +### Added + +* Added `networks` field at the top level of the manifest definition, with `initialBlock` and `params` overrides for each module. See the substreams.yaml.example file in the repository or https://substreams.streamingfast.io/reference-and-specs/manifests for more details and example usage. +* The networks `params` and `initialBlock`` overrides for the chosen network are applied to the module directly before being sent to the server. All network configurations are kept when packing an .spkg file. +* Added the `--network` flag for choosing the network on `run`, `gui` and `alpha service deploy` commands. Default behavior is to use the one defined as `network` in the manifest. +* Added the `--endpoint` flag to `substreams alpha service serve` to specify substreams endpoint to connect to +* Added endpoints for Antelope chains +* Command 'substreams info' now shows the params + +### Removed + +* Removed the handling of the `DeriveFrom` keyword in manifest, this override feature is going away. +* Removed the `--skip-package-validation`` option only on run/gui/inspect/info + +### Changed + +* Added the `--params` flag to `alpha service deploy` to apply per-module parameters to the substreams before pushing it. +* Renamed the `--parameters` flag to `--deployment-params` in `alpha service deploy`, to clarify the intent of those parameters (given to the endpoint, not applied to the substreams modules) +* Small improvement on `substreams gui` command: no longer reads the .spkg multiple times with different behavior during its process. + +## v1.2.0 + +### Client + +* Fixed bug in `substreams init` with numbers in ABI types + +### Backend + +* Return the correct GRPC code instead of wrapping it under an "Unknown" error. "Clean shutdown" now returns CodeUnavailable. This is compatible with previous substreams clients like substreams-sql which should retry automatically. +* Upgraded components to manage the new block encapsulation format in merged-blocks and on the wire required for firehose-core v1.0.0 + +## v1.1.22 + +### alpha service deployments + +* Fix fuzzy matching when endpoint require auth headers +* Fix panic in "serve" when trying to delete a non-existing deployment +* Add validation check of substreams package before sending deploy request to server + +## v1.1.21 + +### Changed + +* Codegen: substreams-database-change to v1.3, properly generates primary key to support chain reorgs in postgres sink. +* Sink server commands all moved from `substreams alpha sink-*` to `substreams alpha service *` +* Sink server: support for deploying sinks with DBT configuration, so that users can deploy their own DBT models (supported on postgres and clickhouse sinks). Example manifest file segment: + + ```yaml + [...] + + sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + wire_protocol_access: true + postgraphile_frontend: + enabled: true + pgweb_frontend: + enabled: true + dbt: + files: "./dbt" + run_interval_seconds: 60 + ``` + + where "./dbt" is a folder containing the dbt project. +* Sink server: added REST interface support for clickhouse sinks. Example manifest file segment: + + ```yaml + [...] + + sink: + module: db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.clickhouse.sql" + wire_protocol_access: true + engine: clickhouse + postgraphile_frontend: + enabled: false + pgweb_frontend: + enabled: false + rest_frontend: + enabled: true + ``` + +### Fixed + +* Fix `substreams info` cli doc field which wasn't printing any doc output + +## v1.1.20 + +* Optimized start of output stream in developer mode when start block is in reversible segment and output module does not have any stores in its dependencies. +* Fixed bug where the first streamable block of a chain was not processed correctly when the start block was set to the default zero value. + +## v1.1.19 + +### Changed + +* Codegen: Now generates separate substreams.{target}.yaml files for sql, clickhouse and graphql sink targets. + +### Added + +* Codegen: Added support for clickhouse in schema.sql + +### Fixed + +* Fixed metrics for time spent in eth\_calls within modules stats (server and GUI) +* Fixed `undo` json message in 'run' command +* Fixed stream ending immediately in dev mode when start/end blocks are both 0. +* Sink-serve: fix missing output details on docker-compose apply errors +* Codegen: Fixed pluralized entity created for db\_out and graph\_out + +## v1.1.18 + +### Fixed + +* Fixed a regression where start block was not resolved correctly when it was in the reversible segment of the chain, causing the substreams to reprocess a segment in tier 2 instead of linearly in tier 1. + +## v1.1.17 + +### Fixed + +* Missing decrement on metrics `substreams_active_requests` + +## v1.1.16 + +### Added + +* `substreams_active_requests` and `substreams_counter` metrics to `substreams-tier1` + +### Changed + +* `evt_block_time` in ms to timestamp in `lib.rs`, proto definition and `schema.sql` + +## v1.1.15 + +### Highlights + +* This release brings the `substreams init` command out of alpha! You can quickly generate a Substreams from an Ethereum ABI: ![init-flow](../assets/init-flow.gif) +* New Alpha feature: deploy your Substreams Sink as a deployable unit to a local docker environment! ![sink-deploy-flow](../assets/sink-deploy-flow.gif) +* See those two new features in action in this [tutorial](https://substreams.streamingfast.io/tutorials/from-ethereum-address-to-sql) + +### Added + +* Sink configs can now use protobuf annotations (aka Field Options) to determine how the field will be interpreted in substreams.yaml: + + * `load_from_file` will put the content of the file directly in the field (string and bytes contents are supported). + * `zip_from_folder` will create a zip archive and put its content in the field (field type must be bytes). + + Example protobuf definition: + + ``` + import "sf/substreams/v1/options.proto"; + + message HostedPostgresDatabase { + bytes schema = 1 [ (sf.substreams.v1.options).load_from_file = true ]; + bytes extra_config_files = 2 [ (sf.substreams.v1.options).zip_from_folder = true ]; + } + ``` + + Example manifest file: + + ```yaml + [...] + network: mainnet + + sink: + module: main:db_out + type: sf.substreams.sink.sql.v1.Service + config: + schema: "./schema.sql" + wire_protocol_access: true + postgraphile_frontend: + enabled: true + pgweb_frontend: + enabled: true + ``` +* `substreams info` command now properly displays the content of sink configs, optionally writing the fields that were bundled from files to disk with `--output-sinkconfig-files-path=` + +### Changed + +* `substreams alpha init` renamed to `substreams init`. It now includes `db_out` module and `schema.sql` to support the substreams-sql-sink directly. +* The override feature has been overhauled. Users may now override an existing substreams by pointing to an override file in `run` or `gui` command. This override manifest will have a `deriveFrom` field which points to the original substreams which is to be overriden. This is useful to port a substreams to one network to another. Example of an override manifest: + + ``` + deriveFrom: path/to/mainnet-substreams.spkg #this can also be a remote url + + package: + name: "polygon-substreams" + version: "100.0.0" + + network: polygon + + initialBlocks: + module1: 17500000 + params: + module1: "address=2a75ca72679cf1299936d6104d825c9654489058" + ``` +* The `substreams run` and `substreams gui` commands now determine the endpoint from the 'network' field in the manifest if no value is passed in the `--substreams-endpoint` flag. +* The endpoint for each network can be set by using an environment variable `SUBSTREAMS_ENDPOINTS_CONFIG_`, ex: `SUBSTREAMS_ENDPOINTS_CONFIG_MAINNET=my-endpoint:443` +* The `substreams alpha init` has been moved to `substreams init` + +### Fixed + +* fixed the `substreams gui` command to correctly compute the stop-block when given a relative value (ex: '-t +10') + +## v1.1.14 + +### Bug fixes + +* Fixed (bumped) substreams protobuf definitions that get embedded in `spkg` to match the new progress messages from v1.1.12. +* Regression fix: fixed a bug where negative start blocks would not be resolved correctly when using `substreams run` or `substreams gui`. +* In the request plan, the process previously panicked when errors related to block number validation occurred. Now the error will be returned to the client. + +## v1.1.13 + +### Bug fixes + +* If the initial block or start block is less than the first block in the chain, the substreams will now start from the first block in the chain. Previously, setting the initial block to a block before the first block in the chain would cause the substreams to hang. +* Fixed a bug where the substreams would fail if the start block was set to a future block. The substreams will now wait for the block to be produced before starting. + +## v1.1.12 + +### Highlights + +* Complete redesign of the progress messages: + * Tier2 internal stats are aggregated on Tier1 and sent out every 500ms (no more bursts) + * No need to collect events on client: a single message now represents the current state + * Message now includes list of running jobs and information about execution stages + * Performance metrics has been added to show which modules are executing slowly and where the time is spent (eth calls, store operations, etc.) + +### Upgrading client and server + +> \[!IMPORTANT] The client and servers will both need to be upgraded at the same time for the new progress messages to be parsed: +> +> * The new Substreams servers will _NOT_ send the old `modules` field as part of its `progress` message, only the new `running_jobs`, `modules_stats`, `stages`. +> * The new Substreams clients will _NOT_ be able to decode the old progress information when connecting to older servers. + +However, the actual data (and cursor) will work correctly between versions. Only incompatible progress information will be ignored. + +### CLI + +#### Changed + +* Bumped `substreams` and `substreams-ethereum` to latest in `substreams alpha init`. +* Improved error message when `` is not received, previously this would lead to weird error message, now, if the input is likely a manifest, the error message will be super clear. + +#### Fixed + +* Fixed compilation errors when tracking some contracts when using `substreams alpha init`. + +#### Added + +* `substreams info` now takes an optional second parameter `` to show how the substreams modules can be divided into stages +* Pack command: added `-c` flag to allow overriding of certain substreams.yaml values by passing in the path of a yaml file. example yaml contents: + + ```yaml + package: + name: my_custom_package_name + + network: arbitrum-one + initialBlocks: + module_name_1: 123123123 + params: + mod1: "custom_parameter" + ``` + +### Backend + +#### Removed + +* Removed `Config.RequestStats`, stats are now always enabled. + +## v1.1.11 + +### Fixes + +* Added metering of live blocks + +## v1.1.10 + +### Backend changes + +* Fixed/Removed: jobs would hang when config parameter `StateBundleSize` was different from `SubrequestsSize`. The latter has been removed completely: Subrequests size will now always be aligned with bundle size. +* Auth: added support for _continuous authentication_ via the grpc auth plugin (allowing cutoff triggered by the auth system). + +### CLI changes + +* Fixed params handling in `gui` mode + +## v1.1.9 + +### Backend changes + +* Massive refactoring of the scheduler: prevent excessive splitting of jobs, grouping them into stages when they have the same dependencies. This should reduce the required number of `tier2` workers (2x to 3x, depending on the substreams). +* The `tier1` and `tier2` config have a new configuration `StateStoreDefaultTag`, will be appended to the `StateStoreURL` value to form the final state store URL, ex: `StateStoreURL="/data/states"` and `StateStoreDefaultTag="v2"` will make `/data/states/v2` the default state store location, while allowing users to provide a `X-Sf-Substreams-Cache-Tag` header (gated by auth module) to point to `/data/states/v1`, and so on. +* Authentication plugin `trust` can now specify an exclusive list of `allowed` headers (all lowercase), ex: `trust://?allowed=x-sf-user-id,x-sf-api-key-id,x-real-ip,x-sf-substreams-cache-tag` +* The `tier2` app no longer has customizable auth plugin (or any Modules), `trust` will always be used, so that `tier` can pass down its headers (e.g. `X-Sf-Substreams-Cache-Tag`). The `tier2` instances should not be accessible publicly. + +### GUI changes + +* Color theme is now adapted to the terminal background (fixes readability on 'light' background) +* Provided parameters are now shown in the 'Request' tab. + +### CLI changes + +#### Added + +* `alpha init` command: replace `initialBlock` for generated manifest based on contract creation block. +* `alpha init` prompt Ethereum chain. Added: Mainnet, BNB, Polygon, Goerli, Mumbai. + +#### Fixed + +* `alpha init` reports better progress specially when performing ABI & creation block retrieval. +* `alpha init` command without contracts fixed Protogen command invocation. + +## v1.1.8 + +### Backend changes + +#### Added + +* Max-subrequests can now be overridden by auth header `X-Sf-Substreams-Parallel-Jobs` (note: if your auth plugin is 'trust', make sure that you filter out this header from public access +* Request Stats logging. When enable it will log metrics associated to a Tier1 and Tier2 request +* On request, save "substreams.partial.spkg" file to the state cache for debugging purposes. +* Manifest reader can now read 'partial' spkg files (without protobuf and metadata) with an option. + +#### Fixed + +* Fixed a bug which caused "live" blocks to be sent while the stream previously received block(s) were historic. + +### CLI changes + +#### Fixed + +* In GUI, module output now shows fields with default values, i.e. `0`, `""`, `false` + +## v1.1.7 (https://github.com/streamingfast/substreams/releases/tag/v1.1.7) + +### Highlights + +Now using `plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1` when generating the Protobuf Rust `mod.rs` which fixes the warning that remote plugins are deprecated. + +Previously we were using `remote: buf.build/prost/plugins/crate:v0.3.1-1`. But remote plugins when using https://buf.build (which we use to generate the Protobuf) are now deprecated and will cease to function on July 10th, 2023. + +The net effect of this is that if you don't update your Substreams CLI to `1.1.7`, on July 10th 2023 and after, the `substreams protogen` will not work anymore. + +## v1.1.6 (https://github.com/streamingfast/substreams/releases/tag/v1.1.6) + +### Backend changes + +* `substreams-tier1` and `substreams-tier2` are now standalone **Apps**, to be used as such by server implementations (_firehose-ethereum_, etc.) +* `substreams-tier1` now listens to [Connect](https://buf.build/blog/connect-a-better-grpc) protocol, enabling browser-based substreams clients +* **Authentication** has been overhauled to take advantage of https://github.com/streamingfast/dauth, allowing the use of a GRPC-based sidecar or reverse-proxy to provide authentication. +* **Metering** has been overhauled to take advantage of https://github.com/streamingfast/dmetering plugins, allowing the use of a GRPC sidecar or logs to expose usage metrics. +* The **tier2 logs** no longer show a `parent_trace_id`: the `trace_id` is now the same as tier1 jobs. Unique tier2 jobs can be distinguished by their `stage` and `segment`, corresponding to the `output_module_name` and `startblock:stopblock` + +### CLI changes + +* The `substreams protogen` command now uses this Buf plugin https://buf.build/community/neoeinstein-prost to generate the Rust code for your Substreams definitions. +* The `substreams protogen` command no longer generate the `FILE_DESCRIPTOR_SET` constant which generates an unsued warning in Rust. We don't think nobody relied on having the `FILE_DESCRIPTOR_SET` constant generated, but if it's the case, you can provide your own `buf.gen.yaml` that will be used instead of the generated one when doing `substreams protogen`. +* Added `-H` flag on the `substreams run` command, to set HTTP Headers in the Substreams request. + +### Fixed + +* Fixed generated `buf.gen.yaml` not being deleted when an error occurs while generating the Rust code. + +## [v1.1.5](https://github.com/streamingfast/substreams/releases/tag/v1.1.5) + +### Highlights + +This release fixes data determinism issues. This comes at a 20% performance cost but is necessary for integration with The Graph ecosystem. + +#### Operators + +* When upgrading a substreams server to this version, you should delete all existing module caches to benefit from deterministic output + +### Added + +* Tier1 now records deterministic failures in wasm, "blacklists" identical requests for 10 minutes (by serving them the same InvalidArgument error) with a forced incremental backoff. This prevents accidental bad actors from hogging tier2 resources when their substreams cannot go passed a certain block. +* Tier1 now sends the ResolvedStartBlock, LinearHandoffBlock and MaxJobWorkers in SessionInit message for the client and gui to show +* Substreams CLI can now read manifests/spkg directly from an IPFS address (subgraph deployment or the spkg itself), using `ipfs://Qm...` notation + +### Fixed + +* When talking to an updated server, the gui will not overflow on a negative start block, using the newly available resolvedStartBlock instead. +* When running in development mode with a start-block in the future on a cold cache, you would sometimes get invalid "updates" from the store passed down to your modules that depend on them. It did not impact the caches but caused invalid output. +* The WASM engine was incorrectly reusing memory, preventing deterministic output. It made things go faster, but at the cost of determinism. Memory is now reset between WASM executions on each block. +* The GUI no longer panics when an invalid output-module is given as argument + +### Changed + +* Changed default WASM engine from `wasmtime` to `wazero`, use `SUBSTREAMS_WASM_RUNTIME=wasmtime` to revert to prior engine. Note that `wasmtime` will now run a lot slower than before because resetting the memory in `wasmtime` is more expensive than in `wazero`. +* Execution of modules is now done in parallel within a single instance, based on a tree of module dependencies. +* The `substreams gui` and `substreams run` now accept commas inside a `param` value. For example: `substreams run --param=p1=bar,baz,qux --param=p2=foo,baz`. However, you can no longer pass multiple parameters using an ENV variable, or a `.yaml` config file. + +## [v1.1.4](https://github.com/streamingfast/substreams/releases/tag/v1.1.4) + +### HIGHLIGHTS + +* Module hashing changed to fix cache reuse on substreams use imported modules +* Memory leak fixed on rpc-enabled servers +* GUI more responsive + +### Fixed + +* BREAKING: The module hashing algorithm wrongfully changed the hash for imported modules, which made it impossible to leverage caches when composing new substreams off of imported ones. + * Operationally, if you want to keep your caches, you will need to copy or move the old hashes to the new ones. + * You can obtain the prior hashes for a given spkg with: `substreams info my.spkg`, using a prior release of the `substreams` + * With a more recent `substreams` release, you can obtain the new hashes with the same command. + * You can then `cp` or `mv` the caches for each module hash. + * You can also ignore this change. This will simply invalidate your cache. +* Fixed a memory leak where "PostJobHooks" were not always called. These are used to hook in rpc calls in Ethereum chain. They are now always called, even if no block has been processed (can be called with `nil` value for the clock) +* Jobs that fail deterministically (during WASM execution) on tier2 will fail faster, without retries from tier1. +* `substreams gui` command now handles params flag (it was ignored) +* Substeams GUI responsiveness improved significantly when handling large payloads + +### Added + +* Added Tracing capabilities, using https://github.com/streamingfast/sf-tracing . See repository for details on how to enable. + +### Known issues + +* If the cached substreams states are missing a 'full-kv' file in its sequence (not a normal scenario), requests will fail with `opening file: not found` https://github.com/streamingfast/substreams/issues/222 + +## [v1.1.3](https://github.com/streamingfast/substreams/releases/tag/v1.1.3) + +### Highlights + +This release contains fixes for race conditions that happen when multiple request tries to sync the same range using the same `.spkg`. Those fixes will avoid weird state error at the cost of duplicating work in some circumstances. A future refactor of the Substreams engine scheduler will come later to fix those inefficiencies. + +Operators, please read the operators section for upgrade instructions. + +#### Operators + +> **Note** This upgrade procedure is applies if your Substreams deployment topology includes both `tier1` and `tier2` processes. If you have defined somewhere the config value `substreams-tier2: true`, then this applies to you, otherwise, if you can ignore the upgrade procedure. + +This release includes a small change in the internal RPC layer between `tier1` processes and `tier2` processes. This change requires an ordered upgrade of the processes to avoid errors. + +The components should be deployed in this order: + +1. Deploy and roll out `tier1` processes first +2. Deploy and roll out `tier2` processes in second + +If you upgrade in the wrong order or if somehow `tier2` processes start using the new protocol without `tier1` being aware, user will end up with backend error(s) saying that some partial file are not found. Those will be resolved only when `tier1` processes have been upgraded successfully. + +### Fixed + +* Fixed a race when multiple Substreams request execute on the same `.spkg`, it was causing races between the two executors. +* GUI: fixed an issue which would slow down message consumption when progress page was shown in ascii art "bars" mode +* GUI: fixed the display of blocks per second to represent actual blocks, not messages count + +### Changed + +* \[`binary`]: Commands `substreams <...>` that fails now correctly return an exit code 1. +* \[`library`]: The `manifest.NewReader` signature changed and will now return a `*Reader, error` (previously `*Reader`). + +### Added + +* \[`library`]: The `manifest.Reader` gained the ability to infer the path if provided with input `""` based on the current working directory. +* \[`library`]: The `manifest.Reader` gained the ability to infer the path if provided with input that is a directory. + +## [v1.1.2](https://github.com/streamingfast/substreams/releases/tag/v1.1.2) + +### Highlights + +This release contains bug fixes and speed/scaling improvements around the Substreams engine. It also contains few small enhancements for `substreams gui`. + +This release contains an important bug that could have generated corrupted `store` state files. This is important for developers and operators. + +#### Sinkers & Developers + +The `store` state files will be fully deleted on the Substreams server to start fresh again. The impact for you as a developer is that Substreams that were fully synced will now need to re-generate from initial block the store's state. So you might see long delays before getting a new block data while the Substreams engine is re-computing the `store` states from scratch. + +### Operators + +You need to clear the state store and remove all the files that are stored under `substreams-state-store-url` flag. You can also make it point to a brand new folder and delete the old one after the rollout. + +### Fixed + +* Fix a bug where not all extra modules would be sent back on debug mode +* Fixed a bug in tier1 that could result in corrupted state files when getting close to chain HEAD +* Fixed some performance and stalling issues when using GCS for blocks +* Fixed storage logs not being shown properly +* GUI: Fixed panic race condition +* GUI: Cosmetic changes + +### Added + +* GUI: Added traceID + +## [v1.1.1](https://github.com/streamingfast/substreams/releases/tag/v1.1.1) + +### Highlights + +This release introduces a new RPC protocol and the old one has been removed. The new RPC protocol is in a new Protobuf package `sf.substreams.rpc.v2` and it drastically changes how chain re-orgs are signaled to the user. Here the highlights of this release: + +* Getting rid of `undo` payload during re-org +* `substreams gui` Improvements +* Substreams integration testing +* Substreams Protobuf definitions updated + +#### Getting rid of `undo` payload during re-org + +Previously, the GRPC endpoint `sf.substreams.v1.Stream/Blocks` would send a payload with the corresponding "step", NEW or UNDO. + +Unfortunately, this led to some cases where the payload could not be deterministically generated for old blocks that had been forked out, resulting in a stalling request, a failure, or in some worst cases, incomplete data. + +The new design, under `sf.substreams.rpc.v2.Stream/Blocks`, takes care of these situations by removing the 'step' component and using these two messages types: + +* `sf.substreams.rpc.v2.BlockScopedData` when chain progresses, with the payload +* `sf.substreams.rpc.v2.BlockUndoSignal` during a reorg, with the last valid block number + block hash + +The client now has the burden of keeping the necessary means of performing the undo actions (ex: a map of previous values for each block). The BlockScopedData message now includes the `final_block_height` to let you know when this "undo data" can be discarded. + +With these changes, a substreams server can even handle a cursor for a block that it has never seen, provided that it is a valid cursor, by signaling the client to revert up to the last known final block, trading efficiency for resilience in these extreme cases. + +### `substreams gui` Improvements + +* Added key 'f' shortcut for changing display encoding of bytes value (hex, pruned string, base64) +* Added `jq` search mode (hit `/` twice). Filters the output with the `jq` expression, and applies the search to match all blocks. +* Added search history (with `up`/`down`), similar to `less`. +* Running a search now applies it to all blocks, and highlights the matching ones in the blocks bar (in red). +* Added `O` and `P`, to jump to prev/next block with matching search results. +* Added module search with `m`, to quickly switch from module to module. + +#### Substreams integration testing + +Added a basic Substreams testing framework that validates module outputs against expected values. The testing framework currently runs on `substreams run` command, where you can specify the following flags: + +* `test-file` Points to a file that contains your test specs +* `test-verbose` Enables verbose mode while testing. + +The test file, specifies the expected output for a given substreams module at a given block. + +#### Substreams Protobuf definitions updated + +We changed the Substreams Protobuf definitions making a major overhaul of the RPC communication. This is a **breaking change** for those consuming Substreams through gRPC. + +> **Note** The is no breaking changes for Substreams developers regarding your Rust code, Substreams manifest and Substreams package. + +* Removed the `Request` and `Response` messages (and related) from `sf.substreams.v1`, they have been moved to `sf.substreams.rpc.v2`. You will need to update your usage if you were consuming Substreams through gRPC. +* The new `Request` excludes fields and usages that were already deprecated, like using multiple `module_outputs`. +* The `Response` now contains a single module output +* In `development` mode, the additional modules output can be inspected under `debug_map_outputs` and `debug_store_outputs`. + +**Separating Tier1 vs Tier2 gRPC protocol (for Substreams server operators)** + +Now that the `Blocks` request has been moved from `sf.substreams.v1` to `sf.substreams.rpc.v2`, the communication between a substreams instance acting as tier1 and a tier2 instance that performs the background processing has also been reworked, and put under `sf.substreams.internal.v2.Stream/ProcessRange`. It has also been stripped of parameters that were not used for that level of communication (ex: `cursor`, `logs`...) + +### Fixed + +* The `final_blocks_only: true` on the `Request` was not honored on the server. It now correctly sends only blocks that are final/irreversible (according to Firehose rules). +* Prevent substreams panic when requested module has unknown value for "type" + +### Added + +* The `substreams run` command now has flag `--final-blocks-only` + +## [1.0.3](https://github.com/streamingfast/substreams/releases/tag/v1.0.3) + +This should be the last release before a breaking change in the API and handling of the reorgs and UNDO messages. + +### Highlights + +* Added support for resolving a negative start-block on server +* CHANGED: The `run` command now resolves a start-block=-1 from the head of the chain (as supported by the servers now). Prior to this change, the `-1` value meant the 'initialBlock' of the requested module. The empty string is now used for this purpose, +* GUI: Added support for search, similar to `less`, with `/`. +* GUI: Search and output offset is conserved when switching module/block number in the "Output" tab. +* Library: protobuf message descriptors now exposed in the `manifest/` package. This is something useful to any sink that would need to interpret the protobuf messages inside a Package. +* Added support for resolving a negative start-block on server (also added to run command) +* The `run` and `gui` command no longer resolve a `start-block=-1` to the 'initialBlock' of the requested module. To get this behavior, simply assign an empty string value to the flag `start-block` instead. +* Added support for search within the Substreams gui `output` view. Usage of search within `output` behaves similar to the `less` command, and can be toggled with "/". + +## [1.0.2](https://github.com/streamingfast/substreams/releases/tag/v1.0.2) + +* Release was retracted because it contained the refactoring expected for 1.1.0 by mistake, check https://github.com/streamingfast/substreams/releases/tag/v1.0.3 instead. + +## [1.0.1](https://github.com/streamingfast/substreams/releases/tag/v1.0.1) + +### Fixed + +* Fixed "undo" messages incorrectly contained too many module outputs (all modules, with some duplicates). +* Fixed status bar message cutoff bug +* Fixed `substreams run` when `manifest` contains unknown attributes +* Fixed bubble tea program error when existing the `run` command + +## [1.0.0](https://github.com/streamingfast/substreams/releases/tag/v1.0.0) + +### Highlights + +* Added command `substreams gui`, providing a terminal-based GUI to inspect the streamed data. Also adds `--replay` support, to save a stream to `replay.log` and load it back in the UI later. You can use it as you would `substreams run`. Feedback welcome. +* Modified command `substreams protogen`, defaulting to generating the `mod.rs` file alongside the rust bindings. Also added `--generate-mod-rs` flag to toggle `mod.rs` generation. +* Added support for module parameterization. Defined in the manifest as: + +``` +module: + name: my_module + inputs: + params: string + ... + +params: + my_module: "0x123123" + "imported:module": override value from imported module +``` + +and on the command-line as: + +* `substreams run -p module=value -p "module2=other value" ...` + +Servers need to be updated for packages to be able to be consumed this way. + +This change keeps backwards compatibility. Old Substreams Packages will still work the same, with no changes to module hashes. + +### Added + +* Added support for `{version}` template in `--output-file` flag value on `substreams pack`. +* Added fuel limit to wasm execution as a server-side option, preventing wasm process from running forever. +* Added 'Network' and 'Sink{Type, Module, Config}' fields in the manifest and protobuf definition for future bundling of substreams sink definitions within a substreams package. + +## [0.2.0](https://github.com/streamingfast/substreams/releases/tag/v0.2.0) + +### Highlights + +* Improved execution speed and module loading speed by bumping to WASM Time to version 4.0. +* Improved developer experience on the CLI by making the `` argument optional. + + The CLI when `` argument is not provided will now look in the current directory for a `substreams.yaml` file and is going to use it if present. So if you are in your Substreams project and your file is named `substreams.yaml`, you can simply do `substreams pack`, `substreams protogen`, etc. + + Moreover, we added to possibility to pass a directory containing a `substreams.yaml` directly so `substreams pack path/to/project` would work as long as `path/to/project` contains a file named `substreams.yaml`. +* Fixed a bug that was preventing production mode to complete properly when using a bounded block range. +* Improved overall stability of the Substreams engine. + +#### Operators Notes + +* **Breaking** Config values `substreams-stores-save-interval` and `substreams-output-cache-save-interval` have been merged together into `substreams-cache-save-interval` in the `firehose-` repositories. Refer to chain specific `firehose-` repository for further details. + +### Added + +* The `` can point to a directory that contains a `substreams.yaml` file instead of having to point to the file directly. +* The `` parameter is now optional in all commands requiring it. + +### Fixed + +* Fixed valuetype mismatch for stores +* Fixed production mode not completing when block range was specified +* Fixed tier1 crashing due to missing context canceled check. +* Fixed some code paths where locking could have happened due to incorrect checking of context cancellation. +* Request validation for blockchain's input type is now made only against the requested module it's transitive dependencies. + +### Updated + +* Updated WASM Time library to 4.0.0 leading to improved execution speed. + +### Changed + +* Remove distinction between `output-save-interval` and `store-save-interval`. +* `substreams init` has been moved under `substreams alpha init` as this is a feature included by mistake in latest release that should not have been displayed in the main list of commands. +* `substreams codegen` has been moved under `substreams alpha codegen` as this is a feature included by mistake in latest release that should not have been displayed in the main list of commands. + +## [0.1.0](https://github.com/streamingfast/substreams/releases/tag/v0.1.0) + +This upcoming release is going to bring significant changes on how Substreams are developed, consumed and speed of execution. Note that there is **no** breaking changes related to your Substreams' Rust code, only breaking changes will be about how Substreams are run and available features/flags. + +Here the highlights of elements that will change in next release: + +* [Production vs Development Mode](change-log.md#production-vs-development-mode) +* [Single Output Module](change-log.md#single-module-output) +* [Output Module must be of type `map`](change-log.md#output-module-must-be-of-type-map) +* [`InitialSnapshots` is now a `development` mode feature only](change-log.md#initialsnapshots-is-now-a-development-mode-feature-only) +* [Enhanced Parallel Execution](change-log.md#enhanced-parallel-execution) + +In this rest of this post, we are going to go through each of them in greater details and the implications they have for you. Full changelog is available after. + +> **Warning** Operators, refer to [Operators Notes](change-log.md#operators-notes) section for specific instructions of deploying this new version. + +### Production vs development mode + +We introduce an execution mode when running Substreams, either `production` mode or `development` mode. The execution mode impacts how the Substreams get executed, specifically: + +* The time to first byte +* The module logs and outputs sent back to the client +* How parallel execution is applied through the requested range + +The difference between the modes are: + +* In `development` mode, the client will receive all the logs of the executed `modules`. In `production` mode, logs are not available at all. +* In `development` mode, module's are always re-executed from request's start block meaning now that logs will always be visible to the user. In `production` mode, if a module's output is found in cache, module execution is skipped completely and data is returned directly. +* In `development` mode, only backward parallel execution can be effective. In `production` mode, both backward parallel execution and forward parallel execution can be effective. See [Enhanced parallel execution](change-log.md#enhanced-parallel-execution) section for further details about parallel execution. +* In `development` mode, every module's output is returned back in the response but only root module is displayed by default in `substreams` CLI (configurable via a flag). In `production` mode, only root module's output is returned. +* In `development` mode, you may request specific `store` snapshot that are in the execution tree via the `substreams` CLI `--debug-modules-initial-snapshots` flag. In `production` mode, this feature is not available. + +The execution mode is specified at that gRPC request level and is the default mode is `development`. The `substreams` CLI tool being a development tool foremost, we do not expect people to activate production mode (`-p`) when using it outside for maybe testing purposes. + +If today's you have `sink` code making the gRPC request yourself and are using that for production consumption, ensure that field `production_mode` in your Substreams request is set to `true`. StreamingFast provided `sink` like [substreams-sink-postgres](https://github.com/streamingfast/substreams-sink-postgres), [substreams-sink-files](https://github.com/streamingfast/substreams-sink-files) and others have already been updated to use `production_mode` by default. + +Final note, we recommend to run the production mode against a compiled `.spkg` file that should ideally be released and versioned. This is to ensure stable modules' hashes and leverage cached output properly. + +### Single module output + +We now only support 1 output module when running a Substreams, while prior this release, it was possible to have multiple ones. + +* Only a single module can now be requested, previous version allowed to request N modules. +* Only `map` module can now be requested, previous version allowed `map` and `store` to be requested. +* `InitialSnapshots` is now forbidden in `production` mode and still allowed in `development` mode. +* In `development` mode, the server sends back output for all executed modules (by default the CLI displays only requested module's output). + +> **Note** We added `output_module` to the Substreams request and kept `output_modules` to remain backwards compatible for a while. If an `output_module` is specified we will honor that module. If not we will check `output_modules` to ensure there is only 1 output module. In a future release, we are going to remove `output_modules` altogether. + +With the introduction of `development` vs `production` mode, we added a change in behavior to reduce frictions this changes has on debugging. Indeed, in `development` mode, all executed modules's output will be sent be to the user. This includes the requested output module as well as all its dependencies. The `substreams` CLI has been adjusted to show only the output of the requested output module by default. The new `substreams` CLI flag `-debug-modules-output` can be used to control which modules' output is actually displayed by the CLI. + +> **Migration Path** If you are currently requesting more than one module, refactor your Substreams code so that a single `map` module aggregates all the required information from your different dependencies in one output. + +### Output module must be of type `map` + +It is now forbidden to request a `store` module as the output module of the Substreams request, the requested output module must now be of kind `map`. Different factors have motivated this change: + +* Recently we have seen incorrect usage of `store` module. A `store` module was not intended to be used as a persistent long term storage, `store` modules were conceived as a place to aggregate data for later steps in computation. Using it as a persistent storage make the store unmanageable. +* We had always expected users to consume a `map` module which would return data formatted according to a final `sink` spec which will then permanently store the extracted data. We never envisioned `store` to act as long term storage. +* Forward parallel execution does not support a `store` as its last step. + +> **Migration Path** If you are currently using a `store` module as your output store. You will need to create a `map` module that will have as input the `deltas` of said `store` module, and return the deltas. + +#### Examples + +Let's assume a Substreams with these dependencies: `[block] --> [map_pools] --> [store_pools] --> [map_transfers]` + +* Running `substreams run substreams.yaml map_transfers` will only print the outputs and logs from the `map_transfers` module. +* Running `substreams run substreams.yaml map_transfers --debug-modules-output=map_pools,map_transfers,store_pools` will print the outputs of those 3 modules. + +### `InitialSnapshots` is now a `development` mode feature only + +Now that a `store` cannot be requested as the output module, the `InitialSnapshots` did not make sense anymore to be available. Moreover, we have seen people using it to retrieve the initial state and then continue syncing. While it's a fair use case, we always wanted people to perform the synchronization using the streaming primitive and not by using `store` as long term storage. + +However, the `InitialSnapshots` is a useful tool for debugging what a store contains at a given block. So we decided to keep it in `development` mode only where you can request the snapshot of a `store` module when doing your request. In the Substreams' request/response, `initial_store_snapshot_for_modules` has been renamed to `debug_initial_store_snapshot_for_modules`, `snapshot_data` to `debug_snapshot_data` and `snapshot_complete` to `debug_snapshot_complete`. + +> **Migration Path** If you were relying on `InitialSnapshots` feature in production. You will need to create a `map` module that will have as input the `deltas` of said `store` module, and then synchronize the full state on the consuming side. + +#### Examples + +Let's assume a Substreams with these dependencies: `[block] --> [map_pools] --> [store_pools] --> [map_transfers]` + +* Running `substreams run substreams.yaml map_transfers -s 1000 -t +5 --debug-modules-initial-snapshot=store_pools` will print all the entries in store\_pools at block 999, then continue with outputs and logs from `map_transfers` in blocks 1000 to 1004. + +### Enhanced parallel execution + +There are 2 ways parallel execution can happen either backward or forward. + +Backward parallel execution consists of executing in parallel block ranges from the module's start block up to the start block of the request. If the start block of the request matches module's start block, there is no backward parallel execution to perform. Also, this is happening only for dependencies of type `store` which means that if you depends only on other `map` modules, no backward parallel execution happens. + +Forward parallel execution consists of executing in parallel block ranges from the start block of the request up to last known final block (a.k.a the irreversible block) or the stop block of the request, depending on which is smaller. Forward parallel execution significantly improves the performance of the Substreams as we execute your module in advanced through the chain history in parallel. What we stream you back is the cached output of your module's execution which means essentially that we stream back to you data written in flat files. This gives a major performance boost because in almost all cases, the data will be already for you to consume. + +Forward parallel execution happens only in `production` mode is always disabled when in `development` mode. Moreover, since we read back data from cache, it means that logs of your modules will never be accessible as we do not store them. + +Backward parallel execution still occurs in `development` and `production` mode. The diagram below gives details about when parallel execution happen. + +![parallel processing](../assets/substreams\_processing.png) + +You can see that in `production` mode, parallel execution happens before the Substreams request range as well as within the requested range. While in `development` mode, we can see that parallel execution happens only before the Substreams request range, so between module's start block and start block of requested range (backward parallel execution only). + +### Operators Notes + +The state output format for `map` and `store` modules has changed internally to be more compact in Protobuf format. When deploying this new version, previous existing state files should be deleted or deployment updated to point to a new store location. The state output store is defined by the flag `--substreams-state-store-url` flag parameter on chain specific binary (i.e. `fireeth`). + +### Library + +* Added `production_mode` to Substreams Request +* Added `output_module` to Substreams Request + +### CLI + +* Fixed `Ctrl-C` not working directly when in TUI mode. +* Added `Trace ID` printing once available. +* Added command `substreams tools analytics store-stats` to get statistic for a given store. +* Added `--debug-modules-output` (comma-separated module names) (unavailable in `production` mode). +* **Breaking** Renamed flag `--initial-snapshots` to `--debug-modules-initial-snapshots` (comma-separated module names) (unavailable in `production` mode). + +## [0.0.21](https://github.com/streamingfast/substreams/releases/tag/v0.0.21) + +* Moved Rust modules to `github.com/streamingfast/substreams-rs` + +### Library + +* Gained significant execution time improvement when saving and loading stores, during the squashing process by leveraging [vtprotobuf](https://github.com/planetscale/vtprotobuf) +* Added XDS support for tier 2s +* Added intrinsic support for type `bigdecimal`, will deprecate `bigfloat` +* Significant improvements in code-coverage and full integration tests. + +### CLI + +* Added `substreams tools proxy ` subcommand to allow calling substreams with a pre-defined package easily from a web browser using bufbuild/connect-web +* Lowered GRPC client keep alive frequency, to prevent "Too Many Pings" disconnection issue. +* Added a fast failure when attempting to connect to an unreachable substreams endpoint. +* CLI is now able to read `.spkg` from `gs://`, `s3://` and `az://` URLs, the URL format must be supported by our [dstore](https://github.com/streamingfast/dstore) library). +* Command `substreams pack` is now restricted to local manifest file. +* Added command `substreams tools module` to introspect a store state in storage. +* Made changes to allow for `substreams` CLI to run on Windows OS (thanks @robinbernon). +* Added flag `--output-file