From 3cbd625f4a7aff6079832e24f2016af15c8f75b4 Mon Sep 17 00:00:00 2001 From: austbot Date: Thu, 11 Aug 2022 21:51:27 -0500 Subject: [PATCH] init- much cleanup needed --- .dockerignore | 7 + .gitignore | 20 + Api.Dockerfile | 42 + Ingest.Dockerfile | 42 + Load.Dockerfile | 20 + digital_asset_types/Cargo.toml | 28 + digital_asset_types/openrpc.json | 827 +++ digital_asset_types/sqlx-data.json | 3 + digital_asset_types/src/adapter/metadata.rs | 55 + digital_asset_types/src/adapter/mod.rs | 2 + digital_asset_types/src/dao/asset.rs | 151 + .../src/dao/asset_authority.rs | 80 + digital_asset_types/src/dao/asset_creators.rs | 83 + digital_asset_types/src/dao/asset_data.rs | 85 + digital_asset_types/src/dao/asset_grouping.rs | 80 + digital_asset_types/src/dao/backfill_items.rs | 70 + digital_asset_types/src/dao/cl_items.rs | 73 + digital_asset_types/src/dao/mod.rs | 13 + digital_asset_types/src/dao/prelude.rs | 10 + digital_asset_types/src/dao/raw_txn.rs | 61 + .../src/dao/sea_orm_active_enums.rs | 51 + digital_asset_types/src/dapi/asset.rs | 240 + digital_asset_types/src/dapi/change_logs.rs | 114 + digital_asset_types/src/dapi/mod.rs | 2 + digital_asset_types/src/json/chain_data.rs | 12 + digital_asset_types/src/json/mod.rs | 3 + digital_asset_types/src/lib.rs | 10 + digital_asset_types/src/rpc/asset.rs | 247 + digital_asset_types/src/rpc/filter.rs | 33 + digital_asset_types/src/rpc/mod.rs | 8 + digital_asset_types/src/rpc/response.rs | 47 + digital_asset_types/src/rpc/sale.rs | 25 + docker-compose.yaml | 53 + helm/api/.helmignore | 23 + helm/api/Chart.yaml | 24 + helm/api/templates/_helpers.tpl | 67 + helm/api/templates/deployment.yaml | 86 + helm/api/templates/hpa.yaml | 28 + helm/api/templates/ingress.yaml | 61 + helm/api/templates/secret.yaml | 6 + helm/api/templates/service.yaml | 15 + helm/api/templates/serviceaccount.yaml | 12 + helm/api/templates/tests/test-connection.yaml | 15 + helm/api/values.yaml | 89 + helm/ingest/.helmignore | 23 + helm/ingest/Chart.yaml | 24 + helm/ingest/templates/_helpers.tpl | 66 + helm/ingest/templates/deployment-load.yaml | 56 + helm/ingest/templates/deployment.yaml | 88 + helm/ingest/templates/hpa.yaml | 28 + helm/ingest/templates/secret.yaml | 7 + helm/ingest/templates/serviceaccount.yaml | 12 + helm/ingest/values.yaml | 58 + helm/values-testnet.yaml | 0 init.sql | 161 + nft_api/.dockerignore | 1 + nft_api/Cargo.toml | 38 + nft_api/rustfmt.toml | 1 + nft_api/src/error.rs | 10 + nft_api/src/main.rs | 485 ++ nft_ingester/.dockerignore | 1 + nft_ingester/Cargo.lock | 5052 +++++++++++++++++ nft_ingester/Cargo.toml | 46 + nft_ingester/rustfmt.toml | 1 + nft_ingester/src/backfiller.rs | 728 +++ nft_ingester/src/error/mod.rs | 75 + nft_ingester/src/events/mod.rs | 27 + nft_ingester/src/main.rs | 277 + nft_ingester/src/parsers/bubblegum.rs | 648 +++ nft_ingester/src/parsers/gummyroll.rs | 202 + nft_ingester/src/parsers/mod.rs | 74 + nft_ingester/src/tasks/mod.rs | 52 + nft_ingester/src/utils/instructions.rs | 72 + nft_ingester/src/utils/logs.rs | 72 + nft_ingester/src/utils/mod.rs | 45 + nft_ingester/src/utils/storage.rs | 24 + skaffold.yaml | 53 + 77 files changed, 11530 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Api.Dockerfile create mode 100644 Ingest.Dockerfile create mode 100644 Load.Dockerfile create mode 100644 digital_asset_types/Cargo.toml create mode 100644 digital_asset_types/openrpc.json create mode 100644 digital_asset_types/sqlx-data.json create mode 100644 digital_asset_types/src/adapter/metadata.rs create mode 100644 digital_asset_types/src/adapter/mod.rs create mode 100644 digital_asset_types/src/dao/asset.rs create mode 100644 digital_asset_types/src/dao/asset_authority.rs create mode 100644 digital_asset_types/src/dao/asset_creators.rs create mode 100644 digital_asset_types/src/dao/asset_data.rs create mode 100644 digital_asset_types/src/dao/asset_grouping.rs create mode 100644 digital_asset_types/src/dao/backfill_items.rs create mode 100644 digital_asset_types/src/dao/cl_items.rs create mode 100644 digital_asset_types/src/dao/mod.rs create mode 100644 digital_asset_types/src/dao/prelude.rs create mode 100644 digital_asset_types/src/dao/raw_txn.rs create mode 100644 digital_asset_types/src/dao/sea_orm_active_enums.rs create mode 100644 digital_asset_types/src/dapi/asset.rs create mode 100644 digital_asset_types/src/dapi/change_logs.rs create mode 100644 digital_asset_types/src/dapi/mod.rs create mode 100644 digital_asset_types/src/json/chain_data.rs create mode 100644 digital_asset_types/src/json/mod.rs create mode 100644 digital_asset_types/src/lib.rs create mode 100644 digital_asset_types/src/rpc/asset.rs create mode 100644 digital_asset_types/src/rpc/filter.rs create mode 100644 digital_asset_types/src/rpc/mod.rs create mode 100644 digital_asset_types/src/rpc/response.rs create mode 100644 digital_asset_types/src/rpc/sale.rs create mode 100644 docker-compose.yaml create mode 100644 helm/api/.helmignore create mode 100644 helm/api/Chart.yaml create mode 100644 helm/api/templates/_helpers.tpl create mode 100644 helm/api/templates/deployment.yaml create mode 100644 helm/api/templates/hpa.yaml create mode 100644 helm/api/templates/ingress.yaml create mode 100644 helm/api/templates/secret.yaml create mode 100644 helm/api/templates/service.yaml create mode 100644 helm/api/templates/serviceaccount.yaml create mode 100644 helm/api/templates/tests/test-connection.yaml create mode 100644 helm/api/values.yaml create mode 100644 helm/ingest/.helmignore create mode 100644 helm/ingest/Chart.yaml create mode 100644 helm/ingest/templates/_helpers.tpl create mode 100644 helm/ingest/templates/deployment-load.yaml create mode 100644 helm/ingest/templates/deployment.yaml create mode 100644 helm/ingest/templates/hpa.yaml create mode 100644 helm/ingest/templates/secret.yaml create mode 100644 helm/ingest/templates/serviceaccount.yaml create mode 100644 helm/ingest/values.yaml create mode 100644 helm/values-testnet.yaml create mode 100644 init.sql create mode 100644 nft_api/.dockerignore create mode 100644 nft_api/Cargo.toml create mode 100644 nft_api/rustfmt.toml create mode 100644 nft_api/src/error.rs create mode 100644 nft_api/src/main.rs create mode 100644 nft_ingester/.dockerignore create mode 100644 nft_ingester/Cargo.lock create mode 100644 nft_ingester/Cargo.toml create mode 100644 nft_ingester/rustfmt.toml create mode 100644 nft_ingester/src/backfiller.rs create mode 100644 nft_ingester/src/error/mod.rs create mode 100644 nft_ingester/src/events/mod.rs create mode 100644 nft_ingester/src/main.rs create mode 100644 nft_ingester/src/parsers/bubblegum.rs create mode 100644 nft_ingester/src/parsers/gummyroll.rs create mode 100644 nft_ingester/src/parsers/mod.rs create mode 100644 nft_ingester/src/tasks/mod.rs create mode 100644 nft_ingester/src/utils/instructions.rs create mode 100644 nft_ingester/src/utils/logs.rs create mode 100644 nft_ingester/src/utils/mod.rs create mode 100644 nft_ingester/src/utils/storage.rs create mode 100644 skaffold.yaml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..1361c02af --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +**/target +*/target +target +db-data +node-modules +ledger +.anchor \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..1c6a2dc85 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ +db-data +.idea +**/target/ + +# These are backup files generated by rustfmt +**/*.rs.bk +ledger +package-lock.json +node_modules +.anchor +target +*.so +.vscode +dist +test-ledger +*.swp +*error.log diff --git a/Api.Dockerfile b/Api.Dockerfile new file mode 100644 index 000000000..5619ff53d --- /dev/null +++ b/Api.Dockerfile @@ -0,0 +1,42 @@ +FROM rust:1.60-bullseye AS chef +RUN cargo install cargo-chef +FROM chef AS planner +COPY das_api /rust/das_api/ +WORKDIR /rust/das_api +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +RUN apt-get update -y && \ + apt-get install -y build-essential make git +COPY lib /rust/lib +COPY contracts /rust/contracts +COPY plerkle /rust/plerkle +COPY deps /rust/deps +COPY plerkle_serialization /rust/plerkle_serialization +COPY digital_asset_types /rust/digital_asset_types +COPY messenger /rust/messenger +RUN mkdir -p /rust/das_api +WORKDIR /rust/das_api +COPY --from=planner /rust/das_api/recipe.json recipe.json +# Build dependencies - this is the caching Docker layer! +COPY das_api/Cargo.toml . +RUN cargo chef cook --release --recipe-path recipe.json +COPY das_api . +# Build application +RUN cargo build --release + +FROM rust:1.61-slim-bullseye +ARG APP=/usr/src/app +RUN apt update \ + && apt install -y curl ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* +ENV TZ=Etc/UTC \ + APP_USER=appuser +RUN groupadd $APP_USER \ + && useradd -g $APP_USER $APP_USER \ + && mkdir -p ${APP} +COPY --from=builder /rust/das_api/target/release/das_api ${APP} +RUN chown -R $APP_USER:$APP_USER ${APP} +USER $APP_USER +WORKDIR ${APP} +CMD /usr/src/app/das_api \ No newline at end of file diff --git a/Ingest.Dockerfile b/Ingest.Dockerfile new file mode 100644 index 000000000..4993a52e8 --- /dev/null +++ b/Ingest.Dockerfile @@ -0,0 +1,42 @@ +FROM rust:1.60-bullseye AS chef +RUN cargo install cargo-chef +FROM chef AS planner +COPY nft_ingester /rust/nft_ingester/ +WORKDIR /rust/nft_ingester +RUN cargo chef prepare --recipe-path recipe.json + +FROM chef AS builder +RUN apt-get update -y && \ + apt-get install -y build-essential make git +COPY lib /rust/lib +COPY contracts /rust/contracts +COPY plerkle /rust/plerkle +COPY deps /rust/deps +COPY plerkle_serialization /rust/plerkle_serialization +COPY digital_asset_types /rust/digital_asset_types +COPY messenger /rust/messenger +RUN mkdir -p /rust/nft_ingester +WORKDIR /rust/nft_ingester +COPY --from=planner /rust/nft_ingester/recipe.json recipe.json +# Build dependencies - this is the caching Docker layer! +COPY nft_ingester/Cargo.toml . +RUN cargo chef cook --release --recipe-path recipe.json +COPY nft_ingester . +# Build application +RUN cargo build --release + +FROM rust:1.60-slim-bullseye +ARG APP=/usr/src/app +RUN apt update \ + && apt install -y curl ca-certificates tzdata \ + && rm -rf /var/lib/apt/lists/* +ENV TZ=Etc/UTC \ + APP_USER=appuser +RUN groupadd $APP_USER \ + && useradd -g $APP_USER $APP_USER \ + && mkdir -p ${APP} +COPY --from=builder /rust/nft_ingester/target/release/nft_ingester ${APP} +RUN chown -R $APP_USER:$APP_USER ${APP} +USER $APP_USER +WORKDIR ${APP} +CMD /usr/src/app/nft_ingester \ No newline at end of file diff --git a/Load.Dockerfile b/Load.Dockerfile new file mode 100644 index 000000000..36853911a --- /dev/null +++ b/Load.Dockerfile @@ -0,0 +1,20 @@ +FROM node:18 +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +RUN sh -c "$(curl -sSfL https://release.solana.com/v1.10.31/install)" +ENV PATH="/root/.cargo/bin:/root/.local/share/solana/install/active_release/bin:${PATH}" +RUN cargo install anchor-cli + +WORKDIR /rust/ +COPY deps /rust/deps +COPY lib /rust/lib +COPY plerkle_serialization /rust/plerkle_serialization +COPY digital_asset_types /rust/digital_asset_types +COPY messenger /rust/messenger +COPY contracts /rust/contracts +WORKDIR /rust/contracts +COPY ./contracts/package.json . +RUN yarn + +COPY ./contracts /rust/ +RUN anchor build +CMD yarn run ts-node tests/bubblegum-test-rpc-fast.ts \ No newline at end of file diff --git a/digital_asset_types/Cargo.toml b/digital_asset_types/Cargo.toml new file mode 100644 index 000000000..ab989d634 --- /dev/null +++ b/digital_asset_types/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "digital_asset_types" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default=["json_types", "sql_types"] +json_types = ["serde", "serde_json"] +sql_types = ["sea-orm"] + +[dependencies] +sea-orm = { optional = true, git = "https://github.com/liberwang1013/sea-orm", branch = "insert-on-conflict", features = [ "macros", "runtime-tokio-rustls", "sqlx-postgres", "with-chrono"] } +sea-query = {version="0.25.0", features = ["postgres-array"]} +serde = { version = "1.0.137", optional = true } +serde_json = { version = "1.0.81", optional = true } +bs58 = "0.4.0" +borsh = { version = "0.9.3", optional = true } +borsh-derive = { version = "0.9.3", optional = true } +solana-sdk = { version = "=1.10.10" } +num-traits = "0.2.15" +num-derive = "0.3.3" +thiserror = "1.0.31" +concurrent-merkle-tree = {path = "../lib/concurrent-merkle-tree" } +mpl-token-metadata = { git = "https://github.com/jarry-xiao/metaplex-program-library", rev = "7e2810a", features = ["no-entrypoint"] } +jsonpath_lib = "0.3.0" +mime_guess = "2.0.4" +url = "2.2.2" \ No newline at end of file diff --git a/digital_asset_types/openrpc.json b/digital_asset_types/openrpc.json new file mode 100644 index 000000000..8b1055d4d --- /dev/null +++ b/digital_asset_types/openrpc.json @@ -0,0 +1,827 @@ +{ + "openrpc": "1.0.0-rc1", + "info": { + "version": "0.0.1", + "title": "Digital Asset API" + }, + "servers": [ + { + "url": "https://rpc.metaplex.com/this_is_fake" + } + ], + "methods": [ + { + "name": "get_asset_proof", + "summary": "Get merkle proof for asset", + "tags": [ + { + "name": "proof" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/asset_id" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/assetProof" + }, + "errors": [ + { + "code": 500, + "message": "server error" + }, + { + "code": 400, + "message": "invalid request $msg" + }, + { + "code": 429, + "message": "slow down sheesh" + } + ] + }, + { + "name": "get_assets_by_owner", + "summary": "List all assets by owner pubkey", + "tags": [ + { + "name": "index" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/owner_param" + }, + { + "$ref": "#/components/contentDescriptors/asset_sort_by" + }, + { + "$ref": "#/components/contentDescriptors/limit" + }, + { + "$ref": "#/components/contentDescriptors/page" + }, + { + "$ref": "#/components/contentDescriptors/before" + }, + { + "$ref": "#/components/contentDescriptors/after" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/assetList" + }, + "errors": [ + { + "code": 500, + "message": "server error" + }, + { + "code": 400, + "message": "invalid request $msg" + }, + { + "code": 429, + "message": "slow down sheesh" + } + ] + }, + { + "name": "get_listed_assets_by_owner", + "summary": "Gets a List of assets ids that are for sale and where they are on sale", + "tags": [ + { + "name": "sales" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/owner_param" + }, + { + "$ref": "#/components/contentDescriptors/asset_sort_by" + }, + { + "$ref": "#/components/contentDescriptors/limit" + }, + { + "$ref": "#/components/contentDescriptors/page" + }, + { + "$ref": "#/components/contentDescriptors/before" + }, + { + "$ref": "#/components/contentDescriptors/after" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/assetIdSaleList" + }, + "errors": [ + { + "code": 500, + "message": "server error" + }, + { + "code": 400, + "message": "invalid request $msg" + }, + { + "code": 429, + "message": "slow down sheesh" + } + ] + }, + { + "name": "get_offers_by_owner", + "summary": "Gets a List of assets ids that are for sale and where they are on sale", + "tags": [ + { + "name": "sales" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/owner_param" + }, + { + "$ref": "#/components/contentDescriptors/offer_sort_by" + }, + { + "$ref": "#/components/contentDescriptors/limit" + }, + { + "$ref": "#/components/contentDescriptors/page" + }, + { + "$ref": "#/components/contentDescriptors/before" + }, + { + "$ref": "#/components/contentDescriptors/after" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/offerList" + }, + "errors": [ + { + "code": 500, + "message": "server error" + }, + { + "code": 400, + "message": "invalid request $msg" + }, + { + "code": 429, + "message": "slow down sheesh" + } + ] + }, + { + "name": "get_assets_by_group", + "summary": "Gets a List of assets based on a group filter expression, see grouping module", + "tags": [ + { + "name": "search" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/group_expression" + }, + { + "$ref": "#/components/contentDescriptors/asset_sort_by" + }, + { + "$ref": "#/components/contentDescriptors/limit" + }, + { + "$ref": "#/components/contentDescriptors/page" + }, + { + "$ref": "#/components/contentDescriptors/before" + }, + { + "$ref": "#/components/contentDescriptors/after" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/assetList" + } + }, + { + "name": "get_assets_by_creator", + "summary": "Gets a List of assets based on a creator filter expression", + "tags": [ + { + "name": "search" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/creator_expression" + }, + { + "$ref": "#/components/contentDescriptors/asset_sort_by" + }, + { + "$ref": "#/components/contentDescriptors/limit" + }, + { + "$ref": "#/components/contentDescriptors/page" + }, + { + "$ref": "#/components/contentDescriptors/before" + }, + { + "$ref": "#/components/contentDescriptors/after" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/assetList" + }, + "errors": [ + { + "code": 500, + "message": "server error" + }, + { + "code": 400, + "message": "invalid request $msg" + }, + { + "code": 429, + "message": "slow down sheesh" + } + ] + }, + { + "name": "search_assets", + "summary": "Search Api for Assets", + "tags": [ + { + "name": "search" + } + ], + "params": [ + { + "$ref": "#/components/contentDescriptors/search_expression" + }, + { + "$ref": "#/components/contentDescriptors/asset_sort_by" + }, + { + "$ref": "#/components/contentDescriptors/limit" + }, + { + "$ref": "#/components/contentDescriptors/page" + }, + { + "$ref": "#/components/contentDescriptors/before" + }, + { + "$ref": "#/components/contentDescriptors/after" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/assetList" + }, + "errors": [ + { + "code": 500, + "message": "server error" + }, + { + "code": 400, + "message": "invalid request $msg" + }, + { + "code": 429, + "message": "slow down sheesh" + } + ] + } + ], + "components": { + "contentDescriptors": { + "search_expression": { + "name": "search_expression", + "description": "M$Q search expression", + "schema": { + "$ref": "#/components/schemas/AssetList" + }, + "summary": "Example `files.length() == 2 AND name LIKE \"%only hands%\"`" + }, + "assetList": { + "name": "assets", + "description": "A paged an array of assets", + "schema": { + "$ref": "#/components/schemas/AssetList" + } + }, + "offerList": { + "name": "offer_list", + "description": "A paged an array of offers on assets", + "schema": { + "$ref": "#/components/schemas/OfferList" + } + }, + "assetProof": { + "name": "asset_proof", + "description": "Array of hashes corresponding to a node_index", + "schema": { + "$ref": "#/components/schemas/AssetProof" + } + }, + "assetIdSaleList": { + "name": "asset_id_sale_list", + "description": "A paged an array of listings", + "schema": { + "title": "AssetListings", + "type": "object", + "required": [ + "items", + "total", + "limit" + ], + "properties": { + "total": { + "type": "number" + }, + "limit": { + "type": "number" + }, + "page": { + "type": "number" + }, + "before": { + "type": "string" + }, + "after": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssetSaleList" + } + } + } + } + }, + "group_expression": { + "name": "group_expression", + "description": "The {group key}:{group value} expression key must be a supported indexable group", + "required": true, + "schema": { + "title": "SearchExpression", + "type": "string" + }, + "summary": "Examples: `collection:ueyrotot89toiueltoerhotuiy`, `trait.fire:true`" + }, + "creator_expression": { + "name": "creator_expression", + "description": "The list of creators to filter by", + "required": true, + "schema": { + "title": "SearchExpression", + "type": "array", + "items": { + "type": "string" + } + }, + "summary": "Examples: [creator1, creator2]" + }, + "after": { + "name": "after", + "description": "Cursor based pagination: After Asset ID", + "required": false, + "schema": { + "title": "After", + "type": "string" + } + }, + "before": { + "name": "before", + "description": "Cursor based pagination: Before Asset ID", + "required": false, + "schema": { + "title": "Before", + "type": "string" + } + }, + "page": { + "name": "page", + "description": "Page based pagination", + "required": false, + "schema": { + "title": "Page", + "type": "integer", + "minimum": 0 + } + }, + "asset_id": { + "name": "asset_id", + "description": "ID of the Asset", + "required": true, + "schema": { + "title": "AssetId", + "type": "string" + } + }, + "limit": { + "name": "limit", + "description": "How many items to return at one time (max 1000)", + "required": false, + "schema": { + "title": "Limit", + "type": "integer", + "minimum": 1 + } + }, + "owner_param": { + "name": "owner_address", + "description": "Owner Public Key", + "required": true, + "schema": { + "title": "OwnerAddress", + "type": "string" + } + }, + "asset_sort_by": { + "name": "sort_by", + "description": "How many items to return at one time (max 1000)", + "required": false, + "schema": { + "title": "AssetSorting", + "type": "string", + "enum": [ + "created", + "updated", + "recent_action" + ] + } + }, + "offer_sort_by": { + "name": "sort_by", + "description": "How many items to return at one time (max 1000)", + "required": false, + "schema": { + "title": "OfferSorting", + "type": "string", + "enum": [ + "created", + "updated", + "price" + ] + } + } + }, + "schemas": { + "OfferList": { + "title": "OfferList", + "type": "array", + "items": { + "title": "Offer", + "type": "object", + "properties": { + "offer_id": { + "title": "OfferId", + "type": "string" + }, + "asset_id": { + "title": "AssetId", + "type": "string" + }, + "price": { + "title": "Price", + "type": "number" + }, + "amount": { + "title": "Amount", + "type": "number" + } + } + } + }, + "AssetSaleList": { + "title": "AssetSaleList", + "type": "array", + "items": { + "title": "AssetSale", + "type": "object", + "properties": { + "listing_id": { + "title": "ListingId", + "type": "string" + }, + "asset_id": { + "title": "AssetId", + "type": "string" + }, + "amount": { + "title": "Amount", + "type": "number" + }, + "price": { + "title": "Price", + "type": "number" + }, + "market_id": { + "title": "MarketId", + "type": "string" + }, + "highest_offers": { + "type": "array", + "title": "HighestOffers", + "items": { + "type": "object", + "title": "HighOffer", + "properties": { + "from": { + "type": "string" + }, + "amount": { + "type": "number" + }, + "price": { + "type": "number" + } + } + } + } + } + } + }, + "AssetProof": { + "title": "AssetProof", + "type": "object", + "required": [ + "node_index", + "tree_id", + "proof" + ], + "properties": { + "proof": { + "type": "array", + "items": { + "title": "ProofItem", + "type": "string" + } + }, + "node_index": { + "title": "NodeIndex", + "type": "number" + }, + "tree_id": { + "title": "TreeId", + "type": "string" + } + } + }, + "Asset": { + "title": "Asset", + "type": "object", + "required": [ + "interface", + "id" + ], + "properties": { + "interface": { + "title": "Interface", + "type": "string", + "enum": [ + "NFT1.0", + "NFT", + "FungibleAsset", + "Custom", + "Identity" + ] + }, + "id": { + "title": "Id", + "type": "string" + }, + "content": { + "title": "Content", + "type": "object", + "required": [ + "$schema" + ], + "properties": { + "$schema": { + "title": "Schema", + "type": "string" + }, + "files": { + "type": "array", + "title": "Files", + "items": { + "type": "object", + "title": "File", + "properties": { + "uri": { + "title": "Uri", + "type": "string" + }, + "mime": { + "title": "Mime", + "type": "string" + }, + "quality": { + "title": "Quality", + "type": "object", + "properties": { + "$$schema": { + "title": "SubSchema", + "type": "string" + } + } + }, + "contexts": { + "title": "Contexts", + "type": "array", + "items": { + "title": "Context", + "type": "string", + "enum": [ + "wallet-default", + "web-desktop", + "web-mobile", + "app-mobile", + "app-desktop", + "app", + "vr" + ] + } + } + } + } + }, + "metadata": { + "title": "Metadata", + "type": "array", + "items": { + "title": "MetadataItem", + "type": "object" + } + }, + "links": { + "title": "Links", + "type": "array", + "items": { + "type": "object" + } + } + } + }, + "authorities": { + "title": "Authorities", + "type": "array", + "items": { + "type": "object", + "title": "Authority", + "address": { + "title": "Address", + "type": "string" + }, + "scopes": { + "title": "Scopes", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "compression": { + "title": "Compression", + "type": "object", + "properties": { + "eligible": { + "title": "Eligible", + "type": "boolean" + }, + "compressed": { + "title": "Compressed", + "type": "boolean" + } + } + }, + "grouping": { + "title": "Grouping", + "type": "array", + "items": { + "title": "Group", + "type": "object", + "required": [ + "$$schema" + ], + "properties": { + "$$schema": { + "title": "SubSchema", + "type": "string" + }, + "group_key": { + "title": "GroupKey", + "type": "string" + }, + "group_value": { + "title": "GroupValue", + "type": "string" + } + } + } + }, + "royalty": { + "type": "object", + "title": "Royalty", + "properties": { + "royalty_model": { + "type": "string", + "title": "RoyaltyModel", + "enum": [ + "creators", + "fanout", + "single" + ] + }, + "target": { + "title": "Target", + "type": "string" + }, + "percent": { + "title": "Percent", + "type": "number" + }, + "locked": { + "title": "Locked", + "type": "boolean" + } + } + }, + "creators": { + "type": "array", + "title": "Creators", + "items": { + "type": "object", + "title": "Creator", + "properties": { + "address": { + "title": "Address", + "type": "string" + }, + "share": { + "title": "Share", + "type": "string" + }, + "verified": { + "title": "Verified", + "type": "boolean" + } + } + } + }, + "ownership": { + "type": "object", + "title": "Ownership", + "properties": { + "frozen": { + "title": "Frozen", + "type": "boolean" + }, + "delegated": { + "title": "Delegated", + "type": "boolean" + }, + "delegate": { + "title": "Delegate", + "type": "string" + }, + "ownership_model": { + "title": "OwnershipModel", + "type": "string", + "enum": [ + "Single", + "Token" + ] + }, + "address": { + "type": "string" + } + } + } + } + }, + "AssetList": { + "title": "AssetList", + "type": "array", + "items": { + "$ref": "#/components/schemas/Asset" + } + } + } + } +} \ No newline at end of file diff --git a/digital_asset_types/sqlx-data.json b/digital_asset_types/sqlx-data.json new file mode 100644 index 000000000..95c8c858b --- /dev/null +++ b/digital_asset_types/sqlx-data.json @@ -0,0 +1,3 @@ +{ + "db": "PostgreSQL" +} \ No newline at end of file diff --git a/digital_asset_types/src/adapter/metadata.rs b/digital_asset_types/src/adapter/metadata.rs new file mode 100644 index 000000000..713e60b63 --- /dev/null +++ b/digital_asset_types/src/adapter/metadata.rs @@ -0,0 +1,55 @@ +use num_derive::FromPrimitive; +use solana_sdk::pubkey::Pubkey; + +#[cfg(feature = "json_types")] +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "json_types", derive(Deserialize, Serialize))] +#[derive(PartialEq, Copy, Clone, Debug, FromPrimitive)] +pub enum TokenProgramVersion { + Original, + Token2022, +} + +#[cfg_attr(feature = "json_types", derive(Deserialize, Serialize))] +#[derive(PartialEq, Copy, Clone, Debug)] +pub struct Creator { + pub address: Pubkey, + pub verified: bool, + // In percentages, NOT basis points ;) Watch out! + pub share: u8, +} + +#[cfg_attr(feature = "json_types", derive(Deserialize, Serialize))] +#[derive(PartialEq, Copy, Clone, Debug, FromPrimitive)] +pub enum TokenStandard { + NonFungible, // This is a master edition + FungibleAsset, // A token with metadata that can also have attrributes + Fungible, // A token with simple metadata + NonFungibleEdition, // This is a limited edition +} + +#[cfg_attr(feature = "json_types", derive(Deserialize, Serialize))] +#[derive(PartialEq, Copy, Clone, Debug, FromPrimitive)] +pub enum UseMethod { + Burn, + Multiple, + Single, +} + +#[cfg_attr(feature = "json_types", derive(Deserialize, Serialize))] +#[derive(PartialEq, Copy, Clone, Debug)] +pub struct Uses { + // 17 bytes + Option byte + pub use_method: UseMethod, //1 + pub remaining: u64, //8 + pub total: u64, //8 +} + +#[repr(C)] +#[cfg_attr(feature = "json_types", derive(Deserialize, Serialize))] +#[derive(PartialEq, Copy, Clone, Debug)] +pub struct Collection { + pub verified: bool, + pub key: Pubkey, +} diff --git a/digital_asset_types/src/adapter/mod.rs b/digital_asset_types/src/adapter/mod.rs new file mode 100644 index 000000000..b60165bfa --- /dev/null +++ b/digital_asset_types/src/adapter/mod.rs @@ -0,0 +1,2 @@ +mod metadata; +pub use metadata::*; diff --git a/digital_asset_types/src/dao/asset.rs b/digital_asset_types/src/dao/asset.rs new file mode 100644 index 000000000..51e627d3a --- /dev/null +++ b/digital_asset_types/src/dao/asset.rs @@ -0,0 +1,151 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use super::sea_orm_active_enums::OwnerType; +use super::sea_orm_active_enums::RoyaltyTargetType; +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "asset" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: Vec, + pub specification_version: i32, + pub owner: Vec, + pub owner_type: OwnerType, + pub delegate: Option>, + pub frozen: bool, + pub supply: i64, + pub supply_mint: Option>, + pub compressed: bool, + pub compressible: bool, + pub tree_id: Option>, + pub leaf: Option>, + pub nonce: i64, + pub royalty_target_type: RoyaltyTargetType, + pub royalty_target: Option>, + pub royalty_amount: i32, + pub chain_data_id: Option, + pub created_at: Option, + pub burnt: bool, + pub seq: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + SpecificationVersion, + Owner, + OwnerType, + Delegate, + Frozen, + Supply, + SupplyMint, + Compressed, + Compressible, + TreeId, + Leaf, + Nonce, + RoyaltyTargetType, + RoyaltyTarget, + RoyaltyAmount, + ChainDataId, + CreatedAt, + Burnt, + Seq, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = Vec; + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + AssetData, + AssetGrouping, + AssetAuthority, + AssetCreators, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Binary.def(), + Self::SpecificationVersion => ColumnType::Integer.def(), + Self::Owner => ColumnType::Binary.def(), + Self::OwnerType => OwnerType::db_type(), + Self::Delegate => ColumnType::Binary.def().null(), + Self::Frozen => ColumnType::Boolean.def(), + Self::Supply => ColumnType::BigInteger.def(), + Self::SupplyMint => ColumnType::Binary.def().null(), + Self::Compressed => ColumnType::Boolean.def(), + Self::Compressible => ColumnType::Boolean.def(), + Self::TreeId => ColumnType::Binary.def().null(), + Self::Leaf => ColumnType::Binary.def().null(), + Self::Nonce => ColumnType::BigInteger.def(), + Self::RoyaltyTargetType => RoyaltyTargetType::db_type(), + Self::RoyaltyTarget => ColumnType::Binary.def().null(), + Self::RoyaltyAmount => ColumnType::Integer.def(), + Self::ChainDataId => ColumnType::BigInteger.def().null(), + Self::CreatedAt => ColumnType::TimestampWithTimeZone.def().null(), + Self::Burnt => ColumnType::Boolean.def(), + Self::Seq => ColumnType::BigInteger.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::AssetData => Entity::belongs_to(super::asset_data::Entity) + .from(Column::ChainDataId) + .to(super::asset_data::Column::Id) + .into(), + Self::AssetGrouping => Entity::has_many(super::asset_grouping::Entity).into(), + Self::AssetAuthority => Entity::has_many(super::asset_authority::Entity).into(), + Self::AssetCreators => Entity::has_many(super::asset_creators::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AssetData.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AssetGrouping.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AssetAuthority.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AssetCreators.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/asset_authority.rs b/digital_asset_types/src/dao/asset_authority.rs new file mode 100644 index 000000000..ee51d4870 --- /dev/null +++ b/digital_asset_types/src/dao/asset_authority.rs @@ -0,0 +1,80 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "asset_authority" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: i64, + pub asset_id: Vec, + pub scopes: Option, + pub authority: Vec, + pub seq: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + AssetId, + Scopes, + Authority, + Seq, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i64; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Asset, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::BigInteger.def(), + Self::AssetId => ColumnType::Binary.def(), + Self::Scopes => ColumnType::Custom("array".to_owned()).def().null(), + Self::Authority => ColumnType::Binary.def(), + Self::Seq => ColumnType::BigInteger.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Asset => Entity::belongs_to(super::asset::Entity) + .from(Column::AssetId) + .to(super::asset::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Asset.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/asset_creators.rs b/digital_asset_types/src/dao/asset_creators.rs new file mode 100644 index 000000000..0d2e456e5 --- /dev/null +++ b/digital_asset_types/src/dao/asset_creators.rs @@ -0,0 +1,83 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "asset_creators" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: i64, + pub asset_id: Vec, + pub creator: Vec, + pub share: i32, + pub verified: bool, + pub seq: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + AssetId, + Creator, + Share, + Verified, + Seq, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i64; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Asset, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::BigInteger.def(), + Self::AssetId => ColumnType::Binary.def(), + Self::Creator => ColumnType::Binary.def(), + Self::Share => ColumnType::Integer.def(), + Self::Verified => ColumnType::Boolean.def(), + Self::Seq => ColumnType::BigInteger.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Asset => Entity::belongs_to(super::asset::Entity) + .from(Column::AssetId) + .to(super::asset::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Asset.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/asset_data.rs b/digital_asset_types/src/dao/asset_data.rs new file mode 100644 index 000000000..457701152 --- /dev/null +++ b/digital_asset_types/src/dao/asset_data.rs @@ -0,0 +1,85 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use super::sea_orm_active_enums::ChainMutability; +use super::sea_orm_active_enums::Mutability; +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "asset_data" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: i64, + pub chain_data_mutability: ChainMutability, + pub schema_version: i32, + pub chain_data: Json, + pub metadata_url: String, + pub metadata_mutability: Mutability, + pub metadata: Json, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + ChainDataMutability, + SchemaVersion, + ChainData, + MetadataUrl, + MetadataMutability, + Metadata, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i64; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Asset, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::BigInteger.def(), + Self::ChainDataMutability => ChainMutability::db_type(), + Self::SchemaVersion => ColumnType::Integer.def(), + Self::ChainData => ColumnType::JsonBinary.def(), + Self::MetadataUrl => ColumnType::String(Some(200u32)).def(), + Self::MetadataMutability => Mutability::db_type(), + Self::Metadata => ColumnType::JsonBinary.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Asset => Entity::has_many(super::asset::Entity).into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Asset.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/asset_grouping.rs b/digital_asset_types/src/dao/asset_grouping.rs new file mode 100644 index 000000000..a5f248fb2 --- /dev/null +++ b/digital_asset_types/src/dao/asset_grouping.rs @@ -0,0 +1,80 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "asset_grouping" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: i64, + pub asset_id: Vec, + pub group_key: String, + pub group_value: String, + pub seq: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + AssetId, + GroupKey, + GroupValue, + Seq, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i64; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Asset, +} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::BigInteger.def(), + Self::AssetId => ColumnType::Binary.def(), + Self::GroupKey => ColumnType::Text.def(), + Self::GroupValue => ColumnType::Text.def(), + Self::Seq => ColumnType::BigInteger.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Asset => Entity::belongs_to(super::asset::Entity) + .from(Column::AssetId) + .to(super::asset::Column::Id) + .into(), + } + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Asset.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/backfill_items.rs b/digital_asset_types/src/dao/backfill_items.rs new file mode 100644 index 000000000..e259721ba --- /dev/null +++ b/digital_asset_types/src/dao/backfill_items.rs @@ -0,0 +1,70 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "backfill_items" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: i64, + pub tree: Vec, + pub seq: i64, + pub slot: i64, + pub force_chk: Option, + pub backfilled: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Tree, + Seq, + Slot, + ForceChk, + Backfilled, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i64; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::BigInteger.def(), + Self::Tree => ColumnType::Binary.def(), + Self::Seq => ColumnType::BigInteger.def(), + Self::Slot => ColumnType::BigInteger.def(), + Self::ForceChk => ColumnType::Boolean.def().null(), + Self::Backfilled => ColumnType::Boolean.def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/cl_items.rs b/digital_asset_types/src/dao/cl_items.rs new file mode 100644 index 000000000..f61f440ef --- /dev/null +++ b/digital_asset_types/src/dao/cl_items.rs @@ -0,0 +1,73 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "cl_items" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub id: i64, + pub tree: Vec, + pub node_idx: i64, + pub leaf_idx: Option, + pub seq: i64, + pub level: i64, + pub hash: Vec, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Tree, + NodeIdx, + LeafIdx, + Seq, + Level, + Hash, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i64; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::BigInteger.def(), + Self::Tree => ColumnType::Binary.def(), + Self::NodeIdx => ColumnType::BigInteger.def(), + Self::LeafIdx => ColumnType::BigInteger.def().null(), + Self::Seq => ColumnType::BigInteger.def(), + Self::Level => ColumnType::BigInteger.def(), + Self::Hash => ColumnType::Binary.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/mod.rs b/digital_asset_types/src/dao/mod.rs new file mode 100644 index 000000000..f66834bc4 --- /dev/null +++ b/digital_asset_types/src/dao/mod.rs @@ -0,0 +1,13 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +pub mod prelude; + +pub mod asset; +pub mod asset_authority; +pub mod asset_creators; +pub mod asset_data; +pub mod asset_grouping; +pub mod backfill_items; +pub mod cl_items; +pub mod raw_txn; +pub mod sea_orm_active_enums; diff --git a/digital_asset_types/src/dao/prelude.rs b/digital_asset_types/src/dao/prelude.rs new file mode 100644 index 000000000..b57eb38f5 --- /dev/null +++ b/digital_asset_types/src/dao/prelude.rs @@ -0,0 +1,10 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +pub use super::asset::Entity as Asset; +pub use super::asset_authority::Entity as AssetAuthority; +pub use super::asset_creators::Entity as AssetCreators; +pub use super::asset_data::Entity as AssetData; +pub use super::asset_grouping::Entity as AssetGrouping; +pub use super::backfill_items::Entity as BackfillItems; +pub use super::cl_items::Entity as ClItems; +pub use super::raw_txn::Entity as RawTxn; diff --git a/digital_asset_types/src/dao/raw_txn.rs b/digital_asset_types/src/dao/raw_txn.rs new file mode 100644 index 000000000..fb5fac129 --- /dev/null +++ b/digital_asset_types/src/dao/raw_txn.rs @@ -0,0 +1,61 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "raw_txn" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Serialize, Deserialize)] +pub struct Model { + pub signature: String, + pub slot: i64, + pub processed: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Signature, + Slot, + Processed, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Signature, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = String; + fn auto_increment() -> bool { + false + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Signature => ColumnType::String(Some(64u32)).def(), + Self::Slot => ColumnType::BigInteger.def(), + Self::Processed => ColumnType::Boolean.def(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/digital_asset_types/src/dao/sea_orm_active_enums.rs b/digital_asset_types/src/dao/sea_orm_active_enums.rs new file mode 100644 index 000000000..847891b60 --- /dev/null +++ b/digital_asset_types/src/dao/sea_orm_active_enums.rs @@ -0,0 +1,51 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.8.0 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "owner_type")] +pub enum OwnerType { + #[sea_orm(string_value = "single")] + Single, + #[sea_orm(string_value = "token")] + Token, + #[sea_orm(string_value = "unknown")] + Unknown, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "chain_mutability")] +pub enum ChainMutability { + #[sea_orm(string_value = "immutable")] + Immutable, + #[sea_orm(string_value = "mutable")] + Mutable, + #[sea_orm(string_value = "unknown")] + Unknown, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm( + rs_type = "String", + db_type = "Enum", + enum_name = "royalty_target_type" +)] +pub enum RoyaltyTargetType { + #[sea_orm(string_value = "creators")] + Creators, + #[sea_orm(string_value = "fanout")] + Fanout, + #[sea_orm(string_value = "single")] + Single, + #[sea_orm(string_value = "unknown")] + Unknown, +} +#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)] +#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "mutability")] +pub enum Mutability { + #[sea_orm(string_value = "immutable")] + Immutable, + #[sea_orm(string_value = "mutable")] + Mutable, + #[sea_orm(string_value = "unknown")] + Unknown, +} diff --git a/digital_asset_types/src/dapi/asset.rs b/digital_asset_types/src/dapi/asset.rs new file mode 100644 index 000000000..02e5cb8e6 --- /dev/null +++ b/digital_asset_types/src/dapi/asset.rs @@ -0,0 +1,240 @@ +use std::collections::HashMap; +use std::path::Path; +use jsonpath_lib::JsonPathError; +use mime_guess::Mime; +use sea_orm::{DatabaseConnection}; +use sea_orm::{entity::*, query::*, DbErr}; +use url::Url; +use crate::dao::{asset, asset_authority, asset_creators, asset_data, asset_grouping}; +use crate::dao::prelude::{Asset, AssetData}; +use crate::rpc::{Asset as RpcAsset, Authority, Compression, Content, Creator, File, Group, Interface, Links, Ownership, Royalty, Scope}; +use serde_json::Value; + +pub fn to_uri(uri: String) -> Option { + Url::parse(&*uri).ok() +} + +pub fn get_mime(url: Url) -> Option { + mime_guess::from_path(Path::new(url.path())).first() +} + +pub fn get_mime_type_from_uri(uri: String) -> Option { + to_uri(uri) + .and_then(get_mime) + .map(|m| { m.to_string() }) +} + +pub fn file_from_str(str: String) -> File { + let mime = get_mime_type_from_uri(str.clone()); + File { + uri: Some(str), + mime, + quality: None, + contexts: None, + } +} + +pub fn track_top_level_file(file_map: &mut HashMap, top_level_file: Option<&serde_json::Value>) { + if top_level_file.is_some() { + let img = top_level_file.and_then(|x| x.as_str()).unwrap(); + let entry = file_map.get(img); + if entry.is_none() { + file_map.insert(img.to_string(), file_from_str(img.to_string())); + } + } +} + +pub fn safe_select<'a>(selector: &mut impl FnMut(&str) -> Result, JsonPathError>, expr: &str) -> Option<&'a Value> { + selector(expr) + .ok() + .filter(|d| !Vec::is_empty(d)) + .as_mut() + .and_then(|v| v.pop()) +} + +fn v1_content_from_json(metadata: &serde_json::Value) -> Result { + // todo -> move this to the bg worker for pre processing + let mut selector_fn = jsonpath_lib::selector(metadata); + let selector = &mut selector_fn; + println!("{}", metadata.to_string()); + let image = safe_select(selector, "$.image"); + let animation = safe_select(selector, "$.animation_url"); + let external_url = safe_select(selector,"$.external_url") + .map(|val| { + let mut links = HashMap::new(); + links.insert("external_url".to_string(), val[0].to_owned()); + links + }); + let metadata = safe_select(selector, "description"); + let mut actual_files: HashMap = HashMap::new(); + selector("$.properties.files[*]").ok() + .filter(|d| !Vec::is_empty(d)) + .map(|files| { + for v in files.iter() { + if v.is_object() { + let uri = v.get("uri"); + let mime_type = v.get("type"); + match (uri, mime_type) { + (Some(u), Some(m)) => { + let str_uri = u.as_str().unwrap().to_string(); + let str_mime = m.as_str().unwrap().to_string(); + actual_files.insert(str_uri.clone(), File { + uri: Some(str_uri), + mime: Some(str_mime), + quality: None, + contexts: None, + }); + } + (Some(u), None) => { + let str_uri = serde_json::to_string(u).unwrap(); + actual_files.insert(str_uri.clone(), file_from_str(str_uri)); + } + _ => {} + } + } else if v.is_string() { + let str_uri = v.as_str().unwrap().to_string(); + actual_files.insert(str_uri.clone(), file_from_str(str_uri)); + } + } + }); + + track_top_level_file(&mut actual_files, image); + track_top_level_file(&mut actual_files, animation); + let files: Vec = actual_files.into_values().collect(); + Ok(Content { + schema: "https://schema.metaplex.com/nft1.0.json".to_string(), + files: Some(files), + metadata: None, + links: external_url, + }) +} + +fn get_content(asset: &asset::Model, data: &asset_data::Model) -> Result { + match data.schema_version { + 1 => { + v1_content_from_json(&data.metadata) + } + _ => Err(DbErr::Custom("Version Not Implemented".to_string())) + } +} + +pub fn to_authority(authority: Vec) -> Vec { + authority.iter().map(|a| { + Authority { + address: bs58::encode(&a.authority).into_string(), + scopes: vec![Scope::Full], + } + }).collect() +} + +pub fn to_creators(creators: Vec) -> Vec { + creators.iter().map(|a| { + Creator { + address: bs58::encode(&a.creator).into_string(), + share: a.share, + verified: a.verified, + } + }).collect() +} + +pub fn to_grouping(groups: Vec) -> Vec { + groups.iter().map(|a| { + Group { + group_key: a.group_key.clone(), + group_value: a.group_value.clone(), + } + }).collect() +} + +pub async fn get_asset(db: &DatabaseConnection, asset_id: Vec) -> Result { + let asset_data: (asset::Model, + asset_data::Model) = Asset::find_by_id(asset_id) + .find_also_related(AssetData) + .one(db) + .await + .and_then(|o| { + match o { + Some((a, Some(d))) => Ok((a, d)), + _ => Err(DbErr::RecordNotFound("Asset Not Found".to_string())) + } + })?; + + let (asset, data) = asset_data; + + let interface = match asset.specification_version { + 1 => Interface::NftOneZero, + _ => Interface::Nft + }; + + let content = get_content(&asset, &data)?; + let authorities: Vec = asset_authority::Entity::find() + .filter(asset_authority::Column::AssetId.eq(asset.id.clone())) + .all(db) + .await?; + + let creators: Vec = asset_creators::Entity::find() + .filter(asset_creators::Column::AssetId.eq(asset.id.clone())) + .all(db) + .await?; + let grouping: Vec = asset_grouping::Entity::find() + .filter(asset_grouping::Column::AssetId.eq(asset.id.clone())) + .all(db) + .await?; + let rpc_authorities = to_authority(authorities); + let rpc_creators = to_creators(creators); + let rpc_groups = to_grouping(grouping); + + + Ok(RpcAsset { + interface, + id: bs58::encode(asset.id).into_string(), + content: Some(content), + authorities: Some(rpc_authorities), + compression: Some( + Compression { + eligible: asset.compressible, + compressed: asset.compressed, + } + ), + grouping: Some(rpc_groups), + royalty: Some( + Royalty { + royalty_model: asset.royalty_target_type.into(), + target: asset.royalty_target.map(|s| bs58::encode(s).into_string()), + percent: (asset.royalty_amount as f64) * 0.0001, + locked: false, + } + ), + creators: Some(rpc_creators), + ownership: Ownership { + frozen: asset.frozen, + delegated: asset.delegate.is_some(), + delegate: asset.delegate.map(|s| bs58::encode(s).into_string()), + ownership_model: asset.owner_type.into(), + owner: bs58::encode(asset.owner).into_string(), + }, + }) +} + +#[cfg(test)] +mod tests { + // Note this useful idiom: importing names from outer (for mod tests) scope. + use super::*; + + #[test] + fn simple_v1_content() { + let doc = r#" + {"name": "Handalf", "image": "https://arweave.net/UicDlez8No5ruKmQ1-Ik0x_NNxc40mT8NEGngWyXyMY", "attributes": [], "properties": {"files": ["https://arweave.net/UicDlez8No5ruKmQ1-Ik0x_NNxc40mT8NEGngWyXyMY"], "category": null}, "description": "The Second NFT ever minted from justmint.xyz", "external_url": ""} + "#; + + let json: Value = serde_json::from_str(doc).unwrap(); + let mut selector = jsonpath_lib::selector(&json); + let c: Content = v1_content_from_json(&json).unwrap(); + assert_eq!(c.files, Some(vec![File { + uri: Some("https://arweave.net/UicDlez8No5ruKmQ1-Ik0x_NNxc40mT8NEGngWyXyMY".to_string()), + mime: None, + quality: None, + contexts: None, + }])) + } +} diff --git a/digital_asset_types/src/dapi/change_logs.rs b/digital_asset_types/src/dapi/change_logs.rs new file mode 100644 index 000000000..bf87f6527 --- /dev/null +++ b/digital_asset_types/src/dapi/change_logs.rs @@ -0,0 +1,114 @@ +use sea_orm::{DatabaseConnection, DbBackend}; +use std::fmt::format; +use sea_orm::sea_query::{Expr, PostgresQueryBuilder, Query}; +use { + crate::dao::asset, + crate::dao::cl_items, + sea_orm::{entity::*, FromQueryResult, query::*, DbErr}, + concurrent_merkle_tree::utils::empty_node, + crate::rpc::AssetProof, +}; + + +#[derive(FromQueryResult, Debug, Default, Clone, Eq, PartialEq)] +struct SimpleChangeLog { + hash: Vec, + level: i64, + node_idx: i64, + seq: i64 +} + +pub async fn get_proof_for_asset( + db: &DatabaseConnection, + asset_id: Vec, +) -> Result { + let leaf: Option = cl_items::Entity::find() + .join_rev( + JoinType::InnerJoin, + asset::Entity::belongs_to(cl_items::Entity) + .from(asset::Column::Nonce) + .to(cl_items::Column::LeafIdx) + .into(), + ) + .order_by_desc(cl_items::Column::Seq) + .filter(Expr::cust("asset.tree_id = cl_items.tree")) + .filter(Expr::cust_with_values("asset.id = ?::bytea", vec![asset_id])) + .filter(cl_items::Column::Level.eq(0i64)) + .one(db).await?; + if leaf.is_none() { + return Err(DbErr::RecordNotFound("Asset Proof Not Found".to_string())); + } + let leaf = leaf.unwrap(); + let req_indexes = get_required_nodes_for_proof(leaf.node_idx); + let expected_proof_size = req_indexes.len(); + let mut final_node_list: Vec = vec![SimpleChangeLog::default(); expected_proof_size]; + let mut query = cl_items::Entity::find() + .select_only() + .column(cl_items::Column::NodeIdx) + .column(cl_items::Column::Hash) + .column(cl_items::Column::Level) + .column(cl_items::Column::Seq) + .column(cl_items::Column::Tree) + .filter(cl_items::Column::NodeIdx.is_in(req_indexes.clone())) + .filter(cl_items::Column::Tree.eq(leaf.tree.clone())) + .order_by_desc(cl_items::Column::NodeIdx) + .order_by_desc(cl_items::Column::Id) + .order_by_desc(cl_items::Column::Seq) + .build(DbBackend::Postgres); + query.sql = query.sql.replace("SELECT", "SELECT DISTINCT ON (cl_items.node_idx)"); + println!("sql {} ", query.sql); + let nodes: Vec = db.query_all(query).await + .map(|qr| { + qr.iter().map(|q| SimpleChangeLog::from_query_result(q, "").unwrap() ).collect() + })?; + if nodes.len() != expected_proof_size { + for node in nodes.iter() { + if node.level < final_node_list.len().try_into().unwrap() { + final_node_list[node.level as usize] = node.to_owned(); + } + } + for (i, (n, nin)) in final_node_list.iter_mut().zip(req_indexes).enumerate() { + if *n == SimpleChangeLog::default() { + *n = make_empty_node(i as i64, nin); + } + } + } + for n in final_node_list.iter() { + println!("level {} index {} seq {} hash {}", n.level, n.node_idx, n.seq, bs58::encode(&n.hash).into_string()); + } + Ok(AssetProof { + root: bs58::encode(final_node_list.pop().unwrap().hash).into_string(), + leaf: bs58::encode(&leaf.hash).into_string(), + proof: final_node_list + .iter() + .map(|model| bs58::encode(&model.hash).into_string()) + .collect(), + node_index: leaf.node_idx, + tree_id: bs58::encode(&leaf.tree).into_string(), + }) +} + + +fn make_empty_node(lvl: i64, node_index: i64) -> SimpleChangeLog { + SimpleChangeLog { + node_idx: node_index, + level: lvl, + hash: empty_node(lvl as u32).to_vec(), + seq: 0, + } +} + +pub fn get_required_nodes_for_proof(index: i64) -> Vec { + let mut indexes = vec![]; + let mut idx = index; + while idx > 1 { + if idx % 2 == 0 { + indexes.push(idx + 1) + } else { + indexes.push(idx - 1) + } + idx >>= 1 + } + indexes.push(1); + return indexes; +} diff --git a/digital_asset_types/src/dapi/mod.rs b/digital_asset_types/src/dapi/mod.rs new file mode 100644 index 000000000..ac915c54f --- /dev/null +++ b/digital_asset_types/src/dapi/mod.rs @@ -0,0 +1,2 @@ +pub mod change_logs; +pub mod asset; \ No newline at end of file diff --git a/digital_asset_types/src/json/chain_data.rs b/digital_asset_types/src/json/chain_data.rs new file mode 100644 index 000000000..6a9a74e97 --- /dev/null +++ b/digital_asset_types/src/json/chain_data.rs @@ -0,0 +1,12 @@ +use crate::adapter::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct ChainDataV1 { + pub name: String, + pub symbol: String, + pub edition_nonce: Option, + pub primary_sale_happened: bool, + pub token_standard: Option, + pub uses: Option, +} diff --git a/digital_asset_types/src/json/mod.rs b/digital_asset_types/src/json/mod.rs new file mode 100644 index 000000000..51459dcc0 --- /dev/null +++ b/digital_asset_types/src/json/mod.rs @@ -0,0 +1,3 @@ +mod chain_data; + +pub use chain_data::*; diff --git a/digital_asset_types/src/lib.rs b/digital_asset_types/src/lib.rs new file mode 100644 index 000000000..79096fe9f --- /dev/null +++ b/digital_asset_types/src/lib.rs @@ -0,0 +1,10 @@ +#[cfg(feature = "json_types")] +pub mod adapter; +#[cfg(feature = "sql_types")] +pub mod dao; +#[cfg(feature = "sql_types")] +pub mod dapi; +#[cfg(feature = "json_types")] +pub mod json; +#[cfg(feature = "json_types")] +pub mod rpc; diff --git a/digital_asset_types/src/rpc/asset.rs b/digital_asset_types/src/rpc/asset.rs new file mode 100644 index 000000000..f52542d3d --- /dev/null +++ b/digital_asset_types/src/rpc/asset.rs @@ -0,0 +1,247 @@ +use std::str::FromStr; +use { + serde::{Deserialize, Serialize}, + std::collections::HashMap, +}; +#[cfg(feature = "sql_types")] +use crate::dao::{ + sea_orm_active_enums::{Mutability,ChainMutability,OwnerType,RoyaltyTargetType} +}; + + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct AssetProof { + pub root: String, + pub proof: Vec, + pub node_index: i64, + pub leaf: String, + pub tree_id: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum Interface { + #[serde(rename = "NFT1.0")] + NftOneZero, + #[serde(rename = "NFT")] + Nft, + #[serde(rename = "FungibleAsset")] + FungibleAsset, + #[serde(rename = "Custom")] + Custom, + #[serde(rename = "Identity")] + Identity, + #[serde(rename = "Executable")] + Executable, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Quality { + #[serde(rename = "$$schema")] + pub schema: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum Context { + #[serde(rename = "wallet-default")] + WalletDefault, + #[serde(rename = "web-desktop")] + WebDesktop, + #[serde(rename = "web-mobile")] + WebMobile, + #[serde(rename = "app-mobile")] + AppMobile, + #[serde(rename = "app-desktop")] + AppDesktop, + #[serde(rename = "app")] + App, + #[serde(rename = "vr")] + Vr, +} + +pub type Contexts = Vec; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct File { + #[serde(skip_serializing_if = "Option::is_none")] + pub uri: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub mime: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub quality: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub contexts: Option, +} + +pub type Files = Vec; +pub type MetadataItem = HashMap; +// TODO sub schema support +pub type Links = HashMap; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Content { + #[serde(rename = "$schema")] + pub schema: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub files: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub metadata: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub links: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub enum Scope { + #[serde(rename = "full")] + Full, + #[serde(rename = "royalty")] + Royalty, + #[serde(rename = "metadata")] + Metadata, + #[serde(rename = "extension")] + Extension, +} + +impl From for Scope { + fn from(s: String) -> Self { + match &*s { + "royalty" => Scope::Royalty, + "metadata" => Scope::Metadata, + "extension" => Scope::Extension, + _ => Scope::Full + } + } +} + + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Authority { + pub address: String, + pub scopes: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Compression { + pub eligible: bool, + pub compressed: bool, +} + +pub type GroupKey = String; +pub type GroupValue = String; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Group { + pub group_key: String, + pub group_value: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum RoyaltyModel { + #[serde(rename = "creators")] + Creators, + #[serde(rename = "fanout")] + Fanout, + #[serde(rename = "single")] + Single, +} + +impl From for RoyaltyModel { + fn from(s: String) -> Self { + match &*s { + "creators" => RoyaltyModel::Creators, + "fanout" => RoyaltyModel::Fanout, + "single" => RoyaltyModel::Single, + _ => RoyaltyModel::Creators + } + } +} + +#[cfg(feature = "sql_types")] +impl From for RoyaltyModel { + fn from(s: RoyaltyTargetType) -> Self { + match s { + RoyaltyTargetType::Creators => RoyaltyModel::Creators, + RoyaltyTargetType::Fanout => RoyaltyModel::Fanout, + RoyaltyTargetType::Single => RoyaltyModel::Single, + _ => RoyaltyModel::Creators + } + } +} + + + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Royalty { + pub royalty_model: RoyaltyModel, + pub target: Option, + pub percent: f64, + pub locked: bool, +} + +pub type Address = String; +pub type Share = String; +pub type Verified = bool; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Creator { + pub address: String, + pub share: i32, + pub verified: bool, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum OwnershipModel { + #[serde(rename = "single")] + Single, + #[serde(rename = "token")] + Token, +} + + +impl From for OwnershipModel { + fn from(s: String) -> Self { + match &*s { + "single" => OwnershipModel::Single, + "token" => OwnershipModel::Token, + _ => OwnershipModel::Single + } + } +} + +#[cfg(feature = "sql_types")] +impl From for OwnershipModel { + fn from(s: OwnerType) -> Self { + match s { + OwnerType::Token => OwnershipModel::Token, + OwnerType::Single => OwnershipModel::Single, + _ => OwnershipModel::Single + } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Ownership { + pub frozen: bool, + pub delegated: bool, + pub delegate: Option, + pub ownership_model: OwnershipModel, + pub owner: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +pub struct Asset { + pub interface: Interface, + pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub authorities: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub compression: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub grouping: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub royalty: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub creators: Option>, + pub ownership: Ownership, +} diff --git a/digital_asset_types/src/rpc/filter.rs b/digital_asset_types/src/rpc/filter.rs new file mode 100644 index 000000000..29bb68410 --- /dev/null +++ b/digital_asset_types/src/rpc/filter.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum AssetSorting { + #[serde(rename = "created")] + Created, + #[serde(rename = "updated")] + Updated, + #[serde(rename = "recent_action")] + RecentAction, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum OfferSorting { + #[serde(rename = "created")] + Created, + #[serde(rename = "updated")] + Updated, + #[serde(rename = "price")] + Price, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub enum ListingSorting { + #[serde(rename = "created")] + Created, + #[serde(rename = "updated")] + Updated, + #[serde(rename = "price")] + Price, + #[serde(rename = "number_of_offers")] + NumberOfOffers, +} diff --git a/digital_asset_types/src/rpc/mod.rs b/digital_asset_types/src/rpc/mod.rs new file mode 100644 index 000000000..04a8be9a8 --- /dev/null +++ b/digital_asset_types/src/rpc/mod.rs @@ -0,0 +1,8 @@ +mod asset; +mod sale; + +pub mod filter; +pub mod response; + +pub use asset::*; +pub use sale::*; diff --git a/digital_asset_types/src/rpc/response.rs b/digital_asset_types/src/rpc/response.rs new file mode 100644 index 000000000..f005c6d83 --- /dev/null +++ b/digital_asset_types/src/rpc/response.rs @@ -0,0 +1,47 @@ +use crate::rpc::Offer; +use { + crate::rpc::{Asset, AssetSale}, + serde::{Deserialize, Serialize}, +}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[serde(default)] +pub struct ListingsList { + pub total: u32, + pub limit: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub before: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub after: Option, + pub items: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[serde(default)] +pub struct OfferList { + pub total: u32, + pub limit: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub before: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub after: Option, + pub items: Vec, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[serde(default)] +pub struct AssetList { + pub total: u32, + pub limit: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub page: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub before: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub after: Option, + pub items: Vec, +} diff --git a/digital_asset_types/src/rpc/sale.rs b/digital_asset_types/src/rpc/sale.rs new file mode 100644 index 000000000..d37bf21a3 --- /dev/null +++ b/digital_asset_types/src/rpc/sale.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[serde(default)] +pub struct Offer { + pub from: String, + pub amount: u64, + pub price: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub market_id: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Default)] +#[serde(default)] +pub struct AssetSale { + #[serde(skip_serializing_if = "Option::is_none")] + pub listing_id: Option, + pub asset_id: String, + pub amount: u64, + pub price: u64, + #[serde(skip_serializing_if = "Option::is_none")] + pub market_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub highest_offers: Option, +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 000000000..10a54250e --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,53 @@ +version: "3.9" +services: + ingester: + depends_on: + - redis + restart: always + environment: + INGESTER_DATABASE_CONFIG: '{listener_channel="backfill_item_added", url="postgres://solana:solana@db/solana"}' + INGESTER_MESSENGER_CONFIG: '{redis_connection_str="redis://redis"}' + INGESTER_RPC_CONFIG: '{url="http://candyland-solana-1:8899", commitment="finalized"}' + build: + context: . + dockerfile: Ingest.Dockerfile + api: + restart: always + environment: + APP_DATABASE_URL: postgres://solana:solana@db/solana + APP_SERVER_PORT: 9090 + APP_METRICS_PORT: 9091 + build: + context: . + dockerfile: Api.Dockerfile + ports: + - 9090:9090 + redis: + image: "redis:alpine" + ports: + - "6379:6379" + db: + image: 'postgres:latest' + ports: + - 5432:5432 + environment: + POSTGRES_USER: solana # The PostgreSQL user (useful to connect to the database) + POSTGRES_PASSWORD: solana # The PostgreSQL password (useful to connect to the database) + POSTGRES_DB: solana + volumes: + - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro + - ./db-data/:/var/lib/postgresql/data/:rw + solana: + build: + context: . + dockerfile: Solana.Dockerfile + volumes: + - ./ledger:/config:rw + environment: + RUST_LOG: warn + PLUGIN_MESSENGER_CONFIG: '{redis_connection_str="redis://redis"}' + ports: + - "8900:8900" + - "8001:8001" + - "8899:8899" + - "9900:9900" \ No newline at end of file diff --git a/helm/api/.helmignore b/helm/api/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/api/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/api/Chart.yaml b/helm/api/Chart.yaml new file mode 100644 index 000000000..cc238c07a --- /dev/null +++ b/helm/api/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: api +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/helm/api/templates/_helpers.tpl b/helm/api/templates/_helpers.tpl new file mode 100644 index 000000000..660601d5d --- /dev/null +++ b/helm/api/templates/_helpers.tpl @@ -0,0 +1,67 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "api.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "api.secret" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}-secret +{{- end }} + + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "api.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "api.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "api.labels" -}} +helm.sh/chart: {{ include "api.chart" . }} +{{ include "api.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "api.selectorLabels" -}} +app.kubernetes.io/name: {{ include "api.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "api.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "api.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/api/templates/deployment.yaml b/helm/api/templates/deployment.yaml new file mode 100644 index 000000000..8ad43148a --- /dev/null +++ b/helm/api/templates/deployment.yaml @@ -0,0 +1,86 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "api.fullname" . }} + labels: + {{- include "api.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "api.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "api.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "api.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: datadog + image: gcr.io/datadoghq/dogstatsd:latest + env: + - name: "DD_API_KEY" + value: "{{ .Values.metrics.data_dog_api_key }}" + ports: + - containerPort: 8125 + hostPort: 8125 + name: dogstatsdport + protocol: UDP + - name: {{ .Chart.Name }} + env: + - name: APP_METRICS_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: APP_METRICS_PORT + value: "8125" + - name: APP_SERVER_PORT + value: "{{ .Values.service.port }}" + - name: APP_DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "api.secret" . }} + key: APP_DATABASE_URL + optional: false + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image }}" + imagePullPolicy: {{ .Values.imagePullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + httpGet: + path: /healthz + port: http + readinessProbe: + httpGet: + path: /healthz + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/api/templates/hpa.yaml b/helm/api/templates/hpa.yaml new file mode 100644 index 000000000..51c153626 --- /dev/null +++ b/helm/api/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "api.fullname" . }} + labels: + {{- include "api.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "api.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/api/templates/ingress.yaml b/helm/api/templates/ingress.yaml new file mode 100644 index 000000000..d7a5ccd53 --- /dev/null +++ b/helm/api/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "api.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "api.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/helm/api/templates/secret.yaml b/helm/api/templates/secret.yaml new file mode 100644 index 000000000..d7c146091 --- /dev/null +++ b/helm/api/templates/secret.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "api.secret" . }} +data: + APP_DATABASE_URL: {{ .Values.api.db_url | b64enc | quote }} \ No newline at end of file diff --git a/helm/api/templates/service.yaml b/helm/api/templates/service.yaml new file mode 100644 index 000000000..bc9138977 --- /dev/null +++ b/helm/api/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "api.fullname" . }} + labels: + {{- include "api.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "api.selectorLabels" . | nindent 4 }} diff --git a/helm/api/templates/serviceaccount.yaml b/helm/api/templates/serviceaccount.yaml new file mode 100644 index 000000000..f9b9dc911 --- /dev/null +++ b/helm/api/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "api.serviceAccountName" . }} + labels: + {{- include "api.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/api/templates/tests/test-connection.yaml b/helm/api/templates/tests/test-connection.yaml new file mode 100644 index 000000000..06a77246b --- /dev/null +++ b/helm/api/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "api.fullname" . }}-test-connection" + labels: + {{- include "api.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "api.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/helm/api/values.yaml b/helm/api/values.yaml new file mode 100644 index 000000000..d870d592c --- /dev/null +++ b/helm/api/values.yaml @@ -0,0 +1,89 @@ +# Default values for api. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: "" +imagePullPolicy: Always +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + + +api: + db_url: + redis_url: + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 9090 + +ingress: + enabled: true + className: "alb" + annotations: + alb.ingress.kubernetes.io/scheme: internet-facing + external-dns.alpha.kubernetes.io/hostname: rpc.aws.metaplex.com + alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]' + alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-2:869291162248:certificate/ff00f95c-de27-4bc2-9e79-7ee44c77b9f9 + alb.ingress.kubernetes.io/target-type: ip + hosts: + - host: rpc.aws.metaplex.com + paths: + - path: / + pathType: Prefix + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +metrics: + data_dog_api_key: \ No newline at end of file diff --git a/helm/ingest/.helmignore b/helm/ingest/.helmignore new file mode 100644 index 000000000..0e8a0eb36 --- /dev/null +++ b/helm/ingest/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/helm/ingest/Chart.yaml b/helm/ingest/Chart.yaml new file mode 100644 index 000000000..67012bd42 --- /dev/null +++ b/helm/ingest/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: ingest +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" \ No newline at end of file diff --git a/helm/ingest/templates/_helpers.tpl b/helm/ingest/templates/_helpers.tpl new file mode 100644 index 000000000..b21922e65 --- /dev/null +++ b/helm/ingest/templates/_helpers.tpl @@ -0,0 +1,66 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ingest.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{- define "ingest.secret" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}-secret +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ingest.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ingest.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "ingest.labels" -}} +helm.sh/chart: {{ include "ingest.chart" . }} +{{ include "ingest.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ingest.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ingest.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ingest.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "ingest.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/helm/ingest/templates/deployment-load.yaml b/helm/ingest/templates/deployment-load.yaml new file mode 100644 index 000000000..5232e6439 --- /dev/null +++ b/helm/ingest/templates/deployment-load.yaml @@ -0,0 +1,56 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: load-generator + labels: + app: load + {{- include "ingest.labels" . | nindent 4 }} +spec: + replicas: 5 + selector: + matchLabels: + app: load + {{- include "ingest.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + app: load + {{- include "ingest.selectorLabels" . | nindent 8 }} + spec: + restartPolicy: Always + + containers: + - name: load-gen + image: {{.Values.loadImage}} + imagePullPolicy: {{ .Values.imagePullPolicy }} + env: + - name: SEED + value: {{.Values.loadSeed}} + readinessProbe: + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 60 + exec: + command: + - echo + - "true" + livenessProbe: + initialDelaySeconds: 1 + failureThreshold: 30 + periodSeconds: 60 + exec: + command: + - echo + - "true" + resources: + requests: + memory: "128Mi" + cpu: "250m" + limits: + memory: "1024Mi" + cpu: "500m" + diff --git a/helm/ingest/templates/deployment.yaml b/helm/ingest/templates/deployment.yaml new file mode 100644 index 000000000..4b82357d6 --- /dev/null +++ b/helm/ingest/templates/deployment.yaml @@ -0,0 +1,88 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ingest.fullname" . }} + labels: + {{- include "ingest.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "ingest.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "ingest.selectorLabels" . | nindent 8 }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "ingest.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: datadog + image: gcr.io/datadoghq/dogstatsd:latest + env: + - name: "DD_API_KEY" + value: "{{ .Values.metrics.data_dog_api_key }}" + ports: + - containerPort: 8125 + hostPort: 8125 + name: dogstatsdport + protocol: UDP + - name: {{ .Chart.Name }} + env: + - name: INGESTER_METRICS_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP + - name: INGESTER_METRICS_PORT + value: "8125" + - name: INGESTER_DATABASE_URL + valueFrom: + secretKeyRef: + name: {{ include "ingest.secret" . }} + key: INGESTER_DATABASE_URL + optional: false + - name: INGESTER_MESSENGER_CONFIG + valueFrom: + secretKeyRef: + name: {{ include "ingest.secret" . }} + key: INGESTER_MESSENGER_CONFIG + optional: false + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image }}" + imagePullPolicy: {{ .Values.imagePullPolicy }} + readinessProbe: + exec: + command: + - echo + - "true" + livenessProbe: + exec: + command: + - echo + - "true" + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/helm/ingest/templates/hpa.yaml b/helm/ingest/templates/hpa.yaml new file mode 100644 index 000000000..04e1540b9 --- /dev/null +++ b/helm/ingest/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "ingest.fullname" . }} + labels: + {{- include "ingest.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "ingest.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/helm/ingest/templates/secret.yaml b/helm/ingest/templates/secret.yaml new file mode 100644 index 000000000..f47d774df --- /dev/null +++ b/helm/ingest/templates/secret.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "ingest.secret" . }} +data: + INGESTER_DATABASE_URL: {{ .Values.ingest.db_url | b64enc | quote }} + INGESTER_MESSENGER_CONFIG: {{ (printf "{redis_connection_str=%s}" .Values.ingest.redis_url) | b64enc | quote }} \ No newline at end of file diff --git a/helm/ingest/templates/serviceaccount.yaml b/helm/ingest/templates/serviceaccount.yaml new file mode 100644 index 000000000..c1c96ce30 --- /dev/null +++ b/helm/ingest/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "ingest.serviceAccountName" . }} + labels: + {{- include "ingest.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/ingest/values.yaml b/helm/ingest/values.yaml new file mode 100644 index 000000000..de31aa32d --- /dev/null +++ b/helm/ingest/values.yaml @@ -0,0 +1,58 @@ +replicaCount: 1 +image: "" +loadImage: "" +imagePullPolicy: Always +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" +loadSeed: "" +ingest: + db_url: + redis_url: + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: + limits: + cpu: 5 + memory: 4096Mi + requests: + cpu: 3 + memory: 1024Mi + +# need to shard consumer groups first +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 1 + targetCPUUtilizationPercentage: 90 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +metrics: + data_dog_api_key: \ No newline at end of file diff --git a/helm/values-testnet.yaml b/helm/values-testnet.yaml new file mode 100644 index 000000000..e69de29bb diff --git a/init.sql b/init.sql new file mode 100644 index 000000000..d0b7c46e3 --- /dev/null +++ b/init.sql @@ -0,0 +1,161 @@ +CREATE TABLE raw_txn +( + signature varchar(64) PRIMARY KEY, + slot bigint not null, + processed bool not null +); + +CREATE INDEX raw_slot on raw_txn (slot); + +CREATE TABLE cl_items +( + id bigserial PRIMARY KEY, + tree BYTEA NOT NULL, + node_idx BIGINT NOT NULL, + leaf_idx BIGINT, + seq BIGINT NOT NULL, + level BIGINT NOT NULL, + hash BYTEA NOT NULL +); +-- Index All the things space is cheap +CREATE INDEX cl_items_tree_idx on cl_items (tree); +CREATE INDEX cl_items_hash_idx on cl_items (hash); +CREATE INDEX cl_items_level on cl_items (level); +CREATE INDEX cl_items_node_idx on cl_items (node_idx); +CREATE INDEX cl_items_leaf_idx on cl_items (leaf_idx); +CREATE UNIQUE INDEX cl_items__tree_node on cl_items (tree, node_idx); + +CREATE TABLE backfill_items +( + id bigserial PRIMARY KEY, + tree BYTEA NOT NULL, + seq BIGINT NOT NULL, + slot BIGINT NOT NULL, + force_chk bool, + backfilled bool +); + +CREATE INDEX backfill_items_tree_idx on backfill_items (tree); +CREATE INDEX backfill_items_seq_idx on backfill_items (seq); +CREATE INDEX backfill_items_slot_idx on backfill_items (slot); +CREATE INDEX backfill_items_force_chk_idx on backfill_items (force_chk); +CREATE INDEX backfill_items_backfilled_idx on backfill_items (backfilled); +CREATE INDEX backfill_items_tree_seq_idx on backfill_items (tree, seq); +CREATE INDEX backfill_items_tree_slot_idx on backfill_items (tree, slot); +CREATE INDEX backfill_items_tree_force_chk_idx on backfill_items (tree, force_chk); + +CREATE or REPLACE FUNCTION notify_new_backfill_item() + RETURNS trigger + LANGUAGE 'plpgsql' +as $BODY$ +declare +begin + if (tg_op = 'INSERT') then + perform pg_notify('backfill_item_added', 'hello'); + end if; + + return null; +end +$BODY$; + +CREATE TRIGGER after_insert_item + AFTER INSERT + ON backfill_items + FOR EACH ROW + EXECUTE PROCEDURE notify_new_backfill_item(); + +-- START NFT METADATA +CREATE TYPE owner_type AS ENUM ('unknown', 'token', 'single'); +CREATE TYPE royalty_target_type AS ENUM ('unknown', 'creators', 'fanout', 'single'); +CREATE TYPE chain_mutability AS ENUM ('unknown', 'mutable', 'immutable'); +CREATE TYPE mutability AS ENUM ('unknown', 'mutable', 'immutable'); + +create table asset_data +( + id bigserial PRIMARY KEY, + chain_data_mutability chain_mutability not null default 'mutable', + schema_version int not null default 1, + chain_data jsonb not null, + metadata_url varchar(200) not null, + metadata_mutability mutability not null default 'mutable', + metadata jsonb not null +); + +create table asset +( + id bytea PRIMARY KEY, + specification_version int not null default 1, + owner bytea not null, + owner_type owner_type not null default 'single', + -- delegation + delegate bytea, + -- freeze + frozen bool not null default false, + -- supply + supply bigint not null default 1, + supply_mint bytea, + -- compression + compressed bool not null default false, + seq bigint not null, + -- -- Can this asset be compressed + compressible bool not null default false, + tree_id bytea, + leaf bytea, + nonce bigint not null, + -- royalty + royalty_target_type royalty_target_type not null default 'creators', + royalty_target bytea, + royalty_amount int not null default 0, + -- data + chain_data_id bigint references asset_data (id), + -- visibility + created_at timestamp with time zone default (now() at time zone 'utc'), + burnt bool not null default false +); + +create index asset_tree on asset (tree_id); +create index asset_leaf on asset (leaf); +create index asset_tree_leaf on asset (tree_id, leaf); +create index asset_revision on asset (tree_id, leaf, nonce); +create index asset_owner on asset (owner); +create index asset_delegate on asset (delegate); + +-- grouping +create table asset_grouping +( + id bigserial PRIMARY KEY, + asset_id bytea references asset (id) not null, + group_key text not null, + group_value text not null, + seq bigint not null +); +-- Limit indexable grouping keys, meaning only create on specific keys, but index the ones we allow +create unique index asset_grouping_asset_id on asset_grouping (asset_id); +create index asset_grouping_key on asset_grouping (group_key, group_value); +create index asset_grouping_value on asset_grouping (group_key, asset_id); + +-- authority +create table asset_authority +( + id bigserial PRIMARY KEY, + asset_id bytea references asset (id) not null, + scopes text[], + authority bytea not null, + seq bigint not null +); +create unique index asset_authority_asset_id on asset_authority (asset_id); +create index asset_authority_idx on asset_authority (asset_id, authority); + +-- creators +create table asset_creators +( + id bigserial PRIMARY KEY, + asset_id bytea references asset (id) not null, + creator bytea not null, + share int not null default 0, + verified bool not null default false, + seq bigint not null +); +create unique index asset_creators_asset_id on asset_creators (asset_id); +create index asset_creator on asset_creators (asset_id, creator); +create index asset_verified_creator on asset_creators (asset_id, verified); diff --git a/nft_api/.dockerignore b/nft_api/.dockerignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/nft_api/.dockerignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/nft_api/Cargo.toml b/nft_api/Cargo.toml new file mode 100644 index 000000000..1082a0493 --- /dev/null +++ b/nft_api/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "nft_api" +version = "0.1.0" +edition = "2021" + +[dependencies] +hex = "0.4.3" +routerify = "3" +routerify-json-response = "3" +redis = { version = "0.21.5", features = ["aio", "tokio-comp", "streams"] } +futures-util = "0.3.0" +hyper = "0.14" +anchor-client = { path = "../deps/anchor/client" } +base64 = "0.13.0" +thiserror = "1.0.30" +tokio = { version = "1.17.0", features = ["full"] } +sqlx = { version = "0.5.11", features = ["runtime-tokio-rustls", "postgres", "uuid"] } +tokio-postgres = "0.7.5" +serde = "1.0.136" +bs58 = "0.4.0" +reqwest = "0.11.10" +csv = "1.1.6" +messenger = { path = "../messenger" } +plerkle = { path = "../plerkle" } +flatbuffers = "2.1.2" +solana-sdk = { version = "=1.10.10" } +lazy_static = "1.4.0" +regex = "1.5.5" +plerkle-serialization = { path = "../plerkle_serialization" } +uuid = "1.0.0" +gummyroll = { path = "../contracts/programs/gummyroll", features = ["no-entrypoint"] } +gummyroll-crud = { path = "../contracts/programs/gummyroll_crud", features = ["no-entrypoint"] } +bubblegum = { path = "../contracts/programs/bubblegum", features = ["no-entrypoint"] } +concurrent-merkle-tree = { path = "../lib/concurrent-merkle-tree" } + +[dependencies.num-integer] +version = "0.1.44" +default-features = false diff --git a/nft_api/rustfmt.toml b/nft_api/rustfmt.toml new file mode 100644 index 000000000..36c419bb3 --- /dev/null +++ b/nft_api/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" \ No newline at end of file diff --git a/nft_api/src/error.rs b/nft_api/src/error.rs new file mode 100644 index 000000000..9585dbf31 --- /dev/null +++ b/nft_api/src/error.rs @@ -0,0 +1,10 @@ +use hyper::StatusCode; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ApiError { + #[error("Parameter Invalid")] + ParameterInvalid, + #[error("Request Error {status:?}, reason {msg:?} ")] + ResponseError { status: StatusCode, msg: String }, +} diff --git a/nft_api/src/main.rs b/nft_api/src/main.rs new file mode 100644 index 000000000..e4e7adb5b --- /dev/null +++ b/nft_api/src/main.rs @@ -0,0 +1,485 @@ +use hyper::{header, Body, Request, Response, Server, StatusCode}; +// Import the routerify prelude traits. +use anchor_client::solana_sdk::pubkey::Pubkey; + +use futures_util::StreamExt; + +use concurrent_merkle_tree::utils::empty_node; +use hyper::header::HeaderValue; + +use redis::Commands; +use routerify::prelude::*; +use routerify::{Middleware, Router, RouterService}; +use routerify_json_response::{json_failed_resp, json_failed_resp_with_message, json_success_resp}; +use serde::Serialize; +use sqlx; +use sqlx::postgres::PgPoolOptions; +use sqlx::{Pool, Postgres}; + +use std::net::SocketAddr; +use std::ops::Index; +use std::str::FromStr; + +mod error; + +use error::ApiError; + +async fn logger(req: Request) -> Result, routerify_json_response::Error> { + println!( + "{} {} {}", + req.remote_addr(), + req.method(), + req.uri().path() + ); + Ok(req) +} + +#[derive(sqlx::FromRow, Clone, Debug)] +struct NodeDAO { + pub hash: Vec, + pub level: i64, + pub node_idx: i64, + pub seq: i64, +} + +#[derive(sqlx::FromRow, Clone, Debug)] +struct AssetDAO { + pub data: String, + pub index: i64, + pub owner: Vec, + pub tree: Vec, + pub admin: Vec, + pub hash: Vec, + pub level: i64, +} + +#[derive(Serialize)] +struct AssetView { + pub data: String, + pub index: i64, + pub owner: String, + pub treeAccount: String, + pub treeAdmin: String, + pub hash: String, +} + +#[derive(sqlx::FromRow)] +struct Root { + pub hash: Vec, +} + +#[derive(sqlx::FromRow)] +struct Level { + pub level: i64, +} + +#[derive(Serialize, Default, Clone, PartialEq)] +struct NodeView { + pub hash: String, + pub level: i64, + pub index: i64, + pub seq: i64, +} + +#[derive(Serialize)] +struct AssetProof { + pub root: String, + pub hash: String, + pub proof: Vec, +} + +fn node_list_to_view(items: Vec) -> Vec { + let mut view = vec![]; + for r in items { + view.push(node_to_view(r)) + } + view +} + +fn node_to_view(r: NodeDAO) -> NodeView { + NodeView { + hash: bs58::encode(r.hash).into_string(), + level: r.level, + index: r.node_idx, + seq: r.seq, + } +} + +fn asset_list_to_view(items: Vec) -> Vec { + let mut view = vec![]; + for r in items { + view.push(asset_to_view(r)) + } + view +} + +fn asset_to_view(r: AssetDAO) -> AssetView { + AssetView { + index: node_idx_to_leaf_idx(r.index, r.level as u32), + treeAccount: bs58::encode(r.tree).into_string(), + owner: bs58::encode(r.owner).into_string().to_string(), + treeAdmin: bs58::encode(r.admin).into_string().to_string(), + hash: bs58::encode(r.hash).into_string().to_string(), + data: r.data, + } +} + +fn leaf_idx_to_node_idx(index: i64, tree_height: u32) -> i64 { + index + 2i64.pow(tree_height) +} + +fn node_idx_to_leaf_idx(index: i64, tree_height: u32) -> i64 { + index - 2i64.pow(tree_height) +} + +/// Takes in an index from leaf-space +async fn handle_get_asset( + req: Request, +) -> Result, routerify_json_response::Error> { + let db: &Pool = req.data::>().unwrap(); + let tree_id = decode_b58_param(req.param("tree_id").unwrap()).unwrap(); + let leaf_idx = req.param("index").unwrap().parse::().unwrap(); + + let tree_height = get_height(db, &tree_id).await.unwrap(); + let node_idx = leaf_idx_to_node_idx(leaf_idx, tree_height); + let result = get_asset(db, &tree_id, node_idx).await; + if result.is_err() { + return json_failed_resp_with_message( + StatusCode::INTERNAL_SERVER_ERROR, + result.err().unwrap().to_string(), + ); + } + let asset = result.unwrap(); + json_success_resp(&asset) +} + +async fn handler_get_assets_for_owner( + req: Request, +) -> Result, routerify_json_response::Error> { + let db: &Pool = req.data::>().unwrap(); + let owner = decode_b58_param(req.param("owner").unwrap()).unwrap(); + + let results = sqlx::query_as::<_, AssetDAO>(r#" + select a.msg as data, c.node_idx as index, a.owner, a.tree_id as tree , aso.authority as admin, a.leaf as hash, max(c.seq) as seq, c2.level as level from app_specific as a + join cl_items as c on c.tree = a.tree_id and c.hash = a.leaf + join app_specific_ownership aso on a.tree_id = aso.tree_id + join cl_items as c2 on c2.tree = c.tree + where a.owner = $1 and c2.node_idx = 1 + group by c.node_idx, a.msg, a.owner, a.tree_id, aso.authority, a.leaf, c2.level + order by seq"# + ) + .bind(owner) + .fetch_all(db).await; + if results.is_err() { + return json_failed_resp_with_message( + StatusCode::INTERNAL_SERVER_ERROR, + results.err().unwrap().to_string(), + ); + } + let assets = results.unwrap(); + json_success_resp(&asset_list_to_view(assets)) +} + +async fn handle_get_tree( + req: Request, +) -> Result, routerify_json_response::Error> { + let db: &Pool = req.data::>().unwrap(); + let tree_id = decode_b58_param(req.param("tree_id").unwrap()).unwrap(); + let results = sqlx::query_as::<_, NodeDAO>("select distinct on (node_idx) node_idx, level, hash, seq from cl_items where tree = $1 order by node_idx, seq, level desc") + .bind(tree_id) + .fetch_all(db).await; + if results.is_err() { + return json_failed_resp_with_message( + StatusCode::INTERNAL_SERVER_ERROR, + results.err().unwrap().to_string(), + ); + } + json_success_resp(&node_list_to_view(results.unwrap())) +} + +async fn handle_get_root( + req: Request, +) -> Result, routerify_json_response::Error> { + let db: &Pool = req.data::>().unwrap(); + let tree_id = decode_b58_param(req.param("tree_id").unwrap()).unwrap(); + let result = get_root(&db, &tree_id).await; + if result.is_err() { + return json_failed_resp_with_message( + StatusCode::INTERNAL_SERVER_ERROR, + result.err().unwrap().to_string(), + ); + } + json_success_resp(&result.unwrap()) +} + +async fn handle_get_proof( + req: Request, +) -> Result, routerify_json_response::Error> { + let db: &Pool = req.data::>().unwrap(); + let tree_id = decode_b58_param(req.param("tree_id").unwrap()).unwrap(); + let index = req.param("index").unwrap().parse::().unwrap(); + + let proof = get_proof_and_root(db, &tree_id, index).await; + if proof.is_err() { + return if let ApiError::ResponseError { status, msg } = proof.err().unwrap() { + json_failed_resp_with_message(status, msg) + } else { + json_failed_resp(StatusCode::INTERNAL_SERVER_ERROR) + }; + } + let proof_unwrapped = proof.unwrap(); + json_success_resp(&proof_unwrapped[..proof_unwrapped.len() - 1].to_vec()) +} + +async fn handle_get_asset_proof( + req: Request, +) -> Result, routerify_json_response::Error> { + let db: &Pool = req.data::>().unwrap(); + let tree_id = decode_b58_param(req.param("tree_id").unwrap()).unwrap(); + + let leaf_idx = req.param("index").unwrap().parse::().unwrap(); + + let tree_height = get_height(db, &tree_id).await.unwrap(); + let node_idx = leaf_idx_to_node_idx(leaf_idx, tree_height); + let proof: Result, ApiError> = get_proof_and_root(db, &tree_id, node_idx) + .await + .map(|p| p.iter().map(|node| node.hash.clone()).collect()); + + let result = get_asset(db, &tree_id, node_idx).await; + let string: String; + if result.is_err() { + println!("Could not find asset...\n"); + let empty_leaf = empty_node(0).to_vec(); + string = bs58::encode(empty_leaf).into_string(); + } else { + string = result.unwrap().hash.clone(); + } + + let asset_proof = proof.map(|p| AssetProof { + hash: string, + root: p[p.len() - 1].clone(), + proof: p[..p.len() - 1].to_vec(), + }); + + if asset_proof.is_err() { + println!("Asset proof is error :/ \n"); + return if let ApiError::ResponseError { status, msg } = asset_proof.err().unwrap() { + json_failed_resp_with_message(status, msg) + } else { + json_failed_resp(StatusCode::INTERNAL_SERVER_ERROR) + }; + } + + json_success_resp(&asset_proof.unwrap()) +} + +async fn get_height(db: &Pool, tree_id: &Vec) -> Result { + let result = sqlx::query_as::<_, Level>( + "select level from cl_items where node_idx = 1 AND tree = $1 order by seq desc limit 1", + ) + .bind(tree_id) + .fetch_one(db) + .await; + + result + .map(|r| r.level as u32) + .map_err(|e| ApiError::ResponseError { + status: StatusCode::INTERNAL_SERVER_ERROR, + msg: e.to_string(), + }) +} + +async fn get_root(db: &Pool, tree_id: &Vec) -> Result { + let result = sqlx::query_as::<_, Root>( + "select hash from cl_items where node_idx = 1 AND tree = $1 order by seq desc limit 1", + ) + .bind(tree_id) + .fetch_one(db) + .await; + + result + .map(|r| bs58::encode(r.hash).into_string()) + .map_err(|e| ApiError::ResponseError { + status: StatusCode::INTERNAL_SERVER_ERROR, + msg: e.to_string(), + }) +} + +async fn get_asset( + db: &Pool, + tree_id: &Vec, + node_idx: i64, +) -> Result { + let result = sqlx::query_as::<_, AssetDAO>(r#" + select a.msg as data, c.node_idx as index, a.owner, a.tree_id as tree , aso.authority as admin, a.leaf as hash, max(c.seq) as seq, c2.level as level from app_specific as a + join cl_items as c on c.tree = a.tree_id and c.hash = a.leaf + join app_specific_ownership aso on a.tree_id = aso.tree_id + join cl_items as c2 on c2.tree = c.tree + where a.tree_id = $1 AND c.node_idx = $2 and c2.node_idx = 1 + group by c.node_idx, a.msg, a.owner, a.tree_id, aso.authority, a.leaf, c2.level + order by seq + limit 1 + "# + ) + .bind(&tree_id) + .bind(&node_idx) + .fetch_one(db).await; + result + .map(asset_to_view) + .map_err(|e| ApiError::ResponseError { + status: StatusCode::INTERNAL_SERVER_ERROR, + msg: e.to_string(), + }) +} + +async fn get_proof_and_root( + db: &Pool, + tree_id: &Vec, + index: i64, +) -> Result, ApiError> { + let nodes = get_required_nodes_for_proof(index); + let expected_proof_size = nodes.len(); + let results = sqlx::query_as::<_, NodeDAO>( + r#" + select distinct on (node_idx) node_idx, hash, level, max(seq) as seq + from cl_items + where node_idx = ANY ($1) and tree = $2 + and seq <= ( + select max(seq) as seq + from cl_items + where node_idx = 1 and tree = $2 + ) + group by seq, node_idx, level, hash + order by node_idx desc, seq desc + "#, + ) + .bind(&nodes.as_slice()) + .bind(&tree_id) + .fetch_all(db) + .await; + let nodes_from_db = results.unwrap(); + let mut final_node_list: Vec = vec![NodeView::default(); expected_proof_size]; + if nodes_from_db.len() > expected_proof_size { + return Err(ApiError::ResponseError { + status: StatusCode::INTERNAL_SERVER_ERROR, + msg: "Tree Corrupted".to_string(), + }); + } + if nodes_from_db.len() != expected_proof_size { + for returned in nodes_from_db.iter() { + let node_view = node_to_view(returned.to_owned()); + println!( + "Node from db: {} {} {}", + &node_view.level, &node_view.hash, &node_view.index + ); + if returned.level < final_node_list.len().try_into().unwrap() { + final_node_list[returned.level as usize] = node_view; + } + } + for (i, (n, nin)) in final_node_list.iter_mut().zip(nodes).enumerate() { + if *n == NodeView::default() { + *n = node_to_view(make_empty_node(i as i64, nin)); + } + } + } else { + final_node_list = node_list_to_view(nodes_from_db); + } + Ok(final_node_list) +} + +fn get_required_nodes_for_proof(index: i64) -> Vec { + let mut indexes = vec![]; + let mut idx = index; + while idx > 1 { + if idx % 2 == 0 { + indexes.push(idx + 1) + } else { + indexes.push(idx - 1) + } + idx >>= 1 + } + indexes.push(1); + println!("nodes {:?}", indexes); + return indexes; +} + +fn decode_b58_param(param: &String) -> Result, ApiError> { + let pub_key = Pubkey::from_str(&*param).map_err(|e| { + println!("{}", e.to_string()); + ApiError::ParameterInvalid + })?; + Ok(pub_key.to_bytes().to_vec()) +} + +fn make_empty_node(lvl: i64, node_index: i64) -> NodeDAO { + NodeDAO { + node_idx: node_index, + level: lvl, + hash: empty_node(lvl as u32).to_vec(), + seq: 0, + } +} + +fn router(db: Pool) -> Router { + Router::builder() + .middleware(Middleware::pre(logger)) + .middleware(Middleware::post(|mut res| async move { + let headers = res.headers_mut(); + headers.insert( + header::ACCESS_CONTROL_ALLOW_ORIGIN, + HeaderValue::from_static("*"), + ); + headers.insert( + header::ACCESS_CONTROL_ALLOW_METHODS, + HeaderValue::from_static("*"), + ); + headers.insert( + header::ACCESS_CONTROL_ALLOW_HEADERS, + HeaderValue::from_static("*"), + ); + headers.insert( + header::ACCESS_CONTROL_EXPOSE_HEADERS, + HeaderValue::from_static("*"), + ); + Ok(res) + })) + .data(db) + .get("/assets/:tree_id/:index/proof", handle_get_asset_proof) + .get("/assets/:tree_id/:index", handle_get_asset) + .get("/owner/:owner/assets", handler_get_assets_for_owner) + .get("/tree/:tree_id", handle_get_tree) + .get("/root/:tree_id", handle_get_root) + .get("/proof/:tree_id/:index", handle_get_proof) + .build() + .unwrap() +} + +#[derive(Default)] +struct AppEvent { + op: String, + message: String, + leaf: String, + owner: String, + tree_id: String, +} + +#[tokio::main] +async fn main() { + let main_pool = PgPoolOptions::new() + .max_connections(5) + .connect("postgres://solana:solana@db/solana") + .await + .unwrap(); + let router = router(main_pool); + // Create a Service from the router above to handle incoming requests. + let service = RouterService::new(router).unwrap(); + // The address on which the server will be listening. + let addr = SocketAddr::from(([0, 0, 0, 0], 9090)); + // Create a server by passing the created service to `.serve` method. + let server = Server::bind(&addr).serve(service); + + println!("App is running on: {}", addr); + if let Err(err) = server.await { + eprintln!("Server error: {}", err); + } +} diff --git a/nft_ingester/.dockerignore b/nft_ingester/.dockerignore new file mode 100644 index 000000000..1de565933 --- /dev/null +++ b/nft_ingester/.dockerignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/nft_ingester/Cargo.lock b/nft_ingester/Cargo.lock new file mode 100644 index 000000000..22b2d34eb --- /dev/null +++ b/nft_ingester/Cargo.lock @@ -0,0 +1,5052 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher 0.3.0", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" +dependencies = [ + "aead", + "aes", + "cipher 0.3.0", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.6", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "anchor-attribute-access-control" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.39", + "quote 1.0.18", + "regex", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "bs58 0.4.0", + "proc-macro2 1.0.39", + "quote 1.0.18", + "rustversion", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "proc-macro2 1.0.39", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-interface" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "heck 0.3.3", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "anchor-attribute-state" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "anchor-client" +version = "0.24.2" +dependencies = [ + "anchor-lang", + "anyhow", + "regex", + "serde", + "solana-account-decoder", + "solana-client", + "solana-sdk", + "thiserror", + "url", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.24.2" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "anchor-lang" +version = "0.24.2" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-interface", + "anchor-attribute-program", + "anchor-attribute-state", + "anchor-derive-accounts", + "arrayref", + "base64 0.13.0", + "bincode", + "borsh", + "bytemuck", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-spl" +version = "0.24.2" +dependencies = [ + "anchor-lang", + "solana-program", + "spl-associated-token-account", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "anchor-syn" +version = "0.24.2" +dependencies = [ + "anyhow", + "bs58 0.3.1", + "heck 0.3.3", + "proc-macro2 1.0.39", + "proc-macro2-diagnostics", + "quote 1.0.18", + "serde", + "serde_json", + "sha2 0.9.9", + "syn 1.0.95", + "thiserror", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-trait" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "atoi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64ct" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2 1.0.39", + "syn 1.0.95", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "bs58" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bubblegum" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bytemuck", + "gummyroll", + "mpl-token-metadata", + "spl-associated-token-account", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdead85bdec19c194affaeeb670c0e41fe23de31459efd1c174d049269cf02cc" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "caps" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61bf7211aad104ce2769ec05efcdfabf85ee84ac92461d142f22cf8badd0e54c" +dependencies = [ + "errno", + "libc", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "serde", + "time 0.1.43", + "winapi", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cipher" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "combine" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948" +dependencies = [ + "bytes", + "futures-core", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util 0.7.2", +] + +[[package]] +name = "console" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "regex", + "terminal_size", + "unicode-width", + "winapi", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501a375961cef1a0d44767200e66e4a559283097e91d0730b1d75dfb2f8a1494" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +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 = "csv" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +dependencies = [ + "bstr", + "csv-core", + "itoa 0.4.8", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher 0.3.0", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" +dependencies = [ + "cfg-if", + "num_cpus", + "rayon", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "dialoguer" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c8ae48e400addc32a8710c8d62d55cb84249a7d58ac4cd959daecfbaddc545" +dependencies = [ + "console", + "tempfile", + "zeroize", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] +name = "dir-diff" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2860407d7d7e2e004bb2128510ad9e8d669e76fa005ccf567977b5d71b8b4a0b" +dependencies = [ + "walkdir", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlopen" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e80ad39f814a9abe68583cd50a2d45c8a67561c3361ab8da240587dda80937" +dependencies = [ + "dlopen_derive", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "dlopen_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f236d9e1b1fbd81cea0f9cbdc8dcc7e8ebcd80e6659cd7cb2ad5f6c05946c581" +dependencies = [ + "libc", + "quote 0.6.13", + "syn 0.15.44", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.2", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "filetime" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "flatbuffers" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b428b715fdbdd1c364b84573b5fdc0f84f8e423661b9f398735278bc7f2b6a" +dependencies = [ + "bitflags", + "smallvec", + "thiserror", +] + +[[package]] +name = "flate2" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot 0.11.2", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.2+wasi-snapshot-preview1", +] + +[[package]] +name = "gummyroll" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "bytemuck", +] + +[[package]] +name = "gummyroll-crud" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "bytemuck", + "gummyroll", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.2", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashlink" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "histogram" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cb882ccb290b8646e554b157ab0b71e64e8d5bef775cd66b6531e52d302669" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "http" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.2", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "hyper" +version = "0.14.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa 1.0.2", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls 0.20.6", + "tokio", + "tokio-rustls 0.23.4", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.3", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "index_list" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9d968042a4902e08810946fc7cd5851eb75e80301342305af755ca06cb82ce" + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indicatif" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d207dc617c7a380ab07ff572a6e52fa202a2a8f355860ac9c38e23f8196be1b" +dependencies = [ + "console", + "lazy_static", + "number_prefix", + "regex", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc-core" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" +dependencies = [ + "futures", + "futures-executor", + "futures-util", + "log", + "serde", + "serde_derive", + "serde_json", +] + +[[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 = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lru" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8015d95cb7b2ddd3c0d32ca38283ceb1eea09b4713ee380bceb942d85a244228" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "md-5" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memmap2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.3", + "zeroize", +] + +[[package]] +name = "messenger" +version = "0.1.0" +dependencies = [ + "plerkle-serialization", + "solana-geyser-plugin-interface", +] + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "mpl-token-metadata" +version = "1.2.5" +source = "git+https://github.com/jarry-xiao/metaplex-program-library?rev=7e2810a#7e2810afa008b53601f4ef515aa5361756a69ae8" +dependencies = [ + "arrayref", + "borsh", + "mpl-token-vault", + "num-derive", + "num-traits", + "solana-program", + "spl-associated-token-account", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token-2022 0.2.0 (git+https://github.com/solana-labs/solana-program-library?rev=7f3cad3)", + "thiserror", +] + +[[package]] +name = "mpl-token-vault" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade4ef15bc06a6033076c4ff28cba9b42521df5ec61211d6f419415ace2746a" +dependencies = [ + "borsh", + "num-derive", + "num-traits", + "solana-program", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nft-ingester" +version = "0.1.0" +dependencies = [ + "anchor-client", + "base64 0.13.0", + "bs58 0.4.0", + "bubblegum", + "csv", + "flatbuffers", + "futures-util", + "gummyroll", + "gummyroll-crud", + "hex", + "hyper", + "lazy_static", + "messenger", + "num-integer", + "plerkle", + "plerkle-serialization", + "redis", + "regex", + "reqwest", + "routerify", + "routerify-json-response", + "serde", + "solana-sdk", + "sqlx", + "thiserror", + "tokio", + "tokio-postgres", + "uuid 1.1.0", +] + +[[package]] +name = "nix" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +dependencies = [ + "bitflags", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[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 = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "openssl" +version = "0.10.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5fd19fb3e0a8191c1e34935718976a3e70c112ab9a24af6d7cadccd9d90bc0" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "ouroboros" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71643f290d126e18ac2598876d01e1d57aed164afc78fdb6e2a0c6589a1f6662" +dependencies = [ + "aliasable", + "ouroboros_macro", + "stable_deref_trait", +] + +[[package]] +name = "ouroboros_macro" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9a247206016d424fe8497bc611e510887af5c261fbbf977877c4bb55ca4d82" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.3", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "pbkdf2" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7" +dependencies = [ + "digest 0.10.3", +] + +[[package]] +name = "pem" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947" +dependencies = [ + "base64 0.13.0", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "plerkle" +version = "1.10.3" +dependencies = [ + "anchor-client", + "async-trait", + "base64 0.13.0", + "bs58 0.4.0", + "bytemuck", + "flatbuffers", + "gummyroll", + "gummyroll-crud", + "hex", + "lazy_static", + "log", + "messenger", + "num-integer", + "plerkle-serialization", + "redis", + "regex", + "serde", + "serde_derive", + "serde_json", + "solana-geyser-plugin-interface", + "solana-logger", + "solana-measure", + "solana-metrics", + "solana-runtime", + "solana-sdk", + "solana-transaction-status", + "thiserror", +] + +[[package]] +name = "plerkle-serialization" +version = "0.1.0" +dependencies = [ + "flatbuffers", +] + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "postgres-protocol" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "878c6cbf956e03af9aa8204b407b9cbf47c072164800aa918c516cd4b056c50c" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes", + "fallible-iterator", + "hmac 0.12.1", + "md-5", + "memchr", + "rand 0.8.5", + "sha2 0.10.2", + "stringprep", +] + +[[package]] +name = "postgres-types" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd6e8b7189a73169290e89bd24c771071f1012d8fe6f738f5226531f0b03d89" +dependencies = [ + "bytes", + "fallible-iterator", + "postgres-protocol", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +dependencies = [ + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", +] + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", + "version_check", + "yansi", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "quinn" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7542006acd6e057ff632307d219954c44048f818898da03113d6c0086bfddd9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "fxhash", + "quinn-proto", + "quinn-udp", + "rustls 0.20.6", + "thiserror", + "tokio", + "tracing", + "webpki 0.22.0", +] + +[[package]] +name = "quinn-proto" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a13a5c0a674c1ce7150c9df7bc4a1e46c2fbbe7c710f56c0dc78b1a810e779e" +dependencies = [ + "bytes", + "fxhash", + "rand 0.8.5", + "ring", + "rustls 0.20.6", + "rustls-native-certs", + "rustls-pemfile 0.2.1", + "slab", + "thiserror", + "tinyvec", + "tracing", + "webpki 0.22.0", +] + +[[package]] +name = "quinn-udp" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3149f7237331015f1a6adf065c397d1be71e032fcf110ba41da52e7926b882f" +dependencies = [ + "futures-util", + "libc", + "quinn-proto", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2 1.0.39", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[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 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.6", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.3", +] + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rcgen" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fa2d386df8533b02184941c76ae2e0d0c1d053f5d43339169d80f21275fc5e" +dependencies = [ + "pem", + "ring", + "time 0.3.9", + "yasna", +] + +[[package]] +name = "redis" +version = "0.21.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852" +dependencies = [ + "async-trait", + "bytes", + "combine", + "dtoa", + "futures-util", + "itoa 0.4.8", + "percent-encoding", + "pin-project-lite", + "sha1", + "tokio", + "tokio-util 0.6.10", + "url", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.6", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[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 = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64 0.13.0", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "hyper-tls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls 0.20.6", + "rustls-pemfile 0.3.0", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.23.4", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 0.22.3", + "winreg", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "routerify" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496c1d3718081c45ba9c31fbfc07417900aa96f4070ff90dc29961836b7a9945" +dependencies = [ + "http", + "hyper", + "lazy_static", + "percent-encoding", + "regex", +] + +[[package]] +name = "routerify-json-response" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "937415f257a9e3ee8747cdca1e19fd10a5ae6960114fb24d6cd66ec67e8922b2" +dependencies = [ + "http", + "http-body", + "serde", + "serde_json", +] + +[[package]] +name = "rpassword" +version = "6.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956" +dependencies = [ + "libc", + "serde", + "serde_json", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.0", + "log", + "ring", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.0", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64 0.13.0", +] + +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64 0.13.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +dependencies = [ + "base64 0.13.0", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212e73464ebcde48d723aa02eb270ba62eff38a9b732df31f33f1b4e145f3a54" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa 1.0.2", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.2", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707d15895415db6628332b737c838b88c598522e4dc70647e59b72312924aebc" +dependencies = [ + "indexmap", + "ryu", + "serde", + "yaml-rust", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha1" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" +dependencies = [ + "sha1_smol", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881bf8156c87b6301fc5ca6b27f11eeb2761224c7081e69b409d5a1951a70c86" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "solana-account-decoder" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "858c2e5bb177b10080e6c8bdfe4667f4ee744c85d32ed09cda46ac147389a177" +dependencies = [ + "Inflector", + "base64 0.13.0", + "bincode", + "bs58 0.4.0", + "bv", + "lazy_static", + "serde", + "serde_derive", + "serde_json", + "solana-config-program", + "solana-sdk", + "solana-vote-program", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token-2022 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-address-lookup-table-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5801ae86e0656c1d30b05e42ecb76eab19b41ddfb822dc91e26a1a05bb38803" +dependencies = [ + "bincode", + "bytemuck", + "log", + "num-derive", + "num-traits", + "rustc_version", + "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-program", + "solana-program-runtime", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-bucket-map" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc5b288739df7e854fd040b6734bc65f8d4ad2b1050fb8e92103038e807b5825" +dependencies = [ + "log", + "memmap2", + "modular-bitfield", + "rand 0.7.3", + "solana-measure", + "solana-sdk", + "tempfile", +] + +[[package]] +name = "solana-clap-utils" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2830353486e61e6b5e299ca1d7d6da0ece894c527da49fb2a02dd779d88899" +dependencies = [ + "chrono", + "clap", + "rpassword", + "solana-perf", + "solana-remote-wallet", + "solana-sdk", + "thiserror", + "tiny-bip39", + "uriparse", + "url", +] + +[[package]] +name = "solana-cli-config" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20e8b79eda21fd109d9b836f8a9827c79ebb80507bfc43d627ce2e15e7fdadab" +dependencies = [ + "dirs-next", + "lazy_static", + "serde", + "serde_derive", + "serde_yaml", + "solana-clap-utils", + "solana-sdk", + "url", +] + +[[package]] +name = "solana-client" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978b13e7b5bac98307181be0f8c08b4ff9893d70d005b2bece3bcbc5a6b7a0b2" +dependencies = [ + "async-mutex", + "async-trait", + "base64 0.13.0", + "bincode", + "bs58 0.4.0", + "bytes", + "clap", + "crossbeam-channel", + "futures", + "futures-util", + "indicatif", + "itertools", + "jsonrpc-core", + "lazy_static", + "log", + "lru", + "quinn", + "quinn-proto", + "rand 0.7.3", + "rand_chacha 0.2.2", + "rayon", + "reqwest", + "rustls 0.20.6", + "semver", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-clap-utils", + "solana-faucet", + "solana-measure", + "solana-metrics", + "solana-net-utils", + "solana-sdk", + "solana-streamer", + "solana-transaction-status", + "solana-version", + "solana-vote-program", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tungstenite", + "url", +] + +[[package]] +name = "solana-compute-budget-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46469e8388ecba1f1fd9c5c0757ba34420bda986abd7ae7ca52cc83e72f7c2c4" +dependencies = [ + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-config-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42be74902c9652250bc08e404a5b7dbfc74ead41ead8e91e2da91d6af4e6747" +dependencies = [ + "bincode", + "chrono", + "serde", + "serde_derive", + "solana-program-runtime", + "solana-sdk", +] + +[[package]] +name = "solana-faucet" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5247b57a9c74ce5e3210a20b7a84b7fed52800b818b57758d7a297c21c931ed" +dependencies = [ + "bincode", + "byteorder", + "clap", + "crossbeam-channel", + "log", + "serde", + "serde_derive", + "solana-clap-utils", + "solana-cli-config", + "solana-logger", + "solana-metrics", + "solana-sdk", + "solana-version", + "spl-memo", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-frozen-abi" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7299c2ca50bd2d8a5b4a8043e4817892b5e700345234a31adc5b4c1208a32283" +dependencies = [ + "bs58 0.4.0", + "bv", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "sha2 0.10.2", + "solana-frozen-abi-macro", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726d2fbe5b1b21cb8a81b8c3c1d1aca32bfcfd795f92536d8ff3e66e2e51df8" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "rustc_version", + "syn 1.0.95", +] + +[[package]] +name = "solana-geyser-plugin-interface" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b43412ec4549c2dae8b57b021259f3fd04ebf27c35853821f924fa7737c26b" +dependencies = [ + "log", + "solana-sdk", + "solana-transaction-status", + "thiserror", +] + +[[package]] +name = "solana-logger" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6aeaa4145cc77bbfab151a233b14e611a82747fd2eee611a52e07f582544d6" +dependencies = [ + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "solana-measure" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cbe2748db2e47f8153c0d58932190c1e3928ec74c7e188332110aa8e5b92aad" +dependencies = [ + "log", + "solana-sdk", +] + +[[package]] +name = "solana-metrics" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00ce23395483222f5f98249477fd410a5f40760ad03de1c3d673cfb52b6a0276" +dependencies = [ + "crossbeam-channel", + "gethostname", + "lazy_static", + "log", + "reqwest", + "solana-sdk", +] + +[[package]] +name = "solana-net-utils" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f87324d80eb16319dde74483bdaf6b26c7b65668f878f3f67edb395a7573774" +dependencies = [ + "bincode", + "clap", + "crossbeam-channel", + "log", + "nix", + "rand 0.7.3", + "serde", + "serde_derive", + "socket2", + "solana-logger", + "solana-sdk", + "solana-version", + "tokio", + "url", +] + +[[package]] +name = "solana-perf" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3007ddf09cddf8d922c07e40d15ed1833c4f3c2f6e0e90cf12b5b54d117c7070" +dependencies = [ + "ahash", + "bincode", + "bv", + "caps", + "curve25519-dalek", + "dlopen", + "dlopen_derive", + "fnv", + "lazy_static", + "libc", + "log", + "nix", + "rand 0.7.3", + "rayon", + "serde", + "solana-metrics", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-vote-program", +] + +[[package]] +name = "solana-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6425f7248eb69806ae5e8c193a5b7dcfc764516d2f0d354cdf80bacaf422a188" +dependencies = [ + "base64 0.13.0", + "bincode", + "bitflags", + "blake3", + "borsh", + "borsh-derive", + "bs58 0.4.0", + "bv", + "bytemuck", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.1.16", + "itertools", + "js-sys", + "lazy_static", + "libsecp256k1", + "log", + "num-derive", + "num-traits", + "parking_lot 0.12.0", + "rand 0.7.3", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "sha2 0.10.2", + "sha3 0.10.1", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "wasm-bindgen", +] + +[[package]] +name = "solana-program-runtime" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb8867c22152b484a6ce400032a8fc13d9615720064f8714480e2dff400878b1" +dependencies = [ + "base64 0.13.0", + "bincode", + "enum-iterator", + "itertools", + "libc", + "libloading", + "log", + "num-derive", + "num-traits", + "rustc_version", + "serde", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-measure", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-rayon-threadlimit" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beb8dc91d2983601099a1a9a41ba0993bc91513fb52a75f329781205880b819d" +dependencies = [ + "lazy_static", + "num_cpus", +] + +[[package]] +name = "solana-remote-wallet" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c99471e63a0fc3e91a6ea282dba0ba473180aba1410579aaacc1e9c33dc7e37" +dependencies = [ + "console", + "dialoguer", + "log", + "num-derive", + "num-traits", + "parking_lot 0.12.0", + "qstring", + "semver", + "solana-sdk", + "thiserror", + "uriparse", +] + +[[package]] +name = "solana-runtime" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0983e25c84f89791e690d11e95e4e12f398b1cd772828943581daeb57a9d07b9" +dependencies = [ + "arrayref", + "bincode", + "blake3", + "bv", + "bytemuck", + "byteorder", + "bzip2", + "crossbeam-channel", + "dashmap", + "dir-diff", + "flate2", + "fnv", + "im", + "index_list", + "itertools", + "lazy_static", + "log", + "memmap2", + "num-derive", + "num-traits", + "num_cpus", + "ouroboros", + "rand 0.7.3", + "rayon", + "regex", + "rustc_version", + "serde", + "serde_derive", + "solana-address-lookup-table-program", + "solana-bucket-map", + "solana-compute-budget-program", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-measure", + "solana-metrics", + "solana-program-runtime", + "solana-rayon-threadlimit", + "solana-sdk", + "solana-stake-program", + "solana-vote-program", + "solana-zk-token-proof-program", + "solana-zk-token-sdk 1.10.10", + "symlink", + "tar", + "tempfile", + "thiserror", + "zstd", +] + +[[package]] +name = "solana-sdk" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ad83e1a502a53512e0e077fbaa76bf73b19e8789dc014c940077506e8fb7ac" +dependencies = [ + "assert_matches", + "base64 0.13.0", + "bincode", + "bitflags", + "borsh", + "bs58 0.4.0", + "bytemuck", + "byteorder", + "chrono", + "derivation-path", + "digest 0.10.3", + "ed25519-dalek", + "ed25519-dalek-bip32", + "generic-array", + "hmac 0.12.1", + "itertools", + "js-sys", + "lazy_static", + "libsecp256k1", + "log", + "memmap2", + "num-derive", + "num-traits", + "pbkdf2 0.10.1", + "qstring", + "rand 0.7.3", + "rand_chacha 0.2.2", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "sha3 0.10.1", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-program", + "solana-sdk-macro", + "thiserror", + "uriparse", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e117d4d001f4f3c1e568979da52d76a75d814344c1debf9febd10b5a571993" +dependencies = [ + "bs58 0.4.0", + "proc-macro2 1.0.39", + "quote 1.0.18", + "rustversion", + "syn 1.0.95", +] + +[[package]] +name = "solana-stake-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b949a717b8df04016ea878422e1e85f0dd5398666886766172733431aafbc39e" +dependencies = [ + "bincode", + "log", + "num-derive", + "num-traits", + "rustc_version", + "serde", + "serde_derive", + "solana-config-program", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-metrics", + "solana-program-runtime", + "solana-sdk", + "solana-vote-program", + "thiserror", +] + +[[package]] +name = "solana-streamer" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36baa4662f7f4813aacf0a5ff34917eac599fee8c3fc952c8174b93860454fa0" +dependencies = [ + "crossbeam-channel", + "futures-util", + "histogram", + "itertools", + "libc", + "log", + "nix", + "pem", + "pkcs8", + "quinn", + "rand 0.7.3", + "rcgen", + "rustls 0.20.6", + "solana-metrics", + "solana-perf", + "solana-sdk", + "thiserror", + "tokio", +] + +[[package]] +name = "solana-transaction-status" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba94f07da9762ecfd5c275349c626d4f5bafdcf6127bcac9d379644db667fda7" +dependencies = [ + "Inflector", + "base64 0.13.0", + "bincode", + "borsh", + "bs58 0.4.0", + "lazy_static", + "log", + "serde", + "serde_derive", + "serde_json", + "solana-account-decoder", + "solana-measure", + "solana-metrics", + "solana-runtime", + "solana-sdk", + "solana-vote-program", + "spl-associated-token-account", + "spl-memo", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "spl-token-2022 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + +[[package]] +name = "solana-version" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d6c6b96efc9d99a135e08f26b26a44aae808b4dbe698c8f189d696320758bd" +dependencies = [ + "log", + "rustc_version", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk", +] + +[[package]] +name = "solana-vote-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de708599b47b636dd7df359bd0d2e954a23fdbb8355b0c393ea54d09e484b99" +dependencies = [ + "bincode", + "log", + "num-derive", + "num-traits", + "rustc_version", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-metrics", + "solana-program-runtime", + "solana-sdk", + "thiserror", +] + +[[package]] +name = "solana-zk-token-proof-program" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eee14125af17031fd5b9903110d7e4c560f6156b1bcc3b9176f4c5a90820cc" +dependencies = [ + "bytemuck", + "getrandom 0.1.16", + "num-derive", + "num-traits", + "solana-program-runtime", + "solana-sdk", + "solana-zk-token-sdk 1.10.10", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9799d68a8e0c6e4a3dff62d9938666c5a710fa88dbd0627524e03695fdd8025b" +dependencies = [ + "aes-gcm-siv", + "arrayref", + "base64 0.13.0", + "bincode", + "bytemuck", + "byteorder", + "cipher 0.3.0", + "curve25519-dalek", + "getrandom 0.1.16", + "lazy_static", + "merlin", + "num-derive", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74b149253f9ed1afb68b3161b53b62b637d0dd7a3b328dffdc8bb5878d48358e" +dependencies = [ + "aes-gcm-siv", + "arrayref", + "base64 0.13.0", + "bincode", + "bytemuck", + "byteorder", + "cipher 0.3.0", + "curve25519-dalek", + "getrandom 0.1.16", + "lazy_static", + "merlin", + "num-derive", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "1.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b48cc8372b23949286546f1b73c3859e06b1d42b8d2c13162484d6ca4b35cb2" +dependencies = [ + "aes-gcm-siv", + "arrayref", + "base64 0.13.0", + "bincode", + "bytemuck", + "byteorder", + "cipher 0.4.3", + "curve25519-dalek", + "getrandom 0.1.16", + "lazy_static", + "merlin", + "num-derive", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "spl-associated-token-account" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b013067447a1396303ddfc294f36e3d260a32f8a16c501c295bcdc7de39b490" +dependencies = [ + "borsh", + "solana-program", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "spl-memo" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-token" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc67166ef99d10c18cb5e9c208901e6d8255c6513bb1f877977eba48e6cc4fb" +dependencies = [ + "arrayref", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token" +version = "3.3.0" +source = "git+https://github.com/solana-labs/solana-program-library?rev=7f3cad3#7f3cad3d11997211639ac2801c3529ae10e56974" +dependencies = [ + "arrayref", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce48c69350134e8678de5c0956a531b7de586b28eebdddc03211ceec0660983" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "solana-zk-token-sdk 0.8.1", + "spl-memo", + "spl-token 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "0.2.0" +source = "git+https://github.com/solana-labs/solana-program-library?rev=7f3cad3#7f3cad3d11997211639ac2801c3529ae10e56974" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive", + "num-traits", + "num_enum", + "solana-program", + "solana-zk-token-sdk 0.6.0", + "spl-token 3.3.0 (git+https://github.com/solana-labs/solana-program-library?rev=7f3cad3)", + "thiserror", +] + +[[package]] +name = "sqlformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7922be017ee70900be125523f38bdd644f4f06a1b16e8fa5a8ee8c34bffd4" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" +dependencies = [ + "ahash", + "atoi", + "base64 0.13.0", + "bitflags", + "byteorder", + "bytes", + "crc", + "crossbeam-queue", + "dirs", + "either", + "event-listener", + "futures-channel", + "futures-core", + "futures-intrusive", + "futures-util", + "hashlink", + "hex", + "hkdf", + "hmac 0.12.1", + "indexmap", + "itoa 1.0.2", + "libc", + "log", + "md-5", + "memchr", + "once_cell", + "paste", + "percent-encoding", + "rand 0.8.5", + "rustls 0.19.1", + "serde", + "serde_json", + "sha-1", + "sha2 0.10.2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "tokio-stream", + "url", + "uuid 0.8.2", + "webpki 0.21.4", + "webpki-roots 0.21.1", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" +dependencies = [ + "dotenv", + "either", + "heck 0.4.0", + "once_cell", + "proc-macro2 1.0.39", + "quote 1.0.18", + "sha2 0.10.2", + "sqlx-core", + "sqlx-rt", + "syn 1.0.95", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" +dependencies = [ + "once_cell", + "tokio", + "tokio-rustls 0.22.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", +] + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", + "unicode-xid 0.2.3", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "libc", + "num_threads", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "tokio" +version = "1.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "parking_lot 0.12.0", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-postgres" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19c88a47a23c5d2dc9ecd28fb38fba5fc7e5ddc1fe64488ec145076b0c71c8ae" +dependencies = [ + "async-trait", + "byteorder", + "bytes", + "fallible-iterator", + "futures", + "log", + "parking_lot 0.12.0", + "percent-encoding", + "phf", + "pin-project-lite", + "postgres-protocol", + "postgres-types", + "socket2", + "tokio", + "tokio-util 0.7.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls 0.19.1", + "tokio", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.6", + "tokio", + "webpki 0.22.0", +] + +[[package]] +name = "tokio-stream" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06cda1232a49558c46f8a504d5b93101d42c0bf7f911f12a105ba48168f821ae" +dependencies = [ + "futures-util", + "log", + "rustls 0.20.6", + "tokio", + "tokio-rustls 0.23.4", + "tungstenite", + "webpki 0.22.0", + "webpki-roots 0.22.3", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tower-service" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" + +[[package]] +name = "tracing" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "try-lock" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" + +[[package]] +name = "tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +dependencies = [ + "base64 0.13.0", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand 0.8.5", + "rustls 0.20.6", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki 0.22.0", + "webpki-roots 0.22.3", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "url" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "uuid" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93bbc61e655a4833cf400d0d15bf3649313422fa7572886ad6dab16d79886365" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[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.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote 1.0.18", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" +dependencies = [ + "webpki 0.21.4", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki 0.22.0", +] + +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yasna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346d34a236c9d3e5f3b9b74563f238f955bbd05fa0b8b4efa53c130c43982f4c" +dependencies = [ + "time 0.3.9", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2 1.0.39", + "quote 1.0.18", + "syn 1.0.95", + "synstructure", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.1+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +dependencies = [ + "cc", + "libc", +] diff --git a/nft_ingester/Cargo.toml b/nft_ingester/Cargo.toml new file mode 100644 index 000000000..4c1a1986e --- /dev/null +++ b/nft_ingester/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "nft_ingester" +version = "0.1.0" +edition = "2021" + +[dependencies] +hex = "0.4.3" +routerify = "3" +routerify-json-response = "3" +redis = { version = "0.21.5", features = ["aio", "tokio-comp", "streams"] } +futures-util = "0.3.0" +hyper = "0.14" +anchor-client = { path = "../deps/anchor/client" } +base64 = "0.13.0" +thiserror = "1.0.31" +serde_json = "1.0.81" +tokio = { version = "1.17.0", features = ["full"] } +sqlx = { version = "0.5.11", features = ["macros", "runtime-tokio-rustls", "postgres", "uuid", "offline", "json"] } +sea-orm = { git = "https://github.com/liberwang1013/sea-orm", branch = "insert-on-conflict", features = ["macros", "runtime-tokio-rustls", "sqlx-postgres", "with-chrono"] } +chrono = "0.4.19" +tokio-postgres = "0.7.5" +serde = "1.0.136" +bs58 = "0.4.0" +reqwest = "0.11.11" +csv = "1.1.6" +messenger = { path = "../messenger" } +flatbuffers = "2.1.2" +solana-sdk = { version = "=1.10.10" } +lazy_static = "1.4.0" +regex = "1.5.5" +plerkle-serialization = { path = "../plerkle_serialization" } +digital_asset_types = { path = "../digital_asset_types", features = ["json_types", "sql_types"] } +uuid = "1.0.0" +async-trait = "0.1.53" +num-traits = "0.2.15" +gummyroll = { path = "../contracts/programs/gummyroll", features = ["no-entrypoint"] } +bubblegum = { path = "../contracts/programs/bubblegum", features = ["no-entrypoint"] } +figment = { version = "0.10.6", features = ["env"] } +cadence = "0.29.0" +cadence-macros = "0.29.0" +solana-client = "1.10.10" +solana-transaction-status="1.10.10" + +[dependencies.num-integer] +version = "0.1.44" +default-features = false diff --git a/nft_ingester/rustfmt.toml b/nft_ingester/rustfmt.toml new file mode 100644 index 000000000..36c419bb3 --- /dev/null +++ b/nft_ingester/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" \ No newline at end of file diff --git a/nft_ingester/src/backfiller.rs b/nft_ingester/src/backfiller.rs new file mode 100644 index 000000000..8d5780d7c --- /dev/null +++ b/nft_ingester/src/backfiller.rs @@ -0,0 +1,728 @@ +//! Backfiller that fills gaps in trees by detecting gaps in sequence numbers +//! in the `backfill_items` table. Inspired by backfiller.ts/backfill.ts. +use { + chrono::Utc, + crate::{ + error::IngesterError, parsers::*, IngesterConfig, DATABASE_LISTENER_CHANNEL_KEY, + RPC_COMMITMENT_KEY, RPC_URL_KEY, + }, + digital_asset_types::dao::backfill_items, + flatbuffers::FlatBufferBuilder, + messenger::{Messenger, TRANSACTION_STREAM}, + plerkle_serialization::transaction_info_generated::transaction_info::{ + self, TransactionInfo, TransactionInfoArgs, + }, + sea_orm::{ + entity::*, + query::*, + TryGetableMany, + sea_query::{Expr, Query}, + DatabaseConnection, DbBackend, DbErr, FromQueryResult, SqlxPostgresConnector, + }, + solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcBlockConfig}, + solana_sdk::{ + commitment_config::{CommitmentConfig, CommitmentLevel}, + pubkey::Pubkey, + }, + solana_transaction_status::{ + EncodedConfirmedBlock, UiInstruction::Compiled, UiRawMessage, UiTransactionEncoding, + UiTransactionStatusMeta, + }, + sqlx::{self, postgres::PgListener, Pool, Postgres}, + std::str::FromStr, + tokio::time::{sleep, Duration}, +}; + +// Constants used for varying delays when failures occur. +const INITIAL_FAILURE_DELAY: u64 = 100; +const MAX_FAILURE_DELAY_MS: u64 = 10_000; + +// Account key used to determine if transaction is a simple vote. +const VOTE: &str = "Vote111111111111111111111111111111111111111"; + +/// Main public entry point for backfiller task. +pub async fn backfiller( + pool: Pool, + config: IngesterConfig, +) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + println!("Backfiller task running"); + + let mut backfiller = Backfiller::::new(pool, config).await; + backfiller.run().await; + }) +} + +/// Struct used when querying for unique trees. +#[derive(Debug, FromQueryResult)] +struct UniqueTree { + tree: Vec, +} + +/// Struct used when storing trees to backfill. +struct BackfillTree { + unique_tree: UniqueTree, + backfill_from_seq_1: bool, +} + +impl BackfillTree { + fn new(unique_tree: UniqueTree, backfill_from_seq_1: bool) -> Self { + Self { + unique_tree, + backfill_from_seq_1, + } + } +} + +/// Struct used when querying the max sequence number of a tree. +#[derive(Debug, FromQueryResult, Clone)] +struct MaxSeqItem { + seq: i64, +} + +/// Struct used when querying for items to backfill. +#[derive(Debug, FromQueryResult, Clone)] +struct SimpleBackfillItem { + seq: i64, + slot: i64, +} + +/// Struct used to store sequence number gap info for a given tree. +struct GapInfo { + prev: SimpleBackfillItem, + curr: SimpleBackfillItem, +} + +impl GapInfo { + fn new(prev: SimpleBackfillItem, curr: SimpleBackfillItem) -> Self { + Self { prev, curr } + } +} + +/// Main struct used for backfiller task. +struct Backfiller { + db: DatabaseConnection, + listener: PgListener, + rpc_client: RpcClient, + rpc_block_config: RpcBlockConfig, + messenger: T, + failure_delay: u64, +} + +impl Backfiller { + /// Create a new `Backfiller` struct. + async fn new(pool: Pool, config: IngesterConfig) -> Self { + // Create Sea ORM database connection used later for queries. + let db = SqlxPostgresConnector::from_sqlx_postgres_pool(pool.clone()); + + // Connect to database using sqlx and create PgListener. + let mut listener = sqlx::postgres::PgListener::connect_with(&pool.clone()) + .await + .map_err(|e| IngesterError::StorageListenerError { + msg: format!("Could not connect to db for PgListener {e}"), + }) + .unwrap(); + + // Get database listener channel. + let channel = config + .database_config + .get(&*DATABASE_LISTENER_CHANNEL_KEY) + .and_then(|u| u.clone().into_string()) + .ok_or(IngesterError::ConfigurationError { + msg: format!( + "Database listener channel missing: {}", + DATABASE_LISTENER_CHANNEL_KEY + ), + }) + .unwrap(); + + // Setup listener on channel. + listener + .listen(&channel) + .await + .map_err(|e| IngesterError::StorageListenerError { + msg: format!("Error listening to channel on backfill_items tbl {e}"), + }) + .unwrap(); + + // Get RPC URL. + let rpc_url = config + .rpc_config + .get(&*RPC_URL_KEY) + .and_then(|u| u.clone().into_string()) + .ok_or(IngesterError::ConfigurationError { + msg: format!("RPC URL missing: {}", RPC_URL_KEY), + }) + .unwrap(); + + // Get RPC commitment level. + let rpc_commitment_level = config + .rpc_config + .get(&*RPC_COMMITMENT_KEY) + .and_then(|v| v.as_str()) + .ok_or(IngesterError::ConfigurationError { + msg: format!("RPC commitment level missing: {}", RPC_COMMITMENT_KEY), + }) + .unwrap(); + + // Check if commitment level is valid and create `CommitmentConfig`. + let rpc_commitment = CommitmentConfig { + commitment: CommitmentLevel::from_str(rpc_commitment_level) + .map_err(|_| IngesterError::ConfigurationError { + msg: format!("Invalid RPC commitment level: {}", rpc_commitment_level), + }) + .unwrap(), + }; + + // Create `RpcBlockConfig` used when getting blocks from RPC provider. + let rpc_block_config = RpcBlockConfig { + encoding: Some(UiTransactionEncoding::Json), + commitment: Some(rpc_commitment), + ..RpcBlockConfig::default() + }; + + // Instantiate RPC client. + let rpc_client = RpcClient::new_with_commitment(rpc_url, rpc_commitment); + + // Instantiate messenger. + let mut messenger = T::new(config.messenger_config).await.unwrap(); + messenger.add_stream(TRANSACTION_STREAM).await; + messenger.set_buffer_size(TRANSACTION_STREAM, 5000).await; + + Self { + db, + listener, + rpc_client, + rpc_block_config, + messenger, + failure_delay: INITIAL_FAILURE_DELAY, + } + } + + /// Run the backfiller task. + async fn run(&mut self) { + // This is always looping, but if there are no trees to backfill, it will wait for a + // notification on the db listener channel before continuing. + loop { + match self.get_trees_to_backfill().await { + Ok(backfill_trees) => { + if backfill_trees.len() == 0 { + // If there are no trees to backfill, wait for a notification on the db + // listener channel. + let _notification = self.listener.recv().await.unwrap(); + } else { + println!("New trees to backfill: {}", backfill_trees.len()); + + // First just check if we can talk to an RPC provider. + match self.rpc_client.get_version().await { + Ok(version) => println!("RPC client version {version}"), + Err(err) => { + println!("RPC client error {err}"); + self.sleep_and_increase_delay().await; + continue; + } + } + + for backfill_tree in backfill_trees { + // Get the tree out of nested structs. + let tree = backfill_tree.unique_tree.tree; + let tree_str = bs58::encode(&tree).into_string(); + println!("Backfilling tree: {tree_str}"); + + // Call different methods based on whether tree needs to be backfilled + // completely from seq number 1 or just have any gaps in seq number + // filled. + let result = if backfill_tree.backfill_from_seq_1 { + self.backfill_tree_from_seq_1(&tree).await + } else { + self.fetch_and_plug_gaps(&tree).await + }; + + match result { + Ok(opt_max_seq) => { + if let Some(max_seq) = opt_max_seq { + // Debug. + println!("Successfully backfilled tree: {tree_str}"); + + // Only delete extra tree rows if fetching and plugging gaps worked. + if let Err(err) = self + .delete_extra_rows_and_mark_as_backfilled( + &tree, max_seq, + ) + .await + { + println!("Error deleting rows and marking as backfilled: {err}"); + self.sleep_and_increase_delay().await; + } else { + // Debug. + println!("Successfully deleted rows up to {max_seq}"); + self.reset_delay() + } + } else { + // Debug. + println!("Unexpected error, tree was in list, but no rows found for {tree_str}"); + self.sleep_and_increase_delay().await; + } + } + Err(err) => { + println!("Failed to fetch and plug gaps for {tree_str}"); + println!("{err}"); + self.sleep_and_increase_delay().await; + } + } + } + } + } + Err(err) => { + // Print error but keep trying. + println!("Could not get trees to backfill from db: {err}"); + self.sleep_and_increase_delay().await; + } + } + } + } + + async fn sleep_and_increase_delay(&mut self) { + sleep(Duration::from_millis(self.failure_delay)).await; + + // Increase failure delay up to `MAX_FAILURE_DELAY_MS`. + self.failure_delay = self.failure_delay.saturating_mul(2); + if self.failure_delay > MAX_FAILURE_DELAY_MS { + self.failure_delay = MAX_FAILURE_DELAY_MS; + } + } + + fn reset_delay(&mut self) { + self.failure_delay = INITIAL_FAILURE_DELAY; + } + + async fn get_trees_to_backfill(&self) -> Result, DbErr> { + // Get trees with the `force_chk` flag set. + let force_chk_trees = UniqueTree::find_by_statement(Statement::from_sql_and_values( + DbBackend::Postgres, + "SELECT DISTINCT backfill_items.tree FROM backfill_items\n\ + WHERE backfill_items.force_chk = TRUE", + vec![], + )) + .all(&self.db) + .await?; + + // Convert this Vec of `UniqueTree` to a Vec of `BackfillTree` (which contain extra info). + let mut trees: Vec = force_chk_trees + .into_iter() + .map(|tree| BackfillTree::new(tree, true)) + .collect(); + + // Get trees with multiple rows from `backfill_items` table. + let multi_row_trees = UniqueTree::find_by_statement(Statement::from_sql_and_values( + DbBackend::Postgres, + "SELECT backfill_items.tree FROM backfill_items\n\ + GROUP BY backfill_items.tree\n\ + HAVING COUNT(*) > 1", + vec![], + )) + .all(&self.db) + .await?; + + // Convert this Vec of `UniqueTree` to a Vec of `BackfillTree` (which contain extra info). + let mut multi_row_trees: Vec = multi_row_trees + .into_iter() + .map(|tree| BackfillTree::new(tree, false)) + .collect(); + + trees.append(&mut multi_row_trees); + + Ok(trees) + } + + async fn backfill_tree_from_seq_1(&self, tree: &[u8]) -> Result, IngesterError> { + //TODO implement gap filler that gap fills from sequence number 1. + //For now just return the max sequence number. + self.get_max_seq(tree).await.map_err(Into::into) + } + + async fn get_max_seq(&self, tree: &[u8]) -> Result, DbErr> { + let query = backfill_items::Entity::find() + .select_only() + .column(backfill_items::Column::Seq) + .filter(backfill_items::Column::Tree.eq(tree)) + .order_by_desc(backfill_items::Column::Seq) + .limit(1) + .build(DbBackend::Postgres); + + let start_seq_vec = MaxSeqItem::find_by_statement(query).all(&self.db).await?; + + Ok(start_seq_vec.last().map(|row| row.seq)) + } + + async fn clear_force_chk_flag(&self, tree: &[u8]) -> Result { + backfill_items::Entity::update_many() + .col_expr(backfill_items::Column::ForceChk, Expr::value(false)) + .filter(backfill_items::Column::Tree.eq(tree)) + .exec(&self.db) + .await + } + + // Similar to `fetchAndPlugGaps()` in `backfiller.ts`. + async fn fetch_and_plug_gaps(&mut self, tree: &[u8]) -> Result, IngesterError> { + let (opt_max_seq, gaps) = self.get_missing_data(tree).await?; + + // Similar to `plugGapsBatched()` in `backfiller.ts` (although not batched). + for gap in gaps.iter() { + // Similar to `plugGaps()` in `backfiller.ts`. + let _result = self.plug_gap(&gap, tree).await?; + } + + Ok(opt_max_seq) + } + + // Similar to `getMissingData()` in `db.ts`. + async fn get_missing_data(&self, tree: &[u8]) -> Result<(Option, Vec), DbErr> { + // Get the maximum sequence number that has been backfilled, and use + // that for the starting sequence number for backfilling. + let query = backfill_items::Entity::find() + .select_only() + .column(backfill_items::Column::Seq) + .filter(backfill_items::Column::Tree.eq(tree)) + .filter(backfill_items::Column::Backfilled.eq(true)) + .order_by_desc(backfill_items::Column::Seq) + .limit(1) + .build(DbBackend::Postgres); + + let start_seq_vec = MaxSeqItem::find_by_statement(query).all(&self.db).await?; + let start_seq = if let Some(seq) = start_seq_vec.last().map(|row| row.seq) { + seq + } else { + 0 + }; + + // Get all rows for the tree that have not yet been backfilled. + let mut query = backfill_items::Entity::find() + .select_only() + .column(backfill_items::Column::Seq) + .column(backfill_items::Column::Slot) + .filter(backfill_items::Column::Seq.gte(start_seq)) + .filter(backfill_items::Column::Tree.eq(tree)) + .order_by_asc(backfill_items::Column::Seq) + .build(DbBackend::Postgres); + + query.sql = query.sql.replace("SELECT", "SELECT DISTINCT"); + let rows = SimpleBackfillItem::find_by_statement(query) + .all(&self.db) + .await?; + let mut gaps = vec![]; + + // Look at each pair of subsequent rows, looking for a gap in sequence number. + for (prev, curr) in rows.iter().zip(rows.iter().skip(1)) { + if curr.seq == prev.seq { + let message = format!( + "Error in DB, identical sequence numbers with different slots: {}, {}", + prev.slot, curr.slot + ); + println!("{}", message); + return Err(DbErr::Custom(message)); + } else if curr.seq - prev.seq > 1 { + gaps.push(GapInfo::new(prev.clone(), curr.clone())); + } + } + + // Get the max sequence number if any rows were returned from the query. + let opt_max_seq = rows.last().map(|row| row.seq); + + Ok((opt_max_seq, gaps)) + } + + // Similar to `plugGaps()` in `backfiller.ts`. + async fn plug_gap(&mut self, gap: &GapInfo, tree: &[u8]) -> Result<(), IngesterError> { + // TODO: This needs to make sure all slots are available otherwise it will partially + // fail and redo the whole backfill process. So for now checking the max block before + // looping as a quick workaround. + let _ = self + .rpc_client + .get_block(gap.curr.slot as u64) + .await + .map_err(|e| IngesterError::RpcGetDataError(e.to_string()))?; + + for slot in gap.prev.slot..gap.curr.slot { + let block_data = EncodedConfirmedBlock::from( + self.rpc_client + .get_block_with_config(slot as u64, self.rpc_block_config) + .await + .map_err(|e| IngesterError::RpcGetDataError(e.to_string()))?, + ); + + // Debug. + println!("num txs: {}", block_data.transactions.len()); + for tx in block_data.transactions { + // Debug. + //println!("TX from RPC provider:"); + //println!("{:#?}", tx); + + // See if transaction has an error. + let meta = if let Some(meta) = &tx.meta { + if let Some(err) = &meta.err { + println!("Transaction has error: {err}"); + continue; + } + meta + } else { + println!("Unexpected, EncodedTransactionWithStatusMeta struct has no metadata"); + continue; + }; + + // Get `UiTransaction` out of `EncodedTransactionWithStatusMeta`. + let ui_transaction = match tx.transaction { + solana_transaction_status::EncodedTransaction::Json(ref ui_transaction) => { + ui_transaction + } + _ => { + return Err(IngesterError::RpcDataUnsupportedFormat( + "EncodedTransaction".to_string(), + )); + } + }; + + // See if transaction is a vote. + let ui_raw_message = match &ui_transaction.message { + solana_transaction_status::UiMessage::Raw(ui_raw_message) => { + if ui_raw_message.account_keys.iter().any(|key| key == VOTE) { + // Debug. + println!("Skipping vote transaction"); + continue; + } else { + ui_raw_message + } + } + _ => { + return Err(IngesterError::RpcDataUnsupportedFormat( + "UiMessage".to_string(), + )); + } + }; + + // Filter out transactions that don't have to do with the tree we are interested in or + // the Bubblegum program. + let tree = bs58::encode(tree).into_string(); + let bubblegum = BubblegumProgramID().to_string(); + if ui_raw_message + .account_keys + .iter() + .all(|pk| *pk != tree && *pk != bubblegum) + { + // Debug. + println!("Skipping tx unrelated to tree or bubblegum PID"); + continue; + } + + // Debug. + println!("Serializing transaction"); + // Serialize data. + let builder = FlatBufferBuilder::new(); + let builder = serialize_transaction( + builder, + &meta, + &ui_raw_message, + slot.try_into().unwrap(), + )?; + + // Debug. + println!("Putting data into Redis"); + // Put data into Redis. + let _ = self + .messenger + .send(TRANSACTION_STREAM, builder.finished_data()) + .await?; + } + } + + Ok(()) + } + + async fn delete_extra_rows_and_mark_as_backfilled( + &self, + tree: &[u8], + max_seq: i64, + ) -> Result<(), DbErr> { + // Debug. + let test_items = backfill_items::Entity::find() + .filter(backfill_items::Column::Tree.eq(tree)) + .all(&self.db) + .await?; + println!("Count of items before delete: {}", test_items.len()); + for item in test_items { + println!("Seq ID {}", item.seq); + } + + // Delete all rows in the `backfill_items` table for a specified tree, except for the row with + // the caller-specified max seq number. One row for each tree must remain so that gaps can be + // detected after subsequent inserts. + backfill_items::Entity::delete_many() + .filter(backfill_items::Column::Tree.eq(tree)) + .filter(backfill_items::Column::Seq.ne(max_seq)) + .exec(&self.db) + .await?; + + // Remove any duplicates that have the caller-specified max seq number. This happens when + // a transaction that was already handled is replayed during backfilling. + let items = backfill_items::Entity::find() + .filter(backfill_items::Column::Tree.eq(tree)) + .filter(backfill_items::Column::Seq.eq(max_seq)) + .all(&self.db) + .await?; + + if items.len() > 1 { + for item in items.iter().skip(1) { + backfill_items::Entity::delete_by_id(item.id) + .exec(&self.db) + .await?; + } + } + + // Mark remaining row as backfilled so future backfilling can start above this sequence number. + backfill_items::Entity::update_many() + .col_expr(backfill_items::Column::Backfilled, Expr::value(true)) + .filter(backfill_items::Column::Tree.eq(tree)) + .exec(&self.db) + .await?; + + // Clear the `force_chk` flag if it was set. + self.clear_force_chk_flag(tree).await?; + + // Debug. + let test_items = backfill_items::Entity::find() + .filter(backfill_items::Column::Tree.eq(tree)) + .all(&self.db) + .await?; + println!("Count of items after delete: {}", test_items.len()); + for item in test_items { + println!("Seq ID {}", item.seq); + } + + Ok(()) + } +} + +fn serialize_transaction<'a>( + mut builder: FlatBufferBuilder<'a>, + meta: &UiTransactionStatusMeta, + ui_raw_message: &UiRawMessage, + slot: u64, +) -> Result, IngesterError> { + // Serialize account keys. + let account_keys = &ui_raw_message.account_keys; + let account_keys_len = account_keys.len(); + + let account_keys = if account_keys_len > 0 { + let mut account_keys_fb_vec = Vec::with_capacity(account_keys_len); + for key in account_keys.iter() { + let key = Pubkey::from_str(key) + .map_err(|e| IngesterError::SerializatonError(e.to_string()))?; + let key = builder.create_vector(&key.to_bytes()); + let pubkey = transaction_info::Pubkey::create( + &mut builder, + &transaction_info::PubkeyArgs { key: Some(key) }, + ); + account_keys_fb_vec.push(pubkey); + } + Some(builder.create_vector(&account_keys_fb_vec)) + } else { + None + }; + + // Serialize log messages. + let log_messages = if let Some(log_messages) = meta.log_messages.as_ref() { + let mut log_messages_fb_vec = Vec::with_capacity(log_messages.len()); + for message in log_messages { + log_messages_fb_vec.push(builder.create_string(&message)); + } + Some(builder.create_vector(&log_messages_fb_vec)) + } else { + None + }; + + // Serialize inner instructions. + let inner_instructions = if let Some(inner_instructions_vec) = meta.inner_instructions.as_ref() + { + let mut overall_fb_vec = Vec::with_capacity(inner_instructions_vec.len()); + for inner_instructions in inner_instructions_vec.iter() { + let index = inner_instructions.index; + let mut instructions_fb_vec = Vec::with_capacity(inner_instructions.instructions.len()); + for ui_instruction in inner_instructions.instructions.iter() { + if let Compiled(ui_compiled_instruction) = ui_instruction { + let program_id_index = ui_compiled_instruction.program_id_index; + let accounts = Some(builder.create_vector(&ui_compiled_instruction.accounts)); + let data = bs58::decode(&ui_compiled_instruction.data) + .into_vec() + .map_err(|e| IngesterError::SerializatonError(e.to_string()))?; + let data = Some(builder.create_vector(&data)); + instructions_fb_vec.push(transaction_info::CompiledInstruction::create( + &mut builder, + &transaction_info::CompiledInstructionArgs { + program_id_index, + accounts, + data, + }, + )); + } + } + + let instructions = Some(builder.create_vector(&instructions_fb_vec)); + overall_fb_vec.push(transaction_info::InnerInstructions::create( + &mut builder, + &transaction_info::InnerInstructionsArgs { + index, + instructions, + }, + )) + } + + Some(builder.create_vector(&overall_fb_vec)) + } else { + None + }; + + // Serialize outer instructions. + let outer_instructions = &ui_raw_message.instructions; + let outer_instructions = if outer_instructions.len() > 0 { + let mut instructions_fb_vec = Vec::with_capacity(outer_instructions.len()); + for ui_compiled_instruction in outer_instructions.iter() { + let program_id_index = ui_compiled_instruction.program_id_index; + let accounts = Some(builder.create_vector(&ui_compiled_instruction.accounts)); + let data = bs58::decode(&ui_compiled_instruction.data) + .into_vec() + .map_err(|e| IngesterError::SerializatonError(e.to_string()))?; + let data = Some(builder.create_vector(&data)); + instructions_fb_vec.push(transaction_info::CompiledInstruction::create( + &mut builder, + &transaction_info::CompiledInstructionArgs { + program_id_index, + accounts, + data, + }, + )); + } + Some(builder.create_vector(&instructions_fb_vec)) + } else { + None + }; + + // Serialize everything into Transaction Info table. + let seen_at = Utc::now(); + let transaction_info = TransactionInfo::create( + &mut builder, + &TransactionInfoArgs { + is_vote: false, + account_keys, + log_messages, + inner_instructions, + outer_instructions, + slot, + seen_at: seen_at.timestamp_millis(), + slot_index: None + }, + ); + + // Finalize buffer and return to caller. + builder.finish(transaction_info, None); + Ok(builder) +} diff --git a/nft_ingester/src/error/mod.rs b/nft_ingester/src/error/mod.rs new file mode 100644 index 000000000..1413dc74e --- /dev/null +++ b/nft_ingester/src/error/mod.rs @@ -0,0 +1,75 @@ +use { + crate::BgTask, + messenger::error::MessengerError, + sea_orm::{DbErr, TransactionError}, + thiserror::Error, + tokio::sync::mpsc::error::SendError, +}; + +#[derive(Error, Debug)] +pub enum IngesterError { + #[error("ChangeLog Event Malformed")] + ChangeLogEventMalformed, + #[error("Compressed Asset Event Malformed")] + CompressedAssetEventMalformed, + #[error("Error downloading batch files")] + BatchInitNetworkingError, + #[error("Error writing batch files")] + BatchInitIOError, + #[error("Storage listener error: ({msg})")] + StorageListenerError { msg: String }, + #[error("Storage Write Error {0}")] + StorageWriteError(String), + #[error("NotImplemented")] + NotImplemented, + #[error("Deserialization Error {0}")] + DeserializationError(String), + #[error("Task Manager Error {0}")] + TaskManagerError(String), + #[error("Missing or invalid configuration: ({msg})")] + ConfigurationError { msg: String }, + #[error("Error getting RPC data {0}")] + RpcGetDataError(String), + #[error("RPC returned data in unsupported format {0}")] + RpcDataUnsupportedFormat(String), + #[error("Data serializaton error {0}")] + SerializatonError(String), + #[error("Messenger error {0}")] + MessengerError(String), +} + +impl From for IngesterError { + fn from(_err: reqwest::Error) -> Self { + IngesterError::BatchInitNetworkingError + } +} + +impl From for IngesterError { + fn from(_err: std::io::Error) -> Self { + IngesterError::BatchInitIOError + } +} + +impl From for IngesterError { + fn from(e: DbErr) -> Self { + IngesterError::StorageWriteError(e.to_string()) + } +} + +impl From> for IngesterError { + fn from(e: TransactionError) -> Self { + IngesterError::StorageWriteError(e.to_string()) + } +} + +impl From>> for IngesterError { + fn from(err: SendError>) -> Self { + IngesterError::TaskManagerError(format!("Could not create task: {:?}", err.to_string())) + } +} + +impl From for IngesterError { + fn from(e: MessengerError) -> Self { + IngesterError::MessengerError(e.to_string()) + } +} diff --git a/nft_ingester/src/events/mod.rs b/nft_ingester/src/events/mod.rs new file mode 100644 index 000000000..d40052104 --- /dev/null +++ b/nft_ingester/src/events/mod.rs @@ -0,0 +1,27 @@ +use {crate::error::IngesterError, anchor_client::anchor_lang, base64}; + +pub fn handle_event( + data: String, +) -> Result { + let borsh_bytes = match base64::decode(&data) { + Ok(borsh_bytes) => borsh_bytes, + _ => { + return Err(IngesterError::ChangeLogEventMalformed); + } + }; + + let mut slice: &[u8] = &borsh_bytes[..]; + let disc: [u8; 8] = { + let mut disc = [0; 8]; + disc.copy_from_slice(&borsh_bytes[..8]); + slice = &slice[8..]; + disc + }; + if disc != T::discriminator() { + return Err(IngesterError::ChangeLogEventMalformed); + } + + let e: T = anchor_lang::AnchorDeserialize::deserialize(&mut slice) + .map_err(|_| IngesterError::ChangeLogEventMalformed)?; + Ok(e) +} diff --git a/nft_ingester/src/main.rs b/nft_ingester/src/main.rs new file mode 100644 index 000000000..302083d09 --- /dev/null +++ b/nft_ingester/src/main.rs @@ -0,0 +1,277 @@ +mod backfiller; +mod error; +mod events; +mod parsers; +mod tasks; +mod utils; + +use cadence_macros::statsd_time; +use chrono::Utc; +use { + crate::{ + backfiller::backfiller, + parsers::*, + utils::{order_instructions, parse_logs}, + }, + futures_util::TryFutureExt, + messenger::{Messenger, RedisMessenger, ACCOUNT_STREAM, TRANSACTION_STREAM}, + plerkle_serialization::account_info_generated::account_info::root_as_account_info, + plerkle_serialization::transaction_info_generated::transaction_info::root_as_transaction_info, + solana_sdk::pubkey::Pubkey, + sqlx::{self, postgres::PgPoolOptions, Pool, Postgres}, + tokio::sync::mpsc::UnboundedSender, + serde::Deserialize, + figment::{Figment, providers::Env}, + cadence_macros::{ + set_global_default, + statsd_count, + }, + cadence::{BufferedUdpMetricSink, QueuingMetricSink, StatsdClient}, + std::net::UdpSocket, +}; +use messenger::MessengerConfig; +use crate::error::IngesterError; +use crate::tasks::{BgTask, TaskManager}; + +async fn setup_manager<'a, 'b>( + mut manager: ProgramHandlerManager<'a>, + pool: Pool, + task_manager: UnboundedSender>, +) -> ProgramHandlerManager<'a> { + // Panic if thread cant be made for background tasks + let bubblegum_parser = BubblegumHandler::new(pool.clone(), task_manager); + let gummyroll_parser = GummyRollHandler::new(pool.clone()); + manager.register_parser(Box::new(bubblegum_parser)); + manager.register_parser(Box::new(gummyroll_parser)); + manager +} + +// Types and constants used for Figment configuration items. +pub type DatabaseConfig = figment::value::Dict; +pub const DATABASE_URL_KEY: &str = "url"; +pub const DATABASE_LISTENER_CHANNEL_KEY: &str = "listener_channel"; +pub type RpcConfig = figment::value::Dict; +pub const RPC_URL_KEY: &str = "url"; +pub const RPC_COMMITMENT_KEY: &str = "commitment"; + +// Struct used for Figment configuration items. +#[derive(Deserialize, PartialEq, Debug, Clone)] +pub struct IngesterConfig { + pub database_config: DatabaseConfig, + pub messenger_config: MessengerConfig, + pub rpc_config: RpcConfig, + pub metrics_port: u16, + pub metrics_host: String, +} + +fn setup_metrics(config: &IngesterConfig) { + let uri = config.metrics_host.clone(); + let port = config.metrics_port.clone(); + let socket = UdpSocket::bind("0.0.0.0:0").unwrap(); + socket.set_nonblocking(true).unwrap(); + let host = (uri, port); + let udp_sink = BufferedUdpMetricSink::from(host, socket).unwrap(); + let queuing_sink = QueuingMetricSink::from(udp_sink); + let client = StatsdClient::from_sink("das_ingester", queuing_sink); + set_global_default(client); +} + +#[tokio::main] +async fn main() { + // Read config. + println!("Starting DASgester"); + let config: IngesterConfig = Figment::new() + .join(Env::prefixed("INGESTER_")) + .extract() + .map_err(|config_error| IngesterError::ConfigurationError { + msg: format!("{}", config_error), + }) + .unwrap(); + // Get database config. + let url = config + .database_config + .get(&*DATABASE_URL_KEY) + .and_then(|u| u.clone().into_string()) + .ok_or(IngesterError::ConfigurationError { + msg: format!("Database connection string missing: {}", DATABASE_URL_KEY), + }) + .unwrap(); + // Setup Postgres. + let mut tasks = vec![]; + let pool = PgPoolOptions::new() + .max_connections(5) + .connect(&url) + .await + .unwrap(); + let background_task_manager = + TaskManager::new("background-tasks".to_string(), pool.clone()).unwrap(); + // Service streams as separate concurrent processes. + println!("Setting up tasks"); + setup_metrics(&config); + tasks.push(service_transaction_stream::(pool.clone(), background_task_manager.get_sender(), config.messenger_config.clone()).await); + statsd_count!("ingester.startup", 1); + + tasks.push(backfiller::(pool.clone(), config.clone()).await); + // Wait for ctrl-c. + match tokio::signal::ctrl_c().await { + Ok(()) => {} + Err(err) => { + println!("Unable to listen for shutdown signal: {}", err); + // We also shut down in case of error. + } + } + + // Kill all tasks. + for task in tasks { + task.abort(); + } +} + +async fn service_transaction_stream( + pool: Pool, + tasks: UnboundedSender>, + messenger_config: MessengerConfig, +) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + let mut manager = ProgramHandlerManager::new(); + + manager = setup_manager(manager, pool, tasks).await; + let mut messenger = T::new(messenger_config).await.unwrap(); + println!("Setting up transaction listener"); + loop { + // This call to messenger.recv() blocks with no timeout until + // a message is received on the stream. + if let Ok(data) = messenger.recv(TRANSACTION_STREAM).await { + handle_transaction(&manager, data).await; + } + } + }) +} + +async fn service_account_stream( + pool: Pool, + tasks: UnboundedSender>, + messenger_config: MessengerConfig, +) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + let mut manager = ProgramHandlerManager::new(); + let task_manager = TaskManager::new("background-tasks".to_string(), pool.clone()).unwrap(); + manager = setup_manager(manager, pool, tasks).await; + let mut messenger = T::new(messenger_config).await.unwrap(); + println!("Setting up account listener"); + loop { + // This call to messenger.recv() blocks with no timeout until + // a message is received on the stream. + if let Ok(data) = messenger.recv(ACCOUNT_STREAM).await { + handle_account(&manager, data).await + } + } + }) +} + +async fn handle_account(manager: &ProgramHandlerManager<'static>, data: Vec<(i64, &[u8])>) { + for (_message_id, data) in data { + // Get root of account info flatbuffers object. + let account_update = match root_as_account_info(data) { + Err(err) => { + println!("Flatbuffers AccountInfo deserialization error: {err}"); + continue; + } + Ok(account_update) => account_update, + }; + let program_id = account_update.owner(); + let parser = manager.match_program(program_id.unwrap()); + statsd_count!("ingester.account_update_seen", 1); + match parser { + Some(p) if p.config().responds_to_account == true => { + let _ = p.handle_account(&account_update).map_err(|e| { + println!("Error in instruction handling {:?}", e); + e + }); + } + _ => { + println!( + "Program Handler not found for program id {:?}", + program_id.map(|p| Pubkey::new(p)) + ); + } + } + } +} + +async fn handle_transaction(manager: &ProgramHandlerManager<'static>, data: Vec<(i64, &[u8])>) { + for (message_id, data) in data { + println!("RECV"); + //TODO -> Dedupe the stream, the stream could have duplicates as a way of ensuring fault tolerance if one validator node goes down. + // Possible solution is dedup on the plerkle side but this doesnt follow our principle of getting messages out of the validator asd fast as possible. + // Consider a Messenger Implementation detail the deduping of whats in this stream so that + // 1. only 1 ingest instance picks it up, two the stream coming out of the ingester can be considered deduped + + // Get root of transaction info flatbuffers object. + let transaction = match root_as_transaction_info(data) { + Err(err) => { + println!("Flatbuffers TransactionInfo deserialization error: {err}"); + continue; + } + Ok(transaction) => transaction, + }; + if let Some(si) = transaction.slot_index() { + let slt_idx = format!("{}-{}", transaction.slot(), si); + statsd_count!("ingester.transaction_event_seen", 1, "slot-idx" => &slt_idx); + } + let seen_at = Utc::now(); + statsd_time!("ingester.bus_ingest_time", (seen_at.timestamp_millis() - transaction.seen_at()) as u64); + // Get account keys flatbuffers object. + let keys = match transaction.account_keys() { + None => { + println!("Flatbuffers account_keys missing"); + continue; + } + Some(keys) => keys, + }; + // Update metadata associated with the programs that store data in leaves + let instructions = order_instructions(&transaction); + let parsed_logs = parse_logs(transaction.log_messages()).unwrap(); + for ((outer_ix, inner_ix), parsed_log) in std::iter::zip(instructions, parsed_logs) { + // Sanity check that instructions and logs were parsed correctly + assert_eq!( + outer_ix.0.key().unwrap(), + parsed_log.0.to_bytes(), + "expected {:?}, but program log was {:?}", + outer_ix.0, + parsed_log.0 + ); + + let (program, instruction) = outer_ix; + let program_id = program.key().unwrap(); + let parser = manager.match_program(program_id); + let str_program_id = bs58::encode(program_id).into_string(); + match parser { + Some(p) if p.config().responds_to_instruction == true => { + let _ = p + .handle_instruction(&InstructionBundle { + message_id, + txn_id: "".to_string(), + instruction, + inner_ix, + keys, + instruction_logs: parsed_log.1, + slot: transaction.slot(), + }) + .await + .map_err(|e| { + // Just for logging + println!("Error in instruction handling onr program {} {:?}", str_program_id, e); + e + }); + let finished_at = Utc::now(); + statsd_time!("ingester.ix_process_time", (finished_at.timestamp_millis() - transaction.seen_at()) as u64, "program_id" => &str_program_id); + } + _ => { + println!("Program Handler not found for program id {:?}", program); + } + } + } + } +} +// Associates logs with the given program ID diff --git a/nft_ingester/src/parsers/bubblegum.rs b/nft_ingester/src/parsers/bubblegum.rs new file mode 100644 index 000000000..da6e5c92e --- /dev/null +++ b/nft_ingester/src/parsers/bubblegum.rs @@ -0,0 +1,648 @@ +use { + crate::{ + error::IngesterError, + events::handle_event, + get_gummy_roll_events, + parsers::{InstructionBundle, ProgramHandler, ProgramHandlerConfig}, + save_changelog_events, + tasks::BgTask, + utils::bytes_from_fb_table, + utils::filter_events_from_logs, + }, + anchor_client::anchor_lang::{self, prelude::Pubkey, AnchorDeserialize}, + async_trait::async_trait, + bubblegum::state::{ + leaf_schema::{LeafSchema, LeafSchemaEvent, Version}, + NFTDecompressionEvent, + }, + digital_asset_types::{ + adapter::{TokenStandard, UseMethod, Uses}, + dao::{ + asset, asset_authority, asset_creators, asset_data, asset_grouping, + sea_orm_active_enums::{ChainMutability, Mutability, OwnerType, RoyaltyTargetType}, + }, + json::ChainDataV1, + }, + flatbuffers::{ForwardsUOffset, Vector}, + lazy_static::lazy_static, + num_traits::FromPrimitive, + plerkle_serialization::transaction_info_generated::transaction_info::{self}, + sea_orm::{ + entity::*, query::*, sea_query::OnConflict, DatabaseConnection, DatabaseTransaction, + DbBackend, DbErr, JsonValue, SqlxPostgresConnector, TransactionTrait, + }, + serde_json, solana_sdk, + solana_sdk::pubkeys, + sqlx::{self, Pool, Postgres}, + std::fmt::{Display, Formatter}, + tokio::sync::mpsc::UnboundedSender, +}; + +pubkeys!( + BubblegumProgramID, + "BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY" +); + +pub struct BubblegumHandler { + id: Pubkey, + storage: DatabaseConnection, + task_sender: UnboundedSender>, +} + +pub struct DownloadMetadata { + asset_data_id: i64, + uri: String, +} + +impl Display for DownloadMetadata { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "DownloadMetadata from {} for {}", + self.uri, self.asset_data_id + ) + } +} + +#[async_trait] +impl BgTask for DownloadMetadata { + async fn task(&self, db: &DatabaseConnection) -> Result<(), IngesterError> { + let body: serde_json::Value = reqwest::get(self.uri.clone()) // Need to check for malicious sites ? + .await? + .json() + .await?; + let model = asset_data::ActiveModel { + id: Unchanged(self.asset_data_id), + metadata: Set(body), + ..Default::default() + }; + asset_data::Entity::update(model) + .filter(asset_data::Column::Id.eq(self.asset_data_id)) + .exec(db) + .await + .map(|_| ()) + .map_err(|db| { + IngesterError::TaskManagerError(format!( + "Database error with {}, error: {}", + self, db + )) + }) + } +} + +#[async_trait] +impl ProgramHandler for BubblegumHandler { + fn id(&self) -> Pubkey { + self.id + } + + fn config(&self) -> &ProgramHandlerConfig { + lazy_static! { + static ref CONFIG: ProgramHandlerConfig = ProgramHandlerConfig { + responds_to_instruction: true, + responds_to_account: false + }; + } + return &CONFIG; + } + + async fn handle_instruction(&self, bundle: &InstructionBundle) -> Result<(), IngesterError> { + handle_bubblegum_instruction( + &bundle.instruction, + bundle.slot, + &bundle.instruction_logs, + &bundle.keys, + &self.storage, + &self.task_sender, + ) + .await + } +} + +impl BubblegumHandler { + pub fn new(pool: Pool, task_queue: UnboundedSender>) -> Self { + BubblegumHandler { + id: BubblegumProgramID(), + task_sender: task_queue, + storage: SqlxPostgresConnector::from_sqlx_postgres_pool(pool), + } + } +} + +fn get_leaf_event(logs: &Vec<&str>) -> Result { + get_bubblegum_event(logs) +} + +fn get_decompress_event(logs: &Vec<&str>) -> Result { + get_bubblegum_event(logs) +} + +fn get_bubblegum_event( + logs: &Vec<&str>, +) -> Result { + let event_logs = filter_events_from_logs(logs); + if event_logs.is_err() { + println!("Error finding event logs in bubblegum logs"); + return Err(IngesterError::CompressedAssetEventMalformed); + } + + let mut found_event: Option = None; + for event in event_logs.unwrap() { + let event = handle_event::(event); + if event.is_ok() { + found_event = event.ok() + } + } + found_event.ok_or(IngesterError::CompressedAssetEventMalformed) +} + +async fn tree_change_only<'a>( + db: &DatabaseConnection, + slot: u64, + logs: &Vec<&'a str>, +) -> Result<(), IngesterError> { + let gummy_roll_events = get_gummy_roll_events(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + save_changelog_events(gummy_roll_events, slot, txn) + .await + .map(|_| ()) + }) + }) + .await + .map_err(Into::into) +} + +async fn update_asset( + txn: &DatabaseTransaction, + id: Vec, + seq: Option, + model: asset::ActiveModel, +) -> Result<(), IngesterError> { + let update_one = if let Some(seq) = seq { + asset::Entity::update(model) + .filter(asset::Column::Id.eq(id)) + .filter(asset::Column::Seq.lte(seq)) + } else { + asset::Entity::update(model).filter(asset::Column::Id.eq(id)) + }; + + match update_one.exec(txn).await { + Ok(_) => Ok(()), + Err(err) => match err { + DbErr::RecordNotFound(ref s) => { + if s.find("None of the database rows are affected") != None { + Ok(()) + } else { + Err(IngesterError::from(err)) + } + } + _ => Err(IngesterError::from(err)), + }, + } +} + +async fn handle_bubblegum_instruction<'a, 'b, 't>( + instruction: &'a transaction_info::CompiledInstruction<'a>, + slot: u64, + logs: &Vec<&'a str>, + keys: &Vector<'b, ForwardsUOffset>>, + db: &DatabaseConnection, + task_manager: &UnboundedSender>, +) -> Result<(), IngesterError> { + let ix_type = bubblegum::get_instruction_type(instruction.data().unwrap()); + match ix_type { + bubblegum::InstructionName::Transfer => { + println!("BGUM: Transfer"); + let gummy_roll_events = get_gummy_roll_events(logs)?; + let leaf_event = get_leaf_event(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + let seq = save_changelog_events(gummy_roll_events, slot, txn) + .await? + .iter() + .max() + .map(|v| *v) + .ok_or(IngesterError::ChangeLogEventMalformed)?; + match leaf_event.schema { + LeafSchema::V1 { + id, + delegate, + owner, + .. + } => { + let id_bytes = id.to_bytes().to_vec(); + let delegate = if owner == delegate { + None + } else { + Some(delegate.to_bytes().to_vec()) + }; + let owner_bytes = owner.to_bytes().to_vec(); + let asset_to_update = asset::ActiveModel { + id: Unchanged(id_bytes.clone()), + leaf: Set(Some(leaf_event.schema.to_node().to_vec())), + delegate: Set(delegate), + owner: Set(owner_bytes), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + update_asset(txn, id_bytes, Some(seq), asset_to_update).await + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + } + bubblegum::InstructionName::Burn => { + println!("BGUM: Burn"); + let gummy_roll_events = get_gummy_roll_events(logs)?; + let leaf_event = get_leaf_event(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + let _seq = save_changelog_events(gummy_roll_events, slot, txn) + .await? + .iter() + .max() + .map(|v| *v) + .ok_or(IngesterError::ChangeLogEventMalformed)?; + match leaf_event.schema { + LeafSchema::V1 { id, .. } => { + let id_bytes = id.to_bytes().to_vec(); + let asset_to_update = asset::ActiveModel { + id: Unchanged(id_bytes.clone()), + burnt: Set(true), + ..Default::default() + }; + // Don't send sequence number with this update, because we will always + // run this update even if it's from a backfill/replay. + update_asset(txn, id_bytes, None, asset_to_update).await + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + } + bubblegum::InstructionName::Delegate => { + println!("BGUM: Delegate"); + let gummy_roll_events = get_gummy_roll_events(logs)?; + let leaf_event = get_leaf_event(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + let seq = save_changelog_events(gummy_roll_events, slot, txn) + .await? + .iter() + .max() + .map(|v| *v) + .ok_or(IngesterError::ChangeLogEventMalformed)?; + match leaf_event.schema { + LeafSchema::V1 { + id, + delegate, + owner, + .. + } => { + let id_bytes = id.to_bytes().to_vec(); + let delegate = if owner == delegate { + None + } else { + Some(delegate.to_bytes().to_vec()) + }; + let owner_bytes = owner.to_bytes().to_vec(); + let asset_to_update = asset::ActiveModel { + id: Unchanged(id_bytes.clone()), + leaf: Set(Some(leaf_event.schema.to_node().to_vec())), + delegate: Set(delegate), + owner: Set(owner_bytes), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + update_asset(txn, id_bytes, Some(seq), asset_to_update).await + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + } + bubblegum::InstructionName::MintV1 => { + println!("BGUM: MINT"); + let gummy_roll_events = get_gummy_roll_events(logs)?; + let leaf_event = get_leaf_event(logs)?; + let data = instruction.data().unwrap()[8..].to_owned(); + let data_buf = &mut data.as_slice(); + let ix: bubblegum::instruction::MintV1 = + bubblegum::instruction::MintV1::deserialize(data_buf).unwrap(); + let accounts = instruction.accounts().unwrap(); + let update_authority = bytes_from_fb_table(keys, accounts[0] as usize); + let owner = bytes_from_fb_table(keys, accounts[4] as usize); + let delegate = bytes_from_fb_table(keys, accounts[5] as usize); + let merkle_slab = bytes_from_fb_table(keys, accounts[6] as usize); + let metadata = ix.message.clone(); + let asset_data_id = db + .transaction::<_, i64, IngesterError>(|txn| { + Box::pin(async move { + let seq = save_changelog_events(gummy_roll_events, slot, txn) + .await? + .iter() + .max() + .map(|v| *v) + .ok_or(IngesterError::ChangeLogEventMalformed)?; + match leaf_event.schema { + LeafSchema::V1 { nonce, id, .. } => { + // Printing metadata instruction arguments for debugging + println!( + "\tMetadata info: {} {} {} {} {}", + id.to_string(), + &metadata.name, + metadata.seller_fee_basis_points, + metadata.primary_sale_happened, + metadata.is_mutable, + ); + + // Insert into `asset_data` table. Note that if a transaction is + // replayed, this will insert the data again resulting in a + // duplicate entry. + let chain_data = ChainDataV1 { + name: metadata.name, + symbol: metadata.symbol, + edition_nonce: metadata.edition_nonce, + primary_sale_happened: metadata.primary_sale_happened, + token_standard: metadata + .token_standard + .and_then(|ts| TokenStandard::from_u8(ts as u8)), + uses: metadata.uses.map(|u| Uses { + use_method: UseMethod::from_u8(u.use_method as u8).unwrap(), + remaining: u.remaining, + total: u.total, + }), + }; + let chain_data_json = + serde_json::to_value(chain_data).map_err(|e| { + IngesterError::DeserializationError(e.to_string()) + })?; + let chain_mutability = match metadata.is_mutable { + true => ChainMutability::Mutable, + false => ChainMutability::Immutable, + }; + + let data = asset_data::ActiveModel { + chain_data_mutability: Set(chain_mutability), + schema_version: Set(1), + chain_data: Set(chain_data_json), + metadata_url: Set(metadata.uri), + metadata: Set(JsonValue::String("processing".to_string())), + metadata_mutability: Set(Mutability::Mutable), + ..Default::default() + } + .insert(txn) + .await?; + + // Insert into `asset` table. + let delegate = if owner == delegate { + None + } else { + Some(delegate) + }; + let model = asset::ActiveModel { + id: Set(id.to_bytes().to_vec()), + owner: Set(owner), + owner_type: Set(OwnerType::Single), + delegate: Set(delegate), + frozen: Set(false), + supply: Set(1), + supply_mint: Set(None), + compressed: Set(true), + compressible: Set(false), + tree_id: Set(Some(merkle_slab)), + specification_version: Set(1), + nonce: Set(nonce as i64), + leaf: Set(Some(leaf_event.schema.to_node().to_vec())), + royalty_target_type: Set(RoyaltyTargetType::Creators), + royalty_target: Set(None), + royalty_amount: Set(metadata.seller_fee_basis_points as i32), //basis points + chain_data_id: Set(Some(data.id)), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + + // Do not attempt to modify any existing values: + // `ON CONFLICT ('id') DO NOTHING`. + let query = asset::Entity::insert(model) + .on_conflict( + OnConflict::columns([asset::Column::Id]) + .do_nothing() + .to_owned(), + ) + .build(DbBackend::Postgres); + txn.execute(query).await?; + + // Insert into `asset_creators` table. + if metadata.creators.len() > 0 { + let mut creators = Vec::with_capacity(metadata.creators.len()); + for c in metadata.creators { + creators.push(asset_creators::ActiveModel { + asset_id: Set(id.to_bytes().to_vec()), + creator: Set(c.address.to_bytes().to_vec()), + share: Set(c.share as i32), + verified: Set(c.verified), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }); + } + + // Do not attempt to modify any existing values: + // `ON CONFLICT ('asset_id') DO NOTHING`. + let query = asset_creators::Entity::insert_many(creators) + .on_conflict( + OnConflict::columns([asset_creators::Column::AssetId]) + .do_nothing() + .to_owned(), + ) + .build(DbBackend::Postgres); + txn.execute(query).await?; + + // Insert into `asset_authority` table. + let model = asset_authority::ActiveModel { + asset_id: Set(id.to_bytes().to_vec()), + authority: Set(update_authority), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + + // Do not attempt to modify any existing values: + // `ON CONFLICT ('asset_id') DO NOTHING`. + let query = asset_authority::Entity::insert(model) + .on_conflict( + OnConflict::columns([asset_authority::Column::AssetId]) + .do_nothing() + .to_owned(), + ) + .build(DbBackend::Postgres); + txn.execute(query).await?; + + // Insert into `asset_grouping` table. + if let Some(c) = metadata.collection { + if c.verified { + let model = asset_grouping::ActiveModel { + asset_id: Set(id.to_bytes().to_vec()), + group_key: Set("collection".to_string()), + group_value: Set(c.key.to_string()), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + + // Do not attempt to modify any existing values: + // `ON CONFLICT ('asset_id') DO NOTHING`. + let query = asset_grouping::Entity::insert(model) + .on_conflict( + OnConflict::columns([ + asset_grouping::Column::AssetId, + ]) + .do_nothing() + .to_owned(), + ) + .build(DbBackend::Postgres); + txn.execute(query).await?; + } + } + } + Ok(data.id) + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + let task = Some(DownloadMetadata { + asset_data_id, + uri: ix.message.uri.clone(), + }); + task_manager.send(Box::new(task.unwrap()))?; + } + bubblegum::InstructionName::Redeem => { + println!("BGUM: Redeem"); + let gummy_roll_events = get_gummy_roll_events(logs)?; + let leaf_event = get_leaf_event(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + let seq = save_changelog_events(gummy_roll_events, slot, txn) + .await? + .iter() + .max() + .map(|v| *v) + .ok_or(IngesterError::ChangeLogEventMalformed)?; + match leaf_event.schema { + LeafSchema::V1 { + id, + delegate, + owner, + .. + } => { + let id_bytes = id.to_bytes().to_vec(); + let delegate = if owner == delegate { + None + } else { + Some(delegate.to_bytes().to_vec()) + }; + let owner_bytes = owner.to_bytes().to_vec(); + let asset_to_update = asset::ActiveModel { + id: Unchanged(id_bytes.clone()), + leaf: Set(Some(vec![0; 32])), + delegate: Set(delegate), + owner: Set(owner_bytes), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + update_asset(txn, id_bytes, Some(seq), asset_to_update).await + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + } + bubblegum::InstructionName::CancelRedeem => { + println!("BGUM: Cancel Redeem"); + let gummy_roll_events = get_gummy_roll_events(logs)?; + let leaf_event = get_leaf_event(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + let seq = save_changelog_events(gummy_roll_events, slot, txn) + .await? + .iter() + .max() + .map(|v| *v) + .ok_or(IngesterError::ChangeLogEventMalformed)?; + match leaf_event.schema { + LeafSchema::V1 { + id, + delegate, + owner, + .. + } => { + let id_bytes = id.to_bytes().to_vec(); + let delegate = if owner == delegate { + None + } else { + Some(delegate.to_bytes().to_vec()) + }; + let owner_bytes = owner.to_bytes().to_vec(); + let asset_to_update = asset::ActiveModel { + id: Unchanged(id_bytes.clone()), + leaf: Set(Some(leaf_event.schema.to_node().to_vec())), + delegate: Set(delegate), + owner: Set(owner_bytes), + seq: Set(seq as i64), // gummyroll seq + ..Default::default() + }; + update_asset(txn, id_bytes, Some(seq), asset_to_update).await + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + } + bubblegum::InstructionName::DecompressV1 => { + println!("BGUM: Decompress"); + let decompress_event = get_decompress_event(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { + match decompress_event.version { + Version::V1 => { + let id_bytes = decompress_event.id.to_bytes().to_vec(); + let model = asset::ActiveModel { + id: Unchanged(id_bytes.clone()), + leaf: Set(None), + compressed: Set(false), + compressible: Set(false), + supply: Set(1), + supply_mint: Set(Some(id_bytes.clone())), + ..Default::default() + }; + + // After the decompress instruction runs, the asset is no longer managed + // by Bubblegum and Gummyroll, so there will not be any other instructions + // after this one. + // + // Do not run this command if the asset is already marked as + // decompressed. + let query = asset::Entity::update(model) + .filter(asset::Column::Id.eq(id_bytes)) + .filter(asset::Column::Compressed.eq(true)) + .build(DbBackend::Postgres); + + txn.execute(query).await.map(|_| ()).map_err(Into::into) + } + _ => Err(IngesterError::NotImplemented), + } + }) + }) + .await?; + } + _ => println!("Bubblegum: Not Implemented Instruction"), + } + Ok(()) +} diff --git a/nft_ingester/src/parsers/gummyroll.rs b/nft_ingester/src/parsers/gummyroll.rs new file mode 100644 index 000000000..83eab5ded --- /dev/null +++ b/nft_ingester/src/parsers/gummyroll.rs @@ -0,0 +1,202 @@ +use { + crate::{ + error::IngesterError, events::handle_event, utils::filter_events_from_logs, + InstructionBundle, ProgramHandler, ProgramHandlerConfig, + }, + async_trait::async_trait, + digital_asset_types::dao::{backfill_items, cl_items}, + gummyroll::state::ChangeLogEvent, + lazy_static::lazy_static, + sea_orm::{ + entity::*, query::*, sea_query::OnConflict, DatabaseConnection, DatabaseTransaction, + DbBackend, SqlxPostgresConnector, TransactionTrait, + }, + serde::Deserialize, + solana_sdk::pubkey::Pubkey, + solana_sdk::pubkeys, + sqlx::{self, Pool, Postgres}, +}; + +#[derive(Debug, Deserialize)] +pub struct CLRecord { + node_idx: u32, + level: u32, + seq: u32, + hash: String, +} + +pubkeys!( + GummyRollProgramID, + "GRoLLzvxpxxu2PGNJMMeZPyMxjAUH9pKqxGXV9DGiceU" +); + +pub struct GummyRollHandler { + id: Pubkey, + storage: DatabaseConnection, +} + +#[async_trait] +impl ProgramHandler for GummyRollHandler { + fn id(&self) -> Pubkey { + self.id + } + + fn config(&self) -> &ProgramHandlerConfig { + lazy_static! { + static ref CONFIG: ProgramHandlerConfig = ProgramHandlerConfig { + responds_to_instruction: true, + responds_to_account: false + }; + } + return &CONFIG; + } + + async fn handle_instruction(&self, bundle: &InstructionBundle) -> Result<(), IngesterError> { + let _ = handle_gummyroll_instruction(&bundle.instruction_logs, bundle.slot, &self.storage) + .await?; + Ok(()) + } +} + +impl GummyRollHandler { + pub fn new(pool: Pool) -> Self { + GummyRollHandler { + id: GummyRollProgramID(), + storage: SqlxPostgresConnector::from_sqlx_postgres_pool(pool), + } + } +} + +pub fn get_gummy_roll_events(logs: &Vec<&str>) -> Result, IngesterError> { + let change_log_event_vec = if let Ok(change_log_event_vec) = filter_events_from_logs(logs) { + change_log_event_vec + } else { + println!("Could not find emitted program data"); + return Err(IngesterError::ChangeLogEventMalformed); + }; + let mut events = vec![]; + // Parse each change log event found in logs + for event in change_log_event_vec { + if let Ok(change_log_event) = handle_event(event) { + events.push(change_log_event); + } else { + continue; + }; + } + Ok(events) +} + +pub async fn save_changelog_events( + gummy_roll_events: Vec, + slot: u64, + txn: &DatabaseTransaction, +) -> Result, IngesterError> { + let mut seq_nums = Vec::with_capacity(gummy_roll_events.len()); + for change_log_event in gummy_roll_events { + gummyroll_change_log_event_to_database(&change_log_event, slot, txn, false).await?; + + seq_nums.push(change_log_event.seq); + } + Ok(seq_nums) +} + +pub async fn handle_gummyroll_instruction( + logs: &Vec<&str>, + slot: u64, + db: &DatabaseConnection, +) -> Result, IngesterError> { + // map to owned vec to avoid static lifetime issues, instead of moving logs into Box + let events = get_gummy_roll_events(logs)?; + db.transaction::<_, _, IngesterError>(|txn| { + Box::pin(async move { save_changelog_events(events, slot, txn).await }) + }) + .await + .map_err(Into::into) +} + +fn node_idx_to_leaf_idx(index: i64, tree_height: u32) -> i64 { + index - 2i64.pow(tree_height) +} + +pub async fn gummyroll_change_log_event_to_database( + change_log_event: &ChangeLogEvent, + slot: u64, + txn: &DatabaseTransaction, + filling: bool, +) -> Result<(), IngesterError> { + let mut i: i64 = 0; + let depth = change_log_event.path.len() - 1; + let tree_id = change_log_event.id.as_ref(); + for p in change_log_event.path.iter() { + let node_idx = p.index as i64; + println!( + "seq {}, index {} level {}, node {:?}", + change_log_event.seq, + p.index, + i, + bs58::encode(p.node).into_string() + ); + let leaf_idx = if i == 0 { + Some(node_idx_to_leaf_idx(node_idx, depth as u32)) + } else { + None + }; + + let item = cl_items::ActiveModel { + tree: Set(tree_id.to_vec()), + level: Set(i), + node_idx: Set(node_idx), + hash: Set(p.node.as_ref().to_vec()), + seq: Set(change_log_event.seq as i64), + leaf_idx: Set(leaf_idx), + ..Default::default() + }; + i += 1; + let mut query = cl_items::Entity::insert(item) + .on_conflict( + OnConflict::columns([cl_items::Column::Tree, cl_items::Column::NodeIdx]) + .update_columns([cl_items::Column::Hash, cl_items::Column::Seq]) + .to_owned(), + ) + .build(DbBackend::Postgres); + if !filling { + query.sql = format!("{} WHERE excluded.seq > cl_items.seq", query.sql); + } + txn.execute(query) + .await + .map_err(|db_err| IngesterError::StorageWriteError(db_err.to_string()))?; + } + + // If and only if the entire path of nodes was inserted into the `cl_items` table, then insert + // a single row into the `backfill_items` table. This way if an incomplete path was inserted + // into `cl_items` due to an error, a gap will be created for the tree and the backfiller will + // fix it. + if i - 1 == depth as i64 { + // See if the tree already exists in the `backfill_items` table. + let rows = backfill_items::Entity::find() + .filter(backfill_items::Column::Tree.eq(tree_id)) + .limit(1) + .all(txn) + .await?; + + // If the tree does not exist in `backfill_items` and the sequence number is greater than 1, + // then we know we will need to backfill the tree from sequence number 1 up to the current + // sequence number. So in this case we set at flag to force checking the tree. + let force_chk = rows.len() == 0 && change_log_event.seq > 1; + + println!("Adding to backfill_items table at level {}", i - 1); + let item = backfill_items::ActiveModel { + tree: Set(tree_id.to_vec()), + seq: Set(change_log_event.seq as i64), + slot: Set(slot as i64), + force_chk: Set(Some(force_chk)), + backfilled: Set(Some(false)), + ..Default::default() + }; + + backfill_items::Entity::insert(item).exec(txn).await?; + } + + Ok(()) + //TODO -> set maximum size of path and break into multiple statements +} diff --git a/nft_ingester/src/parsers/mod.rs b/nft_ingester/src/parsers/mod.rs new file mode 100644 index 000000000..9398985ca --- /dev/null +++ b/nft_ingester/src/parsers/mod.rs @@ -0,0 +1,74 @@ +mod bubblegum; +mod gummyroll; + +pub use self::bubblegum::*; +pub use self::gummyroll::*; + +use { + crate::{error::IngesterError, utils::IxPair}, + async_trait::async_trait, + flatbuffers::{ForwardsUOffset, Vector}, + plerkle_serialization::{ + account_info_generated::account_info, + transaction_info_generated::transaction_info::{self, CompiledInstruction}, + }, + solana_sdk::pubkey::Pubkey, + std::collections::HashMap, +}; + +pub struct ProgramHandlerManager<'a> { + registered_parsers: HashMap>, +} + +impl<'a> ProgramHandlerManager<'a> { + pub fn new() -> Self { + ProgramHandlerManager { + registered_parsers: HashMap::new(), + } + } + + pub fn register_parser(&mut self, parser: Box) { + let id = parser.id(); + self.registered_parsers.insert(id, parser); + } + + pub fn match_program(&self, program_id: &[u8]) -> Option<&dyn ProgramHandler> { + self.registered_parsers + .get(&Pubkey::new(program_id)) + .map(|parser| parser.as_ref()) + } +} + +pub struct ProgramHandlerConfig { + pub responds_to_account: bool, + pub responds_to_instruction: bool, +} + +pub struct InstructionBundle<'a, 'b> { + pub message_id: i64, + pub txn_id: String, + pub instruction: CompiledInstruction<'a>, + pub inner_ix: Option>>, + pub keys: Vector<'b, ForwardsUOffset>>, + pub instruction_logs: Vec<&'b str>, + pub slot: u64, +} + +/// A abtraction over handling program updates, account +#[async_trait] +pub trait ProgramHandler: Sync + Send { + fn id(&self) -> Pubkey; + + fn config(&self) -> &ProgramHandlerConfig; + + async fn handle_instruction(&self, _bundle: &InstructionBundle) -> Result<(), IngesterError> { + Ok(()) + } + + async fn handle_account( + &self, + _account_info: &account_info::AccountInfo, + ) -> Result<(), IngesterError> { + Ok(()) + } +} diff --git a/nft_ingester/src/tasks/mod.rs b/nft_ingester/src/tasks/mod.rs new file mode 100644 index 000000000..92fa49e8a --- /dev/null +++ b/nft_ingester/src/tasks/mod.rs @@ -0,0 +1,52 @@ +use { + crate::error::IngesterError, + async_trait::async_trait, + sea_orm::{DatabaseConnection, SqlxPostgresConnector}, + sqlx::{Pool, Postgres}, + std::fmt::Display, + tokio::runtime::{Builder, Runtime}, + tokio::sync::mpsc::{self, UnboundedSender}, +}; + +#[async_trait] +pub trait BgTask: Send + Sync + Display { + async fn task(&self, db: &DatabaseConnection) -> Result<(), IngesterError>; +} + +pub struct TaskManager { + runtime: Runtime, + producer: UnboundedSender>, +} + +impl TaskManager { + pub fn new(name: String, pool: Pool) -> Result { + let runtime = Builder::new_multi_thread() + .enable_all() + .thread_name(name) + .build() + .map_err(|err| { + IngesterError::TaskManagerError(format!( + "Could not create tokio runtime: {:?}", + err + )) + })?; + + let (producer, mut receiver) = mpsc::unbounded_channel::>(); + let db = SqlxPostgresConnector::from_sqlx_postgres_pool(pool); + runtime.spawn(async move { + while let Some(data) = receiver.recv().await { + let task_res = data.task(&db).await; + match task_res { + Ok(_) => println!("{} completed", data), + Err(e) => println!("{} errored with {:?}", data, e), + } + } + }); + let tm = TaskManager { runtime, producer }; + Ok(tm) + } + + pub fn get_sender(&self) -> UnboundedSender> { + self.producer.clone() + } +} diff --git a/nft_ingester/src/utils/instructions.rs b/nft_ingester/src/utils/instructions.rs new file mode 100644 index 000000000..53b8c10ec --- /dev/null +++ b/nft_ingester/src/utils/instructions.rs @@ -0,0 +1,72 @@ +use { + flatbuffers::{ForwardsUOffset, Vector}, + plerkle_serialization::{ + transaction_info_generated::transaction_info, + transaction_info_generated::transaction_info::TransactionInfo, + }, +}; + +pub type IxPair<'a> = ( + transaction_info::Pubkey<'a>, + transaction_info::CompiledInstruction<'a>, +); +pub fn order_instructions<'a>( + transaction_info: &TransactionInfo<'a>, +) -> Vec<(IxPair<'a>, Option>>)> { + let mut ordered_ixs: Vec<(IxPair, Option>)> = vec![]; + // Get inner instructions. + let inner_ix_list = transaction_info.inner_instructions(); + + // Get outer instructions. + let outer_instructions = match transaction_info.outer_instructions() { + None => { + println!("outer instructions deserialization error"); + return ordered_ixs; + } + Some(instructions) => instructions, + }; + + // Get account keys. + let keys = match transaction_info.account_keys() { + None => { + println!("account_keys deserialization error"); + return ordered_ixs; + } + Some(keys) => keys, + }; + + for (i, instruction) in outer_instructions.iter().enumerate() { + let program_id = keys.get(instruction.program_id_index() as usize); + let program_id = program_id; + let outer: IxPair = (program_id, instruction); + + let inner: Option> = get_inner_ixs(inner_ix_list, i).map(|inner_ixs| { + let mut inner_list: Vec = vec![]; + for inner_ix_instance in inner_ixs.instructions().unwrap() { + let inner_program_id = keys.get(inner_ix_instance.program_id_index() as usize); + inner_list.push((inner_program_id, inner_ix_instance)) + } + inner_list + }); + ordered_ixs.push((outer, inner)); + } + + ordered_ixs +} + +fn get_inner_ixs<'a>( + inner_ixs: Option>>>, + outer_index: usize, +) -> Option> { + match inner_ixs { + Some(inner_ix_list) => { + for inner_ixs in inner_ix_list { + if inner_ixs.index() == (outer_index as u8) { + return Some(inner_ixs); + } + } + None + } + None => None, + } +} diff --git a/nft_ingester/src/utils/logs.rs b/nft_ingester/src/utils/logs.rs new file mode 100644 index 000000000..b5df5b6ea --- /dev/null +++ b/nft_ingester/src/utils/logs.rs @@ -0,0 +1,72 @@ +use { + flatbuffers::{ForwardsUOffset, Vector}, + lazy_static::lazy_static, + regex::{Captures, Regex}, + solana_sdk::pubkey::Pubkey, +}; + +pub fn filter_events_from_logs(log_messages: &Vec<&str>) -> Result, ()> { + lazy_static! { + static ref CLRE: Regex = Regex::new( + r"Program data: ((?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{3}=|[A-Za-z\d+/]{2}==)?$)" + ) + .unwrap(); + } + let mut events: Vec = vec![]; + + for line in log_messages { + let line_str = String::from(*line); + let captures = CLRE.captures(&line_str); + let b64raw = captures.and_then(|c| c.get(1)).map(|c| c.as_str()); + b64raw.map(|raw| events.push((raw).parse().unwrap())); + } + if events.is_empty() { + println!("No events captured!"); + Err(()) + } else { + Ok(events) + } +} + +pub fn parse_logs<'a>( + log_messages: Option>>, +) -> Result, u8)>, ()> { + lazy_static! { + static ref PLRE: Regex = Regex::new(r"Program (\w*) invoke \[(\d)\]").unwrap(); + } + let mut program_logs: Vec<(Pubkey, Vec<&str>, u8)> = vec![]; + + match log_messages { + Some(logs) => { + for log in logs { + let captures: Option = PLRE.captures(log); + let cap: Option<(Pubkey, u8)> = captures.and_then(|c| { + let program = c + .get(1) + .and_then(|prog| bs58::decode(&prog.as_str()).into_vec().ok()) + .map(|bytes| Pubkey::new(&bytes)); + let level: Option = c.get(2).and_then(|l| l.as_str().parse::().ok()); + if program.is_some() && level.is_some() { + return Some((program.unwrap(), level.unwrap())); + } + None + }); + + match cap { + Some((key, level)) if level == 1 => { + program_logs.push((key, vec![], level)); + } + _ => { + let last_program_log = program_logs.last_mut().unwrap(); + (*last_program_log).1.push(log); + } + } + } + Ok(program_logs) + } + None => { + println!("No logs found in transaction info!"); + Err(()) + } + } +} diff --git a/nft_ingester/src/utils/mod.rs b/nft_ingester/src/utils/mod.rs new file mode 100644 index 000000000..c5688e15c --- /dev/null +++ b/nft_ingester/src/utils/mod.rs @@ -0,0 +1,45 @@ +mod instructions; +mod logs; +mod storage; + +pub use instructions::*; +pub use logs::*; +pub use storage::*; + +use { + flatbuffers::{ForwardsUOffset, Vector}, + plerkle_serialization::transaction_info_generated::transaction_info, + solana_sdk::pubkey::Pubkey, +}; + +pub fn un_jank_message(hex_str: &String) -> String { + String::from_utf8(hex::decode(hex_str).unwrap()).unwrap() +} + +pub fn pubkey_from_fb_table( + keys: &Vector>, + index: usize, +) -> Pubkey { + let pubkey = keys.get(index); + Pubkey::new(pubkey.key().unwrap()) +} + +pub fn string_from_fb_table( + keys: &Vector>, + index: usize, +) -> String { + pubkey_from_fb_table(keys, index).to_string() +} + +pub fn bytes_from_fb_table<'a>( + keys: &Vector< + 'a, + ForwardsUOffset< + plerkle_serialization::transaction_info_generated::transaction_info::Pubkey<'a>, + >, + >, + index: usize, +) -> Vec { + let pubkey = keys.get(index); + pubkey.key().unwrap().to_vec() +} diff --git a/nft_ingester/src/utils/storage.rs b/nft_ingester/src/utils/storage.rs new file mode 100644 index 000000000..7662dcb15 --- /dev/null +++ b/nft_ingester/src/utils/storage.rs @@ -0,0 +1,24 @@ +use { + crate::error::IngesterError, + std::{fs::File, io::Write}, +}; + +#[derive(sqlx::FromRow, Clone, Debug)] +pub struct AppSpecificRev { + pub revision: i64, +} + +pub async fn write_assets_to_file( + uri: &str, + tree_id: &str, + key: &str, +) -> Result { + println!("Requesting to see arweave link for {}", key); + let fname = format!("{}-{}.csv", tree_id, key); + let body = reqwest::get(uri).await?.text().await?; + let mut file = File::create(&fname)?; + println!("{:?}", body.len()); + file.write_all(body.as_bytes())?; + println!("Wrote response to {}", &fname); + Ok(fname.to_string()) +} diff --git a/skaffold.yaml b/skaffold.yaml new file mode 100644 index 000000000..2d69e7da3 --- /dev/null +++ b/skaffold.yaml @@ -0,0 +1,53 @@ +apiVersion: skaffold/v2beta29 +kind: Config +build: + artifacts: + - context: . + image: public.ecr.aws/k2z7t6t6/metaplex-rpc-load-generator + docker: + dockerfile: Load.Dockerfile + - context: . + image: public.ecr.aws/k2z7t6t6/metaplex-rpc-api + docker: + dockerfile: Api.Dockerfile + - context: . + image: public.ecr.aws/k2z7t6t6/metaplex-rpc-ingest + docker: + dockerfile: Ingest.Dockerfile +deploy: + helm: + releases: + - name: das-ingester + chartPath: helm/ingest + artifactOverrides: + image: public.ecr.aws/k2z7t6t6/metaplex-rpc-ingest + loadImage: public.ecr.aws/k2z7t6t6/metaplex-rpc-load-generator + setValueTemplates: + ingest.db_url: "{{.DATABASE_URL}}" + ingest.redis_url: "{{.REDIS_URL}}" + metrics.data_dog_api_key: "{{.DATA_DOG_API}}" + loadSeed: "{{.LOAD_SEED}}" + valuesFiles: + - ./helm/ingest/values.yaml + - name: das-api + chartPath: helm/api + artifactOverrides: + image: public.ecr.aws/k2z7t6t6/metaplex-rpc-api + setValueTemplates: + api.db_url: "{{.DATABASE_URL}}" + api.redis_url: "{{.REDIS_URL}}" + metrics.data_dog_api_key: "{{.DATA_DOG_API}}" + valuesFiles: + - ./helm/api/values.yaml + flags: + upgrade: [ "--timeout","900s" ] + install: [ "--timeout","900s" ] +profiles: + - name: dev + patches: + - op: replace + path: /deploy/helm/releases/0/valuesFiles/0 + value: ./helm/values-testnet.yaml + - op: replace + path: /deploy/helm/releases/1/valuesFiles/1 + value: ./helm/values-testnet.yaml \ No newline at end of file